From 1884103cb4f54f475a8ab88ef5e31663a4bc3fdf Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 3 Jun 2020 08:47:36 -0700 Subject: [PATCH 001/146] grpc: added call to stream node movements using geo/xy and tests to validate usage, fixed potential exception when not setting session geo ref and using conversions --- daemon/core/api/grpc/client.py | 13 ++++- daemon/core/api/grpc/server.py | 34 +++++++++++++ daemon/core/location/geo.py | 4 +- daemon/proto/core/api/grpc/core.proto | 15 ++++++ daemon/tests/test_grpc.py | 70 ++++++++++++++++++++++++++- 5 files changed, 132 insertions(+), 4 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index a645c756..280b1cd8 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -5,7 +5,7 @@ gRpc client for interfacing with CORE, when gRPC mode is enabled. import logging import threading from contextlib import contextmanager -from typing import Any, Callable, Dict, Generator, List +from typing import Any, Callable, Dict, Generator, Iterable, List import grpc import netaddr @@ -571,6 +571,17 @@ class CoreGrpcClient: ) return self.stub.EditNode(request) + def move_nodes( + self, move_iterator: Iterable[core_pb2.MoveNodesRequest] + ) -> core_pb2.MoveNodesResponse: + """ + Stream node movements using the provided iterator. + + :param move_iterator: iterator for generating node movements + :return: move nodes response + """ + return self.stub.MoveNodes(move_iterator) + def delete_node(self, session_id: int, node_id: int) -> core_pb2.DeleteNodeResponse: """ Delete node from session. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 16da7e6b..972153e7 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -692,6 +692,40 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): node_proto = grpcutils.get_node_proto(session, node) return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces) + def MoveNodes( + self, request_iterator, context: ServicerContext + ) -> core_pb2.MoveNodesResponse: + """ + Stream node movements + + :param request_iterator: move nodes request iterator + :param context: context object + :return: move nodes response + """ + for request in request_iterator: + if not request.WhichOneof("move_type"): + raise CoreError("move nodes must provide a move type") + session = self.get_session(request.session_id, context) + node = self.get_node(session, request.node_id, context, NodeBase) + options = NodeOptions() + has_geo = request.HasField("geo") + if has_geo: + logging.info("has geo") + lat = request.geo.lat + lon = request.geo.lon + alt = request.geo.alt + options.set_location(lat, lon, alt) + else: + x = request.position.x + y = request.position.y + logging.info("has pos: %s,%s", x, y) + options.set_position(x, y) + session.edit_node(node.id, options) + source = request.source if request.source else None + if not has_geo: + session.broadcast_node(node, source=source) + return core_pb2.MoveNodesResponse() + def EditNode( self, request: core_pb2.EditNodeRequest, context: ServicerContext ) -> core_pb2.EditNodeResponse: diff --git a/daemon/core/location/geo.py b/daemon/core/location/geo.py index 1f78f329..4ff56dd6 100644 --- a/daemon/core/location/geo.py +++ b/daemon/core/location/geo.py @@ -31,7 +31,7 @@ class GeoLocation: CRS_WGS84, CRS_PROJ, always_xy=True ) self.to_geo = pyproj.Transformer.from_crs(CRS_PROJ, CRS_WGS84, always_xy=True) - self.refproj = (0.0, 0.0) + self.refproj = (0.0, 0.0, 0.0) self.refgeo = (0.0, 0.0, 0.0) self.refxyz = (0.0, 0.0, 0.0) self.refscale = 1.0 @@ -58,7 +58,7 @@ class GeoLocation: self.refxyz = (0.0, 0.0, 0.0) self.refgeo = (0.0, 0.0, 0.0) self.refscale = 1.0 - self.refproj = self.to_pixels.transform(self.refgeo[0], self.refgeo[1]) + self.refproj = self.to_pixels.transform(*self.refgeo) def pixels2meters(self, value: float) -> float: """ diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index b0ae6642..cdcd9686 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -61,6 +61,8 @@ service CoreApi { } rpc GetNodeTerminal (GetNodeTerminalRequest) returns (GetNodeTerminalResponse) { } + rpc MoveNodes (stream MoveNodesRequest) returns (MoveNodesResponse) { + } // link rpc rpc GetNodeLinks (GetNodeLinksRequest) returns (GetNodeLinksResponse) { @@ -446,6 +448,19 @@ message GetNodeTerminalResponse { string terminal = 1; } +message MoveNodesRequest { + int32 session_id = 1; + int32 node_id = 2; + string source = 3; + oneof move_type { + Position position = 4; + Geo geo = 5; + } +} + +message MoveNodesResponse { +} + message NodeCommandRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 47cfe744..128863b4 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -18,7 +18,7 @@ from core.api.tlv.dataconversion import ConfigShim from core.api.tlv.enumerations import ConfigFlags from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet -from core.emulator.data import EventData +from core.emulator.data import EventData, NodeData from core.emulator.emudata import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes, ExceptionLevels, NodeTypes from core.errors import CoreError @@ -1170,3 +1170,71 @@ class TestGrpc: # then queue.get(timeout=5) + + def test_move_nodes(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + node = session.add_node(CoreNode) + x, y = 10.0, 15.0 + + def move_iter(): + yield core_pb2.MoveNodesRequest( + session_id=session.id, + node_id=node.id, + position=core_pb2.Position(x=x, y=y), + ) + + # then + with client.context_connect(): + client.move_nodes(move_iter()) + + # assert + assert node.position.x == x + assert node.position.y == y + + def test_move_nodes_geo(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + node = session.add_node(CoreNode) + lon, lat, alt = 10.0, 15.0, 5.0 + queue = Queue() + + def node_handler(node_data: NodeData): + assert node_data.longitude == lon + assert node_data.latitude == lat + assert node_data.altitude == alt + queue.put(node_data) + + session.node_handlers.append(node_handler) + + def move_iter(): + yield core_pb2.MoveNodesRequest( + session_id=session.id, + node_id=node.id, + geo=core_pb2.Geo(lon=lon, lat=lat, alt=alt), + ) + + # then + with client.context_connect(): + client.move_nodes(move_iter()) + + # assert + assert node.position.lon == lon + assert node.position.lat == lat + assert node.position.alt == alt + assert queue.get(timeout=5) + + def test_move_nodes_exception(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + grpc_server.coreemu.create_session() + + def move_iter(): + yield core_pb2.MoveNodesRequest() + + # then + with pytest.raises(grpc.RpcError): + with client.context_connect(): + client.move_nodes(move_iter()) From 3b0ca1638c2725e481e6b52cbfd9dec253892e33 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 3 Jun 2020 14:35:17 -0700 Subject: [PATCH 002/146] grpc: implemened initial support for streaming emane pathloss events --- daemon/core/api/grpc/client.py | 13 +++++++++++++ daemon/core/api/grpc/grpcutils.py | 24 +++++++++++++++++++++++- daemon/core/api/grpc/server.py | 18 +++++++++++++++++- daemon/core/emane/emanemanager.py | 18 +++++++++++++++++- daemon/proto/core/api/grpc/core.proto | 2 ++ daemon/proto/core/api/grpc/emane.proto | 13 +++++++++++++ 6 files changed, 85 insertions(+), 3 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 280b1cd8..6aaf7fac 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -31,6 +31,8 @@ from core.api.grpc.emane_pb2 import ( EmaneLinkRequest, EmaneLinkResponse, EmaneModelConfig, + EmanePathlossesRequest, + EmanePathlossesResponse, GetEmaneConfigRequest, GetEmaneConfigResponse, GetEmaneEventChannelRequest, @@ -1229,6 +1231,17 @@ class CoreGrpcClient: ) return self.stub.WlanLink(request) + def emane_pathlosses( + self, pathloss_iter: Iterable[EmanePathlossesRequest] + ) -> EmanePathlossesResponse: + """ + Stream EMANE pathloss events. + + :param pathloss_iter: iterator for sending EMANE pathloss events + :return: EMANE pathloss response + """ + return self.stub.EmanePathlosses(pathloss_iter) + def connect(self) -> None: """ Open connection to server, must be closed manually. diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 8a69c40f..b0c1e614 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -2,7 +2,9 @@ import logging import time from typing import Any, Dict, List, Tuple, Type +import grpc import netaddr +from grpc import ServicerContext from core import utils from core.api.grpc import common_pb2, core_pb2 @@ -13,7 +15,7 @@ from core.emulator.data import LinkData from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions from core.emulator.enumerations import LinkTypes, NodeTypes from core.emulator.session import Session -from core.nodes.base import NodeBase +from core.nodes.base import CoreNode, NodeBase from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService @@ -478,3 +480,23 @@ def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface: ip6=ip6, ip6mask=ip6mask, ) + + +def get_nem_id(node: CoreNode, netif_id: int, context: ServicerContext) -> int: + """ + Get nem id for a given node and interface id. + + :param node: node to get nem id for + :param netif_id: id of interface on node to get nem id for + :param context: request context + :return: nem id + """ + netif = node.netif(netif_id) + if not netif: + message = f"{node.name} missing interface {netif_id}" + context.abort(grpc.StatusCode.NOT_FOUND, message) + net = netif.net + if not isinstance(net, EmaneNet): + message = f"{node.name} interface {netif_id} is not an EMANE network" + context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) + return net.getnemid(netif) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 972153e7..1d13ec63 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -6,7 +6,7 @@ import tempfile import threading import time from concurrent import futures -from typing import Type +from typing import Iterable, Type import grpc from grpc import ServicerContext @@ -39,6 +39,8 @@ from core.api.grpc.core_pb2 import ExecuteScriptResponse from core.api.grpc.emane_pb2 import ( EmaneLinkRequest, EmaneLinkResponse, + EmanePathlossesRequest, + EmanePathlossesResponse, GetEmaneConfigRequest, GetEmaneConfigResponse, GetEmaneEventChannelRequest, @@ -1751,3 +1753,17 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): wlan.model.sendlinkmsg(n1_netif, n2_netif, unlink=not request.linked) result = True return WlanLinkResponse(result=result) + + def EmanePathlosses( + self, + request_iterator: Iterable[EmanePathlossesRequest], + context: ServicerContext, + ) -> EmanePathlossesResponse: + for request in request_iterator: + session = self.get_session(request.session_id, context) + n1 = self.get_node(session, request.node_one, context, CoreNode) + nem1 = grpcutils.get_nem_id(n1, request.interface_one_id, context) + n2 = self.get_node(session, request.node_two, context, CoreNode) + nem2 = grpcutils.get_nem_id(n2, request.interface_two_id, context) + session.emane.publish_pathloss(nem1, nem2, request.rx_one, request.rx_two) + return EmanePathlossesResponse() diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 438fde00..12b477f0 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -37,7 +37,7 @@ if TYPE_CHECKING: from core.emulator.session import Session try: - from emane.events import EventService + from emane.events import EventService, PathlossEvent from emane.events import LocationEvent from emane.events.eventserviceexception import EventServiceException except ImportError: @@ -48,6 +48,7 @@ except ImportError: except ImportError: EventService = None LocationEvent = None + PathlossEvent = None EventServiceException = None logging.debug("compatible emane python bindings not installed") @@ -868,6 +869,21 @@ class EmaneManager(ModelManager): result = False return result + def publish_pathloss(self, nem1: int, nem2: int, rx1: float, rx2: float) -> None: + """ + Publish pathloss events between provided nems, using provided rx power. + :param nem1: interface one for pathloss + :param nem2: interface two for pathloss + :param rx1: received power from nem2 to nem1 + :param rx2: received power from nem1 to nem2 + :return: nothing + """ + event = PathlossEvent() + event.append(nem1, forward=rx1) + event.append(nem2, forward=rx2) + self.service.publish(nem1, event) + self.service.publish(nem2, event) + class EmaneGlobalModel: """ diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index cdcd9686..1d967d49 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -149,6 +149,8 @@ service CoreApi { } rpc GetEmaneEventChannel (emane.GetEmaneEventChannelRequest) returns (emane.GetEmaneEventChannelResponse) { } + rpc EmanePathlosses (stream emane.EmanePathlossesRequest) returns (emane.EmanePathlossesResponse) { + } // xml rpc rpc SaveXml (SaveXmlRequest) returns (SaveXmlResponse) { diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index 33cb1a2a..8c3ee4ca 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -90,3 +90,16 @@ message EmaneModelConfig { string model = 3; map config = 4; } + +message EmanePathlossesRequest { + int32 session_id = 1; + int32 node_one = 2; + float rx_one = 3; + int32 interface_one_id = 4; + int32 node_two = 5; + float rx_two = 6; + int32 interface_two_id = 7; +} + +message EmanePathlossesResponse { +} From 29d09c8397b71b54966566e1cf16b2d09e92c5cc Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 3 Jun 2020 14:58:29 -0700 Subject: [PATCH 003/146] updates to move_nodes and emane_pathlosses type hinting and naming --- daemon/core/api/grpc/client.py | 6 +++--- daemon/core/api/grpc/server.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 6aaf7fac..1b353bd7 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -1232,15 +1232,15 @@ class CoreGrpcClient: return self.stub.WlanLink(request) def emane_pathlosses( - self, pathloss_iter: Iterable[EmanePathlossesRequest] + self, pathloss_iterator: Iterable[EmanePathlossesRequest] ) -> EmanePathlossesResponse: """ Stream EMANE pathloss events. - :param pathloss_iter: iterator for sending EMANE pathloss events + :param pathloss_iterator: iterator for sending EMANE pathloss events :return: EMANE pathloss response """ - return self.stub.EmanePathlosses(pathloss_iter) + return self.stub.EmanePathlosses(pathloss_iterator) def connect(self) -> None: """ diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 1d13ec63..fcf69d99 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -695,7 +695,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces) def MoveNodes( - self, request_iterator, context: ServicerContext + self, + request_iterator: Iterable[core_pb2.MoveNodesRequest], + context: ServicerContext, ) -> core_pb2.MoveNodesResponse: """ Stream node movements From 7b2dd59c81edeb10caa982c22e0bd297c3b7c7ec Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 4 Jun 2020 13:48:25 -0700 Subject: [PATCH 004/146] grpc: node_command improvements to include return code and options for wait and shell when running commands --- daemon/core/api/grpc/client.py | 15 +++++++++++++-- daemon/core/api/grpc/server.py | 6 ++++-- daemon/proto/core/api/grpc/core.proto | 3 +++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 1b353bd7..68310c67 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -597,7 +597,12 @@ class CoreGrpcClient: return self.stub.DeleteNode(request) def node_command( - self, session_id: int, node_id: int, command: str + self, + session_id: int, + node_id: int, + command: str, + wait: bool = True, + shell: bool = False, ) -> core_pb2.NodeCommandResponse: """ Send command to a node and get the output. @@ -605,11 +610,17 @@ class CoreGrpcClient: :param session_id: session id :param node_id: node id :param command: command to run on node + :param wait: wait for command to complete + :param shell: send shell command :return: response with command combined stdout/stderr :raises grpc.RpcError: when session or node doesn't exist """ request = core_pb2.NodeCommandRequest( - session_id=session_id, node_id=node_id, command=command + session_id=session_id, + node_id=node_id, + command=command, + wait=wait, + shell=shell, ) return self.stub.NodeCommand(request) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index fcf69d99..f85529e6 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -796,10 +796,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, CoreNode) try: - output = node.cmd(request.command) + output = node.cmd(request.command, request.wait, request.shell) + return_code = 0 except CoreCommandError as e: output = e.stderr - return core_pb2.NodeCommandResponse(output=output) + return_code = e.returncode + return core_pb2.NodeCommandResponse(output=output, return_code=return_code) def GetNodeTerminal( self, request: core_pb2.GetNodeTerminalRequest, context: ServicerContext diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 1d967d49..d602f9d3 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -467,10 +467,13 @@ message NodeCommandRequest { int32 session_id = 1; int32 node_id = 2; string command = 3; + bool wait = 4; + bool shell = 5; } message NodeCommandResponse { string output = 1; + int32 return_code = 2; } message GetNodeLinksRequest { From eaa05c34babf0e5bc7d87b229aa4dfd746134794 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 4 Jun 2020 21:14:11 -0700 Subject: [PATCH 005/146] avoid piping subprocess command output when not waiting for results --- daemon/core/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 8a988ede..c16d18b5 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -228,7 +228,8 @@ def cmd( if shell is False: args = shlex.split(args) try: - p = Popen(args, stdout=PIPE, stderr=PIPE, env=env, cwd=cwd, shell=shell) + output = PIPE if wait else DEVNULL + p = Popen(args, stdout=output, stderr=output, env=env, cwd=cwd, shell=shell) if wait: stdout, stderr = p.communicate() stdout = stdout.decode("utf-8").strip() From 9a5fc94ba22406e12dd84ebb72acd95ff47820fc Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 5 Jun 2020 08:44:19 -0700 Subject: [PATCH 006/146] improvements for grpc docs and upates to grpc client pydocs --- daemon/core/api/grpc/client.py | 97 ++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 11 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 68310c67..cabd6dda 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -1,5 +1,5 @@ """ -gRpc client for interfacing with CORE, when gRPC mode is enabled. +gRpc client for interfacing with CORE. """ import logging @@ -289,6 +289,7 @@ class CoreGrpcClient: :param session_id: id of session :return: stop session response + :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.StopSessionRequest(session_id=session_id) return self.stub.StopSession(request) @@ -581,6 +582,7 @@ class CoreGrpcClient: :param move_iterator: iterator for generating node movements :return: move nodes response + :raises grpc.RpcError: when session or nodes do not exist """ return self.stub.MoveNodes(move_iterator) @@ -1122,9 +1124,9 @@ class CoreGrpcClient: def get_emane_model_configs(self, session_id: int) -> GetEmaneModelConfigsResponse: """ - Get all emane model configurations for a session. + Get all EMANE model configurations for a session. - :param session_id: session id + :param session_id: session to get emane model configs :return: response with a dictionary of node/interface ids to configurations :raises grpc.RpcError: when session doesn't exist """ @@ -1135,9 +1137,10 @@ class CoreGrpcClient: """ Save the current scenario to an XML file. - :param session_id: session id + :param session_id: session to save xml file for :param file_path: local path to save scenario XML file to :return: nothing + :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.SaveXmlRequest(session_id=session_id) response = self.stub.SaveXml(request) @@ -1163,11 +1166,12 @@ class CoreGrpcClient: """ Helps broadcast wireless link/unlink between EMANE nodes. - :param session_id: session id - :param nem_one: - :param nem_two: + :param session_id: session to emane link + :param nem_one: first nem for emane link + :param nem_two: second nem for emane link :param linked: True to link, False to unlink - :return: core_pb2.EmaneLinkResponse + :return: get emane link response + :raises grpc.RpcError: when session or nodes related to nems do not exist """ request = EmaneLinkRequest( session_id=session_id, nem_one=nem_one, nem_two=nem_two, linked=linked @@ -1179,30 +1183,57 @@ class CoreGrpcClient: Retrieves a list of interfaces available on the host machine that are not a part of a CORE session. - :return: core_pb2.GetInterfacesResponse + :return: get interfaces response """ request = core_pb2.GetInterfacesRequest() return self.stub.GetInterfaces(request) def get_config_services(self) -> GetConfigServicesResponse: + """ + Retrieve all known config services. + + :return: get config services response + """ request = GetConfigServicesRequest() return self.stub.GetConfigServices(request) def get_config_service_defaults( self, name: str ) -> GetConfigServiceDefaultsResponse: + """ + Retrieves config service default values. + + :param name: name of service to get defaults for + :return: get config service defaults + """ request = GetConfigServiceDefaultsRequest(name=name) return self.stub.GetConfigServiceDefaults(request) def get_node_config_service_configs( self, session_id: int ) -> GetNodeConfigServiceConfigsResponse: + """ + Retrieves all node config service configurations for a session. + + :param session_id: session to get config service configurations for + :return: get node config service configs response + :raises grpc.RpcError: when session doesn't exist + """ request = GetNodeConfigServiceConfigsRequest(session_id=session_id) return self.stub.GetNodeConfigServiceConfigs(request) def get_node_config_service( self, session_id: int, node_id: int, name: str ) -> GetNodeConfigServiceResponse: + """ + Retrieves information for a specific config service on a node. + + :param session_id: session node belongs to + :param node_id: id of node to get service information from + :param name: name of service + :return: get node config service response + :raises grpc.RpcError: when session or node doesn't exist + """ request = GetNodeConfigServiceRequest( session_id=session_id, node_id=node_id, name=name ) @@ -1211,28 +1242,70 @@ class CoreGrpcClient: def get_node_config_services( self, session_id: int, node_id: int ) -> GetNodeConfigServicesResponse: + """ + Retrieves the config services currently assigned to a node. + + :param session_id: session node belongs to + :param node_id: id of node to get config services for + :return: get node config services response + :raises grpc.RpcError: when session or node doesn't exist + """ request = GetNodeConfigServicesRequest(session_id=session_id, node_id=node_id) return self.stub.GetNodeConfigServices(request) def set_node_config_service( self, session_id: int, node_id: int, name: str, config: Dict[str, str] ) -> SetNodeConfigServiceResponse: + """ + Assigns a config service to a node with the provided configuration. + + :param session_id: session node belongs to + :param node_id: id of node to assign config service to + :param name: name of service + :param config: service configuration + :return: set node config service response + :raises grpc.RpcError: when session or node doesn't exist + """ request = SetNodeConfigServiceRequest( session_id=session_id, node_id=node_id, name=name, config=config ) return self.stub.SetNodeConfigService(request) def get_emane_event_channel(self, session_id: int) -> GetEmaneEventChannelResponse: + """ + Retrieves the current emane event channel being used for a session. + + :param session_id: session to get emane event channel for + :return: emane event channel response + :raises grpc.RpcError: when session doesn't exist + """ request = GetEmaneEventChannelRequest(session_id=session_id) return self.stub.GetEmaneEventChannel(request) def execute_script(self, script: str) -> ExecuteScriptResponse: + """ + Executes a python script given context of the current CoreEmu object. + + :param script: script to execute + :return: execute script response + """ request = ExecuteScriptRequest(script=script) return self.stub.ExecuteScript(request) def wlan_link( self, session_id: int, wlan: int, node_one: int, node_two: int, linked: bool ) -> WlanLinkResponse: + """ + Links/unlinks nodes on the same WLAN. + + :param session_id: session id containing wlan and nodes + :param wlan: wlan nodes must belong to + :param node_one: first node of pair to link/unlink + :param node_two: second node of pair to link/unlin + :param linked: True to link, False to unlink + :return: wlan link response + :raises grpc.RpcError: when session or one of the nodes do not exist + """ request = WlanLinkRequest( session_id=session_id, wlan=wlan, @@ -1248,8 +1321,10 @@ class CoreGrpcClient: """ Stream EMANE pathloss events. - :param pathloss_iterator: iterator for sending EMANE pathloss events - :return: EMANE pathloss response + :param pathloss_iterator: iterator for sending emane pathloss events + :return: emane pathloss response + :raises grpc.RpcError: when a pathloss event session or one of the nodes do not + exist """ return self.stub.EmanePathlosses(pathloss_iterator) From 75d5bced9cd5b8c5bb6f044b93e2b992fc7464c2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 5 Jun 2020 11:20:23 -0700 Subject: [PATCH 007/146] grpc doc improvements, grpc examples additions, small tweak to grpc client for setting emane models not requiring a config when using default configuration --- daemon/core/api/grpc/client.py | 2 +- daemon/examples/grpc/distributed_switch.py | 21 +++--- daemon/examples/grpc/emane80211.py | 74 +++++++++++++++++++ daemon/examples/grpc/large.py | 54 -------------- daemon/examples/grpc/switch.py | 57 +++++++++------ daemon/examples/grpc/wlan.py | 82 ++++++++++++++++++++++ docs/grpc.md | 78 ++++++-------------- 7 files changed, 220 insertions(+), 148 deletions(-) create mode 100644 daemon/examples/grpc/emane80211.py delete mode 100644 daemon/examples/grpc/large.py create mode 100644 daemon/examples/grpc/wlan.py diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index cabd6dda..c1d0e2fd 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -1100,7 +1100,7 @@ class CoreGrpcClient: session_id: int, node_id: int, model: str, - config: Dict[str, str], + config: Dict[str, str] = None, interface_id: int = -1, ) -> SetEmaneModelConfigResponse: """ diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index 9cc35f72..0477efdd 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -1,7 +1,8 @@ import argparse import logging -from core.api.grpc import client, core_pb2 +from core.api.grpc import client +from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState def log_event(event): @@ -26,13 +27,11 @@ def main(args): core.events(session_id, log_event) # change session state - response = core.set_session_state( - session_id, core_pb2.SessionState.CONFIGURATION - ) + response = core.set_session_state(session_id, SessionState.CONFIGURATION) logging.info("set session state: %s", response) # create switch node - switch = core_pb2.Node(type=core_pb2.NodeType.SWITCH) + switch = Node(type=NodeType.SWITCH) response = core.add_node(session_id, switch) logging.info("created switch: %s", response) switch_id = response.node_id @@ -41,8 +40,8 @@ def main(args): interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") # create node one - position = core_pb2.Position(x=100, y=50) - node = core_pb2.Node(position=position) + position = Position(x=100, y=50) + node = Node(position=position) response = core.add_node(session_id, node) logging.info("created node one: %s", response) node_one_id = response.node_id @@ -53,8 +52,8 @@ def main(args): logging.info("created link from node one to switch: %s", response) # create node two - position = core_pb2.Position(x=200, y=50) - node = core_pb2.Node(position=position, server=server_name) + position = Position(x=200, y=50) + node = Node(position=position, server=server_name) response = core.add_node(session_id, node) logging.info("created node two: %s", response) node_two_id = response.node_id @@ -65,9 +64,7 @@ def main(args): logging.info("created link from node two to switch: %s", response) # change session state - response = core.set_session_state( - session_id, core_pb2.SessionState.INSTANTIATION - ) + response = core.set_session_state(session_id, SessionState.INSTANTIATION) logging.info("set session state: %s", response) diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py new file mode 100644 index 00000000..5656268c --- /dev/null +++ b/daemon/examples/grpc/emane80211.py @@ -0,0 +1,74 @@ +""" +Example using gRPC API to create a simple EMANE 80211 network. +""" + +import logging + +from core.api.grpc import client +from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.emane.ieee80211abg import EmaneIeee80211abgModel + + +def log_event(event): + logging.info("event: %s", event) + + +def main(): + # helper to create interface addresses + interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24") + + # create grpc client and start connection context, which auto closes connection + core = client.CoreGrpcClient() + with core.context_connect(): + # create session + response = core.create_session() + logging.info("created session: %s", response) + + # handle events session may broadcast + session_id = response.session_id + core.events(session_id, log_event) + + # change session state to configuration so that nodes get started when added + response = core.set_session_state(session_id, SessionState.CONFIGURATION) + logging.info("set session state: %s", response) + + # create emane node + position = Position(x=200, y=200) + emane = Node(type=NodeType.EMANE, position=position) + response = core.add_node(session_id, emane) + logging.info("created emane: %s", response) + emane_id = response.node_id + + # an emane model must be configured for use, by the emane node + core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name) + + # create node one + position = Position(x=100, y=100) + node1 = Node(type=NodeType.DEFAULT, position=position) + response = core.add_node(session_id, node1) + logging.info("created node: %s", response) + node1_id = response.node_id + + # create node two + position = Position(x=300, y=100) + node2 = Node(type=NodeType.DEFAULT, position=position) + response = core.add_node(session_id, node2) + logging.info("created node: %s", response) + node2_id = response.node_id + + # links nodes to switch + interface_one = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, emane_id, interface_one) + logging.info("created link: %s", response) + interface_one = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, emane_id, interface_one) + logging.info("created link: %s", response) + + # change session state + response = core.set_session_state(session_id, SessionState.INSTANTIATION) + logging.info("set session state: %s", response) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + main() diff --git a/daemon/examples/grpc/large.py b/daemon/examples/grpc/large.py deleted file mode 100644 index ef1e6cc4..00000000 --- a/daemon/examples/grpc/large.py +++ /dev/null @@ -1,54 +0,0 @@ -import logging - -from core.api.grpc import client, core_pb2 - - -def log_event(event): - logging.info("event: %s", event) - - -def main(): - core = client.CoreGrpcClient() - - with core.context_connect(): - # create session - response = core.create_session() - session_id = response.session_id - logging.info("created session: %s", response) - - # create nodes for session - nodes = [] - position = core_pb2.Position(x=50, y=100) - switch = core_pb2.Node(id=1, type=core_pb2.NodeType.SWITCH, position=position) - nodes.append(switch) - for i in range(2, 50): - position = core_pb2.Position(x=50 + 50 * i, y=50) - node = core_pb2.Node(id=i, position=position, model="PC") - nodes.append(node) - - # create links - interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") - links = [] - for node in nodes: - interface_one = interface_helper.create_interface(node.id, 0) - link = core_pb2.Link( - type=core_pb2.LinkType.WIRED, - node_one_id=node.id, - node_two_id=switch.id, - interface_one=interface_one, - ) - links.append(link) - - # start session - response = core.start_session(session_id, nodes, links) - logging.info("started session: %s", response) - - input("press enter to shutdown session") - - response = core.stop_session(session_id) - logging.info("stop sessionL %s", response) - - -if __name__ == "__main__": - logging.basicConfig(level=logging.DEBUG) - main() diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 48aa63bc..3ab0e0ba 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -1,6 +1,11 @@ +""" +Example using gRPC API to create a simple switch network. +""" + import logging -from core.api.grpc import client, core_pb2 +from core.api.grpc import client +from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState def log_event(event): @@ -8,8 +13,11 @@ def log_event(event): def main(): - core = client.CoreGrpcClient() + # helper to create interface addresses + interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24") + # create grpc client and start connection context, which auto closes connection + core = client.CoreGrpcClient() with core.context_connect(): # create session response = core.create_session() @@ -19,38 +27,41 @@ def main(): session_id = response.session_id core.events(session_id, log_event) - # change session state - response = core.set_session_state( - session_id, core_pb2.SessionState.CONFIGURATION - ) + # change session state to configuration so that nodes get started when added + response = core.set_session_state(session_id, SessionState.CONFIGURATION) logging.info("set session state: %s", response) # create switch node - switch = core_pb2.Node(type=core_pb2.NodeType.SWITCH) + position = Position(x=200, y=200) + switch = Node(type=NodeType.SWITCH, position=position) response = core.add_node(session_id, switch) logging.info("created switch: %s", response) switch_id = response.node_id - # helper to create interfaces - interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") + # create node one + position = Position(x=100, y=100) + node1 = Node(type=NodeType.DEFAULT, position=position) + response = core.add_node(session_id, node1) + logging.info("created node: %s", response) + node1_id = response.node_id - for i in range(2): - # create node - position = core_pb2.Position(x=50 + 50 * i, y=50) - node = core_pb2.Node(position=position) - response = core.add_node(session_id, node) - logging.info("created node: %s", response) - node_id = response.node_id + # create node two + position = Position(x=300, y=100) + node2 = Node(type=NodeType.DEFAULT, position=position) + response = core.add_node(session_id, node2) + logging.info("created node: %s", response) + node2_id = response.node_id - # create link - interface_one = interface_helper.create_interface(node_id, 0) - response = core.add_link(session_id, node_id, switch_id, interface_one) - logging.info("created link: %s", response) + # links nodes to switch + interface_one = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, switch_id, interface_one) + logging.info("created link: %s", response) + interface_one = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, switch_id, interface_one) + logging.info("created link: %s", response) # change session state - response = core.set_session_state( - session_id, core_pb2.SessionState.INSTANTIATION - ) + response = core.set_session_state(session_id, SessionState.INSTANTIATION) logging.info("set session state: %s", response) diff --git a/daemon/examples/grpc/wlan.py b/daemon/examples/grpc/wlan.py new file mode 100644 index 00000000..6118ae4c --- /dev/null +++ b/daemon/examples/grpc/wlan.py @@ -0,0 +1,82 @@ +""" +Example using gRPC API to create a simple wlan network. +""" + +import logging + +from core.api.grpc import client +from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState + + +def log_event(event): + logging.info("event: %s", event) + + +def main(): + # helper to create interface addresses + interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/24") + + # create grpc client and start connection context, which auto closes connection + core = client.CoreGrpcClient() + with core.context_connect(): + # create session + response = core.create_session() + logging.info("created session: %s", response) + + # handle events session may broadcast + session_id = response.session_id + core.events(session_id, log_event) + + # change session state to configuration so that nodes get started when added + response = core.set_session_state(session_id, SessionState.CONFIGURATION) + logging.info("set session state: %s", response) + + # create wlan node + position = Position(x=200, y=200) + wlan = Node(type=NodeType.WIRELESS_LAN, position=position) + response = core.add_node(session_id, wlan) + logging.info("created wlan: %s", response) + wlan_id = response.node_id + + # change/configure wlan if desired + # NOTE: error = loss, and named this way for legacy purposes for now + config = { + "bandwidth": "54000000", + "range": "500", + "jitter": "0", + "delay": "5000", + "error": "0", + } + response = core.set_wlan_config(session_id, wlan_id, config) + logging.info("set wlan config: %s", response) + + # create node one + position = Position(x=100, y=100) + node1 = Node(type=NodeType.DEFAULT, position=position) + response = core.add_node(session_id, node1) + logging.info("created node: %s", response) + node1_id = response.node_id + + # create node two + position = Position(x=300, y=100) + node2 = Node(type=NodeType.DEFAULT, position=position) + response = core.add_node(session_id, node2) + logging.info("created node: %s", response) + node2_id = response.node_id + + # links nodes to switch + interface_one = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, wlan_id, interface_one) + logging.info("created link: %s", response) + interface_one = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, wlan_id, interface_one) + logging.info("created link: %s", response) + + # change session state + response = core.set_session_state(session_id, SessionState.INSTANTIATION) + logging.info("set session state: %s", response) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + main() diff --git a/docs/grpc.md b/docs/grpc.md index 2430f3ae..69cf4aed 100644 --- a/docs/grpc.md +++ b/docs/grpc.md @@ -1,72 +1,34 @@ # Using the gRPC API -gRPC is the main API for interfacing with CORE. +[gRPC](https://grpc.io/) is the main API for interfacing with CORE and used by +the python GUI for driving all functionality. + +Currently we are providing a python client that wraps the generated files for +leveraging the API, but proto files noted below can also be leveraged to generate +bindings for other languages as well. ## HTTP Proxy -Since gRPC is HTTP2 based, proxy configurations can cause issue. Clear out your -proxy when running if needed. +Since gRPC is HTTP2 based, proxy configurations can cause issue. You can either +properly account for this issue or clear out your proxy when running if needed. ## Python Client -A python client wrapper is provided at **core.api.grpc.client.CoreGrpcClient**. +A python client wrapper is provided at +[CoreGrpcClient](../daemon/core/api/grpc/client.py) to help provide some +conveniences when using the API. -Below is a small example using it. +## Proto Files -```python -import logging +Proto files are used to define the API and protobuf messages that are used for +interfaces with this API. -from core.api.grpc import client, core_pb2 +They can be found [here](../daemon/proto/core/api/grpc) to see the specifics of +what is going on and response message values that would be returned. +## Examples -def log_event(event): - logging.info("event: %s", event) +Example usage of this API can be found [here](../daemon/examples/grpc). These +examples will create a session using the gRPC API when the core-daemon is running. - -def main(): - core = client.CoreGrpcClient() - - with core.context_connect(): - # create session - response = core.create_session() - logging.info("created session: %s", response) - - # handle events session may broadcast - session_id = response.session_id - core.events(session_id, log_event) - - # change session state - response = core.set_session_state(session_id, core_pb2.SessionState.CONFIGURATION) - logging.info("set session state: %s", response) - - # create switch node - switch = core_pb2.Node(type=core_pb2.NodeType.SWITCH) - response = core.add_node(session_id, switch) - logging.info("created switch: %s", response) - switch_id = response.node_id - - # helper to create interfaces - interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") - - for i in range(2): - # create node - position = core_pb2.Position(x=50 + 50 * i, y=50) - node = core_pb2.Node(position=position) - response = core.add_node(session_id, node) - logging.info("created node: %s", response) - node_id = response.node_id - - # create link - interface_one = interface_helper.create_interface(node_id, 0) - response = core.add_link(session_id, node_id, switch_id, interface_one) - logging.info("created link: %s", response) - - # change session state - response = core.set_session_state(session_id, core_pb2.SessionState.INSTANTIATION) - logging.info("set session state: %s", response) - - -if __name__ == "__main__": - logging.basicConfig(level=logging.DEBUG) - main() -``` +You can then switch to and attach to these sessions using either of the CORE GUIs. From bf1bc511e28b429355ef2a5d20a704f4aaf714be Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 5 Jun 2020 14:34:19 -0700 Subject: [PATCH 008/146] removed configuration option for number of for corehandler threads as it cannot properly deal with anything more than 1, updated man pages to current 6.4 versions for now --- daemon/core/api/tlv/corehandlers.py | 12 +++--------- daemon/data/core.conf | 1 - daemon/scripts/core-daemon | 11 +---------- daemon/scripts/core-imn-to-xml | 2 +- daemon/scripts/core-manage | 2 +- daemon/scripts/core-pygui | 2 +- daemon/scripts/core-route-monitor | 2 +- daemon/scripts/core-service-update | 2 +- daemon/scripts/coresendmsg | 2 +- daemon/tests/conftest.py | 6 +++--- man/core-cleanup.1 | 6 +++--- man/core-daemon.1 | 24 +++++++++++------------- man/core-gui.1 | 6 +++--- man/core-manage.1 | 6 +++--- man/coresendmsg.1 | 10 +++++----- man/netns.1 | 6 +++--- man/vcmd.1 | 6 +++--- man/vnoded.1 | 6 +++--- 18 files changed, 47 insertions(+), 65 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 1a22cedd..7e2cd040 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -79,15 +79,9 @@ class CoreHandler(socketserver.BaseRequestHandler): self._sessions_lock = threading.Lock() self.handler_threads = [] - num_threads = int(server.config["numthreads"]) - if num_threads < 1: - raise ValueError(f"invalid number of threads: {num_threads}") - - logging.debug("launching core server handler threads: %s", num_threads) - for _ in range(num_threads): - thread = threading.Thread(target=self.handler_thread) - self.handler_threads.append(thread) - thread.start() + thread = threading.Thread(target=self.handler_thread, daemon=True) + thread.start() + self.handler_threads.append(thread) self.session = None self.coreemu = server.coreemu diff --git a/daemon/data/core.conf b/daemon/data/core.conf index 13b50785..5ff0be7f 100644 --- a/daemon/data/core.conf +++ b/daemon/data/core.conf @@ -4,7 +4,6 @@ listenaddr = localhost port = 4038 grpcaddress = localhost grpcport = 50051 -numthreads = 1 quagga_bin_search = "/usr/local/bin /usr/bin /usr/lib/quagga" quagga_sbin_search = "/usr/local/sbin /usr/sbin /usr/lib/quagga" frr_bin_search = "/usr/local/bin /usr/bin /usr/lib/frr" diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index 9f738467..a95e59fa 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ core-daemon: the CORE daemon is a server process that receives CORE API messages and instantiates emulated nodes and networks within the kernel. Various @@ -93,12 +93,10 @@ def get_merged_config(filename): # these are the defaults used in the config file default_log = os.path.join(constants.CORE_CONF_DIR, "logging.conf") default_grpc_port = "50051" - default_threads = "1" default_address = "localhost" defaults = { "port": str(CORE_API_PORT), "listenaddr": default_address, - "numthreads": default_threads, "grpcport": default_grpc_port, "grpcaddress": default_address, "logfile": default_log @@ -110,8 +108,6 @@ def get_merged_config(filename): help=f"read config from specified file; default = {filename}") parser.add_argument("-p", "--port", dest="port", type=int, help=f"port number to listen on; default = {CORE_API_PORT}") - parser.add_argument("-n", "--numthreads", dest="numthreads", type=int, - help=f"number of server threads; default = {default_threads}") parser.add_argument("--ovs", action="store_true", help="enable experimental ovs mode, default is false") parser.add_argument("--grpc-port", dest="grpcport", help=f"grpc port to listen on; default {default_grpc_port}") @@ -148,14 +144,9 @@ def main(): :return: nothing """ - # get a configuration merged from config file and command-line arguments cfg = get_merged_config(f"{CORE_CONF_DIR}/core.conf") - - # load logging configuration load_logging_config(cfg["logfile"]) - banner() - try: cored(cfg) except KeyboardInterrupt: diff --git a/daemon/scripts/core-imn-to-xml b/daemon/scripts/core-imn-to-xml index 725d7119..495093ed 100755 --- a/daemon/scripts/core-imn-to-xml +++ b/daemon/scripts/core-imn-to-xml @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import argparse import re import sys diff --git a/daemon/scripts/core-manage b/daemon/scripts/core-manage index 14e10e5b..5587c9ae 100755 --- a/daemon/scripts/core-manage +++ b/daemon/scripts/core-manage @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ core-manage: Helper tool to add, remove, or check for services, models, and node types in a CORE installation. diff --git a/daemon/scripts/core-pygui b/daemon/scripts/core-pygui index f30b531b..46860ce9 100755 --- a/daemon/scripts/core-pygui +++ b/daemon/scripts/core-pygui @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import argparse import logging from logging.handlers import TimedRotatingFileHandler diff --git a/daemon/scripts/core-route-monitor b/daemon/scripts/core-route-monitor index a9b48aff..b12e6205 100755 --- a/daemon/scripts/core-route-monitor +++ b/daemon/scripts/core-route-monitor @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import argparse import enum import select diff --git a/daemon/scripts/core-service-update b/daemon/scripts/core-service-update index 6d0be06c..d0ca863f 100755 --- a/daemon/scripts/core-service-update +++ b/daemon/scripts/core-service-update @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 import argparse import re from io import TextIOWrapper diff --git a/daemon/scripts/coresendmsg b/daemon/scripts/coresendmsg index ae89ecb1..13e20b5c 100755 --- a/daemon/scripts/coresendmsg +++ b/daemon/scripts/coresendmsg @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 """ coresendmsg: utility for generating CORE messages """ diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 0c021b25..9d54d9c2 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -44,8 +44,8 @@ class PatchManager: class MockServer: - def __init__(self, config, coreemu): - self.config = config + def __init__(self, coreemu): + self.config = {} self.coreemu = coreemu @@ -108,7 +108,7 @@ def module_grpc(global_coreemu): def module_coretlv(patcher, global_coreemu, global_session): request_mock = MagicMock() request_mock.fileno = MagicMock(return_value=1) - server = MockServer({"numthreads": "1"}, global_coreemu) + server = MockServer(global_coreemu) request_handler = CoreHandler(request_mock, "", server) request_handler.session = global_session request_handler.add_session_handlers() diff --git a/man/core-cleanup.1 b/man/core-cleanup.1 index 64aa18fb..0f56c14c 100644 --- a/man/core-cleanup.1 +++ b/man/core-cleanup.1 @@ -1,7 +1,7 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. -.TH CORE-CLEANUP "1" "June 2019" "CORE" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. +.TH CORE-CLEANUP "1" "June 2020" "CORE" "User Commands" .SH NAME -core-cleanup \- manual page for core-cleanup 5.3.0 +core-cleanup \- manual page for core-cleanup 6.4.0 .SH DESCRIPTION usage: ../daemon/scripts/core\-cleanup [\-d [\-l]] .IP diff --git a/man/core-daemon.1 b/man/core-daemon.1 index c8061e0d..01799894 100644 --- a/man/core-daemon.1 +++ b/man/core-daemon.1 @@ -1,14 +1,14 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. -.TH CORE-DAEMON "1" "June 2019" "CORE" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. +.TH CORE-DAEMON "1" "June 2020" "CORE" "User Commands" .SH NAME -core-daemon \- manual page for core-daemon 5.3.0 +core-daemon \- manual page for core-daemon 6.4.0 .SH DESCRIPTION -usage: core\-daemon [\-h] [\-f CONFIGFILE] [\-p PORT] [\-n NUMTHREADS] [\-\-ovs] +usage: core\-daemon [\-h] [\-f CONFIGFILE] [\-p PORT] [\-\-ovs] .IP -[\-\-grpc] [\-\-grpc\-port GRPCPORT] -[\-\-grpc\-address GRPCADDRESS] +[\-\-grpc\-port GRPCPORT] [\-\-grpc\-address GRPCADDRESS] +[\-l LOGFILE] .PP -CORE daemon v.5.3.0 instantiates Linux network namespace nodes. +CORE daemon v.6.4.0 instantiates Linux network namespace nodes. .SS "optional arguments:" .TP \fB\-h\fR, \fB\-\-help\fR @@ -21,17 +21,15 @@ read config from specified file; default = \fB\-p\fR PORT, \fB\-\-port\fR PORT port number to listen on; default = 4038 .TP -\fB\-n\fR NUMTHREADS, \fB\-\-numthreads\fR NUMTHREADS -number of server threads; default = 1 -.TP \fB\-\-ovs\fR enable experimental ovs mode, default is false .TP -\fB\-\-grpc\fR -enable grpc api, default is false -.TP \fB\-\-grpc\-port\fR GRPCPORT grpc port to listen on; default 50051 .TP \fB\-\-grpc\-address\fR GRPCADDRESS grpc address to listen on; default localhost +.TP +\fB\-l\fR LOGFILE, \fB\-\-logfile\fR LOGFILE +core logging configuration; default +\fI\,/etc/core/logging.conf\/\fP diff --git a/man/core-gui.1 b/man/core-gui.1 index 5792e0c6..2a7b95ac 100644 --- a/man/core-gui.1 +++ b/man/core-gui.1 @@ -1,7 +1,7 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. -.TH CORE-GUI "1" "June 2019" "CORE" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. +.TH CORE-GUI "1" "June 2020" "CORE" "User Commands" .SH NAME -core-gui \- manual page for core-gui version 5.3.0 (20190607) +core-gui \- manual page for core-gui version 6.4.0 (20200513) .SH SYNOPSIS .B core-gui [\fI\,-h|-v\/\fR] [\fI\,-b|-c \/\fR] [\fI\,-s\/\fR] [\fI\,-a address\/\fR] [\fI\,-p port\/\fR] diff --git a/man/core-manage.1 b/man/core-manage.1 index 7e7c6f42..594a5f64 100644 --- a/man/core-manage.1 +++ b/man/core-manage.1 @@ -1,7 +1,7 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. -.TH CORE-MANAGE "1" "June 2019" "CORE" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. +.TH CORE-MANAGE "1" "June 2020" "CORE" "User Commands" .SH NAME -core-manage \- manual page for core-manage 5.3.0 +core-manage \- manual page for core-manage 6.4.0 .SH SYNOPSIS .B core-manage [\fI\,-h\/\fR] [\fI\,options\/\fR] \fI\, \/\fR diff --git a/man/coresendmsg.1 b/man/coresendmsg.1 index 9a42e29a..8815b189 100644 --- a/man/coresendmsg.1 +++ b/man/coresendmsg.1 @@ -1,17 +1,17 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. -.TH CORESENDMSG "1" "June 2019" "CORE" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. +.TH CORESENDMSG "1" "June 2020" "CORE" "User Commands" .SH NAME -coresendmsg \- manual page for coresendmsg 5.3.0 +coresendmsg \- manual page for coresendmsg 6.4.0 .SH SYNOPSIS .B coresendmsg [\fI\,-h|-H\/\fR] [\fI\,options\/\fR] [\fI\,message-type\/\fR] [\fI\,flags=flags\/\fR] [\fI\,message-TLVs\/\fR] .SH DESCRIPTION .SS "Supported message types:" .IP -['NODE', 'LINK', 'EXECUTE', 'REGISTER', 'CONFIG', 'FILE', 'INTERFACE', 'EVENT', 'SESSION', 'EXCEPTION'] +node link execute register config file interface event session exception .SS "Supported message flags (flags=f1,f2,...):" .IP -['ADD', 'DELETE', 'CRI', 'LOCAL', 'STRING', 'TEXT', 'TTY'] +none add delete cri local string text tty .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR diff --git a/man/netns.1 b/man/netns.1 index 5cb6e312..abc62ee2 100644 --- a/man/netns.1 +++ b/man/netns.1 @@ -1,7 +1,7 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. -.TH NETNS "1" "June 2019" "CORE" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. +.TH NETNS "1" "June 2020" "CORE" "User Commands" .SH NAME -netns \- manual page for netns version 5.3.0 +netns \- manual page for netns version 6.4.0 .SH SYNOPSIS .B netns [\fI\,-h|-V\/\fR] [\fI\,-w\/\fR] \fI\,-- command \/\fR[\fI\,args\/\fR...] diff --git a/man/vcmd.1 b/man/vcmd.1 index 79f14f50..f5438978 100644 --- a/man/vcmd.1 +++ b/man/vcmd.1 @@ -1,7 +1,7 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. -.TH VCMD "1" "June 2019" "CORE" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. +.TH VCMD "1" "June 2020" "CORE" "User Commands" .SH NAME -vcmd \- manual page for vcmd version 5.3.0 +vcmd \- manual page for vcmd version 6.4.0 .SH SYNOPSIS .B vcmd [\fI\,-h|-V\/\fR] [\fI\,-v\/\fR] [\fI\,-q|-i|-I\/\fR] \fI\,-c -- command args\/\fR... diff --git a/man/vnoded.1 b/man/vnoded.1 index 841b00e9..11874e39 100644 --- a/man/vnoded.1 +++ b/man/vnoded.1 @@ -1,7 +1,7 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. -.TH VNODED "1" "June 2019" "CORE" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.6. +.TH VNODED "1" "June 2020" "CORE" "User Commands" .SH NAME -vnoded \- manual page for vnoded version 5.3.0 +vnoded \- manual page for vnoded version 6.4.0 .SH SYNOPSIS .B vnoded [\fI\,-h|-V\/\fR] [\fI\,-v\/\fR] [\fI\,-n\/\fR] [\fI\,-C \/\fR] [\fI\,-l \/\fR] [\fI\,-p \/\fR] \fI\,-c \/\fR From 7ffbf457be6020c9934f434c11000db3b813cc2c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:55:05 -0700 Subject: [PATCH 009/146] update to netclient existing bridge check to avoid using the -j flag, which requires version 4.7+ vs 4.5+ that we currently expect --- daemon/core/nodes/netclient.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 091938de..29a70d18 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -1,7 +1,6 @@ """ Clients for dealing with bridge/interface commands. """ -import json from typing import Callable import netaddr @@ -279,12 +278,13 @@ class LinuxNetClient: :param _id: node id to check bridges for :return: True if there are existing bridges, False otherwise """ - output = self.run(f"{IP_BIN} -j link show type bridge") - bridges = json.loads(output) - for bridge in bridges: - name = bridge.get("ifname") - if not name: + output = self.run(f"{IP_BIN} -o link show type bridge") + lines = output.split("\n") + for line in lines: + values = line.split(":") + if not len(values) >= 2: continue + name = values[1] fields = name.split(".") if len(fields) != 3: continue From 199c4618f5e40e77e0bb42677b0b883c60a136d1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 8 Jun 2020 10:08:26 -0700 Subject: [PATCH 010/146] removed comments about rj45 removing addresses and setting to promiscuous, as that is not true and misleading --- docs/gui.md | 6 +----- docs/pygui.md | 6 +----- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/docs/gui.md b/docs/gui.md index 8f6f9057..85bbb6cd 100644 --- a/docs/gui.md +++ b/docs/gui.md @@ -371,11 +371,7 @@ be entered into the text box. > **NOTE:** When you press the Start button to instantiate your topology, the interface assigned to the RJ45 will be connected to the CORE topology. The - interface can no longer be used by the system. For example, if there was an - IP address assigned to the physical interface before execution, the address - will be removed and control given over to CORE. No IP address is needed; the - interface is put into promiscuous mode so it will receive all packets and - send them into the emulated world. + interface can no longer be used by the system. Multiple RJ45 nodes can be used within CORE and assigned to the same physical interface if 802.1x VLANs are used. This allows for more RJ45 nodes than diff --git a/docs/pygui.md b/docs/pygui.md index 4ed3fe09..f3e2c592 100644 --- a/docs/pygui.md +++ b/docs/pygui.md @@ -348,11 +348,7 @@ be entered into the text box. > **NOTE:** When you press the Start button to instantiate your topology, the interface assigned to the RJ45 will be connected to the CORE topology. The - interface can no longer be used by the system. For example, if there was an - IP address assigned to the physical interface before execution, the address - will be removed and control given over to CORE. No IP address is needed; the - interface is put into promiscuous mode so it will receive all packets and - send them into the emulated world. + interface can no longer be used by the system. Multiple RJ45 nodes can be used within CORE and assigned to the same physical interface if 802.1x VLANs are used. This allows for more RJ45 nodes than From 6ddf1ac9a460018515895245761873fc88abdb65 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 00:56:34 -0700 Subject: [PATCH 011/146] removed IdGen class, added simple function to find next valid node id --- daemon/core/emulator/emudata.py | 9 --------- daemon/core/emulator/session.py | 21 ++++++++++++++------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 4e3ebf8a..26e09fe8 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -10,15 +10,6 @@ from core.nodes.interface import CoreInterface from core.nodes.physical import PhysicalNode -class IdGen: - def __init__(self, _id: int = 0) -> None: - self.id = _id - - def next(self) -> int: - self.id += 1 - return self.id - - def link_config( node: Union[CoreNetworkBase, PhysicalNode], interface: CoreInterface, diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 9193196c..24fe05bd 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -20,7 +20,6 @@ from core.emane.nodes import EmaneNet from core.emulator.data import ConfigData, EventData, ExceptionData, FileData, LinkData from core.emulator.distributed import DistributedController from core.emulator.emudata import ( - IdGen, InterfaceData, LinkOptions, NodeOptions, @@ -111,7 +110,6 @@ class Session: self.link_colors = {} # dict of nodes: all nodes and nets - self.node_id_gen = IdGen() self.nodes = {} self._nodes_lock = threading.Lock() @@ -649,6 +647,19 @@ class Session: if node_two: node_two.lock.release() + def _next_node_id(self) -> int: + """ + Find the next valid node id, starting from 1. + + :return: next node id + """ + _id = 1 + while True: + if _id not in self.nodes: + break + _id += 1 + return _id + def add_node( self, _class: Type[NT], _id: int = None, options: NodeOptions = None ) -> NT: @@ -669,10 +680,7 @@ class Session: # determine node id if not _id: - while True: - _id = self.node_id_gen.next() - if _id not in self.nodes: - break + _id = self._next_node_id() # generate name if not provided if not options: @@ -1399,7 +1407,6 @@ class Session: self.sdt.delete_node(node.id) funcs.append((node.shutdown, [], {})) utils.threadpool(funcs) - self.node_id_gen.id = 0 def write_nodes(self) -> None: """ From 18044f947411938e3f8a20f4422b521f982e1a91 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 08:48:18 -0700 Subject: [PATCH 012/146] daemon: cleaned up InterfaceData class, it now leverages dataclass, removed extra bloat and no longer requires parameters as they are optional --- daemon/core/api/grpc/grpcutils.py | 16 +++-- daemon/core/api/tlv/corehandlers.py | 4 +- daemon/core/emulator/emudata.py | 90 ++++++----------------------- daemon/tests/emane/test_emane.py | 4 +- daemon/tests/test_core.py | 2 +- 5 files changed, 31 insertions(+), 85 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index b0c1e614..cf3f2a95 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -59,19 +59,17 @@ def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData: """ interface = None if interface_proto: - name = interface_proto.name - if name == "": - name = None - mac = interface_proto.mac - if mac == "": - mac = None + name = interface_proto.name if interface_proto.name else None + mac = interface_proto.mac if interface_proto.mac else None + ip4 = interface_proto.ip4 if interface_proto.ip4 else None + ip6 = interface_proto.ip6 if interface_proto.ip6 else None interface = InterfaceData( - _id=interface_proto.id, + id=interface_proto.id, name=name, mac=mac, - ip4=interface_proto.ip4, + ip4=ip4, ip4_mask=interface_proto.ip4mask, - ip6=interface_proto.ip6, + ip6=ip6, ip6_mask=interface_proto.ip6mask, ) return interface diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 7e2cd040..a79d7d6d 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -749,7 +749,7 @@ class CoreHandler(socketserver.BaseRequestHandler): node_two_id = message.get_tlv(LinkTlvs.N2_NUMBER.value) interface_one = InterfaceData( - _id=message.get_tlv(LinkTlvs.INTERFACE1_NUMBER.value), + id=message.get_tlv(LinkTlvs.INTERFACE1_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE1_NAME.value), mac=message.get_tlv(LinkTlvs.INTERFACE1_MAC.value), ip4=message.get_tlv(LinkTlvs.INTERFACE1_IP4.value), @@ -758,7 +758,7 @@ class CoreHandler(socketserver.BaseRequestHandler): ip6_mask=message.get_tlv(LinkTlvs.INTERFACE1_IP6_MASK.value), ) interface_two = InterfaceData( - _id=message.get_tlv(LinkTlvs.INTERFACE2_NUMBER.value), + id=message.get_tlv(LinkTlvs.INTERFACE2_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE2_NAME.value), mac=message.get_tlv(LinkTlvs.INTERFACE2_MAC.value), ip4=message.get_tlv(LinkTlvs.INTERFACE2_IP4.value), diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 26e09fe8..3c33ce65 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -1,4 +1,5 @@ -from typing import List, Optional, Union +from dataclasses import dataclass +from typing import List, Union import netaddr @@ -122,87 +123,32 @@ class LinkOptions: self.opaque = None +@dataclass class InterfaceData: """ Convenience class for storing interface data. """ - def __init__( - self, - _id: int, - name: str, - mac: str, - ip4: str, - ip4_mask: int, - ip6: str, - ip6_mask: int, - ) -> None: - """ - Creates an InterfaceData object. - - :param _id: interface id - :param name: name for interface - :param mac: mac address - :param ip4: ipv4 address - :param ip4_mask: ipv4 bit mask - :param ip6: ipv6 address - :param ip6_mask: ipv6 bit mask - """ - self.id = _id - self.name = name - self.mac = mac - self.ip4 = ip4 - self.ip4_mask = ip4_mask - self.ip6 = ip6 - self.ip6_mask = ip6_mask - - def has_ip4(self) -> bool: - """ - Determines if interface has an ip4 address. - - :return: True if has ip4, False otherwise - """ - return all([self.ip4, self.ip4_mask]) - - def has_ip6(self) -> bool: - """ - Determines if interface has an ip6 address. - - :return: True if has ip6, False otherwise - """ - return all([self.ip6, self.ip6_mask]) - - def ip4_address(self) -> Optional[str]: - """ - Retrieve a string representation of the ip4 address and netmask. - - :return: ip4 string or None - """ - if self.has_ip4(): - return f"{self.ip4}/{self.ip4_mask}" - else: - return None - - def ip6_address(self) -> Optional[str]: - """ - Retrieve a string representation of the ip6 address and netmask. - - :return: ip4 string or None - """ - if self.has_ip6(): - return f"{self.ip6}/{self.ip6_mask}" - else: - return None + id: int = None + name: str = None + mac: str = None + ip4: str = None + ip4_mask: int = None + ip6: str = None + ip6_mask: int = None def get_addresses(self) -> List[str]: """ - Returns a list of ip4 and ip6 address when present. + Returns a list of ip4 and ip6 addresses when present. :return: list of addresses """ - ip4 = self.ip4_address() - ip6 = self.ip6_address() - return [i for i in [ip4, ip6] if i] + addresses = [] + if self.ip4 and self.ip4_mask: + addresses.append(f"{self.ip4}/{self.ip4_mask}") + if self.ip6 and self.ip6_mask: + addresses.append(f"{self.ip6}/{self.ip6_mask}") + return addresses class IpPrefixes: @@ -285,7 +231,7 @@ class IpPrefixes: mac = utils.random_mac() return InterfaceData( - _id=inteface_id, + id=inteface_id, name=name, ip4=ip4, ip4_mask=ip4_mask, diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index 328aa94b..62fe15e1 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -29,7 +29,9 @@ _EMANE_MODELS = [ _DIR = os.path.dirname(os.path.abspath(__file__)) -def ping(from_node, to_node, ip_prefixes, count=3): +def ping( + from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes, count: int = 3 +): address = ip_prefixes.ip4_address(to_node) try: from_node.cmd(f"ping -c {count} {address}") diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 1c40393e..88a40906 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -21,7 +21,7 @@ _MOBILITY_FILE = os.path.join(_PATH, "mobility.scen") _WIRED = [PtpNet, HubNode, SwitchNode] -def ping(from_node, to_node, ip_prefixes): +def ping(from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes): address = ip_prefixes.ip4_address(to_node) try: from_node.cmd(f"ping -c 1 {address}") From b5e53e573ac48295a3017832ac8d300e0754c3aa Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 09:12:31 -0700 Subject: [PATCH 013/146] daemon: LinkOptions now leverage dataclass and has type hinting, improve test_gui type hinting --- daemon/core/api/grpc/grpcutils.py | 10 ++------ daemon/core/api/tlv/corehandlers.py | 2 +- daemon/core/emulator/emudata.py | 40 ++++++++++++----------------- daemon/tests/test_gui.py | 14 +++++----- 4 files changed, 27 insertions(+), 39 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index cf3f2a95..7797c86e 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -86,13 +86,8 @@ def add_link_data( """ interface_one = link_interface(link_proto.interface_one) interface_two = link_interface(link_proto.interface_two) - - link_type = None - link_type_value = link_proto.type - if link_type_value is not None: - link_type = LinkTypes(link_type_value) - - options = LinkOptions(_type=link_type) + link_type = LinkTypes(link_proto.type) + options = LinkOptions(type=link_type) options_data = link_proto.options if options_data: options.delay = options_data.delay @@ -106,7 +101,6 @@ def add_link_data( options.unidirectional = options_data.unidirectional options.key = options_data.key options.opaque = options_data.opaque - return interface_one, interface_two, options diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index a79d7d6d..f3e1fbaa 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -772,7 +772,7 @@ class CoreHandler(socketserver.BaseRequestHandler): if link_type_value is not None: link_type = LinkTypes(link_type_value) - link_options = LinkOptions(_type=link_type) + link_options = LinkOptions(type=link_type) link_options.delay = message.get_tlv(LinkTlvs.DELAY.value) link_options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) link_options.session = message.get_tlv(LinkTlvs.SESSION.value) diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 3c33ce65..f47da004 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -93,34 +93,28 @@ class NodeOptions: self.alt = alt +@dataclass class LinkOptions: """ Options for creating and updating links within core. """ - def __init__(self, _type: LinkTypes = LinkTypes.WIRED) -> None: - """ - Create a LinkOptions object. - - :param _type: type of link, defaults to - wired - """ - self.type = _type - self.session = None - self.delay = None - self.bandwidth = None - self.per = None - self.dup = None - self.jitter = None - self.mer = None - self.burst = None - self.mburst = None - self.gui_attributes = None - self.unidirectional = None - self.emulation_id = None - self.network_id = None - self.key = None - self.opaque = None + type: LinkTypes = LinkTypes.WIRED + session: int = None + delay: int = None + bandwidth: int = None + per: float = None + dup: int = None + jitter: int = None + mer: int = None + burst: int = None + mburst: int = None + gui_attributes: str = None + unidirectional: bool = None + emulation_id: int = None + network_id: int = None + key: int = None + opaque: str = None @dataclass diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 89dcd7ab..800a8e62 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -511,7 +511,7 @@ class TestGui: EventTypes.DEFINITION_STATE, ], ) - def test_event_state(self, coretlv, state): + def test_event_state(self, coretlv: CoreHandler, state: EventTypes): message = coreapi.CoreEventMessage.create(0, [(EventTlvs.TYPE, state.value)]) coretlv.handle_message(message) @@ -536,7 +536,7 @@ class TestGui: coretlv.session.add_event.assert_called_once() - def test_event_save_xml(self, coretlv, tmpdir): + def test_event_save_xml(self, coretlv: CoreHandler, tmpdir): xml_file = tmpdir.join("coretlv.session.xml") file_path = xml_file.strpath coretlv.session.add_node(CoreNode) @@ -549,7 +549,7 @@ class TestGui: assert os.path.exists(file_path) - def test_event_open_xml(self, coretlv, tmpdir): + def test_event_open_xml(self, coretlv: CoreHandler, tmpdir): xml_file = tmpdir.join("coretlv.session.xml") file_path = xml_file.strpath node = coretlv.session.add_node(CoreNode) @@ -573,7 +573,7 @@ class TestGui: EventTypes.RECONFIGURE, ], ) - def test_event_service(self, coretlv, state): + def test_event_service(self, coretlv: CoreHandler, state: EventTypes): coretlv.session.broadcast_event = mock.MagicMock() node = coretlv.session.add_node(CoreNode) message = coreapi.CoreEventMessage.create( @@ -599,7 +599,7 @@ class TestGui: EventTypes.RECONFIGURE, ], ) - def test_event_mobility(self, coretlv, state): + def test_event_mobility(self, coretlv: CoreHandler, state: EventTypes): message = coreapi.CoreEventMessage.create( 0, [(EventTlvs.TYPE, state.value), (EventTlvs.NAME, "mobility:ns2script")] ) @@ -610,7 +610,7 @@ class TestGui: message = coreapi.CoreRegMessage.create(0, [(RegisterTlvs.GUI, "gui")]) coretlv.handle_message(message) - def test_register_xml(self, coretlv, tmpdir): + def test_register_xml(self, coretlv: CoreHandler, tmpdir): xml_file = tmpdir.join("coretlv.session.xml") file_path = xml_file.strpath node = coretlv.session.add_node(CoreNode) @@ -625,7 +625,7 @@ class TestGui: assert coretlv.coreemu.sessions[1].get_node(node.id, CoreNode) - def test_register_python(self, coretlv, tmpdir): + def test_register_python(self, coretlv: CoreHandler, tmpdir): xml_file = tmpdir.join("test.py") file_path = xml_file.strpath with open(file_path, "w") as f: From 7d2034df71d59a1c9ef3764e7590b5101233ee64 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 10:45:18 -0700 Subject: [PATCH 014/146] daemon: updated NodeOptions to leverage dataclass --- daemon/core/api/grpc/grpcutils.py | 16 ++++++----- daemon/core/api/grpc/server.py | 3 +- daemon/core/emulator/emudata.py | 46 +++++++++++++------------------ daemon/core/xml/corexml.py | 6 ++-- daemon/tests/test_distributed.py | 6 ++-- daemon/tests/test_grpc.py | 9 ++---- docs/scripting.md | 8 ++---- 7 files changed, 39 insertions(+), 55 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 7797c86e..5c6f3a80 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -31,17 +31,19 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption """ _id = node_proto.id _type = NodeTypes(node_proto.type) - options = NodeOptions(name=node_proto.name, model=node_proto.model) - options.icon = node_proto.icon - options.opaque = node_proto.opaque - options.image = node_proto.image - options.services = node_proto.services - options.config_services = node_proto.config_services + options = NodeOptions( + name=node_proto.name, + model=node_proto.model, + icon=node_proto.icon, + opaque=node_proto.opaque, + image=node_proto.image, + services=node_proto.services, + config_services=node_proto.config_services, + ) if node_proto.emane: options.emane = node_proto.emane if node_proto.server: options.server = node_proto.server - position = node_proto.position options.set_position(position.x, position.y) if node_proto.HasField("geo"): diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index f85529e6..03cef387 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -743,8 +743,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("edit node: %s", request) session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, NodeBase) - options = NodeOptions() - options.icon = request.icon + options = NodeOptions(icon=request.icon) if request.HasField("position"): x = request.position.x y = request.position.y diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index f47da004..7a9daf4f 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -1,5 +1,5 @@ -from dataclasses import dataclass -from typing import List, Union +from dataclasses import dataclass, field +from typing import List, Optional, Union import netaddr @@ -37,36 +37,28 @@ def link_config( ) +@dataclass class NodeOptions: """ Options for creating and updating nodes within core. """ - def __init__(self, name: str = None, model: str = "PC", image: str = None) -> None: - """ - Create a NodeOptions object. - - :param name: name of node, defaults to node class name postfix with its id - :param model: defines services for default and physical nodes, defaults to - "router" - :param image: image to use for docker nodes - """ - self.name = name - self.model = model - self.canvas = None - self.icon = None - self.opaque = None - self.services = [] - self.config_services = [] - self.x = None - self.y = None - self.lat = None - self.lon = None - self.alt = None - self.emulation_id = None - self.server = None - self.image = image - self.emane = None + name: str = None + model: Optional[str] = "PC" + canvas: int = None + icon: str = None + opaque: str = None + services: List[str] = field(default_factory=list) + config_services: List[str] = field(default_factory=list) + x: float = None + y: float = None + lat: float = None + lon: float = None + alt: float = None + emulation_id: int = None + server: str = None + image: str = None + emane: str = None def set_position(self, x: float, y: float) -> None: """ diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index efbf85c8..33005c97 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -832,8 +832,7 @@ class CoreXmlReader: icon = device_element.get("icon") clazz = device_element.get("class") image = device_element.get("image") - options = NodeOptions(name, model, image) - options.icon = icon + options = NodeOptions(name=name, model=model, image=image, icon=icon) node_type = NodeTypes.DEFAULT if clazz == "docker": @@ -874,8 +873,7 @@ class CoreXmlReader: node_type = NodeTypes[network_element.get("type")] _class = self.session.get_node_class(node_type) icon = network_element.get("icon") - options = NodeOptions(name) - options.icon = icon + options = NodeOptions(name=name, icon=icon) position_element = network_element.find("position") if position_element is not None: diff --git a/daemon/tests/test_distributed.py b/daemon/tests/test_distributed.py index 86ddaf99..0f4b1731 100644 --- a/daemon/tests/test_distributed.py +++ b/daemon/tests/test_distributed.py @@ -12,8 +12,7 @@ class TestDistributed: # when session.distributed.add_server(server_name, host) - options = NodeOptions() - options.server = server_name + options = NodeOptions(server=server_name) node = session.add_node(CoreNode, options=options) session.instantiate() @@ -30,8 +29,7 @@ class TestDistributed: # when session.distributed.add_server(server_name, host) - options = NodeOptions() - options.server = server_name + options = NodeOptions(server=server_name) node = session.add_node(HubNode, options=options) session.instantiate() diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 128863b4..c0686d71 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -710,8 +710,7 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions() - options.emane = EmaneIeee80211abgModel.name + options = NodeOptions(emane=EmaneIeee80211abgModel.name) emane_network = session.add_node(EmaneNet, options=options) session.emane.set_model(emane_network, EmaneIeee80211abgModel) config_key = "platform_id_start" @@ -737,8 +736,7 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions() - options.emane = EmaneIeee80211abgModel.name + options = NodeOptions(emane=EmaneIeee80211abgModel.name) emane_network = session.add_node(EmaneNet, options=options) session.emane.set_model(emane_network, EmaneIeee80211abgModel) config_key = "bandwidth" @@ -765,8 +763,7 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions() - options.emane = EmaneIeee80211abgModel.name + options = NodeOptions(emane=EmaneIeee80211abgModel.name) emane_network = session.add_node(EmaneNet, options=options) session.emane.set_model(emane_network, EmaneIeee80211abgModel) diff --git a/docs/scripting.md b/docs/scripting.md index 7c8205c3..8c1a705c 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -136,8 +136,7 @@ coreemu = CoreEmu() session = coreemu.create_session() # create node with custom services -options = NodeOptions() -options.services = ["ServiceName"] +options = NodeOptions(services=["ServiceName"]) node = session.add_node(options=options) # set custom file data @@ -157,7 +156,6 @@ options = NodeOptions() options.set_position(80, 50) emane_network = session.add_node(EmaneNet, options=options) -# set custom emane model config -config = {} -session.emane.set_model(emane_network, EmaneIeee80211abgModel, config) +# set custom emane model config defaults +session.emane.set_model(emane_network, EmaneIeee80211abgModel) ``` From 3691c6029f33724c670422680767f7999d7fb6ce Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 10:48:50 -0700 Subject: [PATCH 015/146] updated corexml InterfaceData instantiation to use named params --- daemon/core/xml/corexml.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 33005c97..cb25e717 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -66,7 +66,15 @@ def create_interface_data(interface_element: etree.Element) -> InterfaceData: ip4_mask = get_int(interface_element, "ip4_mask") ip6 = interface_element.get("ip6") ip6_mask = get_int(interface_element, "ip6_mask") - return InterfaceData(interface_id, name, mac, ip4, ip4_mask, ip6, ip6_mask) + return InterfaceData( + id=interface_id, + name=name, + mac=mac, + ip4=ip4, + ip4_mask=ip4_mask, + ip6=ip6, + ip6_mask=ip6_mask, + ) def create_emane_config(session: "Session") -> etree.Element: From 3be15a131604b87a9661c952c891bce7c2e48513 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 12:42:15 -0700 Subject: [PATCH 016/146] daemon: update CoreNode.newnetif to require parameters, CoreNode.newnetif now depends on being provided InterfaceData --- daemon/core/emulator/emudata.py | 39 +++++++----------------- daemon/core/emulator/session.py | 49 +++++++++++------------------- daemon/core/nodes/base.py | 42 ++++++++------------------ daemon/core/nodes/physical.py | 53 ++++++++++----------------------- daemon/tests/test_nodes.py | 18 +++++++---- 5 files changed, 71 insertions(+), 130 deletions(-) diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 7a9daf4f..b950e58c 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -1,18 +1,22 @@ from dataclasses import dataclass, field -from typing import List, Optional, Union +from typing import TYPE_CHECKING, List, Optional, Union import netaddr from core import utils from core.api.grpc.core_pb2 import LinkOptions 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 + +if TYPE_CHECKING: + from core.nodes.base import CoreNetworkBase, CoreNode + from core.nodes.physical import PhysicalNode + + LinkConfigNode = Union[CoreNetworkBase, PhysicalNode] def link_config( - node: Union[CoreNetworkBase, PhysicalNode], + node: "LinkConfigNode", interface: CoreInterface, link_options: LinkOptions, interface_two: CoreInterface = None, @@ -160,7 +164,7 @@ class IpPrefixes: if ip6_prefix: self.ip6 = netaddr.IPNetwork(ip6_prefix) - def ip4_address(self, node: CoreNode) -> str: + def ip4_address(self, node: "CoreNode") -> str: """ Convenience method to return the IP4 address for a node. @@ -171,7 +175,7 @@ class IpPrefixes: raise ValueError("ip4 prefixes have not been set") return str(self.ip4[node.id]) - def ip6_address(self, node: CoreNode) -> str: + def ip6_address(self, node: "CoreNode") -> str: """ Convenience method to return the IP6 address for a node. @@ -183,7 +187,7 @@ class IpPrefixes: return str(self.ip6[node.id]) def create_interface( - self, node: CoreNode, name: str = None, mac: str = None + self, node: "CoreNode", name: str = None, mac: str = None ) -> InterfaceData: """ Creates interface data for linking nodes, using the nodes unique id for @@ -225,24 +229,3 @@ class IpPrefixes: ip6_mask=ip6_mask, mac=mac, ) - - -def create_interface( - node: CoreNode, network: CoreNetworkBase, interface_data: InterfaceData -): - """ - Create an interface for a node on a network using provided interface data. - - :param node: node to create interface for - :param network: network to associate interface with - :param interface_data: interface data - :return: created interface - """ - node.newnetif( - network, - addrlist=interface_data.get_addresses(), - hwaddr=interface_data.mac, - ifindex=interface_data.id, - ifname=interface_data.name, - ) - return node.netif(interface_data.id) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 24fe05bd..d258ce9f 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -19,13 +19,7 @@ from core.emane.emanemanager import EmaneManager from core.emane.nodes import EmaneNet from core.emulator.data import ConfigData, EventData, ExceptionData, FileData, LinkData from core.emulator.distributed import DistributedController -from core.emulator.emudata import ( - InterfaceData, - LinkOptions, - NodeOptions, - create_interface, - link_config, -) +from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions, link_config from core.emulator.enumerations import ( EventTypes, ExceptionLevels, @@ -360,11 +354,11 @@ class Session: node_one.name, net_one.name, ) - interface = create_interface(node_one, net_one, interface_one) - node_one_interface = interface + ifindex = node_one.newnetif(net_one, interface_one) + node_one_interface = node_one.netif(ifindex) wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) if not wireless_net: - link_config(net_one, interface, link_options) + link_config(net_one, node_one_interface, link_options) # network to node if node_two and net_one: @@ -373,11 +367,11 @@ class Session: node_two.name, net_one.name, ) - interface = create_interface(node_two, net_one, interface_two) - node_two_interface = interface + ifindex = node_two.newnetif(net_one, interface_two) + node_two_interface = node_two.netif(ifindex) wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) if not link_options.unidirectional and not wireless_net: - link_config(net_one, interface, link_options) + link_config(net_one, node_two_interface, link_options) # network to network if net_one and net_two: @@ -1797,35 +1791,28 @@ class Session: control_net = self.add_remove_control_net(net_index, remove, conf_required) if not control_net: return - if not node: return - # ctrl# already exists if node.netif(control_net.CTRLIF_IDX_BASE + net_index): return - - control_ip = node.id - try: - address = control_net.prefix[control_ip] - prefix = control_net.prefix.prefixlen - addrlist = [f"{address}/{prefix}"] + ip4 = control_net.prefix[node.id] + ip4_mask = control_net.prefix.prefixlen + interface = InterfaceData( + id=control_net.CTRLIF_IDX_BASE + net_index, + name=f"ctrl{net_index}", + mac=utils.random_mac(), + ip4=ip4, + ip4_mask=ip4_mask, + ) + ifindex = node.newnetif(control_net, interface) + node.netif(ifindex).control = True except ValueError: msg = f"Control interface not added to node {node.id}. " msg += f"Invalid control network prefix ({control_net.prefix}). " msg += "A longer prefix length may be required for this many nodes." logging.exception(msg) - return - - interface1 = node.newnetif( - net=control_net, - ifindex=control_net.CTRLIF_IDX_BASE + net_index, - ifname=f"ctrl{net_index}", - hwaddr=utils.random_mac(), - addrlist=addrlist, - ) - node.netif(interface1).control = True def update_control_interface_hosts( self, net_index: int = 0, remove: bool = False diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 662815ef..e88912ac 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -15,6 +15,7 @@ from core import utils from core.configservice.dependencies import ConfigServiceDependencies from core.constants import MOUNT_BIN, VNODED_BIN from core.emulator.data import LinkData, NodeData +from core.emulator.emudata import InterfaceData from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError from core.nodes.client import VnodeClient @@ -845,53 +846,36 @@ class CoreNode(CoreNodeBase): interface_name = self.ifname(ifindex) self.node_net_client.device_up(interface_name) - def newnetif( - self, - net: "CoreNetworkBase" = None, - addrlist: List[str] = None, - hwaddr: str = None, - ifindex: int = None, - ifname: str = None, - ) -> int: + def newnetif(self, net: "CoreNetworkBase", interface: InterfaceData) -> int: """ Create a new network interface. :param net: network to associate with - :param addrlist: addresses to add on the interface - :param hwaddr: hardware address to set for interface - :param ifindex: index of interface to create - :param ifname: name for interface + :param interface: interface data for new interface :return: interface index """ - if not addrlist: - addrlist = [] - + addresses = interface.get_addresses() with self.lock: # TODO: emane specific code - if net is not None and net.is_emane is True: - ifindex = self.newtuntap(ifindex, ifname) + if net.is_emane is True: + ifindex = self.newtuntap(interface.id, interface.name) # TUN/TAP is not ready for addressing yet; the device may # take some time to appear, and installing it into a # namespace after it has been bound removes addressing; # save addresses with the interface now self.attachnet(ifindex, net) netif = self.netif(ifindex) - netif.sethwaddr(hwaddr) - for address in utils.make_tuple(addrlist): + netif.sethwaddr(interface.mac) + for address in addresses: netif.addaddr(address) return ifindex else: - ifindex = self.newveth(ifindex, ifname) - - if net is not None: - self.attachnet(ifindex, net) - - if hwaddr: - self.sethwaddr(ifindex, hwaddr) - - for address in utils.make_tuple(addrlist): + ifindex = self.newveth(interface.id, interface.name) + self.attachnet(ifindex, net) + if interface.mac: + self.sethwaddr(ifindex, interface.mac) + for address in addresses: self.addaddr(ifindex, address) - self.ifup(ifindex) return ifindex diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index e5db8a80..ec531505 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -10,10 +10,11 @@ from typing import IO, TYPE_CHECKING, List, Optional, Tuple from core import utils from core.constants import MOUNT_BIN, UMOUNT_BIN from core.emulator.distributed import DistributedServer +from core.emulator.emudata import InterfaceData from core.emulator.enumerations import NodeTypes, TransportType from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNetworkBase, CoreNodeBase -from core.nodes.interface import CoreInterface, Veth +from core.nodes.interface import CoreInterface from core.nodes.network import CoreNetwork, GreTap if TYPE_CHECKING: @@ -168,37 +169,25 @@ class PhysicalNode(CoreNodeBase): self.ifindex += 1 return ifindex - def newnetif( - self, - net: Veth = None, - addrlist: List[str] = None, - hwaddr: str = None, - ifindex: int = None, - ifname: str = None, - ) -> int: + def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int: logging.info("creating interface") - if not addrlist: - addrlist = [] - - if self.up and net is None: - raise NotImplementedError - + addresses = interface.get_addresses() + ifindex = interface.id if ifindex is None: ifindex = self.newifindex() - - if ifname is None: - ifname = f"gt{ifindex}" - + name = interface.name + if name is None: + name = f"gt{ifindex}" if self.up: # this is reached when this node is linked to a network node # tunnel to net not built yet, so build it now and adopt it _, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) - self.adoptnetif(remote_tap, ifindex, hwaddr, addrlist) + self.adoptnetif(remote_tap, ifindex, interface.mac, addresses) return ifindex else: # this is reached when configuring services (self.up=False) - netif = GreTap(node=self, name=ifname, session=self.session, start=False) - self.adoptnetif(netif, ifindex, hwaddr, addrlist) + netif = GreTap(node=self, name=name, session=self.session, start=False) + self.adoptnetif(netif, ifindex, interface.mac, addresses) return ifindex def privatedir(self, path: str) -> None: @@ -320,28 +309,19 @@ class Rj45Node(CoreNodeBase): self.up = False self.restorestate() - def newnetif( - self, - net: CoreNetworkBase = None, - addrlist: List[str] = None, - hwaddr: str = None, - ifindex: int = None, - ifname: str = None, - ) -> int: + def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int: """ This is called when linking with another node. Since this node represents an interface, we do not create another object here, but attach ourselves to the given network. :param net: new network instance - :param addrlist: address list - :param hwaddr: hardware address - :param ifindex: interface index - :param ifname: interface name + :param interface: interface data for new interface :return: interface index :raises ValueError: when an interface has already been created, one max """ with self.lock: + ifindex = interface.id if ifindex is None: ifindex = 0 if self.interface.net is not None: @@ -350,9 +330,8 @@ class Rj45Node(CoreNodeBase): self.ifindex = ifindex if net is not None: self.interface.attachnet(net) - if addrlist: - for addr in utils.make_tuple(addrlist): - self.addaddr(addr) + for addr in interface.get_addresses(): + self.addaddr(addr) return ifindex def delnetif(self, ifindex: int) -> None: diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 65b17949..26e78367 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -1,6 +1,6 @@ import pytest -from core.emulator.emudata import NodeOptions +from core.emulator.emudata import InterfaceData, NodeOptions from core.emulator.session import Session from core.errors import CoreError from core.nodes.base import CoreNode @@ -52,7 +52,9 @@ class TestNodes: def test_node_sethwaddr(self, session: Session): # given node = session.add_node(CoreNode) - index = node.newnetif() + switch = session.add_node(SwitchNode) + interface_data = InterfaceData() + index = node.newnetif(switch, interface_data) interface = node.netif(index) mac = "aa:aa:aa:ff:ff:ff" @@ -65,7 +67,9 @@ class TestNodes: def test_node_sethwaddr_exception(self, session: Session): # given node = session.add_node(CoreNode) - index = node.newnetif() + switch = session.add_node(SwitchNode) + interface_data = InterfaceData() + index = node.newnetif(switch, interface_data) node.netif(index) mac = "aa:aa:aa:ff:ff:fff" @@ -76,7 +80,9 @@ class TestNodes: def test_node_addaddr(self, session: Session): # given node = session.add_node(CoreNode) - index = node.newnetif() + switch = session.add_node(SwitchNode) + interface_data = InterfaceData() + index = node.newnetif(switch, interface_data) interface = node.netif(index) addr = "192.168.0.1/24" @@ -89,7 +95,9 @@ class TestNodes: def test_node_addaddr_exception(self, session): # given node = session.add_node(CoreNode) - index = node.newnetif() + switch = session.add_node(SwitchNode) + interface_data = InterfaceData() + index = node.newnetif(switch, interface_data) node.netif(index) addr = "256.168.0.1/24" From 2965273f58a3782d75f5d10700c0e5fbe50d0ea6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 13:41:31 -0700 Subject: [PATCH 017/146] daemon: CoreNetworkBase.linkconfig now takes a LinkOptions object, removed usage of emudata.link_config --- daemon/core/emane/commeffect.py | 23 ++++++++------------- daemon/core/emane/emanemodel.py | 16 +++------------ daemon/core/emane/nodes.py | 12 +++-------- daemon/core/emulator/emudata.py | 35 ++------------------------------ daemon/core/emulator/session.py | 34 +++++++++++++++---------------- daemon/core/location/mobility.py | 9 ++++++-- daemon/core/nodes/base.py | 17 +++------------- daemon/core/nodes/network.py | 21 ++++++++----------- daemon/core/nodes/physical.py | 13 +++--------- 9 files changed, 53 insertions(+), 127 deletions(-) diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 99fdb9b1..b7060e96 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -11,6 +11,7 @@ from lxml import etree from core.config import ConfigGroup, Configuration from core.emane import emanemanifest, emanemodel from core.emane.nodes import EmaneNet +from core.emulator.emudata import LinkOptions from core.emulator.enumerations import TransportType from core.nodes.interface import CoreInterface from core.xml import emanexml @@ -114,14 +115,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): emanexml.create_file(shim_element, "shim", shim_file) def linkconfig( - self, - netif: CoreInterface, - bw: float = None, - delay: float = None, - loss: float = None, - duplicate: float = None, - jitter: float = None, - netif2: CoreInterface = None, + self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None ) -> None: """ Generate CommEffect events when a Link Message is received having @@ -142,15 +136,14 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): emane_node = self.session.get_node(self.id, EmaneNet) nemid = emane_node.getnemid(netif) nemid2 = emane_node.getnemid(netif2) - mbw = bw logging.info("sending comm effect event") event.append( nemid, - latency=convert_none(delay), - jitter=convert_none(jitter), - loss=convert_none(loss), - duplicate=convert_none(duplicate), - unicast=int(convert_none(bw)), - broadcast=int(convert_none(mbw)), + latency=convert_none(options.delay), + jitter=convert_none(options.jitter), + loss=convert_none(options.per), + duplicate=convert_none(options.dup), + unicast=int(convert_none(options.bandwidth)), + broadcast=int(convert_none(options.bandwidth)), ) service.publish(nemid2, event) diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 3a21643b..f42caa14 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -8,6 +8,7 @@ from typing import Dict, List from core.config import ConfigGroup, Configuration from core.emane import emanemanifest from core.emane.nodes import EmaneNet +from core.emulator.emudata import LinkOptions from core.emulator.enumerations import ConfigDataTypes, TransportType from core.errors import CoreError from core.location.mobility import WirelessModel @@ -155,24 +156,13 @@ class EmaneModel(WirelessModel): logging.exception("error during update") def linkconfig( - self, - netif: CoreInterface, - bw: float = None, - delay: float = None, - loss: float = None, - duplicate: float = None, - jitter: float = None, - netif2: CoreInterface = None, + self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None ) -> None: """ Invoked when a Link Message is received. Default is unimplemented. :param netif: interface one - :param bw: bandwidth to set to - :param delay: packet delay to set to - :param loss: packet loss to set to - :param duplicate: duplicate percentage to set to - :param jitter: jitter to set to + :param options: options for configuring link :param netif2: interface two :return: nothing """ diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index bbe59b95..f4de8f47 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type from core.emulator.data import LinkData from core.emulator.distributed import DistributedServer +from core.emulator.emudata import LinkOptions from core.emulator.enumerations import ( LinkTypes, MessageFlags, @@ -60,21 +61,14 @@ class EmaneNet(CoreNetworkBase): self.mobility = None def linkconfig( - self, - netif: CoreInterface, - bw: float = None, - delay: float = None, - loss: float = None, - duplicate: float = None, - jitter: float = None, - netif2: CoreInterface = None, + self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None ) -> None: """ The CommEffect model supports link configuration. """ if not self.model: return - self.model.linkconfig(netif, bw, delay, loss, duplicate, jitter, netif2) + self.model.linkconfig(netif, options, netif2) def config(self, conf: str) -> None: self.conf = conf diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index b950e58c..3ccf11cc 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -1,44 +1,13 @@ from dataclasses import dataclass, field -from typing import TYPE_CHECKING, List, Optional, Union +from typing import TYPE_CHECKING, List, Optional import netaddr from core import utils -from core.api.grpc.core_pb2 import LinkOptions from core.emulator.enumerations import LinkTypes -from core.nodes.interface import CoreInterface if TYPE_CHECKING: - from core.nodes.base import CoreNetworkBase, CoreNode - from core.nodes.physical import PhysicalNode - - LinkConfigNode = Union[CoreNetworkBase, PhysicalNode] - - -def link_config( - node: "LinkConfigNode", - interface: CoreInterface, - link_options: LinkOptions, - interface_two: CoreInterface = None, -) -> None: - """ - Convenience method for configuring a link, - - :param node: network to configure link for - :param interface: interface to configure - :param link_options: data to configure link with - :param interface_two: other interface associated, default is None - :return: nothing - """ - node.linkconfig( - interface, - link_options.bandwidth, - link_options.delay, - link_options.per, - link_options.dup, - link_options.jitter, - interface_two, - ) + from core.nodes.base import CoreNode @dataclass diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index d258ce9f..59bd3cf3 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -19,7 +19,7 @@ from core.emane.emanemanager import EmaneManager from core.emane.nodes import EmaneNet from core.emulator.data import ConfigData, EventData, ExceptionData, FileData, LinkData from core.emulator.distributed import DistributedController -from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions, link_config +from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions from core.emulator.enumerations import ( EventTypes, ExceptionLevels, @@ -358,7 +358,7 @@ class Session: node_one_interface = node_one.netif(ifindex) wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) if not wireless_net: - link_config(net_one, node_one_interface, link_options) + net_one.linkconfig(node_one_interface, link_options) # network to node if node_two and net_one: @@ -371,7 +371,7 @@ class Session: node_two_interface = node_two.netif(ifindex) wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) if not link_options.unidirectional and not wireless_net: - link_config(net_one, node_two_interface, link_options) + net_one.linkconfig(node_two_interface, link_options) # network to network if net_one and net_two: @@ -382,18 +382,16 @@ class Session: ) interface = net_one.linknet(net_two) node_one_interface = interface - link_config(net_one, interface, link_options) - + net_one.linkconfig(interface, link_options) if not link_options.unidirectional: interface.swapparams("_params_up") - link_config(net_two, interface, link_options) + net_two.linkconfig(interface, link_options) interface.swapparams("_params_up") # a tunnel node was found for the nodes addresses = [] if not node_one and all([net_one, interface_one]): addresses.extend(interface_one.get_addresses()) - if not node_two and all([net_two, interface_two]): addresses.extend(interface_two.get_addresses()) @@ -418,14 +416,14 @@ class Session: node_one.adoptnetif( tunnel, interface_one.id, interface_one.mac, addresses ) - link_config(node_one, tunnel, link_options) + node_one.linkconfig(tunnel, link_options) elif node_two and isinstance(node_two, PhysicalNode): logging.info("adding link for physical node: %s", node_two.name) addresses = interface_two.get_addresses() node_two.adoptnetif( tunnel, interface_two.id, interface_two.mac, addresses ) - link_config(node_two, tunnel, link_options) + node_two.linkconfig(tunnel, link_options) finally: if node_one: node_one.lock.release() @@ -596,28 +594,28 @@ class Session: if upstream: interface.swapparams("_params_up") - link_config(net_one, interface, link_options) + net_one.linkconfig(interface, link_options) interface.swapparams("_params_up") else: - link_config(net_one, interface, link_options) + net_one.linkconfig(interface, link_options) if not link_options.unidirectional: if upstream: - link_config(net_two, interface, link_options) + net_two.linkconfig(interface, link_options) else: interface.swapparams("_params_up") - link_config(net_two, interface, link_options) + net_two.linkconfig(interface, link_options) interface.swapparams("_params_up") else: raise CoreError("modify link for unknown nodes") elif not node_one: # node1 = layer 2node, node2 = layer3 node interface = node_two.netif(interface_two_id) - link_config(net_one, interface, link_options) + net_one.linkconfig(interface, link_options) elif not node_two: # node2 = layer 2node, node1 = layer3 node interface = node_one.netif(interface_one_id) - link_config(net_one, interface, link_options) + net_one.linkconfig(interface, link_options) else: common_networks = node_one.commonnets(node_two) if not common_networks: @@ -630,10 +628,10 @@ class Session: ): continue - link_config(net_one, interface_one, link_options, interface_two) + net_one.linkconfig(interface_one, link_options, interface_two) if not link_options.unidirectional: - link_config( - net_one, interface_two, link_options, interface_one + net_one.linkconfig( + interface_two, link_options, interface_one ) finally: if node_one: diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 5041f144..3ca46418 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -14,6 +14,7 @@ from typing import TYPE_CHECKING, Dict, List, Tuple from core import utils from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager from core.emulator.data import EventData, LinkData +from core.emulator.emudata import LinkOptions from core.emulator.enumerations import ( ConfigDataTypes, EventTypes, @@ -334,9 +335,13 @@ class BasicRangeModel(WirelessModel): """ with self._netifslock: for netif in self._netifs: - self.wlan.linkconfig( - netif, self.bw, self.delay, self.loss, jitter=self.jitter + options = LinkOptions( + bandwidth=self.bw, + delay=self.delay, + per=self.loss, + jitter=self.jitter, ) + self.wlan.linkconfig(netif, options) def get_position(self, netif: CoreInterface) -> Tuple[float, float, float]: """ diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index e88912ac..0c76d6a2 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -15,7 +15,7 @@ from core import utils from core.configservice.dependencies import ConfigServiceDependencies from core.constants import MOUNT_BIN, VNODED_BIN from core.emulator.data import LinkData, NodeData -from core.emulator.emudata import InterfaceData +from core.emulator.emudata import InterfaceData, LinkOptions from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError from core.nodes.client import VnodeClient @@ -1147,24 +1147,13 @@ class CoreNetworkBase(NodeBase): return all_links def linkconfig( - self, - netif: CoreInterface, - bw: float = None, - delay: float = None, - loss: float = None, - duplicate: float = None, - jitter: float = None, - netif2: float = None, + self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None ) -> None: """ Configure link parameters by applying tc queuing disciplines on the interface. :param netif: interface one - :param bw: bandwidth to set to - :param delay: packet delay to set to - :param loss: packet loss to set to - :param duplicate: duplicate percentage to set to - :param jitter: jitter to set to + :param options: options for configuring link :param netif2: interface two :return: nothing """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index b08d87d4..095fbe9b 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -12,6 +12,7 @@ import netaddr from core import utils from core.constants import EBTABLES_BIN, TC_BIN from core.emulator.data import LinkData, NodeData +from core.emulator.emudata import LinkOptions from core.emulator.enumerations import ( LinkTypes, MessageFlags, @@ -441,24 +442,13 @@ class CoreNetwork(CoreNetworkBase): ebq.ebchange(self) def linkconfig( - self, - netif: CoreInterface, - bw: float = None, - delay: float = None, - loss: float = None, - duplicate: float = None, - jitter: float = None, - netif2: float = None, + self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None ) -> None: """ Configure link parameters by applying tc queuing disciplines on the interface. :param netif: interface one - :param bw: bandwidth to set to - :param delay: packet delay to set to - :param loss: packet loss to set to - :param duplicate: duplicate percentage to set to - :param jitter: jitter to set to + :param options: options for configuring link :param netif2: interface two :return: nothing """ @@ -466,6 +456,7 @@ class CoreNetwork(CoreNetworkBase): tc = f"{TC_BIN} qdisc replace dev {devname}" parent = "root" changed = False + bw = options.bandwidth if netif.setparam("bw", bw): # from tc-tbf(8): minimum value for burst is rate / kernel_hz burst = max(2 * netif.mtu, int(bw / 1000)) @@ -489,13 +480,17 @@ class CoreNetwork(CoreNetworkBase): if netif.getparam("has_tbf"): parent = "parent 1:1" netem = "netem" + delay = options.delay changed = max(changed, netif.setparam("delay", delay)) + loss = options.per if loss is not None: loss = float(loss) changed = max(changed, netif.setparam("loss", loss)) + duplicate = options.dup if duplicate is not None: duplicate = int(duplicate) changed = max(changed, netif.setparam("duplicate", duplicate)) + jitter = options.jitter changed = max(changed, netif.setparam("jitter", jitter)) if not changed: return diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index ec531505..018ca60d 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -10,7 +10,7 @@ from typing import IO, TYPE_CHECKING, List, Optional, Tuple from core import utils from core.constants import MOUNT_BIN, UMOUNT_BIN from core.emulator.distributed import DistributedServer -from core.emulator.emudata import InterfaceData +from core.emulator.emudata import InterfaceData, LinkOptions from core.emulator.enumerations import NodeTypes, TransportType from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNetworkBase, CoreNodeBase @@ -144,21 +144,14 @@ class PhysicalNode(CoreNodeBase): self.net_client.device_up(netif.localname) def linkconfig( - self, - netif: CoreInterface, - bw: float = None, - delay: float = None, - loss: float = None, - duplicate: float = None, - jitter: float = None, - netif2: CoreInterface = None, + self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None ) -> None: """ Apply tc queing disciplines using linkconfig. """ linux_bridge = CoreNetwork(session=self.session, start=False) linux_bridge.up = True - linux_bridge.linkconfig(netif, bw, delay, loss, duplicate, jitter, netif2) + linux_bridge.linkconfig(netif, options, netif2) del linux_bridge def newifindex(self) -> int: From 21da67069803f323f1cb1e57290a278a9d53fc09 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 13:46:26 -0700 Subject: [PATCH 018/146] daemon: renamed link_options to options in both session.add_link and session.update_link --- daemon/core/api/grpc/server.py | 2 +- daemon/core/emulator/session.py | 60 ++++++++++++++++----------------- daemon/tests/test_links.py | 2 +- daemon/tests/test_xml.py | 4 +-- 4 files changed, 32 insertions(+), 36 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 03cef387..7d7f7c80 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -854,7 +854,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): node_two_id = request.link.node_two_id interface_one, interface_two, options = grpcutils.add_link_data(request.link) node_one_interface, node_two_interface = session.add_link( - node_one_id, node_two_id, interface_one, interface_two, link_options=options + node_one_id, node_two_id, interface_one, interface_two, options=options ) interface_one_proto = None interface_two_proto = None diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 59bd3cf3..a1e71612 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -299,7 +299,7 @@ class Session: node_two_id: int, interface_one: InterfaceData = None, interface_two: InterfaceData = None, - link_options: LinkOptions = None, + options: LinkOptions = None, ) -> Tuple[CoreInterface, CoreInterface]: """ Add a link between nodes. @@ -310,12 +310,12 @@ class Session: data, defaults to none :param interface_two: node two interface data, defaults to none - :param link_options: data for creating link, + :param options: data for creating link, defaults to no options :return: tuple of created core interfaces, depending on link """ - if not link_options: - link_options = LinkOptions() + if not options: + options = LinkOptions() # get node objects identified by link data node_one, node_two, net_one, net_two, tunnel = self._link_nodes( @@ -332,7 +332,7 @@ class Session: try: # wireless link - if link_options.type == LinkTypes.WIRELESS: + if options.type == LinkTypes.WIRELESS: objects = [node_one, node_two, net_one, net_two] self._link_wireless(objects, connect=True) # wired link @@ -358,7 +358,7 @@ class Session: node_one_interface = node_one.netif(ifindex) wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) if not wireless_net: - net_one.linkconfig(node_one_interface, link_options) + net_one.linkconfig(node_one_interface, options) # network to node if node_two and net_one: @@ -370,8 +370,8 @@ class Session: ifindex = node_two.newnetif(net_one, interface_two) node_two_interface = node_two.netif(ifindex) wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) - if not link_options.unidirectional and not wireless_net: - net_one.linkconfig(node_two_interface, link_options) + if not options.unidirectional and not wireless_net: + net_one.linkconfig(node_two_interface, options) # network to network if net_one and net_two: @@ -382,10 +382,10 @@ class Session: ) interface = net_one.linknet(net_two) node_one_interface = interface - net_one.linkconfig(interface, link_options) - if not link_options.unidirectional: + net_one.linkconfig(interface, options) + if not options.unidirectional: interface.swapparams("_params_up") - net_two.linkconfig(interface, link_options) + net_two.linkconfig(interface, options) interface.swapparams("_params_up") # a tunnel node was found for the nodes @@ -396,7 +396,7 @@ class Session: addresses.extend(interface_two.get_addresses()) # tunnel node logic - key = link_options.key + key = options.key if key and isinstance(net_one, TunnelNode): logging.info("setting tunnel key for: %s", net_one.name) net_one.setkey(key) @@ -416,14 +416,14 @@ class Session: node_one.adoptnetif( tunnel, interface_one.id, interface_one.mac, addresses ) - node_one.linkconfig(tunnel, link_options) + node_one.linkconfig(tunnel, options) elif node_two and isinstance(node_two, PhysicalNode): logging.info("adding link for physical node: %s", node_two.name) addresses = interface_two.get_addresses() node_two.adoptnetif( tunnel, interface_two.id, interface_two.mac, addresses ) - node_two.linkconfig(tunnel, link_options) + node_two.linkconfig(tunnel, options) finally: if node_one: node_one.lock.release() @@ -547,7 +547,7 @@ class Session: node_two_id: int, interface_one_id: int = None, interface_two_id: int = None, - link_options: LinkOptions = None, + options: LinkOptions = None, ) -> None: """ Update link information between nodes. @@ -556,13 +556,13 @@ class Session: :param node_two_id: node two id :param interface_one_id: interface id for node one :param interface_two_id: interface id for node two - :param link_options: data to update link with + :param options: data to update link with :return: nothing :raises core.CoreError: when updating a wireless type link, when there is a unknown link between networks """ - if not link_options: - link_options = LinkOptions() + if not options: + options = LinkOptions() # get node objects identified by link data node_one, node_two, net_one, net_two, _tunnel = self._link_nodes( @@ -576,7 +576,7 @@ class Session: try: # wireless link - if link_options.type == LinkTypes.WIRELESS: + if options.type == LinkTypes.WIRELESS: raise CoreError("cannot update wireless link") else: if not node_one and not node_two: @@ -594,28 +594,28 @@ class Session: if upstream: interface.swapparams("_params_up") - net_one.linkconfig(interface, link_options) + net_one.linkconfig(interface, options) interface.swapparams("_params_up") else: - net_one.linkconfig(interface, link_options) + net_one.linkconfig(interface, options) - if not link_options.unidirectional: + if not options.unidirectional: if upstream: - net_two.linkconfig(interface, link_options) + net_two.linkconfig(interface, options) else: interface.swapparams("_params_up") - net_two.linkconfig(interface, link_options) + net_two.linkconfig(interface, options) interface.swapparams("_params_up") else: raise CoreError("modify link for unknown nodes") elif not node_one: # node1 = layer 2node, node2 = layer3 node interface = node_two.netif(interface_two_id) - net_one.linkconfig(interface, link_options) + net_one.linkconfig(interface, options) elif not node_two: # node2 = layer 2node, node1 = layer3 node interface = node_one.netif(interface_one_id) - net_one.linkconfig(interface, link_options) + net_one.linkconfig(interface, options) else: common_networks = node_one.commonnets(node_two) if not common_networks: @@ -628,11 +628,9 @@ class Session: ): continue - net_one.linkconfig(interface_one, link_options, interface_two) - if not link_options.unidirectional: - net_one.linkconfig( - interface_two, link_options, interface_one - ) + net_one.linkconfig(interface_one, options, interface_two) + if not options.unidirectional: + net_one.linkconfig(interface_two, options, interface_one) finally: if node_one: node_one.lock.release() diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 94b2e53f..9736537e 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -105,7 +105,7 @@ class TestLinks: node_one.id, node_two.id, interface_one_id=interface_one_data.id, - link_options=link_options, + options=link_options, ) # then diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 70117fb8..c40a9ef3 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -309,9 +309,7 @@ class TestXml: link_options.jitter = 10 link_options.delay = 30 link_options.dup = 5 - session.add_link( - node_one.id, switch.id, interface_one, link_options=link_options - ) + session.add_link(node_one.id, switch.id, interface_one, options=link_options) # instantiate session session.instantiate() From d71d84fae7da24cabe7a94aee2b60a84786959b0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 18:40:50 -0700 Subject: [PATCH 019/146] daemon: updated IpPrefixes and InterfaceHelper to remove duplicate code --- daemon/core/api/grpc/client.py | 77 ++++--------------------------- daemon/core/emulator/emudata.py | 78 +++++++++++++++++--------------- daemon/tests/emane/test_emane.py | 2 +- daemon/tests/test_core.py | 2 +- 4 files changed, 53 insertions(+), 106 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index c1d0e2fd..64b8d29f 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -8,9 +8,7 @@ from contextlib import contextmanager from typing import Any, Callable, Dict, Generator, Iterable, List import grpc -import netaddr -from core import utils from core.api.grpc import configservices_pb2, core_pb2, core_pb2_grpc from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsRequest, @@ -94,6 +92,7 @@ from core.api.grpc.wlan_pb2 import ( WlanLinkRequest, WlanLinkResponse, ) +from core.emulator.emudata import IpPrefixes class InterfaceHelper: @@ -109,78 +108,20 @@ class InterfaceHelper: :param ip6_prefix: ip6 prefix to use for generation :raises ValueError: when both ip4 and ip6 prefixes have not been provided """ - if not ip4_prefix and not ip6_prefix: - raise ValueError("ip4 or ip6 must be provided") - - self.ip4 = None - if ip4_prefix: - self.ip4 = netaddr.IPNetwork(ip4_prefix) - self.ip6 = None - if ip6_prefix: - self.ip6 = netaddr.IPNetwork(ip6_prefix) - - def ip4_address(self, node_id: int) -> str: - """ - Convenience method to return the IP4 address for a node. - - :param node_id: node id to get IP4 address for - :return: IP4 address or None - """ - if not self.ip4: - raise ValueError("ip4 prefixes have not been set") - return str(self.ip4[node_id]) - - def ip6_address(self, node_id: int) -> str: - """ - Convenience method to return the IP6 address for a node. - - :param node_id: node id to get IP6 address for - :return: IP4 address or None - """ - if not self.ip6: - raise ValueError("ip6 prefixes have not been set") - return str(self.ip6[node_id]) + self.prefixes = IpPrefixes(ip4_prefix, ip6_prefix) def create_interface( self, node_id: int, interface_id: int, name: str = None, mac: str = None ) -> core_pb2.Interface: - """ - Creates interface data for linking nodes, using the nodes unique id for - generation, along with a random mac address, unless provided. - - :param node_id: node id to create interface for - :param interface_id: interface id for interface - :param name: name to set for interface, default is eth{id} - :param mac: mac address to use for this interface, default is random - generation - :return: new interface data for the provided node - """ - # generate ip4 data - ip4 = None - ip4_mask = None - if self.ip4: - ip4 = self.ip4_address(node_id) - ip4_mask = self.ip4.prefixlen - - # generate ip6 data - ip6 = None - ip6_mask = None - if self.ip6: - ip6 = self.ip6_address(node_id) - ip6_mask = self.ip6.prefixlen - - # random mac - if not mac: - mac = utils.random_mac() - + interface_data = self.prefixes.gen_interface(node_id, name, mac) return core_pb2.Interface( id=interface_id, - name=name, - ip4=ip4, - ip4mask=ip4_mask, - ip6=ip6, - ip6mask=ip6_mask, - mac=str(mac), + name=interface_data.name, + ip4=interface_data.ip4, + ip4mask=interface_data.ip4_mask, + ip6=interface_data.ip6, + ip6mask=interface_data.ip6_mask, + mac=interface_data.mac, ) diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 3ccf11cc..b6dbd57c 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -133,27 +133,60 @@ class IpPrefixes: if ip6_prefix: self.ip6 = netaddr.IPNetwork(ip6_prefix) - def ip4_address(self, node: "CoreNode") -> str: + def ip4_address(self, node_id: int) -> str: """ Convenience method to return the IP4 address for a node. - :param node: node to get IP4 address for + :param node_id: node id to get IP4 address for :return: IP4 address or None """ if not self.ip4: raise ValueError("ip4 prefixes have not been set") - return str(self.ip4[node.id]) + return str(self.ip4[node_id]) - def ip6_address(self, node: "CoreNode") -> str: + def ip6_address(self, node_id: int) -> str: """ Convenience method to return the IP6 address for a node. - :param node: node to get IP6 address for + :param node_id: node id to get IP6 address for :return: IP4 address or None """ if not self.ip6: raise ValueError("ip6 prefixes have not been set") - return str(self.ip6[node.id]) + return str(self.ip6[node_id]) + + def gen_interface(self, node_id: int, name: str = None, mac: str = None): + """ + Creates interface data for linking nodes, using the nodes unique id for + generation, along with a random mac address, unless provided. + + :param node_id: node id to create an interface for + :param name: name to set for interface, default is eth{id} + :param mac: mac address to use for this interface, default is random + generation + :return: new interface data for the provided node + """ + # generate ip4 data + ip4 = None + ip4_mask = None + if self.ip4: + ip4 = self.ip4_address(node_id) + ip4_mask = self.ip4.prefixlen + + # generate ip6 data + ip6 = None + ip6_mask = None + if self.ip6: + ip6 = self.ip6_address(node_id) + ip6_mask = self.ip6.prefixlen + + # random mac + if not mac: + mac = utils.random_mac() + + return InterfaceData( + name=name, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask, mac=mac + ) def create_interface( self, node: "CoreNode", name: str = None, mac: str = None @@ -168,33 +201,6 @@ class IpPrefixes: generation :return: new interface data for the provided node """ - # interface id - inteface_id = node.newifindex() - - # generate ip4 data - ip4 = None - ip4_mask = None - if self.ip4: - ip4 = self.ip4_address(node) - ip4_mask = self.ip4.prefixlen - - # generate ip6 data - ip6 = None - ip6_mask = None - if self.ip6: - ip6 = self.ip6_address(node) - ip6_mask = self.ip6.prefixlen - - # random mac - if not mac: - mac = utils.random_mac() - - return InterfaceData( - id=inteface_id, - name=name, - ip4=ip4, - ip4_mask=ip4_mask, - ip6=ip6, - ip6_mask=ip6_mask, - mac=mac, - ) + interface = self.gen_interface(node.id, name, mac) + interface.id = node.newifindex() + return interface diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index 62fe15e1..2d90ebcc 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -32,7 +32,7 @@ _DIR = os.path.dirname(os.path.abspath(__file__)) def ping( from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes, count: int = 3 ): - address = ip_prefixes.ip4_address(to_node) + address = ip_prefixes.ip4_address(to_node.id) try: from_node.cmd(f"ping -c {count} {address}") status = 0 diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 88a40906..68515a41 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -22,7 +22,7 @@ _WIRED = [PtpNet, HubNode, SwitchNode] def ping(from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes): - address = ip_prefixes.ip4_address(to_node) + address = ip_prefixes.ip4_address(to_node.id) try: from_node.cmd(f"ping -c 1 {address}") status = 0 From f73c617ecfc81ce4b393e39b631a591e144ca725 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 18:53:42 -0700 Subject: [PATCH 020/146] daemon: removed utils.make_tuple and last remaining usage --- daemon/core/nodes/physical.py | 7 +------ daemon/core/utils.py | 13 ------------- 2 files changed, 1 insertion(+), 19 deletions(-) diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 018ca60d..ee00c705 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -126,20 +126,15 @@ class PhysicalNode(CoreNodeBase): netif.name = f"gt{ifindex}" netif.node = self self.addnetif(netif, ifindex) - # use a more reasonable name, e.g. "gt0" instead of "gt.56286.150" if self.up: self.net_client.device_down(netif.localname) self.net_client.device_name(netif.localname, netif.name) - netif.localname = netif.name - if hwaddr: self.sethwaddr(ifindex, hwaddr) - - for addr in utils.make_tuple(addrlist): + for addr in addrlist: self.addaddr(ifindex, addr) - if self.up: self.net_client.device_up(netif.localname) diff --git a/daemon/core/utils.py b/daemon/core/utils.py index c16d18b5..3b1ea46a 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -158,19 +158,6 @@ def which(command: str, required: bool) -> str: return found_path -def make_tuple(obj: Generic[T]) -> Tuple[T]: - """ - Create a tuple from an object, or return the object itself. - - :param obj: object to convert to a tuple - :return: converted tuple or the object itself - """ - if hasattr(obj, "__iter__"): - return tuple(obj) - else: - return (obj,) - - def make_tuple_fromstr(s: str, value_type: Callable[[str], T]) -> Tuple[T]: """ Create a tuple from a string. From 4cc9d3debfc17461a87abfe2bfd6e9da1e8fe67f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 18:59:14 -0700 Subject: [PATCH 021/146] added pydoc for grpc client InterfaceHelper --- daemon/core/api/grpc/client.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 64b8d29f..0361a69b 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -113,6 +113,15 @@ class InterfaceHelper: def create_interface( self, node_id: int, interface_id: int, name: str = None, mac: str = None ) -> core_pb2.Interface: + """ + Create an interface protobuf object. + + :param node_id: node id to create interface for + :param interface_id: interface id + :param name: name of interface + :param mac: mac address for interface + :return: interface protobuf + """ interface_data = self.prefixes.gen_interface(node_id, name, mac) return core_pb2.Interface( id=interface_id, From a79ba1b8d32efd4416156a3cb4edb8b6348b10f8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 19:48:29 -0700 Subject: [PATCH 022/146] daemon: added type hints to CoreEmu --- daemon/core/emulator/coreemu.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 90f75427..6a7f8b80 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -3,7 +3,7 @@ import logging import os import signal import sys -from typing import Mapping, Type +from typing import Dict, List, Type import core.services from core import configservices @@ -36,7 +36,7 @@ class CoreEmu: Provides logic for creating and configuring CORE sessions and the nodes within them. """ - def __init__(self, config: Mapping[str, str] = None) -> None: + def __init__(self, config: Dict[str, str] = None) -> None: """ Create a CoreEmu object. @@ -48,17 +48,17 @@ class CoreEmu: # configuration if config is None: config = {} - self.config = config + self.config: Dict[str, str] = config # session management - self.sessions = {} + self.sessions: Dict[int, Session] = {} # load services - self.service_errors = [] + self.service_errors: List[str] = [] self.load_services() # config services - self.service_manager = ConfigServiceManager() + self.service_manager: ConfigServiceManager = ConfigServiceManager() config_services_path = os.path.abspath(os.path.dirname(configservices.__file__)) self.service_manager.load(config_services_path) custom_dir = self.config.get("custom_config_services_dir") From 32ad8a9b683bd7625b5f9aff57e2cedaa4a33bb3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 20:03:32 -0700 Subject: [PATCH 023/146] daemon: added type hinting to Session --- daemon/core/emulator/session.py | 70 ++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index a1e71612..a2b2670b 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -15,9 +15,17 @@ import time from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, TypeVar from core import constants, utils +from core.configservice.manager import ConfigServiceManager from core.emane.emanemanager import EmaneManager from core.emane.nodes import EmaneNet -from core.emulator.data import ConfigData, EventData, ExceptionData, FileData, LinkData +from core.emulator.data import ( + ConfigData, + EventData, + ExceptionData, + FileData, + LinkData, + NodeData, +) from core.emulator.distributed import DistributedController from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions from core.emulator.enumerations import ( @@ -89,62 +97,62 @@ class Session: :param config: session configuration :param mkdir: flag to determine if a directory should be made """ - self.id = _id + self.id: int = _id # define and create session directory when desired - self.session_dir = os.path.join(tempfile.gettempdir(), f"pycore.{self.id}") + self.session_dir: str = os.path.join(tempfile.gettempdir(), f"pycore.{self.id}") if mkdir: os.mkdir(self.session_dir) - self.name = None - self.file_name = None - self.thumbnail = None - self.user = None - self.event_loop = EventLoop() - self.link_colors = {} + self.name: Optional[str] = None + self.file_name: Optional[str] = None + self.thumbnail: Optional[str] = None + self.user: Optional[str] = None + self.event_loop: EventLoop = EventLoop() + self.link_colors: Dict[int, str] = {} # dict of nodes: all nodes and nets - self.nodes = {} + self.nodes: Dict[int, NodeBase] = {} self._nodes_lock = threading.Lock() - self.state = EventTypes.DEFINITION_STATE - self._state_time = time.monotonic() - self._state_file = os.path.join(self.session_dir, "state") + self.state: EventTypes = EventTypes.DEFINITION_STATE + self._state_time: float = time.monotonic() + self._state_file: str = os.path.join(self.session_dir, "state") # hooks handlers - self._hooks = {} - self._state_hooks = {} + self._hooks: Dict[EventTypes, Tuple[str, str]] = {} + self._state_hooks: Dict[EventTypes, Callable[[int], None]] = {} self.add_state_hook( state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook ) # handlers for broadcasting information - self.event_handlers = [] - self.exception_handlers = [] - self.node_handlers = [] - self.link_handlers = [] - self.file_handlers = [] - self.config_handlers = [] - self.shutdown_handlers = [] + self.event_handlers: List[Callable[[EventData], None]] = [] + self.exception_handlers: List[Callable[[ExceptionData], None]] = [] + self.node_handlers: List[Callable[[NodeData], None]] = [] + self.link_handlers: List[Callable[[LinkData], None]] = [] + self.file_handlers: List[Callable[[FileData], None]] = [] + self.config_handlers: List[Callable[[ConfigData], None]] = [] + self.shutdown_handlers: List[Callable[[Session], None]] = [] # session options/metadata - self.options = SessionConfig() + self.options: SessionConfig = SessionConfig() if not config: config = {} for key in config: value = config[key] self.options.set_config(key, value) - self.metadata = {} + self.metadata: Dict[str, str] = {} # distributed support and logic - self.distributed = DistributedController(self) + self.distributed: DistributedController = DistributedController(self) # initialize session feature helpers - self.location = GeoLocation() - self.mobility = MobilityManager(session=self) - self.services = CoreServices(session=self) - self.emane = EmaneManager(session=self) - self.sdt = Sdt(session=self) + self.location: GeoLocation = GeoLocation() + self.mobility: MobilityManager = MobilityManager(self) + self.services: CoreServices = CoreServices(self) + self.emane: EmaneManager = EmaneManager(self) + self.sdt: Sdt = Sdt(self) # initialize default node services self.services.default_services = { @@ -156,7 +164,7 @@ class Session: } # config services - self.service_manager = None + self.service_manager: Optional[ConfigServiceManager] = None @classmethod def get_node_class(cls, _type: NodeTypes) -> Type[NodeBase]: From 452e0720f2232f748c26a04fe1631d349e08cab3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 9 Jun 2020 21:03:19 -0700 Subject: [PATCH 024/146] daemon: added type hinting to DistributedControll and removed bad logic looking for tunnels during add_link --- daemon/core/emulator/distributed.py | 32 +++++------------- daemon/core/emulator/session.py | 48 ++++----------------------- daemon/core/emulator/sessionconfig.py | 8 ++--- 3 files changed, 18 insertions(+), 70 deletions(-) diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index 5f188cb0..3753e1c2 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -37,10 +37,10 @@ class DistributedServer: :param name: convenience name to associate with host :param host: host to connect to """ - self.name = name - self.host = host - self.conn = Connection(host, user="root") - self.lock = threading.Lock() + self.name: str = name + self.host: str = host + self.conn: Connection = Connection(host, user="root") + self.lock: threading.Lock = threading.Lock() def remote_cmd( self, cmd: str, env: Dict[str, str] = None, cwd: str = None, wait: bool = True @@ -117,10 +117,10 @@ class DistributedController: :param session: session """ - self.session = session - self.servers = OrderedDict() - self.tunnels = {} - self.address = self.session.options.get_config( + self.session: "Session" = session + self.servers: Dict[str, DistributedServer] = OrderedDict() + self.tunnels: Dict[int, Tuple[GreTap, GreTap]] = {} + self.address: str = self.session.options.get_config( "distributed_address", default=None ) @@ -178,13 +178,10 @@ class DistributedController: """ for node_id in self.session.nodes: node = self.session.nodes[node_id] - if not isinstance(node, CoreNetwork): continue - if isinstance(node, CtrlNet) and node.serverintf is not None: continue - for name in self.servers: server = self.servers[name] self.create_gre_tunnel(node, server) @@ -195,7 +192,6 @@ class DistributedController: """ Create gre tunnel using a pair of gre taps between the local and remote server. - :param node: node to create gre tunnel for :param server: server to create tunnel for @@ -243,15 +239,3 @@ class DistributedController: (self.session.id << 16) ^ utils.hashkey(n1_id) ^ (utils.hashkey(n2_id) << 8) ) return key & 0xFFFFFFFF - - def get_tunnel(self, n1_id: int, n2_id: int) -> GreTap: - """ - Return the GreTap between two nodes if it exists. - - :param n1_id: node one id - :param n2_id: node two id - :return: gre tap between nodes or None - """ - key = self.tunnel_key(n1_id, n2_id) - logging.debug("checking for tunnel key(%s) in: %s", key, self.tunnels) - return self.tunnels.get(key) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index a2b2670b..45c17743 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -42,7 +42,7 @@ from core.location.geo import GeoLocation from core.location.mobility import BasicRangeModel, MobilityManager from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase from core.nodes.docker import DockerNode -from core.nodes.interface import CoreInterface, GreTap +from core.nodes.interface import CoreInterface from core.nodes.lxd import LxcNode from core.nodes.network import ( CtrlNet, @@ -200,7 +200,6 @@ class Session: Optional[CoreNode], Optional[CoreNetworkBase], Optional[CoreNetworkBase], - GreTap, ]: """ Convenience method for retrieving nodes within link data. @@ -221,23 +220,6 @@ class Session: node_one = self.get_node(node_one_id, NodeBase) node_two = self.get_node(node_two_id, NodeBase) - # both node ids are provided - tunnel = self.distributed.get_tunnel(node_one_id, node_two_id) - logging.debug("tunnel between nodes: %s", tunnel) - if isinstance(tunnel, GreTapBridge): - net_one = tunnel - if tunnel.remotenum == node_one_id: - node_one = None - else: - node_two = None - # physical node connected via gre tap tunnel - # TODO: double check this cases type - elif tunnel: - if tunnel.remotenum == node_one_id: - node_one = None - else: - node_two = None - if isinstance(node_one, CoreNetworkBase): if not net_one: net_one = node_one @@ -253,14 +235,13 @@ class Session: node_two = None logging.debug( - "link node types n1(%s) n2(%s) net1(%s) net2(%s) tunnel(%s)", + "link node types n1(%s) n2(%s) net1(%s) net2(%s)", node_one, node_two, net_one, net_two, - tunnel, ) - return node_one, node_two, net_one, net_two, tunnel + return node_one, node_two, net_one, net_two def _link_wireless(self, objects: Iterable[CoreNodeBase], connect: bool) -> None: """ @@ -326,7 +307,7 @@ class Session: options = LinkOptions() # get node objects identified by link data - node_one, node_two, net_one, net_two, tunnel = self._link_nodes( + node_one, node_two, net_one, net_two = self._link_nodes( node_one_id, node_two_id ) @@ -415,23 +396,6 @@ class Session: net_two.setkey(key) if addresses: net_two.addrconfig(addresses) - - # physical node connected with tunnel - if not net_one and not net_two and (node_one or node_two): - if node_one and isinstance(node_one, PhysicalNode): - logging.info("adding link for physical node: %s", node_one.name) - addresses = interface_one.get_addresses() - node_one.adoptnetif( - tunnel, interface_one.id, interface_one.mac, addresses - ) - node_one.linkconfig(tunnel, options) - elif node_two and isinstance(node_two, PhysicalNode): - logging.info("adding link for physical node: %s", node_two.name) - addresses = interface_two.get_addresses() - node_two.adoptnetif( - tunnel, interface_two.id, interface_two.mac, addresses - ) - node_two.linkconfig(tunnel, options) finally: if node_one: node_one.lock.release() @@ -461,7 +425,7 @@ class Session: :raises core.CoreError: when no common network is found for link being deleted """ # get node objects identified by link data - node_one, node_two, net_one, net_two, _tunnel = self._link_nodes( + node_one, node_two, net_one, net_two = self._link_nodes( node_one_id, node_two_id ) @@ -573,7 +537,7 @@ class Session: options = LinkOptions() # get node objects identified by link data - node_one, node_two, net_one, net_two, _tunnel = self._link_nodes( + node_one, node_two, net_one, net_two = self._link_nodes( node_one_id, node_two_id ) diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py index ffeccdc4..e22e852e 100644 --- a/daemon/core/emulator/sessionconfig.py +++ b/daemon/core/emulator/sessionconfig.py @@ -1,4 +1,4 @@ -from typing import Any +from typing import Any, List from core.config import ConfigurableManager, ConfigurableOptions, Configuration from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs @@ -10,8 +10,8 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions): Provides session configuration. """ - name = "session" - options = [ + name: str = "session" + options: List[Configuration] = [ Configuration( _id="controlnet", _type=ConfigDataTypes.STRING, label="Control Network" ), @@ -57,7 +57,7 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions): label="SDT3D URL", ), ] - config_type = RegisterTlvs.UTILITY + config_type: RegisterTlvs = RegisterTlvs.UTILITY def __init__(self) -> None: super().__init__() From 6ee9590bdc3629e61ee128d77238041b58ac3c65 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 10 Jun 2020 08:52:51 -0700 Subject: [PATCH 025/146] daemon: finished class variable type hinting for core.nodes --- daemon/core/nodes/client.py | 4 ++-- daemon/core/nodes/docker.py | 12 ++++++------ daemon/core/nodes/lxd.py | 12 ++++++------ daemon/core/nodes/netclient.py | 2 +- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index d7642863..c004b814 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -20,8 +20,8 @@ class VnodeClient: :param name: name for client :param ctrlchnlname: control channel name """ - self.name = name - self.ctrlchnlname = ctrlchnlname + self.name: str = name + self.ctrlchnlname: str = ctrlchnlname def _verify_connection(self) -> None: """ diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index 684e8452..fa4b8f8b 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -2,7 +2,7 @@ import json import logging import os from tempfile import NamedTemporaryFile -from typing import TYPE_CHECKING, Callable, Dict +from typing import TYPE_CHECKING, Callable, Dict, Optional from core import utils from core.emulator.distributed import DistributedServer @@ -17,10 +17,10 @@ if TYPE_CHECKING: class DockerClient: def __init__(self, name: str, image: str, run: Callable[..., str]) -> None: - self.name = name - self.image = image - self.run = run - self.pid = None + self.name: str = name + self.image: str = image + self.run: Callable[..., str] = run + self.pid: Optional[str] = None def create_container(self) -> str: self.run( @@ -95,7 +95,7 @@ class DockerNode(CoreNode): """ if image is None: image = "ubuntu" - self.image = image + self.image: str = image super().__init__(session, _id, name, nodedir, start, server) def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index 3b4c88c0..af906f01 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -3,7 +3,7 @@ import logging import os import time from tempfile import NamedTemporaryFile -from typing import TYPE_CHECKING, Callable, Dict +from typing import TYPE_CHECKING, Callable, Dict, Optional from core import utils from core.emulator.distributed import DistributedServer @@ -18,10 +18,10 @@ if TYPE_CHECKING: class LxdClient: def __init__(self, name: str, image: str, run: Callable[..., str]) -> None: - self.name = name - self.image = image - self.run = run - self.pid = None + self.name: str = name + self.image: str = image + self.run: Callable[..., str] = run + self.pid: Optional[int] = None def create_container(self) -> int: self.run(f"lxc launch {self.image} {self.name}") @@ -92,7 +92,7 @@ class LxcNode(CoreNode): """ if image is None: image = "ubuntu" - self.image = image + self.image: str = image super().__init__(session, _id, name, nodedir, start, server) def alive(self) -> bool: diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 29a70d18..25a10b99 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -19,7 +19,7 @@ class LinuxNetClient: :param run: function to run commands with """ - self.run = run + self.run: Callable[..., str] = run def set_hostname(self, name: str) -> None: """ From fd341bd69bd0874656b0e7f178e5cad0ba8e9fbb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 10 Jun 2020 09:01:38 -0700 Subject: [PATCH 026/146] daemon: added class variable type hinting to core.plugins --- daemon/core/plugins/sdt.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 06c23de5..8b4ec39f 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -5,7 +5,7 @@ sdt.py: Scripted Display Tool (SDT3D) helper import logging import socket import threading -from typing import TYPE_CHECKING, Optional +from typing import IO, TYPE_CHECKING, Dict, Optional, Set, Tuple from urllib.parse import urlparse from core import constants @@ -42,11 +42,11 @@ class Sdt: when a node position or link has changed. """ - DEFAULT_SDT_URL = "tcp://127.0.0.1:50000/" + DEFAULT_SDT_URL: str = "tcp://127.0.0.1:50000/" # default altitude (in meters) for flyto view - DEFAULT_ALT = 2500 + DEFAULT_ALT: int = 2500 # TODO: read in user"s nodes.conf here; below are default node types from the GUI - DEFAULT_SPRITES = [ + DEFAULT_SPRITES: Dict[str, str] = [ ("router", "router.gif"), ("host", "host.gif"), ("PC", "pc.gif"), @@ -65,14 +65,14 @@ class Sdt: :param session: session this manager is tied to """ - self.session = session - self.lock = threading.Lock() - self.sock = None - self.connected = False - self.url = self.DEFAULT_SDT_URL - self.address = None - self.protocol = None - self.network_layers = set() + self.session: "Session" = session + self.lock: threading.Lock = threading.Lock() + self.sock: Optional[IO] = None + self.connected: bool = False + self.url: str = self.DEFAULT_SDT_URL + self.address: Optional[Tuple[Optional[str], Optional[int]]] = None + self.protocol: Optional[str] = None + self.network_layers: Set[str] = set() self.session.node_handlers.append(self.handle_node_update) self.session.link_handlers.append(self.handle_link_update) From 784c4d241976a0385c4ee96886f49db329b27b26 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 10 Jun 2020 10:24:44 -0700 Subject: [PATCH 027/146] daemon: added core.location class variable type hinting --- daemon/core/emane/emanemodel.py | 5 +- daemon/core/location/event.py | 81 ++++++++-------- daemon/core/location/geo.py | 19 ++-- daemon/core/location/mobility.py | 152 +++++++++++++++---------------- daemon/tests/test_mobility.py | 10 +- 5 files changed, 137 insertions(+), 130 deletions(-) diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index f42caa14..7b5ff417 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -12,6 +12,7 @@ from core.emulator.emudata import LinkOptions from core.emulator.enumerations import ConfigDataTypes, TransportType from core.errors import CoreError from core.location.mobility import WirelessModel +from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface from core.xml import emanexml @@ -139,13 +140,13 @@ class EmaneModel(WirelessModel): """ logging.debug("emane model(%s) has no post setup tasks", self.name) - def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: """ Invoked from MobilityModel when nodes are moved; this causes emane location events to be generated for the nodes in the moved list, making EmaneModels compatible with Ns2ScriptedMobility. - :param moved: were nodes moved + :param moved: moved nodes :param moved_netifs: interfaces that were moved :return: nothing """ diff --git a/daemon/core/location/event.py b/daemon/core/location/event.py index 8826c42b..7f8a33a1 100644 --- a/daemon/core/location/event.py +++ b/daemon/core/location/event.py @@ -6,7 +6,7 @@ import heapq import threading import time from functools import total_ordering -from typing import Any, Callable +from typing import Any, Callable, Dict, List, Optional, Tuple class Timer(threading.Thread): @@ -16,34 +16,33 @@ class Timer(threading.Thread): """ def __init__( - self, interval: float, function: Callable, args: Any = None, kwargs: Any = None + self, + interval: float, + func: Callable[..., None], + args: Tuple[Any] = None, + kwargs: Dict[Any, Any] = None, ) -> None: """ Create a Timer instance. :param interval: time interval - :param function: function to call when timer finishes + :param func: function to call when timer finishes :param args: function arguments :param kwargs: function keyword arguments """ super().__init__() - self.interval = interval - self.function = function - - self.finished = threading.Event() - self._running = threading.Lock() - + self.interval: float = interval + self.func: Callable[..., None] = func + self.finished: threading.Event = threading.Event() + self._running: threading.Lock = threading.Lock() # validate arguments were provided - if args: - self.args = args - else: - self.args = [] - + if args is None: + args = () + self.args: Tuple[Any] = args # validate keyword arguments were provided - if kwargs: - self.kwargs = kwargs - else: - self.kwargs = {} + if kwargs is None: + kwargs = {} + self.kwargs: Dict[Any, Any] = kwargs def cancel(self) -> bool: """ @@ -67,7 +66,7 @@ class Timer(threading.Thread): self.finished.wait(self.interval) with self._running: if not self.finished.is_set(): - self.function(*self.args, **self.kwargs) + self.func(*self.args, **self.kwargs) self.finished.set() @@ -78,7 +77,12 @@ class Event: """ def __init__( - self, eventnum: int, event_time: float, func: Callable, *args: Any, **kwds: Any + self, + eventnum: int, + event_time: float, + func: Callable[..., None], + *args: Any, + **kwds: Any ) -> None: """ Create an Event instance. @@ -89,12 +93,12 @@ class Event: :param args: function arguments :param kwds: function keyword arguments """ - self.eventnum = eventnum - self.time = event_time - self.func = func - self.args = args - self.kwds = kwds - self.canceled = False + self.eventnum: int = eventnum + self.time: float = event_time + self.func: Callable[..., None] = func + self.args: Tuple[Any] = args + self.kwds: Dict[Any, Any] = kwds + self.canceled: bool = False def __lt__(self, other: "Event") -> bool: result = self.time < other.time @@ -118,7 +122,6 @@ class Event: :return: nothing """ - # XXX not thread-safe self.canceled = True @@ -131,14 +134,14 @@ class EventLoop: """ Creates a EventLoop instance. """ - self.lock = threading.RLock() - self.queue = [] - self.eventnum = 0 - self.timer = None - self.running = False - self.start = None + self.lock: threading.RLock = threading.RLock() + self.queue: List[Event] = [] + self.eventnum: int = 0 + self.timer: Optional[Timer] = None + self.running: bool = False + self.start: Optional[float] = None - def __run_events(self) -> None: + def _run_events(self) -> None: """ Run events. @@ -161,9 +164,9 @@ class EventLoop: with self.lock: self.timer = None if schedule: - self.__schedule_event() + self._schedule_event() - def __schedule_event(self) -> None: + def _schedule_event(self) -> None: """ Schedule event. @@ -177,7 +180,7 @@ class EventLoop: delay = self.queue[0].time - time.monotonic() if self.timer: raise ValueError("timer was already set") - self.timer = Timer(delay, self.__run_events) + self.timer = Timer(delay, self._run_events) self.timer.daemon = True self.timer.start() @@ -194,7 +197,7 @@ class EventLoop: self.start = time.monotonic() for event in self.queue: event.time += self.start - self.__schedule_event() + self._schedule_event() def stop(self) -> None: """ @@ -242,5 +245,5 @@ class EventLoop: if self.timer is not None and self.timer.cancel(): self.timer = None if self.running and self.timer is None: - self.__schedule_event() + self._schedule_event() return event diff --git a/daemon/core/location/geo.py b/daemon/core/location/geo.py index 4ff56dd6..6c8eb651 100644 --- a/daemon/core/location/geo.py +++ b/daemon/core/location/geo.py @@ -6,6 +6,7 @@ import logging from typing import Tuple import pyproj +from pyproj import Transformer from core.emulator.enumerations import RegisterTlvs @@ -20,21 +21,23 @@ class GeoLocation: defined projections. """ - name = "location" - config_type = RegisterTlvs.UTILITY + name: str = "location" + config_type: RegisterTlvs = RegisterTlvs.UTILITY def __init__(self) -> None: """ Creates a GeoLocation instance. """ - self.to_pixels = pyproj.Transformer.from_crs( + self.to_pixels: Transformer = pyproj.Transformer.from_crs( CRS_WGS84, CRS_PROJ, always_xy=True ) - self.to_geo = pyproj.Transformer.from_crs(CRS_PROJ, CRS_WGS84, always_xy=True) - self.refproj = (0.0, 0.0, 0.0) - self.refgeo = (0.0, 0.0, 0.0) - self.refxyz = (0.0, 0.0, 0.0) - self.refscale = 1.0 + self.to_geo: Transformer = pyproj.Transformer.from_crs( + CRS_PROJ, CRS_WGS84, always_xy=True + ) + self.refproj: Tuple[float, float, float] = (0.0, 0.0, 0.0) + self.refgeo: Tuple[float, float, float] = (0.0, 0.0, 0.0) + self.refxyz: Tuple[float, float, float] = (0.0, 0.0, 0.0) + self.refscale: float = 1.0 def setrefgeo(self, lat: float, lon: float, alt: float) -> None: """ diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 3ca46418..87cd7141 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -9,7 +9,7 @@ import os import threading import time from functools import total_ordering -from typing import TYPE_CHECKING, Dict, List, Tuple +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple from core import utils from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager @@ -23,7 +23,7 @@ from core.emulator.enumerations import ( RegisterTlvs, ) from core.errors import CoreError -from core.nodes.base import CoreNode, NodeBase +from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode @@ -47,7 +47,7 @@ class MobilityManager(ModelManager): :param session: session this manager is tied to """ super().__init__() - self.session = session + self.session: "Session" = session self.models[BasicRangeModel.name] = BasicRangeModel self.models[Ns2ScriptedMobility.name] = Ns2ScriptedMobility @@ -178,7 +178,7 @@ class MobilityManager(ModelManager): self.session.broadcast_event(event_data) def updatewlans( - self, moved: List[NodeBase], moved_netifs: List[CoreInterface] + self, moved: List[CoreNode], moved_netifs: List[CoreInterface] ) -> None: """ A mobility script has caused nodes in the 'moved' list to move. @@ -204,21 +204,21 @@ class WirelessModel(ConfigurableOptions): Used for managing arbitrary configuration parameters. """ - config_type = RegisterTlvs.WIRELESS - bitmap = None - position_callback = None + config_type: RegisterTlvs = RegisterTlvs.WIRELESS + bitmap: str = None + position_callback: Callable[[CoreInterface], None] = None - def __init__(self, session: "Session", _id: int): + def __init__(self, session: "Session", _id: int) -> None: """ Create a WirelessModel instance. :param session: core session we are tied to :param _id: object id """ - self.session = session - self.id = _id + self.session: "Session" = session + self.id: int = _id - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List: + def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ May be used if the model can populate the GUI with wireless (green) link lines. @@ -228,11 +228,11 @@ class WirelessModel(ConfigurableOptions): """ return [] - def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: """ Update this wireless model. - :param moved: flag is it was moved + :param moved: moved nodes :param moved_netifs: moved network interfaces :return: nothing """ @@ -256,8 +256,8 @@ class BasicRangeModel(WirelessModel): the GUI. """ - name = "basic_range" - options = [ + name: str = "basic_range" + options: List[Configuration] = [ Configuration( _id="range", _type=ConfigDataTypes.UINT32, @@ -299,15 +299,15 @@ class BasicRangeModel(WirelessModel): :param _id: object id """ super().__init__(session, _id) - self.session = session - self.wlan = session.get_node(_id, WlanNode) - self._netifs = {} - self._netifslock = threading.Lock() - self.range = 0 - self.bw = None - self.delay = None - self.loss = None - self.jitter = None + self.session: "Session" = session + self.wlan: WlanNode = session.get_node(_id, WlanNode) + self._netifs: Dict[CoreInterface, Tuple[float, float, float]] = {} + self._netifslock: threading.Lock = threading.Lock() + self.range: int = 0 + self.bw: Optional[int] = None + self.delay: Optional[int] = None + self.loss: Optional[float] = None + self.jitter: Optional[int] = None def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int: """ @@ -374,14 +374,14 @@ class BasicRangeModel(WirelessModel): position_callback = set_position - def update(self, moved: bool, moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: """ Node positions have changed without recalc. Update positions from node.position, then re-calculate links for those that have moved. Assumes bidirectional links, with one calculation per node pair, where one of the nodes has moved. - :param moved: flag is it was moved + :param moved: moved nodes :param moved_netifs: moved network interfaces :return: nothing """ @@ -535,29 +535,35 @@ class WayPoint: Maintains information regarding waypoints. """ - def __init__(self, time: float, nodenum: int, coords, speed: float): + def __init__( + self, + _time: float, + node_id: int, + coords: Tuple[float, float, float], + speed: float, + ) -> None: """ Creates a WayPoint instance. - :param time: waypoint time - :param nodenum: node id + :param _time: waypoint time + :param node_id: node id :param coords: waypoint coordinates :param speed: waypoint speed """ - self.time = time - self.nodenum = nodenum - self.coords = coords - self.speed = speed + self.time: float = _time + self.node_id: int = node_id + self.coords: Tuple[float, float, float] = coords + self.speed: float = speed def __eq__(self, other: "WayPoint") -> bool: - return (self.time, self.nodenum) == (other.time, other.nodenum) + return (self.time, self.node_id) == (other.time, other.node_id) def __ne__(self, other: "WayPoint") -> bool: return not self == other def __lt__(self, other: "WayPoint") -> bool: if self.time == other.time: - return self.nodenum < other.nodenum + return self.node_id < other.node_id else: return self.time < other.time @@ -567,12 +573,11 @@ class WayPointMobility(WirelessModel): Abstract class for mobility models that set node waypoints. """ - name = "waypoint" - config_type = RegisterTlvs.MOBILITY - - STATE_STOPPED = 0 - STATE_RUNNING = 1 - STATE_PAUSED = 2 + name: str = "waypoint" + config_type: RegisterTlvs = RegisterTlvs.MOBILITY + STATE_STOPPED: int = 0 + STATE_RUNNING: int = 1 + STATE_PAUSED: int = 2 def __init__(self, session: "Session", _id: int) -> None: """ @@ -583,20 +588,21 @@ class WayPointMobility(WirelessModel): :return: """ super().__init__(session=session, _id=_id) - self.state = self.STATE_STOPPED - self.queue = [] - self.queue_copy = [] - self.points = {} - self.initial = {} - self.lasttime = None - self.endtime = None - self.wlan = session.get_node(_id, WlanNode) + self.state: int = self.STATE_STOPPED + self.queue: List[WayPoint] = [] + self.queue_copy: List[WayPoint] = [] + self.points: Dict[int, WayPoint] = {} + self.initial: Dict[int, WayPoint] = {} + self.lasttime: Optional[float] = None + self.endtime: Optional[int] = None + self.timezero: float = 0.0 + self.wlan: WlanNode = session.get_node(_id, WlanNode) # these are really set in child class via confmatrix - self.loop = False - self.refresh_ms = 50 + self.loop: bool = False + self.refresh_ms: int = 50 # flag whether to stop scheduling when queue is empty # (ns-3 sets this to False as new waypoints may be added from trace) - self.empty_queue_stop = True + self.empty_queue_stop: bool = True def runround(self) -> None: """ @@ -684,16 +690,11 @@ class WayPointMobility(WirelessModel): self.setnodeposition(node, x2, y2, z2) del self.points[node.id] return True - # speed can be a velocity vector or speed value - if isinstance(speed, (float, int)): - # linear speed value - alpha = math.atan2(y2 - y1, x2 - x1) - sx = speed * math.cos(alpha) - sy = speed * math.sin(alpha) - else: - # velocity vector - sx = speed[0] - sy = speed[1] + + # linear speed value + alpha = math.atan2(y2 - y1, x2 - x1) + sx = speed * math.cos(alpha) + sy = speed * math.sin(alpha) # calculate dt * speed = distance moved dx = sx * dt @@ -776,7 +777,7 @@ class WayPointMobility(WirelessModel): if self.queue[0].time > now: break wp = heapq.heappop(self.queue) - self.points[wp.nodenum] = wp + self.points[wp.node_id] = wp def copywaypoints(self) -> None: """ @@ -876,8 +877,8 @@ class Ns2ScriptedMobility(WayPointMobility): BonnMotion. """ - name = "ns2script" - options = [ + name: str = "ns2script" + options: List[Configuration] = [ Configuration( _id="file", _type=ConfigDataTypes.STRING, label="mobility script file" ), @@ -923,7 +924,7 @@ class Ns2ScriptedMobility(WayPointMobility): ConfigGroup("ns-2 Mobility Script Parameters", 1, len(cls.configurations())) ] - def __init__(self, session: "Session", _id: int): + def __init__(self, session: "Session", _id: int) -> None: """ Creates a Ns2ScriptedMobility instance. @@ -931,17 +932,14 @@ class Ns2ScriptedMobility(WayPointMobility): :param _id: object id """ super().__init__(session, _id) - self._netifs = {} - self._netifslock = threading.Lock() - - self.file = None - self.refresh_ms = None - self.loop = None - self.autostart = None - self.nodemap = {} - self.script_start = None - self.script_pause = None - self.script_stop = None + self.file: Optional[str] = None + self.refresh_ms: Optional[int] = None + self.loop: Optional[bool] = None + self.autostart: Optional[str] = None + self.nodemap: Dict[int, int] = {} + self.script_start: Optional[str] = None + self.script_pause: Optional[str] = None + self.script_stop: Optional[str] = None def update_config(self, config: Dict[str, str]) -> None: self.file = config["file"] diff --git a/daemon/tests/test_mobility.py b/daemon/tests/test_mobility.py index e2e8f90e..aab7b30f 100644 --- a/daemon/tests/test_mobility.py +++ b/daemon/tests/test_mobility.py @@ -2,15 +2,17 @@ import pytest from core.location.mobility import WayPoint +POSITION = (0.0, 0.0, 0.0) + class TestMobility: @pytest.mark.parametrize( "wp1, wp2, expected", [ - (WayPoint(10.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), False), - (WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(10.0, 2, [0, 0], 1.0), True), - (WayPoint(1.0, 1, [0, 0], 1.0), WayPoint(1.0, 2, [0, 0], 1.0), True), - (WayPoint(1.0, 2, [0, 0], 1.0), WayPoint(1.0, 1, [0, 0], 1.0), False), + (WayPoint(10.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), False), + (WayPoint(1.0, 1, POSITION, 1.0), WayPoint(10.0, 2, POSITION, 1.0), True), + (WayPoint(1.0, 1, POSITION, 1.0), WayPoint(1.0, 2, POSITION, 1.0), True), + (WayPoint(1.0, 2, POSITION, 1.0), WayPoint(1.0, 1, POSITION, 1.0), False), ], ) def test_waypoint_lessthan(self, wp1, wp2, expected): From a389dc6240ba744418152855cb093367c33c06dd Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 10 Jun 2020 10:31:13 -0700 Subject: [PATCH 028/146] daemon: improve type hinting for WayPoint --- daemon/core/location/mobility.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 87cd7141..e9efa16b 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -539,7 +539,7 @@ class WayPoint: self, _time: float, node_id: int, - coords: Tuple[float, float, float], + coords: Tuple[float, float, Optional[float]], speed: float, ) -> None: """ @@ -552,7 +552,7 @@ class WayPoint: """ self.time: float = _time self.node_id: int = node_id - self.coords: Tuple[float, float, float] = coords + self.coords: Tuple[float, float, Optional[float]] = coords self.speed: float = speed def __eq__(self, other: "WayPoint") -> bool: @@ -737,7 +737,13 @@ class WayPointMobility(WirelessModel): self.session.mobility.updatewlans(moved, moved_netifs) def addwaypoint( - self, _time: float, nodenum: int, x: float, y: float, z: float, speed: float + self, + _time: float, + nodenum: int, + x: float, + y: float, + z: Optional[float], + speed: float, ) -> None: """ Waypoints are pushed to a heapq, sorted by time. From 39fd11efb304d93377fcb599f5ed82bf5d1574b0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 10 Jun 2020 10:40:24 -0700 Subject: [PATCH 029/146] daemon: added missing type hint to core.nodes.interface.CoreInterface --- daemon/core/nodes/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 16c242e9..e73e2989 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -50,7 +50,7 @@ class CoreInterface: self.mtu: int = mtu self.net: Optional[CoreNetworkBase] = None self.othernet: Optional[CoreNetworkBase] = None - self._params = {} + self._params: Dict[str, float] = {} self.addrlist: List[str] = [] self.hwaddr: Optional[str] = None # placeholder position hook From 9ed42cfba894c760b70a5711e46c55dec7ee5473 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 10 Jun 2020 11:04:33 -0700 Subject: [PATCH 030/146] pygui: avoid issue when joining opened xml that has a node with no ip4 address --- daemon/core/gui/interface.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 437bd37c..1973fe99 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -12,7 +12,9 @@ if TYPE_CHECKING: from core.gui.graph.node import CanvasNode -def get_index(interface: "core_pb2.Interface") -> int: +def get_index(interface: "core_pb2.Interface") -> Optional[int]: + if not interface.ip4: + return None net = netaddr.IPNetwork(f"{interface.ip4}/{interface.ip4mask}") ip_value = net.value cidr_value = net.cidr.value @@ -108,7 +110,8 @@ class InterfaceManager: self.used_subnets.pop(subnets.key(), None) else: index = get_index(interface) - subnets.used_indexes.discard(index) + if index is not None: + subnets.used_indexes.discard(index) self.current_subnets = None def joined(self, links: List["core_pb2.Link"]) -> None: @@ -123,6 +126,8 @@ class InterfaceManager: for interface in interfaces: subnets = self.get_subnets(interface) index = get_index(interface) + if index is None: + continue subnets.used_indexes.add(index) if subnets.key() not in self.used_subnets: self.used_subnets[subnets.key()] = subnets From ccf2646c00fd5641137d20f578ab448e97e99ee9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 13:59:29 -0700 Subject: [PATCH 031/146] daemon: refactored add_link,update_link,delete_link to have more specific logic, refactored CoreNodeBase to have newnetif and for it to return the interface created --- daemon/core/api/tlv/corehandlers.py | 2 +- daemon/core/emulator/session.py | 510 ++++++++++------------------ daemon/core/nodes/base.py | 37 +- daemon/core/nodes/network.py | 8 +- daemon/core/nodes/physical.py | 10 +- daemon/tests/test_nodes.py | 20 +- 6 files changed, 232 insertions(+), 355 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index f3e1fbaa..5531e5af 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -767,7 +767,7 @@ class CoreHandler(socketserver.BaseRequestHandler): ip6_mask=message.get_tlv(LinkTlvs.INTERFACE2_IP6_MASK.value), ) - link_type = None + link_type = LinkTypes.WIRED link_type_value = message.get_tlv(LinkTlvs.TYPE.value) if link_type_value is not None: link_type = LinkTypes(link_type_value) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 45c17743..54486bfb 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -12,7 +12,7 @@ import subprocess import tempfile import threading import time -from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Type, TypeVar +from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar from core import constants, utils from core.configservice.manager import ConfigServiceManager @@ -193,76 +193,28 @@ class Session: raise CoreError(f"invalid node class: {_class}") return node_type - def _link_nodes( - self, node_one_id: int, node_two_id: int - ) -> Tuple[ - Optional[CoreNode], - Optional[CoreNode], - Optional[CoreNetworkBase], - Optional[CoreNetworkBase], - ]: - """ - Convenience method for retrieving nodes within link data. - - :param node_one_id: node one id - :param node_two_id: node two id - :return: nodes, network nodes if present, and tunnel if present - """ - logging.debug( - "link message between node1(%s) and node2(%s)", node_one_id, node_two_id - ) - - # values to fill - net_one = None - net_two = None - - # retrieve node one - node_one = self.get_node(node_one_id, NodeBase) - node_two = self.get_node(node_two_id, NodeBase) - - if isinstance(node_one, CoreNetworkBase): - if not net_one: - net_one = node_one - else: - net_two = node_one - node_one = None - - if isinstance(node_two, CoreNetworkBase): - if not net_one: - net_one = node_two - else: - net_two = node_two - node_two = None - - logging.debug( - "link node types n1(%s) n2(%s) net1(%s) net2(%s)", - node_one, - node_two, - net_one, - net_two, - ) - return node_one, node_two, net_one, net_two - - def _link_wireless(self, objects: Iterable[CoreNodeBase], connect: bool) -> None: + def _link_wireless( + self, node_one: CoreNodeBase, node_two: CoreNodeBase, connect: bool + ) -> None: """ Objects to deal with when connecting/disconnecting wireless links. - :param objects: possible objects to deal with + :param node_one: node one for wireless link + :param node_two: node two for wireless link :param connect: link interfaces if True, unlink otherwise :return: nothing :raises core.CoreError: when objects to link is less than 2, or no common networks are found """ - objects = [x for x in objects if x] - if len(objects) < 2: - raise CoreError(f"wireless link failure: {objects}") - logging.debug( - "handling wireless linking objects(%s) connect(%s)", objects, connect + logging.info( + "handling wireless linking node1(%s) node2(%s): %s", + node_one.name, + node_two.name, + connect, ) - common_networks = objects[0].commonnets(objects[1]) + common_networks = node_one.commonnets(node_one) if not common_networks: raise CoreError("no common network found for wireless link/unlink") - for common_network, interface_one, interface_two in common_networks: if not isinstance(common_network, (WlanNode, EmaneNet)): logging.info( @@ -270,13 +222,6 @@ class Session: common_network, ) continue - - logging.info( - "wireless linking connect(%s): %s - %s", - connect, - interface_one, - interface_two, - ) if connect: common_network.link(interface_one, interface_two) else: @@ -305,105 +250,70 @@ class Session: """ if not options: options = LinkOptions() + node1 = self.get_node(node_one_id, NodeBase) + node2 = self.get_node(node_two_id, NodeBase) + node1_interface = None + node2_interface = None - # get node objects identified by link data - node_one, node_two, net_one, net_two = self._link_nodes( - node_one_id, node_two_id - ) - - if node_one: - node_one.lock.acquire() - if node_two: - node_two.lock.acquire() - - node_one_interface = None - node_two_interface = None - - try: - # wireless link - if options.type == LinkTypes.WIRELESS: - objects = [node_one, node_two, net_one, net_two] - self._link_wireless(objects, connect=True) - # wired link + # wireless link + if options.type == LinkTypes.WIRELESS: + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + self._link_wireless(node1, node2, connect=True) else: - # 2 nodes being linked, ptp network - if all([node_one, node_two]) and not net_one: - logging.info( - "adding link for peer to peer nodes: %s - %s", - node_one.name, - node_two.name, - ) - start = self.state.should_start() - net_one = self.create_node(PtpNet, start=start) - - # node to network - if node_one and net_one: - logging.info( - "adding link from node to network: %s - %s", - node_one.name, - net_one.name, - ) - ifindex = node_one.newnetif(net_one, interface_one) - node_one_interface = node_one.netif(ifindex) - wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) - if not wireless_net: - net_one.linkconfig(node_one_interface, options) - - # network to node - if node_two and net_one: - logging.info( - "adding link from network to node: %s - %s", - node_two.name, - net_one.name, - ) - ifindex = node_two.newnetif(net_one, interface_two) - node_two_interface = node_two.netif(ifindex) - wireless_net = isinstance(net_one, (EmaneNet, WlanNode)) - if not options.unidirectional and not wireless_net: - net_one.linkconfig(node_two_interface, options) - - # network to network - if net_one and net_two: - logging.info( - "adding link from network to network: %s - %s", - net_one.name, - net_two.name, - ) - interface = net_one.linknet(net_two) - node_one_interface = interface - net_one.linkconfig(interface, options) - if not options.unidirectional: - interface.swapparams("_params_up") - net_two.linkconfig(interface, options) - interface.swapparams("_params_up") - - # a tunnel node was found for the nodes - addresses = [] - if not node_one and all([net_one, interface_one]): - addresses.extend(interface_one.get_addresses()) - if not node_two and all([net_two, interface_two]): - addresses.extend(interface_two.get_addresses()) - - # tunnel node logic - key = options.key - if key and isinstance(net_one, TunnelNode): - logging.info("setting tunnel key for: %s", net_one.name) - net_one.setkey(key) - if addresses: - net_one.addrconfig(addresses) - if key and isinstance(net_two, TunnelNode): - logging.info("setting tunnel key for: %s", net_two.name) - net_two.setkey(key) - if addresses: - net_two.addrconfig(addresses) - finally: - if node_one: - node_one.lock.release() - if node_two: - node_two.lock.release() + raise CoreError( + f"cannot wireless link node1({type(node1)}) node2({type(node2)})" + ) + # wired link + else: + # peer to peer link + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + logging.info("linking ptp: %s - %s", node1.name, node2.name) + start = self.state.should_start() + ptp = self.create_node(PtpNet, start=start) + node1_interface = node1.newnetif(ptp, interface_one) + node2_interface = node2.newnetif(ptp, interface_two) + ptp.linkconfig(node1_interface, options) + if not options.unidirectional: + ptp.linkconfig(node2_interface, options) + # link node to net + elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): + node1_interface = node1.newnetif(node2, interface_one) + if not isinstance(node2, (EmaneNet, WlanNode)): + node2.linkconfig(node1_interface, options) + # link net to node + elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): + node2_interface = node2.newnetif(node1, interface_two) + wireless_net = isinstance(node1, (EmaneNet, WlanNode)) + if not options.unidirectional and not wireless_net: + node1.linkconfig(node2_interface, options) + # network to network + elif isinstance(node1, CoreNetworkBase) and isinstance( + node2, CoreNetworkBase + ): + logging.info( + "linking network to network: %s - %s", node1.name, node2.name + ) + node1_interface = node1.linknet(node2) + node1.linkconfig(node1_interface, options) + if not options.unidirectional: + node1_interface.swapparams("_params_up") + node2.linkconfig(node1_interface, options) + node1_interface.swapparams("_params_up") + else: + raise CoreError( + f"cannot link node1({type(node1)}) node2({type(node2)})" + ) + # configure tunnel nodes + key = options.key + if isinstance(node1, TunnelNode): + logging.info("setting tunnel key for: %s", node1.name) + node1.setkey(key, interface_one) + if isinstance(node2, TunnelNode): + logging.info("setting tunnel key for: %s", node2.name) + node2.setkey(key, interface_two) self.sdt.add_link(node_one_id, node_two_id) - return node_one_interface, node_two_interface + return node1_interface, node2_interface def delete_link( self, @@ -424,93 +334,52 @@ class Session: :return: nothing :raises core.CoreError: when no common network is found for link being deleted """ - # get node objects identified by link data - node_one, node_two, net_one, net_two = self._link_nodes( - node_one_id, node_two_id + node1 = self.get_node(node_one_id, NodeBase) + node2 = self.get_node(node_two_id, NodeBase) + logging.info( + "deleting link(%s) node(%s):interface(%s) node(%s):interface(%s)", + link_type.name, + node1.name, + interface_one_id, + node2.name, + interface_two_id, ) - if node_one: - node_one.lock.acquire() - if node_two: - node_two.lock.acquire() - - try: - # wireless link - if link_type == LinkTypes.WIRELESS: - objects = [node_one, node_two, net_one, net_two] - self._link_wireless(objects, connect=False) - # wired link + # wireless link + if link_type == LinkTypes.WIRELESS: + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + self._link_wireless(node1, node2, connect=False) else: - if all([node_one, node_two]): - # TODO: fix this for the case where ifindex[1,2] are not specified - # a wired unlink event, delete the connecting bridge - interface_one = node_one.netif(interface_one_id) - interface_two = node_two.netif(interface_two_id) - - # get interfaces from common network, if no network node - # otherwise get interfaces between a node and network - if not interface_one and not interface_two: - common_networks = node_one.commonnets(node_two) - for ( - network, - common_interface_one, - common_interface_two, - ) in common_networks: - if (net_one and network == net_one) or not net_one: - interface_one = common_interface_one - interface_two = common_interface_two - break - - if all([interface_one, interface_two]) and any( - [interface_one.net, interface_two.net] - ): - if interface_one.net != interface_two.net and all( - [interface_one.up, interface_two.up] - ): - raise CoreError("no common network found") - - logging.info( - "deleting link node(%s):interface(%s) node(%s):interface(%s)", - node_one.name, - interface_one.name, - node_two.name, - interface_two.name, - ) - net_one = interface_one.net - interface_one.detachnet() - interface_two.detachnet() - if net_one.numnetif() == 0: - self.delete_node(net_one.id) - node_one.delnetif(interface_one.netindex) - node_two.delnetif(interface_two.netindex) - elif node_one and net_one: - interface = node_one.netif(interface_one_id) - if interface: - logging.info( - "deleting link node(%s):interface(%s) node(%s)", - node_one.name, - interface.name, - net_one.name, - ) - interface.detachnet() - node_one.delnetif(interface.netindex) - elif node_two and net_one: - interface = node_two.netif(interface_two_id) - if interface: - logging.info( - "deleting link node(%s):interface(%s) node(%s)", - node_two.name, - interface.name, - net_one.name, - ) - interface.detachnet() - node_two.delnetif(interface.netindex) - finally: - if node_one: - node_one.lock.release() - if node_two: - node_two.lock.release() - + raise CoreError( + "cannot delete wireless link " + f"node1({type(node1)}) node2({type(node2)})" + ) + # wired link + else: + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + interface1 = node1.netif(interface_one_id) + interface2 = node2.netif(interface_two_id) + if not interface1: + raise CoreError( + f"node({node1.name}) missing interface({interface_one_id})" + ) + if not interface2: + raise CoreError( + f"node({node2.name}) missing interface({interface_two_id})" + ) + if interface1.net != interface2.net: + raise CoreError( + f"node1({node1.name}) node2({node2.name}) " + "not connected to same net" + ) + ptp = interface1.net + node1.delnetif(interface_one_id) + node2.delnetif(interface_two_id) + self.delete_node(ptp.id) + elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): + node1.delnetif(interface_one_id) + elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): + node2.delnetif(interface_two_id) self.sdt.delete_link(node_one_id, node_two_id) def update_link( @@ -530,84 +399,79 @@ class Session: :param interface_two_id: interface id for node two :param options: data to update link with :return: nothing - :raises core.CoreError: when updating a wireless type link, when there is a unknown - link between networks + :raises core.CoreError: when updating a wireless type link, when there is a + unknown link between networks """ if not options: options = LinkOptions() - - # get node objects identified by link data - node_one, node_two, net_one, net_two = self._link_nodes( - node_one_id, node_two_id + node1 = self.get_node(node_one_id, NodeBase) + node2 = self.get_node(node_two_id, NodeBase) + logging.info( + "update link(%s) node(%s):interface(%s) node(%s):interface(%s)", + options.type.name, + node1.name, + interface_one_id, + node2.name, + interface_two_id, ) - if node_one: - node_one.lock.acquire() - if node_two: - node_two.lock.acquire() - - try: - # wireless link - if options.type == LinkTypes.WIRELESS: - raise CoreError("cannot update wireless link") - else: - if not node_one and not node_two: - if net_one and net_two: - # modify link between nets - interface = net_one.getlinknetif(net_two) - upstream = False - - if not interface: - upstream = True - interface = net_two.getlinknetif(net_one) - - if not interface: - raise CoreError("modify unknown link between nets") - - if upstream: - interface.swapparams("_params_up") - net_one.linkconfig(interface, options) - interface.swapparams("_params_up") - else: - net_one.linkconfig(interface, options) - - if not options.unidirectional: - if upstream: - net_two.linkconfig(interface, options) - else: - interface.swapparams("_params_up") - net_two.linkconfig(interface, options) - interface.swapparams("_params_up") - else: - raise CoreError("modify link for unknown nodes") - elif not node_one: - # node1 = layer 2node, node2 = layer3 node - interface = node_two.netif(interface_two_id) - net_one.linkconfig(interface, options) - elif not node_two: - # node2 = layer 2node, node1 = layer3 node - interface = node_one.netif(interface_one_id) - net_one.linkconfig(interface, options) + # wireless link + if options.type == LinkTypes.WIRELESS: + raise CoreError("cannot update wireless link") + else: + if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): + interface1 = node1.netif(interface_one_id) + interface2 = node2.netif(interface_two_id) + if not interface1: + raise CoreError( + f"node({node1.name}) missing interface({interface_one_id})" + ) + if not interface2: + raise CoreError( + f"node({node2.name}) missing interface({interface_two_id})" + ) + if interface1.net != interface2.net: + raise CoreError( + f"node1({node1.name}) node2({node2.name}) " + "not connected to same net" + ) + ptp = interface1.net + ptp.linkconfig(interface1, options, interface2) + if not options.unidirectional: + ptp.linkconfig(interface2, options, interface1) + elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): + interface = node1.netif(interface_one_id) + node2.linkconfig(interface, options) + elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): + interface = node2.netif(interface_two_id) + node1.linkconfig(interface, options) + elif isinstance(node1, CoreNetworkBase) and isinstance( + node2, CoreNetworkBase + ): + interface = node1.getlinknetif(node2) + upstream = False + if not interface: + upstream = True + interface = node2.getlinknetif(node1) + if not interface: + raise CoreError("modify unknown link between nets") + if upstream: + interface.swapparams("_params_up") + node1.linkconfig(interface, options) + interface.swapparams("_params_up") else: - common_networks = node_one.commonnets(node_two) - if not common_networks: - raise CoreError("no common network found") - - for net_one, interface_one, interface_two in common_networks: - if ( - interface_one_id is not None - and interface_one_id != node_one.getifindex(interface_one) - ): - continue - - net_one.linkconfig(interface_one, options, interface_two) - if not options.unidirectional: - net_one.linkconfig(interface_two, options, interface_one) - finally: - if node_one: - node_one.lock.release() - if node_two: - node_two.lock.release() + node1.linkconfig(interface, options) + if not options.unidirectional: + if upstream: + node2.linkconfig(interface, options) + else: + interface.swapparams("_params_up") + node2.linkconfig(interface, options) + interface.swapparams("_params_up") + else: + raise CoreError( + f"cannot update link node1({type(node1)}) node2({type(node2)})" + ) def _next_node_id(self) -> int: """ @@ -1345,17 +1209,15 @@ class Session: :return: True if node deleted, False otherwise """ # delete node and check for session shutdown if a node was removed - logging.info("deleting node(%s)", _id) node = None with self._nodes_lock: if _id in self.nodes: node = self.nodes.pop(_id) - + logging.info("deleted node(%s)", node.name) if node: node.shutdown() self.sdt.delete_node(_id) self.check_shutdown() - return node is not None def delete_nodes(self) -> None: @@ -1767,15 +1629,15 @@ class Session: try: ip4 = control_net.prefix[node.id] ip4_mask = control_net.prefix.prefixlen - interface = InterfaceData( + interface_data = InterfaceData( id=control_net.CTRLIF_IDX_BASE + net_index, name=f"ctrl{net_index}", mac=utils.random_mac(), ip4=ip4, ip4_mask=ip4_mask, ) - ifindex = node.newnetif(control_net, interface) - node.netif(ifindex).control = True + interface = node.newnetif(control_net, interface_data) + interface.control = True except ValueError: msg = f"Control interface not added to node {node.id}. " msg += f"Invalid control network prefix ({control_net.prefix}). " diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 0c76d6a2..498a9beb 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -355,10 +355,11 @@ class CoreNodeBase(NodeBase): :return: nothing """ if ifindex not in self._netif: - raise ValueError(f"ifindex {ifindex} does not exist") + raise CoreError(f"node({self.name}) ifindex({ifindex}) does not exist") netif = self._netif.pop(ifindex) + logging.info("node(%s) removing interface(%s)", self.name, netif.name) + netif.detachnet() netif.shutdown() - del netif def netif(self, ifindex: int) -> Optional[CoreInterface]: """ @@ -473,6 +474,18 @@ class CoreNodeBase(NodeBase): """ raise NotImplementedError + def newnetif( + self, net: "CoreNetworkBase", interface: InterfaceData + ) -> CoreInterface: + """ + Create a new network interface. + + :param net: network to associate with + :param interface: interface data for new interface + :return: interface index + """ + raise NotImplementedError + class CoreNode(CoreNodeBase): """ @@ -846,7 +859,9 @@ class CoreNode(CoreNodeBase): interface_name = self.ifname(ifindex) self.node_net_client.device_up(interface_name) - def newnetif(self, net: "CoreNetworkBase", interface: InterfaceData) -> int: + def newnetif( + self, net: "CoreNetworkBase", interface: InterfaceData + ) -> CoreInterface: """ Create a new network interface. @@ -868,16 +883,16 @@ class CoreNode(CoreNodeBase): netif.sethwaddr(interface.mac) for address in addresses: netif.addaddr(address) - return ifindex else: ifindex = self.newveth(interface.id, interface.name) - self.attachnet(ifindex, net) - if interface.mac: - self.sethwaddr(ifindex, interface.mac) - for address in addresses: - self.addaddr(ifindex, address) - self.ifup(ifindex) - return ifindex + self.attachnet(ifindex, net) + if interface.mac: + self.sethwaddr(ifindex, interface.mac) + for address in addresses: + self.addaddr(ifindex, address) + self.ifup(ifindex) + netif = self.netif(ifindex) + return netif def addfile(self, srcname: str, filename: str) -> None: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 095fbe9b..6d6ad589 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -12,7 +12,7 @@ import netaddr from core import utils from core.constants import EBTABLES_BIN, TC_BIN from core.emulator.data import LinkData, NodeData -from core.emulator.emudata import LinkOptions +from core.emulator.emudata import InterfaceData, LinkOptions from core.emulator.enumerations import ( LinkTypes, MessageFlags, @@ -697,15 +697,19 @@ class GreTapBridge(CoreNetwork): ) self.attach(self.gretap) - def setkey(self, key: int) -> None: + def setkey(self, key: int, interface_data: InterfaceData) -> None: """ Set the GRE key used for the GreTap device. This needs to be set prior to instantiating the GreTap device (before addrconfig). :param key: gre key + :param interface_data: interface data for setting up tunnel key :return: nothing """ self.grekey = key + addresses = interface_data.get_addresses() + if addresses: + self.addrconfig(addresses) class CtrlNet(CoreNetwork): diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index ee00c705..6faa7824 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -157,7 +157,7 @@ class PhysicalNode(CoreNodeBase): self.ifindex += 1 return ifindex - def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int: + def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface: logging.info("creating interface") addresses = interface.get_addresses() ifindex = interface.id @@ -171,12 +171,12 @@ class PhysicalNode(CoreNodeBase): # tunnel to net not built yet, so build it now and adopt it _, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) self.adoptnetif(remote_tap, ifindex, interface.mac, addresses) - return ifindex + return remote_tap else: # this is reached when configuring services (self.up=False) netif = GreTap(node=self, name=name, session=self.session, start=False) self.adoptnetif(netif, ifindex, interface.mac, addresses) - return ifindex + return netif def privatedir(self, path: str) -> None: if path[0] != "/": @@ -297,7 +297,7 @@ class Rj45Node(CoreNodeBase): self.up = False self.restorestate() - def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> int: + def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface: """ This is called when linking with another node. Since this node represents an interface, we do not create another object here, @@ -320,7 +320,7 @@ class Rj45Node(CoreNodeBase): self.interface.attachnet(net) for addr in interface.get_addresses(): self.addaddr(addr) - return ifindex + return self.interface def delnetif(self, ifindex: int) -> None: """ diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 26e78367..0cbdb8ae 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -54,12 +54,11 @@ class TestNodes: node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) interface_data = InterfaceData() - index = node.newnetif(switch, interface_data) - interface = node.netif(index) + interface = node.newnetif(switch, interface_data) mac = "aa:aa:aa:ff:ff:ff" # when - node.sethwaddr(index, mac) + node.sethwaddr(interface.netindex, mac) # then assert interface.hwaddr == mac @@ -69,25 +68,23 @@ class TestNodes: node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) interface_data = InterfaceData() - index = node.newnetif(switch, interface_data) - node.netif(index) + interface = node.newnetif(switch, interface_data) mac = "aa:aa:aa:ff:ff:fff" # when with pytest.raises(CoreError): - node.sethwaddr(index, mac) + node.sethwaddr(interface.netindex, mac) def test_node_addaddr(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) interface_data = InterfaceData() - index = node.newnetif(switch, interface_data) - interface = node.netif(index) + interface = node.newnetif(switch, interface_data) addr = "192.168.0.1/24" # when - node.addaddr(index, addr) + node.addaddr(interface.netindex, addr) # then assert interface.addrlist[0] == addr @@ -97,13 +94,12 @@ class TestNodes: node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) interface_data = InterfaceData() - index = node.newnetif(switch, interface_data) - node.netif(index) + interface = node.newnetif(switch, interface_data) addr = "256.168.0.1/24" # when with pytest.raises(CoreError): - node.addaddr(index, addr) + node.addaddr(interface.netindex, addr) @pytest.mark.parametrize("net_type", NET_TYPES) def test_net(self, session, net_type): From e325bcfc5549be738fae934790a87020958c6484 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 14:41:05 -0700 Subject: [PATCH 032/146] bumped version for release --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 90b731a9..ae2d0c8d 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # this defines the CORE version number, must be static for AC_INIT -AC_INIT(core, 6.4.0) +AC_INIT(core, 6.5.0) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) From f2409d0604892d9727d08b6205d6e91386bf6088 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 15:40:25 -0700 Subject: [PATCH 033/146] updated changelog for 6.5.0 --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f4557a3a..96f7b30a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,40 @@ +## 2020-06-11 CORE 6.5.0 +* Breaking Changes + * CoreNode.newnetif - both parameters are required and now takes an InterfaceData object as its second parameter + * CoreNetworkBase.linkconfig - now takes a LinkOptions parameter instead of a subset of some of the options (ie bandwidth, delay, etc) + * \#453 - Session.add_node and Session.get_node now requires the node class you expect to create/retrieve + * \#458 - rj45 cleanup to only inherit from one class +* Enhancements + * fixed issues with handling bad commands for TLV execute messages + * removed unused boot.sh from CoreNode types + * added linkconfig to CoreNetworkBase and cleaned up function signature + * emane position hook now saves geo position to node + * emane pathloss support + * core.emulator.emudata leveraged dataclass and type hinting + * \#459 - updated transport type usage to an enum + * \#460 - updated network policy type usage to an enum +* Python GUI Enhancements + * fixed throughput events do not work for joined sessions + * fixed exiting app with a toolbar picker showing + * fixed issue with creating interfaces and reusing subnets after deletion + * fixed issue with moving text shapes + * fixed scaling with custom node selected + * fixed toolbar state switching issues + * enable/disable toolbar when running stop/start + * marker config integrated into toolbar + * improved color picker layout + * shapes can now be moved while drawing shapes + * added observers to toolbar in run mode +* gRPC API + * node events will now have geo positional data + * node geo data is now returned in get_session and get_node calls + * \#451 - added wlan link api to allow direct linking/unlinking of wireless links between nodes + * \#462 - added streaming call for sending node position/geo changes + * \#463 - added streaming call for emane pathloss events +* Bugfixes + * \#454 - fixed issue creating docker nodes, but containers are now required to have networking tools + * \#466 - fixed issue in python gui when xml file is loading nodes with no ip4 addresses + ## 2020-05-11 CORE 6.4.0 * Enhancements * updates to core-route-monitor, allow specific session, configurable settings, and properly From c64094ac1c219c5dc9b0d56f07279c702b3115cf Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:01:38 -0700 Subject: [PATCH 034/146] daemon: updated session.delete_link to have the interface ids default to none, since only one may need to be provided, updated link tests to account for more cases --- daemon/core/api/tlv/corehandlers.py | 1 - daemon/core/emulator/session.py | 4 +- daemon/tests/test_links.py | 136 +++++++++++++++++++++++++--- 3 files changed, 126 insertions(+), 15 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 5531e5af..3adaed63 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -788,7 +788,6 @@ class CoreHandler(socketserver.BaseRequestHandler): link_options.network_id = message.get_tlv(LinkTlvs.NETWORK_ID.value) link_options.key = message.get_tlv(LinkTlvs.KEY.value) link_options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) - if message.flags & MessageFlags.ADD.value: self.session.add_link( node_one_id, node_two_id, interface_one, interface_two, link_options diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 54486bfb..854d5cc8 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -319,8 +319,8 @@ class Session: self, node_one_id: int, node_two_id: int, - interface_one_id: int, - interface_two_id: int, + interface_one_id: int = None, + interface_two_id: int = None, link_type: LinkTypes = LinkTypes.WIRED, ) -> None: """ diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 9736537e..71942e4b 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -25,7 +25,7 @@ def create_ptp_network( class TestLinks: - def test_ptp(self, session: Session, ip_prefixes: IpPrefixes): + def test_add_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given node_one = session.add_node(CoreNode) node_two = session.add_node(CoreNode) @@ -39,20 +39,20 @@ class TestLinks: assert node_one.netif(interface_one.id) assert node_two.netif(interface_two.id) - def test_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): + def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given node_one = session.add_node(CoreNode) node_two = session.add_node(SwitchNode) interface_one = ip_prefixes.create_interface(node_one) # when - session.add_link(node_one.id, node_two.id, interface_one) + session.add_link(node_one.id, node_two.id, interface_one=interface_one) # then assert node_two.all_link_data() assert node_one.netif(interface_one.id) - def test_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): + def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given node_one = session.add_node(SwitchNode) node_two = session.add_node(CoreNode) @@ -76,7 +76,7 @@ class TestLinks: # then assert node_one.all_link_data() - def test_link_update(self, session: Session, ip_prefixes: IpPrefixes): + def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given delay = 50 bandwidth = 5000000 @@ -95,12 +95,9 @@ class TestLinks: assert interface_one.getparam("jitter") != jitter # when - link_options = LinkOptions() - link_options.delay = delay - link_options.bandwidth = bandwidth - link_options.per = per - link_options.dup = dup - link_options.jitter = jitter + link_options = LinkOptions( + delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + ) session.update_link( node_one.id, node_two.id, @@ -115,7 +112,94 @@ class TestLinks: assert interface_one.getparam("duplicate") == dup assert interface_one.getparam("jitter") == jitter - def test_link_delete(self, session: Session, ip_prefixes: IpPrefixes): + def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): + # given + delay = 50 + bandwidth = 5000000 + per = 25 + dup = 25 + jitter = 10 + node_one = session.add_node(SwitchNode) + node_two = session.add_node(CoreNode) + interface_two_data = ip_prefixes.create_interface(node_two) + session.add_link(node_one.id, node_two.id, interface_two=interface_two_data) + interface_two = node_two.netif(interface_two_data.id) + assert interface_two.getparam("delay") != delay + assert interface_two.getparam("bw") != bandwidth + assert interface_two.getparam("loss") != per + assert interface_two.getparam("duplicate") != dup + assert interface_two.getparam("jitter") != jitter + + # when + link_options = LinkOptions( + delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + ) + session.update_link( + node_one.id, + node_two.id, + interface_two_id=interface_two_data.id, + options=link_options, + ) + + # then + assert interface_two.getparam("delay") == delay + assert interface_two.getparam("bw") == bandwidth + assert interface_two.getparam("loss") == per + assert interface_two.getparam("duplicate") == dup + assert interface_two.getparam("jitter") == jitter + + def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): + # given + delay = 50 + bandwidth = 5000000 + per = 25 + dup = 25 + jitter = 10 + node_one = session.add_node(CoreNode) + node_two = session.add_node(CoreNode) + interface_one_data = ip_prefixes.create_interface(node_one) + interface_two_data = ip_prefixes.create_interface(node_two) + session.add_link( + node_one.id, node_two.id, interface_one_data, interface_two_data + ) + interface_one = node_one.netif(interface_one_data.id) + interface_two = node_two.netif(interface_two_data.id) + assert interface_one.getparam("delay") != delay + assert interface_one.getparam("bw") != bandwidth + assert interface_one.getparam("loss") != per + assert interface_one.getparam("duplicate") != dup + assert interface_one.getparam("jitter") != jitter + assert interface_two.getparam("delay") != delay + assert interface_two.getparam("bw") != bandwidth + assert interface_two.getparam("loss") != per + assert interface_two.getparam("duplicate") != dup + assert interface_two.getparam("jitter") != jitter + + # when + link_options = LinkOptions( + delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + ) + session.update_link( + node_one.id, + node_two.id, + interface_one_data.id, + interface_two_data.id, + link_options, + ) + + # then + assert interface_one.getparam("delay") == delay + assert interface_one.getparam("bw") == bandwidth + assert interface_one.getparam("loss") == per + assert interface_one.getparam("duplicate") == dup + assert interface_one.getparam("jitter") == jitter + assert interface_two.getparam("delay") == delay + assert interface_two.getparam("bw") == bandwidth + assert interface_two.getparam("loss") == per + assert interface_two.getparam("duplicate") == dup + assert interface_two.getparam("jitter") == jitter + + def test_delete_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given node_one = session.add_node(CoreNode) node_two = session.add_node(CoreNode) @@ -133,3 +217,31 @@ class TestLinks: # then assert not node_one.netif(interface_one.id) assert not node_two.netif(interface_two.id) + + def test_delete_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): + # given + node_one = session.add_node(CoreNode) + node_two = session.add_node(SwitchNode) + interface_one = ip_prefixes.create_interface(node_one) + session.add_link(node_one.id, node_two.id, interface_one) + assert node_one.netif(interface_one.id) + + # when + session.delete_link(node_one.id, node_two.id, interface_one_id=interface_one.id) + + # then + assert not node_one.netif(interface_one.id) + + def test_delete_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): + # given + node_one = session.add_node(SwitchNode) + node_two = session.add_node(CoreNode) + interface_two = ip_prefixes.create_interface(node_two) + session.add_link(node_one.id, node_two.id, interface_two=interface_two) + assert node_two.netif(interface_two.id) + + # when + session.delete_link(node_one.id, node_two.id, interface_two_id=interface_two.id) + + # then + assert not node_two.netif(interface_two.id) From 00cda5c55067db0f995817bcd0d4e04f1fe2eae8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:08:50 -0700 Subject: [PATCH 035/146] fixed test_link name --- daemon/tests/test_links.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 71942e4b..9f693da1 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -65,7 +65,7 @@ class TestLinks: assert node_one.all_link_data() assert node_two.netif(interface_two.id) - def test_net_to_net(self, session): + def test_add_net_to_net(self, session): # given node_one = session.add_node(SwitchNode) node_two = session.add_node(SwitchNode) From e72e332babe9aa8502b903bac2f6a8bd0662f2c9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:12:51 -0700 Subject: [PATCH 036/146] daemon: removed need to use getaddr for CoreInterface.othernet as it now has a default of None --- daemon/core/nodes/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 498a9beb..4b8d513b 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1040,7 +1040,7 @@ class CoreNetworkBase(NodeBase): :return: interface the provided network is linked to """ for netif in self.netifs(): - if getattr(netif, "othernet", None) == net: + if netif.othernet == net: return netif return None From cfaa9397ada5df0a5cde683376e0bf3de4bfe807 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 08:34:02 -0700 Subject: [PATCH 037/146] daemon: added class variable type hinting to core.api.grpc --- daemon/core/api/grpc/client.py | 12 ++++++------ daemon/core/api/grpc/events.py | 6 +++--- daemon/core/api/grpc/grpcutils.py | 7 ++++--- daemon/core/api/grpc/server.py | 8 ++++---- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 0361a69b..1bc88069 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -5,7 +5,7 @@ gRpc client for interfacing with CORE. import logging import threading from contextlib import contextmanager -from typing import Any, Callable, Dict, Generator, Iterable, List +from typing import Any, Callable, Dict, Generator, Iterable, List, Optional import grpc @@ -108,7 +108,7 @@ class InterfaceHelper: :param ip6_prefix: ip6 prefix to use for generation :raises ValueError: when both ip4 and ip6 prefixes have not been provided """ - self.prefixes = IpPrefixes(ip4_prefix, ip6_prefix) + self.prefixes: IpPrefixes = IpPrefixes(ip4_prefix, ip6_prefix) def create_interface( self, node_id: int, interface_id: int, name: str = None, mac: str = None @@ -177,10 +177,10 @@ class CoreGrpcClient: :param address: grpc server address to connect to """ - self.address = address - self.stub = None - self.channel = None - self.proxy = proxy + self.address: str = address + self.stub: Optional[core_pb2_grpc.CoreApiStub] = None + self.channel: Optional[grpc.Channel] = None + self.proxy: bool = proxy def start_session( self, diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 837860e3..82cf1eac 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -140,9 +140,9 @@ class EventStreamer: :param session: session to process events for :param event_types: types of events to process """ - self.session = session - self.event_types = event_types - self.queue = Queue() + self.session: Session = session + self.event_types: Iterable[core_pb2.EventType] = event_types + self.queue: Queue = Queue() self.add_handlers() def add_handlers(self) -> None: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 5c6f3a80..73d19a2a 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -1,6 +1,6 @@ import logging import time -from typing import Any, Dict, List, Tuple, Type +from typing import Any, Dict, List, Tuple, Type, Union import grpc import netaddr @@ -190,7 +190,8 @@ def convert_value(value: Any) -> str: def get_config_options( - config: Dict[str, str], configurable_options: Type[ConfigurableOptions] + config: Dict[str, str], + configurable_options: Union[ConfigurableOptions, Type[ConfigurableOptions]], ) -> Dict[str, common_pb2.ConfigOption]: """ Retrieve configuration options in a form that is used by the grpc server. @@ -418,7 +419,7 @@ def service_configuration(session: Session, config: ServiceConfig) -> None: service.shutdown = tuple(config.shutdown) -def get_service_configuration(service: Type[CoreService]) -> NodeServiceData: +def get_service_configuration(service: CoreService) -> NodeServiceData: """ Convenience for converting a service to service data proto. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 7d7f7c80..adddff14 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -6,7 +6,7 @@ import tempfile import threading import time from concurrent import futures -from typing import Iterable, Type +from typing import Iterable, Optional, Type import grpc from grpc import ServicerContext @@ -131,9 +131,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def __init__(self, coreemu: CoreEmu) -> None: super().__init__() - self.coreemu = coreemu - self.running = True - self.server = None + self.coreemu: CoreEmu = coreemu + self.running: bool = True + self.server: Optional[grpc.Server] = None atexit.register(self._exit_handler) def _exit_handler(self) -> None: From ef3cf5697d0080b45a5bf5894c5e87f2b105c0ea Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 08:54:06 -0700 Subject: [PATCH 038/146] daemon: added class variable type hinting for core.xml --- daemon/core/config.py | 7 ++++--- daemon/core/xml/corexml.py | 28 +++++++++++----------------- daemon/core/xml/corexmldeployment.py | 6 +++--- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/daemon/core/config.py b/daemon/core/config.py index 1f5bc3c0..d4ba6164 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -4,7 +4,7 @@ Common support for configurable CORE objects. import logging from collections import OrderedDict -from typing import TYPE_CHECKING, Dict, List, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, Union from core.emane.nodes import EmaneNet from core.emulator.enumerations import ConfigDataTypes @@ -136,7 +136,8 @@ class ConfigurableManager: """ Clears all configurations or configuration for a specific node. - :param node_id: node id to clear configurations for, default is None and clears all configurations + :param node_id: node id to clear configurations for, default is None and clears + all configurations :return: nothing """ if not node_id: @@ -222,7 +223,7 @@ class ConfigurableManager: result = node_configs.get(config_type) return result - def get_all_configs(self, node_id: int = _default_node) -> List[Dict[str, str]]: + def get_all_configs(self, node_id: int = _default_node) -> Dict[str, Any]: """ Retrieve all current configuration types for a node. diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index cb25e717..973eb77f 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -124,9 +124,9 @@ def add_configuration(parent: etree.Element, name: str, value: str) -> None: class NodeElement: def __init__(self, session: "Session", node: NodeBase, element_name: str) -> None: - self.session = session - self.node = node - self.element = etree.Element(element_name) + self.session: "Session" = session + self.node: NodeBase = node + self.element: etree.Element = etree.Element(element_name) add_attribute(self.element, "id", node.id) add_attribute(self.element, "name", node.name) add_attribute(self.element, "icon", node.icon) @@ -151,8 +151,8 @@ class NodeElement: class ServiceElement: def __init__(self, service: Type[CoreService]) -> None: - self.service = service - self.element = etree.Element("service") + self.service: Type[CoreService] = service + self.element: etree.Element = etree.Element("service") add_attribute(self.element, "name", service.name) self.add_directories() self.add_startup() @@ -268,10 +268,10 @@ class NetworkElement(NodeElement): class CoreXmlWriter: def __init__(self, session: "Session") -> None: - self.session = session - self.scenario = etree.Element("scenario") - self.networks = None - self.devices = None + self.session: "Session" = session + self.scenario: etree.Element = etree.Element("scenario") + self.networks: etree.SubElement = etree.SubElement(self.scenario, "networks") + self.devices: etree.SubElement = etree.SubElement(self.scenario, "devices") self.write_session() def write_session(self) -> None: @@ -362,13 +362,11 @@ class CoreXmlWriter: def write_emane_configs(self) -> None: emane_global_configuration = create_emane_config(self.session) self.scenario.append(emane_global_configuration) - emane_configurations = etree.Element("emane_configurations") for node_id in self.session.emane.nodes(): all_configs = self.session.emane.get_all_configs(node_id) if not all_configs: continue - for model_name in all_configs: config = all_configs[model_name] logging.debug( @@ -453,9 +451,6 @@ class CoreXmlWriter: self.scenario.append(node_types) def write_nodes(self) -> List[LinkData]: - self.networks = etree.SubElement(self.scenario, "networks") - self.devices = etree.SubElement(self.scenario, "devices") - links = [] for node_id in self.session.nodes: node = self.session.nodes[node_id] @@ -472,7 +467,6 @@ class CoreXmlWriter: # add known links links.extend(node.all_link_data()) - return links def write_network(self, node: NodeBase) -> None: @@ -597,8 +591,8 @@ class CoreXmlWriter: class CoreXmlReader: def __init__(self, session: "Session") -> None: - self.session = session - self.scenario = None + self.session: "Session" = session + self.scenario: Optional[etree.ElementTree] = None def read(self, file_name: str) -> None: xml_tree = etree.parse(file_name) diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 5f340b69..04915bf1 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -101,9 +101,9 @@ def get_ipv4_addresses(hostname: str) -> List[Tuple[str, str]]: class CoreXmlDeployment: def __init__(self, session: "Session", scenario: etree.Element) -> None: - self.session = session - self.scenario = scenario - self.root = etree.SubElement( + self.session: "Session" = session + self.scenario: etree.Element = scenario + self.root: etree.SubElement = etree.SubElement( scenario, "container", id="TestBed", name="TestBed" ) self.add_deployment() From 6201875b782c266335f23518a4cfd8cfc30f5b42 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 09:52:01 -0700 Subject: [PATCH 039/146] daemon: added class variable type hinting to core.emane --- daemon/core/emane/bypass.py | 13 ++++--- daemon/core/emane/commeffect.py | 16 ++++---- daemon/core/emane/emanemanager.py | 44 +++++++++++---------- daemon/core/emane/emanemanifest.py | 1 + daemon/core/emane/emanemodel.py | 26 +++++++------ daemon/core/emane/ieee80211abg.py | 6 +-- daemon/core/emane/linkmonitor.py | 61 ++++++++++++++++-------------- daemon/core/emane/nodes.py | 26 +++++++------ daemon/core/emane/rfpipe.py | 6 +-- daemon/core/emane/tdma.py | 13 ++++--- daemon/core/nodes/network.py | 4 +- 11 files changed, 116 insertions(+), 100 deletions(-) diff --git a/daemon/core/emane/bypass.py b/daemon/core/emane/bypass.py index 83f3b6e8..8aabc3f9 100644 --- a/daemon/core/emane/bypass.py +++ b/daemon/core/emane/bypass.py @@ -1,6 +1,7 @@ """ EMANE Bypass model for CORE """ +from typing import List, Set from core.config import Configuration from core.emane import emanemodel @@ -8,14 +9,14 @@ from core.emulator.enumerations import ConfigDataTypes class EmaneBypassModel(emanemodel.EmaneModel): - name = "emane_bypass" + name: str = "emane_bypass" # values to ignore, when writing xml files - config_ignore = {"none"} + config_ignore: Set[str] = {"none"} # mac definitions - mac_library = "bypassmaclayer" - mac_config = [ + mac_library: str = "bypassmaclayer" + mac_config: List[Configuration] = [ Configuration( _id="none", _type=ConfigDataTypes.BOOL, @@ -25,8 +26,8 @@ class EmaneBypassModel(emanemodel.EmaneModel): ] # phy definitions - phy_library = "bypassphylayer" - phy_config = [] + phy_library: str = "bypassphylayer" + phy_config: List[Configuration] = [] @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index b7060e96..71acb199 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -22,6 +22,7 @@ except ImportError: try: from emanesh.events.commeffectevent import CommEffectEvent except ImportError: + CommEffectEvent = None logging.debug("compatible emane python bindings not installed") @@ -38,16 +39,15 @@ def convert_none(x: float) -> int: class EmaneCommEffectModel(emanemodel.EmaneModel): - name = "emane_commeffect" - - shim_library = "commeffectshim" - shim_xml = "commeffectshim.xml" - shim_defaults = {} - config_shim = [] + name: str = "emane_commeffect" + shim_library: str = "commeffectshim" + shim_xml: str = "commeffectshim.xml" + shim_defaults: Dict[str, str] = {} + config_shim: List[Configuration] = [] # comm effect does not need the default phy and external configurations - phy_config = [] - external_config = [] + phy_config: List[Configuration] = [] + external_config: List[Configuration] = [] @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 12b477f0..146d186f 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -70,11 +70,13 @@ class EmaneManager(ModelManager): controlling the EMANE daemons. """ - name = "emane" - config_type = RegisterTlvs.EMULATION_SERVER - SUCCESS, NOT_NEEDED, NOT_READY = (0, 1, 2) - EVENTCFGVAR = "LIBEMANEEVENTSERVICECONFIG" - DEFAULT_LOG_LEVEL = 3 + name: str = "emane" + config_type: RegisterTlvs = RegisterTlvs.EMULATION_SERVER + SUCCESS: int = 0 + NOT_NEEDED: int = 1 + NOT_READY: int = 2 + EVENTCFGVAR: str = "LIBEMANEEVENTSERVICECONFIG" + DEFAULT_LOG_LEVEL: int = 3 def __init__(self, session: "Session") -> None: """ @@ -84,29 +86,29 @@ class EmaneManager(ModelManager): :return: nothing """ super().__init__() - self.session = session - self._emane_nets = {} - self._emane_node_lock = threading.Lock() + self.session: "Session" = session + self._emane_nets: Dict[int, EmaneNet] = {} + self._emane_node_lock: threading.Lock = threading.Lock() # port numbers are allocated from these counters - self.platformport = self.session.options.get_config_int( + self.platformport: int = self.session.options.get_config_int( "emane_platform_port", 8100 ) - self.transformport = self.session.options.get_config_int( + self.transformport: int = self.session.options.get_config_int( "emane_transform_port", 8200 ) - self.doeventloop = False - self.eventmonthread = None + self.doeventloop: bool = False + self.eventmonthread: Optional[threading.Thread] = None # model for global EMANE configuration options - self.emane_config = EmaneGlobalModel(session) + self.emane_config: EmaneGlobalModel = EmaneGlobalModel(session) self.set_configs(self.emane_config.default_values()) # link monitor - self.link_monitor = EmaneLinkMonitor(self) + self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self) - self.service = None - self.eventchannel = None - self.event_device = None + self.service: Optional[EventService] = None + self.eventchannel: Optional[Tuple[str, int, str]] = None + self.event_device: Optional[str] = None self.emane_check() def getifcconfig( @@ -890,12 +892,12 @@ class EmaneGlobalModel: Global EMANE configuration options. """ - name = "emane" - bitmap = None + name: str = "emane" + bitmap: Optional[str] = None def __init__(self, session: "Session") -> None: - self.session = session - self.core_config = [ + self.session: "Session" = session + self.core_config: List[Configuration] = [ Configuration( _id="platform_id_start", _type=ConfigDataTypes.INT32, diff --git a/daemon/core/emane/emanemanifest.py b/daemon/core/emane/emanemanifest.py index 914b4f83..41dc7beb 100644 --- a/daemon/core/emane/emanemanifest.py +++ b/daemon/core/emane/emanemanifest.py @@ -11,6 +11,7 @@ except ImportError: try: from emanesh import manifest except ImportError: + manifest = None logging.debug("compatible emane python bindings not installed") diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 7b5ff417..78d5ec5e 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -3,7 +3,7 @@ Defines Emane Models used within CORE. """ import logging import os -from typing import Dict, List +from typing import Dict, List, Optional, Set from core.config import ConfigGroup, Configuration from core.emane import emanemanifest @@ -25,19 +25,23 @@ class EmaneModel(WirelessModel): """ # default mac configuration settings - mac_library = None - mac_xml = None - mac_defaults = {} - mac_config = [] + mac_library: Optional[str] = None + mac_xml: Optional[str] = None + mac_defaults: Dict[str, str] = {} + mac_config: List[Configuration] = [] # default phy configuration settings, using the universal model - phy_library = None - phy_xml = "emanephy.xml" - phy_defaults = {"subid": "1", "propagationmodel": "2ray", "noisemode": "none"} - phy_config = [] + phy_library: Optional[str] = None + phy_xml: str = "emanephy.xml" + phy_defaults: Dict[str, str] = { + "subid": "1", + "propagationmodel": "2ray", + "noisemode": "none", + } + phy_config: List[Configuration] = [] # support for external configurations - external_config = [ + external_config: List[Configuration] = [ Configuration("external", ConfigDataTypes.BOOL, default="0"), Configuration( "platformendpoint", ConfigDataTypes.STRING, default="127.0.0.1:40001" @@ -47,7 +51,7 @@ class EmaneModel(WirelessModel): ), ] - config_ignore = set() + config_ignore: Set[str] = set() @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/ieee80211abg.py b/daemon/core/emane/ieee80211abg.py index ecfd3694..0d58ec9e 100644 --- a/daemon/core/emane/ieee80211abg.py +++ b/daemon/core/emane/ieee80211abg.py @@ -8,11 +8,11 @@ from core.emane import emanemodel class EmaneIeee80211abgModel(emanemodel.EmaneModel): # model name - name = "emane_ieee80211abg" + name: str = "emane_ieee80211abg" # mac configuration - mac_library = "ieee80211abgmaclayer" - mac_xml = "ieee80211abgmaclayer.xml" + mac_library: str = "ieee80211abgmaclayer" + mac_xml: str = "ieee80211abgmaclayer.xml" @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index 861c108c..b9fd9a2a 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -2,7 +2,7 @@ import logging import sched import threading import time -from typing import TYPE_CHECKING, Dict, List, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple import netaddr from lxml import etree @@ -17,28 +17,29 @@ except ImportError: try: from emanesh import shell except ImportError: + shell = None logging.debug("compatible emane python bindings not installed") if TYPE_CHECKING: from core.emane.emanemanager import EmaneManager -DEFAULT_PORT = 47_000 -MAC_COMPONENT_INDEX = 1 -EMANE_RFPIPE = "rfpipemaclayer" -EMANE_80211 = "ieee80211abgmaclayer" -EMANE_TDMA = "tdmaeventschedulerradiomodel" -SINR_TABLE = "NeighborStatusTable" -NEM_SELF = 65535 +DEFAULT_PORT: int = 47_000 +MAC_COMPONENT_INDEX: int = 1 +EMANE_RFPIPE: str = "rfpipemaclayer" +EMANE_80211: str = "ieee80211abgmaclayer" +EMANE_TDMA: str = "tdmaeventschedulerradiomodel" +SINR_TABLE: str = "NeighborStatusTable" +NEM_SELF: int = 65535 class LossTable: def __init__(self, losses: Dict[float, float]) -> None: - self.losses = losses - self.sinrs = sorted(self.losses.keys()) - self.loss_lookup = {} + self.losses: Dict[float, float] = losses + self.sinrs: List[float] = sorted(self.losses.keys()) + self.loss_lookup: Dict[int, float] = {} for index, value in enumerate(self.sinrs): self.loss_lookup[index] = self.losses[value] - self.mac_id = None + self.mac_id: Optional[str] = None def get_loss(self, sinr: float) -> float: index = self._get_index(sinr) @@ -54,11 +55,11 @@ class LossTable: class EmaneLink: def __init__(self, from_nem: int, to_nem: int, sinr: float) -> None: - self.from_nem = from_nem - self.to_nem = to_nem - self.sinr = sinr - self.last_seen = None - self.updated = False + self.from_nem: int = from_nem + self.to_nem: int = to_nem + self.sinr: float = sinr + self.last_seen: Optional[float] = None + self.updated: bool = False self.touch() def update(self, sinr: float) -> None: @@ -78,9 +79,11 @@ class EmaneLink: class EmaneClient: def __init__(self, address: str) -> None: - self.address = address - self.client = shell.ControlPortClient(self.address, DEFAULT_PORT) - self.nems = {} + self.address: str = address + self.client: shell.ControlPortClient = shell.ControlPortClient( + self.address, DEFAULT_PORT + ) + self.nems: Dict[int, LossTable] = {} self.setup() def setup(self) -> None: @@ -174,15 +177,15 @@ class EmaneClient: class EmaneLinkMonitor: def __init__(self, emane_manager: "EmaneManager") -> None: - self.emane_manager = emane_manager - self.clients = [] - self.links = {} - self.complete_links = set() - self.loss_threshold = None - self.link_interval = None - self.link_timeout = None - self.scheduler = None - self.running = False + self.emane_manager: "EmaneManager" = emane_manager + self.clients: List[EmaneClient] = [] + self.links: Dict[Tuple[int, int], EmaneLink] = {} + self.complete_links: Set[Tuple[int, int]] = set() + self.loss_threshold: Optional[int] = None + self.link_interval: Optional[int] = None + self.link_timeout: Optional[int] = None + self.scheduler: Optional[sched.scheduler] = None + self.running: bool = False def start(self) -> None: self.loss_threshold = int(self.emane_manager.get_config("loss_threshold")) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index f4de8f47..e88cb194 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -16,13 +16,16 @@ from core.emulator.enumerations import ( RegisterTlvs, TransportType, ) +from core.errors import CoreError from core.nodes.base import CoreNetworkBase from core.nodes.interface import CoreInterface if TYPE_CHECKING: + from core.emane.emanemodel import EmaneModel from core.emulator.session import Session - from core.location.mobility import WirelessModel + from core.location.mobility import WirelessModel, WayPointMobility + OptionalEmaneModel = Optional[EmaneModel] WirelessModelType = Type[WirelessModel] try: @@ -31,6 +34,7 @@ except ImportError: try: from emanesh.events import LocationEvent except ImportError: + LocationEvent = None logging.debug("compatible emane python bindings not installed") @@ -41,10 +45,10 @@ class EmaneNet(CoreNetworkBase): Emane controller object that exists in a session. """ - apitype = NodeTypes.EMANE - linktype = LinkTypes.WIRED - type = "wlan" - is_emane = True + apitype: NodeTypes = NodeTypes.EMANE + linktype: LinkTypes = LinkTypes.WIRED + type: str = "wlan" + is_emane: bool = True def __init__( self, @@ -55,10 +59,10 @@ class EmaneNet(CoreNetworkBase): server: DistributedServer = None, ) -> None: super().__init__(session, _id, name, start, server) - self.conf = "" - self.nemidmap = {} - self.model = None - self.mobility = None + self.conf: str = "" + self.nemidmap: Dict[CoreInterface, int] = {} + self.model: "OptionalEmaneModel" = None + self.mobility: Optional[WayPointMobility] = None def linkconfig( self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None @@ -84,11 +88,11 @@ class EmaneNet(CoreNetworkBase): def updatemodel(self, config: Dict[str, str]) -> None: if not self.model: - raise ValueError("no model set to update for node(%s)", self.id) + raise CoreError(f"no model set to update for node({self.name})") logging.info( "node(%s) updating model(%s): %s", self.id, self.model.name, config ) - self.model.set_configs(config, node_id=self.id) + self.model.update_config(config) def setmodel(self, model: "WirelessModelType", config: Dict[str, str]) -> None: """ diff --git a/daemon/core/emane/rfpipe.py b/daemon/core/emane/rfpipe.py index 23790b3c..068ef800 100644 --- a/daemon/core/emane/rfpipe.py +++ b/daemon/core/emane/rfpipe.py @@ -8,11 +8,11 @@ from core.emane import emanemodel class EmaneRfPipeModel(emanemodel.EmaneModel): # model name - name = "emane_rfpipe" + name: str = "emane_rfpipe" # mac configuration - mac_library = "rfpipemaclayer" - mac_xml = "rfpipemaclayer.xml" + mac_library: str = "rfpipemaclayer" + mac_xml: str = "rfpipemaclayer.xml" @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py index 17f5328f..ee80f3d7 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/tdma.py @@ -4,6 +4,7 @@ tdma.py: EMANE TDMA model bindings for CORE import logging import os +from typing import Set from core import constants, utils from core.config import Configuration @@ -13,18 +14,18 @@ from core.emulator.enumerations import ConfigDataTypes class EmaneTdmaModel(emanemodel.EmaneModel): # model name - name = "emane_tdma" + name: str = "emane_tdma" # mac configuration - mac_library = "tdmaeventschedulerradiomodel" - mac_xml = "tdmaeventschedulerradiomodel.xml" + mac_library: str = "tdmaeventschedulerradiomodel" + mac_xml: str = "tdmaeventschedulerradiomodel.xml" # add custom schedule options and ignore it when writing emane xml - schedule_name = "schedule" - default_schedule = os.path.join( + schedule_name: str = "schedule" + default_schedule: str = os.path.join( constants.CORE_DATA_DIR, "examples", "tdma", "schedule.xml" ) - config_ignore = {schedule_name} + config_ignore: Set[str] = {schedule_name} @classmethod def load(cls, emane_prefix: str) -> None: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 6d6ad589..235b43f2 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -1090,12 +1090,12 @@ class WlanNode(CoreNetwork): def update_mobility(self, config: Dict[str, str]) -> None: if not self.mobility: - raise ValueError(f"no mobility set to update for node({self.id})") + raise CoreError(f"no mobility set to update for node({self.name})") self.mobility.update_config(config) def updatemodel(self, config: Dict[str, str]) -> None: if not self.model: - raise ValueError(f"no model set to update for node({self.id})") + raise CoreError(f"no model set to update for node({self.name})") logging.debug( "node(%s) updating model(%s): %s", self.id, self.model.name, config ) From b28ef76d65a483c09bc19f28bec8e1c6635568d8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 10:05:49 -0700 Subject: [PATCH 040/146] daemon: added class variable type hinting to core.config --- daemon/core/config.py | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/daemon/core/config.py b/daemon/core/config.py index d4ba6164..618e1273 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -4,7 +4,7 @@ Common support for configurable CORE objects. import logging from collections import OrderedDict -from typing import TYPE_CHECKING, Any, Dict, List, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union from core.emane.nodes import EmaneNet from core.emulator.enumerations import ConfigDataTypes @@ -29,9 +29,9 @@ class ConfigGroup: :param start: configurations start index for this group :param stop: configurations stop index for this group """ - self.name = name - self.start = start - self.stop = stop + self.name: str = name + self.start: int = start + self.stop: int = stop class Configuration: @@ -56,18 +56,21 @@ class Configuration: :param default: default value for configuration :param options: list options if this is a configuration with a combobox """ - self.id = _id - self.type = _type - self.default = default + self.id: str = _id + self.type: ConfigDataTypes = _type + self.default: str = default if not options: options = [] - self.options = options + self.options: List[str] = options if not label: label = _id - self.label = label + self.label: str = label def __str__(self): - return f"{self.__class__.__name__}(id={self.id}, type={self.type}, default={self.default}, options={self.options})" + return ( + f"{self.__class__.__name__}(id={self.id}, type={self.type}, " + f"default={self.default}, options={self.options})" + ) class ConfigurableOptions: @@ -75,9 +78,9 @@ class ConfigurableOptions: Provides a base for defining configuration options within CORE. """ - name = None - bitmap = None - options = [] + name: Optional[str] = None + bitmap: Optional[str] = None + options: List[Configuration] = [] @classmethod def configurations(cls) -> List[Configuration]: @@ -115,8 +118,8 @@ class ConfigurableManager: nodes. """ - _default_node = -1 - _default_type = _default_node + _default_node: int = -1 + _default_type: int = _default_node def __init__(self) -> None: """ @@ -243,8 +246,8 @@ class ModelManager(ConfigurableManager): Creates a ModelManager object. """ super().__init__() - self.models = {} - self.node_models = {} + self.models: Dict[str, Any] = {} + self.node_models: Dict[int, str] = {} def set_model_config( self, node_id: int, model_name: str, config: Dict[str, str] = None From 76305f72577b273050818148c3303c9c9102232c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 12:49:53 -0700 Subject: [PATCH 041/146] converted usages of per to loss --- daemon/core/api/grpc/grpcutils.py | 4 ++-- daemon/core/api/grpc/server.py | 27 +++++++++++++------------- daemon/core/api/tlv/coreapi.py | 2 +- daemon/core/api/tlv/corehandlers.py | 10 +++++----- daemon/core/api/tlv/enumerations.py | 2 +- daemon/core/emane/commeffect.py | 2 +- daemon/core/emulator/data.py | 2 +- daemon/core/emulator/emudata.py | 2 +- daemon/core/gui/dialogs/linkconfig.py | 8 ++++---- daemon/core/location/mobility.py | 2 +- daemon/core/nodes/base.py | 4 ++-- daemon/core/nodes/network.py | 6 +++--- daemon/core/xml/corexml.py | 4 ++-- daemon/proto/core/api/grpc/core.proto | 2 +- daemon/tests/test_links.py | 28 +++++++++++++-------------- daemon/tests/test_xml.py | 16 +++++++-------- 16 files changed, 61 insertions(+), 60 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 73d19a2a..4acecad9 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -94,7 +94,7 @@ def add_link_data( if options_data: options.delay = options_data.delay options.bandwidth = options_data.bandwidth - options.per = options_data.per + options.loss = options_data.loss options.dup = options_data.dup options.jitter = options_data.jitter options.mer = options_data.mer @@ -343,7 +343,7 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: key=link_data.key, mburst=link_data.mburst, mer=link_data.mer, - per=link_data.per, + loss=link_data.loss, bandwidth=link_data.bandwidth, burst=link_data.burst, delay=link_data.delay, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index adddff14..9ea4e555 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -885,20 +885,21 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): interface_one_id = request.interface_one_id interface_two_id = request.interface_two_id options_data = request.options - link_options = LinkOptions() - link_options.delay = options_data.delay - link_options.bandwidth = options_data.bandwidth - link_options.per = options_data.per - link_options.dup = options_data.dup - link_options.jitter = options_data.jitter - link_options.mer = options_data.mer - link_options.burst = options_data.burst - link_options.mburst = options_data.mburst - link_options.unidirectional = options_data.unidirectional - link_options.key = options_data.key - link_options.opaque = options_data.opaque + options = LinkOptions( + delay=options_data.delay, + bandwidth=options_data.bandwidth, + loss=options_data.loss, + dup=options_data.dup, + jitter=options_data.jitter, + mer=options_data.mer, + burst=options_data.burst, + mburst=options_data.mburst, + unidirectional=options_data.unidirectional, + key=options_data.key, + opaque=options_data.opaque, + ) session.update_link( - node_one_id, node_two_id, interface_one_id, interface_two_id, link_options + node_one_id, node_two_id, interface_one_id, interface_two_id, options ) return core_pb2.EditLinkResponse(result=True) diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index df60e374..088a7631 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -495,7 +495,7 @@ class CoreLinkTlv(CoreTlv): LinkTlvs.N2_NUMBER.value: CoreTlvDataUint32, LinkTlvs.DELAY.value: CoreTlvDataUint64, LinkTlvs.BANDWIDTH.value: CoreTlvDataUint64, - LinkTlvs.PER.value: CoreTlvDataString, + LinkTlvs.LOSS.value: CoreTlvDataString, LinkTlvs.DUP.value: CoreTlvDataString, LinkTlvs.JITTER.value: CoreTlvDataUint64, LinkTlvs.MER.value: CoreTlvDataUint16, diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 3adaed63..d02c274d 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -334,9 +334,9 @@ class CoreHandler(socketserver.BaseRequestHandler): :return: nothing """ logging.debug("handling broadcast link: %s", link_data) - per = "" - if link_data.per is not None: - per = str(link_data.per) + loss = "" + if link_data.loss is not None: + loss = str(link_data.loss) dup = "" if link_data.dup is not None: dup = str(link_data.dup) @@ -348,7 +348,7 @@ class CoreHandler(socketserver.BaseRequestHandler): (LinkTlvs.N2_NUMBER, link_data.node2_id), (LinkTlvs.DELAY, link_data.delay), (LinkTlvs.BANDWIDTH, link_data.bandwidth), - (LinkTlvs.PER, per), + (LinkTlvs.LOSS, loss), (LinkTlvs.DUP, dup), (LinkTlvs.JITTER, link_data.jitter), (LinkTlvs.MER, link_data.mer), @@ -776,7 +776,7 @@ class CoreHandler(socketserver.BaseRequestHandler): link_options.delay = message.get_tlv(LinkTlvs.DELAY.value) link_options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) link_options.session = message.get_tlv(LinkTlvs.SESSION.value) - link_options.per = message.get_tlv(LinkTlvs.PER.value) + link_options.loss = message.get_tlv(LinkTlvs.LOSS.value) link_options.dup = message.get_tlv(LinkTlvs.DUP.value) link_options.jitter = message.get_tlv(LinkTlvs.JITTER.value) link_options.mer = message.get_tlv(LinkTlvs.MER.value) diff --git a/daemon/core/api/tlv/enumerations.py b/daemon/core/api/tlv/enumerations.py index ed06bbe7..0efb7c99 100644 --- a/daemon/core/api/tlv/enumerations.py +++ b/daemon/core/api/tlv/enumerations.py @@ -59,7 +59,7 @@ class LinkTlvs(Enum): N2_NUMBER = 0x02 DELAY = 0x03 BANDWIDTH = 0x04 - PER = 0x05 + LOSS = 0x05 DUP = 0x06 JITTER = 0x07 MER = 0x08 diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 71acb199..21252b6f 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -141,7 +141,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): nemid, latency=convert_none(options.delay), jitter=convert_none(options.jitter), - loss=convert_none(options.per), + loss=convert_none(options.loss), duplicate=convert_none(options.dup), unicast=int(convert_none(options.bandwidth)), broadcast=int(convert_none(options.bandwidth)), diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index d3283974..819716e3 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -101,7 +101,7 @@ class LinkData: node2_id: int = None delay: float = None bandwidth: float = None - per: float = None + loss: float = None dup: float = None jitter: float = None mer: float = None diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index b6dbd57c..992b9cd2 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -68,7 +68,7 @@ class LinkOptions: session: int = None delay: int = None bandwidth: int = None - per: float = None + loss: float = None dup: int = None jitter: int = None mer: int = None diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 92361ed4..c553bb94 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -223,7 +223,7 @@ class LinkConfigurationDialog(Dialog): duplicate = get_int(self.duplicate) loss = get_float(self.loss) options = core_pb2.LinkOptions( - bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, per=loss + bandwidth=bandwidth, jitter=jitter, delay=delay, dup=duplicate, loss=loss ) link.options.CopyFrom(options) @@ -252,7 +252,7 @@ class LinkConfigurationDialog(Dialog): jitter=down_jitter, delay=down_delay, dup=down_duplicate, - per=down_loss, + loss=down_loss, unidirectional=True, ) self.edge.asymmetric_link = core_pb2.Link( @@ -317,12 +317,12 @@ class LinkConfigurationDialog(Dialog): self.bandwidth.set(str(link.options.bandwidth)) self.jitter.set(str(link.options.jitter)) self.duplicate.set(str(link.options.dup)) - self.loss.set(str(link.options.per)) + self.loss.set(str(link.options.loss)) self.delay.set(str(link.options.delay)) if not self.is_symmetric: asym_link = self.edge.asymmetric_link self.down_bandwidth.set(str(asym_link.options.bandwidth)) self.down_jitter.set(str(asym_link.options.jitter)) self.down_duplicate.set(str(asym_link.options.dup)) - self.down_loss.set(str(asym_link.options.per)) + self.down_loss.set(str(asym_link.options.loss)) self.down_delay.set(str(asym_link.options.delay)) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index e9efa16b..43996ba3 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -338,7 +338,7 @@ class BasicRangeModel(WirelessModel): options = LinkOptions( bandwidth=self.bw, delay=self.delay, - per=self.loss, + loss=self.loss, jitter=self.jitter, ) self.wlan.linkconfig(netif, options) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 4b8d513b..2b3c7751 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1134,7 +1134,7 @@ class CoreNetworkBase(NodeBase): bandwidth=netif.getparam("bw"), dup=netif.getparam("duplicate"), jitter=netif.getparam("jitter"), - per=netif.getparam("loss"), + loss=netif.getparam("loss"), ) all_links.append(link_data) @@ -1153,7 +1153,7 @@ class CoreNetworkBase(NodeBase): bandwidth=netif.getparam("bw"), dup=netif.getparam("duplicate"), jitter=netif.getparam("jitter"), - per=netif.getparam("loss"), + loss=netif.getparam("loss"), ) netif.swapparams("_params_up") diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 235b43f2..8ac1939e 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -482,7 +482,7 @@ class CoreNetwork(CoreNetworkBase): netem = "netem" delay = options.delay changed = max(changed, netif.setparam("delay", delay)) - loss = options.per + loss = options.loss if loss is not None: loss = float(loss) changed = max(changed, netif.setparam("loss", loss)) @@ -939,7 +939,7 @@ class PtpNet(CoreNetwork): unidirectional=unidirectional, delay=if1.getparam("delay"), bandwidth=if1.getparam("bw"), - per=if1.getparam("loss"), + loss=if1.getparam("loss"), dup=if1.getparam("duplicate"), jitter=if1.getparam("jitter"), interface1_id=if1.node.getifindex(if1), @@ -970,7 +970,7 @@ class PtpNet(CoreNetwork): node2_id=if1.node.id, delay=if2.getparam("delay"), bandwidth=if2.getparam("bw"), - per=if2.getparam("loss"), + loss=if2.getparam("loss"), dup=if2.getparam("duplicate"), jitter=if2.getparam("jitter"), unidirectional=1, diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 973eb77f..820f1cea 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -569,7 +569,7 @@ class CoreXmlWriter: options = etree.Element("options") add_attribute(options, "delay", link_data.delay) add_attribute(options, "bandwidth", link_data.bandwidth) - add_attribute(options, "per", link_data.per) + add_attribute(options, "per", link_data.loss) add_attribute(options, "dup", link_data.dup) add_attribute(options, "jitter", link_data.jitter) add_attribute(options, "mer", link_data.mer) @@ -957,7 +957,7 @@ class CoreXmlReader: link_options.mburst = get_int(options_element, "mburst") link_options.jitter = get_int(options_element, "jitter") link_options.key = get_int(options_element, "key") - link_options.per = get_float(options_element, "per") + link_options.loss = get_float(options_element, "per") link_options.unidirectional = get_int(options_element, "unidirectional") link_options.session = options_element.get("session") link_options.emulation_id = get_int(options_element, "emulation_id") diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index d602f9d3..c9c2d94b 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -719,7 +719,7 @@ message LinkOptions { int32 key = 3; int32 mburst = 4; int32 mer = 5; - float per = 6; + float loss = 6; int64 bandwidth = 7; int32 burst = 8; int64 delay = 9; diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 9f693da1..9c4fd4f2 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -80,7 +80,7 @@ class TestLinks: # given delay = 50 bandwidth = 5000000 - per = 25 + loss = 25 dup = 25 jitter = 10 node_one = session.add_node(CoreNode) @@ -90,13 +90,13 @@ class TestLinks: interface_one = node_one.netif(interface_one_data.id) assert interface_one.getparam("delay") != delay assert interface_one.getparam("bw") != bandwidth - assert interface_one.getparam("loss") != per + assert interface_one.getparam("loss") != loss assert interface_one.getparam("duplicate") != dup assert interface_one.getparam("jitter") != jitter # when link_options = LinkOptions( - delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( node_one.id, @@ -108,7 +108,7 @@ class TestLinks: # then assert interface_one.getparam("delay") == delay assert interface_one.getparam("bw") == bandwidth - assert interface_one.getparam("loss") == per + assert interface_one.getparam("loss") == loss assert interface_one.getparam("duplicate") == dup assert interface_one.getparam("jitter") == jitter @@ -116,7 +116,7 @@ class TestLinks: # given delay = 50 bandwidth = 5000000 - per = 25 + loss = 25 dup = 25 jitter = 10 node_one = session.add_node(SwitchNode) @@ -126,13 +126,13 @@ class TestLinks: interface_two = node_two.netif(interface_two_data.id) assert interface_two.getparam("delay") != delay assert interface_two.getparam("bw") != bandwidth - assert interface_two.getparam("loss") != per + assert interface_two.getparam("loss") != loss assert interface_two.getparam("duplicate") != dup assert interface_two.getparam("jitter") != jitter # when link_options = LinkOptions( - delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( node_one.id, @@ -144,7 +144,7 @@ class TestLinks: # then assert interface_two.getparam("delay") == delay assert interface_two.getparam("bw") == bandwidth - assert interface_two.getparam("loss") == per + assert interface_two.getparam("loss") == loss assert interface_two.getparam("duplicate") == dup assert interface_two.getparam("jitter") == jitter @@ -152,7 +152,7 @@ class TestLinks: # given delay = 50 bandwidth = 5000000 - per = 25 + loss = 25 dup = 25 jitter = 10 node_one = session.add_node(CoreNode) @@ -166,18 +166,18 @@ class TestLinks: interface_two = node_two.netif(interface_two_data.id) assert interface_one.getparam("delay") != delay assert interface_one.getparam("bw") != bandwidth - assert interface_one.getparam("loss") != per + assert interface_one.getparam("loss") != loss assert interface_one.getparam("duplicate") != dup assert interface_one.getparam("jitter") != jitter assert interface_two.getparam("delay") != delay assert interface_two.getparam("bw") != bandwidth - assert interface_two.getparam("loss") != per + assert interface_two.getparam("loss") != loss assert interface_two.getparam("duplicate") != dup assert interface_two.getparam("jitter") != jitter # when link_options = LinkOptions( - delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter + delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( node_one.id, @@ -190,12 +190,12 @@ class TestLinks: # then assert interface_one.getparam("delay") == delay assert interface_one.getparam("bw") == bandwidth - assert interface_one.getparam("loss") == per + assert interface_one.getparam("loss") == loss assert interface_one.getparam("duplicate") == dup assert interface_one.getparam("jitter") == jitter assert interface_two.getparam("delay") == delay assert interface_two.getparam("bw") == bandwidth - assert interface_two.getparam("loss") == per + assert interface_two.getparam("loss") == loss assert interface_two.getparam("duplicate") == dup assert interface_two.getparam("jitter") == jitter diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index c40a9ef3..0345daed 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -304,7 +304,7 @@ class TestXml: # create link link_options = LinkOptions() - link_options.per = 10.5 + link_options.loss = 10.5 link_options.bandwidth = 50000 link_options.jitter = 10 link_options.delay = 30 @@ -347,7 +347,7 @@ class TestXml: node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert link_options.per == link.per + assert link_options.loss == link.loss assert link_options.bandwidth == link.bandwidth assert link_options.jitter == link.jitter assert link_options.delay == link.delay @@ -371,7 +371,7 @@ class TestXml: # create link link_options = LinkOptions() - link_options.per = 10.5 + link_options.loss = 10.5 link_options.bandwidth = 50000 link_options.jitter = 10 link_options.delay = 30 @@ -416,7 +416,7 @@ class TestXml: node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert link_options.per == link.per + assert link_options.loss == link.loss assert link_options.bandwidth == link.bandwidth assert link_options.jitter == link.jitter assert link_options.delay == link.delay @@ -443,7 +443,7 @@ class TestXml: link_options_one.unidirectional = 1 link_options_one.bandwidth = 5000 link_options_one.delay = 10 - link_options_one.per = 10.5 + link_options_one.loss = 10.5 link_options_one.dup = 5 link_options_one.jitter = 5 session.add_link( @@ -453,7 +453,7 @@ class TestXml: link_options_two.unidirectional = 1 link_options_two.bandwidth = 10000 link_options_two.delay = 20 - link_options_two.per = 10 + link_options_two.loss = 10 link_options_two.dup = 10 link_options_two.jitter = 10 session.update_link( @@ -504,11 +504,11 @@ class TestXml: link_two = links[1] assert link_options_one.bandwidth == link_one.bandwidth assert link_options_one.delay == link_one.delay - assert link_options_one.per == link_one.per + assert link_options_one.loss == link_one.loss assert link_options_one.dup == link_one.dup assert link_options_one.jitter == link_one.jitter assert link_options_two.bandwidth == link_two.bandwidth assert link_options_two.delay == link_two.delay - assert link_options_two.per == link_two.per + assert link_options_two.loss == link_two.loss assert link_options_two.dup == link_two.dup assert link_options_two.jitter == link_two.jitter From 876699e8efeb03daee471b0662d7d3eba8183917 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 16:52:41 -0700 Subject: [PATCH 042/146] variable/grpc cleanup to rename everything using spelt out numbers instead of actual numbers --- daemon/core/api/grpc/client.py | 94 +++---- daemon/core/api/grpc/grpcutils.py | 38 +-- daemon/core/api/grpc/server.py | 116 ++++----- daemon/core/api/tlv/corehandlers.py | 54 ++-- daemon/core/emane/linkmonitor.py | 20 +- daemon/core/emulator/distributed.py | 12 +- daemon/core/emulator/session.py | 154 +++++------ daemon/core/gui/coreclient.py | 58 ++--- daemon/core/gui/dialogs/linkconfig.py | 48 ++-- daemon/core/gui/graph/edges.py | 14 +- daemon/core/gui/graph/graph.py | 78 +++--- daemon/core/gui/interface.py | 24 +- daemon/core/plugins/sdt.py | 56 ++-- daemon/core/xml/corexml.py | 56 ++-- daemon/examples/configservices/testing.py | 14 +- daemon/examples/docker/docker2core.py | 10 +- daemon/examples/docker/docker2docker.py | 10 +- daemon/examples/docker/switch.py | 12 +- daemon/examples/grpc/distributed_switch.py | 12 +- daemon/examples/grpc/emane80211.py | 8 +- daemon/examples/grpc/switch.py | 8 +- daemon/examples/grpc/wlan.py | 8 +- daemon/examples/lxd/lxd2core.py | 10 +- daemon/examples/lxd/lxd2lxd.py | 10 +- daemon/examples/lxd/switch.py | 18 +- daemon/examples/python/distributed_emane.py | 12 +- daemon/examples/python/distributed_lxd.py | 10 +- daemon/examples/python/distributed_ptp.py | 10 +- daemon/examples/python/distributed_switch.py | 12 +- daemon/examples/python/emane80211.py | 2 +- daemon/examples/python/switch.py | 2 +- daemon/examples/python/switch_inject.py | 2 +- daemon/examples/python/wlan.py | 2 +- daemon/proto/core/api/grpc/core.proto | 28 +- daemon/proto/core/api/grpc/emane.proto | 16 +- daemon/proto/core/api/grpc/wlan.proto | 4 +- daemon/tests/emane/test_emane.py | 37 +-- daemon/tests/test_conf.py | 16 +- daemon/tests/test_core.py | 64 ++--- daemon/tests/test_grpc.py | 54 ++-- daemon/tests/test_gui.py | 159 ++++++------ daemon/tests/test_links.py | 246 +++++++++--------- daemon/tests/test_services.py | 20 +- daemon/tests/test_xml.py | 258 +++++++++---------- docs/scripting.md | 2 +- 45 files changed, 932 insertions(+), 966 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 1bc88069..3a16d4fd 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -609,30 +609,30 @@ class CoreGrpcClient: def add_link( self, session_id: int, - node_one_id: int, - node_two_id: int, - interface_one: core_pb2.Interface = None, - interface_two: core_pb2.Interface = None, + node1_id: int, + node2_id: int, + interface1: core_pb2.Interface = None, + interface2: core_pb2.Interface = None, options: core_pb2.LinkOptions = None, ) -> core_pb2.AddLinkResponse: """ Add a link between nodes. :param session_id: session id - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one: node one interface data - :param interface_two: node two interface data + :param node1_id: node one id + :param node2_id: node two id + :param interface1: node one interface data + :param interface2: node two interface data :param options: options for link (jitter, bandwidth, etc) :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ link = core_pb2.Link( - node_one_id=node_one_id, - node_two_id=node_two_id, + node1_id=node1_id, + node2_id=node2_id, type=core_pb2.LinkType.WIRED, - interface_one=interface_one, - interface_two=interface_two, + interface1=interface1, + interface2=interface2, options=options, ) request = core_pb2.AddLinkRequest(session_id=session_id, link=link) @@ -641,59 +641,59 @@ class CoreGrpcClient: def edit_link( self, session_id: int, - node_one_id: int, - node_two_id: int, + node1_id: int, + node2_id: int, options: core_pb2.LinkOptions, - interface_one_id: int = None, - interface_two_id: int = None, + interface1_id: int = None, + interface2_id: int = None, ) -> core_pb2.EditLinkResponse: """ Edit a link between nodes. :param session_id: session id - :param node_one_id: node one id - :param node_two_id: node two id + :param node1_id: node one id + :param node2_id: node two id :param options: options for link (jitter, bandwidth, etc) - :param interface_one_id: node one interface id - :param interface_two_id: node two interface id + :param interface1_id: node one interface id + :param interface2_id: node two interface id :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ request = core_pb2.EditLinkRequest( session_id=session_id, - node_one_id=node_one_id, - node_two_id=node_two_id, + node1_id=node1_id, + node2_id=node2_id, options=options, - interface_one_id=interface_one_id, - interface_two_id=interface_two_id, + interface1_id=interface1_id, + interface2_id=interface2_id, ) return self.stub.EditLink(request) def delete_link( self, session_id: int, - node_one_id: int, - node_two_id: int, - interface_one_id: int = None, - interface_two_id: int = None, + node1_id: int, + node2_id: int, + interface1_id: int = None, + interface2_id: int = None, ) -> core_pb2.DeleteLinkResponse: """ Delete a link between nodes. :param session_id: session id - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one_id: node one interface id - :param interface_two_id: node two interface id + :param node1_id: node one id + :param node2_id: node two id + :param interface1_id: node one interface id + :param interface2_id: node two interface id :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.DeleteLinkRequest( session_id=session_id, - node_one_id=node_one_id, - node_two_id=node_two_id, - interface_one_id=interface_one_id, - interface_two_id=interface_two_id, + node1_id=node1_id, + node2_id=node2_id, + interface1_id=interface1_id, + interface2_id=interface2_id, ) return self.stub.DeleteLink(request) @@ -1111,20 +1111,20 @@ class CoreGrpcClient: return self.stub.OpenXml(request) def emane_link( - self, session_id: int, nem_one: int, nem_two: int, linked: bool + self, session_id: int, nem1: int, nem2: int, linked: bool ) -> EmaneLinkResponse: """ Helps broadcast wireless link/unlink between EMANE nodes. :param session_id: session to emane link - :param nem_one: first nem for emane link - :param nem_two: second nem for emane link + :param nem1: first nem for emane link + :param nem2: second nem for emane link :param linked: True to link, False to unlink :return: get emane link response :raises grpc.RpcError: when session or nodes related to nems do not exist """ request = EmaneLinkRequest( - session_id=session_id, nem_one=nem_one, nem_two=nem_two, linked=linked + session_id=session_id, nem1=nem1, nem2=nem2, linked=linked ) return self.stub.EmaneLink(request) @@ -1243,24 +1243,24 @@ class CoreGrpcClient: return self.stub.ExecuteScript(request) def wlan_link( - self, session_id: int, wlan: int, node_one: int, node_two: int, linked: bool + self, session_id: int, wlan_id: int, node1_id: int, node2_id: int, linked: bool ) -> WlanLinkResponse: """ Links/unlinks nodes on the same WLAN. :param session_id: session id containing wlan and nodes - :param wlan: wlan nodes must belong to - :param node_one: first node of pair to link/unlink - :param node_two: second node of pair to link/unlin + :param wlan_id: wlan nodes must belong to + :param node1_id: first node of pair to link/unlink + :param node2_id: second node of pair to link/unlin :param linked: True to link, False to unlink :return: wlan link response :raises grpc.RpcError: when session or one of the nodes do not exist """ request = WlanLinkRequest( session_id=session_id, - wlan=wlan, - node_one=node_one, - node_two=node_two, + wlan=wlan_id, + node1_id=node1_id, + node2_id=node2_id, linked=linked, ) return self.stub.WlanLink(request) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 73d19a2a..539face1 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -86,8 +86,8 @@ def add_link_data( :param link_proto: link proto :return: link interfaces and options """ - interface_one = link_interface(link_proto.interface_one) - interface_two = link_interface(link_proto.interface_two) + interface1_data = link_interface(link_proto.interface1) + interface2_data = link_interface(link_proto.interface2) link_type = LinkTypes(link_proto.type) options = LinkOptions(type=link_type) options_data = link_proto.options @@ -103,7 +103,7 @@ def add_link_data( options.unidirectional = options_data.unidirectional options.key = options_data.key options.opaque = options_data.opaque - return interface_one, interface_two, options + return interface1_data, interface2_data, options def create_nodes( @@ -141,10 +141,10 @@ def create_links( """ funcs = [] for link_proto in link_protos: - node_one_id = link_proto.node_one_id - node_two_id = link_proto.node_two_id - interface_one, interface_two, options = add_link_data(link_proto) - args = (node_one_id, node_two_id, interface_one, interface_two, options) + node1_id = link_proto.node1_id + node2_id = link_proto.node2_id + interface1, interface2, options = add_link_data(link_proto) + args = (node1_id, node2_id, interface1, interface2, options) funcs.append((session.add_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -165,10 +165,10 @@ def edit_links( """ funcs = [] for link_proto in link_protos: - node_one_id = link_proto.node_one_id - node_two_id = link_proto.node_two_id - interface_one, interface_two, options = add_link_data(link_proto) - args = (node_one_id, node_two_id, interface_one.id, interface_two.id, options) + node1_id = link_proto.node1_id + node2_id = link_proto.node2_id + interface1, interface2, options = add_link_data(link_proto) + args = (node1_id, node2_id, interface1.id, interface2.id, options) funcs.append((session.update_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -315,9 +315,9 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: :param link_data: link to convert :return: core protobuf Link """ - interface_one = None + interface1 = None if link_data.interface1_id is not None: - interface_one = core_pb2.Interface( + interface1 = core_pb2.Interface( id=link_data.interface1_id, name=link_data.interface1_name, mac=convert_value(link_data.interface1_mac), @@ -326,9 +326,9 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: ip6=convert_value(link_data.interface1_ip6), ip6mask=link_data.interface1_ip6_mask, ) - interface_two = None + interface2 = None if link_data.interface2_id is not None: - interface_two = core_pb2.Interface( + interface2 = core_pb2.Interface( id=link_data.interface2_id, name=link_data.interface2_name, mac=convert_value(link_data.interface2_mac), @@ -352,10 +352,10 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: ) return core_pb2.Link( type=link_data.link_type.value, - node_one_id=link_data.node1_id, - node_two_id=link_data.node2_id, - interface_one=interface_one, - interface_two=interface_two, + node1_id=link_data.node1_id, + node2_id=link_data.node2_id, + interface1=interface1, + interface2=interface2, options=options, network_id=link_data.network_id, label=link_data.label, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index adddff14..a0ddf806 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -845,27 +845,23 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :return: add-link response """ logging.debug("add link: %s", request) - # validate session and nodes session = self.get_session(request.session_id, context) - self.get_node(session, request.link.node_one_id, context, NodeBase) - self.get_node(session, request.link.node_two_id, context, NodeBase) - - node_one_id = request.link.node_one_id - node_two_id = request.link.node_two_id - interface_one, interface_two, options = grpcutils.add_link_data(request.link) - node_one_interface, node_two_interface = session.add_link( - node_one_id, node_two_id, interface_one, interface_two, options=options + node1_id = request.link.node1_id + node2_id = request.link.node2_id + self.get_node(session, node1_id, context, NodeBase) + self.get_node(session, node2_id, context, NodeBase) + interface1, interface2, options = grpcutils.add_link_data(request.link) + node1_interface, node2_interface = session.add_link( + node1_id, node2_id, interface1, interface2, options=options ) - interface_one_proto = None - interface_two_proto = None - if node_one_interface: - interface_one_proto = grpcutils.interface_to_proto(node_one_interface) - if node_two_interface: - interface_two_proto = grpcutils.interface_to_proto(node_two_interface) + interface1_proto = None + interface2_proto = None + if node1_interface: + interface1_proto = grpcutils.interface_to_proto(node1_interface) + if node2_interface: + interface2_proto = grpcutils.interface_to_proto(node2_interface) return core_pb2.AddLinkResponse( - result=True, - interface_one=interface_one_proto, - interface_two=interface_two_proto, + result=True, interface1=interface1_proto, interface2=interface2_proto ) def EditLink( @@ -880,10 +876,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("edit link: %s", request) session = self.get_session(request.session_id, context) - node_one_id = request.node_one_id - node_two_id = request.node_two_id - interface_one_id = request.interface_one_id - interface_two_id = request.interface_two_id + node1_id = request.node1_id + node2_id = request.node2_id + interface1_id = request.interface1_id + interface2_id = request.interface2_id options_data = request.options link_options = LinkOptions() link_options.delay = options_data.delay @@ -898,7 +894,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): link_options.key = options_data.key link_options.opaque = options_data.opaque session.update_link( - node_one_id, node_two_id, interface_one_id, interface_two_id, link_options + node1_id, node2_id, interface1_id, interface2_id, link_options ) return core_pb2.EditLinkResponse(result=True) @@ -914,13 +910,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("delete link: %s", request) session = self.get_session(request.session_id, context) - node_one_id = request.node_one_id - node_two_id = request.node_two_id - interface_one_id = request.interface_one_id - interface_two_id = request.interface_two_id - session.delete_link( - node_one_id, node_two_id, interface_one_id, interface_two_id - ) + node1_id = request.node1_id + node2_id = request.node2_id + interface1_id = request.interface1_id + interface2_id = request.interface2_id + session.delete_link(node1_id, node2_id, interface1_id, interface2_id) return core_pb2.DeleteLinkResponse(result=True) def GetHooks( @@ -1519,30 +1513,30 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("emane link: %s", request) session = self.get_session(request.session_id, context) - nem_one = request.nem_one - emane_one, netif = session.emane.nemlookup(nem_one) - if not emane_one or not netif: - context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem_one} not found") - node_one = netif.node + nem1 = request.nem1 + emane1, netif = session.emane.nemlookup(nem1) + if not emane1 or not netif: + context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found") + node1 = netif.node - nem_two = request.nem_two - emane_two, netif = session.emane.nemlookup(nem_two) - if not emane_two or not netif: - context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem_two} not found") - node_two = netif.node + nem2 = request.nem2 + emane2, netif = session.emane.nemlookup(nem2) + if not emane2 or not netif: + context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found") + node2 = netif.node - if emane_one.id == emane_two.id: + if emane1.id == emane2.id: if request.linked: flag = MessageFlags.ADD else: flag = MessageFlags.DELETE - color = session.get_link_color(emane_one.id) + color = session.get_link_color(emane1.id) link = LinkData( message_type=flag, link_type=LinkTypes.WIRELESS, - node1_id=node_one.id, - node2_id=node_two.id, - network_id=emane_one.id, + node1_id=node1.id, + node2_id=node2.id, + network_id=emane1.id, color=color, ) session.broadcast_link(link) @@ -1739,21 +1733,23 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): grpc.StatusCode.NOT_FOUND, f"wlan node {request.wlan} does not using BasicRangeModel", ) - n1 = self.get_node(session, request.node_one, context, CoreNode) - n2 = self.get_node(session, request.node_two, context, CoreNode) - n1_netif, n2_netif = None, None - for net, netif1, netif2 in n1.commonnets(n2): + node1 = self.get_node(session, request.node1_id, context, CoreNode) + node2 = self.get_node(session, request.node2_id, context, CoreNode) + node1_interface, node2_interface = None, None + for net, interface1, interface2 in node1.commonnets(node2): if net == wlan: - n1_netif = netif1 - n2_netif = netif2 + node1_interface = interface1 + node2_interface = interface2 break result = False - if n1_netif and n2_netif: + if node1_interface and node2_interface: if request.linked: - wlan.link(n1_netif, n2_netif) + wlan.link(node1_interface, node2_interface) else: - wlan.unlink(n1_netif, n2_netif) - wlan.model.sendlinkmsg(n1_netif, n2_netif, unlink=not request.linked) + wlan.unlink(node1_interface, node2_interface) + wlan.model.sendlinkmsg( + node1_interface, node2_interface, unlink=not request.linked + ) result = True return WlanLinkResponse(result=result) @@ -1764,9 +1760,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): ) -> EmanePathlossesResponse: for request in request_iterator: session = self.get_session(request.session_id, context) - n1 = self.get_node(session, request.node_one, context, CoreNode) - nem1 = grpcutils.get_nem_id(n1, request.interface_one_id, context) - n2 = self.get_node(session, request.node_two, context, CoreNode) - nem2 = grpcutils.get_nem_id(n2, request.interface_two_id, context) - session.emane.publish_pathloss(nem1, nem2, request.rx_one, request.rx_two) + node1 = self.get_node(session, request.node1_id, context, CoreNode) + nem1 = grpcutils.get_nem_id(node1, request.interface1_id, context) + node2 = self.get_node(session, request.node2_id, context, CoreNode) + nem2 = grpcutils.get_nem_id(node2, request.interface2_id, context) + session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2) return EmanePathlossesResponse() diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 3adaed63..e7a67b3e 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -745,10 +745,9 @@ class CoreHandler(socketserver.BaseRequestHandler): :param core.api.tlv.coreapi.CoreLinkMessage message: link message to handle :return: link message replies """ - node_one_id = message.get_tlv(LinkTlvs.N1_NUMBER.value) - node_two_id = message.get_tlv(LinkTlvs.N2_NUMBER.value) - - interface_one = InterfaceData( + node1_id = message.get_tlv(LinkTlvs.N1_NUMBER.value) + node2_id = message.get_tlv(LinkTlvs.N2_NUMBER.value) + interface1_data = InterfaceData( id=message.get_tlv(LinkTlvs.INTERFACE1_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE1_NAME.value), mac=message.get_tlv(LinkTlvs.INTERFACE1_MAC.value), @@ -757,7 +756,7 @@ class CoreHandler(socketserver.BaseRequestHandler): ip6=message.get_tlv(LinkTlvs.INTERFACE1_IP6.value), ip6_mask=message.get_tlv(LinkTlvs.INTERFACE1_IP6_MASK.value), ) - interface_two = InterfaceData( + interface2_data = InterfaceData( id=message.get_tlv(LinkTlvs.INTERFACE2_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE2_NAME.value), mac=message.get_tlv(LinkTlvs.INTERFACE2_MAC.value), @@ -766,45 +765,38 @@ class CoreHandler(socketserver.BaseRequestHandler): ip6=message.get_tlv(LinkTlvs.INTERFACE2_IP6.value), ip6_mask=message.get_tlv(LinkTlvs.INTERFACE2_IP6_MASK.value), ) - link_type = LinkTypes.WIRED link_type_value = message.get_tlv(LinkTlvs.TYPE.value) if link_type_value is not None: link_type = LinkTypes(link_type_value) - - link_options = LinkOptions(type=link_type) - link_options.delay = message.get_tlv(LinkTlvs.DELAY.value) - link_options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) - link_options.session = message.get_tlv(LinkTlvs.SESSION.value) - link_options.per = message.get_tlv(LinkTlvs.PER.value) - link_options.dup = message.get_tlv(LinkTlvs.DUP.value) - link_options.jitter = message.get_tlv(LinkTlvs.JITTER.value) - link_options.mer = message.get_tlv(LinkTlvs.MER.value) - link_options.burst = message.get_tlv(LinkTlvs.BURST.value) - link_options.mburst = message.get_tlv(LinkTlvs.MBURST.value) - link_options.gui_attributes = message.get_tlv(LinkTlvs.GUI_ATTRIBUTES.value) - link_options.unidirectional = message.get_tlv(LinkTlvs.UNIDIRECTIONAL.value) - link_options.emulation_id = message.get_tlv(LinkTlvs.EMULATION_ID.value) - link_options.network_id = message.get_tlv(LinkTlvs.NETWORK_ID.value) - link_options.key = message.get_tlv(LinkTlvs.KEY.value) - link_options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) + options = LinkOptions(type=link_type) + options.delay = message.get_tlv(LinkTlvs.DELAY.value) + options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) + options.session = message.get_tlv(LinkTlvs.SESSION.value) + options.per = message.get_tlv(LinkTlvs.PER.value) + options.dup = message.get_tlv(LinkTlvs.DUP.value) + options.jitter = message.get_tlv(LinkTlvs.JITTER.value) + options.mer = message.get_tlv(LinkTlvs.MER.value) + options.burst = message.get_tlv(LinkTlvs.BURST.value) + options.mburst = message.get_tlv(LinkTlvs.MBURST.value) + options.gui_attributes = message.get_tlv(LinkTlvs.GUI_ATTRIBUTES.value) + options.unidirectional = message.get_tlv(LinkTlvs.UNIDIRECTIONAL.value) + options.emulation_id = message.get_tlv(LinkTlvs.EMULATION_ID.value) + options.network_id = message.get_tlv(LinkTlvs.NETWORK_ID.value) + options.key = message.get_tlv(LinkTlvs.KEY.value) + options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) if message.flags & MessageFlags.ADD.value: self.session.add_link( - node_one_id, node_two_id, interface_one, interface_two, link_options + node1_id, node2_id, interface1_data, interface2_data, options ) elif message.flags & MessageFlags.DELETE.value: self.session.delete_link( - node_one_id, node_two_id, interface_one.id, interface_two.id + node1_id, node2_id, interface1_data.id, interface2_data.id ) else: self.session.update_link( - node_one_id, - node_two_id, - interface_one.id, - interface_two.id, - link_options, + node1_id, node2_id, interface1_data.id, interface2_data.id, options ) - return () def handle_execute_message(self, message): diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index b9fd9a2a..ca9f4493 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -269,11 +269,11 @@ class EmaneLinkMonitor: self.scheduler.enter(self.link_interval, 0, self.check_links) def get_complete_id(self, link_id: Tuple[int, int]) -> Tuple[int, int]: - value_one, value_two = link_id - if value_one < value_two: - return value_one, value_two + value1, value2 = link_id + if value1 < value2: + return value1, value2 else: - return value_two, value_one + return value2, value1 def is_complete_link(self, link_id: Tuple[int, int]) -> bool: reverse_id = link_id[1], link_id[0] @@ -287,8 +287,8 @@ class EmaneLinkMonitor: return f"{source_link.sinr:.1f} / {dest_link.sinr:.1f}" def send_link(self, message_type: MessageFlags, link_id: Tuple[int, int]) -> None: - nem_one, nem_two = link_id - link = self.emane_manager.get_nem_link(nem_one, nem_two, message_type) + nem1, nem2 = link_id + link = self.emane_manager.get_nem_link(nem1, nem2, message_type) if link: label = self.get_link_label(link_id) link.label = label @@ -298,16 +298,16 @@ class EmaneLinkMonitor: self, message_type: MessageFlags, label: str, - node_one: int, - node_two: int, + node1: int, + node2: int, emane_id: int, ) -> None: color = self.emane_manager.session.get_link_color(emane_id) link_data = LinkData( message_type=message_type, label=label, - node1_id=node_one, - node2_id=node_two, + node1_id=node1, + node2_id=node2, network_id=emane_id, link_type=LinkTypes.WIRELESS, color=color, diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index 3753e1c2..75081447 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -224,18 +224,20 @@ class DistributedController: self.tunnels[key] = tunnel return tunnel - def tunnel_key(self, n1_id: int, n2_id: int) -> int: + def tunnel_key(self, node1_id: int, node2_id: int) -> int: """ Compute a 32-bit key used to uniquely identify a GRE tunnel. The hash(n1num), hash(n2num) values are used, so node numbers may be None or string values (used for e.g. "ctrlnet"). - :param n1_id: node one id - :param n2_id: node two id + :param node1_id: node one id + :param node2_id: node two id :return: tunnel key for the node pair """ - logging.debug("creating tunnel key for: %s, %s", n1_id, n2_id) + logging.debug("creating tunnel key for: %s, %s", node1_id, node2_id) key = ( - (self.session.id << 16) ^ utils.hashkey(n1_id) ^ (utils.hashkey(n2_id) << 8) + (self.session.id << 16) + ^ utils.hashkey(node1_id) + ^ (utils.hashkey(node2_id) << 8) ) return key & 0xFFFFFFFF diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 854d5cc8..0a90b943 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -194,13 +194,13 @@ class Session: return node_type def _link_wireless( - self, node_one: CoreNodeBase, node_two: CoreNodeBase, connect: bool + self, node1: CoreNodeBase, node2: CoreNodeBase, connect: bool ) -> None: """ Objects to deal with when connecting/disconnecting wireless links. - :param node_one: node one for wireless link - :param node_two: node two for wireless link + :param node1: node one for wireless link + :param node2: node two for wireless link :param connect: link interfaces if True, unlink otherwise :return: nothing :raises core.CoreError: when objects to link is less than 2, or no common @@ -208,14 +208,14 @@ class Session: """ logging.info( "handling wireless linking node1(%s) node2(%s): %s", - node_one.name, - node_two.name, + node1.name, + node2.name, connect, ) - common_networks = node_one.commonnets(node_one) + common_networks = node1.commonnets(node1) if not common_networks: raise CoreError("no common network found for wireless link/unlink") - for common_network, interface_one, interface_two in common_networks: + for common_network, interface1, interface2 in common_networks: if not isinstance(common_network, (WlanNode, EmaneNet)): logging.info( "skipping common network that is not wireless/emane: %s", @@ -223,26 +223,26 @@ class Session: ) continue if connect: - common_network.link(interface_one, interface_two) + common_network.link(interface1, interface2) else: - common_network.unlink(interface_one, interface_two) + common_network.unlink(interface1, interface2) def add_link( self, - node_one_id: int, - node_two_id: int, - interface_one: InterfaceData = None, - interface_two: InterfaceData = None, + node1_id: int, + node2_id: int, + interface1_data: InterfaceData = None, + interface2_data: InterfaceData = None, options: LinkOptions = None, ) -> Tuple[CoreInterface, CoreInterface]: """ Add a link between nodes. - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one: node one interface + :param node1_id: node one id + :param node2_id: node two id + :param interface1_data: node one interface data, defaults to none - :param interface_two: node two interface + :param interface2_data: node two interface data, defaults to none :param options: data for creating link, defaults to no options @@ -250,10 +250,10 @@ class Session: """ if not options: options = LinkOptions() - node1 = self.get_node(node_one_id, NodeBase) - node2 = self.get_node(node_two_id, NodeBase) - node1_interface = None - node2_interface = None + node1 = self.get_node(node1_id, NodeBase) + node2 = self.get_node(node2_id, NodeBase) + interface1 = None + interface2 = None # wireless link if options.type == LinkTypes.WIRELESS: @@ -270,22 +270,22 @@ class Session: logging.info("linking ptp: %s - %s", node1.name, node2.name) start = self.state.should_start() ptp = self.create_node(PtpNet, start=start) - node1_interface = node1.newnetif(ptp, interface_one) - node2_interface = node2.newnetif(ptp, interface_two) - ptp.linkconfig(node1_interface, options) + interface1 = node1.newnetif(ptp, interface1_data) + interface2 = node2.newnetif(ptp, interface2_data) + ptp.linkconfig(interface1, options) if not options.unidirectional: - ptp.linkconfig(node2_interface, options) + ptp.linkconfig(interface2, options) # link node to net elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - node1_interface = node1.newnetif(node2, interface_one) + interface1 = node1.newnetif(node2, interface1_data) if not isinstance(node2, (EmaneNet, WlanNode)): - node2.linkconfig(node1_interface, options) + node2.linkconfig(interface1, options) # link net to node elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - node2_interface = node2.newnetif(node1, interface_two) + interface2 = node2.newnetif(node1, interface2_data) wireless_net = isinstance(node1, (EmaneNet, WlanNode)) if not options.unidirectional and not wireless_net: - node1.linkconfig(node2_interface, options) + node1.linkconfig(interface2, options) # network to network elif isinstance(node1, CoreNetworkBase) and isinstance( node2, CoreNetworkBase @@ -293,12 +293,12 @@ class Session: logging.info( "linking network to network: %s - %s", node1.name, node2.name ) - node1_interface = node1.linknet(node2) - node1.linkconfig(node1_interface, options) + interface1 = node1.linknet(node2) + node1.linkconfig(interface1, options) if not options.unidirectional: - node1_interface.swapparams("_params_up") - node2.linkconfig(node1_interface, options) - node1_interface.swapparams("_params_up") + interface1.swapparams("_params_up") + node2.linkconfig(interface1, options) + interface1.swapparams("_params_up") else: raise CoreError( f"cannot link node1({type(node1)}) node2({type(node2)})" @@ -308,41 +308,41 @@ class Session: key = options.key if isinstance(node1, TunnelNode): logging.info("setting tunnel key for: %s", node1.name) - node1.setkey(key, interface_one) + node1.setkey(key, interface1_data) if isinstance(node2, TunnelNode): logging.info("setting tunnel key for: %s", node2.name) - node2.setkey(key, interface_two) - self.sdt.add_link(node_one_id, node_two_id) - return node1_interface, node2_interface + node2.setkey(key, interface2_data) + self.sdt.add_link(node1_id, node2_id) + return interface1, interface2 def delete_link( self, - node_one_id: int, - node_two_id: int, - interface_one_id: int = None, - interface_two_id: int = None, + node1_id: int, + node2_id: int, + interface1_id: int = None, + interface2_id: int = None, link_type: LinkTypes = LinkTypes.WIRED, ) -> None: """ Delete a link between nodes. - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one_id: interface id for node one - :param interface_two_id: interface id for node two + :param node1_id: node one id + :param node2_id: node two id + :param interface1_id: interface id for node one + :param interface2_id: interface id for node two :param link_type: link type to delete :return: nothing :raises core.CoreError: when no common network is found for link being deleted """ - node1 = self.get_node(node_one_id, NodeBase) - node2 = self.get_node(node_two_id, NodeBase) + node1 = self.get_node(node1_id, NodeBase) + node2 = self.get_node(node2_id, NodeBase) logging.info( "deleting link(%s) node(%s):interface(%s) node(%s):interface(%s)", link_type.name, node1.name, - interface_one_id, + interface1_id, node2.name, - interface_two_id, + interface2_id, ) # wireless link @@ -357,15 +357,15 @@ class Session: # wired link else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): - interface1 = node1.netif(interface_one_id) - interface2 = node2.netif(interface_two_id) + interface1 = node1.netif(interface1_id) + interface2 = node2.netif(interface2_id) if not interface1: raise CoreError( - f"node({node1.name}) missing interface({interface_one_id})" + f"node({node1.name}) missing interface({interface1_id})" ) if not interface2: raise CoreError( - f"node({node2.name}) missing interface({interface_two_id})" + f"node({node2.name}) missing interface({interface2_id})" ) if interface1.net != interface2.net: raise CoreError( @@ -373,30 +373,30 @@ class Session: "not connected to same net" ) ptp = interface1.net - node1.delnetif(interface_one_id) - node2.delnetif(interface_two_id) + node1.delnetif(interface1_id) + node2.delnetif(interface2_id) self.delete_node(ptp.id) elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - node1.delnetif(interface_one_id) + node1.delnetif(interface1_id) elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - node2.delnetif(interface_two_id) - self.sdt.delete_link(node_one_id, node_two_id) + node2.delnetif(interface2_id) + self.sdt.delete_link(node1_id, node2_id) def update_link( self, - node_one_id: int, - node_two_id: int, - interface_one_id: int = None, - interface_two_id: int = None, + node1_id: int, + node2_id: int, + interface1_id: int = None, + interface2_id: int = None, options: LinkOptions = None, ) -> None: """ Update link information between nodes. - :param node_one_id: node one id - :param node_two_id: node two id - :param interface_one_id: interface id for node one - :param interface_two_id: interface id for node two + :param node1_id: node one id + :param node2_id: node two id + :param interface1_id: interface id for node one + :param interface2_id: interface id for node two :param options: data to update link with :return: nothing :raises core.CoreError: when updating a wireless type link, when there is a @@ -404,15 +404,15 @@ class Session: """ if not options: options = LinkOptions() - node1 = self.get_node(node_one_id, NodeBase) - node2 = self.get_node(node_two_id, NodeBase) + node1 = self.get_node(node1_id, NodeBase) + node2 = self.get_node(node2_id, NodeBase) logging.info( "update link(%s) node(%s):interface(%s) node(%s):interface(%s)", options.type.name, node1.name, - interface_one_id, + interface1_id, node2.name, - interface_two_id, + interface2_id, ) # wireless link @@ -420,15 +420,15 @@ class Session: raise CoreError("cannot update wireless link") else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): - interface1 = node1.netif(interface_one_id) - interface2 = node2.netif(interface_two_id) + interface1 = node1.netif(interface1_id) + interface2 = node2.netif(interface2_id) if not interface1: raise CoreError( - f"node({node1.name}) missing interface({interface_one_id})" + f"node({node1.name}) missing interface({interface1_id})" ) if not interface2: raise CoreError( - f"node({node2.name}) missing interface({interface_two_id})" + f"node({node2.name}) missing interface({interface2_id})" ) if interface1.net != interface2.net: raise CoreError( @@ -440,10 +440,10 @@ class Session: if not options.unidirectional: ptp.linkconfig(interface2, options, interface1) elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - interface = node1.netif(interface_one_id) + interface = node1.netif(interface1_id) node2.linkconfig(interface, options) elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - interface = node2.netif(interface_two_id) + interface = node2.netif(interface2_id) node1.linkconfig(interface, options) elif isinstance(node1, CoreNetworkBase) and isinstance( node2, CoreNetworkBase diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 2b565e7f..5c1c52a0 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -164,25 +164,19 @@ class CoreClient: def handle_link_event(self, event: core_pb2.LinkEvent): logging.debug("Link event: %s", event) - node_one_id = event.link.node_one_id - node_two_id = event.link.node_two_id - if node_one_id == node_two_id: + node1_id = event.link.node1_id + node2_id = event.link.node2_id + if node1_id == node2_id: logging.warning("ignoring links with loops: %s", event) return - canvas_node_one = self.canvas_nodes[node_one_id] - canvas_node_two = self.canvas_nodes[node_two_id] + canvas_node1 = self.canvas_nodes[node1_id] + canvas_node2 = self.canvas_nodes[node2_id] if event.message_type == core_pb2.MessageType.ADD: - self.app.canvas.add_wireless_edge( - canvas_node_one, canvas_node_two, event.link - ) + self.app.canvas.add_wireless_edge(canvas_node1, canvas_node2, event.link) elif event.message_type == core_pb2.MessageType.DELETE: - self.app.canvas.delete_wireless_edge( - canvas_node_one, canvas_node_two, event.link - ) + self.app.canvas.delete_wireless_edge(canvas_node1, canvas_node2, event.link) elif event.message_type == core_pb2.MessageType.NONE: - self.app.canvas.update_wireless_edge( - canvas_node_one, canvas_node_two, event.link - ) + self.app.canvas.update_wireless_edge(canvas_node1, canvas_node2, event.link) else: logging.warning("unknown link event: %s", event) @@ -472,10 +466,10 @@ class CoreClient: for edge in self.links.values(): link = core_pb2.Link() link.CopyFrom(edge.link) - if link.HasField("interface_one") and not link.interface_one.mac: - link.interface_one.mac = self.interfaces_manager.next_mac() - if link.HasField("interface_two") and not link.interface_two.mac: - link.interface_two.mac = self.interfaces_manager.next_mac() + if link.HasField("interface1") and not link.interface1.mac: + link.interface1.mac = self.interfaces_manager.next_mac() + if link.HasField("interface2") and not link.interface2.mac: + link.interface2.mac = self.interfaces_manager.next_mac() links.append(link) wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() @@ -693,10 +687,10 @@ class CoreClient: for link_proto in link_protos: response = self.client.add_link( self.session_id, - link_proto.node_one_id, - link_proto.node_two_id, - link_proto.interface_one, - link_proto.interface_two, + link_proto.node1_id, + link_proto.node2_id, + link_proto.interface1, + link_proto.interface2, link_proto.options, ) logging.debug("create link: %s", response) @@ -881,20 +875,20 @@ class CoreClient: link = core_pb2.Link( type=core_pb2.LinkType.WIRED, - node_one_id=src_node.id, - node_two_id=dst_node.id, - interface_one=src_interface, - interface_two=dst_interface, + node1_id=src_node.id, + node2_id=dst_node.id, + interface1=src_interface, + interface2=dst_interface, ) # assign after creating link proto, since interfaces are copied if src_interface: - interface_one = link.interface_one - edge.src_interface = interface_one - canvas_src_node.interfaces[interface_one.id] = interface_one + interface1 = link.interface1 + edge.src_interface = interface1 + canvas_src_node.interfaces[interface1.id] = interface1 if dst_interface: - interface_two = link.interface_two - edge.dst_interface = interface_two - canvas_dst_node.interfaces[interface_two.id] = interface_two + interface2 = link.interface2 + edge.dst_interface = interface2 + canvas_dst_node.interfaces[interface2.id] = interface2 edge.set_link(link) self.links[edge.token] = edge logging.info("Add link between %s and %s", src_node.name, dst_node.name) diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 92361ed4..1c20e2e1 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -227,21 +227,21 @@ class LinkConfigurationDialog(Dialog): ) link.options.CopyFrom(options) - interface_one = None - if link.HasField("interface_one"): - interface_one = link.interface_one.id - interface_two = None - if link.HasField("interface_two"): - interface_two = link.interface_two.id + interface1_id = None + if link.HasField("interface1"): + interface1_id = link.interface1.id + interface2_id = None + if link.HasField("interface2"): + interface2_id = link.interface2.id if not self.is_symmetric: link.options.unidirectional = True - asym_interface_one = None - if interface_one: - asym_interface_one = core_pb2.Interface(id=interface_one) - asym_interface_two = None - if interface_two: - asym_interface_two = core_pb2.Interface(id=interface_two) + asym_interface1 = None + if interface1_id: + asym_interface1 = core_pb2.Interface(id=interface1_id) + asym_interface2 = None + if interface2_id: + asym_interface2 = core_pb2.Interface(id=interface2_id) down_bandwidth = get_int(self.down_bandwidth) down_jitter = get_int(self.down_jitter) down_delay = get_int(self.down_delay) @@ -256,10 +256,10 @@ class LinkConfigurationDialog(Dialog): unidirectional=True, ) self.edge.asymmetric_link = core_pb2.Link( - node_one_id=link.node_two_id, - node_two_id=link.node_one_id, - interface_one=asym_interface_one, - interface_two=asym_interface_two, + node1_id=link.node2_id, + node2_id=link.node1_id, + interface1=asym_interface1, + interface2=asym_interface2, options=options, ) else: @@ -270,20 +270,20 @@ class LinkConfigurationDialog(Dialog): session_id = self.app.core.session_id self.app.core.client.edit_link( session_id, - link.node_one_id, - link.node_two_id, + link.node1_id, + link.node2_id, link.options, - interface_one, - interface_two, + interface1_id, + interface2_id, ) if self.edge.asymmetric_link: self.app.core.client.edit_link( session_id, - link.node_two_id, - link.node_one_id, + link.node2_id, + link.node1_id, self.edge.asymmetric_link.options, - interface_one, - interface_two, + interface1_id, + interface2_id, ) self.destroy() diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 00268c88..1d2264eb 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -296,13 +296,13 @@ class CanvasEdge(Edge): return label def create_node_labels(self) -> Tuple[str, str]: - label_one = None - if self.link.HasField("interface_one"): - label_one = self.interface_label(self.link.interface_one) - label_two = None - if self.link.HasField("interface_two"): - label_two = self.interface_label(self.link.interface_two) - return label_one, label_two + label1 = None + if self.link.HasField("interface1"): + label1 = self.interface_label(self.link.interface1) + label2 = None + if self.link.HasField("interface2"): + label2 = self.interface_label(self.link.interface2) + return label1, label2 def draw_labels(self) -> None: src_text, dst_text = self.create_node_labels() diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 3d6fd369..90dcd9f6 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -300,41 +300,39 @@ class CanvasGraph(tk.Canvas): # draw existing links for link in session.links: logging.debug("drawing link: %s", link) - canvas_node_one = self.core.canvas_nodes[link.node_one_id] - node_one = canvas_node_one.core_node - canvas_node_two = self.core.canvas_nodes[link.node_two_id] - node_two = canvas_node_two.core_node - token = create_edge_token(canvas_node_one.id, canvas_node_two.id) + canvas_node1 = self.core.canvas_nodes[link.node1_id] + node1 = canvas_node1.core_node + canvas_node2 = self.core.canvas_nodes[link.node2_id] + node2 = canvas_node2.core_node + token = create_edge_token(canvas_node1.id, canvas_node2.id) if link.type == core_pb2.LinkType.WIRELESS: - self.add_wireless_edge(canvas_node_one, canvas_node_two, link) + self.add_wireless_edge(canvas_node1, canvas_node2, link) else: if token not in self.edges: - src_pos = (node_one.position.x, node_one.position.y) - dst_pos = (node_two.position.x, node_two.position.y) - edge = CanvasEdge(self, canvas_node_one.id, src_pos, dst_pos) + src_pos = (node1.position.x, node1.position.y) + dst_pos = (node2.position.x, node2.position.y) + edge = CanvasEdge(self, canvas_node1.id, src_pos, dst_pos) edge.token = token - edge.dst = canvas_node_two.id + edge.dst = canvas_node2.id edge.set_link(link) edge.check_wireless() - canvas_node_one.edges.add(edge) - canvas_node_two.edges.add(edge) + canvas_node1.edges.add(edge) + canvas_node2.edges.add(edge) self.edges[edge.token] = edge self.core.links[edge.token] = edge - if link.HasField("interface_one"): - interface_one = link.interface_one + if link.HasField("interface1"): + interface1 = link.interface1 + self.core.interface_to_edge[(node1.id, interface1.id)] = token + canvas_node1.interfaces[interface1.id] = interface1 + edge.src_interface = interface1 + if link.HasField("interface2"): + interface2 = link.interface2 self.core.interface_to_edge[ - (node_one.id, interface_one.id) - ] = token - canvas_node_one.interfaces[interface_one.id] = interface_one - edge.src_interface = interface_one - if link.HasField("interface_two"): - interface_two = link.interface_two - self.core.interface_to_edge[ - (node_two.id, interface_two.id) + (node2.id, interface2.id) ] = edge.token - canvas_node_two.interfaces[interface_two.id] = interface_two - edge.dst_interface = interface_two + canvas_node2.interfaces[interface2.id] = interface2 + edge.dst_interface = interface2 elif link.options.unidirectional: edge = self.edges[token] edge.asymmetric_link = link @@ -965,26 +963,26 @@ class CanvasGraph(tk.Canvas): copy_link = copy_edge.link options = edge.link.options copy_link.options.CopyFrom(options) - interface_one = None - if copy_link.HasField("interface_one"): - interface_one = copy_link.interface_one.id - interface_two = None - if copy_link.HasField("interface_two"): - interface_two = copy_link.interface_two.id + interface1_id = None + if copy_link.HasField("interface1"): + interface1_id = copy_link.interface1.id + interface2_id = None + if copy_link.HasField("interface2"): + interface2_id = copy_link.interface2.id if not options.unidirectional: copy_edge.asymmetric_link = None else: - asym_interface_one = None - if interface_one: - asym_interface_one = core_pb2.Interface(id=interface_one) - asym_interface_two = None - if interface_two: - asym_interface_two = core_pb2.Interface(id=interface_two) + asym_interface1 = None + if interface1_id: + asym_interface1 = core_pb2.Interface(id=interface1_id) + asym_interface2 = None + if interface2_id: + asym_interface2 = core_pb2.Interface(id=interface2_id) copy_edge.asymmetric_link = core_pb2.Link( - node_one_id=copy_link.node_two_id, - node_two_id=copy_link.node_one_id, - interface_one=asym_interface_one, - interface_two=asym_interface_two, + node1_id=copy_link.node2_id, + node2_id=copy_link.node1_id, + interface1=asym_interface1, + interface2=asym_interface2, options=edge.asymmetric_link.options, ) self.itemconfig( diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 1973fe99..34270f56 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -89,21 +89,21 @@ class InterfaceManager: remaining_subnets = set() for edge in self.app.core.links.values(): link = edge.link - if link.HasField("interface_one"): - subnets = self.get_subnets(link.interface_one) + if link.HasField("interface1"): + subnets = self.get_subnets(link.interface1) remaining_subnets.add(subnets) - if link.HasField("interface_two"): - subnets = self.get_subnets(link.interface_two) + if link.HasField("interface2"): + subnets = self.get_subnets(link.interface2) remaining_subnets.add(subnets) # remove all subnets from used subnets when no longer present # or remove used indexes from subnet interfaces = [] for link in links: - if link.HasField("interface_one"): - interfaces.append(link.interface_one) - if link.HasField("interface_two"): - interfaces.append(link.interface_two) + if link.HasField("interface1"): + interfaces.append(link.interface1) + if link.HasField("interface2"): + interfaces.append(link.interface2) for interface in interfaces: subnets = self.get_subnets(interface) if subnets not in remaining_subnets: @@ -117,10 +117,10 @@ class InterfaceManager: def joined(self, links: List["core_pb2.Link"]) -> None: interfaces = [] for link in links: - if link.HasField("interface_one"): - interfaces.append(link.interface_one) - if link.HasField("interface_two"): - interfaces.append(link.interface_two) + if link.HasField("interface1"): + interfaces.append(link.interface1) + if link.HasField("interface2"): + interfaces.append(link.interface2) # add to used subnets and mark used indexes for interface in interfaces: diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 8b4ec39f..062217cb 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -21,8 +21,8 @@ if TYPE_CHECKING: from core.emulator.session import Session -def get_link_id(node_one: int, node_two: int, network_id: int) -> str: - link_id = f"{node_one}-{node_two}" +def get_link_id(node1_id: int, node2_id: int, network_id: int) -> str: + link_id = f"{node1_id}-{node2_id}" if network_id is not None: link_id = f"{link_id}-{network_id}" return link_id @@ -351,27 +351,27 @@ class Sdt: return result def add_link( - self, node_one: int, node_two: int, network_id: int = None, label: str = None + self, node1_id: int, node2_id: int, network_id: int = None, label: str = None ) -> None: """ Handle adding a link in SDT. - :param node_one: node one id - :param node_two: node two id + :param node1_id: node one id + :param node2_id: node two id :param network_id: network link is associated with, None otherwise :param label: label for link :return: nothing """ - logging.debug("sdt add link: %s, %s, %s", node_one, node_two, network_id) + logging.debug("sdt add link: %s, %s, %s", node1_id, node2_id, network_id) if not self.connect(): return - if self.wireless_net_check(node_one) or self.wireless_net_check(node_two): + if self.wireless_net_check(node1_id) or self.wireless_net_check(node2_id): return color = DEFAULT_LINK_COLOR if network_id: color = self.session.get_link_color(network_id) line = f"{color},2" - link_id = get_link_id(node_one, node_two, network_id) + link_id = get_link_id(node1_id, node2_id, network_id) layer = LINK_LAYER if network_id: node = self.session.nodes.get(network_id) @@ -383,47 +383,47 @@ class Sdt: if label: link_label = f'linklabel on,"{label}"' self.cmd( - f"link {node_one},{node_two},{link_id} linkLayer {layer} line {line} " + f"link {node1_id},{node2_id},{link_id} linkLayer {layer} line {line} " f"{link_label}" ) - def delete_link(self, node_one: int, node_two: int, network_id: int = None) -> None: + def delete_link(self, node1_id: int, node2_id: int, network_id: int = None) -> None: """ Handle deleting a link in SDT. - :param node_one: node one id - :param node_two: node two id + :param node1_id: node one id + :param node2_id: node two id :param network_id: network link is associated with, None otherwise :return: nothing """ - logging.debug("sdt delete link: %s, %s, %s", node_one, node_two, network_id) + logging.debug("sdt delete link: %s, %s, %s", node1_id, node2_id, network_id) if not self.connect(): return - if self.wireless_net_check(node_one) or self.wireless_net_check(node_two): + if self.wireless_net_check(node1_id) or self.wireless_net_check(node2_id): return - link_id = get_link_id(node_one, node_two, network_id) - self.cmd(f"delete link,{node_one},{node_two},{link_id}") + link_id = get_link_id(node1_id, node2_id, network_id) + self.cmd(f"delete link,{node1_id},{node2_id},{link_id}") def edit_link( - self, node_one: int, node_two: int, network_id: int, label: str + self, node1_id: int, node2_id: int, network_id: int, label: str ) -> None: """ Handle editing a link in SDT. - :param node_one: node one id - :param node_two: node two id + :param node1_id: node one id + :param node2_id: node two id :param network_id: network link is associated with, None otherwise :param label: label to update :return: nothing """ - logging.debug("sdt edit link: %s, %s, %s", node_one, node_two, network_id) + logging.debug("sdt edit link: %s, %s, %s", node1_id, node2_id, network_id) if not self.connect(): return - if self.wireless_net_check(node_one) or self.wireless_net_check(node_two): + if self.wireless_net_check(node1_id) or self.wireless_net_check(node2_id): return - link_id = get_link_id(node_one, node_two, network_id) + link_id = get_link_id(node1_id, node2_id, network_id) link_label = f'linklabel on,"{label}"' - self.cmd(f"link {node_one},{node_two},{link_id} {link_label}") + self.cmd(f"link {node1_id},{node2_id},{link_id} {link_label}") def handle_link_update(self, link_data: LinkData) -> None: """ @@ -432,13 +432,13 @@ class Sdt: :param link_data: link data to handle :return: nothing """ - node_one = link_data.node1_id - node_two = link_data.node2_id + node1_id = link_data.node1_id + node2_id = link_data.node2_id network_id = link_data.network_id label = link_data.label if link_data.message_type == MessageFlags.ADD: - self.add_link(node_one, node_two, network_id, label) + self.add_link(node1_id, node2_id, network_id, label) elif link_data.message_type == MessageFlags.DELETE: - self.delete_link(node_one, node_two, network_id) + self.delete_link(node1_id, node2_id, network_id) elif link_data.message_type == MessageFlags.NONE and label: - self.edit_link(node_one, node_two, network_id, label) + self.edit_link(node1_id, node2_id, network_id, label) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 973eb77f..afc1d826 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -534,7 +534,7 @@ class CoreXmlWriter: # check for interface one if link_data.interface1_id is not None: - interface_one = self.create_interface_element( + interface1 = self.create_interface_element( "interface_one", link_data.node1_id, link_data.interface1_id, @@ -544,11 +544,11 @@ class CoreXmlWriter: link_data.interface1_ip6, link_data.interface1_ip6_mask, ) - link_element.append(interface_one) + link_element.append(interface1) # check for interface two if link_data.interface2_id is not None: - interface_two = self.create_interface_element( + interface2 = self.create_interface_element( "interface_two", link_data.node2_id, link_data.interface2_id, @@ -558,14 +558,14 @@ class CoreXmlWriter: link_data.interface2_ip6, link_data.interface2_ip6_mask, ) - link_element.append(interface_two) + link_element.append(interface2) # check for options, don't write for emane/wlan links - node_one = self.session.get_node(link_data.node1_id, NodeBase) - node_two = self.session.get_node(link_data.node2_id, NodeBase) - is_node_one_wireless = isinstance(node_one, (WlanNode, EmaneNet)) - is_node_two_wireless = isinstance(node_two, (WlanNode, EmaneNet)) - if not any([is_node_one_wireless, is_node_two_wireless]): + node1 = self.session.get_node(link_data.node1_id, NodeBase) + node2 = self.session.get_node(link_data.node2_id, NodeBase) + is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet)) + is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet)) + if not any([is_node1_wireless, is_node2_wireless]): options = etree.Element("options") add_attribute(options, "delay", link_data.delay) add_attribute(options, "bandwidth", link_data.bandwidth) @@ -932,19 +932,19 @@ class CoreXmlReader: node_sets = set() for link_element in link_elements.iterchildren(): - node_one = get_int(link_element, "node_one") - node_two = get_int(link_element, "node_two") - node_set = frozenset((node_one, node_two)) + node1_id = get_int(link_element, "node_one") + node2_id = get_int(link_element, "node_two") + node_set = frozenset((node1_id, node2_id)) - interface_one_element = link_element.find("interface_one") - interface_one = None - if interface_one_element is not None: - interface_one = create_interface_data(interface_one_element) + interface1_element = link_element.find("interface_one") + interface1_data = None + if interface1_element is not None: + interface1_data = create_interface_data(interface1_element) - interface_two_element = link_element.find("interface_two") - interface_two = None - if interface_two_element is not None: - interface_two = create_interface_data(interface_two_element) + interface2_element = link_element.find("interface_two") + interface2_data = None + if interface2_element is not None: + interface2_data = create_interface_data(interface2_element) options_element = link_element.find("options") link_options = LinkOptions() @@ -966,18 +966,18 @@ class CoreXmlReader: link_options.gui_attributes = options_element.get("gui_attributes") if link_options.unidirectional == 1 and node_set in node_sets: - logging.info( - "updating link node_one(%s) node_two(%s)", node_one, node_two - ) + logging.info("updating link node1(%s) node2(%s)", node1_id, node2_id) self.session.update_link( - node_one, node_two, interface_one.id, interface_two.id, link_options + node1_id, + node2_id, + interface1_data.id, + interface2_data.id, + link_options, ) else: - logging.info( - "adding link node_one(%s) node_two(%s)", node_one, node_two - ) + logging.info("adding link node1(%s) node2(%s)", node1_id, node2_id) self.session.add_link( - node_one, node_two, interface_one, interface_two, link_options + node1_id, node2_id, interface1_data, interface2_data, link_options ) node_sets.add(node_set) diff --git a/daemon/examples/configservices/testing.py b/daemon/examples/configservices/testing.py index bc67ff46..948ec739 100644 --- a/daemon/examples/configservices/testing.py +++ b/daemon/examples/configservices/testing.py @@ -11,7 +11,7 @@ if __name__ == "__main__": # setup basic network prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(model="nothing") + options = NodeOptions(model=None) coreemu = CoreEmu() session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) @@ -19,14 +19,14 @@ if __name__ == "__main__": # node one options.config_services = ["DefaultRoute", "IPForward"] - node_one = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node_one) - session.add_link(node_one.id, switch.id, interface_one=interface) + node1 = session.add_node(CoreNode, options=options) + interface = prefixes.create_interface(node1) + session.add_link(node1.id, switch.id, interface1_data=interface) # node two - node_two = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node_two) - session.add_link(node_two.id, switch.id, interface_one=interface) + node2 = session.add_node(CoreNode, options=options) + interface = prefixes.create_interface(node2) + session.add_link(node2.id, switch.id, interface1_data=interface) # start session and run services session.instantiate() diff --git a/daemon/examples/docker/docker2core.py b/daemon/examples/docker/docker2core.py index 1211a16f..8151a590 100644 --- a/daemon/examples/docker/docker2core.py +++ b/daemon/examples/docker/docker2core.py @@ -17,15 +17,15 @@ if __name__ == "__main__": options = NodeOptions(model=None, image="ubuntu") # create node one - node_one = session.add_node(DockerNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(DockerNode, options=options) + interface1_data = prefixes.create_interface(node1) # create node two - node_two = session.add_node(CoreNode) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(CoreNode) + interface2_data = prefixes.create_interface(node2) # add link - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session.instantiate() diff --git a/daemon/examples/docker/docker2docker.py b/daemon/examples/docker/docker2docker.py index 9e1ae11f..a7a70534 100644 --- a/daemon/examples/docker/docker2docker.py +++ b/daemon/examples/docker/docker2docker.py @@ -18,15 +18,15 @@ if __name__ == "__main__": options = NodeOptions(model=None, image="ubuntu") # create node one - node_one = session.add_node(DockerNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(DockerNode, options=options) + interface1_data = prefixes.create_interface(node1) # create node two - node_two = session.add_node(DockerNode, options=options) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(DockerNode, options=options) + interface2_data = prefixes.create_interface(node2) # add link - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session.instantiate() diff --git a/daemon/examples/docker/switch.py b/daemon/examples/docker/switch.py index 74d58fe0..ef057945 100644 --- a/daemon/examples/docker/switch.py +++ b/daemon/examples/docker/switch.py @@ -22,20 +22,20 @@ if __name__ == "__main__": switch = session.add_node(SwitchNode) # node one - node_one = session.add_node(DockerNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(DockerNode, options=options) + interface1_data = prefixes.create_interface(node1) # node two - node_two = session.add_node(DockerNode, options=options) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(DockerNode, options=options) + interface2_data = prefixes.create_interface(node2) # node three node_three = session.add_node(CoreNode) interface_three = prefixes.create_interface(node_three) # add links - session.add_link(node_one.id, switch.id, interface_one) - session.add_link(node_two.id, switch.id, interface_two) + session.add_link(node1.id, switch.id, interface1_data) + session.add_link(node2.id, switch.id, interface2_data) session.add_link(node_three.id, switch.id, interface_three) # instantiate diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index 0477efdd..e847016f 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -44,11 +44,11 @@ def main(args): node = Node(position=position) response = core.add_node(session_id, node) logging.info("created node one: %s", response) - node_one_id = response.node_id + node1_id = response.node_id # create link - interface_one = interface_helper.create_interface(node_one_id, 0) - response = core.add_link(session_id, node_one_id, switch_id, interface_one) + interface1 = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, switch_id, interface1) logging.info("created link from node one to switch: %s", response) # create node two @@ -56,11 +56,11 @@ def main(args): node = Node(position=position, server=server_name) response = core.add_node(session_id, node) logging.info("created node two: %s", response) - node_two_id = response.node_id + node2_id = response.node_id # create link - interface_one = interface_helper.create_interface(node_two_id, 0) - response = core.add_link(session_id, node_two_id, switch_id, interface_one) + interface1 = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, switch_id, interface1) logging.info("created link from node two to switch: %s", response) # change session state diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py index 5656268c..24532266 100644 --- a/daemon/examples/grpc/emane80211.py +++ b/daemon/examples/grpc/emane80211.py @@ -57,11 +57,11 @@ def main(): node2_id = response.node_id # links nodes to switch - interface_one = interface_helper.create_interface(node1_id, 0) - response = core.add_link(session_id, node1_id, emane_id, interface_one) + interface1 = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, emane_id, interface1) logging.info("created link: %s", response) - interface_one = interface_helper.create_interface(node2_id, 0) - response = core.add_link(session_id, node2_id, emane_id, interface_one) + interface1 = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, emane_id, interface1) logging.info("created link: %s", response) # change session state diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 3ab0e0ba..74e315c6 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -53,11 +53,11 @@ def main(): node2_id = response.node_id # links nodes to switch - interface_one = interface_helper.create_interface(node1_id, 0) - response = core.add_link(session_id, node1_id, switch_id, interface_one) + interface1 = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, switch_id, interface1) logging.info("created link: %s", response) - interface_one = interface_helper.create_interface(node2_id, 0) - response = core.add_link(session_id, node2_id, switch_id, interface_one) + interface1 = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, switch_id, interface1) logging.info("created link: %s", response) # change session state diff --git a/daemon/examples/grpc/wlan.py b/daemon/examples/grpc/wlan.py index 6118ae4c..d60ca1be 100644 --- a/daemon/examples/grpc/wlan.py +++ b/daemon/examples/grpc/wlan.py @@ -65,11 +65,11 @@ def main(): node2_id = response.node_id # links nodes to switch - interface_one = interface_helper.create_interface(node1_id, 0) - response = core.add_link(session_id, node1_id, wlan_id, interface_one) + interface1 = interface_helper.create_interface(node1_id, 0) + response = core.add_link(session_id, node1_id, wlan_id, interface1) logging.info("created link: %s", response) - interface_one = interface_helper.create_interface(node2_id, 0) - response = core.add_link(session_id, node2_id, wlan_id, interface_one) + interface1 = interface_helper.create_interface(node2_id, 0) + response = core.add_link(session_id, node2_id, wlan_id, interface1) logging.info("created link: %s", response) # change session state diff --git a/daemon/examples/lxd/lxd2core.py b/daemon/examples/lxd/lxd2core.py index 1365bd4c..49b68943 100644 --- a/daemon/examples/lxd/lxd2core.py +++ b/daemon/examples/lxd/lxd2core.py @@ -17,15 +17,15 @@ if __name__ == "__main__": options = NodeOptions(image="ubuntu") # create node one - node_one = session.add_node(LxcNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(LxcNode, options=options) + interface1_data = prefixes.create_interface(node1) # create node two - node_two = session.add_node(CoreNode) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(CoreNode) + interface2_data = prefixes.create_interface(node2) # add link - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session.instantiate() diff --git a/daemon/examples/lxd/lxd2lxd.py b/daemon/examples/lxd/lxd2lxd.py index 53a360e8..18af8037 100644 --- a/daemon/examples/lxd/lxd2lxd.py +++ b/daemon/examples/lxd/lxd2lxd.py @@ -18,15 +18,15 @@ if __name__ == "__main__": options = NodeOptions(image="ubuntu:18.04") # create node one - node_one = session.add_node(LxcNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(LxcNode, options=options) + interface1_data = prefixes.create_interface(node1) # create node two - node_two = session.add_node(LxcNode, options=options) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(LxcNode, options=options) + interface2_data = prefixes.create_interface(node2) # add link - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session.instantiate() diff --git a/daemon/examples/lxd/switch.py b/daemon/examples/lxd/switch.py index 3b6226e4..31a79887 100644 --- a/daemon/examples/lxd/switch.py +++ b/daemon/examples/lxd/switch.py @@ -22,21 +22,21 @@ if __name__ == "__main__": switch = session.add_node(SwitchNode) # node one - node_one = session.add_node(LxcNode, options=options) - interface_one = prefixes.create_interface(node_one) + node1 = session.add_node(LxcNode, options=options) + interface1_data = prefixes.create_interface(node1) # node two - node_two = session.add_node(LxcNode, options=options) - interface_two = prefixes.create_interface(node_two) + node2 = session.add_node(LxcNode, options=options) + interface2_data = prefixes.create_interface(node2) # node three - node_three = session.add_node(CoreNode) - interface_three = prefixes.create_interface(node_three) + node3 = session.add_node(CoreNode) + interface3_data = prefixes.create_interface(node3) # add links - session.add_link(node_one.id, switch.id, interface_one) - session.add_link(node_two.id, switch.id, interface_two) - session.add_link(node_three.id, switch.id, interface_three) + session.add_link(node1.id, switch.id, interface1_data) + session.add_link(node2.id, switch.id, interface2_data) + session.add_link(node3.id, switch.id, interface3_data) # instantiate session.instantiate() diff --git a/daemon/examples/python/distributed_emane.py b/daemon/examples/python/distributed_emane.py index 3248a8e3..d9b41ea4 100644 --- a/daemon/examples/python/distributed_emane.py +++ b/daemon/examples/python/distributed_emane.py @@ -52,17 +52,17 @@ def main(args): # create local node, switch, and remote nodes options = NodeOptions(model="mdr") options.set_position(0, 0) - node_one = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) emane_net = session.add_node(EmaneNet) session.emane.set_model(emane_net, EmaneIeee80211abgModel) options.server = server_name - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface_one = prefixes.create_interface(node_one) - interface_two = prefixes.create_interface(node_two) - session.add_link(node_one.id, emane_net.id, interface_one=interface_one) - session.add_link(node_two.id, emane_net.id, interface_one=interface_two) + interface1_data = prefixes.create_interface(node1) + interface2_data = prefixes.create_interface(node2) + session.add_link(node1.id, emane_net.id, interface1_data=interface1_data) + session.add_link(node2.id, emane_net.id, interface1_data=interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/distributed_lxd.py b/daemon/examples/python/distributed_lxd.py index de919012..affb16a8 100644 --- a/daemon/examples/python/distributed_lxd.py +++ b/daemon/examples/python/distributed_lxd.py @@ -43,14 +43,14 @@ def main(args): # create local node, switch, and remote nodes options = NodeOptions(image="ubuntu:18.04") - node_one = session.add_node(LxcNode, options=options) + node1 = session.add_node(LxcNode, options=options) options.server = server_name - node_two = session.add_node(LxcNode, options=options) + node2 = session.add_node(LxcNode, options=options) # create node interfaces and link - interface_one = prefixes.create_interface(node_one) - interface_two = prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + interface1_data = prefixes.create_interface(node1) + interface2_data = prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/distributed_ptp.py b/daemon/examples/python/distributed_ptp.py index 26531399..6bf33474 100644 --- a/daemon/examples/python/distributed_ptp.py +++ b/daemon/examples/python/distributed_ptp.py @@ -43,14 +43,14 @@ def main(args): # create local node, switch, and remote nodes options = NodeOptions() - node_one = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) options.server = server_name - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface_one = prefixes.create_interface(node_one) - interface_two = prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + interface1_data = prefixes.create_interface(node1) + interface2_data = prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/distributed_switch.py b/daemon/examples/python/distributed_switch.py index c52c1cc1..8991161e 100644 --- a/daemon/examples/python/distributed_switch.py +++ b/daemon/examples/python/distributed_switch.py @@ -45,17 +45,17 @@ def main(args): session.set_state(EventTypes.CONFIGURATION_STATE) # create local node, switch, and remote nodes - node_one = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) switch = session.add_node(SwitchNode) options = NodeOptions() options.server = server_name - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface_one = prefixes.create_interface(node_one) - interface_two = prefixes.create_interface(node_two) - session.add_link(node_one.id, switch.id, interface_one=interface_one) - session.add_link(node_two.id, switch.id, interface_one=interface_two) + interface1_data = prefixes.create_interface(node1) + interface2_data = prefixes.create_interface(node2) + session.add_link(node1.id, switch.id, interface1_data=interface1_data) + session.add_link(node2.id, switch.id, interface1_data=interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index da93026b..d3f6652a 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -43,7 +43,7 @@ def main(): node = session.add_node(CoreNode, options=options) node.setposition(x=150 * (i + 1), y=150) interface = prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface_one=interface) + session.add_link(node.id, emane_network.id, interface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/switch.py b/daemon/examples/python/switch.py index 9475fc47..1b939cd7 100644 --- a/daemon/examples/python/switch.py +++ b/daemon/examples/python/switch.py @@ -32,7 +32,7 @@ def main(): for _ in range(NODES): node = session.add_node(CoreNode) interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface_one=interface) + session.add_link(node.id, switch.id, interface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/switch_inject.py b/daemon/examples/python/switch_inject.py index 8c929e91..59816b19 100644 --- a/daemon/examples/python/switch_inject.py +++ b/daemon/examples/python/switch_inject.py @@ -34,7 +34,7 @@ def main(): for _ in range(NODES): node = session.add_node(CoreNode) interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface_one=interface) + session.add_link(node.id, switch.id, interface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/wlan.py b/daemon/examples/python/wlan.py index b09ae5ce..0302bbd3 100644 --- a/daemon/examples/python/wlan.py +++ b/daemon/examples/python/wlan.py @@ -36,7 +36,7 @@ def main(): for _ in range(NODES): node = session.add_node(CoreNode, options=options) interface = prefixes.create_interface(node) - session.add_link(node.id, wlan.id, interface_one=interface) + session.add_link(node.id, wlan.id, interface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index d602f9d3..8062a731 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -492,16 +492,16 @@ message AddLinkRequest { message AddLinkResponse { bool result = 1; - Interface interface_one = 2; - Interface interface_two = 3; + Interface interface1 = 2; + Interface interface2 = 3; } message EditLinkRequest { int32 session_id = 1; - int32 node_one_id = 2; - int32 node_two_id = 3; - int32 interface_one_id = 4; - int32 interface_two_id = 5; + int32 node1_id = 2; + int32 node2_id = 3; + int32 interface1_id = 4; + int32 interface2_id = 5; LinkOptions options = 6; } @@ -511,10 +511,10 @@ message EditLinkResponse { message DeleteLinkRequest { int32 session_id = 1; - int32 node_one_id = 2; - int32 node_two_id = 3; - int32 interface_one_id = 4; - int32 interface_two_id = 5; + int32 node1_id = 2; + int32 node2_id = 3; + int32 interface1_id = 4; + int32 interface2_id = 5; } message DeleteLinkResponse { @@ -702,11 +702,11 @@ message Node { } message Link { - int32 node_one_id = 1; - int32 node_two_id = 2; + int32 node1_id = 1; + int32 node2_id = 2; LinkType.Enum type = 3; - Interface interface_one = 4; - Interface interface_two = 5; + Interface interface1 = 4; + Interface interface2 = 5; LinkOptions options = 6; int32 network_id = 7; string label = 8; diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index 8c3ee4ca..e4189700 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -75,8 +75,8 @@ message GetEmaneEventChannelResponse { message EmaneLinkRequest { int32 session_id = 1; - int32 nem_one = 2; - int32 nem_two = 3; + int32 nem1 = 2; + int32 nem2 = 3; bool linked = 4; } @@ -93,12 +93,12 @@ message EmaneModelConfig { message EmanePathlossesRequest { int32 session_id = 1; - int32 node_one = 2; - float rx_one = 3; - int32 interface_one_id = 4; - int32 node_two = 5; - float rx_two = 6; - int32 interface_two_id = 7; + int32 node1_id = 2; + float rx1 = 3; + int32 interface1_id = 4; + int32 node2_id = 5; + float rx2 = 6; + int32 interface2_id = 7; } message EmanePathlossesResponse { diff --git a/daemon/proto/core/api/grpc/wlan.proto b/daemon/proto/core/api/grpc/wlan.proto index bbb9757f..9605d633 100644 --- a/daemon/proto/core/api/grpc/wlan.proto +++ b/daemon/proto/core/api/grpc/wlan.proto @@ -38,8 +38,8 @@ message SetWlanConfigResponse { message WlanLinkRequest { int32 session_id = 1; int32 wlan = 2; - int32 node_one = 3; - int32 node_two = 4; + int32 node1_id = 3; + int32 node2_id = 4; bool linked = 5; } diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index 2d90ebcc..15e3d869 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -3,6 +3,7 @@ Unit tests for testing CORE EMANE networks. """ import os from tempfile import TemporaryFile +from typing import Type from xml.etree import ElementTree import pytest @@ -43,7 +44,9 @@ def ping( class TestEmane: @pytest.mark.parametrize("model", _EMANE_MODELS) - def test_models(self, session: Session, model: EmaneModel, ip_prefixes: IpPrefixes): + def test_models( + self, session: Session, model: Type[EmaneModel], ip_prefixes: IpPrefixes + ): """ Test emane models within a basic network. @@ -70,20 +73,20 @@ class TestEmane: # create nodes options = NodeOptions(model="mdr") options.set_position(150, 150) - node_one = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) options.set_position(300, 150) - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) - for i, node in enumerate([node_one, node_two]): + for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) interface = ip_prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface_one=interface) + session.add_link(node.id, emane_network.id, interface1_data=interface) # instantiate session session.instantiate() - # ping n2 from n1 and assert success - status = ping(node_one, node_two, ip_prefixes, count=5) + # ping node2 from node1 and assert success + status = ping(node1, node2, ip_prefixes, count=5) assert not status def test_xml_emane( @@ -110,22 +113,22 @@ class TestEmane: # create nodes options = NodeOptions(model="mdr") options.set_position(150, 150) - node_one = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) options.set_position(300, 150) - node_two = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) - for i, node in enumerate([node_one, node_two]): + for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) interface = ip_prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface_one=interface) + session.add_link(node.id, emane_network.id, interface1_data=interface) # instantiate session session.instantiate() # get ids for nodes emane_id = emane_network.id - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -141,9 +144,9 @@ class TestEmane: # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) @@ -154,7 +157,7 @@ class TestEmane: ) # verify nodes and configuration were restored - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) assert session.get_node(emane_id, EmaneNet) assert value == config_value diff --git a/daemon/tests/test_conf.py b/daemon/tests/test_conf.py index 1973dcee..e90acfbd 100644 --- a/daemon/tests/test_conf.py +++ b/daemon/tests/test_conf.py @@ -14,11 +14,11 @@ from core.nodes.network import WlanNode class TestConfigurableOptions(ConfigurableOptions): - name_one = "value1" - name_two = "value2" + name1 = "value1" + name2 = "value2" options = [ - Configuration(_id=name_one, _type=ConfigDataTypes.STRING, label=name_one), - Configuration(_id=name_two, _type=ConfigDataTypes.STRING, label=name_two), + Configuration(_id=name1, _type=ConfigDataTypes.STRING, label=name1), + Configuration(_id=name2, _type=ConfigDataTypes.STRING, label=name2), ] @@ -33,11 +33,11 @@ class TestConf: # then assert len(default_values) == 2 - assert TestConfigurableOptions.name_one in default_values - assert TestConfigurableOptions.name_two in default_values + assert TestConfigurableOptions.name1 in default_values + assert TestConfigurableOptions.name2 in default_values assert len(instance_default_values) == 2 - assert TestConfigurableOptions.name_one in instance_default_values - assert TestConfigurableOptions.name_two in instance_default_values + assert TestConfigurableOptions.name1 in instance_default_values + assert TestConfigurableOptions.name2 in instance_default_values def test_nodes(self): # given diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 68515a41..626f84a7 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -48,19 +48,19 @@ class TestCore: net_node = session.add_node(net_type) # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to net node - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, net_node.id, interface_one=interface) + session.add_link(node.id, net_node.id, interface1_data=interface) # instantiate session session.instantiate() - # ping n2 from n1 and assert success - status = ping(node_one, node_two, ip_prefixes) + # ping node2 from node1 and assert success + status = ping(node1, node2, ip_prefixes) assert not status def test_vnode_client(self, request, session: Session, ip_prefixes: IpPrefixes): @@ -75,16 +75,16 @@ class TestCore: ptp_node = session.add_node(PtpNet) # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to ptp net - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface_one=interface) + session.add_link(node.id, ptp_node.id, interface1_data=interface) # get node client for testing - client = node_one.client + client = node1.client # instantiate session session.instantiate() @@ -108,13 +108,13 @@ class TestCore: ptp_node = session.add_node(PtpNet) # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to ptp net - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface_one=interface) + session.add_link(node.id, ptp_node.id, interface1_data=interface) # instantiate session session.instantiate() @@ -123,22 +123,22 @@ class TestCore: assert ptp_node.all_link_data(MessageFlags.ADD) # check common nets exist between linked nodes - assert node_one.commonnets(node_two) - assert node_two.commonnets(node_one) + assert node1.commonnets(node2) + assert node2.commonnets(node1) # check we can retrieve netif index - assert node_one.ifname(0) - assert node_two.ifname(0) + assert node1.ifname(0) + assert node2.ifname(0) # check interface parameters - interface = node_one.netif(0) + interface = node1.netif(0) interface.setparam("test", 1) assert interface.getparam("test") == 1 assert interface.getparams() # delete netif and test that if no longer exists - node_one.delnetif(0) - assert not node_one.netif(0) + node1.delnetif(0) + assert not node1.netif(0) def test_wlan_ping(self, session: Session, ip_prefixes: IpPrefixes): """ @@ -155,19 +155,19 @@ class TestCore: # create nodes options = NodeOptions(model="mdr") options.set_position(0, 0) - node_one = session.add_node(CoreNode, options=options) - node_two = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # link nodes - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface_one=interface) + session.add_link(node.id, wlan_node.id, interface1_data=interface) # instantiate session session.instantiate() - # ping n2 from n1 and assert success - status = ping(node_one, node_two, ip_prefixes) + # ping node2 from node1 and assert success + status = ping(node1, node2, ip_prefixes) assert not status def test_mobility(self, session: Session, ip_prefixes: IpPrefixes): @@ -185,13 +185,13 @@ class TestCore: # create nodes options = NodeOptions(model="mdr") options.set_position(0, 0) - node_one = session.add_node(CoreNode, options=options) - node_two = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # link nodes - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface_one=interface) + session.add_link(node.id, wlan_node.id, interface1_data=interface) # configure mobility script for session config = { diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index c0686d71..131af93d 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -34,23 +34,23 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() position = core_pb2.Position(x=50, y=100) - node_one = core_pb2.Node(id=1, position=position, model="PC") + node1 = core_pb2.Node(id=1, position=position, model="PC") position = core_pb2.Position(x=100, y=100) - node_two = core_pb2.Node(id=2, position=position, model="PC") + node2 = core_pb2.Node(id=2, position=position, model="PC") position = core_pb2.Position(x=200, y=200) wlan_node = core_pb2.Node( id=3, type=NodeTypes.WIRELESS_LAN.value, position=position ) - nodes = [node_one, node_two, wlan_node] + nodes = [node1, node2, wlan_node] interface_helper = InterfaceHelper(ip4_prefix="10.83.0.0/16") - interface_one = interface_helper.create_interface(node_one.id, 0) - interface_two = interface_helper.create_interface(node_two.id, 0) + interface1 = interface_helper.create_interface(node1.id, 0) + interface2 = interface_helper.create_interface(node2.id, 0) link = core_pb2.Link( type=core_pb2.LinkType.WIRED, - node_one_id=node_one.id, - node_two_id=node_two.id, - interface_one=interface_one, - interface_two=interface_two, + node1_id=node1.id, + node2_id=node2.id, + interface1=interface1, + interface2=interface2, ) links = [link] hook = core_pb2.Hook( @@ -99,11 +99,11 @@ class TestGrpc: ) mobility_configs = [mobility_config] service_config = ServiceConfig( - node_id=node_one.id, service="DefaultRoute", validate=["echo hello"] + node_id=node1.id, service="DefaultRoute", validate=["echo hello"] ) service_configs = [service_config] service_file_config = ServiceFileConfig( - node_id=node_one.id, + node_id=node1.id, service="DefaultRoute", file="defaultroute.sh", data="echo hello", @@ -128,11 +128,11 @@ class TestGrpc: ) # then - assert node_one.id in session.nodes - assert node_two.id in session.nodes + assert node1.id in session.nodes + assert node2.id in session.nodes assert wlan_node.id in session.nodes - assert session.nodes[node_one.id].netif(0) is not None - assert session.nodes[node_two.id].netif(0) is not None + assert session.nodes[node1.id].netif(0) is not None + assert session.nodes[node2.id].netif(0) is not None hook_file, hook_data = session._hooks[EventTypes.RUNTIME_STATE][0] assert hook_file == hook.file assert hook_data == hook.data @@ -153,11 +153,11 @@ class TestGrpc: ) assert set_model_config[model_config_key] == model_config_value service = session.services.get_service( - node_one.id, service_config.service, default_service=True + node1.id, service_config.service, default_service=True ) assert service.validate == tuple(service_config.validate) service_file = session.services.get_service_file( - node_one, service_file_config.service, service_file_config.file + node1, service_file_config.service, service_file_config.file ) assert service_file.data == service_file_config.data @@ -596,7 +596,7 @@ class TestGrpc: # then with client.context_connect(): response = client.edit_link( - session.id, node.id, switch.id, options, interface_one_id=interface.id + session.id, node.id, switch.id, options, interface1_id=interface.id ) # then @@ -608,28 +608,28 @@ class TestGrpc: # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() - node_one = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + node1 = session.add_node(CoreNode) + interface1 = ip_prefixes.create_interface(node1) + node2 = session.add_node(CoreNode) + interface2 = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1, interface2) link_node = None for node_id in session.nodes: node = session.nodes[node_id] - if node.id not in {node_one.id, node_two.id}: + if node.id not in {node1.id, node2.id}: link_node = node break - assert len(link_node.all_link_data(0)) == 1 + assert len(link_node.all_link_data()) == 1 # then with client.context_connect(): response = client.delete_link( - session.id, node_one.id, node_two.id, interface_one.id, interface_two.id + session.id, node1.id, node2.id, interface1.id, interface2.id ) # then assert response.result is True - assert len(link_node.all_link_data(0)) == 0 + assert len(link_node.all_link_data()) == 0 def test_get_wlan_config(self, grpc_server: CoreGrpcServer): # given diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 800a8e62..1187b4d7 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -50,12 +50,13 @@ class TestGui: self, coretlv: CoreHandler, node_type: NodeTypes, model: Optional[str] ): node_id = 1 + name = "node1" message = coreapi.CoreNodeMessage.create( MessageFlags.ADD.value, [ (NodeTlvs.NUMBER, node_id), (NodeTlvs.TYPE, node_type.value), - (NodeTlvs.NAME, "n1"), + (NodeTlvs.NAME, name), (NodeTlvs.X_POSITION, 0), (NodeTlvs.Y_POSITION, 0), (NodeTlvs.MODEL, model), @@ -63,7 +64,9 @@ class TestGui: ) coretlv.handle_message(message) - assert coretlv.session.get_node(node_id, NodeBase) is not None + node = coretlv.session.get_node(node_id, NodeBase) + assert node + assert node.name == name def test_node_update(self, coretlv: CoreHandler): node_id = 1 @@ -99,71 +102,71 @@ class TestGui: coretlv.session.get_node(node_id, NodeBase) def test_link_add_node_to_net(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 def test_link_add_net_to_node(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface2_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, switch), - (LinkTlvs.N2_NUMBER, node_one), + (LinkTlvs.N1_NUMBER, switch_id), + (LinkTlvs.N2_NUMBER, node1_id), (LinkTlvs.INTERFACE2_NUMBER, 0), - (LinkTlvs.INTERFACE2_IP4, interface_one), + (LinkTlvs.INTERFACE2_IP4, interface2_ip4), (LinkTlvs.INTERFACE2_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 def test_link_add_node_to_node(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - node_two = 2 - coretlv.session.add_node(CoreNode, _id=node_two) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + node2_id = 2 + coretlv.session.add_node(CoreNode, _id=node2_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) - interface_two = str(ip_prefix[node_two]) + interface1_ip4 = str(ip_prefix[node1_id]) + interface2_ip4 = str(ip_prefix[node2_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, node_two), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, node2_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), (LinkTlvs.INTERFACE2_NUMBER, 0), - (LinkTlvs.INTERFACE2_IP4, interface_two), + (LinkTlvs.INTERFACE2_IP4, interface2_ip4), (LinkTlvs.INTERFACE2_IP4_MASK, 24), ], ) @@ -177,24 +180,24 @@ class TestGui: assert len(all_links) == 1 def test_link_update(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 link = all_links[0] @@ -204,37 +207,37 @@ class TestGui: message = coreapi.CoreLinkMessage.create( 0, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), (LinkTlvs.BANDWIDTH, bandwidth), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 link = all_links[0] assert link.bandwidth == bandwidth def test_link_delete_node_to_node(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - node_two = 2 - coretlv.session.add_node(CoreNode, _id=node_two) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + node2_id = 2 + coretlv.session.add_node(CoreNode, _id=node2_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) - interface_two = str(ip_prefix[node_two]) + interface1_ip4 = str(ip_prefix[node1_id]) + interface2_ip4 = str(ip_prefix[node2_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, node_two), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, node2_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), - (LinkTlvs.INTERFACE2_IP4, interface_two), + (LinkTlvs.INTERFACE2_IP4, interface2_ip4), (LinkTlvs.INTERFACE2_IP4_MASK, 24), ], ) @@ -248,8 +251,8 @@ class TestGui: message = coreapi.CoreLinkMessage.create( MessageFlags.DELETE.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, node_two), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, node2_id), (LinkTlvs.INTERFACE1_NUMBER, 0), (LinkTlvs.INTERFACE2_NUMBER, 0), ], @@ -263,74 +266,74 @@ class TestGui: assert len(all_links) == 0 def test_link_delete_node_to_net(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( MessageFlags.DELETE.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 0 def test_link_delete_net_to_node(self, coretlv: CoreHandler): - node_one = 1 - coretlv.session.add_node(CoreNode, _id=node_one) - switch = 2 - coretlv.session.add_node(SwitchNode, _id=switch) + node1_id = 1 + coretlv.session.add_node(CoreNode, _id=node1_id) + switch_id = 2 + coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface_one = str(ip_prefix[node_one]) + interface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ - (LinkTlvs.N1_NUMBER, node_one), - (LinkTlvs.N2_NUMBER, switch), + (LinkTlvs.N1_NUMBER, node1_id), + (LinkTlvs.N2_NUMBER, switch_id), (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface_one), + (LinkTlvs.INTERFACE1_IP4, interface1_ip4), (LinkTlvs.INTERFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( MessageFlags.DELETE.value, [ - (LinkTlvs.N1_NUMBER, switch), - (LinkTlvs.N2_NUMBER, node_one), + (LinkTlvs.N1_NUMBER, switch_id), + (LinkTlvs.N2_NUMBER, node1_id), (LinkTlvs.INTERFACE2_NUMBER, 0), ], ) coretlv.handle_message(message) - switch_node = coretlv.session.get_node(switch, SwitchNode) + switch_node = coretlv.session.get_node(switch_id, SwitchNode) all_links = switch_node.all_link_data() assert len(all_links) == 0 diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 9f693da1..61f9d13d 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -10,71 +10,71 @@ def create_ptp_network( session: Session, ip_prefixes: IpPrefixes ) -> Tuple[CoreNode, CoreNode]: # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to net node - interface_one = ip_prefixes.create_interface(node_one) - interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + interface1_data = ip_prefixes.create_interface(node1) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session session.instantiate() - return node_one, node_two + return node1, node2 class TestLinks: def test_add_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - interface_two = ip_prefixes.create_interface(node_two) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + interface2_data = ip_prefixes.create_interface(node2) # when - session.add_link(node_one.id, node_two.id, interface_one, interface_two) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) # then - assert node_one.netif(interface_one.id) - assert node_two.netif(interface_two.id) + assert node1.netif(interface1_data.id) + assert node2.netif(interface2_data.id) def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(CoreNode) - node_two = session.add_node(SwitchNode) - interface_one = ip_prefixes.create_interface(node_one) + node1 = session.add_node(CoreNode) + node2 = session.add_node(SwitchNode) + interface1_data = ip_prefixes.create_interface(node1) # when - session.add_link(node_one.id, node_two.id, interface_one=interface_one) + session.add_link(node1.id, node2.id, interface1_data=interface1_data) # then - assert node_two.all_link_data() - assert node_one.netif(interface_one.id) + assert node2.all_link_data() + assert node1.netif(interface1_data.id) def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(SwitchNode) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) + node1 = session.add_node(SwitchNode) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) # when - session.add_link(node_one.id, node_two.id, interface_two=interface_two) + session.add_link(node1.id, node2.id, interface2_data=interface2_data) # then - assert node_one.all_link_data() - assert node_two.netif(interface_two.id) + assert node1.all_link_data() + assert node2.netif(interface2_data.id) def test_add_net_to_net(self, session): # given - node_one = session.add_node(SwitchNode) - node_two = session.add_node(SwitchNode) + node1 = session.add_node(SwitchNode) + node2 = session.add_node(SwitchNode) # when - session.add_link(node_one.id, node_two.id) + session.add_link(node1.id, node2.id) # then - assert node_one.all_link_data() + assert node1.all_link_data() def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -83,34 +83,31 @@ class TestLinks: per = 25 dup = 25 jitter = 10 - node_one = session.add_node(CoreNode) - node_two = session.add_node(SwitchNode) - interface_one_data = ip_prefixes.create_interface(node_one) - session.add_link(node_one.id, node_two.id, interface_one_data) - interface_one = node_one.netif(interface_one_data.id) - assert interface_one.getparam("delay") != delay - assert interface_one.getparam("bw") != bandwidth - assert interface_one.getparam("loss") != per - assert interface_one.getparam("duplicate") != dup - assert interface_one.getparam("jitter") != jitter + node1 = session.add_node(CoreNode) + node2 = session.add_node(SwitchNode) + interface1_data = ip_prefixes.create_interface(node1) + session.add_link(node1.id, node2.id, interface1_data) + interface1 = node1.netif(interface1_data.id) + assert interface1.getparam("delay") != delay + assert interface1.getparam("bw") != bandwidth + assert interface1.getparam("loss") != per + assert interface1.getparam("duplicate") != dup + assert interface1.getparam("jitter") != jitter # when - link_options = LinkOptions( + options = LinkOptions( delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter ) session.update_link( - node_one.id, - node_two.id, - interface_one_id=interface_one_data.id, - options=link_options, + node1.id, node2.id, interface1_id=interface1_data.id, options=options ) # then - assert interface_one.getparam("delay") == delay - assert interface_one.getparam("bw") == bandwidth - assert interface_one.getparam("loss") == per - assert interface_one.getparam("duplicate") == dup - assert interface_one.getparam("jitter") == jitter + assert interface1.getparam("delay") == delay + assert interface1.getparam("bw") == bandwidth + assert interface1.getparam("loss") == per + assert interface1.getparam("duplicate") == dup + assert interface1.getparam("jitter") == jitter def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -119,34 +116,31 @@ class TestLinks: per = 25 dup = 25 jitter = 10 - node_one = session.add_node(SwitchNode) - node_two = session.add_node(CoreNode) - interface_two_data = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_two=interface_two_data) - interface_two = node_two.netif(interface_two_data.id) - assert interface_two.getparam("delay") != delay - assert interface_two.getparam("bw") != bandwidth - assert interface_two.getparam("loss") != per - assert interface_two.getparam("duplicate") != dup - assert interface_two.getparam("jitter") != jitter + node1 = session.add_node(SwitchNode) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface2_data=interface2_data) + interface2 = node2.netif(interface2_data.id) + assert interface2.getparam("delay") != delay + assert interface2.getparam("bw") != bandwidth + assert interface2.getparam("loss") != per + assert interface2.getparam("duplicate") != dup + assert interface2.getparam("jitter") != jitter # when - link_options = LinkOptions( + options = LinkOptions( delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter ) session.update_link( - node_one.id, - node_two.id, - interface_two_id=interface_two_data.id, - options=link_options, + node1.id, node2.id, interface2_id=interface2_data.id, options=options ) # then - assert interface_two.getparam("delay") == delay - assert interface_two.getparam("bw") == bandwidth - assert interface_two.getparam("loss") == per - assert interface_two.getparam("duplicate") == dup - assert interface_two.getparam("jitter") == jitter + assert interface2.getparam("delay") == delay + assert interface2.getparam("bw") == bandwidth + assert interface2.getparam("loss") == per + assert interface2.getparam("duplicate") == dup + assert interface2.getparam("jitter") == jitter def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -155,93 +149,85 @@ class TestLinks: per = 25 dup = 25 jitter = 10 - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) - interface_one_data = ip_prefixes.create_interface(node_one) - interface_two_data = ip_prefixes.create_interface(node_two) - session.add_link( - node_one.id, node_two.id, interface_one_data, interface_two_data - ) - interface_one = node_one.netif(interface_one_data.id) - interface_two = node_two.netif(interface_two_data.id) - assert interface_one.getparam("delay") != delay - assert interface_one.getparam("bw") != bandwidth - assert interface_one.getparam("loss") != per - assert interface_one.getparam("duplicate") != dup - assert interface_one.getparam("jitter") != jitter - assert interface_two.getparam("delay") != delay - assert interface_two.getparam("bw") != bandwidth - assert interface_two.getparam("loss") != per - assert interface_two.getparam("duplicate") != dup - assert interface_two.getparam("jitter") != jitter + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) + interface1 = node1.netif(interface1_data.id) + interface2 = node2.netif(interface2_data.id) + assert interface1.getparam("delay") != delay + assert interface1.getparam("bw") != bandwidth + assert interface1.getparam("loss") != per + assert interface1.getparam("duplicate") != dup + assert interface1.getparam("jitter") != jitter + assert interface2.getparam("delay") != delay + assert interface2.getparam("bw") != bandwidth + assert interface2.getparam("loss") != per + assert interface2.getparam("duplicate") != dup + assert interface2.getparam("jitter") != jitter # when - link_options = LinkOptions( + options = LinkOptions( delay=delay, bandwidth=bandwidth, per=per, dup=dup, jitter=jitter ) session.update_link( - node_one.id, - node_two.id, - interface_one_data.id, - interface_two_data.id, - link_options, + node1.id, node2.id, interface1_data.id, interface2_data.id, options ) # then - assert interface_one.getparam("delay") == delay - assert interface_one.getparam("bw") == bandwidth - assert interface_one.getparam("loss") == per - assert interface_one.getparam("duplicate") == dup - assert interface_one.getparam("jitter") == jitter - assert interface_two.getparam("delay") == delay - assert interface_two.getparam("bw") == bandwidth - assert interface_two.getparam("loss") == per - assert interface_two.getparam("duplicate") == dup - assert interface_two.getparam("jitter") == jitter + assert interface1.getparam("delay") == delay + assert interface1.getparam("bw") == bandwidth + assert interface1.getparam("loss") == per + assert interface1.getparam("duplicate") == dup + assert interface1.getparam("jitter") == jitter + assert interface2.getparam("delay") == delay + assert interface2.getparam("bw") == bandwidth + assert interface2.getparam("loss") == per + assert interface2.getparam("duplicate") == dup + assert interface2.getparam("jitter") == jitter def test_delete_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_one, interface_two) - assert node_one.netif(interface_one.id) - assert node_two.netif(interface_two.id) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface1_data, interface2_data) + assert node1.netif(interface1_data.id) + assert node2.netif(interface2_data.id) # when - session.delete_link( - node_one.id, node_two.id, interface_one.id, interface_two.id - ) + session.delete_link(node1.id, node2.id, interface1_data.id, interface2_data.id) # then - assert not node_one.netif(interface_one.id) - assert not node_two.netif(interface_two.id) + assert not node1.netif(interface1_data.id) + assert not node2.netif(interface2_data.id) def test_delete_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(CoreNode) - node_two = session.add_node(SwitchNode) - interface_one = ip_prefixes.create_interface(node_one) - session.add_link(node_one.id, node_two.id, interface_one) - assert node_one.netif(interface_one.id) + node1 = session.add_node(CoreNode) + node2 = session.add_node(SwitchNode) + interface1_data = ip_prefixes.create_interface(node1) + session.add_link(node1.id, node2.id, interface1_data) + assert node1.netif(interface1_data.id) # when - session.delete_link(node_one.id, node_two.id, interface_one_id=interface_one.id) + session.delete_link(node1.id, node2.id, interface1_id=interface1_data.id) # then - assert not node_one.netif(interface_one.id) + assert not node1.netif(interface1_data.id) def test_delete_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given - node_one = session.add_node(SwitchNode) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.id, node_two.id, interface_two=interface_two) - assert node_two.netif(interface_two.id) + node1 = session.add_node(SwitchNode) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) + session.add_link(node1.id, node2.id, interface2_data=interface2_data) + assert node2.netif(interface2_data.id) # when - session.delete_link(node_one.id, node_two.id, interface_two_id=interface_two.id) + session.delete_link(node1.id, node2.id, interface2_id=interface2_data.id) # then - assert not node_two.netif(interface_two.id) + assert not node2.netif(interface2_data.id) diff --git a/daemon/tests/test_services.py b/daemon/tests/test_services.py index e304a275..264a6566 100644 --- a/daemon/tests/test_services.py +++ b/daemon/tests/test_services.py @@ -206,23 +206,23 @@ class TestServices: # given ServiceManager.add_services(_SERVICES_PATH) my_service = ServiceManager.get(SERVICE_ONE) - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) file_name = my_service.configs[0] - file_data_one = "# custom file one" - file_data_two = "# custom file two" + file_data1 = "# custom file one" + file_data2 = "# custom file two" session.services.set_service_file( - node_one.id, my_service.name, file_name, file_data_one + node1.id, my_service.name, file_name, file_data1 ) session.services.set_service_file( - node_two.id, my_service.name, file_name, file_data_two + node2.id, my_service.name, file_name, file_data2 ) # when - custom_service_one = session.services.get_service(node_one.id, my_service.name) - session.services.create_service_files(node_one, custom_service_one) - custom_service_two = session.services.get_service(node_two.id, my_service.name) - session.services.create_service_files(node_two, custom_service_two) + custom_service1 = session.services.get_service(node1.id, my_service.name) + session.services.create_service_files(node1, custom_service1) + custom_service2 = session.services.get_service(node2.id, my_service.name) + session.services.create_service_files(node2, custom_service2) def test_service_import(self): """ diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index c40a9ef3..35e03f7d 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -68,20 +68,20 @@ class TestXml: ptp_node = session.add_node(PtpNet) # create nodes - node_one = session.add_node(CoreNode) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode) # link nodes to ptp net - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface_one=interface) + session.add_link(node.id, ptp_node.id, interface1_data=interface) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -97,16 +97,16 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) def test_xml_ptp_services( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -123,28 +123,28 @@ class TestXml: # create nodes options = NodeOptions(model="host") - node_one = session.add_node(CoreNode, options=options) - node_two = session.add_node(CoreNode) + node1 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode) # link nodes to ptp net - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface_one=interface) + session.add_link(node.id, ptp_node.id, interface1_data=interface) # set custom values for node service - session.services.set_service(node_one.id, SshService.name) + session.services.set_service(node1.id, SshService.name) service_file = SshService.configs[0] file_data = "# test" session.services.set_service_file( - node_one.id, SshService.name, service_file, file_data + node1.id, SshService.name, service_file, file_data ) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -160,19 +160,19 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) # retrieve custom service - service = session.services.get_service(node_one.id, SshService.name) + service = session.services.get_service(node1.id, SshService.name) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) assert service.config_data.get(service_file) == file_data def test_xml_mobility( @@ -192,21 +192,21 @@ class TestXml: # create nodes options = NodeOptions(model="mdr") options.set_position(0, 0) - node_one = session.add_node(CoreNode, options=options) - node_two = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, options=options) # link nodes - for node in [node_one, node_two]: + for node in [node1, node2]: interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface_one=interface) + session.add_link(node.id, wlan_node.id, interface1_data=interface) # instantiate session session.instantiate() # get ids for nodes wlan_id = wlan_node.id - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -222,9 +222,9 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) @@ -233,8 +233,8 @@ class TestXml: value = str(session.mobility.get_config("test", wlan_id, BasicRangeModel.name)) # verify nodes and configuration were restored - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) assert session.get_node(wlan_id, WlanNode) assert value == "1" @@ -246,18 +246,18 @@ class TestXml: :param tmpdir: tmpdir to create data in """ # create nodes - switch_one = session.add_node(SwitchNode) - switch_two = session.add_node(SwitchNode) + switch1 = session.add_node(SwitchNode) + switch2 = session.add_node(SwitchNode) # link nodes - session.add_link(switch_one.id, switch_two.id) + session.add_link(switch1.id, switch2.id) # instantiate session session.instantiate() # get ids for nodes - n1_id = switch_one.id - n2_id = switch_two.id + node1_id = switch1.id + node2_id = switch2.id # save xml xml_file = tmpdir.join("session.xml") @@ -273,19 +273,19 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, SwitchNode) + assert not session.get_node(node1_id, SwitchNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, SwitchNode) + assert not session.get_node(node2_id, SwitchNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - switch_one = session.get_node(n1_id, SwitchNode) - switch_two = session.get_node(n2_id, SwitchNode) - assert switch_one - assert switch_two - assert len(switch_one.all_link_data() + switch_two.all_link_data()) == 1 + switch1 = session.get_node(node1_id, SwitchNode) + switch2 = session.get_node(node2_id, SwitchNode) + assert switch1 + assert switch2 + assert len(switch1.all_link_data() + switch2.all_link_data()) == 1 def test_link_options( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -298,25 +298,25 @@ class TestXml: :param ip_prefixes: generates ip addresses for nodes """ # create nodes - node_one = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) + node1 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) switch = session.add_node(SwitchNode) # create link - link_options = LinkOptions() - link_options.per = 10.5 - link_options.bandwidth = 50000 - link_options.jitter = 10 - link_options.delay = 30 - link_options.dup = 5 - session.add_link(node_one.id, switch.id, interface_one, options=link_options) + options = LinkOptions() + options.per = 10.5 + options.bandwidth = 50000 + options.jitter = 10 + options.delay = 30 + options.dup = 5 + session.add_link(node1.id, switch.id, interface1_data, options=options) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = switch.id + node1_id = node1.id + node2_id = switch.id # save xml xml_file = tmpdir.join("session.xml") @@ -332,26 +332,26 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, SwitchNode) + assert not session.get_node(node2_id, SwitchNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, SwitchNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, SwitchNode) links = [] for node_id in session.nodes: node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert link_options.per == link.per - assert link_options.bandwidth == link.bandwidth - assert link_options.jitter == link.jitter - assert link_options.delay == link.delay - assert link_options.dup == link.dup + assert options.per == link.per + assert options.bandwidth == link.bandwidth + assert options.jitter == link.jitter + assert options.delay == link.delay + assert options.dup == link.dup def test_link_options_ptp( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -364,28 +364,26 @@ class TestXml: :param ip_prefixes: generates ip addresses for nodes """ # create nodes - node_one = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) + node1 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) # create link - link_options = LinkOptions() - link_options.per = 10.5 - link_options.bandwidth = 50000 - link_options.jitter = 10 - link_options.delay = 30 - link_options.dup = 5 - session.add_link( - node_one.id, node_two.id, interface_one, interface_two, link_options - ) + options = LinkOptions() + options.per = 10.5 + options.bandwidth = 50000 + options.jitter = 10 + options.delay = 30 + options.dup = 5 + session.add_link(node1.id, node2.id, interface1_data, interface2_data, options) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -401,26 +399,26 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) links = [] for node_id in session.nodes: node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert link_options.per == link.per - assert link_options.bandwidth == link.bandwidth - assert link_options.jitter == link.jitter - assert link_options.delay == link.delay - assert link_options.dup == link.dup + assert options.per == link.per + assert options.bandwidth == link.bandwidth + assert options.jitter == link.jitter + assert options.delay == link.delay + assert options.dup == link.dup def test_link_options_bidirectional( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -433,43 +431,37 @@ class TestXml: :param ip_prefixes: generates ip addresses for nodes """ # create nodes - node_one = session.add_node(CoreNode) - interface_one = ip_prefixes.create_interface(node_one) - node_two = session.add_node(CoreNode) - interface_two = ip_prefixes.create_interface(node_two) + node1 = session.add_node(CoreNode) + interface1_data = ip_prefixes.create_interface(node1) + node2 = session.add_node(CoreNode) + interface2_data = ip_prefixes.create_interface(node2) # create link - link_options_one = LinkOptions() - link_options_one.unidirectional = 1 - link_options_one.bandwidth = 5000 - link_options_one.delay = 10 - link_options_one.per = 10.5 - link_options_one.dup = 5 - link_options_one.jitter = 5 - session.add_link( - node_one.id, node_two.id, interface_one, interface_two, link_options_one - ) - link_options_two = LinkOptions() - link_options_two.unidirectional = 1 - link_options_two.bandwidth = 10000 - link_options_two.delay = 20 - link_options_two.per = 10 - link_options_two.dup = 10 - link_options_two.jitter = 10 + options1 = LinkOptions() + options1.unidirectional = 1 + options1.bandwidth = 5000 + options1.delay = 10 + options1.per = 10.5 + options1.dup = 5 + options1.jitter = 5 + session.add_link(node1.id, node2.id, interface1_data, interface2_data, options1) + options2 = LinkOptions() + options2.unidirectional = 1 + options2.bandwidth = 10000 + options2.delay = 20 + options2.per = 10 + options2.dup = 10 + options2.jitter = 10 session.update_link( - node_two.id, - node_one.id, - interface_two.id, - interface_one.id, - link_options_two, + node2.id, node1.id, interface2_data.id, interface1_data.id, options2 ) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.id - n2_id = node_two.id + node1_id = node1.id + node2_id = node2.id # save xml xml_file = tmpdir.join("session.xml") @@ -485,30 +477,30 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(CoreError): - assert not session.get_node(n1_id, CoreNode) + assert not session.get_node(node1_id, CoreNode) with pytest.raises(CoreError): - assert not session.get_node(n2_id, CoreNode) + assert not session.get_node(node2_id, CoreNode) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - assert session.get_node(n1_id, CoreNode) - assert session.get_node(n2_id, CoreNode) + assert session.get_node(node1_id, CoreNode) + assert session.get_node(node2_id, CoreNode) links = [] for node_id in session.nodes: node = session.nodes[node_id] links += node.all_link_data() assert len(links) == 2 - link_one = links[0] - link_two = links[1] - assert link_options_one.bandwidth == link_one.bandwidth - assert link_options_one.delay == link_one.delay - assert link_options_one.per == link_one.per - assert link_options_one.dup == link_one.dup - assert link_options_one.jitter == link_one.jitter - assert link_options_two.bandwidth == link_two.bandwidth - assert link_options_two.delay == link_two.delay - assert link_options_two.per == link_two.per - assert link_options_two.dup == link_two.dup - assert link_options_two.jitter == link_two.jitter + link1 = links[0] + link2 = links[1] + assert options1.bandwidth == link1.bandwidth + assert options1.delay == link1.delay + assert options1.per == link1.per + assert options1.dup == link1.dup + assert options1.jitter == link1.jitter + assert options2.bandwidth == link2.bandwidth + assert options2.delay == link2.delay + assert options2.per == link2.per + assert options2.dup == link2.dup + assert options2.jitter == link2.jitter diff --git a/docs/scripting.md b/docs/scripting.md index 8c1a705c..59bc02ae 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -62,7 +62,7 @@ def main(): for _ in range(NODES): node = session.add_node(CoreNode) interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface_one=interface) + session.add_link(node.id, switch.id, interface1_data=interface) # instantiate session session.instantiate() From 178d12b32761a1a5708b70cfe8948a09115aaf4a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 17:32:55 -0700 Subject: [PATCH 043/146] daemon: updated variables for InterfaceData to be denote data to make it more clear --- daemon/core/api/grpc/grpcutils.py | 6 +++--- daemon/core/emulator/emudata.py | 6 +++--- daemon/core/nodes/base.py | 20 ++++++++++---------- daemon/core/nodes/physical.py | 24 ++++++++++++++---------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 539face1..9a944bbe 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -59,13 +59,13 @@ def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData: :param interface_proto: interface proto :return: interface data """ - interface = None + interface_data = None if interface_proto: name = interface_proto.name if interface_proto.name else None mac = interface_proto.mac if interface_proto.mac else None ip4 = interface_proto.ip4 if interface_proto.ip4 else None ip6 = interface_proto.ip6 if interface_proto.ip6 else None - interface = InterfaceData( + interface_data = InterfaceData( id=interface_proto.id, name=name, mac=mac, @@ -74,7 +74,7 @@ def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData: ip6=ip6, ip6_mask=interface_proto.ip6mask, ) - return interface + return interface_data def add_link_data( diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index b6dbd57c..24b9495a 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -201,6 +201,6 @@ class IpPrefixes: generation :return: new interface data for the provided node """ - interface = self.gen_interface(node.id, name, mac) - interface.id = node.newifindex() - return interface + interface_data = self.gen_interface(node.id, name, mac) + interface_data.id = node.newifindex() + return interface_data diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 4b8d513b..37a41b81 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -475,13 +475,13 @@ class CoreNodeBase(NodeBase): raise NotImplementedError def newnetif( - self, net: "CoreNetworkBase", interface: InterfaceData + self, net: "CoreNetworkBase", interface_data: InterfaceData ) -> CoreInterface: """ Create a new network interface. :param net: network to associate with - :param interface: interface data for new interface + :param interface_data: interface data for new interface :return: interface index """ raise NotImplementedError @@ -860,34 +860,34 @@ class CoreNode(CoreNodeBase): self.node_net_client.device_up(interface_name) def newnetif( - self, net: "CoreNetworkBase", interface: InterfaceData + self, net: "CoreNetworkBase", interface_data: InterfaceData ) -> CoreInterface: """ Create a new network interface. :param net: network to associate with - :param interface: interface data for new interface + :param interface_data: interface data for new interface :return: interface index """ - addresses = interface.get_addresses() + addresses = interface_data.get_addresses() with self.lock: # TODO: emane specific code if net.is_emane is True: - ifindex = self.newtuntap(interface.id, interface.name) + ifindex = self.newtuntap(interface_data.id, interface_data.name) # TUN/TAP is not ready for addressing yet; the device may # take some time to appear, and installing it into a # namespace after it has been bound removes addressing; # save addresses with the interface now self.attachnet(ifindex, net) netif = self.netif(ifindex) - netif.sethwaddr(interface.mac) + netif.sethwaddr(interface_data.mac) for address in addresses: netif.addaddr(address) else: - ifindex = self.newveth(interface.id, interface.name) + ifindex = self.newveth(interface_data.id, interface_data.name) self.attachnet(ifindex, net) - if interface.mac: - self.sethwaddr(ifindex, interface.mac) + if interface_data.mac: + self.sethwaddr(ifindex, interface_data.mac) for address in addresses: self.addaddr(ifindex, address) self.ifup(ifindex) diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 6faa7824..a72ff128 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -157,25 +157,27 @@ class PhysicalNode(CoreNodeBase): self.ifindex += 1 return ifindex - def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface: + def newnetif( + self, net: CoreNetworkBase, interface_data: InterfaceData + ) -> CoreInterface: logging.info("creating interface") - addresses = interface.get_addresses() - ifindex = interface.id + addresses = interface_data.get_addresses() + ifindex = interface_data.id if ifindex is None: ifindex = self.newifindex() - name = interface.name + name = interface_data.name if name is None: name = f"gt{ifindex}" if self.up: # this is reached when this node is linked to a network node # tunnel to net not built yet, so build it now and adopt it _, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) - self.adoptnetif(remote_tap, ifindex, interface.mac, addresses) + self.adoptnetif(remote_tap, ifindex, interface_data.mac, addresses) return remote_tap else: # this is reached when configuring services (self.up=False) netif = GreTap(node=self, name=name, session=self.session, start=False) - self.adoptnetif(netif, ifindex, interface.mac, addresses) + self.adoptnetif(netif, ifindex, interface_data.mac, addresses) return netif def privatedir(self, path: str) -> None: @@ -297,19 +299,21 @@ class Rj45Node(CoreNodeBase): self.up = False self.restorestate() - def newnetif(self, net: CoreNetworkBase, interface: InterfaceData) -> CoreInterface: + def newnetif( + self, net: CoreNetworkBase, interface_data: InterfaceData + ) -> CoreInterface: """ This is called when linking with another node. Since this node represents an interface, we do not create another object here, but attach ourselves to the given network. :param net: new network instance - :param interface: interface data for new interface + :param interface_data: interface data for new interface :return: interface index :raises ValueError: when an interface has already been created, one max """ with self.lock: - ifindex = interface.id + ifindex = interface_data.id if ifindex is None: ifindex = 0 if self.interface.net is not None: @@ -318,7 +322,7 @@ class Rj45Node(CoreNodeBase): self.ifindex = ifindex if net is not None: self.interface.attachnet(net) - for addr in interface.get_addresses(): + for addr in interface_data.get_addresses(): self.addaddr(addr) return self.interface From 23d957679e5bf121607fff392c3213ce8b0637b3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 12 Jun 2020 20:22:51 -0700 Subject: [PATCH 044/146] daemon: Session cleanup, removed unused functions, used context managers for writing files, made variables used externally no longer private --- daemon/core/api/grpc/server.py | 4 +- daemon/core/api/tlv/corehandlers.py | 12 +- daemon/core/emane/emanemanager.py | 2 +- daemon/core/emulator/session.py | 277 +++++++-------------------- daemon/core/nodes/base.py | 2 +- daemon/core/plugins/sdt.py | 2 +- daemon/core/services/coreservices.py | 8 +- daemon/core/xml/corexml.py | 4 +- daemon/tests/test_grpc.py | 2 +- daemon/tests/test_gui.py | 4 +- daemon/tests/test_xml.py | 2 +- 11 files changed, 99 insertions(+), 220 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index a0ddf806..ca9e0133 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -930,8 +930,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("get hooks: %s", request) session = self.get_session(request.session_id, context) hooks = [] - for state in session._hooks: - state_hooks = session._hooks[state] + for state in session.hooks: + state_hooks = session.hooks[state] for file_name, file_data in state_hooks: hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data) hooks.append(hook) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index e7a67b3e..33222cf3 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -12,6 +12,7 @@ import threading import time from itertools import repeat from queue import Empty, Queue +from typing import Optional from core import utils from core.api.tlv import coreapi, dataconversion, structutils @@ -39,6 +40,7 @@ from core.emulator.enumerations import ( NodeTypes, RegisterTlvs, ) +from core.emulator.session import Session from core.errors import CoreCommandError, CoreError from core.location.mobility import BasicRangeModel from core.nodes.base import CoreNode, CoreNodeBase, NodeBase @@ -83,7 +85,7 @@ class CoreHandler(socketserver.BaseRequestHandler): thread.start() self.handler_threads.append(thread) - self.session = None + self.session: Optional[Session] = None self.coreemu = server.coreemu utils.close_onexec(request.fileno()) socketserver.BaseRequestHandler.__init__(self, request, client_address, server) @@ -176,7 +178,7 @@ class CoreHandler(socketserver.BaseRequestHandler): node_count_list.append(str(session.get_node_count())) - date_list.append(time.ctime(session._state_time)) + date_list.append(time.ctime(session.state_time)) thumb = session.thumbnail if not thumb: @@ -1819,7 +1821,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ # find all nodes and links links_data = [] - with self.session._nodes_lock: + with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] self.session.broadcast_node(node, MessageFlags.ADD) @@ -1897,8 +1899,8 @@ class CoreHandler(socketserver.BaseRequestHandler): # TODO: send location info # send hook scripts - for state in sorted(self.session._hooks.keys()): - for file_name, config_data in self.session._hooks[state]: + for state in sorted(self.session.hooks.keys()): + for file_name, config_data in self.session.hooks[state]: file_data = FileData( message_type=MessageFlags.ADD, name=str(file_name), diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 146d186f..cb978cb9 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -279,7 +279,7 @@ class EmaneManager(ModelManager): logging.debug("emane setup") # TODO: drive this from the session object - with self.session._nodes_lock: + with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] if isinstance(node, EmaneNet): diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 0a90b943..f3506048 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -6,7 +6,6 @@ that manages a CORE session. import logging import os import pwd -import random import shutil import subprocess import tempfile @@ -113,15 +112,13 @@ class Session: # dict of nodes: all nodes and nets self.nodes: Dict[int, NodeBase] = {} - self._nodes_lock = threading.Lock() + self.nodes_lock = threading.Lock() + # states and hooks handlers self.state: EventTypes = EventTypes.DEFINITION_STATE - self._state_time: float = time.monotonic() - self._state_file: str = os.path.join(self.session_dir, "state") - - # hooks handlers - self._hooks: Dict[EventTypes, Tuple[str, str]] = {} - self._state_hooks: Dict[EventTypes, Callable[[int], None]] = {} + self.state_time: float = time.monotonic() + self.hooks: Dict[EventTypes, Tuple[str, str]] = {} + self.state_hooks: Dict[EventTypes, List[Callable[[EventTypes], None]]] = {} self.add_state_hook( state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook ) @@ -154,15 +151,6 @@ class Session: self.emane: EmaneManager = EmaneManager(self) self.sdt: Sdt = Sdt(self) - # initialize default node services - self.services.default_services = { - "mdr": ("zebra", "OSPFv3MDR", "IPForward"), - "PC": ("DefaultRoute",), - "prouter": (), - "router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), - "host": ("DefaultRoute", "SSH"), - } - # config services self.service_manager: Optional[ConfigServiceManager] = None @@ -473,7 +461,7 @@ class Session: f"cannot update link node1({type(node1)}) node2({type(node2)})" ) - def _next_node_id(self) -> int: + def next_node_id(self) -> int: """ Find the next valid node id, starting from 1. @@ -506,7 +494,7 @@ class Session: # determine node id if not _id: - _id = self._next_node_id() + _id = self.next_node_id() # generate name if not provided if not options: @@ -692,7 +680,7 @@ class Session: "setting state hook: %s - %s source(%s)", state, file_name, source_name ) hook = file_name, data - state_hooks = self._hooks.setdefault(state, []) + state_hooks = self.hooks.setdefault(state, []) state_hooks.append(hook) # immediately run a hook if it is in the current state @@ -727,7 +715,7 @@ class Session: self.emane.shutdown() self.delete_nodes() self.distributed.shutdown() - self.del_hooks() + self.hooks.clear() self.emane.reset() self.emane.config_reset() self.location.reset() @@ -795,7 +783,6 @@ class Session: :param event_data: event data to send out :return: nothing """ - for handler in self.event_handlers: handler(event_data) @@ -806,7 +793,6 @@ class Session: :param exception_data: exception data to send out :return: nothing """ - for handler in self.exception_handlers: handler(exception_data) @@ -837,7 +823,6 @@ class Session: :param file_data: file data to send out :return: nothing """ - for handler in self.file_handlers: handler(file_data) @@ -848,7 +833,6 @@ class Session: :param config_data: config data to send out :return: nothing """ - for handler in self.config_handlers: handler(config_data) @@ -859,7 +843,6 @@ class Session: :param link_data: link data to send out :return: nothing """ - for handler in self.link_handlers: handler(link_data) @@ -871,22 +854,14 @@ class Session: :param send_event: if true, generate core API event messages :return: nothing """ - state_name = state.name if self.state == state: - logging.info( - "session(%s) is already in state: %s, skipping change", - self.id, - state_name, - ) return - self.state = state - self._state_time = time.monotonic() - logging.info("changing session(%s) to state %s", self.id, state_name) + self.state_time = time.monotonic() + logging.info("changing session(%s) to state %s", self.id, state.name) self.write_state(state) self.run_hooks(state) self.run_state_hooks(state) - if send_event: event_data = EventData(event_type=state, time=str(time.monotonic())) self.broadcast_event(event_data) @@ -898,10 +873,10 @@ class Session: :param state: state to write to file :return: nothing """ + state_file = os.path.join(self.session_dir, "state") try: - state_file = open(self._state_file, "w") - state_file.write(f"{state.value} {state.name}\n") - state_file.close() + with open(state_file, "w") as f: + f.write(f"{state.value} {state.name}\n") except IOError: logging.exception("error writing state file: %s", state.name) @@ -913,61 +888,10 @@ class Session: :param state: state to run hooks for :return: nothing """ - - # check that state change hooks exist - if state not in self._hooks: - return - - # retrieve all state hooks - hooks = self._hooks.get(state, []) - - # execute all state hooks - if hooks: - for hook in hooks: - self.run_hook(hook) - else: - logging.info("no state hooks for %s", state) - - def set_hook( - self, hook_type: str, file_name: str, source_name: str, data: str - ) -> None: - """ - Store a hook from a received file message. - - :param hook_type: hook type - :param file_name: file name for hook - :param source_name: source name - :param data: hook data - :return: nothing - """ - logging.info( - "setting state hook: %s - %s from %s", hook_type, file_name, source_name - ) - - _hook_id, state = hook_type.split(":")[:2] - if not state.isdigit(): - logging.error("error setting hook having state '%s'", state) - return - - state = int(state) - hook = file_name, data - - # append hook to current state hooks - state_hooks = self._hooks.setdefault(state, []) - state_hooks.append(hook) - - # immediately run a hook if it is in the current state - # (this allows hooks in the definition and configuration states) - if self.state == state: - logging.info("immediately running new state hook") + hooks = self.hooks.get(state, []) + for hook in hooks: self.run_hook(hook) - def del_hooks(self) -> None: - """ - Clear the hook scripts dict. - """ - self._hooks.clear() - def run_hook(self, hook: Tuple[str, str]) -> None: """ Run a hook. @@ -977,37 +901,23 @@ class Session: """ file_name, data = hook logging.info("running hook %s", file_name) - - # write data to hook file + file_path = os.path.join(self.session_dir, file_name) + log_path = os.path.join(self.session_dir, f"{file_name}.log") try: - hook_file = open(os.path.join(self.session_dir, file_name), "w") - hook_file.write(data) - hook_file.close() - except IOError: - logging.exception("error writing hook '%s'", file_name) - - # setup hook stdout and stderr - try: - stdout = open(os.path.join(self.session_dir, file_name + ".log"), "w") - stderr = subprocess.STDOUT - except IOError: - logging.exception("error setting up hook stderr and stdout") - stdout = None - stderr = None - - # execute hook file - try: - args = ["/bin/sh", file_name] - subprocess.check_call( - args, - stdout=stdout, - stderr=stderr, - close_fds=True, - cwd=self.session_dir, - env=self.get_environment(), - ) - except (OSError, subprocess.CalledProcessError): - logging.exception("error running hook: %s", file_name) + with open(file_path, "w") as f: + f.write(data) + with open(log_path, "w") as f: + args = ["/bin/sh", file_name] + subprocess.check_call( + args, + stdout=f, + stderr=subprocess.STDOUT, + close_fds=True, + cwd=self.session_dir, + env=self.get_environment(), + ) + except (IOError, subprocess.CalledProcessError): + logging.exception("error running hook: %s", file_path) def run_state_hooks(self, state: EventTypes) -> None: """ @@ -1016,17 +926,16 @@ class Session: :param state: state to run hooks for :return: nothing """ - for hook in self._state_hooks.get(state, []): - try: - hook(state) - except Exception: - message = ( - f"exception occured when running {state.name} state hook: {hook}" - ) - logging.exception(message) - self.exception( - ExceptionLevels.ERROR, "Session.run_state_hooks", message - ) + for hook in self.state_hooks.get(state, []): + self.run_state_hook(state, hook) + + def run_state_hook(self, state: EventTypes, hook: Callable[[EventTypes], None]): + try: + hook(state) + except Exception: + message = f"exception occurred when running {state.name} state hook: {hook}" + logging.exception(message) + self.exception(ExceptionLevels.ERROR, "Session.run_state_hooks", message) def add_state_hook( self, state: EventTypes, hook: Callable[[EventTypes], None] @@ -1038,15 +947,16 @@ class Session: :param hook: hook callback for the state :return: nothing """ - hooks = self._state_hooks.setdefault(state, []) + hooks = self.state_hooks.setdefault(state, []) if hook in hooks: raise CoreError("attempting to add duplicate state hook") hooks.append(hook) - if self.state == state: - hook(state) + self.run_state_hook(state, hook) - def del_state_hook(self, state: int, hook: Callable[[int], None]) -> None: + def del_state_hook( + self, state: EventTypes, hook: Callable[[EventTypes], None] + ) -> None: """ Delete a state hook. @@ -1054,24 +964,23 @@ class Session: :param hook: hook to delete :return: nothing """ - hooks = self._state_hooks.setdefault(state, []) - hooks.remove(hook) + hooks = self.state_hooks.get(state, []) + if hook in hooks: + hooks.remove(hook) - def runtime_state_hook(self, state: EventTypes) -> None: + def runtime_state_hook(self, _state: EventTypes) -> None: """ Runtime state hook check. - :param state: state to check + :param _state: state to check :return: nothing """ - if state == EventTypes.RUNTIME_STATE: - self.emane.poststartup() - - # create session deployed xml - xml_file_name = os.path.join(self.session_dir, "session-deployed.xml") - xml_writer = corexml.CoreXmlWriter(self) - corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario) - xml_writer.write(xml_file_name) + self.emane.poststartup() + # create session deployed xml + xml_file_name = os.path.join(self.session_dir, "session-deployed.xml") + xml_writer = corexml.CoreXmlWriter(self) + corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario) + xml_writer.write(xml_file_name) def get_environment(self, state: bool = True) -> Dict[str, str]: """ @@ -1090,10 +999,8 @@ class Session: env["SESSION_FILENAME"] = str(self.file_name) env["SESSION_USER"] = str(self.user) env["SESSION_NODE_COUNT"] = str(self.get_node_count()) - if state: env["SESSION_STATE"] = str(self.state) - # attempt to read and add environment config file environment_config_file = os.path.join(constants.CORE_CONF_DIR, "environment") try: @@ -1104,7 +1011,6 @@ class Session: "environment configuration file does not exist: %s", environment_config_file, ) - # attempt to read and add user environment file if self.user: environment_user_file = os.path.join( @@ -1117,7 +1023,6 @@ class Session: "user core environment settings file not present: %s", environment_user_file, ) - return env def set_thumbnail(self, thumb_file: str) -> None: @@ -1131,7 +1036,6 @@ class Session: logging.error("thumbnail file to set does not exist: %s", thumb_file) self.thumbnail = None return - destination_file = os.path.join(self.session_dir, os.path.basename(thumb_file)) shutil.copy(thumb_file, destination_file) self.thumbnail = destination_file @@ -1151,20 +1055,8 @@ class Session: os.chown(self.session_dir, uid, gid) except IOError: logging.exception("failed to set permission on %s", self.session_dir) - self.user = user - def get_node_id(self) -> int: - """ - Return a unique, new node id. - """ - with self._nodes_lock: - while True: - node_id = random.randint(1, 0xFFFF) - if node_id not in self.nodes: - break - return node_id - def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT: """ Create an emulation node. @@ -1176,7 +1068,7 @@ class Session: :raises core.CoreError: when id of the node to create already exists """ node = _class(self, *args, **kwargs) - with self._nodes_lock: + with self.nodes_lock: if node.id in self.nodes: node.shutdown() raise CoreError(f"duplicate node id {node.id} for {node.name}") @@ -1192,9 +1084,9 @@ class Session: :return: node for the given id :raises core.CoreError: when node does not exist """ - if _id not in self.nodes: + node = self.nodes.get(_id) + if node is None: raise CoreError(f"unknown node id {_id}") - node = self.nodes[_id] if not isinstance(node, _class): actual = node.__class__.__name__ expected = _class.__name__ @@ -1210,7 +1102,7 @@ class Session: """ # delete node and check for session shutdown if a node was removed node = None - with self._nodes_lock: + with self.nodes_lock: if _id in self.nodes: node = self.nodes.pop(_id) logging.info("deleted node(%s)", node.name) @@ -1224,7 +1116,7 @@ class Session: """ Clear the nodes dictionary, and call shutdown for each node. """ - with self._nodes_lock: + with self.nodes_lock: funcs = [] while self.nodes: _, node = self.nodes.popitem() @@ -1237,29 +1129,15 @@ class Session: Write nodes to a 'nodes' file in the session dir. The 'nodes' file lists: number, name, api-type, class-type """ + file_path = os.path.join(self.session_dir, "nodes") try: - with self._nodes_lock: - file_path = os.path.join(self.session_dir, "nodes") + with self.nodes_lock: with open(file_path, "w") as f: - for _id in self.nodes.keys(): - node = self.nodes[_id] + for _id, node in self.nodes.items(): f.write(f"{_id} {node.name} {node.apitype} {type(node)}\n") except IOError: logging.exception("error writing nodes file") - def dump_session(self) -> None: - """ - Log information about the session in its current state. - """ - logging.info("session id=%s name=%s state=%s", self.id, self.name, self.state) - logging.info( - "file=%s thumbnail=%s node_count=%s/%s", - self.file_name, - self.thumbnail, - self.get_node_count(), - len(self.nodes), - ) - def exception( self, level: ExceptionLevels, source: str, text: str, node_id: int = None ) -> None: @@ -1327,17 +1205,15 @@ class Session: :return: created node count """ - with self._nodes_lock: + with self.nodes_lock: count = 0 - for node_id in self.nodes: - node = self.nodes[node_id] + for node in self.nodes.values(): is_p2p_ctrlnet = isinstance(node, (PtpNet, CtrlNet)) is_tap = isinstance(node, GreTapBridge) and not isinstance( node, TunnelNode ) if is_p2p_ctrlnet or is_tap: continue - count += 1 return count @@ -1359,7 +1235,6 @@ class Session: if self.state == EventTypes.RUNTIME_STATE: logging.info("valid runtime state found, returning") return - # start event loop and set to runtime self.event_loop.run() self.set_state(EventTypes.RUNTIME_STATE, send_event=True) @@ -1375,7 +1250,7 @@ class Session: self.event_loop.stop() # stop node services - with self._nodes_lock: + with self.nodes_lock: funcs = [] for node_id in self.nodes: node = self.nodes[node_id] @@ -1447,7 +1322,7 @@ class Session: :return: service boot exceptions """ - with self._nodes_lock: + with self.nodes_lock: funcs = [] start = time.monotonic() for _id in self.nodes: @@ -1545,7 +1420,6 @@ class Session: else: prefix_spec = CtrlNet.DEFAULT_PREFIX_LIST[net_index] logging.debug("prefix spec: %s", prefix_spec) - server_interface = self.get_control_net_server_interfaces()[net_index] # return any existing controlnet bridge @@ -1685,7 +1559,7 @@ class Session: if not in runtime. """ if self.state == EventTypes.RUNTIME_STATE: - return time.monotonic() - self._state_time + return time.monotonic() - self.state_time else: return 0.0 @@ -1708,7 +1582,6 @@ class Session: """ event_time = float(event_time) current_time = self.runtime() - if current_time > 0: if event_time <= current_time: logging.warning( @@ -1718,11 +1591,9 @@ class Session: ) return event_time = event_time - current_time - self.event_loop.add_event( event_time, self.run_event, node=node, name=name, data=data ) - if not name: name = "" logging.info( @@ -1732,8 +1603,6 @@ class Session: data, ) - # TODO: if data is None, this blows up, but this ties into how event functions - # are ran, need to clean that up def run_event( self, node_id: int = None, name: str = None, data: str = None ) -> None: @@ -1745,10 +1614,12 @@ class Session: :param data: event data :return: nothing """ + if data is None: + logging.warning("no data for event node(%s) name(%s)", node_id, name) + return now = self.runtime() if not name: name = "" - logging.info("running event %s at time %s cmd=%s", name, now, data) if not node_id: utils.mute_detach(data) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 37a41b81..66ea41a0 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -63,7 +63,7 @@ class NodeBase: self.session: "Session" = session if _id is None: - _id = session.get_node_id() + _id = session.next_node_id() self.id: int = _id if name is None: name = f"o{self.id}" diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 062217cb..04fff3e4 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -215,7 +215,7 @@ class Sdt: for layer in CORE_LAYERS: self.cmd(f"layer {layer}") - with self.session._nodes_lock: + with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] if isinstance(node, CoreNetworkBase): diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 491113ff..391b53d1 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -325,7 +325,13 @@ class CoreServices: """ self.session = session # dict of default services tuples, key is node type - self.default_services = {} + self.default_services = { + "mdr": ("zebra", "OSPFv3MDR", "IPForward"), + "PC": ("DefaultRoute",), + "prouter": (), + "router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), + "host": ("DefaultRoute", "SSH"), + } # dict of node ids to dict of custom services by name self.custom_services = {} diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index afc1d826..45e8d9c5 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -320,8 +320,8 @@ class CoreXmlWriter: def write_session_hooks(self) -> None: # hook scripts hooks = etree.Element("session_hooks") - for state in sorted(self.session._hooks, key=lambda x: x.value): - for file_name, data in self.session._hooks[state]: + for state in sorted(self.session.hooks, key=lambda x: x.value): + for file_name, data in self.session.hooks[state]: hook = etree.SubElement(hooks, "hook") add_attribute(hook, "name", file_name) add_attribute(hook, "state", state.value) diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 131af93d..8beb4b9a 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -133,7 +133,7 @@ class TestGrpc: assert wlan_node.id in session.nodes assert session.nodes[node1.id].netif(0) is not None assert session.nodes[node2.id].netif(0) is not None - hook_file, hook_data = session._hooks[EventTypes.RUNTIME_STATE][0] + hook_file, hook_data = session.hooks[EventTypes.RUNTIME_STATE][0] assert hook_file == hook.file assert hook_data == hook.data assert session.location.refxyz == (location_x, location_y, location_z) diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 1187b4d7..d3b9362d 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -382,7 +382,7 @@ class TestGui: def test_file_hook_add(self, coretlv: CoreHandler): state = EventTypes.DATACOLLECT_STATE - assert coretlv.session._hooks.get(state) is None + assert coretlv.session.hooks.get(state) is None file_name = "test.sh" file_data = "echo hello" message = coreapi.CoreFileMessage.create( @@ -396,7 +396,7 @@ class TestGui: coretlv.handle_message(message) - hooks = coretlv.session._hooks.get(state) + hooks = coretlv.session.hooks.get(state) assert len(hooks) == 1 name, data = hooks[0] assert file_name == name diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 35e03f7d..b28e0986 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -48,7 +48,7 @@ class TestXml: session.open_xml(file_path, start=True) # verify nodes have been recreated - runtime_hooks = session._hooks.get(state) + runtime_hooks = session.hooks.get(state) assert runtime_hooks runtime_hook = runtime_hooks[0] assert file_name == runtime_hook[0] From e18ffaafce70b5c467d03ad11c6777e17197097c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 17:41:13 -0700 Subject: [PATCH 045/146] daemon: xml files will now write and read loss, but fallback to looking for per for compatibility --- daemon/core/xml/corexml.py | 42 ++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 820f1cea..2b15aa5a 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -569,7 +569,7 @@ class CoreXmlWriter: options = etree.Element("options") add_attribute(options, "delay", link_data.delay) add_attribute(options, "bandwidth", link_data.bandwidth) - add_attribute(options, "per", link_data.loss) + add_attribute(options, "loss", link_data.loss) add_attribute(options, "dup", link_data.dup) add_attribute(options, "jitter", link_data.jitter) add_attribute(options, "mer", link_data.mer) @@ -947,37 +947,39 @@ class CoreXmlReader: interface_two = create_interface_data(interface_two_element) options_element = link_element.find("options") - link_options = LinkOptions() + options = LinkOptions() if options_element is not None: - link_options.bandwidth = get_int(options_element, "bandwidth") - link_options.burst = get_int(options_element, "burst") - link_options.delay = get_int(options_element, "delay") - link_options.dup = get_int(options_element, "dup") - link_options.mer = get_int(options_element, "mer") - link_options.mburst = get_int(options_element, "mburst") - link_options.jitter = get_int(options_element, "jitter") - link_options.key = get_int(options_element, "key") - link_options.loss = get_float(options_element, "per") - link_options.unidirectional = get_int(options_element, "unidirectional") - link_options.session = options_element.get("session") - link_options.emulation_id = get_int(options_element, "emulation_id") - link_options.network_id = get_int(options_element, "network_id") - link_options.opaque = options_element.get("opaque") - link_options.gui_attributes = options_element.get("gui_attributes") + options.bandwidth = get_int(options_element, "bandwidth") + options.burst = get_int(options_element, "burst") + options.delay = get_int(options_element, "delay") + options.dup = get_int(options_element, "dup") + options.mer = get_int(options_element, "mer") + options.mburst = get_int(options_element, "mburst") + options.jitter = get_int(options_element, "jitter") + options.key = get_int(options_element, "key") + options.loss = get_float(options_element, "loss") + if options.loss is None: + options.loss = get_float(options_element, "per") + options.unidirectional = get_int(options_element, "unidirectional") + options.session = options_element.get("session") + options.emulation_id = get_int(options_element, "emulation_id") + options.network_id = get_int(options_element, "network_id") + options.opaque = options_element.get("opaque") + options.gui_attributes = options_element.get("gui_attributes") - if link_options.unidirectional == 1 and node_set in node_sets: + if options.unidirectional == 1 and node_set in node_sets: logging.info( "updating link node_one(%s) node_two(%s)", node_one, node_two ) self.session.update_link( - node_one, node_two, interface_one.id, interface_two.id, link_options + node_one, node_two, interface_one.id, interface_two.id, options ) else: logging.info( "adding link node_one(%s) node_two(%s)", node_one, node_two ) self.session.add_link( - node_one, node_two, interface_one, interface_two, link_options + node_one, node_two, interface_one, interface_two, options ) node_sets.add(node_set) From 5df2e36083acaec721d3ef6b78fe2110c6d411e2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 21:48:51 -0700 Subject: [PATCH 046/146] daemon: fixed session.add_event parameter to be specific to node_id --- daemon/core/api/tlv/corehandlers.py | 32 ++++++++++++++--------------- daemon/core/emulator/session.py | 11 +++------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 562f8c89..2cd7bfac 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -809,38 +809,38 @@ class CoreHandler(socketserver.BaseRequestHandler): :param core.api.tlv.coreapi.CoreExecMessage message: execute message to handle :return: reply messages """ - node_num = message.get_tlv(ExecuteTlvs.NODE.value) + node_id = message.get_tlv(ExecuteTlvs.NODE.value) execute_num = message.get_tlv(ExecuteTlvs.NUMBER.value) execute_time = message.get_tlv(ExecuteTlvs.TIME.value) command = message.get_tlv(ExecuteTlvs.COMMAND.value) # local flag indicates command executed locally, not on a node - if node_num is None and not message.flags & MessageFlags.LOCAL.value: + if node_id is None and not message.flags & MessageFlags.LOCAL.value: raise ValueError("Execute Message is missing node number.") if execute_num is None: raise ValueError("Execute Message is missing execution number.") if execute_time is not None: - self.session.add_event(execute_time, node=node_num, name=None, data=command) + self.session.add_event( + float(execute_time), node_id=node_id, name=None, data=command + ) return () try: - node = self.session.get_node(node_num, CoreNodeBase) + node = self.session.get_node(node_id, CoreNodeBase) # build common TLV items for reply tlv_data = b"" - if node_num is not None: - tlv_data += coreapi.CoreExecuteTlv.pack( - ExecuteTlvs.NODE.value, node_num - ) + if node_id is not None: + tlv_data += coreapi.CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node_id) tlv_data += coreapi.CoreExecuteTlv.pack( ExecuteTlvs.NUMBER.value, execute_num ) tlv_data += coreapi.CoreExecuteTlv.pack(ExecuteTlvs.COMMAND.value, command) if message.flags & MessageFlags.TTY.value: - if node_num is None: + if node_id is None: raise NotImplementedError # echo back exec message with cmd for spawning interactive terminal if command == "bash": @@ -850,7 +850,6 @@ class CoreHandler(socketserver.BaseRequestHandler): reply = coreapi.CoreExecMessage.pack(MessageFlags.TTY.value, tlv_data) return (reply,) else: - logging.info("execute message with cmd=%s", command) # execute command and send a response if ( message.flags & MessageFlags.STRING.value @@ -870,7 +869,6 @@ class CoreHandler(socketserver.BaseRequestHandler): except CoreCommandError as e: res = e.stderr status = e.returncode - logging.info("done exec cmd=%s with status=%d", command, status) if message.flags & MessageFlags.TEXT.value: tlv_data += coreapi.CoreExecuteTlv.pack( ExecuteTlvs.RESULT.value, res @@ -888,7 +886,7 @@ class CoreHandler(socketserver.BaseRequestHandler): else: node.cmd(command, wait=False) except CoreError: - logging.exception("error getting object: %s", node_num) + logging.exception("error getting object: %s", node_id) # XXX wait and queue this message to try again later # XXX maybe this should be done differently if not message.flags & MessageFlags.LOCAL.value: @@ -1549,11 +1547,11 @@ class CoreHandler(socketserver.BaseRequestHandler): if event_type == EventTypes.INSTANTIATION_STATE and isinstance( node, WlanNode ): - self.session.start_mobility(node_ids=(node.id,)) + self.session.start_mobility(node_ids=[node.id]) return () logging.warning( - "dropping unhandled event message for node: %s", node_id + "dropping unhandled event message for node: %s", node.name ) return () self.session.set_state(event_type) @@ -1611,14 +1609,16 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.save_xml(filename) elif event_type == EventTypes.SCHEDULED: etime = event_data.time - node = event_data.node + node_id = event_data.node name = event_data.name data = event_data.data if etime is None: logging.warning("Event message scheduled event missing start time") return () if message.flags & MessageFlags.ADD.value: - self.session.add_event(float(etime), node=node, name=name, data=data) + self.session.add_event( + float(etime), node_id=node_id, name=name, data=data + ) else: raise NotImplementedError diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index f3506048..2225bb6f 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1564,23 +1564,18 @@ class Session: return 0.0 def add_event( - self, - event_time: float, - node: CoreNode = None, - name: str = None, - data: str = None, + self, event_time: float, node_id: int = None, name: str = None, data: str = None ) -> None: """ Add an event to the event queue, with a start time relative to the start of the runtime state. :param event_time: event time - :param node: node to add event for + :param node_id: node to add event for :param name: name of event :param data: data for event :return: nothing """ - event_time = float(event_time) current_time = self.runtime() if current_time > 0: if event_time <= current_time: @@ -1592,7 +1587,7 @@ class Session: return event_time = event_time - current_time self.event_loop.add_event( - event_time, self.run_event, node=node, name=name, data=data + event_time, self.run_event, node_id=node_id, name=name, data=data ) if not name: name = "" From 8d48393525094387f7598d192e21a68cbdabfae0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 21:53:09 -0700 Subject: [PATCH 047/146] daemon: updated usage of if1/2 to be consistent with interface1/2 for now --- daemon/core/nodes/network.py | 54 +++++++++++++++++------------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 8ac1939e..9b46dfd5 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -898,16 +898,16 @@ class PtpNet(CoreNetwork): if len(self._netif) != 2: return all_links - if1, if2 = self._netif.values() + interface1, interface2 = self._netif.values() unidirectional = 0 - if if1.getparams() != if2.getparams(): + if interface1.getparams() != interface2.getparams(): unidirectional = 1 interface1_ip4 = None interface1_ip4_mask = None interface1_ip6 = None interface1_ip6_mask = None - for address in if1.addrlist: + for address in interface1.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): @@ -921,7 +921,7 @@ class PtpNet(CoreNetwork): interface2_ip4_mask = None interface2_ip6 = None interface2_ip6_mask = None - for address in if2.addrlist: + for address in interface2.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): @@ -933,31 +933,30 @@ class PtpNet(CoreNetwork): link_data = LinkData( message_type=flags, - node1_id=if1.node.id, - node2_id=if2.node.id, + node1_id=interface1.node.id, + node2_id=interface2.node.id, link_type=self.linktype, unidirectional=unidirectional, - delay=if1.getparam("delay"), - bandwidth=if1.getparam("bw"), - loss=if1.getparam("loss"), - dup=if1.getparam("duplicate"), - jitter=if1.getparam("jitter"), - interface1_id=if1.node.getifindex(if1), - interface1_name=if1.name, - interface1_mac=if1.hwaddr, + delay=interface1.getparam("delay"), + bandwidth=interface1.getparam("bw"), + loss=interface1.getparam("loss"), + dup=interface1.getparam("duplicate"), + jitter=interface1.getparam("jitter"), + interface1_id=interface1.node.getifindex(interface1), + interface1_name=interface1.name, + interface1_mac=interface1.hwaddr, interface1_ip4=interface1_ip4, interface1_ip4_mask=interface1_ip4_mask, interface1_ip6=interface1_ip6, interface1_ip6_mask=interface1_ip6_mask, - interface2_id=if2.node.getifindex(if2), - interface2_name=if2.name, - interface2_mac=if2.hwaddr, + interface2_id=interface2.node.getifindex(interface2), + interface2_name=interface2.name, + interface2_mac=interface2.hwaddr, interface2_ip4=interface2_ip4, interface2_ip4_mask=interface2_ip4_mask, interface2_ip6=interface2_ip6, interface2_ip6_mask=interface2_ip6_mask, ) - all_links.append(link_data) # build a 2nd link message for the upstream link parameters @@ -966,19 +965,18 @@ class PtpNet(CoreNetwork): link_data = LinkData( message_type=MessageFlags.NONE, link_type=self.linktype, - node1_id=if2.node.id, - node2_id=if1.node.id, - delay=if2.getparam("delay"), - bandwidth=if2.getparam("bw"), - loss=if2.getparam("loss"), - dup=if2.getparam("duplicate"), - jitter=if2.getparam("jitter"), + node1_id=interface2.node.id, + node2_id=interface1.node.id, + delay=interface2.getparam("delay"), + bandwidth=interface2.getparam("bw"), + loss=interface2.getparam("loss"), + dup=interface2.getparam("duplicate"), + jitter=interface2.getparam("jitter"), unidirectional=1, - interface1_id=if2.node.getifindex(if2), - interface2_id=if1.node.getifindex(if1), + interface1_id=interface2.node.getifindex(interface2), + interface2_id=interface1.node.getifindex(interface1), ) all_links.append(link_data) - return all_links From 91f1f7f004dba90735b735753a492b8e193424a8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 22:01:07 -0700 Subject: [PATCH 048/146] daemon: added global type hinting to core.emulator.session and core.api.grpc.server --- daemon/core/api/grpc/server.py | 6 +++--- daemon/core/emulator/session.py | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 3374df2e..8b349b67 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -6,7 +6,7 @@ import tempfile import threading import time from concurrent import futures -from typing import Iterable, Optional, Type +from typing import Iterable, Optional, Pattern, Type import grpc from grpc import ServicerContext @@ -118,8 +118,8 @@ from core.nodes.base import CoreNode, CoreNodeBase, NodeBase from core.nodes.network import WlanNode from core.services.coreservices import ServiceManager -_ONE_DAY_IN_SECONDS = 60 * 60 * 24 -_INTERFACE_REGEX = re.compile(r"veth(?P[0-9a-fA-F]+)") +_ONE_DAY_IN_SECONDS: int = 60 * 60 * 24 +_INTERFACE_REGEX: Pattern = re.compile(r"veth(?P[0-9a-fA-F]+)") class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 2225bb6f..53f5156d 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -11,7 +11,7 @@ import subprocess import tempfile import threading import time -from typing import Any, Callable, Dict, List, Optional, Tuple, Type, TypeVar +from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar from core import constants, utils from core.configservice.manager import ConfigServiceManager @@ -59,7 +59,7 @@ from core.xml import corexml, corexmldeployment from core.xml.corexml import CoreXmlReader, CoreXmlWriter # maps for converting from API call node type values to classes and vice versa -NODES = { +NODES: Dict[NodeTypes, Type[NodeBase]] = { NodeTypes.DEFAULT: CoreNode, NodeTypes.PHYSICAL: PhysicalNode, NodeTypes.SWITCH: SwitchNode, @@ -74,11 +74,11 @@ NODES = { NodeTypes.DOCKER: DockerNode, NodeTypes.LXC: LxcNode, } -NODES_TYPE = {NODES[x]: x for x in NODES} -CONTAINER_NODES = {DockerNode, LxcNode} -CTRL_NET_ID = 9001 -LINK_COLORS = ["green", "blue", "orange", "purple", "turquoise"] -NT = TypeVar("NT", bound=NodeBase) +NODES_TYPE: Dict[Type[NodeBase], NodeTypes] = {NODES[x]: x for x in NODES} +CONTAINER_NODES: Set[Type[NodeBase]] = {DockerNode, LxcNode} +CTRL_NET_ID: int = 9001 +LINK_COLORS: List[str] = ["green", "blue", "orange", "purple", "turquoise"] +NT: TypeVar = TypeVar("NT", bound=NodeBase) class Session: From d94bae6b42aabddd92d42a99a1fa7b4a9f4cab6b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 22:25:38 -0700 Subject: [PATCH 049/146] daemon: added class variable type hinting to core.services.coreservices --- daemon/core/services/coreservices.py | 83 ++++++++++++++-------------- 1 file changed, 42 insertions(+), 41 deletions(-) diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 391b53d1..d22bc7a5 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -10,7 +10,7 @@ services. import enum import logging import time -from typing import TYPE_CHECKING, Iterable, List, Tuple, Type +from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple, Type from core import utils from core.constants import which @@ -36,14 +36,15 @@ class ServiceMode(enum.Enum): class ServiceDependencies: """ Can generate boot paths for services, based on their dependencies. Will validate - 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: List[Type["CoreService"]]) -> None: + def __init__(self, services: List["CoreService"]) -> None: # helpers to check validity - self.dependents = {} - self.booted = set() - self.node_services = {} + self.dependents: Dict[str, Set[str]] = {} + self.booted: Set[str] = set() + self.node_services: Dict[str, "CoreService"] = {} for service in services: self.node_services[service.name] = service for dependency in service.dependencies: @@ -51,9 +52,9 @@ class ServiceDependencies: dependents.add(service.name) # used to find paths - self.path = [] - self.visited = set() - self.visiting = set() + self.path: List["CoreService"] = [] + self.visited: Set[str] = set() + self.visiting: Set[str] = set() def boot_paths(self) -> List[List["CoreService"]]: """ @@ -131,7 +132,7 @@ class ServiceDependencies: class ServiceShim: - keys = [ + keys: List[str] = [ "dirs", "files", "startidx", @@ -241,10 +242,10 @@ class ServiceManager: Manages services available for CORE nodes to use. """ - services = {} + services: Dict[str, Type["CoreService"]] = {} @classmethod - def add(cls, service: "CoreService") -> None: + def add(cls, service: Type["CoreService"]) -> None: """ Add a service to manager. @@ -314,8 +315,8 @@ class CoreServices: custom service configuration. A CoreService is not a Configurable. """ - name = "services" - config_type = RegisterTlvs.UTILITY + name: str = "services" + config_type: RegisterTlvs = RegisterTlvs.UTILITY def __init__(self, session: "Session") -> None: """ @@ -323,17 +324,17 @@ class CoreServices: :param session: session this manager is tied to """ - self.session = session + self.session: "Session" = session # dict of default services tuples, key is node type - self.default_services = { - "mdr": ("zebra", "OSPFv3MDR", "IPForward"), - "PC": ("DefaultRoute",), - "prouter": (), - "router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), - "host": ("DefaultRoute", "SSH"), + self.default_services: Dict[str, List[str]] = { + "mdr": ["zebra", "OSPFv3MDR", "IPForward"], + "PC": ["DefaultRoute"], + "prouter": [], + "router": ["zebra", "OSPFv2", "OSPFv3", "IPForward"], + "host": ["DefaultRoute", "SSH"], } # dict of node ids to dict of custom services by name - self.custom_services = {} + self.custom_services: Dict[int, Dict[str, "CoreService"]] = {} def reset(self) -> None: """ @@ -425,7 +426,7 @@ class CoreServices: continue node.services.append(service) - def all_configs(self) -> List[Tuple[int, Type["CoreService"]]]: + def all_configs(self) -> List[Tuple[int, "CoreService"]]: """ Return (node_id, service) tuples for all stored configs. Used when reconnecting to a session or opening XML. @@ -808,50 +809,50 @@ class CoreService: """ # service name should not include spaces - name = None + name: Optional[str] = None # executables that must exist for service to run - executables = () + executables: Tuple[str, ...] = () # sets service requirements that must be started prior to this service starting - dependencies = () + dependencies: Tuple[str, ...] = () # group string allows grouping services together - group = None + group: Optional[str] = None # private, per-node directories required by this service - dirs = () + dirs: Tuple[str, ...] = () # config files written by this service - configs = () + configs: Tuple[str, ...] = () # config file data - config_data = {} + config_data: Dict[str, str] = {} # list of startup commands - startup = () + startup: Tuple[str, ...] = () # list of shutdown commands - shutdown = () + shutdown: Tuple[str, ...] = () # list of validate commands - validate = () + validate: Tuple[str, ...] = () # validation mode, used to determine startup success - validation_mode = ServiceMode.NON_BLOCKING + validation_mode: ServiceMode = ServiceMode.NON_BLOCKING # time to wait in seconds for determining if service started successfully - validation_timer = 5 + validation_timer: int = 5 # validation period in seconds, how frequent validation is attempted - validation_period = 0.5 + validation_period: float = 0.5 # metadata associated with this service - meta = None + meta: Optional[str] = None # custom configuration text - custom = False - custom_needed = False + custom: bool = False + custom_needed: bool = False def __init__(self) -> None: """ @@ -859,8 +860,8 @@ class CoreService: against their config. Services are instantiated when a custom configuration is used to override their default parameters. """ - self.custom = True - self.config_data = self.__class__.config_data.copy() + self.custom: bool = True + self.config_data: Dict[str, str] = self.__class__.config_data.copy() @classmethod def on_load(cls) -> None: From 8587da062161130c9ab81b072548c79a7e860b5d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 13 Jun 2020 23:50:08 -0700 Subject: [PATCH 050/146] daemon: moved node instantiation into lock to guarantee id uniqueness, removed node count from environment as it also attmpts to use lock and wouldnt be accurate either --- daemon/core/emulator/session.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 53f5156d..826e3f0a 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -998,7 +998,6 @@ class Session: env["SESSION_NAME"] = str(self.name) env["SESSION_FILENAME"] = str(self.file_name) env["SESSION_USER"] = str(self.user) - env["SESSION_NODE_COUNT"] = str(self.get_node_count()) if state: env["SESSION_STATE"] = str(self.state) # attempt to read and add environment config file @@ -1067,8 +1066,8 @@ class Session: :return: the created node instance :raises core.CoreError: when id of the node to create already exists """ - node = _class(self, *args, **kwargs) with self.nodes_lock: + node = _class(self, *args, **kwargs) if node.id in self.nodes: node.shutdown() raise CoreError(f"duplicate node id {node.id} for {node.name}") From 3243a69afa891a23d75fcd149b2cc0d1b98261fb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 00:46:11 -0700 Subject: [PATCH 051/146] daemon: updated xml files to use node1 and interface1 instead of node_one and interface_one, will still fallback to parse old names --- daemon/core/xml/corexml.py | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 831ffae6..759de680 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -45,11 +45,11 @@ def get_type(element: etree.Element, name: str, _type: Generic[T]) -> Optional[T return value -def get_float(element: etree.Element, name: str) -> float: +def get_float(element: etree.Element, name: str) -> Optional[float]: return get_type(element, name, float) -def get_int(element: etree.Element, name: str) -> int: +def get_int(element: etree.Element, name: str) -> Optional[int]: return get_type(element, name, int) @@ -529,13 +529,13 @@ class CoreXmlWriter: def create_link_element(self, link_data: LinkData) -> etree.Element: link_element = etree.Element("link") - add_attribute(link_element, "node_one", link_data.node1_id) - add_attribute(link_element, "node_two", link_data.node2_id) + add_attribute(link_element, "node1", link_data.node1_id) + add_attribute(link_element, "node2", link_data.node2_id) # check for interface one if link_data.interface1_id is not None: interface1 = self.create_interface_element( - "interface_one", + "interface1", link_data.node1_id, link_data.interface1_id, link_data.interface1_mac, @@ -549,7 +549,7 @@ class CoreXmlWriter: # check for interface two if link_data.interface2_id is not None: interface2 = self.create_interface_element( - "interface_two", + "interface2", link_data.node2_id, link_data.interface2_id, link_data.interface2_mac, @@ -932,16 +932,24 @@ class CoreXmlReader: node_sets = set() for link_element in link_elements.iterchildren(): - node1_id = get_int(link_element, "node_one") - node2_id = get_int(link_element, "node_two") + node1_id = get_int(link_element, "node1") + if node1_id is None: + node1_id = get_int(link_element, "node_one") + node2_id = get_int(link_element, "node2") + if node2_id is None: + node2_id = get_int(link_element, "node_two") node_set = frozenset((node1_id, node2_id)) - interface1_element = link_element.find("interface_one") + interface1_element = link_element.find("interface1") + if interface1_element is None: + interface1_element = link_element.find("interface_one") interface1_data = None if interface1_element is not None: interface1_data = create_interface_data(interface1_element) - interface2_element = link_element.find("interface_two") + interface2_element = link_element.find("interface2") + if interface2_element is None: + interface2_element = link_element.find("interface_two") interface2_data = None if interface2_element is not None: interface2_data = create_interface_data(interface2_element) From c4c667bb741fbd2027eedd61c61ddb35c38ca414 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 09:37:58 -0700 Subject: [PATCH 052/146] daemon: removed node.startup from inside constructor, session is now responsible, providing more control and avoiding issues when using super calls where you dont want to start just yet --- daemon/core/emane/nodes.py | 3 +-- daemon/core/emulator/session.py | 14 ++++++++++---- daemon/core/nodes/base.py | 26 ++++++++------------------ daemon/core/nodes/docker.py | 4 +--- daemon/core/nodes/lxd.py | 4 +--- daemon/core/nodes/network.py | 20 +++++--------------- daemon/core/nodes/physical.py | 15 ++++----------- 7 files changed, 30 insertions(+), 56 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index e88cb194..68c1bc05 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -55,10 +55,9 @@ class EmaneNet(CoreNetworkBase): 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, server) self.conf: str = "" self.nemidmap: Dict[CoreInterface, int] = {} self.model: "OptionalEmaneModel" = None diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 826e3f0a..e63c30c7 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -257,7 +257,7 @@ class Session: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): logging.info("linking ptp: %s - %s", node1.name, node2.name) start = self.state.should_start() - ptp = self.create_node(PtpNet, start=start) + ptp = self.create_node(PtpNet, start) interface1 = node1.newnetif(ptp, interface1_data) interface2 = node2.newnetif(ptp, interface2_data) ptp.linkconfig(interface1, options) @@ -517,10 +517,10 @@ class Session: name, start, ) - kwargs = dict(_id=_id, name=name, start=start, server=server) + kwargs = dict(_id=_id, name=name, server=server) if _class in CONTAINER_NODES: kwargs["image"] = options.image - node = self.create_node(_class, **kwargs) + node = self.create_node(_class, start, **kwargs) # set node attributes node.icon = options.icon @@ -1056,11 +1056,14 @@ class Session: logging.exception("failed to set permission on %s", self.session_dir) self.user = user - def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT: + def create_node( + self, _class: Type[NT], start: bool, *args: Any, **kwargs: Any + ) -> NT: """ Create an emulation node. :param _class: node class to create + :param start: True to start node, False otherwise :param args: list of arguments for the class to create :param kwargs: dictionary of arguments for the class to create :return: the created node instance @@ -1072,6 +1075,8 @@ class Session: node.shutdown() raise CoreError(f"duplicate node id {node.id} for {node.name}") self.nodes[node.id] = node + if start: + node.startup() return node def get_node(self, _id: int, _class: Type[NT]) -> NT: @@ -1464,6 +1469,7 @@ class Session: ) control_net = self.create_node( CtrlNet, + True, prefix, _id=_id, updown_script=updown_script, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index cd77f857..49fe7620 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -7,7 +7,7 @@ import os import shutil import threading from threading import RLock -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type import netaddr @@ -47,7 +47,6 @@ class NodeBase: session: "Session", _id: int = None, name: str = None, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -56,7 +55,6 @@ class NodeBase: :param session: CORE session object :param _id: id :param name: object name - :param start: start value :param server: remote server node will run on, default is None for localhost """ @@ -254,7 +252,6 @@ class CoreNodeBase(NodeBase): session: "Session", _id: int = None, name: str = None, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -263,11 +260,10 @@ class CoreNodeBase(NodeBase): :param session: CORE session object :param _id: object id :param name: object name - :param start: boolean for starting :param server: remote server node will run on, default is None for localhost """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) self.config_services: Dict[str, "ConfigService"] = {} self.nodedir: Optional[str] = None self.tmpnodedir: bool = False @@ -492,8 +488,8 @@ class CoreNode(CoreNodeBase): Provides standard core node logic. """ - apitype = NodeTypes.DEFAULT - valid_address_types = {"inet", "inet6", "inet6link"} + apitype: NodeTypes = NodeTypes.DEFAULT + valid_address_types: Set[str] = {"inet", "inet6", "inet6link"} def __init__( self, @@ -501,7 +497,6 @@ class CoreNode(CoreNodeBase): _id: int = None, name: str = None, nodedir: str = None, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -511,11 +506,10 @@ class CoreNode(CoreNodeBase): :param _id: object id :param name: object name :param nodedir: node directory - :param start: start flag :param server: remote server node will run on, default is None for localhost """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) self.nodedir: Optional[str] = nodedir self.ctrlchnlname: str = os.path.abspath( os.path.join(self.session.session_dir, self.name) @@ -526,8 +520,6 @@ class CoreNode(CoreNodeBase): self._mounts: List[Tuple[str, str]] = [] use_ovs = session.options.get_config("ovs") == "True" self.node_net_client: LinuxNetClient = self.create_node_net_client(use_ovs) - if start: - self.startup() def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: """ @@ -981,15 +973,14 @@ class CoreNetworkBase(NodeBase): Base class for networks """ - linktype = LinkTypes.WIRED - is_emane = False + linktype: LinkTypes = LinkTypes.WIRED + is_emane: bool = False def __init__( self, session: "Session", _id: int, name: str, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -998,11 +989,10 @@ class CoreNetworkBase(NodeBase): :param session: CORE session object :param _id: object id :param name: object name - :param start: should object start :param server: remote server node will run on, default is None for localhost """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) self.brname = None self._linked = {} self._linked_lock = threading.Lock() diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index fa4b8f8b..e911db74 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -77,7 +77,6 @@ class DockerNode(CoreNode): _id: int = None, name: str = None, nodedir: str = None, - start: bool = True, server: DistributedServer = None, image: str = None ) -> None: @@ -88,7 +87,6 @@ class DockerNode(CoreNode): :param _id: object id :param name: object name :param nodedir: node directory - :param start: start flag :param server: remote server node will run on, default is None for localhost :param image: image to start container with @@ -96,7 +94,7 @@ class DockerNode(CoreNode): if image is None: image = "ubuntu" self.image: str = image - super().__init__(session, _id, name, nodedir, start, server) + super().__init__(session, _id, name, nodedir, server) def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: """ diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index af906f01..a66791ce 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -74,7 +74,6 @@ class LxcNode(CoreNode): _id: int = None, name: str = None, nodedir: str = None, - start: bool = True, server: DistributedServer = None, image: str = None, ) -> None: @@ -85,7 +84,6 @@ class LxcNode(CoreNode): :param _id: object id :param name: object name :param nodedir: node directory - :param start: start flag :param server: remote server node will run on, default is None for localhost :param image: image to start container with @@ -93,7 +91,7 @@ class LxcNode(CoreNode): if image is None: image = "ubuntu" self.image: str = image - super().__init__(session, _id, name, nodedir, start, server) + super().__init__(session, _id, name, nodedir, server) def alive(self) -> bool: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 9b46dfd5..b85c2eee 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -264,7 +264,6 @@ class CoreNetwork(CoreNetworkBase): session: "Session", _id: int = None, name: str = None, - start: bool = True, server: "DistributedServer" = None, policy: NetworkPolicy = None, ) -> None: @@ -274,12 +273,11 @@ class CoreNetwork(CoreNetworkBase): :param session: core session instance :param _id: object id :param name: object name - :param start: start flag :param server: remote server node will run on, default is None for localhost :param policy: network policy """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) if name is None: name = str(self.id) if policy is not None: @@ -288,9 +286,6 @@ class CoreNetwork(CoreNetworkBase): sessionid = self.session.short_session_id() self.brname: str = f"b.{self.id}.{sessionid}" self.has_ebtables_chain: bool = False - if start: - self.startup() - ebq.startupdateloop(self) def host_cmd( self, @@ -327,6 +322,7 @@ class CoreNetwork(CoreNetworkBase): self.net_client.create_bridge(self.brname) self.has_ebtables_chain = False self.up = True + ebq.startupdateloop(self) def shutdown(self) -> None: """ @@ -610,7 +606,6 @@ class GreTapBridge(CoreNetwork): localip: str = None, ttl: int = 255, key: int = None, - start: bool = True, server: "DistributedServer" = None, ) -> None: """ @@ -628,7 +623,7 @@ class GreTapBridge(CoreNetwork): :param server: remote server node will run on, default is None for localhost """ - CoreNetwork.__init__(self, session, _id, name, False, server, policy) + CoreNetwork.__init__(self, session, _id, name, server, policy) if key is None: key = self.session.id ^ self.id self.grekey: int = key @@ -647,8 +642,6 @@ class GreTapBridge(CoreNetwork): ttl=ttl, key=self.grekey, ) - if start: - self.startup() def startup(self) -> None: """ @@ -734,7 +727,6 @@ class CtrlNet(CoreNetwork): _id: int = None, name: str = None, hostid: int = None, - start: bool = True, server: "DistributedServer" = None, assign_address: bool = True, updown_script: str = None, @@ -748,7 +740,6 @@ class CtrlNet(CoreNetwork): :param name: node namee :param prefix: control network ipv4 prefix :param hostid: host id - :param start: start flag :param server: remote server node will run on, default is None for localhost :param assign_address: assigned address @@ -761,7 +752,7 @@ class CtrlNet(CoreNetwork): self.assign_address: bool = assign_address self.updown_script: Optional[str] = updown_script self.serverintf: Optional[str] = serverintf - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) def add_addresses(self, index: int) -> None: """ @@ -1025,7 +1016,6 @@ class WlanNode(CoreNetwork): session: "Session", _id: int = None, name: str = None, - start: bool = True, server: "DistributedServer" = None, policy: NetworkPolicy = None, ) -> None: @@ -1040,7 +1030,7 @@ class WlanNode(CoreNetwork): will run on, default is None for localhost :param policy: wlan policy """ - super().__init__(session, _id, name, start, server, policy) + super().__init__(session, _id, name, server, policy) # wireless and mobility models (BasicRangeModel, Ns2WaypointMobility) self.model: Optional[WirelessModel] = None self.mobility: Optional[WayPointMobility] = None diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index a72ff128..a2e39d49 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -28,22 +28,19 @@ class PhysicalNode(CoreNodeBase): _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, server) if not self.server: raise CoreError("physical nodes must be assigned to a remote server") self.nodedir: Optional[str] = nodedir - self.up: bool = start self.lock: threading.RLock = threading.RLock() self._mounts: List[Tuple[str, str]] = [] - if start: - self.startup() def startup(self) -> None: with self.lock: self.makenodedir() + self.up = True def shutdown(self) -> None: if not self.up: @@ -144,7 +141,7 @@ class PhysicalNode(CoreNodeBase): """ Apply tc queing disciplines using linkconfig. """ - linux_bridge = CoreNetwork(session=self.session, start=False) + linux_bridge = CoreNetwork(self.session) linux_bridge.up = True linux_bridge.linkconfig(netif, options, netif2) del linux_bridge @@ -244,7 +241,6 @@ class Rj45Node(CoreNodeBase): _id: int = None, name: str = None, mtu: int = 1500, - start: bool = True, server: DistributedServer = None, ) -> None: """ @@ -254,19 +250,16 @@ class Rj45Node(CoreNodeBase): :param _id: node id :param name: node name :param mtu: rj45 mtu - :param start: start flag :param server: remote server node will run on, default is None for localhost """ - super().__init__(session, _id, name, start, server) + super().__init__(session, _id, name, server) self.interface = CoreInterface(session, self, name, name, mtu, server) self.interface.transport_type = TransportType.RAW self.lock: threading.RLock = threading.RLock() self.ifindex: Optional[int] = None self.old_up: bool = False self.old_addrs: List[Tuple[str, Optional[str]]] = [] - if start: - self.startup() def startup(self) -> None: """ From cf4194889410c1faab5d04ca307a85f4f9b4f656 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 12:36:07 -0700 Subject: [PATCH 053/146] daemon: fixed error with EmaneNet startup throwing an error, updated Rj45Node and PhysicalNode to implement all abstract methods --- daemon/core/emane/nodes.py | 3 +++ daemon/core/nodes/physical.py | 26 ++++++++++++++++---------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 68c1bc05..8383cdd1 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -76,6 +76,9 @@ class EmaneNet(CoreNetworkBase): def config(self, conf: str) -> None: self.conf = conf + def startup(self) -> None: + pass + def shutdown(self) -> None: pass diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index a2e39d49..13214093 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -217,14 +217,17 @@ class PhysicalNode(CoreNodeBase): return open(hostfilename, mode) def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: - with self.opennodefile(filename, "w") as node_file: - node_file.write(contents) - os.chmod(node_file.name, mode) - logging.info("created nodefile: '%s'; mode: 0%o", node_file.name, mode) + with self.opennodefile(filename, "w") as f: + f.write(contents) + os.chmod(f.name, mode) + logging.info("created nodefile: '%s'; mode: 0%o", f.name, mode) def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: return self.host_cmd(args, wait=wait) + def addfile(self, srcname: str, filename: str) -> None: + raise NotImplementedError + class Rj45Node(CoreNodeBase): """ @@ -446,10 +449,13 @@ class Rj45Node(CoreNodeBase): self.interface.setposition() def termcmdstring(self, sh: str) -> str: - """ - Create a terminal command string. - - :param sh: shell to execute command in - :return: str - """ + raise NotImplementedError + + def addfile(self, srcname: str, filename: str) -> None: + raise NotImplementedError + + def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: + raise NotImplementedError + + def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: raise NotImplementedError From f5916fab5b041c3dcb20ba8589abd4f7e6e698df Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 12:44:51 -0700 Subject: [PATCH 054/146] daemon: added not implemented methods to CoreNodeBase --- daemon/core/nodes/base.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 49fe7620..3e9dfe7a 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -41,7 +41,6 @@ class NodeBase: apitype: Optional[NodeTypes] = None - # TODO: appears start has no usage, verify and remove def __init__( self, session: "Session", @@ -268,6 +267,12 @@ class CoreNodeBase(NodeBase): self.nodedir: Optional[str] = None self.tmpnodedir: bool = False + def startup(self) -> None: + raise NotImplementedError + + def shutdown(self) -> None: + raise NotImplementedError + def add_config_service(self, service_class: "ConfigServiceType") -> None: """ Adds a configuration service to the node. From 0462c1b0841bebdc0516d885344b3c52a3a03096 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 14 Jun 2020 13:35:06 -0700 Subject: [PATCH 055/146] daemon: added usage of ABC to NodeBase, CoreNodeBase, and CoreNetworkBase to help enforce accounting for abstract functions --- daemon/core/emane/nodes.py | 3 + daemon/core/nodes/base.py | 155 ++++++++++++++++++---------------- daemon/core/nodes/physical.py | 10 +-- 3 files changed, 92 insertions(+), 76 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 8383cdd1..c4c3428b 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -88,6 +88,9 @@ class EmaneNet(CoreNetworkBase): def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None: pass + def linknet(self, net: "CoreNetworkBase") -> CoreInterface: + raise CoreError("emane networks cannot be linked to other networks") + def updatemodel(self, config: Dict[str, str]) -> None: if not self.model: raise CoreError(f"no model set to update for node({self.name})") diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 3e9dfe7a..6c7ebcf0 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1,7 +1,7 @@ """ Defines the base logic for nodes used within core. """ - +import abc import logging import os import shutil @@ -34,7 +34,7 @@ if TYPE_CHECKING: _DEFAULT_MTU = 1500 -class NodeBase: +class NodeBase(abc.ABC): """ Base class for CORE nodes (nodes and networks) """ @@ -78,6 +78,7 @@ class NodeBase: use_ovs = session.options.get_config("ovs") == "True" self.net_client: LinuxNetClient = get_net_client(use_ovs, self.host_cmd) + @abc.abstractmethod def startup(self) -> None: """ Each object implements its own startup method. @@ -86,6 +87,7 @@ class NodeBase: """ raise NotImplementedError + @abc.abstractmethod def shutdown(self) -> None: """ Each object implements its own shutdown method. @@ -267,12 +269,74 @@ class CoreNodeBase(NodeBase): self.nodedir: Optional[str] = None self.tmpnodedir: bool = False + @abc.abstractmethod def startup(self) -> None: raise NotImplementedError + @abc.abstractmethod def shutdown(self) -> None: raise NotImplementedError + @abc.abstractmethod + def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: + """ + Create a node file with a given mode. + + :param filename: name of file to create + :param contents: contents of file + :param mode: mode for file + :return: nothing + """ + raise NotImplementedError + + @abc.abstractmethod + def addfile(self, srcname: str, filename: str) -> None: + """ + Add a file. + + :param srcname: source file name + :param filename: file name to add + :return: nothing + :raises CoreCommandError: when a non-zero exit status occurs + """ + raise NotImplementedError + + @abc.abstractmethod + def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: + """ + Runs a command within a node container. + + :param args: command to run + :param wait: True to wait for status, False otherwise + :param shell: True to use shell, False otherwise + :return: combined stdout and stderr + :raises CoreCommandError: when a non-zero exit status occurs + """ + raise NotImplementedError + + @abc.abstractmethod + def termcmdstring(self, sh: str) -> str: + """ + Create a terminal command string. + + :param sh: shell to execute command in + :return: str + """ + raise NotImplementedError + + @abc.abstractmethod + def newnetif( + self, net: "CoreNetworkBase", interface_data: InterfaceData + ) -> CoreInterface: + """ + Create a new network interface. + + :param net: network to associate with + :param interface_data: interface data for new interface + :return: interface index + """ + raise NotImplementedError + def add_config_service(self, service_class: "ConfigServiceType") -> None: """ Adds a configuration service to the node. @@ -432,61 +496,6 @@ class CoreNodeBase(NodeBase): common.append((netif1.net, netif1, netif2)) return common - def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: - """ - Create a node file with a given mode. - - :param filename: name of file to create - :param contents: contents of file - :param mode: mode for file - :return: nothing - """ - raise NotImplementedError - - def addfile(self, srcname: str, filename: str) -> None: - """ - Add a file. - - :param srcname: source file name - :param filename: file name to add - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - raise NotImplementedError - - def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: - """ - Runs a command within a node container. - - :param args: command to run - :param wait: True to wait for status, False otherwise - :param shell: True to use shell, False otherwise - :return: combined stdout and stderr - :raises CoreCommandError: when a non-zero exit status occurs - """ - raise NotImplementedError - - def termcmdstring(self, sh: str) -> str: - """ - Create a terminal command string. - - :param sh: shell to execute command in - :return: str - """ - raise NotImplementedError - - def newnetif( - self, net: "CoreNetworkBase", interface_data: InterfaceData - ) -> CoreInterface: - """ - Create a new network interface. - - :param net: network to associate with - :param interface_data: interface data for new interface - :return: interface index - """ - raise NotImplementedError - class CoreNode(CoreNodeBase): """ @@ -1002,6 +1011,7 @@ class CoreNetworkBase(NodeBase): self._linked = {} self._linked_lock = threading.Lock() + @abc.abstractmethod def startup(self) -> None: """ Each object implements its own startup method. @@ -1010,6 +1020,7 @@ class CoreNetworkBase(NodeBase): """ raise NotImplementedError + @abc.abstractmethod def shutdown(self) -> None: """ Each object implements its own shutdown method. @@ -1018,6 +1029,7 @@ class CoreNetworkBase(NodeBase): """ raise NotImplementedError + @abc.abstractmethod def linknet(self, net: "CoreNetworkBase") -> CoreInterface: """ Link network to another. @@ -1025,7 +1037,21 @@ class CoreNetworkBase(NodeBase): :param net: network to link with :return: created interface """ - pass + raise NotImplementedError + + @abc.abstractmethod + def linkconfig( + self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + ) -> None: + """ + Configure link parameters by applying tc queuing disciplines on the interface. + + :param netif: interface one + :param options: options for configuring link + :param netif2: interface two + :return: nothing + """ + raise NotImplementedError def getlinknetif(self, net: "CoreNetworkBase") -> Optional[CoreInterface]: """ @@ -1156,19 +1182,6 @@ class CoreNetworkBase(NodeBase): return all_links - def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None - ) -> None: - """ - Configure link parameters by applying tc queuing disciplines on the interface. - - :param netif: interface one - :param options: options for configuring link - :param netif2: interface two - :return: nothing - """ - raise NotImplementedError - class Position: """ diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 13214093..741fe7d5 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -226,7 +226,7 @@ class PhysicalNode(CoreNodeBase): return self.host_cmd(args, wait=wait) def addfile(self, srcname: str, filename: str) -> None: - raise NotImplementedError + raise CoreError("physical node does not support addfile") class Rj45Node(CoreNodeBase): @@ -449,13 +449,13 @@ class Rj45Node(CoreNodeBase): self.interface.setposition() def termcmdstring(self, sh: str) -> str: - raise NotImplementedError + raise CoreError("rj45 does not support terminal commands") def addfile(self, srcname: str, filename: str) -> None: - raise NotImplementedError + raise CoreError("rj45 does not support addfile") def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: - raise NotImplementedError + raise CoreError("rj45 does not support nodefile") def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: - raise NotImplementedError + raise CoreError("rj45 does not support cmds") From 0725199d6d2f9f5420628201861e0e78b64bd4b7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 09:30:16 -0700 Subject: [PATCH 056/146] initial sweeping changes to call all usages of various interface related variables and functions (netif, interface, if, ifc, etc) to use a consistent name iface --- daemon/core/api/grpc/client.py | 72 ++-- daemon/core/api/grpc/events.py | 2 +- daemon/core/api/grpc/grpcutils.py | 134 +++--- daemon/core/api/grpc/server.py | 117 +++--- daemon/core/api/tlv/coreapi.py | 26 +- daemon/core/api/tlv/corehandlers.py | 88 ++-- daemon/core/api/tlv/dataconversion.py | 2 +- daemon/core/api/tlv/enumerations.py | 26 +- .../configservices/frrservices/services.py | 88 ++-- .../frrservices/templates/frr.conf | 6 +- .../frrservices/templates/frrboot.sh | 6 +- .../configservices/nrlservices/services.py | 42 +- .../nrlservices/templates/nrlnhdp.sh | 4 +- .../nrlservices/templates/nrlolsrv2.sh | 4 +- .../nrlservices/templates/olsrd.sh | 4 +- .../nrlservices/templates/startsmf.sh | 4 +- .../configservices/quaggaservices/services.py | 98 ++--- .../quaggaservices/templates/Quagga.conf | 6 +- .../sercurityservices/services.py | 12 +- .../configservices/utilservices/services.py | 64 ++- .../utilservices/templates/index.html | 4 +- daemon/core/emane/commeffect.py | 18 +- daemon/core/emane/emanemanager.py | 84 ++-- daemon/core/emane/emanemodel.py | 28 +- daemon/core/emane/linkmonitor.py | 6 +- daemon/core/emane/nodes.py | 80 ++-- daemon/core/emulator/data.py | 30 +- daemon/core/emulator/distributed.py | 4 +- daemon/core/emulator/emudata.py | 10 +- daemon/core/emulator/session.py | 184 ++++----- daemon/core/gui/coreclient.py | 117 +++--- daemon/core/gui/dialogs/emaneconfig.py | 10 +- daemon/core/gui/dialogs/ipdialog.py | 2 +- daemon/core/gui/dialogs/linkconfig.py | 36 +- daemon/core/gui/dialogs/macdialog.py | 2 +- daemon/core/gui/dialogs/nodeconfig.py | 76 ++-- daemon/core/gui/graph/edges.py | 26 +- daemon/core/gui/graph/graph.py | 84 ++-- daemon/core/gui/graph/node.py | 14 +- daemon/core/gui/interface.py | 64 +-- daemon/core/gui/menubar.py | 2 +- daemon/core/location/mobility.py | 127 +++--- daemon/core/nodes/base.py | 388 ++++++++---------- daemon/core/nodes/docker.py | 2 +- daemon/core/nodes/interface.py | 13 +- daemon/core/nodes/lxd.py | 6 +- daemon/core/nodes/netclient.py | 34 +- daemon/core/nodes/network.py | 282 ++++++------- daemon/core/nodes/physical.py | 193 ++++----- daemon/core/services/bird.py | 34 +- daemon/core/services/emaneservices.py | 6 +- daemon/core/services/frr.py | 144 +++---- daemon/core/services/nrl.py | 58 ++- daemon/core/services/quagga.py | 138 +++---- daemon/core/services/sdn.py | 24 +- daemon/core/services/security.py | 18 +- daemon/core/services/utility.py | 62 ++- daemon/core/services/xorp.py | 100 ++--- daemon/core/xml/corexml.py | 118 +++--- daemon/core/xml/corexmldeployment.py | 36 +- daemon/core/xml/emanexml.py | 76 ++-- daemon/examples/configservices/testing.py | 8 +- daemon/examples/docker/docker2core.py | 4 +- daemon/examples/docker/docker2docker.py | 4 +- daemon/examples/docker/switch.py | 6 +- daemon/examples/grpc/distributed_switch.py | 4 +- daemon/examples/grpc/emane80211.py | 4 +- daemon/examples/grpc/switch.py | 4 +- daemon/examples/grpc/wlan.py | 4 +- daemon/examples/lxd/lxd2core.py | 4 +- daemon/examples/lxd/lxd2lxd.py | 4 +- daemon/examples/lxd/switch.py | 6 +- daemon/examples/myservices/sample.py | 4 +- daemon/examples/python/distributed_emane.py | 8 +- daemon/examples/python/distributed_lxd.py | 4 +- daemon/examples/python/distributed_ptp.py | 4 +- daemon/examples/python/distributed_switch.py | 8 +- daemon/examples/python/emane80211.py | 4 +- daemon/examples/python/switch.py | 4 +- daemon/examples/python/switch_inject.py | 4 +- daemon/examples/python/wlan.py | 4 +- daemon/proto/core/api/grpc/core.proto | 26 +- daemon/proto/core/api/grpc/emane.proto | 10 +- daemon/scripts/core-route-monitor | 4 +- daemon/tests/conftest.py | 2 +- daemon/tests/emane/test_emane.py | 8 +- daemon/tests/test_core.py | 44 +- daemon/tests/test_grpc.py | 60 +-- daemon/tests/test_gui.py | 80 ++-- daemon/tests/test_links.py | 174 ++++---- daemon/tests/test_nodes.py | 28 +- daemon/tests/test_xml.py | 30 +- docs/scripting.md | 4 +- 93 files changed, 1955 insertions(+), 2156 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 3a16d4fd..47aaef63 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -110,27 +110,27 @@ class InterfaceHelper: """ self.prefixes: IpPrefixes = IpPrefixes(ip4_prefix, ip6_prefix) - def create_interface( - self, node_id: int, interface_id: int, name: str = None, mac: str = None + def create_iface( + self, node_id: int, iface_id: int, name: str = None, mac: str = None ) -> core_pb2.Interface: """ Create an interface protobuf object. :param node_id: node id to create interface for - :param interface_id: interface id + :param iface_id: interface id :param name: name of interface :param mac: mac address for interface :return: interface protobuf """ - interface_data = self.prefixes.gen_interface(node_id, name, mac) + iface_data = self.prefixes.gen_iface(node_id, name, mac) return core_pb2.Interface( - id=interface_id, - name=interface_data.name, - ip4=interface_data.ip4, - ip4mask=interface_data.ip4_mask, - ip6=interface_data.ip6, - ip6mask=interface_data.ip6_mask, - mac=interface_data.mac, + id=iface_id, + name=iface_data.name, + ip4=iface_data.ip4, + ip4mask=iface_data.ip4_mask, + ip6=iface_data.ip6, + ip6mask=iface_data.ip6_mask, + mac=iface_data.mac, ) @@ -611,8 +611,8 @@ class CoreGrpcClient: session_id: int, node1_id: int, node2_id: int, - interface1: core_pb2.Interface = None, - interface2: core_pb2.Interface = None, + iface1: core_pb2.Interface = None, + iface2: core_pb2.Interface = None, options: core_pb2.LinkOptions = None, ) -> core_pb2.AddLinkResponse: """ @@ -621,8 +621,8 @@ class CoreGrpcClient: :param session_id: session id :param node1_id: node one id :param node2_id: node two id - :param interface1: node one interface data - :param interface2: node two interface data + :param iface1: node one interface data + :param iface2: node two interface data :param options: options for link (jitter, bandwidth, etc) :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist @@ -631,8 +631,8 @@ class CoreGrpcClient: node1_id=node1_id, node2_id=node2_id, type=core_pb2.LinkType.WIRED, - interface1=interface1, - interface2=interface2, + iface1=iface1, + iface2=iface2, options=options, ) request = core_pb2.AddLinkRequest(session_id=session_id, link=link) @@ -644,8 +644,8 @@ class CoreGrpcClient: node1_id: int, node2_id: int, options: core_pb2.LinkOptions, - interface1_id: int = None, - interface2_id: int = None, + iface1_id: int = None, + iface2_id: int = None, ) -> core_pb2.EditLinkResponse: """ Edit a link between nodes. @@ -654,8 +654,8 @@ class CoreGrpcClient: :param node1_id: node one id :param node2_id: node two id :param options: options for link (jitter, bandwidth, etc) - :param interface1_id: node one interface id - :param interface2_id: node two interface id + :param iface1_id: node one interface id + :param iface2_id: node two interface id :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ @@ -664,8 +664,8 @@ class CoreGrpcClient: node1_id=node1_id, node2_id=node2_id, options=options, - interface1_id=interface1_id, - interface2_id=interface2_id, + iface1_id=iface1_id, + iface2_id=iface2_id, ) return self.stub.EditLink(request) @@ -674,8 +674,8 @@ class CoreGrpcClient: session_id: int, node1_id: int, node2_id: int, - interface1_id: int = None, - interface2_id: int = None, + iface1_id: int = None, + iface2_id: int = None, ) -> core_pb2.DeleteLinkResponse: """ Delete a link between nodes. @@ -683,8 +683,8 @@ class CoreGrpcClient: :param session_id: session id :param node1_id: node one id :param node2_id: node two id - :param interface1_id: node one interface id - :param interface2_id: node two interface id + :param iface1_id: node one interface id + :param iface2_id: node two interface id :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ @@ -692,8 +692,8 @@ class CoreGrpcClient: session_id=session_id, node1_id=node1_id, node2_id=node2_id, - interface1_id=interface1_id, - interface2_id=interface2_id, + iface1_id=iface1_id, + iface2_id=iface2_id, ) return self.stub.DeleteLink(request) @@ -1028,7 +1028,7 @@ class CoreGrpcClient: return self.stub.GetEmaneModels(request) def get_emane_model_config( - self, session_id: int, node_id: int, model: str, interface_id: int = -1 + self, session_id: int, node_id: int, model: str, iface_id: int = -1 ) -> GetEmaneModelConfigResponse: """ Get emane model configuration for a node or a node's interface. @@ -1036,12 +1036,12 @@ class CoreGrpcClient: :param session_id: session id :param node_id: node id :param model: emane model name - :param interface_id: node interface id + :param iface_id: node interface id :return: response with a list of configuration groups :raises grpc.RpcError: when session doesn't exist """ request = GetEmaneModelConfigRequest( - session_id=session_id, node_id=node_id, model=model, interface=interface_id + session_id=session_id, node_id=node_id, model=model, iface_id=iface_id ) return self.stub.GetEmaneModelConfig(request) @@ -1051,7 +1051,7 @@ class CoreGrpcClient: node_id: int, model: str, config: Dict[str, str] = None, - interface_id: int = -1, + iface_id: int = -1, ) -> SetEmaneModelConfigResponse: """ Set emane model configuration for a node or a node's interface. @@ -1060,12 +1060,12 @@ class CoreGrpcClient: :param node_id: node id :param model: emane model name :param config: emane model configuration - :param interface_id: node interface id + :param iface_id: node interface id :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ model_config = EmaneModelConfig( - node_id=node_id, model=model, config=config, interface_id=interface_id + node_id=node_id, model=model, config=config, iface_id=iface_id ) request = SetEmaneModelConfigRequest( session_id=session_id, emane_model_config=model_config @@ -1128,7 +1128,7 @@ class CoreGrpcClient: ) return self.stub.EmaneLink(request) - def get_interfaces(self) -> core_pb2.GetInterfacesResponse: + def get_ifaces(self) -> core_pb2.GetInterfacesResponse: """ Retrieves a list of interfaces available on the host machine that are not a part of a CORE session. diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 82cf1eac..ff65142d 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -82,7 +82,7 @@ def handle_config_event(event: ConfigData) -> core_pb2.ConfigEvent: data_values=event.data_values, possible_values=event.possible_values, groups=event.groups, - interface=event.interface_number, + iface_id=event.iface_id, network_id=event.network_id, opaque=event.opaque, data_types=event.data_types, diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index c9b76b73..f2f85798 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -52,29 +52,29 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption return _type, _id, options -def link_interface(interface_proto: core_pb2.Interface) -> InterfaceData: +def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData: """ Create interface data from interface proto. - :param interface_proto: interface proto + :param iface_proto: interface proto :return: interface data """ - interface_data = None - if interface_proto: - name = interface_proto.name if interface_proto.name else None - mac = interface_proto.mac if interface_proto.mac else None - ip4 = interface_proto.ip4 if interface_proto.ip4 else None - ip6 = interface_proto.ip6 if interface_proto.ip6 else None - interface_data = InterfaceData( - id=interface_proto.id, + iface_data = None + if iface_proto: + name = iface_proto.name if iface_proto.name else None + mac = iface_proto.mac if iface_proto.mac else None + ip4 = iface_proto.ip4 if iface_proto.ip4 else None + ip6 = iface_proto.ip6 if iface_proto.ip6 else None + iface_data = InterfaceData( + id=iface_proto.id, name=name, mac=mac, ip4=ip4, - ip4_mask=interface_proto.ip4mask, + ip4_mask=iface_proto.ip4mask, ip6=ip6, - ip6_mask=interface_proto.ip6mask, + ip6_mask=iface_proto.ip6mask, ) - return interface_data + return iface_data def add_link_data( @@ -86,8 +86,8 @@ def add_link_data( :param link_proto: link proto :return: link interfaces and options """ - interface1_data = link_interface(link_proto.interface1) - interface2_data = link_interface(link_proto.interface2) + iface1_data = link_iface(link_proto.iface1) + iface2_data = link_iface(link_proto.iface2) link_type = LinkTypes(link_proto.type) options = LinkOptions(type=link_type) options_data = link_proto.options @@ -103,7 +103,7 @@ def add_link_data( options.unidirectional = options_data.unidirectional options.key = options_data.key options.opaque = options_data.opaque - return interface1_data, interface2_data, options + return iface1_data, iface2_data, options def create_nodes( @@ -143,8 +143,8 @@ def create_links( for link_proto in link_protos: node1_id = link_proto.node1_id node2_id = link_proto.node2_id - interface1, interface2, options = add_link_data(link_proto) - args = (node1_id, node2_id, interface1, interface2, options) + iface1, iface2, options = add_link_data(link_proto) + args = (node1_id, node2_id, iface1, iface2, options) funcs.append((session.add_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -167,8 +167,8 @@ def edit_links( for link_proto in link_protos: node1_id = link_proto.node1_id node2_id = link_proto.node2_id - interface1, interface2, options = add_link_data(link_proto) - args = (node1_id, node2_id, interface1.id, interface2.id, options) + iface1, iface2, options = add_link_data(link_proto) + args = (node1_id, node2_id, iface1.id, iface2.id, options) funcs.append((session.update_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -279,16 +279,16 @@ def get_links(node: NodeBase): return links -def get_emane_model_id(node_id: int, interface_id: int) -> int: +def get_emane_model_id(node_id: int, iface_id: int) -> int: """ Get EMANE model id :param node_id: node id - :param interface_id: interface id + :param iface_id: interface id :return: EMANE model id """ - if interface_id >= 0: - return node_id * 1000 + interface_id + if iface_id >= 0: + return node_id * 1000 + iface_id else: return node_id @@ -300,12 +300,12 @@ def parse_emane_model_id(_id: int) -> Tuple[int, int]: :param _id: id to parse :return: node id and interface id """ - interface = -1 + iface_id = -1 node_id = _id if _id >= 1000: - interface = _id % 1000 + iface_id = _id % 1000 node_id = int(_id / 1000) - return node_id, interface + return node_id, iface_id def convert_link(link_data: LinkData) -> core_pb2.Link: @@ -315,27 +315,27 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: :param link_data: link to convert :return: core protobuf Link """ - interface1 = None - if link_data.interface1_id is not None: - interface1 = core_pb2.Interface( - id=link_data.interface1_id, - name=link_data.interface1_name, - mac=convert_value(link_data.interface1_mac), - ip4=convert_value(link_data.interface1_ip4), - ip4mask=link_data.interface1_ip4_mask, - ip6=convert_value(link_data.interface1_ip6), - ip6mask=link_data.interface1_ip6_mask, + iface1 = None + if link_data.iface1_id is not None: + iface1 = core_pb2.Interface( + id=link_data.iface1_id, + name=link_data.iface1_name, + mac=convert_value(link_data.iface1_mac), + ip4=convert_value(link_data.iface1_ip4), + ip4mask=link_data.iface1_ip4_mask, + ip6=convert_value(link_data.iface1_ip6), + ip6mask=link_data.iface1_ip6_mask, ) - interface2 = None - if link_data.interface2_id is not None: - interface2 = core_pb2.Interface( - id=link_data.interface2_id, - name=link_data.interface2_name, - mac=convert_value(link_data.interface2_mac), - ip4=convert_value(link_data.interface2_ip4), - ip4mask=link_data.interface2_ip4_mask, - ip6=convert_value(link_data.interface2_ip6), - ip6mask=link_data.interface2_ip6_mask, + iface2 = None + if link_data.iface2_id is not None: + iface2 = core_pb2.Interface( + id=link_data.iface2_id, + name=link_data.iface2_name, + mac=convert_value(link_data.iface2_mac), + ip4=convert_value(link_data.iface2_ip4), + ip4mask=link_data.iface2_ip4_mask, + ip6=convert_value(link_data.iface2_ip6), + ip6mask=link_data.iface2_ip6_mask, ) options = core_pb2.LinkOptions( opaque=link_data.opaque, @@ -354,8 +354,8 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: type=link_data.link_type.value, node1_id=link_data.node1_id, node2_id=link_data.node2_id, - interface1=interface1, - interface2=interface2, + iface1=iface1, + iface2=iface2, options=options, network_id=link_data.network_id, label=link_data.label, @@ -440,20 +440,20 @@ def get_service_configuration(service: CoreService) -> NodeServiceData: ) -def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface: +def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: """ Convenience for converting a core interface to the protobuf representation. - :param interface: interface to convert + :param iface: interface to convert :return: interface proto """ net_id = None - if interface.net: - net_id = interface.net.id + if iface.net: + net_id = iface.net.id ip4 = None ip4mask = None ip6 = None ip6mask = None - for addr in interface.addrlist: + for addr in iface.addrlist: network = netaddr.IPNetwork(addr) mask = network.prefixlen ip = str(network.ip) @@ -464,12 +464,12 @@ def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface: ip6 = ip ip6mask = mask return core_pb2.Interface( - id=interface.netindex, + id=iface.node_id, netid=net_id, - name=interface.name, - mac=str(interface.hwaddr), - mtu=interface.mtu, - flowid=interface.flow_id, + name=iface.name, + mac=str(iface.hwaddr), + mtu=iface.mtu, + flowid=iface.flow_id, ip4=ip4, ip4mask=ip4mask, ip6=ip6, @@ -477,21 +477,21 @@ def interface_to_proto(interface: CoreInterface) -> core_pb2.Interface: ) -def get_nem_id(node: CoreNode, netif_id: int, context: ServicerContext) -> int: +def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int: """ Get nem id for a given node and interface id. :param node: node to get nem id for - :param netif_id: id of interface on node to get nem id for + :param iface_id: id of interface on node to get nem id for :param context: request context :return: nem id """ - netif = node.netif(netif_id) - if not netif: - message = f"{node.name} missing interface {netif_id}" + iface = node.ifaces.get(iface_id) + if not iface: + message = f"{node.name} missing interface {iface_id}" context.abort(grpc.StatusCode.NOT_FOUND, message) - net = netif.net + net = iface.net if not isinstance(net, EmaneNet): - message = f"{node.name} interface {netif_id} is not an EMANE network" + message = f"{node.name} interface {iface_id} is not an EMANE network" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) - return net.getnemid(netif) + return net.getnemid(iface) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 8b349b67..87b69a77 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -246,7 +246,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): config = session.emane.get_configs() config.update(request.emane_config) for config in request.emane_model_configs: - _id = get_emane_model_id(config.node_id, config.interface_id) + _id = get_emane_model_id(config.node_id, config.iface_id) session.emane.set_model_config(_id, config.model, config.config) # wlan configs @@ -625,16 +625,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): key = key.split(".") node_id = _INTERFACE_REGEX.search(key[0]).group("node") node_id = int(node_id, base=16) - interface_id = int(key[1], base=16) + iface_id = int(key[1], base=16) session_id = int(key[2], base=16) if session.id != session_id: continue - interface_throughput = ( - throughputs_event.interface_throughputs.add() - ) - interface_throughput.node_id = node_id - interface_throughput.interface_id = interface_id - interface_throughput.throughput = throughput + iface_throughput = throughputs_event.iface_throughputs.add() + iface_throughput.node_id = node_id + iface_throughput.iface_id = iface_id + iface_throughput.throughput = throughput elif key.startswith("b."): try: key = key.split(".") @@ -686,13 +684,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("get node: %s", request) session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, NodeBase) - interfaces = [] - for interface_id in node._netif: - interface = node._netif[interface_id] - interface_proto = grpcutils.interface_to_proto(interface) - interfaces.append(interface_proto) + ifaces = [] + for iface_id in node.ifaces: + iface = node.ifaces[iface_id] + iface_proto = grpcutils.iface_to_proto(iface) + ifaces.append(iface_proto) node_proto = grpcutils.get_node_proto(session, node) - return core_pb2.GetNodeResponse(node=node_proto, interfaces=interfaces) + return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces) def MoveNodes( self, @@ -850,18 +848,18 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): node2_id = request.link.node2_id self.get_node(session, node1_id, context, NodeBase) self.get_node(session, node2_id, context, NodeBase) - interface1, interface2, options = grpcutils.add_link_data(request.link) - node1_interface, node2_interface = session.add_link( - node1_id, node2_id, interface1, interface2, options=options + iface1_data, iface2_data, options = grpcutils.add_link_data(request.link) + node1_iface, node2_iface = session.add_link( + node1_id, node2_id, iface1_data, iface2_data, options=options ) - interface1_proto = None - interface2_proto = None - if node1_interface: - interface1_proto = grpcutils.interface_to_proto(node1_interface) - if node2_interface: - interface2_proto = grpcutils.interface_to_proto(node2_interface) + iface1_proto = None + iface2_proto = None + if node1_iface: + iface1_proto = grpcutils.iface_to_proto(node1_iface) + if node2_iface: + iface2_proto = grpcutils.iface_to_proto(node2_iface) return core_pb2.AddLinkResponse( - result=True, interface1=interface1_proto, interface2=interface2_proto + result=True, iface1=iface1_proto, iface2=iface2_proto ) def EditLink( @@ -878,8 +876,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session = self.get_session(request.session_id, context) node1_id = request.node1_id node2_id = request.node2_id - interface1_id = request.interface1_id - interface2_id = request.interface2_id + iface1_id = request.iface1_id + iface2_id = request.iface2_id options_data = request.options options = LinkOptions( delay=options_data.delay, @@ -894,7 +892,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): key=options_data.key, opaque=options_data.opaque, ) - session.update_link(node1_id, node2_id, interface1_id, interface2_id, options) + session.update_link(node1_id, node2_id, iface1_id, iface2_id, options) return core_pb2.EditLinkResponse(result=True) def DeleteLink( @@ -911,9 +909,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session = self.get_session(request.session_id, context) node1_id = request.node1_id node2_id = request.node2_id - interface1_id = request.interface1_id - interface2_id = request.interface2_id - session.delete_link(node1_id, node2_id, interface1_id, interface2_id) + iface1_id = request.iface1_id + iface2_id = request.iface2_id + session.delete_link(node1_id, node2_id, iface1_id, iface2_id) return core_pb2.DeleteLinkResponse(result=True) def GetHooks( @@ -1371,7 +1369,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("get emane model config: %s", request) session = self.get_session(request.session_id, context) model = session.emane.models[request.model] - _id = get_emane_model_id(request.node_id, request.interface) + _id = get_emane_model_id(request.node_id, request.iface_id) current_config = session.emane.get_model_config(_id, request.model) config = get_config_options(current_config, model) return GetEmaneModelConfigResponse(config=config) @@ -1390,7 +1388,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("set emane model config: %s", request) session = self.get_session(request.session_id, context) model_config = request.emane_model_config - _id = get_emane_model_id(model_config.node_id, model_config.interface_id) + _id = get_emane_model_id(model_config.node_id, model_config.iface_id) session.emane.set_model_config(_id, model_config.model, model_config.config) return SetEmaneModelConfigResponse(result=True) @@ -1419,12 +1417,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): model = session.emane.models[model_name] current_config = session.emane.get_model_config(_id, model_name) config = get_config_options(current_config, model) - node_id, interface = grpcutils.parse_emane_model_id(_id) + node_id, iface_id = grpcutils.parse_emane_model_id(_id) model_config = GetEmaneModelConfigsResponse.ModelConfig( - node_id=node_id, - model=model_name, - interface=interface, - config=config, + node_id=node_id, model=model_name, iface_id=iface_id, config=config ) configs.append(model_config) return GetEmaneModelConfigsResponse(configs=configs) @@ -1489,16 +1484,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-interfaces response that has all the system's interfaces """ - interfaces = [] - for interface in os.listdir("/sys/class/net"): - if ( - interface.startswith("b.") - or interface.startswith("veth") - or interface == "lo" - ): + ifaces = [] + for iface in os.listdir("/sys/class/net"): + if iface.startswith("b.") or iface.startswith("veth") or iface == "lo": continue - interfaces.append(interface) - return core_pb2.GetInterfacesResponse(interfaces=interfaces) + ifaces.append(iface) + return core_pb2.GetInterfacesResponse(ifaces=ifaces) def EmaneLink( self, request: EmaneLinkRequest, context: ServicerContext @@ -1513,16 +1504,16 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("emane link: %s", request) session = self.get_session(request.session_id, context) nem1 = request.nem1 - emane1, netif = session.emane.nemlookup(nem1) - if not emane1 or not netif: + emane1, iface = session.emane.nemlookup(nem1) + if not emane1 or not iface: context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found") - node1 = netif.node + node1 = iface.node nem2 = request.nem2 - emane2, netif = session.emane.nemlookup(nem2) - if not emane2 or not netif: + emane2, iface = session.emane.nemlookup(nem2) + if not emane2 or not iface: context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found") - node2 = netif.node + node2 = iface.node if emane1.id == emane2.id: if request.linked: @@ -1734,21 +1725,19 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): ) node1 = self.get_node(session, request.node1_id, context, CoreNode) node2 = self.get_node(session, request.node2_id, context, CoreNode) - node1_interface, node2_interface = None, None - for net, interface1, interface2 in node1.commonnets(node2): + node1_iface, node2_iface = None, None + for net, iface1, iface2 in node1.commonnets(node2): if net == wlan: - node1_interface = interface1 - node2_interface = interface2 + node1_iface = iface1 + node2_iface = iface2 break result = False - if node1_interface and node2_interface: + if node1_iface and node2_iface: if request.linked: - wlan.link(node1_interface, node2_interface) + wlan.link(node1_iface, node2_iface) else: - wlan.unlink(node1_interface, node2_interface) - wlan.model.sendlinkmsg( - node1_interface, node2_interface, unlink=not request.linked - ) + wlan.unlink(node1_iface, node2_iface) + wlan.model.sendlinkmsg(node1_iface, node2_iface, unlink=not request.linked) result = True return WlanLinkResponse(result=result) @@ -1760,8 +1749,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): for request in request_iterator: session = self.get_session(request.session_id, context) node1 = self.get_node(session, request.node1_id, context, CoreNode) - nem1 = grpcutils.get_nem_id(node1, request.interface1_id, context) + nem1 = grpcutils.get_nem_id(node1, request.iface1_id, context) node2 = self.get_node(session, request.node2_id, context, CoreNode) - nem2 = grpcutils.get_nem_id(node2, request.interface2_id, context) + nem2 = grpcutils.get_nem_id(node2, request.iface2_id, context) session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2) return EmanePathlossesResponse() diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index 088a7631..5d0b08e7 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -508,18 +508,18 @@ class CoreLinkTlv(CoreTlv): LinkTlvs.EMULATION_ID.value: CoreTlvDataUint32, LinkTlvs.NETWORK_ID.value: CoreTlvDataUint32, LinkTlvs.KEY.value: CoreTlvDataUint32, - LinkTlvs.INTERFACE1_NUMBER.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE1_IP4.value: CoreTlvDataIpv4Addr, - LinkTlvs.INTERFACE1_IP4_MASK.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE1_MAC.value: CoreTlvDataMacAddr, - LinkTlvs.INTERFACE1_IP6.value: CoreTlvDataIPv6Addr, - LinkTlvs.INTERFACE1_IP6_MASK.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE2_NUMBER.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE2_IP4.value: CoreTlvDataIpv4Addr, - LinkTlvs.INTERFACE2_IP4_MASK.value: CoreTlvDataUint16, - LinkTlvs.INTERFACE2_MAC.value: CoreTlvDataMacAddr, - LinkTlvs.INTERFACE2_IP6.value: CoreTlvDataIPv6Addr, - LinkTlvs.INTERFACE2_IP6_MASK.value: CoreTlvDataUint16, + LinkTlvs.IFACE1_NUMBER.value: CoreTlvDataUint16, + LinkTlvs.IFACE1_IP4.value: CoreTlvDataIpv4Addr, + LinkTlvs.IFACE1_IP4_MASK.value: CoreTlvDataUint16, + LinkTlvs.IFACE1_MAC.value: CoreTlvDataMacAddr, + LinkTlvs.IFACE1_IP6.value: CoreTlvDataIPv6Addr, + LinkTlvs.IFACE1_IP6_MASK.value: CoreTlvDataUint16, + LinkTlvs.IFACE2_NUMBER.value: CoreTlvDataUint16, + LinkTlvs.IFACE2_IP4.value: CoreTlvDataIpv4Addr, + LinkTlvs.IFACE2_IP4_MASK.value: CoreTlvDataUint16, + LinkTlvs.IFACE2_MAC.value: CoreTlvDataMacAddr, + LinkTlvs.IFACE2_IP6.value: CoreTlvDataIPv6Addr, + LinkTlvs.IFACE2_IP6_MASK.value: CoreTlvDataUint16, LinkTlvs.INTERFACE1_NAME.value: CoreTlvDataString, LinkTlvs.INTERFACE2_NAME.value: CoreTlvDataString, LinkTlvs.OPAQUE.value: CoreTlvDataString, @@ -577,7 +577,7 @@ class CoreConfigTlv(CoreTlv): ConfigTlvs.POSSIBLE_VALUES.value: CoreTlvDataString, ConfigTlvs.GROUPS.value: CoreTlvDataString, ConfigTlvs.SESSION.value: CoreTlvDataString, - ConfigTlvs.INTERFACE_NUMBER.value: CoreTlvDataUint16, + ConfigTlvs.IFACE_ID.value: CoreTlvDataUint16, ConfigTlvs.NETWORK_ID.value: CoreTlvDataUint32, ConfigTlvs.OPAQUE.value: CoreTlvDataString, } diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 2cd7bfac..b09a37fe 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -71,7 +71,7 @@ class CoreHandler(socketserver.BaseRequestHandler): MessageTypes.REGISTER.value: self.handle_register_message, MessageTypes.CONFIG.value: self.handle_config_message, MessageTypes.FILE.value: self.handle_file_message, - MessageTypes.INTERFACE.value: self.handle_interface_message, + MessageTypes.INTERFACE.value: self.handle_iface_message, MessageTypes.EVENT.value: self.handle_event_message, MessageTypes.SESSION.value: self.handle_session_message, } @@ -363,18 +363,18 @@ class CoreHandler(socketserver.BaseRequestHandler): (LinkTlvs.EMULATION_ID, link_data.emulation_id), (LinkTlvs.NETWORK_ID, link_data.network_id), (LinkTlvs.KEY, link_data.key), - (LinkTlvs.INTERFACE1_NUMBER, link_data.interface1_id), - (LinkTlvs.INTERFACE1_IP4, link_data.interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, link_data.interface1_ip4_mask), - (LinkTlvs.INTERFACE1_MAC, link_data.interface1_mac), - (LinkTlvs.INTERFACE1_IP6, link_data.interface1_ip6), - (LinkTlvs.INTERFACE1_IP6_MASK, link_data.interface1_ip6_mask), - (LinkTlvs.INTERFACE2_NUMBER, link_data.interface2_id), - (LinkTlvs.INTERFACE2_IP4, link_data.interface2_ip4), - (LinkTlvs.INTERFACE2_IP4_MASK, link_data.interface2_ip4_mask), - (LinkTlvs.INTERFACE2_MAC, link_data.interface2_mac), - (LinkTlvs.INTERFACE2_IP6, link_data.interface2_ip6), - (LinkTlvs.INTERFACE2_IP6_MASK, link_data.interface2_ip6_mask), + (LinkTlvs.IFACE1_NUMBER, link_data.iface1_id), + (LinkTlvs.IFACE1_IP4, link_data.iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, link_data.iface1_ip4_mask), + (LinkTlvs.IFACE1_MAC, link_data.iface1_mac), + (LinkTlvs.IFACE1_IP6, link_data.iface1_ip6), + (LinkTlvs.IFACE1_IP6_MASK, link_data.iface1_ip6_mask), + (LinkTlvs.IFACE2_NUMBER, link_data.iface2_id), + (LinkTlvs.IFACE2_IP4, link_data.iface2_ip4), + (LinkTlvs.IFACE2_IP4_MASK, link_data.iface2_ip4_mask), + (LinkTlvs.IFACE2_MAC, link_data.iface2_mac), + (LinkTlvs.IFACE2_IP6, link_data.iface2_ip6), + (LinkTlvs.IFACE2_IP6_MASK, link_data.iface2_ip6_mask), (LinkTlvs.OPAQUE, link_data.opaque), ], ) @@ -749,23 +749,23 @@ class CoreHandler(socketserver.BaseRequestHandler): """ node1_id = message.get_tlv(LinkTlvs.N1_NUMBER.value) node2_id = message.get_tlv(LinkTlvs.N2_NUMBER.value) - interface1_data = InterfaceData( - id=message.get_tlv(LinkTlvs.INTERFACE1_NUMBER.value), + iface1_data = InterfaceData( + id=message.get_tlv(LinkTlvs.IFACE1_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE1_NAME.value), - mac=message.get_tlv(LinkTlvs.INTERFACE1_MAC.value), - ip4=message.get_tlv(LinkTlvs.INTERFACE1_IP4.value), - ip4_mask=message.get_tlv(LinkTlvs.INTERFACE1_IP4_MASK.value), - ip6=message.get_tlv(LinkTlvs.INTERFACE1_IP6.value), - ip6_mask=message.get_tlv(LinkTlvs.INTERFACE1_IP6_MASK.value), + mac=message.get_tlv(LinkTlvs.IFACE1_MAC.value), + ip4=message.get_tlv(LinkTlvs.IFACE1_IP4.value), + ip4_mask=message.get_tlv(LinkTlvs.IFACE1_IP4_MASK.value), + ip6=message.get_tlv(LinkTlvs.IFACE1_IP6.value), + ip6_mask=message.get_tlv(LinkTlvs.IFACE1_IP6_MASK.value), ) - interface2_data = InterfaceData( - id=message.get_tlv(LinkTlvs.INTERFACE2_NUMBER.value), + iface2_data = InterfaceData( + id=message.get_tlv(LinkTlvs.IFACE2_NUMBER.value), name=message.get_tlv(LinkTlvs.INTERFACE2_NAME.value), - mac=message.get_tlv(LinkTlvs.INTERFACE2_MAC.value), - ip4=message.get_tlv(LinkTlvs.INTERFACE2_IP4.value), - ip4_mask=message.get_tlv(LinkTlvs.INTERFACE2_IP4_MASK.value), - ip6=message.get_tlv(LinkTlvs.INTERFACE2_IP6.value), - ip6_mask=message.get_tlv(LinkTlvs.INTERFACE2_IP6_MASK.value), + mac=message.get_tlv(LinkTlvs.IFACE2_MAC.value), + ip4=message.get_tlv(LinkTlvs.IFACE2_IP4.value), + ip4_mask=message.get_tlv(LinkTlvs.IFACE2_IP4_MASK.value), + ip6=message.get_tlv(LinkTlvs.IFACE2_IP6.value), + ip6_mask=message.get_tlv(LinkTlvs.IFACE2_IP6_MASK.value), ) link_type = LinkTypes.WIRED link_type_value = message.get_tlv(LinkTlvs.TYPE.value) @@ -789,16 +789,12 @@ class CoreHandler(socketserver.BaseRequestHandler): options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) if message.flags & MessageFlags.ADD.value: - self.session.add_link( - node1_id, node2_id, interface1_data, interface2_data, options - ) + self.session.add_link(node1_id, node2_id, iface1_data, iface2_data, options) elif message.flags & MessageFlags.DELETE.value: - self.session.delete_link( - node1_id, node2_id, interface1_data.id, interface2_data.id - ) + self.session.delete_link(node1_id, node2_id, iface1_data.id, iface2_data.id) else: self.session.update_link( - node1_id, node2_id, interface1_data.id, interface2_data.id, options + node1_id, node2_id, iface1_data.id, iface2_data.id, options ) return () @@ -1008,7 +1004,7 @@ class CoreHandler(socketserver.BaseRequestHandler): possible_values=message.get_tlv(ConfigTlvs.POSSIBLE_VALUES.value), groups=message.get_tlv(ConfigTlvs.GROUPS.value), session=message.get_tlv(ConfigTlvs.SESSION.value), - interface_number=message.get_tlv(ConfigTlvs.INTERFACE_NUMBER.value), + iface_id=message.get_tlv(ConfigTlvs.IFACE_ID.value), network_id=message.get_tlv(ConfigTlvs.NETWORK_ID.value), opaque=message.get_tlv(ConfigTlvs.OPAQUE.value), ) @@ -1325,11 +1321,11 @@ class CoreHandler(socketserver.BaseRequestHandler): replies = [] node_id = config_data.node object_name = config_data.object - interface_id = config_data.interface_number + iface_id = config_data.iface_id values_str = config_data.data_values - if interface_id is not None: - node_id = node_id * 1000 + interface_id + if iface_id is not None: + node_id = node_id * 1000 + iface_id logging.debug( "received configure message for %s nodenum: %s", object_name, node_id @@ -1375,11 +1371,11 @@ class CoreHandler(socketserver.BaseRequestHandler): replies = [] node_id = config_data.node object_name = config_data.object - interface_id = config_data.interface_number + iface_id = config_data.iface_id values_str = config_data.data_values - if interface_id is not None: - node_id = node_id * 1000 + interface_id + if iface_id is not None: + node_id = node_id * 1000 + iface_id logging.debug( "received configure message for %s nodenum: %s", object_name, node_id @@ -1407,11 +1403,11 @@ class CoreHandler(socketserver.BaseRequestHandler): replies = [] node_id = config_data.node object_name = config_data.object - interface_id = config_data.interface_number + iface_id = config_data.iface_id values_str = config_data.data_values - if interface_id is not None: - node_id = node_id * 1000 + interface_id + if iface_id is not None: + node_id = node_id * 1000 + iface_id logging.debug( "received configure message for %s nodenum: %s", object_name, node_id @@ -1505,7 +1501,7 @@ class CoreHandler(socketserver.BaseRequestHandler): return () - def handle_interface_message(self, message): + def handle_iface_message(self, message): """ Interface Message handler. @@ -1950,7 +1946,7 @@ class CoreUdpHandler(CoreHandler): MessageTypes.REGISTER.value: self.handle_register_message, MessageTypes.CONFIG.value: self.handle_config_message, MessageTypes.FILE.value: self.handle_file_message, - MessageTypes.INTERFACE.value: self.handle_interface_message, + MessageTypes.INTERFACE.value: self.handle_iface_message, MessageTypes.EVENT.value: self.handle_event_message, MessageTypes.SESSION.value: self.handle_session_message, } diff --git a/daemon/core/api/tlv/dataconversion.py b/daemon/core/api/tlv/dataconversion.py index 876e72a5..cd10ef04 100644 --- a/daemon/core/api/tlv/dataconversion.py +++ b/daemon/core/api/tlv/dataconversion.py @@ -75,7 +75,7 @@ def convert_config(config_data): (ConfigTlvs.POSSIBLE_VALUES, config_data.possible_values), (ConfigTlvs.GROUPS, config_data.groups), (ConfigTlvs.SESSION, session), - (ConfigTlvs.INTERFACE_NUMBER, config_data.interface_number), + (ConfigTlvs.IFACE_ID, config_data.iface_id), (ConfigTlvs.NETWORK_ID, config_data.network_id), (ConfigTlvs.OPAQUE, config_data.opaque), ], diff --git a/daemon/core/api/tlv/enumerations.py b/daemon/core/api/tlv/enumerations.py index 0efb7c99..b4ec254a 100644 --- a/daemon/core/api/tlv/enumerations.py +++ b/daemon/core/api/tlv/enumerations.py @@ -72,18 +72,18 @@ class LinkTlvs(Enum): EMULATION_ID = 0x23 NETWORK_ID = 0x24 KEY = 0x25 - INTERFACE1_NUMBER = 0x30 - INTERFACE1_IP4 = 0x31 - INTERFACE1_IP4_MASK = 0x32 - INTERFACE1_MAC = 0x33 - INTERFACE1_IP6 = 0x34 - INTERFACE1_IP6_MASK = 0x35 - INTERFACE2_NUMBER = 0x36 - INTERFACE2_IP4 = 0x37 - INTERFACE2_IP4_MASK = 0x38 - INTERFACE2_MAC = 0x39 - INTERFACE2_IP6 = 0x40 - INTERFACE2_IP6_MASK = 0x41 + IFACE1_NUMBER = 0x30 + IFACE1_IP4 = 0x31 + IFACE1_IP4_MASK = 0x32 + IFACE1_MAC = 0x33 + IFACE1_IP6 = 0x34 + IFACE1_IP6_MASK = 0x35 + IFACE2_NUMBER = 0x36 + IFACE2_IP4 = 0x37 + IFACE2_IP4_MASK = 0x38 + IFACE2_MAC = 0x39 + IFACE2_IP6 = 0x40 + IFACE2_IP6_MASK = 0x41 INTERFACE1_NAME = 0x42 INTERFACE2_NAME = 0x43 OPAQUE = 0x50 @@ -118,7 +118,7 @@ class ConfigTlvs(Enum): POSSIBLE_VALUES = 0x08 GROUPS = 0x09 SESSION = 0x0A - INTERFACE_NUMBER = 0x0B + IFACE_ID = 0x0B NETWORK_ID = 0x24 OPAQUE = 0x50 diff --git a/daemon/core/configservices/frrservices/services.py b/daemon/core/configservices/frrservices/services.py index c4502f86..8764e32c 100644 --- a/daemon/core/configservices/frrservices/services.py +++ b/daemon/core/configservices/frrservices/services.py @@ -13,33 +13,33 @@ from core.nodes.network import WlanNode GROUP = "FRR" -def has_mtu_mismatch(ifc: CoreInterface) -> bool: +def has_mtu_mismatch(iface: CoreInterface) -> bool: """ Helper to detect MTU mismatch and add the appropriate FRR mtu-ignore command. This is needed when e.g. a node is linked via a GreTap device. """ - if ifc.mtu != 1500: + if iface.mtu != 1500: return True - if not ifc.net: + if not iface.net: return False - for i in ifc.net.netifs(): - if i.mtu != ifc.mtu: + for iface in iface.net.get_ifaces(): + if iface.mtu != iface.mtu: return True return False -def get_min_mtu(ifc): +def get_min_mtu(iface): """ Helper to discover the minimum MTU of interfaces linked with the given interface. """ - mtu = ifc.mtu - if not ifc.net: + mtu = iface.mtu + if not iface.net: return mtu - for i in ifc.net.netifs(): - if i.mtu < mtu: - mtu = i.mtu + for iface in iface.net.get_ifaces(): + if iface.mtu < mtu: + mtu = iface.mtu return mtu @@ -47,10 +47,8 @@ def get_router_id(node: CoreNodeBase) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if getattr(ifc, "control", False): - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -97,25 +95,25 @@ class FRRZebra(ConfigService): want_ip6 = True services.append(service) - interfaces = [] - for ifc in self.node.netifs(): + ifaces = [] + for iface in self.node.get_ifaces(): ip4s = [] ip6s = [] - for x in ifc.addrlist: + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv4(addr): ip4s.append(x) else: ip6s.append(x) - is_control = getattr(ifc, "control", False) - interfaces.append((ifc, ip4s, ip6s, is_control)) + is_control = getattr(iface, "control", False) + ifaces.append((iface, ip4s, ip6s, is_control)) return dict( frr_conf=frr_conf, frr_sbin_search=frr_sbin_search, frr_bin_search=frr_bin_search, frr_state_dir=constants.FRR_STATE_DIR, - interfaces=interfaces, + ifaces=ifaces, want_ip4=want_ip4, want_ip6=want_ip6, services=services, @@ -138,7 +136,7 @@ class FrrService(abc.ABC): ipv6_routing = False @abc.abstractmethod - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: raise NotImplementedError @abc.abstractmethod @@ -162,10 +160,8 @@ class FRROspfv2(FrrService, ConfigService): def frr_config(self) -> str: router_id = get_router_id(self.node) addresses = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for a in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for a in iface.addrlist: addr = a.split("/")[0] if netaddr.valid_ipv4(addr): addresses.append(a) @@ -180,8 +176,8 @@ class FRROspfv2(FrrService, ConfigService): """ return self.render_text(text, data) - def frr_interface_config(self, ifc: CoreInterface) -> str: - if has_mtu_mismatch(ifc): + def frr_iface_config(self, iface: CoreInterface) -> str: + if has_mtu_mismatch(iface): return "ip ospf mtu-ignore" else: return "" @@ -203,10 +199,8 @@ class FRROspfv3(FrrService, ConfigService): def frr_config(self) -> str: router_id = get_router_id(self.node) ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) data = dict(router_id=router_id, ifnames=ifnames) text = """ router ospf6 @@ -218,9 +212,9 @@ class FRROspfv3(FrrService, ConfigService): """ return self.render_text(text, data) - def frr_interface_config(self, ifc: CoreInterface) -> str: - mtu = get_min_mtu(ifc) - if mtu < ifc.mtu: + def frr_iface_config(self, iface: CoreInterface) -> str: + mtu = get_min_mtu(iface) + if mtu < iface.mtu: return f"ipv6 ospf6 ifmtu {mtu}" else: return "" @@ -254,7 +248,7 @@ class FRRBgp(FrrService, ConfigService): """ return self.clean_text(text) - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: return "" @@ -279,7 +273,7 @@ class FRRRip(FrrService, ConfigService): """ return self.clean_text(text) - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: return "" @@ -304,7 +298,7 @@ class FRRRipng(FrrService, ConfigService): """ return self.clean_text(text) - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: return "" @@ -321,10 +315,8 @@ class FRRBabel(FrrService, ConfigService): def frr_config(self) -> str: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) text = """ router babel % for ifname in ifnames: @@ -337,8 +329,8 @@ class FRRBabel(FrrService, ConfigService): data = dict(ifnames=ifnames) return self.render_text(text, data) - def frr_interface_config(self, ifc: CoreInterface) -> str: - if isinstance(ifc.net, (WlanNode, EmaneNet)): + def frr_iface_config(self, iface: CoreInterface) -> str: + if isinstance(iface.net, (WlanNode, EmaneNet)): text = """ babel wireless no babel split-horizon @@ -363,9 +355,9 @@ class FRRpimd(FrrService, ConfigService): def frr_config(self) -> str: ifname = "eth0" - for ifc in self.node.netifs(): - if ifc.name != "lo": - ifname = ifc.name + for iface in self.node.get_ifaces(): + if iface.name != "lo": + ifname = iface.name break text = f""" @@ -382,7 +374,7 @@ class FRRpimd(FrrService, ConfigService): """ return self.clean_text(text) - def frr_interface_config(self, ifc: CoreInterface) -> str: + def frr_iface_config(self, iface: CoreInterface) -> str: text = """ ip mfea ip igmp diff --git a/daemon/core/configservices/frrservices/templates/frr.conf b/daemon/core/configservices/frrservices/templates/frr.conf index 748c8692..8e036136 100644 --- a/daemon/core/configservices/frrservices/templates/frr.conf +++ b/daemon/core/configservices/frrservices/templates/frr.conf @@ -1,5 +1,5 @@ -% for ifc, ip4s, ip6s, is_control in interfaces: -interface ${ifc.name} +% for iface, ip4s, ip6s, is_control in ifaces: +interface ${iface.name} % if want_ip4: % for addr in ip4s: ip address ${addr} @@ -12,7 +12,7 @@ interface ${ifc.name} % endif % if not is_control: % for service in services: - % for line in service.frr_interface_config(ifc).split("\n"): + % for line in service.frr_iface_config(iface).split("\n"): ${line} % endfor % endfor diff --git a/daemon/core/configservices/frrservices/templates/frrboot.sh b/daemon/core/configservices/frrservices/templates/frrboot.sh index 3c14cd1a..db47b6d1 100644 --- a/daemon/core/configservices/frrservices/templates/frrboot.sh +++ b/daemon/core/configservices/frrservices/templates/frrboot.sh @@ -98,8 +98,8 @@ confcheck bootfrr # reset interfaces -% for ifc, _, _ , _ in interfaces: -ip link set dev ${ifc.name} down +% for iface, _, _ , _ in ifaces: +ip link set dev ${iface.name} down sleep 1 -ip link set dev ${ifc.name} up +ip link set dev ${iface.name} up % endfor diff --git a/daemon/core/configservices/nrlservices/services.py b/daemon/core/configservices/nrlservices/services.py index 3dddf1ba..ca95b8f6 100644 --- a/daemon/core/configservices/nrlservices/services.py +++ b/daemon/core/configservices/nrlservices/services.py @@ -24,8 +24,8 @@ class MgenSinkService(ConfigService): def data(self) -> Dict[str, Any]: ifnames = [] - for ifc in self.node.netifs(): - name = utils.sysctl_devname(ifc.name) + for iface in self.node.get_ifaces(): + name = utils.sysctl_devname(iface.name) ifnames.append(name) return dict(ifnames=ifnames) @@ -47,10 +47,8 @@ class NrlNhdp(ConfigService): def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(has_smf=has_smf, ifnames=ifnames) @@ -74,13 +72,11 @@ class NrlSmf(ConfigService): has_olsr = "OLSR" in self.node.config_services ifnames = [] ip4_prefix = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) if ip4_prefix: continue - for a in ifc.addrlist: + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): ip4_prefix = f"{a}/{24}" @@ -112,10 +108,8 @@ class NrlOlsr(ConfigService): has_smf = "SMF" in self.node.config_services has_zebra = "zebra" in self.node.config_services ifname = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifname = ifc.name + for iface in self.node.get_ifaces(control=False): + ifname = iface.name break return dict(has_smf=has_smf, has_zebra=has_zebra, ifname=ifname) @@ -137,10 +131,8 @@ class NrlOlsrv2(ConfigService): def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(has_smf=has_smf, ifnames=ifnames) @@ -161,10 +153,8 @@ class OlsrOrg(ConfigService): def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(has_smf=has_smf, ifnames=ifnames) @@ -199,12 +189,10 @@ class Arouted(ConfigService): def data(self) -> Dict[str, Any]: ip4_prefix = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue + for iface in self.node.get_ifaces(control=False): if ip4_prefix: continue - for a in ifc.addrlist: + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): ip4_prefix = f"{a}/{24}" diff --git a/daemon/core/configservices/nrlservices/templates/nrlnhdp.sh b/daemon/core/configservices/nrlservices/templates/nrlnhdp.sh index 00b7e11d..4513dfe9 100644 --- a/daemon/core/configservices/nrlservices/templates/nrlnhdp.sh +++ b/daemon/core/configservices/nrlservices/templates/nrlnhdp.sh @@ -1,7 +1,7 @@ <% - interfaces = "-i " + " -i ".join(ifnames) + ifaces = "-i " + " -i ".join(ifnames) smf = "" if has_smf: smf = "-flooding ecds -smfClient %s_smf" % node.name %> -nrlnhdp -l /var/log/nrlnhdp.log -rpipe ${node.name}_nhdp ${smf} ${interfaces} +nrlnhdp -l /var/log/nrlnhdp.log -rpipe ${node.name}_nhdp ${smf} ${ifaces} diff --git a/daemon/core/configservices/nrlservices/templates/nrlolsrv2.sh b/daemon/core/configservices/nrlservices/templates/nrlolsrv2.sh index d7a8d3b6..81196e26 100644 --- a/daemon/core/configservices/nrlservices/templates/nrlolsrv2.sh +++ b/daemon/core/configservices/nrlservices/templates/nrlolsrv2.sh @@ -1,7 +1,7 @@ <% - interfaces = "-i " + " -i ".join(ifnames) + ifaces = "-i " + " -i ".join(ifnames) smf = "" if has_smf: smf = "-flooding ecds -smfClient %s_smf" % node.name %> -nrlolsrv2 -l /var/log/nrlolsrv2.log -rpipe ${node.name}_olsrv2 -p olsr ${smf} ${interfaces} +nrlolsrv2 -l /var/log/nrlolsrv2.log -rpipe ${node.name}_olsrv2 -p olsr ${smf} ${ifaces} diff --git a/daemon/core/configservices/nrlservices/templates/olsrd.sh b/daemon/core/configservices/nrlservices/templates/olsrd.sh index 076f049b..3040ca6b 100644 --- a/daemon/core/configservices/nrlservices/templates/olsrd.sh +++ b/daemon/core/configservices/nrlservices/templates/olsrd.sh @@ -1,4 +1,4 @@ <% - interfaces = "-i " + " -i ".join(ifnames) + ifaces = "-i " + " -i ".join(ifnames) %> -olsrd ${interfaces} +olsrd ${ifaces} diff --git a/daemon/core/configservices/nrlservices/templates/startsmf.sh b/daemon/core/configservices/nrlservices/templates/startsmf.sh index 67fc0fe6..921568de 100644 --- a/daemon/core/configservices/nrlservices/templates/startsmf.sh +++ b/daemon/core/configservices/nrlservices/templates/startsmf.sh @@ -1,5 +1,5 @@ <% - interfaces = ",".join(ifnames) + ifaces = ",".join(ifnames) arouted = "" if has_arouted: arouted = "tap %s_tap unicast %s push lo,%s resequence on" % (node.name, ip4_prefix, ifnames[0]) @@ -12,4 +12,4 @@ %> #!/bin/sh # auto-generated by NrlSmf service -nrlsmf instance ${node.name}_smf ${interfaces} ${arouted} ${flood} hash MD5 log /var/log/nrlsmf.log < /dev/null > /dev/null 2>&1 & +nrlsmf instance ${node.name}_smf ${ifaces} ${arouted} ${flood} hash MD5 log /var/log/nrlsmf.log < /dev/null > /dev/null 2>&1 & diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 32ce99be..19e21476 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -14,33 +14,33 @@ from core.nodes.network import WlanNode GROUP = "Quagga" -def has_mtu_mismatch(ifc: CoreInterface) -> bool: +def has_mtu_mismatch(iface: CoreInterface) -> bool: """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a GreTap device. """ - if ifc.mtu != 1500: + if iface.mtu != 1500: return True - if not ifc.net: + if not iface.net: return False - for i in ifc.net.netifs(): - if i.mtu != ifc.mtu: + for iface in iface.net.get_ifaces(): + if iface.mtu != iface.mtu: return True return False -def get_min_mtu(ifc): +def get_min_mtu(iface: CoreInterface): """ Helper to discover the minimum MTU of interfaces linked with the given interface. """ - mtu = ifc.mtu - if not ifc.net: + mtu = iface.mtu + if not iface.net: return mtu - for i in ifc.net.netifs(): - if i.mtu < mtu: - mtu = i.mtu + for iface in iface.net.get_ifaces(): + if iface.mtu < mtu: + mtu = iface.mtu return mtu @@ -48,10 +48,8 @@ def get_router_id(node: CoreNodeBase) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if getattr(ifc, "control", False): - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -98,25 +96,25 @@ class Zebra(ConfigService): want_ip6 = True services.append(service) - interfaces = [] - for ifc in self.node.netifs(): + ifaces = [] + for iface in self.node.get_ifaces(): ip4s = [] ip6s = [] - for x in ifc.addrlist: + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv4(addr): ip4s.append(x) else: ip6s.append(x) - is_control = getattr(ifc, "control", False) - interfaces.append((ifc, ip4s, ip6s, is_control)) + is_control = getattr(iface, "control", False) + ifaces.append((iface, ip4s, ip6s, is_control)) return dict( quagga_bin_search=quagga_bin_search, quagga_sbin_search=quagga_sbin_search, quagga_state_dir=quagga_state_dir, quagga_conf=quagga_conf, - interfaces=interfaces, + ifaces=ifaces, want_ip4=want_ip4, want_ip6=want_ip6, services=services, @@ -139,7 +137,7 @@ class QuaggaService(abc.ABC): ipv6_routing = False @abc.abstractmethod - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: raise NotImplementedError @abc.abstractmethod @@ -159,8 +157,8 @@ class Ospfv2(QuaggaService, ConfigService): shutdown = ["killall ospfd"] ipv4_routing = True - def quagga_interface_config(self, ifc: CoreInterface) -> str: - if has_mtu_mismatch(ifc): + def quagga_iface_config(self, iface: CoreInterface) -> str: + if has_mtu_mismatch(iface): return "ip ospf mtu-ignore" else: return "" @@ -168,10 +166,8 @@ class Ospfv2(QuaggaService, ConfigService): def quagga_config(self) -> str: router_id = get_router_id(self.node) addresses = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for a in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for a in iface.addrlist: addr = a.split("/")[0] if netaddr.valid_ipv4(addr): addresses.append(a) @@ -200,9 +196,9 @@ class Ospfv3(QuaggaService, ConfigService): ipv4_routing = True ipv6_routing = True - def quagga_interface_config(self, ifc: CoreInterface) -> str: - mtu = get_min_mtu(ifc) - if mtu < ifc.mtu: + def quagga_iface_config(self, iface: CoreInterface) -> str: + mtu = get_min_mtu(iface) + if mtu < iface.mtu: return f"ipv6 ospf6 ifmtu {mtu}" else: return "" @@ -210,10 +206,8 @@ class Ospfv3(QuaggaService, ConfigService): def quagga_config(self) -> str: router_id = get_router_id(self.node) ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) data = dict(router_id=router_id, ifnames=ifnames) text = """ router ospf6 @@ -238,14 +232,14 @@ class Ospfv3mdr(Ospfv3): name = "OSPFv3MDR" def data(self) -> Dict[str, Any]: - for ifc in self.node.netifs(): - is_wireless = isinstance(ifc.net, (WlanNode, EmaneNet)) + for iface in self.node.get_ifaces(): + is_wireless = isinstance(iface.net, (WlanNode, EmaneNet)) logging.info("MDR wireless: %s", is_wireless) return dict() - def quagga_interface_config(self, ifc: CoreInterface) -> str: - config = super().quagga_interface_config(ifc) - if isinstance(ifc.net, (WlanNode, EmaneNet)): + def quagga_iface_config(self, iface: CoreInterface) -> str: + config = super().quagga_iface_config(iface) + if isinstance(iface.net, (WlanNode, EmaneNet)): config = self.clean_text( f""" {config} @@ -277,7 +271,7 @@ class Bgp(QuaggaService, ConfigService): def quagga_config(self) -> str: return "" - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: router_id = get_router_id(self.node) text = f""" ! BGP configuration @@ -313,7 +307,7 @@ class Rip(QuaggaService, ConfigService): """ return self.clean_text(text) - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: return "" @@ -338,7 +332,7 @@ class Ripng(QuaggaService, ConfigService): """ return self.clean_text(text) - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: return "" @@ -355,10 +349,8 @@ class Babel(QuaggaService, ConfigService): def quagga_config(self) -> str: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) text = """ router babel % for ifname in ifnames: @@ -371,8 +363,8 @@ class Babel(QuaggaService, ConfigService): data = dict(ifnames=ifnames) return self.render_text(text, data) - def quagga_interface_config(self, ifc: CoreInterface) -> str: - if isinstance(ifc.net, (WlanNode, EmaneNet)): + def quagga_iface_config(self, iface: CoreInterface) -> str: + if isinstance(iface.net, (WlanNode, EmaneNet)): text = """ babel wireless no babel split-horizon @@ -397,9 +389,9 @@ class Xpimd(QuaggaService, ConfigService): def quagga_config(self) -> str: ifname = "eth0" - for ifc in self.node.netifs(): - if ifc.name != "lo": - ifname = ifc.name + for iface in self.node.get_ifaces(): + if iface.name != "lo": + ifname = iface.name break text = f""" @@ -416,7 +408,7 @@ class Xpimd(QuaggaService, ConfigService): """ return self.clean_text(text) - def quagga_interface_config(self, ifc: CoreInterface) -> str: + def quagga_iface_config(self, iface: CoreInterface) -> str: text = """ ip mfea ip pim diff --git a/daemon/core/configservices/quaggaservices/templates/Quagga.conf b/daemon/core/configservices/quaggaservices/templates/Quagga.conf index 853b1707..1d69838f 100644 --- a/daemon/core/configservices/quaggaservices/templates/Quagga.conf +++ b/daemon/core/configservices/quaggaservices/templates/Quagga.conf @@ -1,5 +1,5 @@ -% for ifc, ip4s, ip6s, is_control in interfaces: -interface ${ifc.name} +% for iface, ip4s, ip6s, is_control in ifaces: +interface ${iface.name} % if want_ip4: % for addr in ip4s: ip address ${addr} @@ -12,7 +12,7 @@ interface ${ifc.name} % endif % if not is_control: % for service in services: - % for line in service.quagga_interface_config(ifc).split("\n"): + % for line in service.quagga_iface_config(iface).split("\n"): ${line} % endfor % endfor diff --git a/daemon/core/configservices/sercurityservices/services.py b/daemon/core/configservices/sercurityservices/services.py index 17f081cd..6e92bf62 100644 --- a/daemon/core/configservices/sercurityservices/services.py +++ b/daemon/core/configservices/sercurityservices/services.py @@ -78,10 +78,8 @@ class VpnServer(ConfigService): def data(self) -> Dict[str, Any]: address = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for x in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv4(addr): address = addr @@ -134,8 +132,6 @@ class Nat(ConfigService): def data(self) -> Dict[str, Any]: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(ifnames=ifnames) diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index 8ddf1cc7..5aa3bb54 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -25,10 +25,10 @@ class DefaultRouteService(ConfigService): def data(self) -> Dict[str, Any]: # only add default routes for linked routing nodes routes = [] - netifs = self.node.netifs(sort=True) - if netifs: - netif = netifs[0] - for x in netif.addrlist: + ifaces = self.node.get_ifaces() + if ifaces: + iface = ifaces[0] + for x in iface.addrlist: net = netaddr.IPNetwork(x).cidr if net.size > 1: router = net[1] @@ -52,10 +52,8 @@ class DefaultMulticastRouteService(ConfigService): def data(self) -> Dict[str, Any]: ifname = None - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifname = ifc.name + for iface in self.node.get_ifaces(control=False): + ifname = iface.name break return dict(ifname=ifname) @@ -76,10 +74,8 @@ class StaticRouteService(ConfigService): def data(self) -> Dict[str, Any]: routes = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for x in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv6(addr): dst = "3ffe:4::/64" @@ -107,8 +103,8 @@ class IpForwardService(ConfigService): def data(self) -> Dict[str, Any]: devnames = [] - for ifc in self.node.netifs(): - devname = utils.sysctl_devname(ifc.name) + for iface in self.node.get_ifaces(): + devname = utils.sysctl_devname(iface.name) devnames.append(devname) return dict(devnames=devnames) @@ -151,10 +147,8 @@ class DhcpService(ConfigService): def data(self) -> Dict[str, Any]: subnets = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - for x in ifc.addrlist: + for iface in self.node.get_ifaces(control=False): + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv4(addr): net = netaddr.IPNetwork(x) @@ -182,10 +176,8 @@ class DhcpClientService(ConfigService): def data(self) -> Dict[str, Any]: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict(ifnames=ifnames) @@ -220,10 +212,8 @@ class PcapService(ConfigService): def data(self) -> Dict[str, Any]: ifnames = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - ifnames.append(ifc.name) + for iface in self.node.get_ifaces(control=False): + ifnames.append(iface.name) return dict() @@ -242,19 +232,17 @@ class RadvdService(ConfigService): modes = {} def data(self) -> Dict[str, Any]: - interfaces = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue + ifaces = [] + for iface in self.node.get_ifaces(control=False): prefixes = [] - for x in ifc.addrlist: + for x in iface.addrlist: addr = x.split("/")[0] if netaddr.valid_ipv6(addr): prefixes.append(x) if not prefixes: continue - interfaces.append((ifc.name, prefixes)) - return dict(interfaces=interfaces) + ifaces.append((iface.name, prefixes)) + return dict(ifaces=ifaces) class AtdService(ConfigService): @@ -294,9 +282,7 @@ class HttpService(ConfigService): modes = {} def data(self) -> Dict[str, Any]: - interfaces = [] - for ifc in self.node.netifs(): - if getattr(ifc, "control", False): - continue - interfaces.append(ifc) - return dict(interfaces=interfaces) + ifaces = [] + for iface in self.node.get_ifaces(control=False): + ifaces.append(iface) + return dict(ifaces=ifaces) diff --git a/daemon/core/configservices/utilservices/templates/index.html b/daemon/core/configservices/utilservices/templates/index.html index aaf9d9fa..bed270ae 100644 --- a/daemon/core/configservices/utilservices/templates/index.html +++ b/daemon/core/configservices/utilservices/templates/index.html @@ -5,8 +5,8 @@

This is the default web page for this server.

The web server software is running but no content has been added, yet.

    -% for ifc in interfaces: -
  • ${ifc.name} - ${ifc.addrlist}
  • +% for iface in ifaces: +
  • ${iface.name} - ${iface.addrlist}
  • % endfor
diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 21252b6f..0f441d76 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -63,7 +63,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))] def build_xml_files( - self, config: Dict[str, str], interface: CoreInterface = None + self, config: Dict[str, str], iface: CoreInterface = None ) -> None: """ Build the necessary nem and commeffect XMLs in the given path. @@ -72,17 +72,17 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): nXXemane_commeffectnem.xml, nXXemane_commeffectshim.xml are used. :param config: emane model configuration for the node and interface - :param interface: interface for the emane node + :param iface: interface for the emane node :return: nothing """ # retrieve xml names - nem_name = emanexml.nem_file_name(self, interface) - shim_name = emanexml.shim_file_name(self, interface) + nem_name = emanexml.nem_file_name(self, iface) + shim_name = emanexml.shim_file_name(self, iface) # create and write nem document nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured") transport_type = TransportType.VIRTUAL - if interface and interface.transport_type == TransportType.RAW: + if iface and iface.transport_type == TransportType.RAW: transport_type = TransportType.RAW transport_file = emanexml.transport_file_name(self.id, transport_type) etree.SubElement(nem_element, "transport", definition=transport_file) @@ -115,7 +115,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): emanexml.create_file(shim_element, "shim", shim_file) def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Generate CommEffect events when a Link Message is received having @@ -126,7 +126,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): logging.warning("%s: EMANE event service unavailable", self.name) return - if netif is None or netif2 is None: + if iface is None or iface2 is None: logging.warning("%s: missing NEM information", self.name) return @@ -134,8 +134,8 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): # TODO: may want to split out seconds portion of delay and jitter event = CommEffectEvent() emane_node = self.session.get_node(self.id, EmaneNet) - nemid = emane_node.getnemid(netif) - nemid2 = emane_node.getnemid(netif2) + nemid = emane_node.getnemid(iface) + nemid2 = emane_node.getnemid(iface2) logging.info("sending comm effect event") event.append( nemid, diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index cb978cb9..58b85080 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -111,41 +111,39 @@ class EmaneManager(ModelManager): self.event_device: Optional[str] = None self.emane_check() - def getifcconfig( - self, node_id: int, interface: CoreInterface, model_name: str + def get_iface_config( + self, node_id: int, iface: CoreInterface, model_name: str ) -> Dict[str, str]: """ Retrieve interface configuration or node configuration if not provided. :param node_id: node id - :param interface: node interface + :param iface: node interface :param model_name: model to get configuration for :return: node/interface model configuration """ # use the network-wide config values or interface(NEM)-specific values? - if interface is None: + if iface is None: return self.get_configs(node_id=node_id, config_type=model_name) else: # don"t use default values when interface config is the same as net - # note here that using ifc.node.id as key allows for only one type + # note here that using iface.node.id as key allows for only one type # of each model per node; # TODO: use both node and interface as key - # Adamson change: first check for iface config keyed by "node:ifc.name" + # Adamson change: first check for iface config keyed by "node:iface.name" # (so that nodes w/ multiple interfaces of same conftype can have # different configs for each separate interface) - key = 1000 * interface.node.id - if interface.netindex is not None: - key += interface.netindex + key = 1000 * iface.node.id + if iface.node_id is not None: + key += iface.node_id # try retrieve interface specific configuration, avoid getting defaults config = self.get_configs(node_id=key, config_type=model_name) # otherwise retrieve the interfaces node configuration, avoid using defaults if not config: - config = self.get_configs( - node_id=interface.node.id, config_type=model_name - ) + config = self.get_configs(node_id=iface.node.id, config_type=model_name) # get non interface config, when none found if not config: @@ -265,8 +263,8 @@ class EmaneManager(ModelManager): # assumes self._objslock already held nodes = set() for emane_net in self._emane_nets.values(): - for netif in emane_net.netifs(): - nodes.add(netif.node) + for iface in emane_net.get_ifaces(): + nodes.add(iface.node) return nodes def setup(self) -> int: @@ -352,13 +350,13 @@ class EmaneManager(ModelManager): if self.numnems() > 0: self.startdaemons() - self.installnetifs() + self.install_ifaces() for node_id in self._emane_nets: emane_node = self._emane_nets[node_id] - for netif in emane_node.netifs(): + for iface in emane_node.get_ifaces(): nems.append( - (netif.node.name, netif.name, emane_node.getnemid(netif)) + (iface.node.name, iface.name, emane_node.getnemid(iface)) ) if nems: @@ -392,8 +390,8 @@ class EmaneManager(ModelManager): emane_node.name, ) emane_node.model.post_startup() - for netif in emane_node.netifs(): - netif.setposition() + for iface in emane_node.get_ifaces(): + iface.setposition() def reset(self) -> None: """ @@ -420,7 +418,7 @@ class EmaneManager(ModelManager): logging.info("stopping EMANE daemons") if self.links_enabled(): self.link_monitor.stop() - self.deinstallnetifs() + self.deinstall_ifaces() self.stopdaemons() self.stopeventmonitor() @@ -474,31 +472,31 @@ class EmaneManager(ModelManager): EMANE network and NEM interface. """ emane_node = None - netif = None + iface = None for node_id in self._emane_nets: emane_node = self._emane_nets[node_id] - netif = emane_node.getnemnetif(nemid) - if netif is not None: + iface = emane_node.get_nem_iface(nemid) + if iface is not None: break else: emane_node = None - return emane_node, netif + return emane_node, iface def get_nem_link( self, nem1: int, nem2: int, flags: MessageFlags = MessageFlags.NONE ) -> Optional[LinkData]: - emane1, netif = self.nemlookup(nem1) - if not emane1 or not netif: + emane1, iface = self.nemlookup(nem1) + if not emane1 or not iface: logging.error("invalid nem: %s", nem1) return None - node1 = netif.node - emane2, netif = self.nemlookup(nem2) - if not emane2 or not netif: + node1 = iface.node + emane2, iface = self.nemlookup(nem2) + if not emane2 or not iface: logging.error("invalid nem: %s", nem2) return None - node2 = netif.node + node2 = iface.node color = self.session.get_link_color(emane1.id) return LinkData( message_type=flags, @@ -516,7 +514,7 @@ class EmaneManager(ModelManager): count = 0 for node_id in self._emane_nets: emane_node = self._emane_nets[node_id] - count += len(emane_node.netifs()) + count += len(emane_node.ifaces) return count def buildplatformxml(self, ctrlnet: CtrlNet) -> None: @@ -607,19 +605,19 @@ class EmaneManager(ModelManager): n = node.id # control network not yet started here - self.session.add_remove_control_interface( + self.session.add_remove_control_iface( node, 0, remove=False, conf_required=False ) if otanetidx > 0: logging.info("adding ota device ctrl%d", otanetidx) - self.session.add_remove_control_interface( + self.session.add_remove_control_iface( node, otanetidx, remove=False, conf_required=False ) if eventservicenetidx >= 0: logging.info("adding event service device ctrl%d", eventservicenetidx) - self.session.add_remove_control_interface( + self.session.add_remove_control_iface( node, eventservicenetidx, remove=False, conf_required=False ) @@ -676,23 +674,23 @@ class EmaneManager(ModelManager): except CoreCommandError: logging.exception("error shutting down emane daemons") - def installnetifs(self) -> None: + def install_ifaces(self) -> None: """ Install TUN/TAP virtual interfaces into their proper namespaces now that the EMANE daemons are running. """ for key in sorted(self._emane_nets.keys()): - emane_node = self._emane_nets[key] - logging.info("emane install netifs for node: %d", key) - emane_node.installnetifs() + node = self._emane_nets[key] + logging.info("emane install interface for node(%s): %d", node.name, key) + node.install_ifaces() - def deinstallnetifs(self) -> None: + def deinstall_ifaces(self) -> None: """ Uninstall TUN/TAP virtual interfaces. """ for key in sorted(self._emane_nets.keys()): emane_node = self._emane_nets[key] - emane_node.deinstallnetifs() + emane_node.deinstall_ifaces() def doeventmonitor(self) -> bool: """ @@ -808,12 +806,12 @@ class EmaneManager(ModelManager): Returns True if successfully parsed and a Node Message was sent. """ # convert nemid to node number - _emanenode, netif = self.nemlookup(nemid) - if netif is None: + _emanenode, iface = self.nemlookup(nemid) + if iface is None: logging.info("location event for unknown NEM %s", nemid) return False - n = netif.node.id + n = iface.node.id # convert from lat/long/alt to x,y,z coordinates x, y, z = self.session.location.getxyz(lat, lon, alt) x = int(x) diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 78d5ec5e..1a14011a 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -97,28 +97,28 @@ class EmaneModel(WirelessModel): ] def build_xml_files( - self, config: Dict[str, str], interface: CoreInterface = None + self, config: Dict[str, str], iface: CoreInterface = None ) -> None: """ Builds xml files for this emane model. Creates a nem.xml file that points to both mac.xml and phy.xml definitions. :param config: emane model configuration for the node and interface - :param interface: interface for the emane node + :param iface: interface for the emane node :return: nothing """ - nem_name = emanexml.nem_file_name(self, interface) - mac_name = emanexml.mac_file_name(self, interface) - phy_name = emanexml.phy_file_name(self, interface) + nem_name = emanexml.nem_file_name(self, iface) + mac_name = emanexml.mac_file_name(self, iface) + phy_name = emanexml.phy_file_name(self, iface) # remote server for file server = None - if interface is not None: - server = interface.node.server + if iface is not None: + server = iface.node.server # check if this is external transport_type = TransportType.VIRTUAL - if interface and interface.transport_type == TransportType.RAW: + if iface and iface.transport_type == TransportType.RAW: transport_type = TransportType.RAW transport_name = emanexml.transport_file_name(self.id, transport_type) @@ -144,31 +144,31 @@ class EmaneModel(WirelessModel): """ logging.debug("emane model(%s) has no post setup tasks", self.name) - def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None: """ Invoked from MobilityModel when nodes are moved; this causes emane location events to be generated for the nodes in the moved list, making EmaneModels compatible with Ns2ScriptedMobility. :param moved: moved nodes - :param moved_netifs: interfaces that were moved + :param moved_ifaces: interfaces that were moved :return: nothing """ try: wlan = self.session.get_node(self.id, EmaneNet) - wlan.setnempositions(moved_netifs) + wlan.setnempositions(moved_ifaces) except CoreError: logging.exception("error during update") def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Invoked when a Link Message is received. Default is unimplemented. - :param netif: interface one + :param iface: interface one :param options: options for configuring link - :param netif2: interface two + :param iface2: interface two :return: nothing """ logging.warning("emane model(%s) does not support link config", self.name) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index ca9f4493..097080c3 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -212,10 +212,10 @@ class EmaneLinkMonitor: addresses = [] nodes = self.emane_manager.getnodes() for node in nodes: - for netif in node.netifs(): - if isinstance(netif.net, CtrlNet): + for iface in node.get_ifaces(): + if isinstance(iface.net, CtrlNet): ip4 = None - for x in netif.addrlist: + for x in iface.addrlist: address, prefix = x.split("/") if netaddr.valid_ipv4(address): ip4 = address diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index c4c3428b..eed51ff2 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -64,14 +64,14 @@ class EmaneNet(CoreNetworkBase): self.mobility: Optional[WayPointMobility] = None def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ The CommEffect model supports link configuration. """ if not self.model: return - self.model.linkconfig(netif, options, netif2) + self.model.linkconfig(iface, options, iface2) def config(self, conf: str) -> None: self.conf = conf @@ -82,10 +82,10 @@ class EmaneNet(CoreNetworkBase): def shutdown(self) -> None: pass - def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None: + def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None: pass - def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None: + def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None: pass def linknet(self, net: "CoreNetworkBase") -> CoreInterface: @@ -113,39 +113,33 @@ class EmaneNet(CoreNetworkBase): self.mobility = model(session=self.session, _id=self.id) self.mobility.update_config(config) - def setnemid(self, netif: CoreInterface, nemid: int) -> None: + def setnemid(self, iface: CoreInterface, nemid: int) -> None: """ Record an interface to numerical ID mapping. The Emane controller object manages and assigns these IDs for all NEMs. """ - self.nemidmap[netif] = nemid + self.nemidmap[iface] = nemid - def getnemid(self, netif: CoreInterface) -> Optional[int]: + def getnemid(self, iface: CoreInterface) -> Optional[int]: """ Given an interface, return its numerical ID. """ - if netif not in self.nemidmap: + if iface not in self.nemidmap: return None else: - return self.nemidmap[netif] + return self.nemidmap[iface] - def getnemnetif(self, nemid: int) -> Optional[CoreInterface]: + def get_nem_iface(self, nemid: int) -> Optional[CoreInterface]: """ Given a numerical NEM ID, return its interface. This returns the first interface that matches the given NEM ID. """ - for netif in self.nemidmap: - if self.nemidmap[netif] == nemid: - return netif + for iface in self.nemidmap: + if self.nemidmap[iface] == nemid: + return iface return None - def netifs(self, sort: bool = True) -> List[CoreInterface]: - """ - Retrieve list of linked interfaces sorted by node number. - """ - return sorted(self._netif.values(), key=lambda ifc: ifc.node.id) - - def installnetifs(self) -> None: + def install_ifaces(self) -> None: """ Install TAP devices into their namespaces. This is done after EMANE daemons have been started, because that is their only chance @@ -159,48 +153,48 @@ class EmaneNet(CoreNetworkBase): warntxt += "Python bindings failed to load" logging.error(warntxt) - for netif in self.netifs(): + for iface in self.get_ifaces(): external = self.session.emane.get_config( "external", self.id, self.model.name ) if external == "0": - netif.setaddrs() + iface.setaddrs() if not self.session.emane.genlocationevents(): - netif.poshook = None + iface.poshook = None continue # at this point we register location handlers for generating # EMANE location events - netif.poshook = self.setnemposition - netif.setposition() + iface.poshook = self.setnemposition + iface.setposition() - def deinstallnetifs(self) -> None: + def deinstall_ifaces(self) -> None: """ Uninstall TAP devices. This invokes their shutdown method for any required cleanup; the device may be actually removed when emanetransportd terminates. """ - for netif in self.netifs(): - if netif.transport_type == TransportType.VIRTUAL: - netif.shutdown() - netif.poshook = None + for iface in self.get_ifaces(): + if iface.transport_type == TransportType.VIRTUAL: + iface.shutdown() + iface.poshook = None def _nem_position( - self, netif: CoreInterface + self, iface: CoreInterface ) -> Optional[Tuple[int, float, float, float]]: """ Creates nem position for emane event for a given interface. - :param netif: interface to get nem emane position for + :param iface: interface to get nem emane position for :return: nem position tuple, None otherwise """ - nemid = self.getnemid(netif) - ifname = netif.localname + nemid = self.getnemid(iface) + ifname = iface.localname if nemid is None: logging.info("nemid for %s is unknown", ifname) return - node = netif.node + node = iface.node x, y, z = node.getposition() lat, lon, alt = self.session.location.getgeo(x, y, z) if node.position.alt is not None: @@ -210,30 +204,30 @@ class EmaneNet(CoreNetworkBase): alt = int(round(alt)) return nemid, lon, lat, alt - def setnemposition(self, netif: CoreInterface) -> None: + def setnemposition(self, iface: CoreInterface) -> None: """ Publish a NEM location change event using the EMANE event service. - :param netif: interface to set nem position for + :param iface: interface to set nem position for """ if self.session.emane.service is None: logging.info("position service not available") return - position = self._nem_position(netif) + position = self._nem_position(iface) if position: nemid, lon, lat, alt = position event = LocationEvent() event.append(nemid, latitude=lat, longitude=lon, altitude=alt) self.session.emane.service.publish(0, event) - def setnempositions(self, moved_netifs: List[CoreInterface]) -> None: + def setnempositions(self, moved_ifaces: List[CoreInterface]) -> None: """ Several NEMs have moved, from e.g. a WaypointMobilityModel calculation. Generate an EMANE Location Event having several - entries for each netif that has moved. + entries for each interface that has moved. """ - if len(moved_netifs) == 0: + if len(moved_ifaces) == 0: return if self.session.emane.service is None: @@ -241,8 +235,8 @@ class EmaneNet(CoreNetworkBase): return event = LocationEvent() - for netif in moved_netifs: - position = self._nem_position(netif) + for iface in moved_ifaces: + position = self._nem_position(iface) if position: nemid, lon, lat, alt = position event.append(nemid, latitude=lat, longitude=lon, altitude=alt) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 819716e3..47f45820 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -27,7 +27,7 @@ class ConfigData: possible_values: str = None groups: str = None session: int = None - interface_number: int = None + iface_id: int = None network_id: int = None opaque: str = None @@ -114,19 +114,19 @@ class LinkData: emulation_id: int = None network_id: int = None key: int = None - interface1_id: int = None - interface1_name: str = None - interface1_ip4: str = None - interface1_ip4_mask: int = None - interface1_mac: str = None - interface1_ip6: str = None - interface1_ip6_mask: int = None - interface2_id: int = None - interface2_name: str = None - interface2_ip4: str = None - interface2_ip4_mask: int = None - interface2_mac: str = None - interface2_ip6: str = None - interface2_ip6_mask: int = None + iface1_id: int = None + iface1_name: str = None + iface1_ip4: str = None + iface1_ip4_mask: int = None + iface1_mac: str = None + iface1_ip6: str = None + iface1_ip6_mask: int = None + iface2_id: int = None + iface2_name: str = None + iface2_ip4: str = None + iface2_ip4_mask: int = None + iface2_mac: str = None + iface2_ip6: str = None + iface2_ip6_mask: int = None opaque: str = None color: str = None diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index 75081447..381eb019 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -208,7 +208,7 @@ class DistributedController: "local tunnel node(%s) to remote(%s) key(%s)", node.name, host, key ) local_tap = GreTap(session=self.session, remoteip=host, key=key) - local_tap.net_client.set_interface_master(node.brname, local_tap.localname) + local_tap.net_client.set_iface_master(node.brname, local_tap.localname) # server to local logging.info( @@ -217,7 +217,7 @@ class DistributedController: remote_tap = GreTap( session=self.session, remoteip=self.address, key=key, server=server ) - remote_tap.net_client.set_interface_master(node.brname, remote_tap.localname) + remote_tap.net_client.set_iface_master(node.brname, remote_tap.localname) # save tunnels for shutdown tunnel = (local_tap, remote_tap) diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 2aecdace..25ce71ac 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -155,7 +155,7 @@ class IpPrefixes: raise ValueError("ip6 prefixes have not been set") return str(self.ip6[node_id]) - def gen_interface(self, node_id: int, name: str = None, mac: str = None): + def gen_iface(self, node_id: int, name: str = None, mac: str = None): """ Creates interface data for linking nodes, using the nodes unique id for generation, along with a random mac address, unless provided. @@ -188,7 +188,7 @@ class IpPrefixes: name=name, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask, mac=mac ) - def create_interface( + def create_iface( self, node: "CoreNode", name: str = None, mac: str = None ) -> InterfaceData: """ @@ -201,6 +201,6 @@ class IpPrefixes: generation :return: new interface data for the provided node """ - interface_data = self.gen_interface(node.id, name, mac) - interface_data.id = node.newifindex() - return interface_data + iface_data = self.gen_iface(node.id, name, mac) + iface_data.id = node.next_iface_id() + return iface_data diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index e63c30c7..2dc5ad12 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -203,7 +203,7 @@ class Session: common_networks = node1.commonnets(node1) if not common_networks: raise CoreError("no common network found for wireless link/unlink") - for common_network, interface1, interface2 in common_networks: + for common_network, iface1, iface2 in common_networks: if not isinstance(common_network, (WlanNode, EmaneNet)): logging.info( "skipping common network that is not wireless/emane: %s", @@ -211,16 +211,16 @@ class Session: ) continue if connect: - common_network.link(interface1, interface2) + common_network.link(iface1, iface2) else: - common_network.unlink(interface1, interface2) + common_network.unlink(iface1, iface2) def add_link( self, node1_id: int, node2_id: int, - interface1_data: InterfaceData = None, - interface2_data: InterfaceData = None, + iface1_data: InterfaceData = None, + iface2_data: InterfaceData = None, options: LinkOptions = None, ) -> Tuple[CoreInterface, CoreInterface]: """ @@ -228,9 +228,9 @@ class Session: :param node1_id: node one id :param node2_id: node two id - :param interface1_data: node one interface + :param iface1_data: node one interface data, defaults to none - :param interface2_data: node two interface + :param iface2_data: node two interface data, defaults to none :param options: data for creating link, defaults to no options @@ -240,8 +240,8 @@ class Session: options = LinkOptions() node1 = self.get_node(node1_id, NodeBase) node2 = self.get_node(node2_id, NodeBase) - interface1 = None - interface2 = None + iface1 = None + iface2 = None # wireless link if options.type == LinkTypes.WIRELESS: @@ -258,22 +258,22 @@ class Session: logging.info("linking ptp: %s - %s", node1.name, node2.name) start = self.state.should_start() ptp = self.create_node(PtpNet, start) - interface1 = node1.newnetif(ptp, interface1_data) - interface2 = node2.newnetif(ptp, interface2_data) - ptp.linkconfig(interface1, options) + iface1 = node1.new_iface(ptp, iface1_data) + iface2 = node2.new_iface(ptp, iface2_data) + ptp.linkconfig(iface1, options) if not options.unidirectional: - ptp.linkconfig(interface2, options) + ptp.linkconfig(iface2, options) # link node to net elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - interface1 = node1.newnetif(node2, interface1_data) + iface1 = node1.new_iface(node2, iface1_data) if not isinstance(node2, (EmaneNet, WlanNode)): - node2.linkconfig(interface1, options) + node2.linkconfig(iface1, options) # link net to node elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - interface2 = node2.newnetif(node1, interface2_data) + iface2 = node2.new_iface(node1, iface2_data) wireless_net = isinstance(node1, (EmaneNet, WlanNode)) if not options.unidirectional and not wireless_net: - node1.linkconfig(interface2, options) + node1.linkconfig(iface2, options) # network to network elif isinstance(node1, CoreNetworkBase) and isinstance( node2, CoreNetworkBase @@ -281,12 +281,12 @@ class Session: logging.info( "linking network to network: %s - %s", node1.name, node2.name ) - interface1 = node1.linknet(node2) - node1.linkconfig(interface1, options) + iface1 = node1.linknet(node2) + node1.linkconfig(iface1, options) if not options.unidirectional: - interface1.swapparams("_params_up") - node2.linkconfig(interface1, options) - interface1.swapparams("_params_up") + iface1.swapparams("_params_up") + node2.linkconfig(iface1, options) + iface1.swapparams("_params_up") else: raise CoreError( f"cannot link node1({type(node1)}) node2({type(node2)})" @@ -296,19 +296,19 @@ class Session: key = options.key if isinstance(node1, TunnelNode): logging.info("setting tunnel key for: %s", node1.name) - node1.setkey(key, interface1_data) + node1.setkey(key, iface1_data) if isinstance(node2, TunnelNode): logging.info("setting tunnel key for: %s", node2.name) - node2.setkey(key, interface2_data) + node2.setkey(key, iface2_data) self.sdt.add_link(node1_id, node2_id) - return interface1, interface2 + return iface1, iface2 def delete_link( self, node1_id: int, node2_id: int, - interface1_id: int = None, - interface2_id: int = None, + iface1_id: int = None, + iface2_id: int = None, link_type: LinkTypes = LinkTypes.WIRED, ) -> None: """ @@ -316,8 +316,8 @@ class Session: :param node1_id: node one id :param node2_id: node two id - :param interface1_id: interface id for node one - :param interface2_id: interface id for node two + :param iface1_id: interface id for node one + :param iface2_id: interface id for node two :param link_type: link type to delete :return: nothing :raises core.CoreError: when no common network is found for link being deleted @@ -328,9 +328,9 @@ class Session: "deleting link(%s) node(%s):interface(%s) node(%s):interface(%s)", link_type.name, node1.name, - interface1_id, + iface1_id, node2.name, - interface2_id, + iface2_id, ) # wireless link @@ -345,37 +345,29 @@ class Session: # wired link else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): - interface1 = node1.netif(interface1_id) - interface2 = node2.netif(interface2_id) - if not interface1: - raise CoreError( - f"node({node1.name}) missing interface({interface1_id})" - ) - if not interface2: - raise CoreError( - f"node({node2.name}) missing interface({interface2_id})" - ) - if interface1.net != interface2.net: + iface1 = node1.get_iface(iface1_id) + iface2 = node2.get_iface(iface2_id) + if iface1.net != iface2.net: raise CoreError( f"node1({node1.name}) node2({node2.name}) " "not connected to same net" ) - ptp = interface1.net - node1.delnetif(interface1_id) - node2.delnetif(interface2_id) + ptp = iface1.net + node1.delete_iface(iface1_id) + node2.delete_iface(iface2_id) self.delete_node(ptp.id) elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - node1.delnetif(interface1_id) + node1.delete_iface(iface1_id) elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - node2.delnetif(interface2_id) + node2.delete_iface(iface2_id) self.sdt.delete_link(node1_id, node2_id) def update_link( self, node1_id: int, node2_id: int, - interface1_id: int = None, - interface2_id: int = None, + iface1_id: int = None, + iface2_id: int = None, options: LinkOptions = None, ) -> None: """ @@ -383,8 +375,8 @@ class Session: :param node1_id: node one id :param node2_id: node two id - :param interface1_id: interface id for node one - :param interface2_id: interface id for node two + :param iface1_id: interface id for node one + :param iface2_id: interface id for node two :param options: data to update link with :return: nothing :raises core.CoreError: when updating a wireless type link, when there is a @@ -398,9 +390,9 @@ class Session: "update link(%s) node(%s):interface(%s) node(%s):interface(%s)", options.type.name, node1.name, - interface1_id, + iface1_id, node2.name, - interface2_id, + iface2_id, ) # wireless link @@ -408,54 +400,54 @@ class Session: raise CoreError("cannot update wireless link") else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): - interface1 = node1.netif(interface1_id) - interface2 = node2.netif(interface2_id) - if not interface1: + iface1 = node1.ifaces.get(iface1_id) + iface2 = node2.ifaces.get(iface2_id) + if not iface1: raise CoreError( - f"node({node1.name}) missing interface({interface1_id})" + f"node({node1.name}) missing interface({iface1_id})" ) - if not interface2: + if not iface2: raise CoreError( - f"node({node2.name}) missing interface({interface2_id})" + f"node({node2.name}) missing interface({iface2_id})" ) - if interface1.net != interface2.net: + if iface1.net != iface2.net: raise CoreError( f"node1({node1.name}) node2({node2.name}) " "not connected to same net" ) - ptp = interface1.net - ptp.linkconfig(interface1, options, interface2) + ptp = iface1.net + ptp.linkconfig(iface1, options, iface2) if not options.unidirectional: - ptp.linkconfig(interface2, options, interface1) + ptp.linkconfig(iface2, options, iface1) elif isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNetworkBase): - interface = node1.netif(interface1_id) - node2.linkconfig(interface, options) + iface = node1.get_iface(iface1_id) + node2.linkconfig(iface, options) elif isinstance(node2, CoreNodeBase) and isinstance(node1, CoreNetworkBase): - interface = node2.netif(interface2_id) - node1.linkconfig(interface, options) + iface = node2.get_iface(iface2_id) + node1.linkconfig(iface, options) elif isinstance(node1, CoreNetworkBase) and isinstance( node2, CoreNetworkBase ): - interface = node1.getlinknetif(node2) + iface = node1.get_linked_iface(node2) upstream = False - if not interface: + if not iface: upstream = True - interface = node2.getlinknetif(node1) - if not interface: + iface = node2.get_linked_iface(node1) + if not iface: raise CoreError("modify unknown link between nets") if upstream: - interface.swapparams("_params_up") - node1.linkconfig(interface, options) - interface.swapparams("_params_up") + iface.swapparams("_params_up") + node1.linkconfig(iface, options) + iface.swapparams("_params_up") else: - node1.linkconfig(interface, options) + node1.linkconfig(iface, options) if not options.unidirectional: if upstream: - node2.linkconfig(interface, options) + node2.linkconfig(iface, options) else: - interface.swapparams("_params_up") - node2.linkconfig(interface, options) - interface.swapparams("_params_up") + iface.swapparams("_params_up") + node2.linkconfig(iface, options) + iface.swapparams("_params_up") else: raise CoreError( f"cannot update link node1({type(node1)}) node2({type(node2)})" @@ -553,7 +545,7 @@ class Session: is_boot_node = isinstance(node, CoreNodeBase) and not isinstance(node, Rj45Node) if self.state == EventTypes.RUNTIME_STATE and is_boot_node: self.write_nodes() - self.add_remove_control_interface(node=node, remove=False) + self.add_remove_control_iface(node=node, remove=False) self.services.boot_services(node) self.sdt.add_node(node) @@ -1268,7 +1260,7 @@ class Session: self.emane.shutdown() # update control interface hosts - self.update_control_interface_hosts(remove=True) + self.update_control_iface_hosts(remove=True) # remove all four possible control networks self.add_remove_control_net(0, remove=True) @@ -1314,7 +1306,7 @@ class Session: :return: nothing """ logging.info("booting node(%s): %s", node.name, [x.name for x in node.services]) - self.add_remove_control_interface(node=node, remove=False) + self.add_remove_control_iface(node=node, remove=False) self.services.boot_services(node) node.start_config_services() @@ -1338,7 +1330,7 @@ class Session: total = time.monotonic() - start logging.debug("boot run time: %s", total) if not exceptions: - self.update_control_interface_hosts() + self.update_control_iface_hosts() return exceptions def get_control_net_prefixes(self) -> List[str]: @@ -1356,7 +1348,7 @@ class Session: p0 = p return [p0, p1, p2, p3] - def get_control_net_server_interfaces(self) -> List[str]: + def get_control_net_server_ifaces(self) -> List[str]: """ Retrieve control net server interfaces. @@ -1424,7 +1416,7 @@ class Session: else: prefix_spec = CtrlNet.DEFAULT_PREFIX_LIST[net_index] logging.debug("prefix spec: %s", prefix_spec) - server_interface = self.get_control_net_server_interfaces()[net_index] + server_iface = self.get_control_net_server_ifaces()[net_index] # return any existing controlnet bridge try: @@ -1465,7 +1457,7 @@ class Session: _id, prefix, updown_script, - server_interface, + server_iface, ) control_net = self.create_node( CtrlNet, @@ -1473,11 +1465,11 @@ class Session: prefix, _id=_id, updown_script=updown_script, - serverintf=server_interface, + serverintf=server_iface, ) return control_net - def add_remove_control_interface( + def add_remove_control_iface( self, node: CoreNode, net_index: int = 0, @@ -1503,27 +1495,27 @@ class Session: if not node: return # ctrl# already exists - if node.netif(control_net.CTRLIF_IDX_BASE + net_index): + if node.ifaces.get(control_net.CTRLIF_IDX_BASE + net_index): return try: ip4 = control_net.prefix[node.id] ip4_mask = control_net.prefix.prefixlen - interface_data = InterfaceData( + iface_data = InterfaceData( id=control_net.CTRLIF_IDX_BASE + net_index, name=f"ctrl{net_index}", mac=utils.random_mac(), ip4=ip4, ip4_mask=ip4_mask, ) - interface = node.newnetif(control_net, interface_data) - interface.control = True + iface = node.new_iface(control_net, iface_data) + iface.control = True except ValueError: msg = f"Control interface not added to node {node.id}. " msg += f"Invalid control network prefix ({control_net.prefix}). " msg += "A longer prefix length may be required for this many nodes." logging.exception(msg) - def update_control_interface_hosts( + def update_control_iface_hosts( self, net_index: int = 0, remove: bool = False ) -> None: """ @@ -1549,9 +1541,9 @@ class Session: return entries = [] - for interface in control_net.netifs(): - name = interface.node.name - for address in interface.addrlist: + for iface in control_net.get_ifaces(): + name = iface.node.name + for address in iface.addrlist: address = address.split("/")[0] entries.append(f"{address} {name}") diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 5c1c52a0..3be58e17 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -57,8 +57,8 @@ class CoreClient: self.read_config() # helpers - self.interface_to_edge = {} - self.interfaces_manager = InterfaceManager(self.app) + self.iface_to_edge = {} + self.ifaces_manager = InterfaceManager(self.app) # session data self.state = None @@ -91,8 +91,8 @@ class CoreClient: def reset(self): # helpers - self.interfaces_manager.reset() - self.interface_to_edge.clear() + self.ifaces_manager.reset() + self.iface_to_edge.clear() # session data self.canvas_nodes.clear() self.links.clear() @@ -263,7 +263,7 @@ class CoreClient: self.emane_config = response.config # update interface manager - self.interfaces_manager.joined(session.links) + self.ifaces_manager.joined(session.links) # draw session self.app.canvas.reset_and_redraw(session) @@ -278,11 +278,11 @@ class CoreClient: # get emane model config response = self.client.get_emane_model_configs(self.session_id) for config in response.configs: - interface = None - if config.interface != -1: - interface = config.interface + iface_id = None + if config.iface_id != -1: + iface_id = config.iface_id canvas_node = self.canvas_nodes[config.node_id] - canvas_node.emane_model_configs[(config.model, interface)] = dict( + canvas_node.emane_model_configs[(config.model, iface_id)] = dict( config.config ) @@ -460,16 +460,16 @@ class CoreClient: self.app.show_grpc_exception("Edit Node Error", e) def start_session(self) -> core_pb2.StartSessionResponse: - self.interfaces_manager.reset_mac() + self.ifaces_manager.reset_mac() nodes = [x.core_node for x in self.canvas_nodes.values()] links = [] for edge in self.links.values(): link = core_pb2.Link() link.CopyFrom(edge.link) - if link.HasField("interface1") and not link.interface1.mac: - link.interface1.mac = self.interfaces_manager.next_mac() - if link.HasField("interface2") and not link.interface2.mac: - link.interface2.mac = self.interfaces_manager.next_mac() + if link.HasField("iface1") and not link.iface1.mac: + link.iface1.mac = self.ifaces_manager.next_mac() + if link.HasField("iface2") and not link.iface2.mac: + link.iface2.mac = self.ifaces_manager.next_mac() links.append(link) wlan_configs = self.get_wlan_configs_proto() mobility_configs = self.get_mobility_configs_proto() @@ -689,8 +689,8 @@ class CoreClient: self.session_id, link_proto.node1_id, link_proto.node2_id, - link_proto.interface1, - link_proto.interface2, + link_proto.iface1, + link_proto.iface2, link_proto.options, ) logging.debug("create link: %s", response) @@ -733,7 +733,7 @@ class CoreClient: config_proto.node_id, config_proto.model, config_proto.config, - config_proto.interface_id, + config_proto.iface_id, ) if self.emane_config: config = {x: self.emane_config[x].value for x in self.emane_config} @@ -824,31 +824,26 @@ class CoreClient: for edge in edges: del self.links[edge.token] links.append(edge.link) - self.interfaces_manager.removed(links) + self.ifaces_manager.removed(links) - def create_interface(self, canvas_node: CanvasNode) -> core_pb2.Interface: + def create_iface(self, canvas_node: CanvasNode) -> core_pb2.Interface: node = canvas_node.core_node - ip4, ip6 = self.interfaces_manager.get_ips(node) - ip4_mask = self.interfaces_manager.ip4_mask - ip6_mask = self.interfaces_manager.ip6_mask - interface_id = canvas_node.next_interface_id() - name = f"eth{interface_id}" - interface = core_pb2.Interface( - id=interface_id, - name=name, - ip4=ip4, - ip4mask=ip4_mask, - ip6=ip6, - ip6mask=ip6_mask, + ip4, ip6 = self.ifaces_manager.get_ips(node) + ip4_mask = self.ifaces_manager.ip4_mask + ip6_mask = self.ifaces_manager.ip6_mask + iface_id = canvas_node.next_iface_id() + name = f"eth{iface_id}" + iface = core_pb2.Interface( + id=iface_id, name=name, ip4=ip4, ip4mask=ip4_mask, ip6=ip6, ip6mask=ip6_mask ) logging.info( "create node(%s) interface(%s) IPv4(%s) IPv6(%s)", node.name, - interface.name, - interface.ip4, - interface.ip6, + iface.name, + iface.ip4, + iface.ip6, ) - return interface + return iface def create_link( self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode @@ -861,34 +856,34 @@ class CoreClient: dst_node = canvas_dst_node.core_node # determine subnet - self.interfaces_manager.determine_subnets(canvas_src_node, canvas_dst_node) + self.ifaces_manager.determine_subnets(canvas_src_node, canvas_dst_node) - src_interface = None + src_iface = None if NodeUtils.is_container_node(src_node.type): - src_interface = self.create_interface(canvas_src_node) - self.interface_to_edge[(src_node.id, src_interface.id)] = edge.token + src_iface = self.create_iface(canvas_src_node) + self.iface_to_edge[(src_node.id, src_iface.id)] = edge.token - dst_interface = None + dst_iface = None if NodeUtils.is_container_node(dst_node.type): - dst_interface = self.create_interface(canvas_dst_node) - self.interface_to_edge[(dst_node.id, dst_interface.id)] = edge.token + dst_iface = self.create_iface(canvas_dst_node) + self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token link = core_pb2.Link( type=core_pb2.LinkType.WIRED, node1_id=src_node.id, node2_id=dst_node.id, - interface1=src_interface, - interface2=dst_interface, + iface1=src_iface, + iface2=dst_iface, ) # assign after creating link proto, since interfaces are copied - if src_interface: - interface1 = link.interface1 - edge.src_interface = interface1 - canvas_src_node.interfaces[interface1.id] = interface1 - if dst_interface: - interface2 = link.interface2 - edge.dst_interface = interface2 - canvas_dst_node.interfaces[interface2.id] = interface2 + if src_iface: + iface1 = link.iface1 + edge.src_iface = iface1 + canvas_src_node.ifaces[iface1.id] = iface1 + if dst_iface: + iface2 = link.iface2 + edge.dst_iface = iface2 + canvas_dst_node.ifaces[iface2.id] = iface2 edge.set_link(link) self.links[edge.token] = edge logging.info("Add link between %s and %s", src_node.name, dst_node.name) @@ -928,12 +923,12 @@ class CoreClient: continue node_id = canvas_node.core_node.id for key, config in canvas_node.emane_model_configs.items(): - model, interface = key + model, iface_id = key config = {x: config[x].value for x in config} - if interface is None: - interface = -1 + if iface_id is None: + iface_id = -1 config_proto = EmaneModelConfig( - node_id=node_id, interface_id=interface, model=model, config=config + node_id=node_id, iface_id=iface_id, model=model, config=config ) configs.append(config_proto) return configs @@ -1021,19 +1016,19 @@ class CoreClient: return dict(config) def get_emane_model_config( - self, node_id: int, model: str, interface: int = None + self, node_id: int, model: str, iface_id: int = None ) -> Dict[str, common_pb2.ConfigOption]: - if interface is None: - interface = -1 + if iface_id is None: + iface_id = -1 response = self.client.get_emane_model_config( - self.session_id, node_id, model, interface + self.session_id, node_id, model, iface_id ) config = response.config logging.debug( "get emane model config: node id: %s, EMANE model: %s, interface: %s, config: %s", node_id, model, - interface, + iface_id, config, ) return dict(config) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index 000ebb05..8f7ca089 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -56,7 +56,7 @@ class EmaneModelDialog(Dialog): app: "Application", canvas_node: "CanvasNode", model: str, - interface: int = None, + iface_id: int = None, ): super().__init__( app, f"{canvas_node.core_node.name} {model} Configuration", master=master @@ -64,16 +64,16 @@ class EmaneModelDialog(Dialog): self.canvas_node = canvas_node self.node = canvas_node.core_node self.model = f"emane_{model}" - self.interface = interface + self.iface_id = iface_id self.config_frame = None self.has_error = False try: self.config = self.canvas_node.emane_model_configs.get( - (self.model, self.interface) + (self.model, self.iface_id) ) if not self.config: self.config = self.app.core.get_emane_model_config( - self.node.id, self.model, self.interface + self.node.id, self.model, self.iface_id ) self.draw() except grpc.RpcError as e: @@ -103,7 +103,7 @@ class EmaneModelDialog(Dialog): def click_apply(self): self.config_frame.parse_config() - key = (self.model, self.interface) + key = (self.model, self.iface_id) self.canvas_node.emane_model_configs[key] = self.config self.destroy() diff --git a/daemon/core/gui/dialogs/ipdialog.py b/daemon/core/gui/dialogs/ipdialog.py index 62f5d0ba..d31dcdff 100644 --- a/daemon/core/gui/dialogs/ipdialog.py +++ b/daemon/core/gui/dialogs/ipdialog.py @@ -146,6 +146,6 @@ class IpConfigDialog(Dialog): ip_config.ip6 = self.ip6 ip_config.ip4s = ip4s ip_config.ip6s = ip6s - self.app.core.interfaces_manager.update_ips(self.ip4, self.ip6) + self.app.core.ifaces_manager.update_ips(self.ip4, self.ip6) self.app.save_config() self.destroy() diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index 9c3fc987..adf8156f 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -227,21 +227,21 @@ class LinkConfigurationDialog(Dialog): ) link.options.CopyFrom(options) - interface1_id = None - if link.HasField("interface1"): - interface1_id = link.interface1.id - interface2_id = None - if link.HasField("interface2"): - interface2_id = link.interface2.id + iface1_id = None + if link.HasField("iface1"): + iface1_id = link.iface1.id + iface2_id = None + if link.HasField("iface2"): + iface2_id = link.iface2.id if not self.is_symmetric: link.options.unidirectional = True - asym_interface1 = None - if interface1_id: - asym_interface1 = core_pb2.Interface(id=interface1_id) - asym_interface2 = None - if interface2_id: - asym_interface2 = core_pb2.Interface(id=interface2_id) + asym_iface1 = None + if iface1_id: + asym_iface1 = core_pb2.Interface(id=iface1_id) + asym_iface2 = None + if iface2_id: + asym_iface2 = core_pb2.Interface(id=iface2_id) down_bandwidth = get_int(self.down_bandwidth) down_jitter = get_int(self.down_jitter) down_delay = get_int(self.down_delay) @@ -258,8 +258,8 @@ class LinkConfigurationDialog(Dialog): self.edge.asymmetric_link = core_pb2.Link( node1_id=link.node2_id, node2_id=link.node1_id, - interface1=asym_interface1, - interface2=asym_interface2, + iface1=asym_iface1, + iface2=asym_iface2, options=options, ) else: @@ -273,8 +273,8 @@ class LinkConfigurationDialog(Dialog): link.node1_id, link.node2_id, link.options, - interface1_id, - interface2_id, + iface1_id, + iface2_id, ) if self.edge.asymmetric_link: self.app.core.client.edit_link( @@ -282,8 +282,8 @@ class LinkConfigurationDialog(Dialog): link.node2_id, link.node1_id, self.edge.asymmetric_link.options, - interface1_id, - interface2_id, + iface1_id, + iface2_id, ) self.destroy() diff --git a/daemon/core/gui/dialogs/macdialog.py b/daemon/core/gui/dialogs/macdialog.py index caca9fd0..46414cf9 100644 --- a/daemon/core/gui/dialogs/macdialog.py +++ b/daemon/core/gui/dialogs/macdialog.py @@ -55,7 +55,7 @@ class MacConfigDialog(Dialog): if not netaddr.valid_mac(mac): messagebox.showerror("MAC Error", f"{mac} is an invalid mac") else: - self.app.core.interfaces_manager.mac = netaddr.EUI(mac) + self.app.core.ifaces_manager.mac = netaddr.EUI(mac) self.app.guiconfig.mac = mac self.app.save_config() self.destroy() diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 0d46ae06..29ce2010 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -111,7 +111,7 @@ class NodeConfigDialog(Dialog): if self.node.server: server = self.node.server self.server = tk.StringVar(value=server) - self.interfaces = {} + self.ifaces = {} self.draw() def draw(self): @@ -183,53 +183,53 @@ class NodeConfigDialog(Dialog): row += 1 if NodeUtils.is_rj45_node(self.node.type): - response = self.app.core.client.get_interfaces() + response = self.app.core.client.get_ifaces() logging.debug("host machine available interfaces: %s", response) - interfaces = ListboxScroll(frame) - interfaces.listbox.config(state=state) - interfaces.grid( + ifaces = ListboxScroll(frame) + ifaces.listbox.config(state=state) + ifaces.grid( row=row, column=0, columnspan=2, sticky="ew", padx=PADX, pady=PADY ) - for inf in sorted(response.interfaces[:]): - interfaces.listbox.insert(tk.END, inf) + for inf in sorted(response.ifaces[:]): + ifaces.listbox.insert(tk.END, inf) row += 1 - interfaces.listbox.bind("<>", self.interface_select) + ifaces.listbox.bind("<>", self.iface_select) # interfaces - if self.canvas_node.interfaces: - self.draw_interfaces() + if self.canvas_node.ifaces: + self.draw_ifaces() self.draw_spacer() self.draw_buttons() - def draw_interfaces(self): + def draw_ifaces(self): notebook = ttk.Notebook(self.top) notebook.grid(sticky="nsew", pady=PADY) self.top.rowconfigure(notebook.grid_info()["row"], weight=1) state = tk.DISABLED if self.app.core.is_runtime() else tk.NORMAL - for interface_id in sorted(self.canvas_node.interfaces): - interface = self.canvas_node.interfaces[interface_id] + for iface_id in sorted(self.canvas_node.ifaces): + iface = self.canvas_node.ifaces[iface_id] tab = ttk.Frame(notebook, padding=FRAME_PAD) tab.grid(sticky="nsew", pady=PADY) tab.columnconfigure(1, weight=1) tab.columnconfigure(2, weight=1) - notebook.add(tab, text=interface.name) + notebook.add(tab, text=iface.name) row = 0 - emane_node = self.canvas_node.has_emane_link(interface.id) + emane_node = self.canvas_node.has_emane_link(iface.id) if emane_node: emane_model = emane_node.emane.split("_")[1] button = ttk.Button( tab, text=f"Configure EMANE {emane_model}", - command=lambda: self.click_emane_config(emane_model, interface.id), + command=lambda: self.click_emane_config(emane_model, iface.id), ) button.grid(row=row, sticky="ew", columnspan=3, pady=PADY) row += 1 label = ttk.Label(tab, text="MAC") label.grid(row=row, column=0, padx=PADX, pady=PADY) - auto_set = not interface.mac + auto_set = not iface.mac mac_state = tk.DISABLED if auto_set else tk.NORMAL is_auto = tk.BooleanVar(value=auto_set) checkbutton = ttk.Checkbutton( @@ -237,7 +237,7 @@ class NodeConfigDialog(Dialog): ) checkbutton.var = is_auto checkbutton.grid(row=row, column=1, padx=PADX) - mac = tk.StringVar(value=interface.mac) + mac = tk.StringVar(value=iface.mac) entry = ttk.Entry(tab, textvariable=mac, state=mac_state) entry.grid(row=row, column=2, sticky="ew") func = partial(mac_auto, is_auto, entry, mac) @@ -247,8 +247,8 @@ class NodeConfigDialog(Dialog): label = ttk.Label(tab, text="IPv4") label.grid(row=row, column=0, padx=PADX, pady=PADY) ip4_net = "" - if interface.ip4: - ip4_net = f"{interface.ip4}/{interface.ip4mask}" + if iface.ip4: + ip4_net = f"{iface.ip4}/{iface.ip4mask}" ip4 = tk.StringVar(value=ip4_net) entry = ttk.Entry(tab, textvariable=ip4, state=state) entry.grid(row=row, column=1, columnspan=2, sticky="ew") @@ -257,13 +257,13 @@ class NodeConfigDialog(Dialog): label = ttk.Label(tab, text="IPv6") label.grid(row=row, column=0, padx=PADX, pady=PADY) ip6_net = "" - if interface.ip6: - ip6_net = f"{interface.ip6}/{interface.ip6mask}" + if iface.ip6: + ip6_net = f"{iface.ip6}/{iface.ip6mask}" ip6 = tk.StringVar(value=ip6_net) entry = ttk.Entry(tab, textvariable=ip6, state=state) entry.grid(row=row, column=1, columnspan=2, sticky="ew") - self.interfaces[interface.id] = InterfaceData(is_auto, mac, ip4, ip6) + self.ifaces[iface.id] = InterfaceData(is_auto, mac, ip4, ip6) def draw_buttons(self): frame = ttk.Frame(self.top) @@ -277,9 +277,9 @@ class NodeConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_emane_config(self, emane_model: str, interface_id: int): + def click_emane_config(self, emane_model: str, iface_id: int): dialog = EmaneModelDialog( - self, self.app, self.canvas_node, emane_model, interface_id + self, self.app, self.canvas_node, emane_model, iface_id ) dialog.show() @@ -309,12 +309,12 @@ class NodeConfigDialog(Dialog): self.canvas_node.image = self.image # update node interface data - for interface in self.canvas_node.interfaces.values(): - data = self.interfaces[interface.id] + for iface in self.canvas_node.ifaces.values(): + data = self.ifaces[iface.id] # validate ip4 ip4_net = data.ip4.get() - if not check_ip4(self, interface.name, ip4_net): + if not check_ip4(self, iface.name, ip4_net): error = True break if ip4_net: @@ -322,12 +322,12 @@ class NodeConfigDialog(Dialog): ip4mask = int(ip4mask) else: ip4, ip4mask = "", 0 - interface.ip4 = ip4 - interface.ip4mask = ip4mask + iface.ip4 = ip4 + iface.ip4mask = ip4mask # validate ip6 ip6_net = data.ip6.get() - if not check_ip6(self, interface.name, ip6_net): + if not check_ip6(self, iface.name, ip6_net): error = True break if ip6_net: @@ -335,28 +335,28 @@ class NodeConfigDialog(Dialog): ip6mask = int(ip6mask) else: ip6, ip6mask = "", 0 - interface.ip6 = ip6 - interface.ip6mask = ip6mask + iface.ip6 = ip6 + iface.ip6mask = ip6mask mac = data.mac.get() auto_mac = data.is_auto.get() if not auto_mac and not netaddr.valid_mac(mac): - title = f"MAC Error for {interface.name}" + title = f"MAC Error for {iface.name}" messagebox.showerror(title, "Invalid MAC Address") error = True break elif not auto_mac: mac = netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded) - interface.mac = str(mac) + iface.mac = str(mac) # redraw if not error: self.canvas_node.redraw() self.destroy() - def interface_select(self, event: tk.Event): + def iface_select(self, event: tk.Event): listbox = event.widget cur = listbox.curselection() if cur: - interface = listbox.get(cur[0]) - self.name.set(interface) + iface = listbox.get(cur[0]) + self.name.set(iface) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 1d2264eb..152e1a2f 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -259,8 +259,8 @@ class CanvasEdge(Edge): Create an instance of canvas edge object """ super().__init__(canvas, src) - self.src_interface = None - self.dst_interface = None + self.src_iface = None + self.dst_iface = None self.text_src = None self.text_dst = None self.link = None @@ -283,25 +283,25 @@ class CanvasEdge(Edge): self.link = link self.draw_labels() - def interface_label(self, interface: core_pb2.Interface) -> str: + def iface_label(self, iface: core_pb2.Interface) -> str: label = "" - if interface.name and self.canvas.show_interface_names.get(): - label = f"{interface.name}" - if interface.ip4 and self.canvas.show_ip4s.get(): + if iface.name and self.canvas.show_iface_names.get(): + label = f"{iface.name}" + if iface.ip4 and self.canvas.show_ip4s.get(): label = f"{label}\n" if label else "" - label += f"{interface.ip4}/{interface.ip4mask}" - if interface.ip6 and self.canvas.show_ip6s.get(): + label += f"{iface.ip4}/{iface.ip4mask}" + if iface.ip6 and self.canvas.show_ip6s.get(): label = f"{label}\n" if label else "" - label += f"{interface.ip6}/{interface.ip6mask}" + label += f"{iface.ip6}/{iface.ip6mask}" return label def create_node_labels(self) -> Tuple[str, str]: label1 = None - if self.link.HasField("interface1"): - label1 = self.interface_label(self.link.interface1) + if self.link.HasField("iface1"): + label1 = self.iface_label(self.link.iface1) label2 = None - if self.link.HasField("interface2"): - label2 = self.interface_label(self.link.interface2) + if self.link.HasField("iface2"): + label2 = self.iface_label(self.link.iface2) return label1, label2 def draw_labels(self) -> None: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 90dcd9f6..269e3973 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -97,7 +97,7 @@ class CanvasGraph(tk.Canvas): self.show_link_labels = ShowVar(self, tags.LINK_LABEL, value=True) self.show_grid = ShowVar(self, tags.GRIDLINE, value=True) self.show_annotations = ShowVar(self, tags.ANNOTATION, value=True) - self.show_interface_names = BooleanVar(value=False) + self.show_iface_names = BooleanVar(value=False) self.show_ip4s = BooleanVar(value=True) self.show_ip6s = BooleanVar(value=True) @@ -136,7 +136,7 @@ class CanvasGraph(tk.Canvas): self.show_link_labels.set(True) self.show_grid.set(True) self.show_annotations.set(True) - self.show_interface_names.set(False) + self.show_iface_names.set(False) self.show_ip4s.set(True) self.show_ip6s.set(True) @@ -195,19 +195,19 @@ class CanvasGraph(tk.Canvas): return valid_topleft and valid_bottomright def set_throughputs(self, throughputs_event: core_pb2.ThroughputsEvent): - for interface_throughput in throughputs_event.interface_throughputs: - node_id = interface_throughput.node_id - interface_id = interface_throughput.interface_id - throughput = interface_throughput.throughput - interface_to_edge_id = (node_id, interface_id) - token = self.core.interface_to_edge.get(interface_to_edge_id) + for iface_throughput in throughputs_event.iface_throughputs: + node_id = iface_throughput.node_id + iface_id = iface_throughput.iface_id + throughput = iface_throughput.throughput + iface_to_edge_id = (node_id, iface_id) + token = self.core.iface_to_edge.get(iface_to_edge_id) if not token: continue edge = self.edges.get(token) if edge: edge.set_throughput(throughput) else: - del self.core.interface_to_edge[interface_to_edge_id] + del self.core.iface_to_edge[iface_to_edge_id] def draw_grid(self): """ @@ -321,18 +321,16 @@ class CanvasGraph(tk.Canvas): canvas_node2.edges.add(edge) self.edges[edge.token] = edge self.core.links[edge.token] = edge - if link.HasField("interface1"): - interface1 = link.interface1 - self.core.interface_to_edge[(node1.id, interface1.id)] = token - canvas_node1.interfaces[interface1.id] = interface1 - edge.src_interface = interface1 - if link.HasField("interface2"): - interface2 = link.interface2 - self.core.interface_to_edge[ - (node2.id, interface2.id) - ] = edge.token - canvas_node2.interfaces[interface2.id] = interface2 - edge.dst_interface = interface2 + if link.HasField("iface1"): + iface1 = link.iface1 + self.core.iface_to_edge[(node1.id, iface1.id)] = token + canvas_node1.ifaces[iface1.id] = iface1 + edge.src_iface = iface1 + if link.HasField("iface2"): + iface2 = link.iface2 + self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token + canvas_node2.ifaces[iface2.id] = iface2 + edge.dst_iface = iface2 elif link.options.unidirectional: edge = self.edges[token] edge.asymmetric_link = link @@ -513,14 +511,14 @@ class CanvasGraph(tk.Canvas): edge.delete() # update node connected to edge being deleted other_id = edge.src - other_interface = edge.src_interface + other_iface = edge.src_iface if edge.src == object_id: other_id = edge.dst - other_interface = edge.dst_interface + other_iface = edge.dst_iface other_node = self.nodes[other_id] other_node.edges.remove(edge) - if other_interface: - del other_node.interfaces[other_interface.id] + if other_iface: + del other_node.ifaces[other_iface.id] if is_wireless: other_node.delete_antenna() @@ -538,12 +536,12 @@ class CanvasGraph(tk.Canvas): del self.edges[edge.token] src_node = self.nodes[edge.src] src_node.edges.discard(edge) - if edge.src_interface: - del src_node.interfaces[edge.src_interface.id] + if edge.src_iface: + del src_node.ifaces[edge.src_iface.id] dst_node = self.nodes[edge.dst] dst_node.edges.discard(edge) - if edge.dst_interface: - del dst_node.interfaces[edge.dst_interface.id] + if edge.dst_iface: + del dst_node.ifaces[edge.dst_iface.id] src_wireless = NodeUtils.is_wireless_node(src_node.core_node.type) if src_wireless: dst_node.delete_antenna() @@ -963,26 +961,26 @@ class CanvasGraph(tk.Canvas): copy_link = copy_edge.link options = edge.link.options copy_link.options.CopyFrom(options) - interface1_id = None - if copy_link.HasField("interface1"): - interface1_id = copy_link.interface1.id - interface2_id = None - if copy_link.HasField("interface2"): - interface2_id = copy_link.interface2.id + iface1_id = None + if copy_link.HasField("iface1"): + iface1_id = copy_link.iface1.id + iface2_id = None + if copy_link.HasField("iface2"): + iface2_id = copy_link.iface2.id if not options.unidirectional: copy_edge.asymmetric_link = None else: - asym_interface1 = None - if interface1_id: - asym_interface1 = core_pb2.Interface(id=interface1_id) - asym_interface2 = None - if interface2_id: - asym_interface2 = core_pb2.Interface(id=interface2_id) + asym_iface1 = None + if iface1_id: + asym_iface1 = core_pb2.Interface(id=iface1_id) + asym_iface2 = None + if iface2_id: + asym_iface2 = core_pb2.Interface(id=iface2_id) copy_edge.asymmetric_link = core_pb2.Link( node1_id=copy_link.node2_id, node2_id=copy_link.node1_id, - interface1=asym_interface1, - interface2=asym_interface2, + iface1=asym_iface1, + iface2=asym_iface2, options=edge.asymmetric_link.options, ) self.itemconfig( diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 8ad3f02a..3ba4b3f7 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -55,7 +55,7 @@ class CanvasNode: ) self.tooltip = CanvasTooltip(self.canvas) self.edges = set() - self.interfaces = {} + self.ifaces = {} self.wireless_edges = set() self.antennas = [] self.antenna_images = {} @@ -70,9 +70,9 @@ class CanvasNode: self.context = tk.Menu(self.canvas) themes.style_menu(self.context) - def next_interface_id(self) -> int: + def next_iface_id(self) -> int: i = 0 - while i in self.interfaces: + while i in self.ifaces: i += 1 return i @@ -300,16 +300,16 @@ class CanvasNode: dialog = NodeConfigServiceDialog(self.app, self) dialog.show() - def has_emane_link(self, interface_id: int) -> core_pb2.Node: + def has_emane_link(self, iface_id: int) -> core_pb2.Node: result = None for edge in self.edges: if self.id == edge.src: other_id = edge.dst - edge_interface_id = edge.src_interface.id + edge_iface_id = edge.src_iface.id else: other_id = edge.src - edge_interface_id = edge.dst_interface.id - if edge_interface_id != interface_id: + edge_iface_id = edge.dst_iface.id + if edge_iface_id != iface_id: continue other_node = self.canvas.nodes[other_id] if other_node.core_node.type == NodeType.EMANE: diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 34270f56..14cba024 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -12,10 +12,10 @@ if TYPE_CHECKING: from core.gui.graph.node import CanvasNode -def get_index(interface: "core_pb2.Interface") -> Optional[int]: - if not interface.ip4: +def get_index(iface: "core_pb2.Interface") -> Optional[int]: + if not iface.ip4: return None - net = netaddr.IPNetwork(f"{interface.ip4}/{interface.ip4mask}") + net = netaddr.IPNetwork(f"{iface.ip4}/{iface.ip4mask}") ip_value = net.value cidr_value = net.cidr.value return ip_value - cidr_value @@ -89,43 +89,43 @@ class InterfaceManager: remaining_subnets = set() for edge in self.app.core.links.values(): link = edge.link - if link.HasField("interface1"): - subnets = self.get_subnets(link.interface1) + if link.HasField("iface1"): + subnets = self.get_subnets(link.iface1) remaining_subnets.add(subnets) - if link.HasField("interface2"): - subnets = self.get_subnets(link.interface2) + if link.HasField("iface2"): + subnets = self.get_subnets(link.iface2) remaining_subnets.add(subnets) # remove all subnets from used subnets when no longer present # or remove used indexes from subnet - interfaces = [] + ifaces = [] for link in links: - if link.HasField("interface1"): - interfaces.append(link.interface1) - if link.HasField("interface2"): - interfaces.append(link.interface2) - for interface in interfaces: - subnets = self.get_subnets(interface) + if link.HasField("iface1"): + ifaces.append(link.iface1) + if link.HasField("iface2"): + ifaces.append(link.iface2) + for iface in ifaces: + subnets = self.get_subnets(iface) if subnets not in remaining_subnets: self.used_subnets.pop(subnets.key(), None) else: - index = get_index(interface) + index = get_index(iface) if index is not None: subnets.used_indexes.discard(index) self.current_subnets = None def joined(self, links: List["core_pb2.Link"]) -> None: - interfaces = [] + ifaces = [] for link in links: - if link.HasField("interface1"): - interfaces.append(link.interface1) - if link.HasField("interface2"): - interfaces.append(link.interface2) + if link.HasField("iface1"): + ifaces.append(link.iface1) + if link.HasField("iface2"): + ifaces.append(link.iface2) # add to used subnets and mark used indexes - for interface in interfaces: - subnets = self.get_subnets(interface) - index = get_index(interface) + for iface in ifaces: + subnets = self.get_subnets(iface) + index = get_index(iface) if index is None: continue subnets.used_indexes.add(index) @@ -150,13 +150,13 @@ class InterfaceManager: ip6 = self.current_subnets.ip6[index] return str(ip4), str(ip6) - def get_subnets(self, interface: "core_pb2.Interface") -> Subnets: + def get_subnets(self, iface: "core_pb2.Interface") -> Subnets: ip4_subnet = self.ip4_subnets - if interface.ip4: - ip4_subnet = IPNetwork(f"{interface.ip4}/{interface.ip4mask}").cidr + if iface.ip4: + ip4_subnet = IPNetwork(f"{iface.ip4}/{iface.ip4mask}").cidr ip6_subnet = self.ip6_subnets - if interface.ip6: - ip6_subnet = IPNetwork(f"{interface.ip6}/{interface.ip6mask}").cidr + if iface.ip6: + ip6_subnet = IPNetwork(f"{iface.ip6}/{iface.ip6mask}").cidr subnets = Subnets(ip4_subnet, ip6_subnet) return self.used_subnets.get(subnets.key(), subnets) @@ -196,16 +196,16 @@ class InterfaceManager: for edge in canvas_node.edges: src_node = canvas.nodes[edge.src] dst_node = canvas.nodes[edge.dst] - interface = edge.src_interface + iface = edge.src_iface check_node = src_node if src_node == canvas_node: - interface = edge.dst_interface + iface = edge.dst_iface check_node = dst_node if check_node.core_node.id in visited: continue visited.add(check_node.core_node.id) - if interface: - subnets = self.get_subnets(interface) + if iface: + subnets = self.get_subnets(iface) else: subnets = self.find_subnets(check_node, visited) if subnets: diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 62a9ceae..cf4216d8 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -139,7 +139,7 @@ class Menubar(tk.Menu): menu.add_checkbutton( label="Interface Names", command=self.click_edge_label_change, - variable=self.canvas.show_interface_names, + variable=self.canvas.show_iface_names, ) menu.add_checkbutton( label="IPv4 Addresses", diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 43996ba3..d56c40aa 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -178,7 +178,7 @@ class MobilityManager(ModelManager): self.session.broadcast_event(event_data) def updatewlans( - self, moved: List[CoreNode], moved_netifs: List[CoreInterface] + self, moved: List[CoreNode], moved_ifaces: List[CoreInterface] ) -> None: """ A mobility script has caused nodes in the 'moved' list to move. @@ -186,7 +186,7 @@ class MobilityManager(ModelManager): were to recalculate for each individual node movement. :param moved: moved nodes - :param moved_netifs: moved network interfaces + :param moved_ifaces: moved network interfaces :return: nothing """ for node_id in self.nodes(): @@ -195,7 +195,7 @@ class MobilityManager(ModelManager): except CoreError: continue if node.model: - node.model.update(moved, moved_netifs) + node.model.update(moved, moved_ifaces) class WirelessModel(ConfigurableOptions): @@ -228,12 +228,12 @@ class WirelessModel(ConfigurableOptions): """ return [] - def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None: """ Update this wireless model. :param moved: moved nodes - :param moved_netifs: moved network interfaces + :param moved_ifaces: moved network interfaces :return: nothing """ raise NotImplementedError @@ -301,8 +301,8 @@ class BasicRangeModel(WirelessModel): super().__init__(session, _id) self.session: "Session" = session self.wlan: WlanNode = session.get_node(_id, WlanNode) - self._netifs: Dict[CoreInterface, Tuple[float, float, float]] = {} - self._netifslock: threading.Lock = threading.Lock() + self.iface_to_pos: Dict[CoreInterface, Tuple[float, float, float]] = {} + self.iface_lock: threading.Lock = threading.Lock() self.range: int = 0 self.bw: Optional[int] = None self.delay: Optional[int] = None @@ -333,48 +333,48 @@ class BasicRangeModel(WirelessModel): Apply link parameters to all interfaces. This is invoked from WlanNode.setmodel() after the position callback has been set. """ - with self._netifslock: - for netif in self._netifs: + with self.iface_lock: + for iface in self.iface_to_pos: options = LinkOptions( bandwidth=self.bw, delay=self.delay, loss=self.loss, jitter=self.jitter, ) - self.wlan.linkconfig(netif, options) + self.wlan.linkconfig(iface, options) - def get_position(self, netif: CoreInterface) -> Tuple[float, float, float]: + def get_position(self, iface: CoreInterface) -> Tuple[float, float, float]: """ Retrieve network interface position. - :param netif: network interface position to retrieve + :param iface: network interface position to retrieve :return: network interface position """ - with self._netifslock: - return self._netifs[netif] + with self.iface_lock: + return self.iface_to_pos[iface] - def set_position(self, netif: CoreInterface) -> None: + def set_position(self, iface: CoreInterface) -> None: """ A node has moved; given an interface, a new (x,y,z) position has been set; calculate the new distance between other nodes and link or unlink node pairs based on the configured range. - :param netif: network interface to set position for + :param iface: network interface to set position for :return: nothing """ - x, y, z = netif.node.position.get() - self._netifslock.acquire() - self._netifs[netif] = (x, y, z) + x, y, z = iface.node.position.get() + self.iface_lock.acquire() + self.iface_to_pos[iface] = (x, y, z) if x is None or y is None: - self._netifslock.release() + self.iface_lock.release() return - for netif2 in self._netifs: - self.calclink(netif, netif2) - self._netifslock.release() + for iface2 in self.iface_to_pos: + self.calclink(iface, iface2) + self.iface_lock.release() position_callback = set_position - def update(self, moved: List[CoreNode], moved_netifs: List[CoreInterface]) -> None: + def update(self, moved: List[CoreNode], moved_ifaces: List[CoreInterface]) -> None: """ Node positions have changed without recalc. Update positions from node.position, then re-calculate links for those that have moved. @@ -382,37 +382,37 @@ class BasicRangeModel(WirelessModel): one of the nodes has moved. :param moved: moved nodes - :param moved_netifs: moved network interfaces + :param moved_ifaces: moved network interfaces :return: nothing """ - with self._netifslock: - while len(moved_netifs): - netif = moved_netifs.pop() - nx, ny, nz = netif.node.getposition() - if netif in self._netifs: - self._netifs[netif] = (nx, ny, nz) - for netif2 in self._netifs: - if netif2 in moved_netifs: + with self.iface_lock: + while len(moved_ifaces): + iface = moved_ifaces.pop() + nx, ny, nz = iface.node.getposition() + if iface in self.iface_to_pos: + self.iface_to_pos[iface] = (nx, ny, nz) + for iface2 in self.iface_to_pos: + if iface2 in moved_ifaces: continue - self.calclink(netif, netif2) + self.calclink(iface, iface2) - def calclink(self, netif: CoreInterface, netif2: CoreInterface) -> None: + def calclink(self, iface: CoreInterface, iface2: CoreInterface) -> None: """ Helper used by set_position() and update() to calculate distance between two interfaces and perform linking/unlinking. Sends link/unlink messages and updates the WlanNode's linked dict. - :param netif: interface one - :param netif2: interface two + :param iface: interface one + :param iface2: interface two :return: nothing """ - if netif == netif2: + if iface == iface2: return try: - x, y, z = self._netifs[netif] - x2, y2, z2 = self._netifs[netif2] + x, y, z = self.iface_to_pos[iface] + x2, y2, z2 = self.iface_to_pos[iface2] if x2 is None or y2 is None: return @@ -420,8 +420,8 @@ class BasicRangeModel(WirelessModel): d = self.calcdistance((x, y, z), (x2, y2, z2)) # ordering is important, to keep the wlan._linked dict organized - a = min(netif, netif2) - b = max(netif, netif2) + a = min(iface, iface2) + b = max(iface, iface2) with self.wlan._linked_lock: linked = self.wlan.linked(a, b) @@ -475,42 +475,39 @@ class BasicRangeModel(WirelessModel): self.setlinkparams() def create_link_data( - self, - interface1: CoreInterface, - interface2: CoreInterface, - message_type: MessageFlags, + self, iface1: CoreInterface, iface2: CoreInterface, message_type: MessageFlags ) -> LinkData: """ Create a wireless link/unlink data message. - :param interface1: interface one - :param interface2: interface two + :param iface1: interface one + :param iface2: interface two :param message_type: link message type :return: link data """ color = self.session.get_link_color(self.wlan.id) return LinkData( message_type=message_type, - node1_id=interface1.node.id, - node2_id=interface2.node.id, + node1_id=iface1.node.id, + node2_id=iface2.node.id, network_id=self.wlan.id, link_type=LinkTypes.WIRELESS, color=color, ) def sendlinkmsg( - self, netif: CoreInterface, netif2: CoreInterface, unlink: bool = False + self, iface: CoreInterface, iface2: CoreInterface, unlink: bool = False ) -> None: """ Send a wireless link/unlink API message to the GUI. - :param netif: interface one - :param netif2: interface two + :param iface: interface one + :param iface2: interface two :param unlink: unlink or not :return: nothing """ message_type = MessageFlags.DELETE if unlink else MessageFlags.ADD - link_data = self.create_link_data(netif, netif2, message_type) + link_data = self.create_link_data(iface, iface2, message_type) self.session.broadcast_link(link_data) def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: @@ -643,17 +640,17 @@ class WayPointMobility(WirelessModel): return return self.run() - # only move netifs attached to self.wlan, or all nodenum in script? + # only move interfaces attached to self.wlan, or all nodenum in script? moved = [] - moved_netifs = [] - for netif in self.wlan.netifs(): - node = netif.node + moved_ifaces = [] + for iface in self.wlan.get_ifaces(): + node = iface.node if self.movenode(node, dt): moved.append(node) - moved_netifs.append(netif) + moved_ifaces.append(iface) # calculate all ranges after moving nodes; this saves calculations - self.session.mobility.updatewlans(moved, moved_netifs) + self.session.mobility.updatewlans(moved, moved_ifaces) # TODO: check session state self.session.event_loop.add_event(0.001 * self.refresh_ms, self.runround) @@ -725,16 +722,16 @@ class WayPointMobility(WirelessModel): :return: nothing """ moved = [] - moved_netifs = [] - for netif in self.wlan.netifs(): - node = netif.node + moved_ifaces = [] + for iface in self.wlan.get_ifaces(): + node = iface.node if node.id not in self.initial: continue x, y, z = self.initial[node.id].coords self.setnodeposition(node, x, y, z) moved.append(node) - moved_netifs.append(netif) - self.session.mobility.updatewlans(moved, moved_netifs) + moved_ifaces.append(iface) + self.session.mobility.updatewlans(moved, moved_ifaces) def addwaypoint( self, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 6c7ebcf0..40aae6a8 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -68,8 +68,8 @@ class NodeBase(abc.ABC): self.server: "DistributedServer" = server self.type: Optional[str] = None self.services: CoreServices = [] - self._netif: Dict[int, CoreInterface] = {} - self.ifindex: int = 0 + self.ifaces: Dict[int, CoreInterface] = {} + self.iface_id: int = 0 self.canvas: Optional[int] = None self.icon: Optional[str] = None self.opaque: Optional[str] = None @@ -139,58 +139,50 @@ class NodeBase(abc.ABC): """ return self.position.get() - def ifname(self, ifindex: int) -> str: - """ - Retrieve interface name for index. + def get_iface(self, iface_id: int) -> CoreInterface: + if iface_id not in self.ifaces: + raise CoreError(f"node({self.name}) does not have interface({iface_id})") + return self.ifaces[iface_id] - :param ifindex: interface index - :return: interface name + def get_ifaces(self, control: bool = True) -> List[CoreInterface]: """ - return self._netif[ifindex].name + Retrieve sorted list of interfaces, optionally do not include control + interfaces. - def netifs(self, sort: bool = False) -> List[CoreInterface]: + :param control: False to exclude control interfaces, included otherwise + :return: list of interfaces """ - Retrieve network interfaces, sorted if desired. + ifaces = [] + for iface_id in sorted(self.ifaces): + iface = self.ifaces[iface_id] + if not control and getattr(iface, "control", False): + continue + ifaces.append(iface) + return ifaces - :param sort: boolean used to determine if interfaces should be sorted - :return: network interfaces + def get_iface_id(self, iface: CoreInterface) -> int: """ - if sort: - return [self._netif[x] for x in sorted(self._netif)] - else: - return list(self._netif.values()) + Retrieve id for an interface. - def numnetif(self) -> int: - """ - Return the attached interface count. - - :return: number of network interfaces - """ - return len(self._netif) - - def getifindex(self, netif: CoreInterface) -> int: - """ - Retrieve index for an interface. - - :param netif: interface to get index for + :param iface: interface to get id for :return: interface index if found, -1 otherwise """ - for ifindex in self._netif: - if self._netif[ifindex] is netif: - return ifindex - return -1 + for iface_id, local_iface in self.ifaces.items(): + if local_iface is iface: + return iface_id + raise CoreError(f"node({self.name}) does not have interface({iface.name})") - def newifindex(self) -> int: + def next_iface_id(self) -> int: """ Create a new interface index. :return: interface index """ - while self.ifindex in self._netif: - self.ifindex += 1 - ifindex = self.ifindex - self.ifindex += 1 - return ifindex + while self.iface_id in self.ifaces: + self.iface_id += 1 + iface_id = self.iface_id + self.iface_id += 1 + return iface_id def data( self, message_type: MessageFlags = MessageFlags.NONE, source: str = None @@ -325,14 +317,14 @@ class CoreNodeBase(NodeBase): raise NotImplementedError @abc.abstractmethod - def newnetif( - self, net: "CoreNetworkBase", interface_data: InterfaceData + def new_iface( + self, net: "CoreNetworkBase", iface_data: InterfaceData ) -> CoreInterface: """ - Create a new network interface. + Create a new interface. :param net: network to associate with - :param interface_data: interface data for new interface + :param iface_data: interface data for new interface :return: interface index """ raise NotImplementedError @@ -399,67 +391,53 @@ class CoreNodeBase(NodeBase): if self.tmpnodedir: self.host_cmd(f"rm -rf {self.nodedir}") - def addnetif(self, netif: CoreInterface, ifindex: int) -> None: + def add_iface(self, iface: CoreInterface, iface_id: int) -> None: """ Add network interface to node and set the network interface index if successful. - :param netif: network interface to add - :param ifindex: interface index + :param iface: network interface to add + :param iface_id: interface id :return: nothing """ - if ifindex in self._netif: - raise ValueError(f"ifindex {ifindex} already exists") - self._netif[ifindex] = netif - netif.netindex = ifindex + if iface_id in self.ifaces: + raise CoreError(f"interface({iface_id}) already exists") + self.ifaces[iface_id] = iface + iface.node_id = iface_id - def delnetif(self, ifindex: int) -> None: + def delete_iface(self, iface_id: int) -> None: """ Delete a network interface - :param ifindex: interface index to delete + :param iface_id: interface index to delete :return: nothing """ - if ifindex not in self._netif: - raise CoreError(f"node({self.name}) ifindex({ifindex}) does not exist") - netif = self._netif.pop(ifindex) - logging.info("node(%s) removing interface(%s)", self.name, netif.name) - netif.detachnet() - netif.shutdown() + if iface_id not in self.ifaces: + raise CoreError(f"node({self.name}) interface({iface_id}) does not exist") + iface = self.ifaces.pop(iface_id) + logging.info("node(%s) removing interface(%s)", self.name, iface.name) + iface.detachnet() + iface.shutdown() - def netif(self, ifindex: int) -> Optional[CoreInterface]: - """ - Retrieve network interface. - - :param ifindex: index of interface to retrieve - :return: network interface, or None if not found - """ - if ifindex in self._netif: - return self._netif[ifindex] - else: - return None - - def attachnet(self, ifindex: int, net: "CoreNetworkBase") -> None: + def attachnet(self, iface_id: int, net: "CoreNetworkBase") -> None: """ Attach a network. - :param ifindex: interface of index to attach + :param iface_id: interface of index to attach :param net: network to attach :return: nothing """ - if ifindex not in self._netif: - raise ValueError(f"ifindex {ifindex} does not exist") - self._netif[ifindex].attachnet(net) + iface = self.get_iface(iface_id) + iface.attachnet(net) - def detachnet(self, ifindex: int) -> None: + def detachnet(self, iface_id: int) -> None: """ Detach network interface. - :param ifindex: interface index to detach + :param iface_id: interface id to detach :return: nothing """ - if ifindex not in self._netif: - raise ValueError(f"ifindex {ifindex} does not exist") - self._netif[ifindex].detachnet() + iface = self.get_iface(iface_id) + iface.detachnet() def setposition(self, x: float = None, y: float = None, z: float = None) -> None: """ @@ -472,8 +450,8 @@ class CoreNodeBase(NodeBase): """ changed = super().setposition(x, y, z) if changed: - for netif in self.netifs(sort=True): - netif.setposition() + for iface in self.get_ifaces(): + iface.setposition() def commonnets( self, node: "CoreNodeBase", want_ctrl: bool = False @@ -488,12 +466,10 @@ class CoreNodeBase(NodeBase): :return: tuples of common networks """ common = [] - for netif1 in self.netifs(): - if not want_ctrl and hasattr(netif1, "control"): - continue - for netif2 in node.netifs(): - if netif1.net == netif2.net: - common.append((netif1.net, netif1, netif2)) + for iface1 in self.get_ifaces(control=want_ctrl): + for iface2 in node.get_ifaces(): + if iface1.net == iface2.net: + common.append((iface1.net, iface1, iface2)) return common @@ -620,8 +596,8 @@ class CoreNode(CoreNodeBase): self._mounts = [] # shutdown all interfaces - for netif in self.netifs(): - netif.shutdown() + for iface in self.get_ifaces(): + iface.shutdown() # kill node process if present try: @@ -636,7 +612,7 @@ class CoreNode(CoreNodeBase): logging.exception("error removing node directory") # clear interface data, close client, and mark self and not up - self._netif.clear() + self.ifaces.clear() self.client.close() self.up = False except OSError: @@ -704,36 +680,36 @@ class CoreNode(CoreNodeBase): self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}") self._mounts.append((source, target)) - def newifindex(self) -> int: + def next_iface_id(self) -> int: """ Retrieve a new interface index. :return: new interface index """ with self.lock: - return super().newifindex() + return super().next_iface_id() - def newveth(self, ifindex: int = None, ifname: str = None) -> int: + def newveth(self, iface_id: int = None, ifname: str = None) -> int: """ Create a new interface. - :param ifindex: index for the new interface + :param iface_id: id for the new interface :param ifname: name for the new interface :return: nothing """ with self.lock: - if ifindex is None: - ifindex = self.newifindex() + if iface_id is None: + iface_id = self.next_iface_id() if ifname is None: - ifname = f"eth{ifindex}" + ifname = f"eth{iface_id}" sessionid = self.session.short_session_id() try: - suffix = f"{self.id:x}.{ifindex}.{sessionid}" + suffix = f"{self.id:x}.{iface_id}.{sessionid}" except TypeError: - suffix = f"{self.id}.{ifindex}.{sessionid}" + suffix = f"{self.id}.{iface_id}.{sessionid}" localname = f"veth{suffix}" if len(localname) >= 16: @@ -765,140 +741,138 @@ class CoreNode(CoreNodeBase): try: # add network interface to the node. If unsuccessful, destroy the # network interface and raise exception. - self.addnetif(veth, ifindex) + self.add_iface(veth, iface_id) except ValueError as e: veth.shutdown() del veth raise e - return ifindex + return iface_id - def newtuntap(self, ifindex: int = None, ifname: str = None) -> int: + def newtuntap(self, iface_id: int = None, ifname: str = None) -> int: """ Create a new tunnel tap. - :param ifindex: interface index + :param iface_id: interface id :param ifname: interface name :return: interface index """ with self.lock: - if ifindex is None: - ifindex = self.newifindex() + if iface_id is None: + iface_id = self.next_iface_id() if ifname is None: - ifname = f"eth{ifindex}" + ifname = f"eth{iface_id}" sessionid = self.session.short_session_id() - localname = f"tap{self.id}.{ifindex}.{sessionid}" + localname = f"tap{self.id}.{iface_id}.{sessionid}" name = ifname tuntap = TunTap(self.session, self, name, localname, start=self.up) try: - self.addnetif(tuntap, ifindex) + self.add_iface(tuntap, iface_id) except ValueError as e: tuntap.shutdown() del tuntap raise e - return ifindex + return iface_id - def sethwaddr(self, ifindex: int, addr: str) -> None: + def sethwaddr(self, iface_id: int, addr: str) -> None: """ - Set hardware addres for an interface. + Set hardware address for an interface. - :param ifindex: index of interface to set hardware address for + :param iface_id: id of interface to set hardware address for :param addr: hardware address to set :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ addr = utils.validate_mac(addr) - interface = self._netif[ifindex] - interface.sethwaddr(addr) + iface = self.get_iface(iface_id) + iface.sethwaddr(addr) if self.up: - self.node_net_client.device_mac(interface.name, addr) + self.node_net_client.device_mac(iface.name, addr) - def addaddr(self, ifindex: int, addr: str) -> None: + def addaddr(self, iface_id: int, addr: str) -> None: """ Add interface address. - :param ifindex: index of interface to add address to + :param iface_id: id of interface to add address to :param addr: address to add to interface :return: nothing """ addr = utils.validate_ip(addr) - interface = self._netif[ifindex] - interface.addaddr(addr) + iface = self.get_iface(iface_id) + iface.addaddr(addr) if self.up: # ipv4 check broadcast = None if netaddr.valid_ipv4(addr): broadcast = "+" - self.node_net_client.create_address(interface.name, addr, broadcast) + self.node_net_client.create_address(iface.name, addr, broadcast) - def deladdr(self, ifindex: int, addr: str) -> None: + def deladdr(self, iface_id: int, addr: str) -> None: """ Delete address from an interface. - :param ifindex: index of interface to delete address from + :param iface_id: id of interface to delete address from :param addr: address to delete from interface :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - interface = self._netif[ifindex] - + iface = self.get_iface(iface_id) try: - interface.deladdr(addr) + iface.deladdr(addr) except ValueError: logging.exception("trying to delete unknown address: %s", addr) - if self.up: - self.node_net_client.delete_address(interface.name, addr) + self.node_net_client.delete_address(iface.name, addr) - def ifup(self, ifindex: int) -> None: + def ifup(self, iface_id: int) -> None: """ Bring an interface up. - :param ifindex: index of interface to bring up + :param iface_id: index of interface to bring up :return: nothing """ if self.up: - interface_name = self.ifname(ifindex) - self.node_net_client.device_up(interface_name) + iface = self.get_iface(iface_id) + self.node_net_client.device_up(iface.name) - def newnetif( - self, net: "CoreNetworkBase", interface_data: InterfaceData + def new_iface( + self, net: "CoreNetworkBase", iface_data: InterfaceData ) -> CoreInterface: """ Create a new network interface. :param net: network to associate with - :param interface_data: interface data for new interface + :param iface_data: interface data for new interface :return: interface index """ - addresses = interface_data.get_addresses() + addresses = iface_data.get_addresses() with self.lock: # TODO: emane specific code if net.is_emane is True: - ifindex = self.newtuntap(interface_data.id, interface_data.name) + iface_id = self.newtuntap(iface_data.id, iface_data.name) # TUN/TAP is not ready for addressing yet; the device may # take some time to appear, and installing it into a # namespace after it has been bound removes addressing; # save addresses with the interface now - self.attachnet(ifindex, net) - netif = self.netif(ifindex) - netif.sethwaddr(interface_data.mac) + self.attachnet(iface_id, net) + iface = self.get_iface(iface_id) + iface.sethwaddr(iface_data.mac) for address in addresses: - netif.addaddr(address) + iface.addaddr(address) else: - ifindex = self.newveth(interface_data.id, interface_data.name) - self.attachnet(ifindex, net) - if interface_data.mac: - self.sethwaddr(ifindex, interface_data.mac) + iface_id = self.newveth(iface_data.id, iface_data.name) + self.attachnet(iface_id, net) + if iface_data.mac: + self.sethwaddr(iface_id, iface_data.mac) for address in addresses: - self.addaddr(ifindex, address) - self.ifup(ifindex) - netif = self.netif(ifindex) - return netif + self.addaddr(iface_id, address) + self.ifup(iface_id) + iface = self.get_iface(iface_id) + return iface def addfile(self, srcname: str, filename: str) -> None: """ @@ -1041,54 +1015,54 @@ class CoreNetworkBase(NodeBase): @abc.abstractmethod def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Configure link parameters by applying tc queuing disciplines on the interface. - :param netif: interface one + :param iface: interface one :param options: options for configuring link - :param netif2: interface two + :param iface2: interface two :return: nothing """ raise NotImplementedError - def getlinknetif(self, net: "CoreNetworkBase") -> Optional[CoreInterface]: + def get_linked_iface(self, net: "CoreNetworkBase") -> Optional[CoreInterface]: """ - Return the interface of that links this net with another net. + Return the interface that links this net with another net. :param net: interface to get link for :return: interface the provided network is linked to """ - for netif in self.netifs(): - if netif.othernet == net: - return netif + for iface in self.get_ifaces(): + if iface.othernet == net: + return iface return None - def attach(self, netif: CoreInterface) -> None: + def attach(self, iface: CoreInterface) -> None: """ Attach network interface. - :param netif: network interface to attach + :param iface: network interface to attach :return: nothing """ - i = self.newifindex() - self._netif[i] = netif - netif.netifi = i + i = self.next_iface_id() + self.ifaces[i] = iface + iface.net_id = i with self._linked_lock: - self._linked[netif] = {} + self._linked[iface] = {} - def detach(self, netif: CoreInterface) -> None: + def detach(self, iface: CoreInterface) -> None: """ Detach network interface. - :param netif: network interface to detach + :param iface: network interface to detach :return: nothing """ - del self._netif[netif.netifi] - netif.netifi = None + del self.ifaces[iface.net_id] + iface.net_id = None with self._linked_lock: - del self._linked[netif] + del self._linked[iface] def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ @@ -1102,41 +1076,39 @@ class CoreNetworkBase(NodeBase): # build a link message from this network node to each node having a # connected interface - for netif in self.netifs(sort=True): - if not hasattr(netif, "node"): - continue + for iface in self.get_ifaces(): uni = False - linked_node = netif.node + linked_node = iface.node if linked_node is None: # two layer-2 switches/hubs linked together via linknet() - if not netif.othernet: + if not iface.othernet: continue - linked_node = netif.othernet + linked_node = iface.othernet if linked_node.id == self.id: continue - netif.swapparams("_params_up") - upstream_params = netif.getparams() - netif.swapparams("_params_up") - if netif.getparams() != upstream_params: + iface.swapparams("_params_up") + upstream_params = iface.getparams() + iface.swapparams("_params_up") + if iface.getparams() != upstream_params: uni = True unidirectional = 0 if uni: unidirectional = 1 - interface2_ip4 = None - interface2_ip4_mask = None - interface2_ip6 = None - interface2_ip6_mask = None - for address in netif.addrlist: + iface2_ip4 = None + iface2_ip4_mask = None + iface2_ip6 = None + iface2_ip6_mask = None + for address in iface.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - interface2_ip4 = ip - interface2_ip4_mask = mask + iface2_ip4 = ip + iface2_ip4_mask = mask else: - interface2_ip6 = ip - interface2_ip6_mask = mask + iface2_ip6 = ip + iface2_ip6_mask = mask link_data = LinkData( message_type=flags, @@ -1144,42 +1116,38 @@ class CoreNetworkBase(NodeBase): node2_id=linked_node.id, link_type=self.linktype, unidirectional=unidirectional, - interface2_id=linked_node.getifindex(netif), - interface2_name=netif.name, - interface2_mac=netif.hwaddr, - interface2_ip4=interface2_ip4, - interface2_ip4_mask=interface2_ip4_mask, - interface2_ip6=interface2_ip6, - interface2_ip6_mask=interface2_ip6_mask, - delay=netif.getparam("delay"), - bandwidth=netif.getparam("bw"), - dup=netif.getparam("duplicate"), - jitter=netif.getparam("jitter"), - loss=netif.getparam("loss"), + iface2_id=linked_node.get_iface_id(iface), + iface2_name=iface.name, + iface2_mac=iface.hwaddr, + iface2_ip4=iface2_ip4, + iface2_ip4_mask=iface2_ip4_mask, + iface2_ip6=iface2_ip6, + iface2_ip6_mask=iface2_ip6_mask, + delay=iface.getparam("delay"), + bandwidth=iface.getparam("bw"), + dup=iface.getparam("duplicate"), + jitter=iface.getparam("jitter"), + loss=iface.getparam("loss"), ) - all_links.append(link_data) if not uni: continue - - netif.swapparams("_params_up") + iface.swapparams("_params_up") link_data = LinkData( message_type=MessageFlags.NONE, node1_id=linked_node.id, node2_id=self.id, link_type=self.linktype, unidirectional=1, - delay=netif.getparam("delay"), - bandwidth=netif.getparam("bw"), - dup=netif.getparam("duplicate"), - jitter=netif.getparam("jitter"), - loss=netif.getparam("loss"), + delay=iface.getparam("delay"), + bandwidth=iface.getparam("bw"), + dup=iface.getparam("duplicate"), + jitter=iface.getparam("jitter"), + loss=iface.getparam("loss"), ) - netif.swapparams("_params_up") - + iface.swapparams("_params_up") all_links.append(link_data) - return all_links diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index e911db74..1ef814ee 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -141,7 +141,7 @@ class DockerNode(CoreNode): return with self.lock: - self._netif.clear() + self.ifaces.clear() self.client.stop_container() self.up = False diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index e73e2989..dc16517f 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -57,11 +57,11 @@ class CoreInterface: self.poshook: Callable[[CoreInterface], None] = lambda x: None # used with EMANE self.transport_type: Optional[TransportType] = None - # node interface index - self.netindex: Optional[int] = None - # net interface index - self.netifi: Optional[int] = None - # index used to find flow data + # id of interface for node + self.node_id: Optional[int] = None + # id of interface for network + self.net_id: Optional[int] = None + # id used to find flow data self.flow_id: Optional[int] = None self.server: Optional["DistributedServer"] = server use_ovs = session.options.get_config("ovs") == "True" @@ -284,19 +284,16 @@ class Veth(CoreInterface): """ if not self.up: return - if self.node: try: self.node.node_net_client.device_flush(self.name) except CoreCommandError: logging.exception("error shutting down interface") - if self.localname: try: self.net_client.delete_device(self.localname) except CoreCommandError: logging.info("link already removed: %s", self.localname) - self.up = False diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index a66791ce..9773cb95 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -126,7 +126,7 @@ class LxcNode(CoreNode): return with self.lock: - self._netif.clear() + self.ifaces.clear() self.client.stop_container() self.up = False @@ -215,7 +215,7 @@ class LxcNode(CoreNode): self.client.copy_file(source, filename) self.cmd(f"chmod {mode:o} {filename}") - def addnetif(self, netif: CoreInterface, ifindex: int) -> None: - super().addnetif(netif, ifindex) + def add_iface(self, iface: CoreInterface, iface_id: int) -> None: + super().add_iface(iface, iface_id) # adding small delay to allow time for adding addresses to work correctly time.sleep(0.5) diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 25a10b99..b6c164b5 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -155,14 +155,14 @@ class LinuxNetClient: """ self.run(f"{TC_BIN} qdisc delete dev {device} root") - def checksums_off(self, interface_name: str) -> None: + def checksums_off(self, iface_name: str) -> None: """ Turns interface checksums off. - :param interface_name: interface to update + :param iface_name: interface to update :return: nothing """ - self.run(f"{ETHTOOL_BIN} -K {interface_name} rx off tx off") + self.run(f"{ETHTOOL_BIN} -K {iface_name} rx off tx off") def create_address(self, device: str, address: str, broadcast: str = None) -> None: """ @@ -250,26 +250,26 @@ class LinuxNetClient: self.device_down(name) self.run(f"{IP_BIN} link delete {name} type bridge") - def set_interface_master(self, bridge_name: str, interface_name: str) -> None: + def set_iface_master(self, bridge_name: str, iface_name: str) -> None: """ Assign interface master to a Linux bridge. :param bridge_name: bridge name - :param interface_name: interface name + :param iface_name: interface name :return: nothing """ - self.run(f"{IP_BIN} link set dev {interface_name} master {bridge_name}") - self.device_up(interface_name) + self.run(f"{IP_BIN} link set dev {iface_name} master {bridge_name}") + self.device_up(iface_name) - def delete_interface(self, bridge_name: str, interface_name: str) -> None: + def delete_iface(self, bridge_name: str, iface_name: str) -> None: """ Delete an interface associated with a Linux bridge. :param bridge_name: bridge name - :param interface_name: interface name + :param iface_name: interface name :return: nothing """ - self.run(f"{IP_BIN} link set dev {interface_name} nomaster") + self.run(f"{IP_BIN} link set dev {iface_name} nomaster") def existing_bridges(self, _id: int) -> bool: """ @@ -330,26 +330,26 @@ class OvsNetClient(LinuxNetClient): self.device_down(name) self.run(f"{OVS_BIN} del-br {name}") - def set_interface_master(self, bridge_name: str, interface_name: str) -> None: + def set_iface_master(self, bridge_name: str, iface_name: str) -> None: """ Create an interface associated with a network bridge. :param bridge_name: bridge name - :param interface_name: interface name + :param iface_name: interface name :return: nothing """ - self.run(f"{OVS_BIN} add-port {bridge_name} {interface_name}") - self.device_up(interface_name) + self.run(f"{OVS_BIN} add-port {bridge_name} {iface_name}") + self.device_up(iface_name) - def delete_interface(self, bridge_name: str, interface_name: str) -> None: + def delete_iface(self, bridge_name: str, iface_name: str) -> None: """ Delete an interface associated with a OVS bridge. :param bridge_name: bridge name - :param interface_name: interface name + :param iface_name: interface name :return: nothing """ - self.run(f"{OVS_BIN} del-port {bridge_name} {interface_name}") + self.run(f"{OVS_BIN} del-port {bridge_name} {iface_name}") def existing_bridges(self, _id: int) -> bool: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index b85c2eee..85e3e488 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -216,20 +216,20 @@ class EbtablesQueue: ] ) # rebuild the chain - for netif1, v in wlan._linked.items(): - for netif2, linked in v.items(): + for iface1, v in wlan._linked.items(): + for oface2, linked in v.items(): if wlan.policy == NetworkPolicy.DROP and linked: self.cmds.extend( [ - f"-A {wlan.brname} -i {netif1.localname} -o {netif2.localname} -j ACCEPT", - f"-A {wlan.brname} -o {netif1.localname} -i {netif2.localname} -j ACCEPT", + f"-A {wlan.brname} -i {iface1.localname} -o {oface2.localname} -j ACCEPT", + f"-A {wlan.brname} -o {iface1.localname} -i {oface2.localname} -j ACCEPT", ] ) elif wlan.policy == NetworkPolicy.ACCEPT and not linked: self.cmds.extend( [ - f"-A {wlan.brname} -i {netif1.localname} -o {netif2.localname} -j DROP", - f"-A {wlan.brname} -o {netif1.localname} -i {netif2.localname} -j DROP", + f"-A {wlan.brname} -i {iface1.localname} -o {oface2.localname} -j DROP", + f"-A {wlan.brname} -o {iface1.localname} -i {oface2.localname} -j DROP", ] ) @@ -347,53 +347,53 @@ class CoreNetwork(CoreNetworkBase): logging.exception("error during shutdown") # removes veth pairs used for bridge-to-bridge connections - for netif in self.netifs(): - netif.shutdown() + for iface in self.get_ifaces(): + iface.shutdown() - self._netif.clear() + self.ifaces.clear() self._linked.clear() del self.session self.up = False - def attach(self, netif: CoreInterface) -> None: + def attach(self, iface: CoreInterface) -> None: """ Attach a network interface. - :param netif: network interface to attach + :param iface: network interface to attach :return: nothing """ if self.up: - netif.net_client.set_interface_master(self.brname, netif.localname) - super().attach(netif) + iface.net_client.set_iface_master(self.brname, iface.localname) + super().attach(iface) - def detach(self, netif: CoreInterface) -> None: + def detach(self, iface: CoreInterface) -> None: """ Detach a network interface. - :param netif: network interface to detach + :param iface: network interface to detach :return: nothing """ if self.up: - netif.net_client.delete_interface(self.brname, netif.localname) - super().detach(netif) + iface.net_client.delete_iface(self.brname, iface.localname) + super().detach(iface) - def linked(self, netif1: CoreInterface, netif2: CoreInterface) -> bool: + def linked(self, iface1: CoreInterface, iface2: CoreInterface) -> bool: """ Determine if the provided network interfaces are linked. - :param netif1: interface one - :param netif2: interface two + :param iface1: interface one + :param iface2: interface two :return: True if interfaces are linked, False otherwise """ # check if the network interfaces are attached to this network - if self._netif[netif1.netifi] != netif1: - raise ValueError(f"inconsistency for netif {netif1.name}") + if self.ifaces[iface1.net_id] != iface1: + raise ValueError(f"inconsistency for interface {iface1.name}") - if self._netif[netif2.netifi] != netif2: - raise ValueError(f"inconsistency for netif {netif2.name}") + if self.ifaces[iface2.net_id] != iface2: + raise ValueError(f"inconsistency for interface {iface2.name}") try: - linked = self._linked[netif1][netif2] + linked = self._linked[iface1][iface2] except KeyError: if self.policy == NetworkPolicy.ACCEPT: linked = True @@ -401,93 +401,93 @@ class CoreNetwork(CoreNetworkBase): linked = False else: raise Exception(f"unknown policy: {self.policy.value}") - self._linked[netif1][netif2] = linked + self._linked[iface1][iface2] = linked return linked - def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None: + def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None: """ Unlink two interfaces, resulting in adding or removing ebtables filtering rules. - :param netif1: interface one - :param netif2: interface two + :param iface1: interface one + :param iface2: interface two :return: nothing """ with self._linked_lock: - if not self.linked(netif1, netif2): + if not self.linked(iface1, iface2): return - self._linked[netif1][netif2] = False + self._linked[iface1][iface2] = False ebq.ebchange(self) - def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None: + def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None: """ Link two interfaces together, resulting in adding or removing ebtables filtering rules. - :param netif1: interface one - :param netif2: interface two + :param iface1: interface one + :param iface2: interface two :return: nothing """ with self._linked_lock: - if self.linked(netif1, netif2): + if self.linked(iface1, iface2): return - self._linked[netif1][netif2] = True + self._linked[iface1][iface2] = True ebq.ebchange(self) def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Configure link parameters by applying tc queuing disciplines on the interface. - :param netif: interface one + :param iface: interface one :param options: options for configuring link - :param netif2: interface two + :param iface2: interface two :return: nothing """ - devname = netif.localname + devname = iface.localname tc = f"{TC_BIN} qdisc replace dev {devname}" parent = "root" changed = False bw = options.bandwidth - if netif.setparam("bw", bw): + if iface.setparam("bw", bw): # from tc-tbf(8): minimum value for burst is rate / kernel_hz - burst = max(2 * netif.mtu, int(bw / 1000)) + burst = max(2 * iface.mtu, int(bw / 1000)) # max IP payload limit = 0xFFFF tbf = f"tbf rate {bw} burst {burst} limit {limit}" if bw > 0: if self.up: cmd = f"{tc} {parent} handle 1: {tbf}" - netif.host_cmd(cmd) - netif.setparam("has_tbf", True) + iface.host_cmd(cmd) + iface.setparam("has_tbf", True) changed = True - elif netif.getparam("has_tbf") and bw <= 0: + elif iface.getparam("has_tbf") and bw <= 0: if self.up: cmd = f"{TC_BIN} qdisc delete dev {devname} {parent}" - netif.host_cmd(cmd) - netif.setparam("has_tbf", False) + iface.host_cmd(cmd) + iface.setparam("has_tbf", False) # removing the parent removes the child - netif.setparam("has_netem", False) + iface.setparam("has_netem", False) changed = True - if netif.getparam("has_tbf"): + if iface.getparam("has_tbf"): parent = "parent 1:1" netem = "netem" delay = options.delay - changed = max(changed, netif.setparam("delay", delay)) + changed = max(changed, iface.setparam("delay", delay)) loss = options.loss if loss is not None: loss = float(loss) - changed = max(changed, netif.setparam("loss", loss)) + changed = max(changed, iface.setparam("loss", loss)) duplicate = options.dup if duplicate is not None: duplicate = int(duplicate) - changed = max(changed, netif.setparam("duplicate", duplicate)) + changed = max(changed, iface.setparam("duplicate", duplicate)) jitter = options.jitter - changed = max(changed, netif.setparam("jitter", jitter)) + changed = max(changed, iface.setparam("jitter", jitter)) if not changed: return # jitter and delay use the same delay statement @@ -510,19 +510,19 @@ class CoreNetwork(CoreNetworkBase): duplicate_check = duplicate is None or duplicate <= 0 if all([delay_check, jitter_check, loss_check, duplicate_check]): # possibly remove netem if it exists and parent queue wasn't removed - if not netif.getparam("has_netem"): + if not iface.getparam("has_netem"): return if self.up: cmd = f"{TC_BIN} qdisc delete dev {devname} {parent} handle 10:" - netif.host_cmd(cmd) - netif.setparam("has_netem", False) + iface.host_cmd(cmd) + iface.setparam("has_netem", False) elif len(netem) > 1: if self.up: cmd = ( f"{TC_BIN} qdisc replace dev {devname} {parent} handle 10: {netem}" ) - netif.host_cmd(cmd) - netif.setparam("has_netem", True) + iface.host_cmd(cmd) + iface.setparam("has_netem", True) def linknet(self, net: CoreNetworkBase) -> CoreInterface: """ @@ -551,19 +551,19 @@ class CoreNetwork(CoreNetworkBase): if len(name) >= 16: raise ValueError(f"interface name {name} too long") - netif = Veth(self.session, None, name, localname, start=self.up) - self.attach(netif) + iface = Veth(self.session, None, name, localname, start=self.up) + self.attach(iface) if net.up and net.brname: - netif.net_client.set_interface_master(net.brname, netif.name) - i = net.newifindex() - net._netif[i] = netif + iface.net_client.set_iface_master(net.brname, iface.name) + i = net.next_iface_id() + net.ifaces[i] = iface with net._linked_lock: - net._linked[netif] = {} - netif.net = self - netif.othernet = net - return netif + net._linked[iface] = {} + iface.net = self + iface.othernet = net + return iface - def getlinknetif(self, net: CoreNetworkBase) -> Optional[CoreInterface]: + def get_linked_iface(self, net: CoreNetworkBase) -> Optional[CoreInterface]: """ Return the interface of that links this net with another net (that were linked using linknet()). @@ -571,9 +571,9 @@ class CoreNetwork(CoreNetworkBase): :param net: interface to get link for :return: interface the provided network is linked to """ - for netif in self.netifs(): - if netif.othernet == net: - return netif + for iface in self.get_ifaces(): + if iface.othernet == net: + return iface return None def addrconfig(self, addrlist: List[str]) -> None: @@ -690,17 +690,17 @@ class GreTapBridge(CoreNetwork): ) self.attach(self.gretap) - def setkey(self, key: int, interface_data: InterfaceData) -> None: + def setkey(self, key: int, iface_data: InterfaceData) -> None: """ Set the GRE key used for the GreTap device. This needs to be set prior to instantiating the GreTap device (before addrconfig). :param key: gre key - :param interface_data: interface data for setting up tunnel key + :param iface_data: interface data for setting up tunnel key :return: nothing """ self.grekey = key - addresses = interface_data.get_addresses() + addresses = iface_data.get_addresses() if addresses: self.addrconfig(addresses) @@ -802,7 +802,7 @@ class CtrlNet(CoreNetwork): self.host_cmd(f"{self.updown_script} {self.brname} startup") if self.serverintf: - self.net_client.set_interface_master(self.brname, self.serverintf) + self.net_client.set_iface_master(self.brname, self.serverintf) def shutdown(self) -> None: """ @@ -812,7 +812,7 @@ class CtrlNet(CoreNetwork): """ if self.serverintf is not None: try: - self.net_client.delete_interface(self.brname, self.serverintf) + self.net_client.delete_iface(self.brname, self.serverintf) except CoreCommandError: logging.exception( "error deleting server interface %s from bridge %s", @@ -850,18 +850,18 @@ class PtpNet(CoreNetwork): policy: NetworkPolicy = NetworkPolicy.ACCEPT - def attach(self, netif: CoreInterface) -> None: + def attach(self, iface: CoreInterface) -> None: """ Attach a network interface, but limit attachment to two interfaces. - :param netif: network interface + :param iface: network interface :return: nothing """ - if len(self._netif) >= 2: + if len(self.ifaces) >= 2: raise ValueError( "Point-to-point links support at most 2 network interfaces" ) - super().attach(netif) + super().attach(iface) def data( self, message_type: MessageFlags = MessageFlags.NONE, source: str = None @@ -886,67 +886,67 @@ class PtpNet(CoreNetwork): """ all_links = [] - if len(self._netif) != 2: + if len(self.ifaces) != 2: return all_links - interface1, interface2 = self._netif.values() + iface1, iface2 = self.get_ifaces() unidirectional = 0 - if interface1.getparams() != interface2.getparams(): + if iface1.getparams() != iface2.getparams(): unidirectional = 1 - interface1_ip4 = None - interface1_ip4_mask = None - interface1_ip6 = None - interface1_ip6_mask = None - for address in interface1.addrlist: + iface1_ip4 = None + iface1_ip4_mask = None + iface1_ip6 = None + iface1_ip6_mask = None + for address in iface1.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - interface1_ip4 = ip - interface1_ip4_mask = mask + iface1_ip4 = ip + iface1_ip4_mask = mask else: - interface1_ip6 = ip - interface1_ip6_mask = mask + iface1_ip6 = ip + iface1_ip6_mask = mask - interface2_ip4 = None - interface2_ip4_mask = None - interface2_ip6 = None - interface2_ip6_mask = None - for address in interface2.addrlist: + iface2_ip4 = None + iface2_ip4_mask = None + iface2_ip6 = None + iface2_ip6_mask = None + for address in iface2.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - interface2_ip4 = ip - interface2_ip4_mask = mask + iface2_ip4 = ip + iface2_ip4_mask = mask else: - interface2_ip6 = ip - interface2_ip6_mask = mask + iface2_ip6 = ip + iface2_ip6_mask = mask link_data = LinkData( message_type=flags, - node1_id=interface1.node.id, - node2_id=interface2.node.id, + node1_id=iface1.node.id, + node2_id=iface2.node.id, link_type=self.linktype, unidirectional=unidirectional, - delay=interface1.getparam("delay"), - bandwidth=interface1.getparam("bw"), - loss=interface1.getparam("loss"), - dup=interface1.getparam("duplicate"), - jitter=interface1.getparam("jitter"), - interface1_id=interface1.node.getifindex(interface1), - interface1_name=interface1.name, - interface1_mac=interface1.hwaddr, - interface1_ip4=interface1_ip4, - interface1_ip4_mask=interface1_ip4_mask, - interface1_ip6=interface1_ip6, - interface1_ip6_mask=interface1_ip6_mask, - interface2_id=interface2.node.getifindex(interface2), - interface2_name=interface2.name, - interface2_mac=interface2.hwaddr, - interface2_ip4=interface2_ip4, - interface2_ip4_mask=interface2_ip4_mask, - interface2_ip6=interface2_ip6, - interface2_ip6_mask=interface2_ip6_mask, + delay=iface1.getparam("delay"), + bandwidth=iface1.getparam("bw"), + loss=iface1.getparam("loss"), + dup=iface1.getparam("duplicate"), + jitter=iface1.getparam("jitter"), + iface1_id=iface1.node.get_iface_id(iface1), + iface1_name=iface1.name, + iface1_mac=iface1.hwaddr, + iface1_ip4=iface1_ip4, + iface1_ip4_mask=iface1_ip4_mask, + iface1_ip6=iface1_ip6, + iface1_ip6_mask=iface1_ip6_mask, + iface2_id=iface2.node.get_iface_id(iface2), + iface2_name=iface2.name, + iface2_mac=iface2.hwaddr, + iface2_ip4=iface2_ip4, + iface2_ip4_mask=iface2_ip4_mask, + iface2_ip6=iface2_ip6, + iface2_ip6_mask=iface2_ip6_mask, ) all_links.append(link_data) @@ -956,16 +956,16 @@ class PtpNet(CoreNetwork): link_data = LinkData( message_type=MessageFlags.NONE, link_type=self.linktype, - node1_id=interface2.node.id, - node2_id=interface1.node.id, - delay=interface2.getparam("delay"), - bandwidth=interface2.getparam("bw"), - loss=interface2.getparam("loss"), - dup=interface2.getparam("duplicate"), - jitter=interface2.getparam("jitter"), + node1_id=iface2.node.id, + node2_id=iface1.node.id, + delay=iface2.getparam("delay"), + bandwidth=iface2.getparam("bw"), + loss=iface2.getparam("loss"), + dup=iface2.getparam("duplicate"), + jitter=iface2.getparam("jitter"), unidirectional=1, - interface1_id=interface2.node.getifindex(interface2), - interface2_id=interface1.node.getifindex(interface1), + iface1_id=iface2.node.get_iface_id(iface2), + iface2_id=iface1.node.get_iface_id(iface1), ) all_links.append(link_data) return all_links @@ -1045,17 +1045,17 @@ class WlanNode(CoreNetwork): self.net_client.disable_mac_learning(self.brname) ebq.ebchange(self) - def attach(self, netif: CoreInterface) -> None: + def attach(self, iface: CoreInterface) -> None: """ Attach a network interface. - :param netif: network interface + :param iface: network interface :return: nothing """ - super().attach(netif) + super().attach(iface) if self.model: - netif.poshook = self.model.position_callback - netif.setposition() + iface.poshook = self.model.position_callback + iface.setposition() def setmodel(self, model: "WirelessModelType", config: Dict[str, str]): """ @@ -1068,9 +1068,9 @@ class WlanNode(CoreNetwork): logging.debug("node(%s) setting model: %s", self.name, model.name) if model.config_type == RegisterTlvs.WIRELESS: self.model = model(session=self.session, _id=self.id) - for netif in self.netifs(): - netif.poshook = self.model.position_callback - netif.setposition() + for iface in self.get_ifaces(): + iface.poshook = self.model.position_callback + iface.setposition() self.updatemodel(config) elif model.config_type == RegisterTlvs.MOBILITY: self.mobility = model(session=self.session, _id=self.id) @@ -1088,8 +1088,8 @@ class WlanNode(CoreNetwork): "node(%s) updating model(%s): %s", self.id, self.model.name, config ) self.model.update_config(config) - for netif in self.netifs(): - netif.setposition() + for iface in self.get_ifaces(): + iface.setposition() def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 741fe7d5..555e0ec9 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -51,8 +51,8 @@ class PhysicalNode(CoreNodeBase): _source, target = self._mounts.pop(-1) self.umount(target) - for netif in self.netifs(): - netif.shutdown() + for iface in self.get_ifaces(): + iface.shutdown() self.rmnodedir() @@ -65,117 +65,115 @@ class PhysicalNode(CoreNodeBase): """ return sh - def sethwaddr(self, ifindex: int, addr: str) -> None: + def sethwaddr(self, iface_id: int, addr: str) -> None: """ Set hardware address for an interface. - :param ifindex: index of interface to set hardware address for + :param iface_id: index of interface to set hardware address for :param addr: hardware address to set :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ addr = utils.validate_mac(addr) - interface = self._netif[ifindex] - interface.sethwaddr(addr) + iface = self.ifaces[iface_id] + iface.sethwaddr(addr) if self.up: - self.net_client.device_mac(interface.name, addr) + self.net_client.device_mac(iface.name, addr) - def addaddr(self, ifindex: int, addr: str) -> None: + def addaddr(self, iface_id: int, addr: str) -> None: """ Add an address to an interface. - :param ifindex: index of interface to add address to + :param iface_id: index of interface to add address to :param addr: address to add :return: nothing """ addr = utils.validate_ip(addr) - interface = self._netif[ifindex] + iface = self.get_iface(iface_id) if self.up: - self.net_client.create_address(interface.name, addr) - interface.addaddr(addr) + self.net_client.create_address(iface.name, addr) + iface.addaddr(addr) - def deladdr(self, ifindex: int, addr: str) -> None: + def deladdr(self, iface_id: int, addr: str) -> None: """ Delete an address from an interface. - :param ifindex: index of interface to delete + :param iface_id: index of interface to delete :param addr: address to delete :return: nothing """ - interface = self._netif[ifindex] - + iface = self.ifaces[iface_id] try: - interface.deladdr(addr) + iface.deladdr(addr) except ValueError: logging.exception("trying to delete unknown address: %s", addr) - if self.up: - self.net_client.delete_address(interface.name, addr) + self.net_client.delete_address(iface.name, addr) - def adoptnetif( - self, netif: CoreInterface, ifindex: int, hwaddr: str, addrlist: List[str] + def adopt_iface( + self, iface: CoreInterface, iface_id: int, hwaddr: str, addrlist: List[str] ) -> None: """ When a link message is received linking this node to another part of the emulation, no new interface is created; instead, adopt the - GreTap netif as the node interface. + GreTap interface as the node interface. """ - netif.name = f"gt{ifindex}" - netif.node = self - self.addnetif(netif, ifindex) + iface.name = f"gt{iface_id}" + iface.node = self + self.add_iface(iface, iface_id) # use a more reasonable name, e.g. "gt0" instead of "gt.56286.150" if self.up: - self.net_client.device_down(netif.localname) - self.net_client.device_name(netif.localname, netif.name) - netif.localname = netif.name + self.net_client.device_down(iface.localname) + self.net_client.device_name(iface.localname, iface.name) + iface.localname = iface.name if hwaddr: - self.sethwaddr(ifindex, hwaddr) + self.sethwaddr(iface_id, hwaddr) for addr in addrlist: - self.addaddr(ifindex, addr) + self.addaddr(iface_id, addr) if self.up: - self.net_client.device_up(netif.localname) + self.net_client.device_up(iface.localname) def linkconfig( - self, netif: CoreInterface, options: LinkOptions, netif2: CoreInterface = None + self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None ) -> None: """ Apply tc queing disciplines using linkconfig. """ linux_bridge = CoreNetwork(self.session) linux_bridge.up = True - linux_bridge.linkconfig(netif, options, netif2) + linux_bridge.linkconfig(iface, options, iface2) del linux_bridge - def newifindex(self) -> int: + def next_iface_id(self) -> int: with self.lock: - while self.ifindex in self._netif: - self.ifindex += 1 - ifindex = self.ifindex - self.ifindex += 1 - return ifindex + while self.iface_id in self.ifaces: + self.iface_id += 1 + iface_id = self.iface_id + self.iface_id += 1 + return iface_id - def newnetif( - self, net: CoreNetworkBase, interface_data: InterfaceData + def new_iface( + self, net: CoreNetworkBase, iface_data: InterfaceData ) -> CoreInterface: logging.info("creating interface") - addresses = interface_data.get_addresses() - ifindex = interface_data.id - if ifindex is None: - ifindex = self.newifindex() - name = interface_data.name + addresses = iface_data.get_addresses() + iface_id = iface_data.id + if iface_id is None: + iface_id = self.next_iface_id() + name = iface_data.name if name is None: - name = f"gt{ifindex}" + name = f"gt{iface_id}" if self.up: # this is reached when this node is linked to a network node # tunnel to net not built yet, so build it now and adopt it _, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) - self.adoptnetif(remote_tap, ifindex, interface_data.mac, addresses) + self.adopt_iface(remote_tap, iface_id, iface_data.mac, addresses) return remote_tap else: # this is reached when configuring services (self.up=False) - netif = GreTap(node=self, name=name, session=self.session, start=False) - self.adoptnetif(netif, ifindex, interface_data.mac, addresses) - return netif + iface = GreTap(node=self, name=name, session=self.session, start=False) + self.adopt_iface(iface, iface_id, iface_data.mac, addresses) + return iface def privatedir(self, path: str) -> None: if path[0] != "/": @@ -257,10 +255,10 @@ class Rj45Node(CoreNodeBase): will run on, default is None for localhost """ super().__init__(session, _id, name, server) - self.interface = CoreInterface(session, self, name, name, mtu, server) - self.interface.transport_type = TransportType.RAW + self.iface = CoreInterface(session, self, name, name, mtu, server) + self.iface.transport_type = TransportType.RAW self.lock: threading.RLock = threading.RLock() - self.ifindex: Optional[int] = None + self.iface_id: Optional[int] = None self.old_up: bool = False self.old_addrs: List[Tuple[str, Optional[str]]] = [] @@ -273,7 +271,7 @@ class Rj45Node(CoreNodeBase): """ # interface will also be marked up during net.attach() self.savestate() - self.net_client.device_up(self.interface.localname) + self.net_client.device_up(self.iface.localname) self.up = True def shutdown(self) -> None: @@ -285,7 +283,7 @@ class Rj45Node(CoreNodeBase): """ if not self.up: return - localname = self.interface.localname + localname = self.iface.localname self.net_client.device_down(localname) self.net_client.device_flush(localname) try: @@ -295,8 +293,8 @@ class Rj45Node(CoreNodeBase): self.up = False self.restorestate() - def newnetif( - self, net: CoreNetworkBase, interface_data: InterfaceData + def new_iface( + self, net: CoreNetworkBase, iface_data: InterfaceData ) -> CoreInterface: """ This is called when linking with another node. Since this node @@ -304,70 +302,51 @@ class Rj45Node(CoreNodeBase): but attach ourselves to the given network. :param net: new network instance - :param interface_data: interface data for new interface + :param iface_data: interface data for new interface :return: interface index :raises ValueError: when an interface has already been created, one max """ with self.lock: - ifindex = interface_data.id - if ifindex is None: - ifindex = 0 - if self.interface.net is not None: - raise ValueError("RJ45 nodes support at most 1 network interface") - self._netif[ifindex] = self.interface - self.ifindex = ifindex + iface_id = iface_data.id + if iface_id is None: + iface_id = 0 + if self.iface.net is not None: + raise CoreError("RJ45 nodes support at most 1 network interface") + self.ifaces[iface_id] = self.iface + self.iface_id = iface_id if net is not None: - self.interface.attachnet(net) - for addr in interface_data.get_addresses(): + self.iface.attachnet(net) + for addr in iface_data.get_addresses(): self.addaddr(addr) - return self.interface + return self.iface - def delnetif(self, ifindex: int) -> None: + def delete_iface(self, iface_id: int) -> None: """ Delete a network interface. - :param ifindex: interface index to delete + :param iface_id: interface index to delete :return: nothing """ - if ifindex is None: - ifindex = 0 - self._netif.pop(ifindex) - if ifindex == self.ifindex: - self.shutdown() - else: - raise ValueError(f"ifindex {ifindex} does not exist") + self.get_iface(iface_id) + self.ifaces.pop(iface_id) + self.shutdown() - def netif( - self, ifindex: int, net: CoreNetworkBase = None - ) -> Optional[CoreInterface]: - """ - This object is considered the network interface, so we only - return self here. This keeps the RJ45Node compatible with - real nodes. + def get_iface(self, iface_id: int) -> CoreInterface: + if iface_id != self.iface_id or iface_id not in self.ifaces: + raise CoreError(f"node({self.name}) interface({iface_id}) does not exist") + return self.iface - :param ifindex: interface index to retrieve - :param net: network to retrieve - :return: a network interface - """ - if net is not None and net == self.interface.net: - return self.interface - if ifindex is None: - ifindex = 0 - if ifindex == self.ifindex: - return self.interface - return None - - def getifindex(self, netif: CoreInterface) -> Optional[int]: + def get_iface_id(self, iface: CoreInterface) -> Optional[int]: """ Retrieve network interface index. - :param netif: network interface to retrieve + :param iface: network interface to retrieve index for :return: interface index, None otherwise """ - if netif != self.interface: - return None - return self.ifindex + if iface is not self.iface: + raise CoreError(f"node({self.name}) does not have interface({iface.name})") + return self.iface_id def addaddr(self, addr: str) -> None: """ @@ -380,7 +359,7 @@ class Rj45Node(CoreNodeBase): addr = utils.validate_ip(addr) if self.up: self.net_client.create_address(self.name, addr) - self.interface.addaddr(addr) + self.iface.addaddr(addr) def deladdr(self, addr: str) -> None: """ @@ -392,7 +371,7 @@ class Rj45Node(CoreNodeBase): """ if self.up: self.net_client.delete_address(self.name, addr) - self.interface.deladdr(addr) + self.iface.deladdr(addr) def savestate(self) -> None: """ @@ -404,7 +383,7 @@ class Rj45Node(CoreNodeBase): """ self.old_up = False self.old_addrs: List[Tuple[str, Optional[str]]] = [] - localname = self.interface.localname + localname = self.iface.localname output = self.net_client.address_show(localname) for line in output.split("\n"): items = line.split() @@ -429,7 +408,7 @@ class Rj45Node(CoreNodeBase): :return: nothing :raises CoreCommandError: when there is a command exception """ - localname = self.interface.localname + localname = self.iface.localname logging.info("restoring rj45 state: %s", localname) for addr in self.old_addrs: self.net_client.create_address(localname, addr[0], addr[1]) @@ -446,7 +425,7 @@ class Rj45Node(CoreNodeBase): :return: True if position changed, False otherwise """ super().setposition(x, y, z) - self.interface.setposition() + self.iface.setposition() def termcmdstring(self, sh: str) -> str: raise CoreError("rj45 does not support terminal commands") diff --git a/daemon/core/services/bird.py b/daemon/core/services/bird.py index 4901ea56..16f0bb84 100644 --- a/daemon/core/services/bird.py +++ b/daemon/core/services/bird.py @@ -35,10 +35,8 @@ class Bird(CoreService): """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -84,7 +82,7 @@ protocol device { for s in node.services: if cls.name not in s.dependencies: continue - cfg += s.generatebirdconfig(node) + cfg += s.generate_bird_config(node) return cfg @@ -106,11 +104,11 @@ class BirdService(CoreService): meta = "The config file for this service can be found in the bird service." @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): return "" @classmethod - def generatebirdifcconfig(cls, node): + def generate_bird_iface_config(cls, node): """ Use only bare interfaces descriptions in generated protocol configurations. This has the slight advantage of being the same @@ -118,10 +116,8 @@ class BirdService(CoreService): """ cfg = "" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += ' interface "%s";\n' % ifc.name + for iface in node.get_ifaces(control=False): + cfg += ' interface "%s";\n' % iface.name return cfg @@ -135,7 +131,7 @@ class BirdBgp(BirdService): custom_needed = True @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): return """ /* This is a sample config that should be customized with appropriate AS numbers * and peers; add one section like this for each neighbor */ @@ -165,7 +161,7 @@ class BirdOspf(BirdService): name = "BIRD_OSPFv2" @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): cfg = "protocol ospf {\n" cfg += " export filter {\n" cfg += " if source = RTS_BGP then {\n" @@ -175,7 +171,7 @@ class BirdOspf(BirdService): cfg += " accept;\n" cfg += " };\n" cfg += " area 0.0.0.0 {\n" - cfg += cls.generatebirdifcconfig(node) + cfg += cls.generate_bird_iface_config(node) cfg += " };\n" cfg += "}\n\n" @@ -190,12 +186,12 @@ class BirdRadv(BirdService): name = "BIRD_RADV" @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): cfg = "/* This is a sample config that must be customized */\n" cfg += "protocol radv {\n" cfg += " # auto configuration on all interfaces\n" - cfg += cls.generatebirdifcconfig(node) + cfg += cls.generate_bird_iface_config(node) cfg += " # Advertise DNS\n" cfg += " rdnss {\n" cfg += "# lifetime mult 10;\n" @@ -218,11 +214,11 @@ class BirdRip(BirdService): name = "BIRD_RIP" @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): cfg = "protocol rip {\n" cfg += " period 10;\n" cfg += " garbage time 60;\n" - cfg += cls.generatebirdifcconfig(node) + cfg += cls.generate_bird_iface_config(node) cfg += " honor neighbor;\n" cfg += " authentication none;\n" cfg += " import all;\n" @@ -241,7 +237,7 @@ class BirdStatic(BirdService): custom_needed = True @classmethod - def generatebirdconfig(cls, node): + def generate_bird_config(cls, node): cfg = "/* This is a sample config that must be customized */\n" cfg += "protocol static {\n" cfg += "# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n" diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index 9d09516e..da438bab 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -20,14 +20,14 @@ class EmaneTransportService(CoreService): def generate_config(cls, node, filename): if filename == cls.configs[0]: transport_commands = [] - for interface in node.netifs(sort=True): + for iface in node.get_ifaces(): try: - network_node = node.session.get_node(interface.net.id, EmaneNet) + network_node = node.session.get_node(iface.net.id, EmaneNet) config = node.session.emane.get_configs( network_node.id, network_node.model.name ) if config and emanexml.is_external(config): - nem_id = network_node.getnemid(interface) + nem_id = network_node.getnemid(iface) command = ( "emanetransportd -r -l 0 -d ../transportdaemon%s.xml" % nem_id diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 9a344339..97a8b334 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -59,12 +59,12 @@ class FRRZebra(CoreService): """ # we could verify here that filename == frr.conf cfg = "" - for ifc in node.netifs(): - cfg += "interface %s\n" % ifc.name + for iface in node.get_ifaces(): + cfg += "interface %s\n" % iface.name # include control interfaces in addressing but not routing daemons - if hasattr(ifc, "control") and ifc.control is True: + if hasattr(iface, "control") and iface.control is True: cfg += " " - cfg += "\n ".join(map(cls.addrstr, ifc.addrlist)) + cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) cfg += "\n" continue cfgv4 = "" @@ -74,18 +74,18 @@ class FRRZebra(CoreService): for s in node.services: if cls.name not in s.dependencies: continue - ifccfg = s.generatefrrifcconfig(node, ifc) + iface_config = s.generate_frr_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True if s.ipv6_routing: want_ipv6 = True - cfgv6 += ifccfg + cfgv6 += iface_config else: - cfgv4 += ifccfg + cfgv4 += iface_config if want_ipv4: ipv4list = filter( - lambda x: netaddr.valid_ipv4(x.split("/")[0]), ifc.addrlist + lambda x: netaddr.valid_ipv4(x.split("/")[0]), iface.addrlist ) cfg += " " cfg += "\n ".join(map(cls.addrstr, ipv4list)) @@ -93,7 +93,7 @@ class FRRZebra(CoreService): cfg += cfgv4 if want_ipv6: ipv6list = filter( - lambda x: netaddr.valid_ipv6(x.split("/")[0]), ifc.addrlist + lambda x: netaddr.valid_ipv6(x.split("/")[0]), iface.addrlist ) cfg += " " cfg += "\n ".join(map(cls.addrstr, ipv6list)) @@ -104,7 +104,7 @@ class FRRZebra(CoreService): for s in node.services: if cls.name not in s.dependencies: continue - cfg += s.generatefrrconfig(node) + cfg += s.generate_frr_config(node) return cfg @staticmethod @@ -237,10 +237,10 @@ bootfrr frr_bin_search, constants.FRR_STATE_DIR, ) - for ifc in node.netifs(): - cfg += f"ip link set dev {ifc.name} down\n" + for iface in node.get_ifaces(): + cfg += f"ip link set dev {iface.name} down\n" cfg += "sleep 1\n" - cfg += f"ip link set dev {ifc.name} up\n" + cfg += f"ip link set dev {iface.name} up\n" return cfg @classmethod @@ -334,10 +334,8 @@ class FrrService(CoreService): """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -345,16 +343,16 @@ class FrrService(CoreService): return "0.0.0.0" @staticmethod - def rj45check(ifc): + def rj45check(iface): """ Helper to detect whether interface is connected an external RJ45 link. """ - if ifc.net: - for peerifc in ifc.net.netifs(): - if peerifc == ifc: + if iface.net: + for peer_iface in iface.net.get_ifaces(): + if peer_iface == iface: continue - if isinstance(peerifc.node, Rj45Node): + if isinstance(peer_iface.node, Rj45Node): return True return False @@ -363,11 +361,11 @@ class FrrService(CoreService): return "" @classmethod - def generatefrrifcconfig(cls, node, ifc): + def generate_frr_iface_config(cls, node, iface): return "" @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): return "" @@ -385,43 +383,41 @@ class FRROspfv2(FrrService): ipv4_routing = True @staticmethod - def mtucheck(ifc): + def mtucheck(iface): """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a GreTap device. """ - if ifc.mtu != 1500: + if iface.mtu != 1500: # a workaround for PhysicalNode GreTap, which has no knowledge of # the other nodes/nets return " ip ospf mtu-ignore\n" - if not ifc.net: + if not iface.net: return "" - for i in ifc.net.netifs(): - if i.mtu != ifc.mtu: + for iface in iface.net.get_ifaces(): + if iface.mtu != iface.mtu: return " ip ospf mtu-ignore\n" return "" @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " ip ospf network point-to-point\n" return "" @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "router ospf\n" rtrid = cls.routerid(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: addr = a.split("/")[0] if not netaddr.valid_ipv4(addr): continue @@ -430,8 +426,8 @@ class FRROspfv2(FrrService): return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): - return cls.mtucheck(ifc) + def generate_frr_iface_config(cls, node, iface): + return cls.mtucheck(iface) class FRROspfv3(FrrService): @@ -449,57 +445,55 @@ class FRROspfv3(FrrService): ipv6_routing = True @staticmethod - def minmtu(ifc): + def minmtu(iface): """ Helper to discover the minimum MTU of interfaces linked with the given interface. """ - mtu = ifc.mtu - if not ifc.net: + mtu = iface.mtu + if not iface.net: return mtu - for i in ifc.net.netifs(): - if i.mtu < mtu: - mtu = i.mtu + for iface in iface.net.get_ifaces(): + if iface.mtu < mtu: + mtu = iface.mtu return mtu @classmethod - def mtucheck(cls, ifc): + def mtucheck(cls, iface): """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(ifc) - if minmtu < ifc.mtu: + minmtu = cls.minmtu(iface) + if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " ipv6 ospf6 network point-to-point\n" return "" @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "router ospf6\n" rtrid = cls.routerid(node) cfg += " router-id %s\n" % rtrid - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += " interface %s area 0.0.0.0\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += " interface %s area 0.0.0.0\n" % iface.name cfg += "!\n" return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): - return cls.mtucheck(ifc) + def generate_frr_iface_config(cls, node, iface): + return cls.mtucheck(iface) # cfg = cls.mtucheck(ifc) # external RJ45 connections will use default OSPF timers # if cls.rj45check(ifc): @@ -531,7 +525,7 @@ class FRRBgp(FrrService): ipv6_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" @@ -555,7 +549,7 @@ class FRRRip(FrrService): ipv4_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = """\ router rip redistribute static @@ -579,7 +573,7 @@ class FRRRipng(FrrService): ipv6_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = """\ router ripng redistribute static @@ -604,18 +598,16 @@ class FRRBabel(FrrService): ipv6_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "router babel\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += " network %s\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += " network %s\n" % iface.name cfg += " redistribute static\n redistribute ipv4 connected\n" return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): - if ifc.net and isinstance(ifc.net, (EmaneNet, WlanNode)): + def generate_frr_iface_config(cls, node, iface): + if iface.net and isinstance(iface.net, (EmaneNet, WlanNode)): return " babel wireless\n no babel split-horizon\n" else: return " babel wired\n babel split-horizon\n" @@ -633,11 +625,11 @@ class FRRpimd(FrrService): ipv4_routing = True @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): ifname = "eth0" - for ifc in node.netifs(): - if ifc.name != "lo": - ifname = ifc.name + for iface in node.get_ifaces(): + if iface.name != "lo": + ifname = iface.name break cfg = "router mfea\n!\n" cfg += "router igmp\n!\n" @@ -649,7 +641,7 @@ class FRRpimd(FrrService): return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): + def generate_frr_iface_config(cls, node, iface): return " ip mfea\n ip igmp\n ip pim\n" @@ -668,17 +660,17 @@ class FRRIsis(FrrService): ipv6_routing = True @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " isis network point-to-point\n" return "" @classmethod - def generatefrrconfig(cls, node): + def generate_frr_config(cls, node): cfg = "router isis DEFAULT\n" cfg += " net 47.0001.0000.1900.%04x.00\n" % node.id cfg += " metric-style wide\n" @@ -687,9 +679,9 @@ class FRRIsis(FrrService): return cfg @classmethod - def generatefrrifcconfig(cls, node, ifc): + def generate_frr_iface_config(cls, node, iface): cfg = " ip router isis DEFAULT\n" cfg += " ipv6 router isis DEFAULT\n" cfg += " isis circuit-type level-2-only\n" - cfg += cls.ptpcheck(ifc) + cfg += cls.ptpcheck(iface) return cfg diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 3c9f262d..38b90d48 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -32,10 +32,8 @@ class NrlService(CoreService): prefix of a node, using the supplied prefix length. This ignores the interface's prefix length, so e.g. '/32' can turn into '/24'. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return f"{a}/{prefixlen}" @@ -54,8 +52,8 @@ class MgenSinkService(NrlService): @classmethod def generate_config(cls, node, filename): cfg = "0.0 LISTEN UDP 5000\n" - for ifc in node.netifs(): - name = utils.sysctl_devname(ifc.name) + for iface in node.get_ifaces(): + name = utils.sysctl_devname(iface.name) cfg += "0.0 Join 224.225.1.2 INTERFACE %s\n" % name return cfg @@ -91,11 +89,11 @@ class NrlNhdp(NrlService): cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name - netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs())) - if len(netifs) > 0: - interfacenames = map(lambda x: x.name, netifs) + ifaces = node.get_ifaces(control=False) + if len(ifaces) > 0: + iface_names = map(lambda x: x.name, ifaces) cmd += " -i " - cmd += " -i ".join(interfacenames) + cmd += " -i ".join(iface_names) return (cmd,) @@ -125,16 +123,16 @@ class NrlSmf(NrlService): cmd = "nrlsmf instance %s_smf" % node.name servicenames = map(lambda x: x.name, node.services) - netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs())) - if len(netifs) == 0: + ifaces = node.get_ifaces(control=False) + if len(ifaces) == 0: return "" if "arouted" in servicenames: comments += "# arouted service is enabled\n" cmd += " tap %s_tap" % (node.name,) cmd += " unicast %s" % cls.firstipv4prefix(node, 24) - cmd += " push lo,%s resequence on" % netifs[0].name - if len(netifs) > 0: + cmd += " push lo,%s resequence on" % ifaces[0].name + if len(ifaces) > 0: if "NHDP" in servicenames: comments += "# NHDP service is enabled\n" cmd += " ecds " @@ -143,8 +141,8 @@ class NrlSmf(NrlService): cmd += " smpr " else: cmd += " cf " - interfacenames = map(lambda x: x.name, netifs) - cmd += ",".join(interfacenames) + iface_names = map(lambda x: x.name, ifaces) + cmd += ",".join(iface_names) cmd += " hash MD5" cmd += " log /var/log/nrlsmf.log" @@ -171,10 +169,10 @@ class NrlOlsr(NrlService): """ cmd = cls.startup[0] # are multiple interfaces supported? No. - netifs = list(node.netifs()) - if len(netifs) > 0: - ifc = netifs[0] - cmd += " -i %s" % ifc.name + ifaces = node.get_ifaces() + if len(ifaces) > 0: + iface = ifaces[0] + cmd += " -i %s" % iface.name cmd += " -l /var/log/nrlolsrd.log" cmd += " -rpipe %s_olsr" % node.name @@ -215,11 +213,11 @@ class NrlOlsrv2(NrlService): cmd += " -p olsr" - netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs())) - if len(netifs) > 0: - interfacenames = map(lambda x: x.name, netifs) + ifaces = node.get_ifaces(control=False) + if len(ifaces) > 0: + iface_names = map(lambda x: x.name, ifaces) cmd += " -i " - cmd += " -i ".join(interfacenames) + cmd += " -i ".join(iface_names) return (cmd,) @@ -243,11 +241,11 @@ class OlsrOrg(NrlService): Generate the appropriate command-line based on node interfaces. """ cmd = cls.startup[0] - netifs = list(filter(lambda x: not getattr(x, "control", False), node.netifs())) - if len(netifs) > 0: - interfacenames = map(lambda x: x.name, netifs) + ifaces = node.get_ifaces(control=False) + if len(ifaces) > 0: + iface_names = map(lambda x: x.name, ifaces) cmd += " -i " - cmd += " -i ".join(interfacenames) + cmd += " -i ".join(iface_names) return (cmd,) @@ -607,8 +605,8 @@ class MgenActor(NrlService): comments = "" cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name - netifs = [x for x in node.netifs() if not getattr(x, "control", False)] - if len(netifs) == 0: + ifaces = node.get_ifaces(control=False) + if len(ifaces) == 0: return "" cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index a62cbc5c..41cfa3d8 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -56,12 +56,12 @@ class Zebra(CoreService): """ # we could verify here that filename == Quagga.conf cfg = "" - for ifc in node.netifs(): - cfg += "interface %s\n" % ifc.name + for iface in node.get_ifaces(): + cfg += "interface %s\n" % iface.name # include control interfaces in addressing but not routing daemons - if hasattr(ifc, "control") and ifc.control is True: + if hasattr(iface, "control") and iface.control is True: cfg += " " - cfg += "\n ".join(map(cls.addrstr, ifc.addrlist)) + cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) cfg += "\n" continue cfgv4 = "" @@ -71,18 +71,18 @@ class Zebra(CoreService): for s in node.services: if cls.name not in s.dependencies: continue - ifccfg = s.generatequaggaifcconfig(node, ifc) + iface_config = s.generate_quagga_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True if s.ipv6_routing: want_ipv6 = True - cfgv6 += ifccfg + cfgv6 += iface_config else: - cfgv4 += ifccfg + cfgv4 += iface_config if want_ipv4: ipv4list = filter( - lambda x: netaddr.valid_ipv4(x.split("/")[0]), ifc.addrlist + lambda x: netaddr.valid_ipv4(x.split("/")[0]), iface.addrlist ) cfg += " " cfg += "\n ".join(map(cls.addrstr, ipv4list)) @@ -90,7 +90,7 @@ class Zebra(CoreService): cfg += cfgv4 if want_ipv6: ipv6list = filter( - lambda x: netaddr.valid_ipv6(x.split("/")[0]), ifc.addrlist + lambda x: netaddr.valid_ipv6(x.split("/")[0]), iface.addrlist ) cfg += " " cfg += "\n ".join(map(cls.addrstr, ipv6list)) @@ -101,7 +101,7 @@ class Zebra(CoreService): for s in node.services: if cls.name not in s.dependencies: continue - cfg += s.generatequaggaconfig(node) + cfg += s.generate_quagga_config(node) return cfg @staticmethod @@ -252,10 +252,8 @@ class QuaggaService(CoreService): """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -263,16 +261,16 @@ class QuaggaService(CoreService): return "0.0.0.%d" % node.id @staticmethod - def rj45check(ifc): + def rj45check(iface): """ Helper to detect whether interface is connected an external RJ45 link. """ - if ifc.net: - for peerifc in ifc.net.netifs(): - if peerifc == ifc: + if iface.net: + for peer_iface in iface.net.get_ifaces(): + if peer_iface == iface: continue - if isinstance(peerifc.node, Rj45Node): + if isinstance(peer_iface.node, Rj45Node): return True return False @@ -281,11 +279,11 @@ class QuaggaService(CoreService): return "" @classmethod - def generatequaggaifcconfig(cls, node, ifc): + def generate_quagga_iface_config(cls, node, iface): return "" @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): return "" @@ -303,43 +301,41 @@ class Ospfv2(QuaggaService): ipv4_routing = True @staticmethod - def mtucheck(ifc): + def mtucheck(iface): """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a GreTap device. """ - if ifc.mtu != 1500: + if iface.mtu != 1500: # a workaround for PhysicalNode GreTap, which has no knowledge of # the other nodes/nets return " ip ospf mtu-ignore\n" - if not ifc.net: + if not iface.net: return "" - for i in ifc.net.netifs(): - if i.mtu != ifc.mtu: + for iface in iface.net.get_ifaces(): + if iface.mtu != iface.mtu: return " ip ospf mtu-ignore\n" return "" @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " ip ospf network point-to-point\n" return "" @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = "router ospf\n" rtrid = cls.routerid(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: addr = a.split("/")[0] if netaddr.valid_ipv4(addr): cfg += " network %s area 0\n" % a @@ -347,12 +343,12 @@ class Ospfv2(QuaggaService): return cfg @classmethod - def generatequaggaifcconfig(cls, node, ifc): - cfg = cls.mtucheck(ifc) + def generate_quagga_iface_config(cls, node, iface): + cfg = cls.mtucheck(iface) # external RJ45 connections will use default OSPF timers - if cls.rj45check(ifc): + if cls.rj45check(iface): return cfg - cfg += cls.ptpcheck(ifc) + cfg += cls.ptpcheck(iface) return ( cfg + """\ @@ -378,58 +374,56 @@ class Ospfv3(QuaggaService): ipv6_routing = True @staticmethod - def minmtu(ifc): + def minmtu(iface): """ Helper to discover the minimum MTU of interfaces linked with the given interface. """ - mtu = ifc.mtu - if not ifc.net: + mtu = iface.mtu + if not iface.net: return mtu - for i in ifc.net.netifs(): - if i.mtu < mtu: - mtu = i.mtu + for iface in iface.net.get_ifaces(): + if iface.mtu < mtu: + mtu = iface.mtu return mtu @classmethod - def mtucheck(cls, ifc): + def mtucheck(cls, iface): """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(ifc) - if minmtu < ifc.mtu: + minmtu = cls.minmtu(iface) + if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(ifc): + def ptpcheck(iface): """ Helper to detect whether interface is connected to a notional point-to-point link. """ - if isinstance(ifc.net, PtpNet): + if isinstance(iface.net, PtpNet): return " ipv6 ospf6 network point-to-point\n" return "" @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = "router ospf6\n" rtrid = cls.routerid(node) cfg += " instance-id 65\n" cfg += " router-id %s\n" % rtrid - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += " interface %s area 0.0.0.0\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += " interface %s area 0.0.0.0\n" % iface.name cfg += "!\n" return cfg @classmethod - def generatequaggaifcconfig(cls, node, ifc): - return cls.mtucheck(ifc) + def generate_quagga_iface_config(cls, node, iface): + return cls.mtucheck(iface) class Ospfv3mdr(Ospfv3): @@ -444,9 +438,9 @@ class Ospfv3mdr(Ospfv3): ipv4_routing = True @classmethod - def generatequaggaifcconfig(cls, node, ifc): - cfg = cls.mtucheck(ifc) - if ifc.net is not None and isinstance(ifc.net, (WlanNode, EmaneNet)): + def generate_quagga_iface_config(cls, node, iface): + cfg = cls.mtucheck(iface) + if iface.net is not None and isinstance(iface.net, (WlanNode, EmaneNet)): return ( cfg + """\ @@ -479,7 +473,7 @@ class Bgp(QuaggaService): ipv6_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" @@ -503,7 +497,7 @@ class Rip(QuaggaService): ipv4_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = """\ router rip redistribute static @@ -527,7 +521,7 @@ class Ripng(QuaggaService): ipv6_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = """\ router ripng redistribute static @@ -552,18 +546,16 @@ class Babel(QuaggaService): ipv6_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): cfg = "router babel\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += " network %s\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += " network %s\n" % iface.name cfg += " redistribute static\n redistribute connected\n" return cfg @classmethod - def generatequaggaifcconfig(cls, node, ifc): - if ifc.net and ifc.net.linktype == LinkTypes.WIRELESS: + def generate_quagga_iface_config(cls, node, iface): + if iface.net and iface.net.linktype == LinkTypes.WIRELESS: return " babel wireless\n no babel split-horizon\n" else: return " babel wired\n babel split-horizon\n" @@ -581,11 +573,11 @@ class Xpimd(QuaggaService): ipv4_routing = True @classmethod - def generatequaggaconfig(cls, node): + def generate_quagga_config(cls, node): ifname = "eth0" - for ifc in node.netifs(): - if ifc.name != "lo": - ifname = ifc.name + for iface in node.get_ifaces(): + if iface.name != "lo": + ifname = iface.name break cfg = "router mfea\n!\n" cfg += "router igmp\n!\n" @@ -597,5 +589,5 @@ class Xpimd(QuaggaService): return cfg @classmethod - def generatequaggaifcconfig(cls, node, ifc): + def generate_quagga_iface_config(cls, node, iface): return " ip mfea\n ip igmp\n ip pim\n" diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index ab46f551..71ab815f 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -49,10 +49,8 @@ class OvsService(SdnService): cfg += "\n## Now add all our interfaces as ports to the switch\n" portnum = 1 - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - ifnumstr = re.findall(r"\d+", ifc.name) + for iface in node.get_ifaces(control=False): + ifnumstr = re.findall(r"\d+", iface.name) ifnum = ifnumstr[0] # create virtual interfaces @@ -61,18 +59,18 @@ class OvsService(SdnService): # remove ip address of eths because quagga/zebra will assign same IPs to rtr interfaces # or assign them manually to rtr interfaces if zebra is not running - for ifcaddr in ifc.addrlist: - addr = ifcaddr.split("/")[0] + for addr in iface.addrlist: + addr = addr.split("/")[0] if netaddr.valid_ipv4(addr): - cfg += "ip addr del %s dev %s\n" % (ifcaddr, ifc.name) + cfg += "ip addr del %s dev %s\n" % (addr, iface.name) if has_zebra == 0: - cfg += "ip addr add %s dev rtr%s\n" % (ifcaddr, ifnum) + cfg += "ip addr add %s dev rtr%s\n" % (addr, ifnum) elif netaddr.valid_ipv6(addr): - cfg += "ip -6 addr del %s dev %s\n" % (ifcaddr, ifc.name) + cfg += "ip -6 addr del %s dev %s\n" % (addr, iface.name) if has_zebra == 0: - cfg += "ip -6 addr add %s dev rtr%s\n" % (ifcaddr, ifnum) + cfg += "ip -6 addr add %s dev rtr%s\n" % (addr, ifnum) else: - raise ValueError("invalid address: %s" % ifcaddr) + raise ValueError("invalid address: %s" % addr) # add interfaces to bridge # Make port numbers explicit so they're easier to follow in reading the script @@ -102,9 +100,7 @@ class OvsService(SdnService): cfg += "## if the above controller will be present then you probably want to delete them\n" # Setup default flows portnum = 1 - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue + for iface in node.get_ifaces(control=False): cfg += "## Take the data from the CORE interface and put it on the veth and vice versa\n" cfg += ( "ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n" diff --git a/daemon/core/services/security.py b/daemon/core/services/security.py index eb6545b2..91c942f1 100644 --- a/daemon/core/services/security.py +++ b/daemon/core/services/security.py @@ -131,18 +131,18 @@ class Nat(CoreService): custom_needed = False @classmethod - def generateifcnatrule(cls, ifc, line_prefix=""): + def generate_iface_nat_rule(cls, iface, line_prefix=""): """ Generate a NAT line for one interface. """ cfg = line_prefix + "iptables -t nat -A POSTROUTING -o " - cfg += ifc.name + " -j MASQUERADE\n" + cfg += iface.name + " -j MASQUERADE\n" - cfg += line_prefix + "iptables -A FORWARD -i " + ifc.name + cfg += line_prefix + "iptables -A FORWARD -i " + iface.name cfg += " -m state --state RELATED,ESTABLISHED -j ACCEPT\n" cfg += line_prefix + "iptables -A FORWARD -i " - cfg += ifc.name + " -j DROP\n" + cfg += iface.name + " -j DROP\n" return cfg @classmethod @@ -154,14 +154,12 @@ class Nat(CoreService): cfg += "# generated by security.py\n" cfg += "# NAT out the first interface by default\n" have_nat = False - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue + for iface in node.get_ifaces(control=False): if have_nat: - cfg += cls.generateifcnatrule(ifc, line_prefix="#") + cfg += cls.generate_iface_nat_rule(iface, line_prefix="#") else: have_nat = True - cfg += "# NAT out the " + ifc.name + " interface\n" - cfg += cls.generateifcnatrule(ifc) + cfg += "# NAT out the " + iface.name + " interface\n" + cfg += cls.generate_iface_nat_rule(iface) cfg += "\n" return cfg diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 8a6e828b..273318e1 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -55,8 +55,8 @@ class IPForwardService(UtilService): """ % { "sysctl": constants.SYSCTL_BIN } - for ifc in node.netifs(): - name = utils.sysctl_devname(ifc.name) + for iface in node.get_ifaces(): + name = utils.sysctl_devname(iface.name) cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % ( constants.SYSCTL_BIN, name, @@ -77,10 +77,10 @@ class DefaultRouteService(UtilService): @classmethod def generate_config(cls, node, filename): routes = [] - netifs = node.netifs(sort=True) - if netifs: - netif = netifs[0] - for x in netif.addrlist: + ifaces = node.get_ifaces() + if ifaces: + iface = ifaces[0] + for x in iface.addrlist: net = netaddr.IPNetwork(x).cidr if net.size > 1: router = net[1] @@ -104,14 +104,12 @@ class DefaultMulticastRouteService(UtilService): cfg += "# the first interface is chosen below; please change it " cfg += "as needed\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue + for iface in node.get_ifaces(control=False): if os.uname()[0] == "Linux": rtcmd = "ip route add 224.0.0.0/4 dev" else: raise Exception("unknown platform") - cfg += "%s %s\n" % (rtcmd, ifc.name) + cfg += "%s %s\n" % (rtcmd, iface.name) cfg += "\n" break return cfg @@ -129,10 +127,8 @@ class StaticRouteService(UtilService): cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n" cfg += "# NOTE: this service must be customized to be of any use\n" cfg += "# Below are samples that you can uncomment and edit.\n#\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\n".join(map(cls.routestr, ifc.addrlist)) + for iface in node.get_ifaces(control=False): + cfg += "\n".join(map(cls.routestr, iface.addrlist)) cfg += "\n" return cfg @@ -259,10 +255,8 @@ max-lease-time 7200; ddns-update-style none; """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\n".join(map(cls.subnetentry, ifc.addrlist)) + for iface in node.get_ifaces(control=False): + cfg += "\n".join(map(cls.subnetentry, iface.addrlist)) cfg += "\n" return cfg @@ -320,13 +314,11 @@ class DhcpClientService(UtilService): cfg += "side DNS\n# resolution based on the DHCP server response.\n" cfg += "#mkdir -p /var/run/resolvconf/interface\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % iface.name cfg += " /var/run/resolvconf/resolv.conf\n" - cfg += "/sbin/dhclient -nw -pf /var/run/dhclient-%s.pid" % ifc.name - cfg += " -lf /var/run/dhclient-%s.lease %s\n" % (ifc.name, ifc.name) + cfg += "/sbin/dhclient -nw -pf /var/run/dhclient-%s.pid" % iface.name + cfg += " -lf /var/run/dhclient-%s.lease %s\n" % (iface.name, iface.name) return cfg @@ -585,10 +577,8 @@ export LANG """ % node.name ) - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - body += "
  • %s - %s
  • \n" % (ifc.name, ifc.addrlist) + for iface in node.get_ifaces(control=False): + body += "
  • %s - %s
  • \n" % (iface.name, iface.addrlist) return "%s" % body @@ -619,14 +609,14 @@ DUMPOPTS="-s 12288 -C 10 -n" if [ "x$1" = "xstart" ]; then """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: + for iface in node.get_ifaces(): + if hasattr(iface, "control") and iface.control is True: cfg += "# " redir = "< /dev/null" cfg += "tcpdump ${DUMPOPTS} -w %s.%s.pcap -i %s %s &\n" % ( node.name, - ifc.name, - ifc.name, + iface.name, + iface.name, redir, ) cfg += """ @@ -654,10 +644,8 @@ class RadvdService(UtilService): using the network address of each interface. """ cfg = "# auto-generated by RADVD service (utility.py)\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - prefixes = list(map(cls.subnetentry, ifc.addrlist)) + for iface in node.get_ifaces(control=False): + prefixes = list(map(cls.subnetentry, iface.addrlist)) if len(prefixes) < 1: continue cfg += ( @@ -670,7 +658,7 @@ interface %s AdvDefaultPreference low; AdvHomeAgentFlag off; """ - % ifc.name + % iface.name ) for prefix in prefixes: if prefix == "": diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 2312e6d4..3dfef56a 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -35,11 +35,11 @@ class XorpRtrmgr(CoreService): invoked here. Filename currently ignored. """ cfg = "interfaces {\n" - for ifc in node.netifs(): - cfg += " interface %s {\n" % ifc.name - cfg += "\tvif %s {\n" % ifc.name - cfg += "".join(map(cls.addrstr, ifc.addrlist)) - cfg += cls.lladdrstr(ifc) + for iface in node.get_ifaces(): + cfg += " interface %s {\n" % iface.name + cfg += "\tvif %s {\n" % iface.name + cfg += "".join(map(cls.addrstr, iface.addrlist)) + cfg += cls.lladdrstr(iface) cfg += "\t}\n" cfg += " }\n" cfg += "}\n\n" @@ -65,11 +65,11 @@ class XorpRtrmgr(CoreService): return cfg @staticmethod - def lladdrstr(ifc): + def lladdrstr(iface): """ helper for adding link-local address entries (required by OSPFv3) """ - cfg = "\t address %s {\n" % ifc.hwaddr.tolinklocal() + cfg = "\t address %s {\n" % iface.hwaddr.tolinklocal() cfg += "\t\tprefix-length: 64\n" cfg += "\t }\n" return cfg @@ -104,15 +104,15 @@ class XorpService(CoreService): return cfg @staticmethod - def mfea(forwarding, ifcs): + def mfea(forwarding, ifaces): """ Helper to add a multicast forwarding engine entry to the config file. """ names = [] - for ifc in ifcs: - if hasattr(ifc, "control") and ifc.control is True: + for iface in ifaces: + if hasattr(iface, "control") and iface.control is True: continue - names.append(ifc.name) + names.append(iface.name) names.append("register_vif") cfg = "plumbing {\n" @@ -148,10 +148,8 @@ class XorpService(CoreService): """ Helper to return the first IPv4 address of a node as its router ID. """ - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + for a in iface.addrlist: a = a.split("/")[0] if netaddr.valid_ipv4(a): return a @@ -184,12 +182,10 @@ class XorpOspfv2(XorpService): cfg += " ospf4 {\n" cfg += "\trouter-id: %s\n" % rtrid cfg += "\tarea 0.0.0.0 {\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\t interface %s {\n" % ifc.name - cfg += "\t\tvif %s {\n" % ifc.name - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + cfg += "\t interface %s {\n" % iface.name + cfg += "\t\tvif %s {\n" % iface.name + for a in iface.addrlist: addr = a.split("/")[0] if not netaddr.valid_ipv4(addr): continue @@ -220,11 +216,9 @@ class XorpOspfv3(XorpService): cfg += " ospf6 0 { /* Instance ID 0 */\n" cfg += "\trouter-id: %s\n" % rtrid cfg += "\tarea 0.0.0.0 {\n" - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\t interface %s {\n" % ifc.name - cfg += "\t\tvif %s {\n" % ifc.name + for iface in node.get_ifaces(control=False): + cfg += "\t interface %s {\n" % iface.name + cfg += "\t\tvif %s {\n" % iface.name cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" @@ -277,12 +271,10 @@ class XorpRip(XorpService): cfg += "\nprotocols {\n" cfg += " rip {\n" cfg += '\texport: "export-connected"\n' - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name + for a in iface.addrlist: addr = a.split("/")[0] if not netaddr.valid_ipv4(addr): continue @@ -310,12 +302,10 @@ class XorpRipng(XorpService): cfg += "\nprotocols {\n" cfg += " ripng {\n" cfg += '\texport: "export-connected"\n' - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name - cfg += "\t\taddress %s {\n" % ifc.hwaddr.tolinklocal() + for iface in node.get_ifaces(control=False): + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name + cfg += "\t\taddress %s {\n" % iface.hwaddr.tolinklocal() cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" @@ -334,17 +324,15 @@ class XorpPimSm4(XorpService): @classmethod def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea4", node.netifs()) + cfg = cls.mfea("mfea4", node.get_ifaces()) cfg += "\nprotocols {\n" cfg += " igmp {\n" names = [] - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - names.append(ifc.name) - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name + for iface in node.get_ifaces(control=False): + names.append(iface.name) + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name cfg += "\t\tdisable: false\n" cfg += "\t }\n" cfg += "\t}\n" @@ -394,17 +382,15 @@ class XorpPimSm6(XorpService): @classmethod def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea6", node.netifs()) + cfg = cls.mfea("mfea6", node.get_ifaces()) cfg += "\nprotocols {\n" cfg += " mld {\n" names = [] - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - names.append(ifc.name) - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name + for iface in node.get_ifaces(control=False): + names.append(iface.name) + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name cfg += "\t\tdisable: false\n" cfg += "\t }\n" cfg += "\t}\n" @@ -459,12 +445,10 @@ class XorpOlsr(XorpService): cfg += "\nprotocols {\n" cfg += " olsr4 {\n" cfg += "\tmain-address: %s\n" % rtrid - for ifc in node.netifs(): - if hasattr(ifc, "control") and ifc.control is True: - continue - cfg += "\tinterface %s {\n" % ifc.name - cfg += "\t vif %s {\n" % ifc.name - for a in ifc.addrlist: + for iface in node.get_ifaces(control=False): + cfg += "\tinterface %s {\n" % iface.name + cfg += "\t vif %s {\n" % iface.name + for a in iface.addrlist: addr = a.split("/")[0] if not netaddr.valid_ipv4(addr): continue diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 759de680..fe596d7a 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -58,16 +58,16 @@ def add_attribute(element: etree.Element, name: str, value: Any) -> None: element.set(name, str(value)) -def create_interface_data(interface_element: etree.Element) -> InterfaceData: - interface_id = int(interface_element.get("id")) - name = interface_element.get("name") - mac = interface_element.get("mac") - ip4 = interface_element.get("ip4") - ip4_mask = get_int(interface_element, "ip4_mask") - ip6 = interface_element.get("ip6") - ip6_mask = get_int(interface_element, "ip6_mask") +def create_iface_data(iface_element: etree.Element) -> InterfaceData: + iface_id = int(iface_element.get("id")) + name = iface_element.get("name") + mac = iface_element.get("mac") + ip4 = iface_element.get("ip4") + ip4_mask = get_int(iface_element, "ip4_mask") + ip6 = iface_element.get("ip6") + ip6_mask = get_int(iface_element, "ip6_mask") return InterfaceData( - id=interface_id, + id=iface_id, name=name, mac=mac, ip4=ip4, @@ -482,7 +482,7 @@ class CoreXmlWriter: # add link data for link_data in links: # skip basic range links - if link_data.interface1_id is None and link_data.interface2_id is None: + if link_data.iface1_id is None and link_data.iface2_id is None: continue link_element = self.create_link_element(link_data) @@ -495,37 +495,37 @@ class CoreXmlWriter: device = DeviceElement(self.session, node) self.devices.append(device.element) - def create_interface_element( + def create_iface_element( self, element_name: str, node_id: int, - interface_id: int, + iface_id: int, mac: str, ip4: str, ip4_mask: int, ip6: str, ip6_mask: int, ) -> etree.Element: - interface = etree.Element(element_name) + iface = etree.Element(element_name) node = self.session.get_node(node_id, NodeBase) - interface_name = None + iface_name = None if isinstance(node, CoreNodeBase): - node_interface = node.netif(interface_id) - interface_name = node_interface.name + node_iface = node.get_iface(iface_id) + iface_name = node_iface.name # check if emane interface - if isinstance(node_interface.net, EmaneNet): - nem = node_interface.net.getnemid(node_interface) - add_attribute(interface, "nem", nem) + if isinstance(node_iface.net, EmaneNet): + nem = node_iface.net.getnemid(node_iface) + add_attribute(iface, "nem", nem) - add_attribute(interface, "id", interface_id) - add_attribute(interface, "name", interface_name) - add_attribute(interface, "mac", mac) - add_attribute(interface, "ip4", ip4) - add_attribute(interface, "ip4_mask", ip4_mask) - add_attribute(interface, "ip6", ip6) - add_attribute(interface, "ip6_mask", ip6_mask) - return interface + add_attribute(iface, "id", iface_id) + add_attribute(iface, "name", iface_name) + add_attribute(iface, "mac", mac) + add_attribute(iface, "ip4", ip4) + add_attribute(iface, "ip4_mask", ip4_mask) + add_attribute(iface, "ip6", ip6) + add_attribute(iface, "ip6_mask", ip6_mask) + return iface def create_link_element(self, link_data: LinkData) -> etree.Element: link_element = etree.Element("link") @@ -533,32 +533,32 @@ class CoreXmlWriter: add_attribute(link_element, "node2", link_data.node2_id) # check for interface one - if link_data.interface1_id is not None: - interface1 = self.create_interface_element( + if link_data.iface1_id is not None: + iface1 = self.create_iface_element( "interface1", link_data.node1_id, - link_data.interface1_id, - link_data.interface1_mac, - link_data.interface1_ip4, - link_data.interface1_ip4_mask, - link_data.interface1_ip6, - link_data.interface1_ip6_mask, + link_data.iface1_id, + link_data.iface1_mac, + link_data.iface1_ip4, + link_data.iface1_ip4_mask, + link_data.iface1_ip6, + link_data.iface1_ip6_mask, ) - link_element.append(interface1) + link_element.append(iface1) # check for interface two - if link_data.interface2_id is not None: - interface2 = self.create_interface_element( + if link_data.iface2_id is not None: + iface2 = self.create_iface_element( "interface2", link_data.node2_id, - link_data.interface2_id, - link_data.interface2_mac, - link_data.interface2_ip4, - link_data.interface2_ip4_mask, - link_data.interface2_ip6, - link_data.interface2_ip6_mask, + link_data.iface2_id, + link_data.iface2_mac, + link_data.iface2_ip4, + link_data.iface2_ip4_mask, + link_data.iface2_ip6, + link_data.iface2_ip6_mask, ) - link_element.append(interface2) + link_element.append(iface2) # check for options, don't write for emane/wlan links node1 = self.session.get_node(link_data.node1_id, NodeBase) @@ -940,19 +940,19 @@ class CoreXmlReader: node2_id = get_int(link_element, "node_two") node_set = frozenset((node1_id, node2_id)) - interface1_element = link_element.find("interface1") - if interface1_element is None: - interface1_element = link_element.find("interface_one") - interface1_data = None - if interface1_element is not None: - interface1_data = create_interface_data(interface1_element) + iface1_element = link_element.find("interface1") + if iface1_element is None: + iface1_element = link_element.find("interface_one") + iface1_data = None + if iface1_element is not None: + iface1_data = create_iface_data(iface1_element) - interface2_element = link_element.find("interface2") - if interface2_element is None: - interface2_element = link_element.find("interface_two") - interface2_data = None - if interface2_element is not None: - interface2_data = create_interface_data(interface2_element) + iface2_element = link_element.find("interface2") + if iface2_element is None: + iface2_element = link_element.find("interface_two") + iface2_data = None + if iface2_element is not None: + iface2_data = create_iface_data(iface2_element) options_element = link_element.find("options") options = LinkOptions() @@ -978,12 +978,12 @@ class CoreXmlReader: if options.unidirectional == 1 and node_set in node_sets: logging.info("updating link node1(%s) node2(%s)", node1_id, node2_id) self.session.update_link( - node1_id, node2_id, interface1_data.id, interface2_data.id, options + node1_id, node2_id, iface1_data.id, iface2_data.id, options ) else: logging.info("adding link node1(%s) node2(%s)", node1_id, node2_id) self.session.add_link( - node1_id, node2_id, interface1_data, interface2_data, options + node1_id, node2_id, iface1_data, iface2_data, options ) node_sets.add(node_set) diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 04915bf1..7954b71a 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -24,25 +24,25 @@ def add_address( parent_element: etree.Element, address_type: str, address: str, - interface_name: str = None, + iface_name: str = None, ) -> None: address_element = etree.SubElement(parent_element, "address", type=address_type) address_element.text = address - if interface_name is not None: - address_element.set("iface", interface_name) + if iface_name is not None: + address_element.set("iface", iface_name) def add_mapping(parent_element: etree.Element, maptype: str, mapref: str) -> None: etree.SubElement(parent_element, "mapping", type=maptype, ref=mapref) -def add_emane_interface( +def add_emane_iface( host_element: etree.Element, - netif: CoreInterface, + iface: CoreInterface, platform_name: str = "p1", transport_name: str = "t1", ) -> etree.Element: - nem_id = netif.net.nemidmap[netif] + nem_id = iface.net.nemidmap[iface] host_id = host_element.get("id") # platform data @@ -89,10 +89,10 @@ def get_ipv4_addresses(hostname: str) -> List[Tuple[str, str]]: split = line.split() if not split: continue - interface_name = split[1] + iface_name = split[1] address = split[3] if not address.startswith("127."): - addresses.append((interface_name, address)) + addresses.append((iface_name, address)) return addresses else: # TODO: handle other hosts @@ -112,11 +112,11 @@ class CoreXmlDeployment: device = self.scenario.find(f"devices/device[@name='{name}']") return device - def find_interface(self, device: NodeBase, name: str) -> etree.Element: - interface = self.scenario.find( + def find_iface(self, device: NodeBase, name: str) -> etree.Element: + iface = self.scenario.find( f"devices/device[@name='{device.name}']/interfaces/interface[@name='{name}']" ) - return interface + return iface def add_deployment(self) -> None: physical_host = self.add_physical_host(socket.gethostname()) @@ -136,8 +136,8 @@ class CoreXmlDeployment: add_type(host_element, "physical") # add ipv4 addresses - for interface_name, address in get_ipv4_addresses("localhost"): - add_address(host_element, "IPv4", address, interface_name) + for iface_name, address in get_ipv4_addresses("localhost"): + add_address(host_element, "IPv4", address, iface_name) return host_element @@ -155,15 +155,15 @@ class CoreXmlDeployment: # add host type add_type(host_element, "virtual") - for netif in node.netifs(): + for iface in node.get_ifaces(): emane_element = None - if isinstance(netif.net, EmaneNet): - emane_element = add_emane_interface(host_element, netif) + if isinstance(iface.net, EmaneNet): + emane_element = add_emane_iface(host_element, iface) parent_element = host_element if emane_element is not None: parent_element = emane_element - for address in netif.addrlist: + for address in iface.addrlist: address_type = get_address_type(address) - add_address(parent_element, address_type, address, netif.name) + add_address(parent_element, address_type, address, iface.name) diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 2589edd9..4f511476 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -158,19 +158,19 @@ def build_node_platform_xml( logging.warning("warning: EMANE network %s has no associated model", node.name) return nem_id - for netif in node.netifs(): + for iface in node.get_ifaces(): logging.debug( - "building platform xml for interface(%s) nem_id(%s)", netif.name, nem_id + "building platform xml for interface(%s) nem_id(%s)", iface.name, nem_id ) # build nem xml - nem_definition = nem_file_name(node.model, netif) + nem_definition = nem_file_name(node.model, iface) nem_element = etree.Element( - "nem", id=str(nem_id), name=netif.localname, definition=nem_definition + "nem", id=str(nem_id), name=iface.localname, definition=nem_definition ) # check if this is an external transport, get default config if an interface # specific one does not exist - config = emane_manager.getifcconfig(node.model.id, netif, node.model.name) + config = emane_manager.get_iface_config(node.model.id, iface, node.model.name) if is_external(config): nem_element.set("transport", "external") @@ -180,9 +180,9 @@ def build_node_platform_xml( add_param(nem_element, transport_endpoint, config[transport_endpoint]) else: # build transport xml - transport_type = netif.transport_type + transport_type = iface.transport_type if not transport_type: - logging.info("warning: %s interface type unsupported!", netif.name) + logging.info("warning: %s interface type unsupported!", iface.name) transport_type = TransportType.RAW transport_file = transport_file_name(node.id, transport_type) transport_element = etree.SubElement( @@ -190,14 +190,14 @@ def build_node_platform_xml( ) # add transport parameter - add_param(transport_element, "device", netif.name) + add_param(transport_element, "device", iface.name) # add nem entry - nem_entries[netif] = nem_element + nem_entries[iface] = nem_element # merging code - key = netif.node.id - if netif.transport_type == TransportType.RAW: + key = iface.node.id + if iface.transport_type == TransportType.RAW: key = "host" otadev = control_net.brname eventdev = control_net.brname @@ -229,10 +229,10 @@ def build_node_platform_xml( platform_element.append(nem_element) - node.setnemid(netif, nem_id) + node.setnemid(iface, nem_id) macstr = _hwaddr_prefix + ":00:00:" macstr += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" - netif.sethwaddr(macstr) + iface.sethwaddr(macstr) # increment nem id nem_id += 1 @@ -280,19 +280,19 @@ def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None: vtype = TransportType.VIRTUAL rtype = TransportType.RAW - for netif in node.netifs(): + for iface in node.get_ifaces(): # check for interface specific emane configuration and write xml files - config = emane_manager.getifcconfig(node.model.id, netif, node.model.name) + config = emane_manager.get_iface_config(node.model.id, iface, node.model.name) if config: - node.model.build_xml_files(config, netif) + node.model.build_xml_files(config, iface) # check transport type needed for interface - if netif.transport_type == TransportType.VIRTUAL: + if iface.transport_type == TransportType.VIRTUAL: need_virtual = True - vtype = netif.transport_type + vtype = iface.transport_type else: need_raw = True - rtype = netif.transport_type + rtype = iface.transport_type if need_virtual: build_transport_xml(emane_manager, node, vtype) @@ -494,70 +494,70 @@ def transport_file_name(node_id: int, transport_type: TransportType) -> str: return f"n{node_id}trans{transport_type.value}.xml" -def _basename(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def _basename(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Create name that is leveraged for configuration file creation. :param emane_model: emane model to create name for - :param interface: interface for this model + :param iface: interface for this model :return: basename used for file creation """ name = f"n{emane_model.id}" - if interface: - node_id = interface.node.id - if emane_model.session.emane.getifcconfig(node_id, interface, emane_model.name): - name = interface.localname.replace(".", "_") + if iface: + node_id = iface.node.id + if emane_model.session.emane.get_iface_config(node_id, iface, emane_model.name): + name = iface.localname.replace(".", "_") return f"{name}{emane_model.name}" -def nem_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def nem_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Return the string name for the NEM XML file, e.g. "n3rfpipenem.xml" :param emane_model: emane model to create file - :param interface: interface for this model + :param iface: interface for this model :return: nem xml filename """ - basename = _basename(emane_model, interface) + basename = _basename(emane_model, iface) append = "" - if interface and interface.transport_type == TransportType.RAW: + if iface and iface.transport_type == TransportType.RAW: append = "_raw" return f"{basename}nem{append}.xml" -def shim_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def shim_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Return the string name for the SHIM XML file, e.g. "commeffectshim.xml" :param emane_model: emane model to create file - :param interface: interface for this model + :param iface: interface for this model :return: shim xml filename """ - name = _basename(emane_model, interface) + name = _basename(emane_model, iface) return f"{name}shim.xml" -def mac_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def mac_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Return the string name for the MAC XML file, e.g. "n3rfpipemac.xml" :param emane_model: emane model to create file - :param interface: interface for this model + :param iface: interface for this model :return: mac xml filename """ - name = _basename(emane_model, interface) + name = _basename(emane_model, iface) return f"{name}mac.xml" -def phy_file_name(emane_model: "EmaneModel", interface: CoreInterface = None) -> str: +def phy_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: """ Return the string name for the PHY XML file, e.g. "n3rfpipephy.xml" :param emane_model: emane model to create file - :param interface: interface for this model + :param iface: interface for this model :return: phy xml filename """ - name = _basename(emane_model, interface) + name = _basename(emane_model, iface) return f"{name}phy.xml" diff --git a/daemon/examples/configservices/testing.py b/daemon/examples/configservices/testing.py index 948ec739..767d0f45 100644 --- a/daemon/examples/configservices/testing.py +++ b/daemon/examples/configservices/testing.py @@ -20,13 +20,13 @@ if __name__ == "__main__": # node one options.config_services = ["DefaultRoute", "IPForward"] node1 = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node1) - session.add_link(node1.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node1) + session.add_link(node1.id, switch.id, iface1_data=interface) # node two node2 = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node2) - session.add_link(node2.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node2) + session.add_link(node2.id, switch.id, iface1_data=interface) # start session and run services session.instantiate() diff --git a/daemon/examples/docker/docker2core.py b/daemon/examples/docker/docker2core.py index 8151a590..c38f96af 100644 --- a/daemon/examples/docker/docker2core.py +++ b/daemon/examples/docker/docker2core.py @@ -18,11 +18,11 @@ if __name__ == "__main__": # create node one node1 = session.add_node(DockerNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # create node two node2 = session.add_node(CoreNode) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # add link session.add_link(node1.id, node2.id, interface1_data, interface2_data) diff --git a/daemon/examples/docker/docker2docker.py b/daemon/examples/docker/docker2docker.py index a7a70534..5b62d433 100644 --- a/daemon/examples/docker/docker2docker.py +++ b/daemon/examples/docker/docker2docker.py @@ -19,11 +19,11 @@ if __name__ == "__main__": # create node one node1 = session.add_node(DockerNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # create node two node2 = session.add_node(DockerNode, options=options) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # add link session.add_link(node1.id, node2.id, interface1_data, interface2_data) diff --git a/daemon/examples/docker/switch.py b/daemon/examples/docker/switch.py index ef057945..161cd823 100644 --- a/daemon/examples/docker/switch.py +++ b/daemon/examples/docker/switch.py @@ -23,15 +23,15 @@ if __name__ == "__main__": # node one node1 = session.add_node(DockerNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # node two node2 = session.add_node(DockerNode, options=options) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # node three node_three = session.add_node(CoreNode) - interface_three = prefixes.create_interface(node_three) + interface_three = prefixes.create_iface(node_three) # add links session.add_link(node1.id, switch.id, interface1_data) diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index e847016f..0d781c19 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -47,7 +47,7 @@ def main(args): node1_id = response.node_id # create link - interface1 = interface_helper.create_interface(node1_id, 0) + interface1 = interface_helper.create_iface(node1_id, 0) response = core.add_link(session_id, node1_id, switch_id, interface1) logging.info("created link from node one to switch: %s", response) @@ -59,7 +59,7 @@ def main(args): node2_id = response.node_id # create link - interface1 = interface_helper.create_interface(node2_id, 0) + interface1 = interface_helper.create_iface(node2_id, 0) response = core.add_link(session_id, node2_id, switch_id, interface1) logging.info("created link from node two to switch: %s", response) diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py index 24532266..b8036db0 100644 --- a/daemon/examples/grpc/emane80211.py +++ b/daemon/examples/grpc/emane80211.py @@ -57,10 +57,10 @@ def main(): node2_id = response.node_id # links nodes to switch - interface1 = interface_helper.create_interface(node1_id, 0) + interface1 = interface_helper.create_iface(node1_id, 0) response = core.add_link(session_id, node1_id, emane_id, interface1) logging.info("created link: %s", response) - interface1 = interface_helper.create_interface(node2_id, 0) + interface1 = interface_helper.create_iface(node2_id, 0) response = core.add_link(session_id, node2_id, emane_id, interface1) logging.info("created link: %s", response) diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 74e315c6..1ed7c684 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -53,10 +53,10 @@ def main(): node2_id = response.node_id # links nodes to switch - interface1 = interface_helper.create_interface(node1_id, 0) + interface1 = interface_helper.create_iface(node1_id, 0) response = core.add_link(session_id, node1_id, switch_id, interface1) logging.info("created link: %s", response) - interface1 = interface_helper.create_interface(node2_id, 0) + interface1 = interface_helper.create_iface(node2_id, 0) response = core.add_link(session_id, node2_id, switch_id, interface1) logging.info("created link: %s", response) diff --git a/daemon/examples/grpc/wlan.py b/daemon/examples/grpc/wlan.py index d60ca1be..715d4706 100644 --- a/daemon/examples/grpc/wlan.py +++ b/daemon/examples/grpc/wlan.py @@ -65,10 +65,10 @@ def main(): node2_id = response.node_id # links nodes to switch - interface1 = interface_helper.create_interface(node1_id, 0) + interface1 = interface_helper.create_iface(node1_id, 0) response = core.add_link(session_id, node1_id, wlan_id, interface1) logging.info("created link: %s", response) - interface1 = interface_helper.create_interface(node2_id, 0) + interface1 = interface_helper.create_iface(node2_id, 0) response = core.add_link(session_id, node2_id, wlan_id, interface1) logging.info("created link: %s", response) diff --git a/daemon/examples/lxd/lxd2core.py b/daemon/examples/lxd/lxd2core.py index 49b68943..3d8eef6a 100644 --- a/daemon/examples/lxd/lxd2core.py +++ b/daemon/examples/lxd/lxd2core.py @@ -18,11 +18,11 @@ if __name__ == "__main__": # create node one node1 = session.add_node(LxcNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # create node two node2 = session.add_node(CoreNode) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # add link session.add_link(node1.id, node2.id, interface1_data, interface2_data) diff --git a/daemon/examples/lxd/lxd2lxd.py b/daemon/examples/lxd/lxd2lxd.py index 18af8037..a7209b5c 100644 --- a/daemon/examples/lxd/lxd2lxd.py +++ b/daemon/examples/lxd/lxd2lxd.py @@ -19,11 +19,11 @@ if __name__ == "__main__": # create node one node1 = session.add_node(LxcNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # create node two node2 = session.add_node(LxcNode, options=options) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # add link session.add_link(node1.id, node2.id, interface1_data, interface2_data) diff --git a/daemon/examples/lxd/switch.py b/daemon/examples/lxd/switch.py index 31a79887..9b6801f5 100644 --- a/daemon/examples/lxd/switch.py +++ b/daemon/examples/lxd/switch.py @@ -23,15 +23,15 @@ if __name__ == "__main__": # node one node1 = session.add_node(LxcNode, options=options) - interface1_data = prefixes.create_interface(node1) + interface1_data = prefixes.create_iface(node1) # node two node2 = session.add_node(LxcNode, options=options) - interface2_data = prefixes.create_interface(node2) + interface2_data = prefixes.create_iface(node2) # node three node3 = session.add_node(CoreNode) - interface3_data = prefixes.create_interface(node3) + interface3_data = prefixes.create_iface(node3) # add links session.add_link(node1.id, switch.id, interface1_data) diff --git a/daemon/examples/myservices/sample.py b/daemon/examples/myservices/sample.py index 8c6dbe06..e0c9a232 100644 --- a/daemon/examples/myservices/sample.py +++ b/daemon/examples/myservices/sample.py @@ -80,8 +80,8 @@ class MyService(CoreService): if filename == cls.configs[0]: cfg += "# auto-generated by MyService (sample.py)\n" - for ifc in node.netifs(): - cfg += f'echo "Node {node.name} has interface {ifc.name}"\n' + for iface in node.get_ifaces(): + cfg += f'echo "Node {node.name} has interface {iface.name}"\n' elif filename == cls.configs[1]: cfg += "echo hello" diff --git a/daemon/examples/python/distributed_emane.py b/daemon/examples/python/distributed_emane.py index d9b41ea4..3ee56108 100644 --- a/daemon/examples/python/distributed_emane.py +++ b/daemon/examples/python/distributed_emane.py @@ -59,10 +59,10 @@ def main(args): node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface1_data = prefixes.create_interface(node1) - interface2_data = prefixes.create_interface(node2) - session.add_link(node1.id, emane_net.id, interface1_data=interface1_data) - session.add_link(node2.id, emane_net.id, interface1_data=interface2_data) + interface1_data = prefixes.create_iface(node1) + interface2_data = prefixes.create_iface(node2) + session.add_link(node1.id, emane_net.id, iface1_data=interface1_data) + session.add_link(node2.id, emane_net.id, iface1_data=interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/distributed_lxd.py b/daemon/examples/python/distributed_lxd.py index affb16a8..1573836a 100644 --- a/daemon/examples/python/distributed_lxd.py +++ b/daemon/examples/python/distributed_lxd.py @@ -48,8 +48,8 @@ def main(args): node2 = session.add_node(LxcNode, options=options) # create node interfaces and link - interface1_data = prefixes.create_interface(node1) - interface2_data = prefixes.create_interface(node2) + interface1_data = prefixes.create_iface(node1) + interface2_data = prefixes.create_iface(node2) session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session diff --git a/daemon/examples/python/distributed_ptp.py b/daemon/examples/python/distributed_ptp.py index 6bf33474..1486c237 100644 --- a/daemon/examples/python/distributed_ptp.py +++ b/daemon/examples/python/distributed_ptp.py @@ -48,8 +48,8 @@ def main(args): node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface1_data = prefixes.create_interface(node1) - interface2_data = prefixes.create_interface(node2) + interface1_data = prefixes.create_iface(node1) + interface2_data = prefixes.create_iface(node2) session.add_link(node1.id, node2.id, interface1_data, interface2_data) # instantiate session diff --git a/daemon/examples/python/distributed_switch.py b/daemon/examples/python/distributed_switch.py index 8991161e..e9eb1e81 100644 --- a/daemon/examples/python/distributed_switch.py +++ b/daemon/examples/python/distributed_switch.py @@ -52,10 +52,10 @@ def main(args): node2 = session.add_node(CoreNode, options=options) # create node interfaces and link - interface1_data = prefixes.create_interface(node1) - interface2_data = prefixes.create_interface(node2) - session.add_link(node1.id, switch.id, interface1_data=interface1_data) - session.add_link(node2.id, switch.id, interface1_data=interface2_data) + interface1_data = prefixes.create_iface(node1) + interface2_data = prefixes.create_iface(node2) + session.add_link(node1.id, switch.id, iface1_data=interface1_data) + session.add_link(node2.id, switch.id, iface1_data=interface2_data) # instantiate session session.instantiate() diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index d3f6652a..322e569f 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -42,8 +42,8 @@ def main(): for i in range(NODES): node = session.add_node(CoreNode, options=options) node.setposition(x=150 * (i + 1), y=150) - interface = prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, emane_network.id, iface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/switch.py b/daemon/examples/python/switch.py index 1b939cd7..902e79e0 100644 --- a/daemon/examples/python/switch.py +++ b/daemon/examples/python/switch.py @@ -31,8 +31,8 @@ def main(): # create nodes for _ in range(NODES): node = session.add_node(CoreNode) - interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/switch_inject.py b/daemon/examples/python/switch_inject.py index 59816b19..89f70e05 100644 --- a/daemon/examples/python/switch_inject.py +++ b/daemon/examples/python/switch_inject.py @@ -33,8 +33,8 @@ def main(): # create nodes for _ in range(NODES): node = session.add_node(CoreNode) - interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/python/wlan.py b/daemon/examples/python/wlan.py index 0302bbd3..547a5860 100644 --- a/daemon/examples/python/wlan.py +++ b/daemon/examples/python/wlan.py @@ -35,8 +35,8 @@ def main(): options.set_position(0, 0) for _ in range(NODES): node = session.add_node(CoreNode, options=options) - interface = prefixes.create_interface(node) - session.add_link(node.id, wlan.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, wlan.id, iface1_data=interface) # instantiate session session.instantiate() diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 828b41fb..f691621a 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -319,12 +319,12 @@ message ThroughputsRequest { message ThroughputsEvent { int32 session_id = 1; repeated BridgeThroughput bridge_throughputs = 2; - repeated InterfaceThroughput interface_throughputs = 3; + repeated InterfaceThroughput iface_throughputs = 3; } message InterfaceThroughput { int32 node_id = 1; - int32 interface_id = 2; + int32 iface_id = 2; double throughput = 3; } @@ -374,7 +374,7 @@ message ConfigEvent { string bitmap = 8; string possible_values = 9; string groups = 10; - int32 interface = 11; + int32 iface_id = 11; int32 network_id = 12; string opaque = 13; } @@ -416,7 +416,7 @@ message GetNodeRequest { message GetNodeResponse { Node node = 1; - repeated Interface interfaces = 2; + repeated Interface ifaces = 2; } message EditNodeRequest { @@ -492,16 +492,16 @@ message AddLinkRequest { message AddLinkResponse { bool result = 1; - Interface interface1 = 2; - Interface interface2 = 3; + Interface iface1 = 2; + Interface iface2 = 3; } message EditLinkRequest { int32 session_id = 1; int32 node1_id = 2; int32 node2_id = 3; - int32 interface1_id = 4; - int32 interface2_id = 5; + int32 iface1_id = 4; + int32 iface2_id = 5; LinkOptions options = 6; } @@ -513,8 +513,8 @@ message DeleteLinkRequest { int32 session_id = 1; int32 node1_id = 2; int32 node2_id = 3; - int32 interface1_id = 4; - int32 interface2_id = 5; + int32 iface1_id = 4; + int32 iface2_id = 5; } message DeleteLinkResponse { @@ -561,7 +561,7 @@ message GetInterfacesRequest { } message GetInterfacesResponse { - repeated string interfaces = 1; + repeated string ifaces = 1; } message ExecuteScriptRequest { @@ -705,8 +705,8 @@ message Link { int32 node1_id = 1; int32 node2_id = 2; LinkType.Enum type = 3; - Interface interface1 = 4; - Interface interface2 = 5; + Interface iface1 = 4; + Interface iface2 = 5; LinkOptions options = 6; int32 network_id = 7; string label = 8; diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index e4189700..ac5456fd 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -32,7 +32,7 @@ message GetEmaneModelsResponse { message GetEmaneModelConfigRequest { int32 session_id = 1; int32 node_id = 2; - int32 interface = 3; + int32 iface_id = 3; string model = 4; } @@ -57,7 +57,7 @@ message GetEmaneModelConfigsResponse { message ModelConfig { int32 node_id = 1; string model = 2; - int32 interface = 3; + int32 iface_id = 3; map config = 4; } repeated ModelConfig configs = 1; @@ -86,7 +86,7 @@ message EmaneLinkResponse { message EmaneModelConfig { int32 node_id = 1; - int32 interface_id = 2; + int32 iface_id = 2; string model = 3; map config = 4; } @@ -95,10 +95,10 @@ message EmanePathlossesRequest { int32 session_id = 1; int32 node1_id = 2; float rx1 = 3; - int32 interface1_id = 4; + int32 iface1_id = 4; int32 node2_id = 5; float rx2 = 6; - int32 interface2_id = 7; + int32 iface2_id = 7; } message EmanePathlossesResponse { diff --git a/daemon/scripts/core-route-monitor b/daemon/scripts/core-route-monitor index b12e6205..d644ae1b 100755 --- a/daemon/scripts/core-route-monitor +++ b/daemon/scripts/core-route-monitor @@ -101,8 +101,8 @@ class RouterMonitor: node_map[node.id] = node.channel if self.src_id is None: response = self.core.get_node(self.session, node.id) - for netif in response.interfaces: - if self.src == netif.ip4: + for iface in response.ifaces: + if self.src == iface.ip4: self.src_id = node.id break except grpc.RpcError: diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 9d54d9c2..c3315e7c 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -89,7 +89,7 @@ def ip_prefixes(): @pytest.fixture(scope="session") -def interface_helper(): +def iface_helper(): return InterfaceHelper(ip4_prefix="10.83.0.0/16") diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index 15e3d869..e1c7938b 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -79,8 +79,8 @@ class TestEmane: for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, emane_network.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -119,8 +119,8 @@ class TestEmane: for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, emane_network.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, emane_network.id, iface1_data=iface_data) # instantiate session session.instantiate() diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 626f84a7..5771f7ad 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -53,8 +53,8 @@ class TestCore: # link nodes to net node for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, net_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, net_node.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -80,8 +80,8 @@ class TestCore: # link nodes to ptp net for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, ptp_node.id, iface1_data=iface_data) # get node client for testing client = node1.client @@ -96,9 +96,9 @@ class TestCore: if not request.config.getoption("mock"): assert client.check_cmd("echo hello") == "hello" - def test_netif(self, session: Session, ip_prefixes: IpPrefixes): + def test_iface(self, session: Session, ip_prefixes: IpPrefixes): """ - Test netif methods. + Test interface methods. :param session: session for test :param ip_prefixes: generates ip addresses for nodes @@ -113,8 +113,8 @@ class TestCore: # link nodes to ptp net for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface1_data=interface) + iface = ip_prefixes.create_iface(node) + session.add_link(node.id, ptp_node.id, iface1_data=iface) # instantiate session session.instantiate() @@ -126,19 +126,19 @@ class TestCore: assert node1.commonnets(node2) assert node2.commonnets(node1) - # check we can retrieve netif index - assert node1.ifname(0) - assert node2.ifname(0) + # check we can retrieve interface id + assert 0 in node1.ifaces + assert 0 in node2.ifaces # check interface parameters - interface = node1.netif(0) - interface.setparam("test", 1) - assert interface.getparam("test") == 1 - assert interface.getparams() + iface = node1.get_iface(0) + iface.setparam("test", 1) + assert iface.getparam("test") == 1 + assert iface.getparams() - # delete netif and test that if no longer exists - node1.delnetif(0) - assert not node1.netif(0) + # delete interface and test that if no longer exists + node1.delete_iface(0) + assert 0 not in node1.ifaces def test_wlan_ping(self, session: Session, ip_prefixes: IpPrefixes): """ @@ -160,8 +160,8 @@ class TestCore: # link nodes for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface1_data=interface) + iface_id = ip_prefixes.create_iface(node) + session.add_link(node.id, wlan_node.id, iface1_data=iface_id) # instantiate session session.instantiate() @@ -190,8 +190,8 @@ class TestCore: # link nodes for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface1_data=interface) + iface_id = ip_prefixes.create_iface(node) + session.add_link(node.id, wlan_node.id, iface1_data=iface_id) # configure mobility script for session config = { diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 8beb4b9a..23ff0301 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -42,15 +42,17 @@ class TestGrpc: id=3, type=NodeTypes.WIRELESS_LAN.value, position=position ) nodes = [node1, node2, wlan_node] - interface_helper = InterfaceHelper(ip4_prefix="10.83.0.0/16") - interface1 = interface_helper.create_interface(node1.id, 0) - interface2 = interface_helper.create_interface(node2.id, 0) + iface_helper = InterfaceHelper(ip4_prefix="10.83.0.0/16") + iface1_id = 0 + iface1 = iface_helper.create_iface(node1.id, iface1_id) + iface2_id = 0 + iface2 = iface_helper.create_iface(node2.id, iface2_id) link = core_pb2.Link( type=core_pb2.LinkType.WIRED, node1_id=node1.id, node2_id=node2.id, - interface1=interface1, - interface2=interface2, + iface1=iface1, + iface2=iface2, ) links = [link] hook = core_pb2.Hook( @@ -81,7 +83,7 @@ class TestGrpc: model_config_value = "500000" model_config = EmaneModelConfig( node_id=model_node_id, - interface_id=-1, + iface_id=-1, model=EmaneIeee80211abgModel.name, config={model_config_key: model_config_value}, ) @@ -131,8 +133,8 @@ class TestGrpc: assert node1.id in session.nodes assert node2.id in session.nodes assert wlan_node.id in session.nodes - assert session.nodes[node1.id].netif(0) is not None - assert session.nodes[node2.id].netif(0) is not None + assert iface1_id in session.nodes[node1.id].ifaces + assert iface2_id in session.nodes[node2.id].ifaces hook_file, hook_data = session.hooks[EventTypes.RUNTIME_STATE][0] assert hook_file == hook.file assert hook_data == hook.data @@ -522,8 +524,8 @@ class TestGrpc: session = grpc_server.coreemu.create_session() switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface_data) # then with client.context_connect(): @@ -540,17 +542,15 @@ class TestGrpc: session = grpc_server.coreemu.create_session() switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface_data) # then with pytest.raises(grpc.RpcError): with client.context_connect(): client.get_node_links(session.id, 3) - def test_add_link( - self, grpc_server: CoreGrpcServer, interface_helper: InterfaceHelper - ): + def test_add_link(self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelper): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() @@ -559,16 +559,16 @@ class TestGrpc: assert len(switch.all_link_data()) == 0 # then - interface = interface_helper.create_interface(node.id, 0) + iface = iface_helper.create_iface(node.id, 0) with client.context_connect(): - response = client.add_link(session.id, node.id, switch.id, interface) + response = client.add_link(session.id, node.id, switch.id, iface) # then assert response.result is True assert len(switch.all_link_data()) == 1 def test_add_link_exception( - self, grpc_server: CoreGrpcServer, interface_helper: InterfaceHelper + self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelper ): # given client = CoreGrpcClient() @@ -576,10 +576,10 @@ class TestGrpc: node = session.add_node(CoreNode) # then - interface = interface_helper.create_interface(node.id, 0) + iface = iface_helper.create_iface(node.id, 0) with pytest.raises(grpc.RpcError): with client.context_connect(): - client.add_link(session.id, 1, 3, interface) + client.add_link(session.id, 1, 3, iface) def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # given @@ -587,8 +587,8 @@ class TestGrpc: session = grpc_server.coreemu.create_session() switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface) + iface = ip_prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface) options = core_pb2.LinkOptions(bandwidth=30000) link = switch.all_link_data()[0] assert options.bandwidth != link.bandwidth @@ -596,7 +596,7 @@ class TestGrpc: # then with client.context_connect(): response = client.edit_link( - session.id, node.id, switch.id, options, interface1_id=interface.id + session.id, node.id, switch.id, options, iface1_id=iface.id ) # then @@ -609,10 +609,10 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node1 = session.add_node(CoreNode) - interface1 = ip_prefixes.create_interface(node1) + iface1 = ip_prefixes.create_iface(node1) node2 = session.add_node(CoreNode) - interface2 = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface1, interface2) + iface2 = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1, iface2) link_node = None for node_id in session.nodes: node = session.nodes[node_id] @@ -624,7 +624,7 @@ class TestGrpc: # then with client.context_connect(): response = client.delete_link( - session.id, node1.id, node2.id, interface1.id, interface2.id + session.id, node1.id, node2.id, iface1.id, iface2.id ) # then @@ -729,7 +729,7 @@ class TestGrpc: assert emane_network.id == model_config.node_id assert model_config.model == EmaneIeee80211abgModel.name assert len(model_config.config) > 0 - assert model_config.interface == -1 + assert model_config.iface_id == -1 def test_set_emane_model_config(self, grpc_server: CoreGrpcServer): # given @@ -1028,8 +1028,8 @@ class TestGrpc: session = grpc_server.coreemu.create_session() wlan = session.add_node(WlanNode) node = session.add_node(CoreNode) - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan.id, interface) + iface = ip_prefixes.create_iface(node) + session.add_link(node.id, wlan.id, iface) link_data = wlan.all_link_data()[0] queue = Queue() diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index d3b9362d..c413295a 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -107,15 +107,15 @@ class TestGui: switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) + iface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), ], ) @@ -131,15 +131,15 @@ class TestGui: switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface2_ip4 = str(ip_prefix[node1_id]) + iface2_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, switch_id), (LinkTlvs.N2_NUMBER, node1_id), - (LinkTlvs.INTERFACE2_NUMBER, 0), - (LinkTlvs.INTERFACE2_IP4, interface2_ip4), - (LinkTlvs.INTERFACE2_IP4_MASK, 24), + (LinkTlvs.IFACE2_NUMBER, 0), + (LinkTlvs.IFACE2_IP4, iface2_ip4), + (LinkTlvs.IFACE2_IP4_MASK, 24), ], ) @@ -155,19 +155,19 @@ class TestGui: node2_id = 2 coretlv.session.add_node(CoreNode, _id=node2_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) - interface2_ip4 = str(ip_prefix[node2_id]) + iface1_ip4 = str(ip_prefix[node1_id]) + iface2_ip4 = str(ip_prefix[node2_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, node2_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), - (LinkTlvs.INTERFACE2_NUMBER, 0), - (LinkTlvs.INTERFACE2_IP4, interface2_ip4), - (LinkTlvs.INTERFACE2_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE2_NUMBER, 0), + (LinkTlvs.IFACE2_IP4, iface2_ip4), + (LinkTlvs.IFACE2_IP4_MASK, 24), ], ) @@ -185,15 +185,15 @@ class TestGui: switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) + iface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) @@ -209,7 +209,7 @@ class TestGui: [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_NUMBER, 0), (LinkTlvs.BANDWIDTH, bandwidth), ], ) @@ -227,18 +227,18 @@ class TestGui: node2_id = 2 coretlv.session.add_node(CoreNode, _id=node2_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) - interface2_ip4 = str(ip_prefix[node2_id]) + iface1_ip4 = str(ip_prefix[node1_id]) + iface2_ip4 = str(ip_prefix[node2_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, node2_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), - (LinkTlvs.INTERFACE2_IP4, interface2_ip4), - (LinkTlvs.INTERFACE2_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE2_IP4, iface2_ip4), + (LinkTlvs.IFACE2_IP4_MASK, 24), ], ) coretlv.handle_message(message) @@ -253,8 +253,8 @@ class TestGui: [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, node2_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE2_NUMBER, 0), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE2_NUMBER, 0), ], ) coretlv.handle_message(message) @@ -271,15 +271,15 @@ class TestGui: switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) + iface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) @@ -292,7 +292,7 @@ class TestGui: [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_NUMBER, 0), ], ) coretlv.handle_message(message) @@ -307,15 +307,15 @@ class TestGui: switch_id = 2 coretlv.session.add_node(SwitchNode, _id=switch_id) ip_prefix = netaddr.IPNetwork("10.0.0.0/24") - interface1_ip4 = str(ip_prefix[node1_id]) + iface1_ip4 = str(ip_prefix[node1_id]) message = coreapi.CoreLinkMessage.create( MessageFlags.ADD.value, [ (LinkTlvs.N1_NUMBER, node1_id), (LinkTlvs.N2_NUMBER, switch_id), - (LinkTlvs.INTERFACE1_NUMBER, 0), - (LinkTlvs.INTERFACE1_IP4, interface1_ip4), - (LinkTlvs.INTERFACE1_IP4_MASK, 24), + (LinkTlvs.IFACE1_NUMBER, 0), + (LinkTlvs.IFACE1_IP4, iface1_ip4), + (LinkTlvs.IFACE1_IP4_MASK, 24), ], ) coretlv.handle_message(message) @@ -328,7 +328,7 @@ class TestGui: [ (LinkTlvs.N1_NUMBER, switch_id), (LinkTlvs.N2_NUMBER, node1_id), - (LinkTlvs.INTERFACE2_NUMBER, 0), + (LinkTlvs.IFACE2_NUMBER, 0), ], ) coretlv.handle_message(message) diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 819e2be8..fea4f4f8 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -14,9 +14,9 @@ def create_ptp_network( node2 = session.add_node(CoreNode) # link nodes to net node - interface1_data = ip_prefixes.create_interface(node1) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface1_data, interface2_data) + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) # instantiate session session.instantiate() @@ -29,41 +29,41 @@ class TestLinks: # given node1 = session.add_node(CoreNode) node2 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) - interface2_data = ip_prefixes.create_interface(node2) + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) # when - session.add_link(node1.id, node2.id, interface1_data, interface2_data) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) # then - assert node1.netif(interface1_data.id) - assert node2.netif(interface2_data.id) + assert node1.get_iface(iface1_data.id) + assert node2.get_iface(iface2_data.id) def test_add_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(CoreNode) node2 = session.add_node(SwitchNode) - interface1_data = ip_prefixes.create_interface(node1) + iface1_data = ip_prefixes.create_iface(node1) # when - session.add_link(node1.id, node2.id, interface1_data=interface1_data) + session.add_link(node1.id, node2.id, iface1_data=iface1_data) # then assert node2.all_link_data() - assert node1.netif(interface1_data.id) + assert node1.get_iface(iface1_data.id) def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(SwitchNode) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) + iface2_data = ip_prefixes.create_iface(node2) # when - session.add_link(node1.id, node2.id, interface2_data=interface2_data) + session.add_link(node1.id, node2.id, iface2_data=iface2_data) # then assert node1.all_link_data() - assert node2.netif(interface2_data.id) + assert node2.get_iface(iface2_data.id) def test_add_net_to_net(self, session): # given @@ -85,29 +85,29 @@ class TestLinks: jitter = 10 node1 = session.add_node(CoreNode) node2 = session.add_node(SwitchNode) - interface1_data = ip_prefixes.create_interface(node1) - session.add_link(node1.id, node2.id, interface1_data) - interface1 = node1.netif(interface1_data.id) - assert interface1.getparam("delay") != delay - assert interface1.getparam("bw") != bandwidth - assert interface1.getparam("loss") != loss - assert interface1.getparam("duplicate") != dup - assert interface1.getparam("jitter") != jitter + iface1_data = ip_prefixes.create_iface(node1) + session.add_link(node1.id, node2.id, iface1_data) + iface1 = node1.get_iface(iface1_data.id) + assert iface1.getparam("delay") != delay + assert iface1.getparam("bw") != bandwidth + assert iface1.getparam("loss") != loss + assert iface1.getparam("duplicate") != dup + assert iface1.getparam("jitter") != jitter # when options = LinkOptions( delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( - node1.id, node2.id, interface1_id=interface1_data.id, options=options + node1.id, node2.id, iface1_id=iface1_data.id, options=options ) # then - assert interface1.getparam("delay") == delay - assert interface1.getparam("bw") == bandwidth - assert interface1.getparam("loss") == loss - assert interface1.getparam("duplicate") == dup - assert interface1.getparam("jitter") == jitter + assert iface1.getparam("delay") == delay + assert iface1.getparam("bw") == bandwidth + assert iface1.getparam("loss") == loss + assert iface1.getparam("duplicate") == dup + assert iface1.getparam("jitter") == jitter def test_update_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -118,29 +118,29 @@ class TestLinks: jitter = 10 node1 = session.add_node(SwitchNode) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface2_data=interface2_data) - interface2 = node2.netif(interface2_data.id) - assert interface2.getparam("delay") != delay - assert interface2.getparam("bw") != bandwidth - assert interface2.getparam("loss") != loss - assert interface2.getparam("duplicate") != dup - assert interface2.getparam("jitter") != jitter + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface2_data=iface2_data) + iface2 = node2.get_iface(iface2_data.id) + assert iface2.getparam("delay") != delay + assert iface2.getparam("bw") != bandwidth + assert iface2.getparam("loss") != loss + assert iface2.getparam("duplicate") != dup + assert iface2.getparam("jitter") != jitter # when options = LinkOptions( delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) session.update_link( - node1.id, node2.id, interface2_id=interface2_data.id, options=options + node1.id, node2.id, iface2_id=iface2_data.id, options=options ) # then - assert interface2.getparam("delay") == delay - assert interface2.getparam("bw") == bandwidth - assert interface2.getparam("loss") == loss - assert interface2.getparam("duplicate") == dup - assert interface2.getparam("jitter") == jitter + assert iface2.getparam("delay") == delay + assert iface2.getparam("bw") == bandwidth + assert iface2.getparam("loss") == loss + assert iface2.getparam("duplicate") == dup + assert iface2.getparam("jitter") == jitter def test_update_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given @@ -151,83 +151,81 @@ class TestLinks: jitter = 10 node1 = session.add_node(CoreNode) node2 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface1_data, interface2_data) - interface1 = node1.netif(interface1_data.id) - interface2 = node2.netif(interface2_data.id) - assert interface1.getparam("delay") != delay - assert interface1.getparam("bw") != bandwidth - assert interface1.getparam("loss") != loss - assert interface1.getparam("duplicate") != dup - assert interface1.getparam("jitter") != jitter - assert interface2.getparam("delay") != delay - assert interface2.getparam("bw") != bandwidth - assert interface2.getparam("loss") != loss - assert interface2.getparam("duplicate") != dup - assert interface2.getparam("jitter") != jitter + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) + iface1 = node1.get_iface(iface1_data.id) + iface2 = node2.get_iface(iface2_data.id) + assert iface1.getparam("delay") != delay + assert iface1.getparam("bw") != bandwidth + assert iface1.getparam("loss") != loss + assert iface1.getparam("duplicate") != dup + assert iface1.getparam("jitter") != jitter + assert iface2.getparam("delay") != delay + assert iface2.getparam("bw") != bandwidth + assert iface2.getparam("loss") != loss + assert iface2.getparam("duplicate") != dup + assert iface2.getparam("jitter") != jitter # when options = LinkOptions( delay=delay, bandwidth=bandwidth, loss=loss, dup=dup, jitter=jitter ) - session.update_link( - node1.id, node2.id, interface1_data.id, interface2_data.id, options - ) + session.update_link(node1.id, node2.id, iface1_data.id, iface2_data.id, options) # then - assert interface1.getparam("delay") == delay - assert interface1.getparam("bw") == bandwidth - assert interface1.getparam("loss") == loss - assert interface1.getparam("duplicate") == dup - assert interface1.getparam("jitter") == jitter - assert interface2.getparam("delay") == delay - assert interface2.getparam("bw") == bandwidth - assert interface2.getparam("loss") == loss - assert interface2.getparam("duplicate") == dup - assert interface2.getparam("jitter") == jitter + assert iface1.getparam("delay") == delay + assert iface1.getparam("bw") == bandwidth + assert iface1.getparam("loss") == loss + assert iface1.getparam("duplicate") == dup + assert iface1.getparam("jitter") == jitter + assert iface2.getparam("delay") == delay + assert iface2.getparam("bw") == bandwidth + assert iface2.getparam("loss") == loss + assert iface2.getparam("duplicate") == dup + assert iface2.getparam("jitter") == jitter def test_delete_ptp(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(CoreNode) node2 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface1_data, interface2_data) - assert node1.netif(interface1_data.id) - assert node2.netif(interface2_data.id) + iface1_data = ip_prefixes.create_iface(node1) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface1_data, iface2_data) + assert node1.get_iface(iface1_data.id) + assert node2.get_iface(iface2_data.id) # when - session.delete_link(node1.id, node2.id, interface1_data.id, interface2_data.id) + session.delete_link(node1.id, node2.id, iface1_data.id, iface2_data.id) # then - assert not node1.netif(interface1_data.id) - assert not node2.netif(interface2_data.id) + assert iface1_data.id not in node1.ifaces + assert iface2_data.id not in node2.ifaces def test_delete_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(CoreNode) node2 = session.add_node(SwitchNode) - interface1_data = ip_prefixes.create_interface(node1) - session.add_link(node1.id, node2.id, interface1_data) - assert node1.netif(interface1_data.id) + iface1_data = ip_prefixes.create_iface(node1) + session.add_link(node1.id, node2.id, iface1_data) + assert node1.get_iface(iface1_data.id) # when - session.delete_link(node1.id, node2.id, interface1_id=interface1_data.id) + session.delete_link(node1.id, node2.id, iface1_id=iface1_data.id) # then - assert not node1.netif(interface1_data.id) + assert iface1_data.id not in node1.ifaces def test_delete_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): # given node1 = session.add_node(SwitchNode) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) - session.add_link(node1.id, node2.id, interface2_data=interface2_data) - assert node2.netif(interface2_data.id) + iface2_data = ip_prefixes.create_iface(node2) + session.add_link(node1.id, node2.id, iface2_data=iface2_data) + assert node2.get_iface(iface2_data.id) # when - session.delete_link(node1.id, node2.id, interface2_id=interface2_data.id) + session.delete_link(node1.id, node2.id, iface2_id=iface2_data.id) # then - assert not node2.netif(interface2_data.id) + assert iface2_data.id not in node2.ifaces diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 0cbdb8ae..d7e435ab 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -53,53 +53,53 @@ class TestNodes: # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) - interface_data = InterfaceData() - interface = node.newnetif(switch, interface_data) + iface_data = InterfaceData() + iface = node.new_iface(switch, iface_data) mac = "aa:aa:aa:ff:ff:ff" # when - node.sethwaddr(interface.netindex, mac) + node.sethwaddr(iface.node_id, mac) # then - assert interface.hwaddr == mac + assert iface.hwaddr == mac def test_node_sethwaddr_exception(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) - interface_data = InterfaceData() - interface = node.newnetif(switch, interface_data) + iface_data = InterfaceData() + iface = node.new_iface(switch, iface_data) mac = "aa:aa:aa:ff:ff:fff" # when with pytest.raises(CoreError): - node.sethwaddr(interface.netindex, mac) + node.sethwaddr(iface.node_id, mac) def test_node_addaddr(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) - interface_data = InterfaceData() - interface = node.newnetif(switch, interface_data) + iface_data = InterfaceData() + iface = node.new_iface(switch, iface_data) addr = "192.168.0.1/24" # when - node.addaddr(interface.netindex, addr) + node.addaddr(iface.node_id, addr) # then - assert interface.addrlist[0] == addr + assert iface.addrlist[0] == addr def test_node_addaddr_exception(self, session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) - interface_data = InterfaceData() - interface = node.newnetif(switch, interface_data) + iface_data = InterfaceData() + iface = node.new_iface(switch, iface_data) addr = "256.168.0.1/24" # when with pytest.raises(CoreError): - node.addaddr(interface.netindex, addr) + node.addaddr(iface.node_id, addr) @pytest.mark.parametrize("net_type", NET_TYPES) def test_net(self, session, net_type): diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 0b44a354..55f5a2ab 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -73,8 +73,8 @@ class TestXml: # link nodes to ptp net for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, ptp_node.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -128,8 +128,8 @@ class TestXml: # link nodes to ptp net for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, ptp_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, ptp_node.id, iface1_data=iface_data) # set custom values for node service session.services.set_service(node1.id, SshService.name) @@ -197,8 +197,8 @@ class TestXml: # link nodes for node in [node1, node2]: - interface = ip_prefixes.create_interface(node) - session.add_link(node.id, wlan_node.id, interface1_data=interface) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, wlan_node.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -299,7 +299,7 @@ class TestXml: """ # create nodes node1 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) + iface1_data = ip_prefixes.create_iface(node1) switch = session.add_node(SwitchNode) # create link @@ -309,7 +309,7 @@ class TestXml: options.jitter = 10 options.delay = 30 options.dup = 5 - session.add_link(node1.id, switch.id, interface1_data, options=options) + session.add_link(node1.id, switch.id, iface1_data, options=options) # instantiate session session.instantiate() @@ -365,9 +365,9 @@ class TestXml: """ # create nodes node1 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) + iface1_data = ip_prefixes.create_iface(node1) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) + iface2_data = ip_prefixes.create_iface(node2) # create link options = LinkOptions() @@ -376,7 +376,7 @@ class TestXml: options.jitter = 10 options.delay = 30 options.dup = 5 - session.add_link(node1.id, node2.id, interface1_data, interface2_data, options) + session.add_link(node1.id, node2.id, iface1_data, iface2_data, options) # instantiate session session.instantiate() @@ -432,9 +432,9 @@ class TestXml: """ # create nodes node1 = session.add_node(CoreNode) - interface1_data = ip_prefixes.create_interface(node1) + iface1_data = ip_prefixes.create_iface(node1) node2 = session.add_node(CoreNode) - interface2_data = ip_prefixes.create_interface(node2) + iface2_data = ip_prefixes.create_iface(node2) # create link options1 = LinkOptions() @@ -444,7 +444,7 @@ class TestXml: options1.loss = 10.5 options1.dup = 5 options1.jitter = 5 - session.add_link(node1.id, node2.id, interface1_data, interface2_data, options1) + session.add_link(node1.id, node2.id, iface1_data, iface2_data, options1) options2 = LinkOptions() options2.unidirectional = 1 options2.bandwidth = 10000 @@ -453,7 +453,7 @@ class TestXml: options2.dup = 10 options2.jitter = 10 session.update_link( - node2.id, node1.id, interface2_data.id, interface1_data.id, options2 + node2.id, node1.id, iface2_data.id, iface1_data.id, options2 ) # instantiate session diff --git a/docs/scripting.md b/docs/scripting.md index 59bc02ae..18666a9a 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -61,8 +61,8 @@ def main(): # create nodes for _ in range(NODES): node = session.add_node(CoreNode) - interface = prefixes.create_interface(node) - session.add_link(node.id, switch.id, interface1_data=interface) + interface = prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface1_data=interface) # instantiate session session.instantiate() From eeca33e72240aa83e355f584753c8f1e2038f91a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 12:50:24 -0700 Subject: [PATCH 057/146] combined core.emulator.data and core.emulator.emudata, updated LinkData to leverage InterfaceData, instead of repeated interface fields, removed session from LinkData and LinkOptions --- daemon/core/api/grpc/client.py | 2 +- daemon/core/api/grpc/grpcutils.py | 39 ++-- daemon/core/api/grpc/server.py | 3 +- daemon/core/api/tlv/corehandlers.py | 43 ++-- daemon/core/emane/commeffect.py | 2 +- daemon/core/emane/emanemodel.py | 2 +- daemon/core/emane/nodes.py | 3 +- daemon/core/emulator/data.py | 221 +++++++++++++++++-- daemon/core/emulator/emudata.py | 206 ----------------- daemon/core/emulator/session.py | 4 +- daemon/core/location/mobility.py | 3 +- daemon/core/nodes/base.py | 26 +-- daemon/core/nodes/network.py | 55 ++--- daemon/core/nodes/physical.py | 2 +- daemon/core/xml/corexml.py | 71 ++---- daemon/examples/configservices/testing.py | 2 +- daemon/examples/docker/docker2core.py | 2 +- daemon/examples/docker/docker2docker.py | 2 +- daemon/examples/docker/switch.py | 2 +- daemon/examples/lxd/lxd2core.py | 2 +- daemon/examples/lxd/lxd2lxd.py | 2 +- daemon/examples/lxd/switch.py | 2 +- daemon/examples/python/distributed_emane.py | 2 +- daemon/examples/python/distributed_lxd.py | 2 +- daemon/examples/python/distributed_ptp.py | 2 +- daemon/examples/python/distributed_switch.py | 2 +- daemon/examples/python/emane80211.py | 2 +- daemon/examples/python/switch.py | 2 +- daemon/examples/python/switch_inject.py | 2 +- daemon/examples/python/wlan.py | 2 +- daemon/tests/conftest.py | 2 +- daemon/tests/emane/test_emane.py | 2 +- daemon/tests/test_core.py | 2 +- daemon/tests/test_distributed.py | 2 +- daemon/tests/test_grpc.py | 3 +- daemon/tests/test_links.py | 2 +- daemon/tests/test_nodes.py | 2 +- daemon/tests/test_xml.py | 2 +- docs/scripting.md | 2 +- 39 files changed, 332 insertions(+), 399 deletions(-) delete mode 100644 daemon/core/emulator/emudata.py diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 47aaef63..68bfc502 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -92,7 +92,7 @@ from core.api.grpc.wlan_pb2 import ( WlanLinkRequest, WlanLinkResponse, ) -from core.emulator.emudata import IpPrefixes +from core.emulator.data import IpPrefixes class InterfaceHelper: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index f2f85798..095c4d0c 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -11,8 +11,7 @@ from core.api.grpc import common_pb2, core_pb2 from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig from core.config import ConfigurableOptions from core.emane.nodes import EmaneNet -from core.emulator.data import LinkData -from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import LinkTypes, NodeTypes from core.emulator.session import Session from core.nodes.base import CoreNode, NodeBase @@ -308,6 +307,18 @@ def parse_emane_model_id(_id: int) -> Tuple[int, int]: return node_id, iface_id +def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface: + return core_pb2.Interface( + id=iface_data.id, + name=iface_data.name, + mac=iface_data.mac, + ip4=iface_data.ip4, + ip4mask=iface_data.ip4_mask, + ip6=iface_data.ip6, + ip6mask=iface_data.ip6_mask, + ) + + def convert_link(link_data: LinkData) -> core_pb2.Link: """ Convert link_data into core protobuf link. @@ -316,27 +327,11 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: :return: core protobuf Link """ iface1 = None - if link_data.iface1_id is not None: - iface1 = core_pb2.Interface( - id=link_data.iface1_id, - name=link_data.iface1_name, - mac=convert_value(link_data.iface1_mac), - ip4=convert_value(link_data.iface1_ip4), - ip4mask=link_data.iface1_ip4_mask, - ip6=convert_value(link_data.iface1_ip6), - ip6mask=link_data.iface1_ip6_mask, - ) + if link_data.iface1 is not None: + iface1 = convert_iface(link_data.iface1) iface2 = None - if link_data.iface2_id is not None: - iface2 = core_pb2.Interface( - id=link_data.iface2_id, - name=link_data.iface2_name, - mac=convert_value(link_data.iface2_mac), - ip4=convert_value(link_data.iface2_ip4), - ip4mask=link_data.iface2_ip4_mask, - ip6=convert_value(link_data.iface2_ip6), - ip6mask=link_data.iface2_ip6_mask, - ) + if link_data.iface2 is not None: + iface2 = convert_iface(link_data.iface2) options = core_pb2.LinkOptions( opaque=link_data.opaque, jitter=link_data.jitter, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 87b69a77..1be60116 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -108,8 +108,7 @@ from core.api.grpc.wlan_pb2 import ( WlanLinkResponse, ) from core.emulator.coreemu import CoreEmu -from core.emulator.data import LinkData -from core.emulator.emudata import LinkOptions, NodeOptions +from core.emulator.data import LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index b09a37fe..88906e0c 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -29,8 +29,15 @@ from core.api.tlv.enumerations import ( NodeTlvs, SessionTlvs, ) -from core.emulator.data import ConfigData, EventData, ExceptionData, FileData -from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions +from core.emulator.data import ( + ConfigData, + EventData, + ExceptionData, + FileData, + InterfaceData, + LinkOptions, + NodeOptions, +) from core.emulator.enumerations import ( ConfigDataTypes, EventTypes, @@ -342,6 +349,12 @@ class CoreHandler(socketserver.BaseRequestHandler): dup = "" if link_data.dup is not None: dup = str(link_data.dup) + iface1 = link_data.iface1 + if iface1 is None: + iface1 = InterfaceData() + iface2 = link_data.iface2 + if iface2 is None: + iface2 = InterfaceData() tlv_data = structutils.pack_values( coreapi.CoreLinkTlv, @@ -355,7 +368,6 @@ class CoreHandler(socketserver.BaseRequestHandler): (LinkTlvs.JITTER, link_data.jitter), (LinkTlvs.MER, link_data.mer), (LinkTlvs.BURST, link_data.burst), - (LinkTlvs.SESSION, link_data.session), (LinkTlvs.MBURST, link_data.mburst), (LinkTlvs.TYPE, link_data.link_type.value), (LinkTlvs.GUI_ATTRIBUTES, link_data.gui_attributes), @@ -363,18 +375,18 @@ class CoreHandler(socketserver.BaseRequestHandler): (LinkTlvs.EMULATION_ID, link_data.emulation_id), (LinkTlvs.NETWORK_ID, link_data.network_id), (LinkTlvs.KEY, link_data.key), - (LinkTlvs.IFACE1_NUMBER, link_data.iface1_id), - (LinkTlvs.IFACE1_IP4, link_data.iface1_ip4), - (LinkTlvs.IFACE1_IP4_MASK, link_data.iface1_ip4_mask), - (LinkTlvs.IFACE1_MAC, link_data.iface1_mac), - (LinkTlvs.IFACE1_IP6, link_data.iface1_ip6), - (LinkTlvs.IFACE1_IP6_MASK, link_data.iface1_ip6_mask), - (LinkTlvs.IFACE2_NUMBER, link_data.iface2_id), - (LinkTlvs.IFACE2_IP4, link_data.iface2_ip4), - (LinkTlvs.IFACE2_IP4_MASK, link_data.iface2_ip4_mask), - (LinkTlvs.IFACE2_MAC, link_data.iface2_mac), - (LinkTlvs.IFACE2_IP6, link_data.iface2_ip6), - (LinkTlvs.IFACE2_IP6_MASK, link_data.iface2_ip6_mask), + (LinkTlvs.IFACE1_NUMBER, iface1.id), + (LinkTlvs.IFACE1_IP4, iface1.ip4), + (LinkTlvs.IFACE1_IP4_MASK, iface1.ip4_mask), + (LinkTlvs.IFACE1_MAC, iface1.mac), + (LinkTlvs.IFACE1_IP6, iface1.ip6), + (LinkTlvs.IFACE1_IP6_MASK, iface1.ip6_mask), + (LinkTlvs.IFACE2_NUMBER, iface2.id), + (LinkTlvs.IFACE2_IP4, iface2.ip4), + (LinkTlvs.IFACE2_IP4_MASK, iface2.ip4_mask), + (LinkTlvs.IFACE2_MAC, iface2.mac), + (LinkTlvs.IFACE2_IP6, iface2.ip6), + (LinkTlvs.IFACE2_IP6_MASK, iface2.ip6_mask), (LinkTlvs.OPAQUE, link_data.opaque), ], ) @@ -774,7 +786,6 @@ class CoreHandler(socketserver.BaseRequestHandler): options = LinkOptions(type=link_type) options.delay = message.get_tlv(LinkTlvs.DELAY.value) options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) - options.session = message.get_tlv(LinkTlvs.SESSION.value) options.loss = message.get_tlv(LinkTlvs.LOSS.value) options.dup = message.get_tlv(LinkTlvs.DUP.value) options.jitter = message.get_tlv(LinkTlvs.JITTER.value) diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 0f441d76..610099f1 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -11,7 +11,7 @@ from lxml import etree from core.config import ConfigGroup, Configuration from core.emane import emanemanifest, emanemodel from core.emane.nodes import EmaneNet -from core.emulator.emudata import LinkOptions +from core.emulator.data import LinkOptions from core.emulator.enumerations import TransportType from core.nodes.interface import CoreInterface from core.xml import emanexml diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 1a14011a..43fbc0fb 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -8,7 +8,7 @@ from typing import Dict, List, Optional, Set from core.config import ConfigGroup, Configuration from core.emane import emanemanifest from core.emane.nodes import EmaneNet -from core.emulator.emudata import LinkOptions +from core.emulator.data import LinkOptions from core.emulator.enumerations import ConfigDataTypes, TransportType from core.errors import CoreError from core.location.mobility import WirelessModel diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index eed51ff2..c28f1382 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -6,9 +6,8 @@ share the same MAC+PHY model. import logging from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type -from core.emulator.data import LinkData +from core.emulator.data import LinkData, LinkOptions from core.emulator.distributed import DistributedServer -from core.emulator.emudata import LinkOptions from core.emulator.enumerations import ( LinkTypes, MessageFlags, diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 47f45820..c08a70f0 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -1,10 +1,12 @@ """ CORE data objects. """ +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, List, Optional, Tuple -from dataclasses import dataclass -from typing import List, Tuple +import netaddr +from core import utils from core.emulator.enumerations import ( EventTypes, ExceptionLevels, @@ -13,6 +15,9 @@ from core.emulator.enumerations import ( NodeTypes, ) +if TYPE_CHECKING: + from core.nodes.base import CoreNode + @dataclass class ConfigData: @@ -93,6 +98,57 @@ class NodeData: source: str = None +@dataclass +class InterfaceData: + """ + Convenience class for storing interface data. + """ + + id: int = None + name: str = None + mac: str = None + ip4: str = None + ip4_mask: int = None + ip6: str = None + ip6_mask: int = None + + def get_addresses(self) -> List[str]: + """ + Returns a list of ip4 and ip6 addresses when present. + + :return: list of addresses + """ + addresses = [] + if self.ip4 and self.ip4_mask: + addresses.append(f"{self.ip4}/{self.ip4_mask}") + if self.ip6 and self.ip6_mask: + addresses.append(f"{self.ip6}/{self.ip6_mask}") + return addresses + + +@dataclass +class LinkOptions: + """ + Options for creating and updating links within core. + """ + + type: LinkTypes = LinkTypes.WIRED + delay: int = None + bandwidth: int = None + loss: float = None + dup: int = None + jitter: int = None + mer: int = None + burst: int = None + mburst: int = None + gui_attributes: str = None + unidirectional: bool = None + emulation_id: int = None + network_id: int = None + key: int = None + opaque: str = None + + @dataclass class LinkData: message_type: MessageFlags = None @@ -106,7 +162,6 @@ class LinkData: jitter: float = None mer: float = None burst: float = None - session: int = None mburst: float = None link_type: LinkTypes = None gui_attributes: str = None @@ -114,19 +169,151 @@ class LinkData: emulation_id: int = None network_id: int = None key: int = None - iface1_id: int = None - iface1_name: str = None - iface1_ip4: str = None - iface1_ip4_mask: int = None - iface1_mac: str = None - iface1_ip6: str = None - iface1_ip6_mask: int = None - iface2_id: int = None - iface2_name: str = None - iface2_ip4: str = None - iface2_ip4_mask: int = None - iface2_mac: str = None - iface2_ip6: str = None - iface2_ip6_mask: int = None + iface1: InterfaceData = None + iface2: InterfaceData = None opaque: str = None color: str = None + + +class IpPrefixes: + """ + Convenience class to help generate IP4 and IP6 addresses for nodes within CORE. + """ + + def __init__(self, ip4_prefix: str = None, ip6_prefix: str = None) -> None: + """ + Creates an IpPrefixes object. + + :param ip4_prefix: ip4 prefix to use for generation + :param ip6_prefix: ip6 prefix to use for generation + :raises ValueError: when both ip4 and ip6 prefixes have not been provided + """ + if not ip4_prefix and not ip6_prefix: + raise ValueError("ip4 or ip6 must be provided") + + self.ip4 = None + if ip4_prefix: + self.ip4 = netaddr.IPNetwork(ip4_prefix) + self.ip6 = None + if ip6_prefix: + self.ip6 = netaddr.IPNetwork(ip6_prefix) + + def ip4_address(self, node_id: int) -> str: + """ + Convenience method to return the IP4 address for a node. + + :param node_id: node id to get IP4 address for + :return: IP4 address or None + """ + if not self.ip4: + raise ValueError("ip4 prefixes have not been set") + return str(self.ip4[node_id]) + + def ip6_address(self, node_id: int) -> str: + """ + Convenience method to return the IP6 address for a node. + + :param node_id: node id to get IP6 address for + :return: IP4 address or None + """ + if not self.ip6: + raise ValueError("ip6 prefixes have not been set") + return str(self.ip6[node_id]) + + def gen_iface(self, node_id: int, name: str = None, mac: str = None): + """ + Creates interface data for linking nodes, using the nodes unique id for + generation, along with a random mac address, unless provided. + + :param node_id: node id to create an interface for + :param name: name to set for interface, default is eth{id} + :param mac: mac address to use for this interface, default is random + generation + :return: new interface data for the provided node + """ + # generate ip4 data + ip4 = None + ip4_mask = None + if self.ip4: + ip4 = self.ip4_address(node_id) + ip4_mask = self.ip4.prefixlen + + # generate ip6 data + ip6 = None + ip6_mask = None + if self.ip6: + ip6 = self.ip6_address(node_id) + ip6_mask = self.ip6.prefixlen + + # random mac + if not mac: + mac = utils.random_mac() + + return InterfaceData( + name=name, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask, mac=mac + ) + + def create_iface( + self, node: "CoreNode", name: str = None, mac: str = None + ) -> InterfaceData: + """ + Creates interface data for linking nodes, using the nodes unique id for + generation, along with a random mac address, unless provided. + + :param node: node to create interface for + :param name: name to set for interface, default is eth{id} + :param mac: mac address to use for this interface, default is random + generation + :return: new interface data for the provided node + """ + iface_data = self.gen_iface(node.id, name, mac) + iface_data.id = node.next_iface_id() + return iface_data + + +@dataclass +class NodeOptions: + """ + Options for creating and updating nodes within core. + """ + + name: str = None + model: Optional[str] = "PC" + canvas: int = None + icon: str = None + opaque: str = None + services: List[str] = field(default_factory=list) + config_services: List[str] = field(default_factory=list) + x: float = None + y: float = None + lat: float = None + lon: float = None + alt: float = None + emulation_id: int = None + server: str = None + image: str = None + emane: str = None + + def set_position(self, x: float, y: float) -> None: + """ + Convenience method for setting position. + + :param x: x position + :param y: y position + :return: nothing + """ + self.x = x + self.y = y + + def set_location(self, lat: float, lon: float, alt: float) -> None: + """ + Convenience method for setting location. + + :param lat: latitude + :param lon: longitude + :param alt: altitude + :return: nothing + """ + self.lat = lat + self.lon = lon + self.alt = alt diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py deleted file mode 100644 index 25ce71ac..00000000 --- a/daemon/core/emulator/emudata.py +++ /dev/null @@ -1,206 +0,0 @@ -from dataclasses import dataclass, field -from typing import TYPE_CHECKING, List, Optional - -import netaddr - -from core import utils -from core.emulator.enumerations import LinkTypes - -if TYPE_CHECKING: - from core.nodes.base import CoreNode - - -@dataclass -class NodeOptions: - """ - Options for creating and updating nodes within core. - """ - - name: str = None - model: Optional[str] = "PC" - canvas: int = None - icon: str = None - opaque: str = None - services: List[str] = field(default_factory=list) - config_services: List[str] = field(default_factory=list) - x: float = None - y: float = None - lat: float = None - lon: float = None - alt: float = None - emulation_id: int = None - server: str = None - image: str = None - emane: str = None - - def set_position(self, x: float, y: float) -> None: - """ - Convenience method for setting position. - - :param x: x position - :param y: y position - :return: nothing - """ - self.x = x - self.y = y - - def set_location(self, lat: float, lon: float, alt: float) -> None: - """ - Convenience method for setting location. - - :param lat: latitude - :param lon: longitude - :param alt: altitude - :return: nothing - """ - self.lat = lat - self.lon = lon - self.alt = alt - - -@dataclass -class LinkOptions: - """ - Options for creating and updating links within core. - """ - - type: LinkTypes = LinkTypes.WIRED - session: int = None - delay: int = None - bandwidth: int = None - loss: float = None - dup: int = None - jitter: int = None - mer: int = None - burst: int = None - mburst: int = None - gui_attributes: str = None - unidirectional: bool = None - emulation_id: int = None - network_id: int = None - key: int = None - opaque: str = None - - -@dataclass -class InterfaceData: - """ - Convenience class for storing interface data. - """ - - id: int = None - name: str = None - mac: str = None - ip4: str = None - ip4_mask: int = None - ip6: str = None - ip6_mask: int = None - - def get_addresses(self) -> List[str]: - """ - Returns a list of ip4 and ip6 addresses when present. - - :return: list of addresses - """ - addresses = [] - if self.ip4 and self.ip4_mask: - addresses.append(f"{self.ip4}/{self.ip4_mask}") - if self.ip6 and self.ip6_mask: - addresses.append(f"{self.ip6}/{self.ip6_mask}") - return addresses - - -class IpPrefixes: - """ - Convenience class to help generate IP4 and IP6 addresses for nodes within CORE. - """ - - def __init__(self, ip4_prefix: str = None, ip6_prefix: str = None) -> None: - """ - Creates an IpPrefixes object. - - :param ip4_prefix: ip4 prefix to use for generation - :param ip6_prefix: ip6 prefix to use for generation - :raises ValueError: when both ip4 and ip6 prefixes have not been provided - """ - if not ip4_prefix and not ip6_prefix: - raise ValueError("ip4 or ip6 must be provided") - - self.ip4 = None - if ip4_prefix: - self.ip4 = netaddr.IPNetwork(ip4_prefix) - self.ip6 = None - if ip6_prefix: - self.ip6 = netaddr.IPNetwork(ip6_prefix) - - def ip4_address(self, node_id: int) -> str: - """ - Convenience method to return the IP4 address for a node. - - :param node_id: node id to get IP4 address for - :return: IP4 address or None - """ - if not self.ip4: - raise ValueError("ip4 prefixes have not been set") - return str(self.ip4[node_id]) - - def ip6_address(self, node_id: int) -> str: - """ - Convenience method to return the IP6 address for a node. - - :param node_id: node id to get IP6 address for - :return: IP4 address or None - """ - if not self.ip6: - raise ValueError("ip6 prefixes have not been set") - return str(self.ip6[node_id]) - - def gen_iface(self, node_id: int, name: str = None, mac: str = None): - """ - Creates interface data for linking nodes, using the nodes unique id for - generation, along with a random mac address, unless provided. - - :param node_id: node id to create an interface for - :param name: name to set for interface, default is eth{id} - :param mac: mac address to use for this interface, default is random - generation - :return: new interface data for the provided node - """ - # generate ip4 data - ip4 = None - ip4_mask = None - if self.ip4: - ip4 = self.ip4_address(node_id) - ip4_mask = self.ip4.prefixlen - - # generate ip6 data - ip6 = None - ip6_mask = None - if self.ip6: - ip6 = self.ip6_address(node_id) - ip6_mask = self.ip6.prefixlen - - # random mac - if not mac: - mac = utils.random_mac() - - return InterfaceData( - name=name, ip4=ip4, ip4_mask=ip4_mask, ip6=ip6, ip6_mask=ip6_mask, mac=mac - ) - - def create_iface( - self, node: "CoreNode", name: str = None, mac: str = None - ) -> InterfaceData: - """ - Creates interface data for linking nodes, using the nodes unique id for - generation, along with a random mac address, unless provided. - - :param node: node to create interface for - :param name: name to set for interface, default is eth{id} - :param mac: mac address to use for this interface, default is random - generation - :return: new interface data for the provided node - """ - iface_data = self.gen_iface(node.id, name, mac) - iface_data.id = node.next_iface_id() - return iface_data diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 2dc5ad12..f2514e67 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -22,11 +22,13 @@ from core.emulator.data import ( EventData, ExceptionData, FileData, + InterfaceData, LinkData, + LinkOptions, NodeData, + NodeOptions, ) from core.emulator.distributed import DistributedController -from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions from core.emulator.enumerations import ( EventTypes, ExceptionLevels, diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index d56c40aa..91a8baae 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -13,8 +13,7 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple from core import utils from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager -from core.emulator.data import EventData, LinkData -from core.emulator.emudata import LinkOptions +from core.emulator.data import EventData, LinkData, LinkOptions from core.emulator.enumerations import ( ConfigDataTypes, EventTypes, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 40aae6a8..3c754aa2 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -14,8 +14,7 @@ import netaddr from core import utils from core.configservice.dependencies import ConfigServiceDependencies from core.constants import MOUNT_BIN, VNODED_BIN -from core.emulator.data import LinkData, NodeData -from core.emulator.emudata import InterfaceData, LinkOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeData from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError from core.nodes.client import VnodeClient @@ -1096,19 +1095,18 @@ class CoreNetworkBase(NodeBase): if uni: unidirectional = 1 - iface2_ip4 = None - iface2_ip4_mask = None - iface2_ip6 = None - iface2_ip6_mask = None + iface2 = InterfaceData( + id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.hwaddr + ) for address in iface.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - iface2_ip4 = ip - iface2_ip4_mask = mask + iface2.ip4 = ip + iface2.ip4_mask = mask else: - iface2_ip6 = ip - iface2_ip6_mask = mask + iface2.ip6 = ip + iface2.ip6_mask = mask link_data = LinkData( message_type=flags, @@ -1116,13 +1114,7 @@ class CoreNetworkBase(NodeBase): node2_id=linked_node.id, link_type=self.linktype, unidirectional=unidirectional, - iface2_id=linked_node.get_iface_id(iface), - iface2_name=iface.name, - iface2_mac=iface.hwaddr, - iface2_ip4=iface2_ip4, - iface2_ip4_mask=iface2_ip4_mask, - iface2_ip6=iface2_ip6, - iface2_ip6_mask=iface2_ip6_mask, + iface2=iface2, delay=iface.getparam("delay"), bandwidth=iface.getparam("bw"), dup=iface.getparam("duplicate"), diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 85e3e488..b2f6bbf3 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -11,8 +11,7 @@ import netaddr from core import utils from core.constants import EBTABLES_BIN, TC_BIN -from core.emulator.data import LinkData, NodeData -from core.emulator.emudata import InterfaceData, LinkOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeData from core.emulator.enumerations import ( LinkTypes, MessageFlags, @@ -894,33 +893,31 @@ class PtpNet(CoreNetwork): if iface1.getparams() != iface2.getparams(): unidirectional = 1 - iface1_ip4 = None - iface1_ip4_mask = None - iface1_ip6 = None - iface1_ip6_mask = None + iface1_data = InterfaceData( + id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.hwaddr + ) for address in iface1.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - iface1_ip4 = ip - iface1_ip4_mask = mask + iface1.ip4 = ip + iface1.ip4_mask = mask else: - iface1_ip6 = ip - iface1_ip6_mask = mask + iface1.ip6 = ip + iface1.ip6_mask = mask - iface2_ip4 = None - iface2_ip4_mask = None - iface2_ip6 = None - iface2_ip6_mask = None + iface2_data = InterfaceData( + id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.hwaddr + ) for address in iface2.addrlist: ip, _sep, mask = address.partition("/") mask = int(mask) if netaddr.valid_ipv4(ip): - iface2_ip4 = ip - iface2_ip4_mask = mask + iface2.ip4 = ip + iface2.ip4_mask = mask else: - iface2_ip6 = ip - iface2_ip6_mask = mask + iface2.ip6 = ip + iface2.ip6_mask = mask link_data = LinkData( message_type=flags, @@ -933,26 +930,16 @@ class PtpNet(CoreNetwork): loss=iface1.getparam("loss"), dup=iface1.getparam("duplicate"), jitter=iface1.getparam("jitter"), - iface1_id=iface1.node.get_iface_id(iface1), - iface1_name=iface1.name, - iface1_mac=iface1.hwaddr, - iface1_ip4=iface1_ip4, - iface1_ip4_mask=iface1_ip4_mask, - iface1_ip6=iface1_ip6, - iface1_ip6_mask=iface1_ip6_mask, - iface2_id=iface2.node.get_iface_id(iface2), - iface2_name=iface2.name, - iface2_mac=iface2.hwaddr, - iface2_ip4=iface2_ip4, - iface2_ip4_mask=iface2_ip4_mask, - iface2_ip6=iface2_ip6, - iface2_ip6_mask=iface2_ip6_mask, + iface1=iface1_data, + iface2=iface2_data, ) all_links.append(link_data) # build a 2nd link message for the upstream link parameters # (swap if1 and if2) if unidirectional: + iface1_data = InterfaceData(id=iface2.node.get_iface_id(iface2)) + iface2_data = InterfaceData(id=iface1.node.get_iface_id(iface1)) link_data = LinkData( message_type=MessageFlags.NONE, link_type=self.linktype, @@ -964,8 +951,8 @@ class PtpNet(CoreNetwork): dup=iface2.getparam("duplicate"), jitter=iface2.getparam("jitter"), unidirectional=1, - iface1_id=iface2.node.get_iface_id(iface2), - iface2_id=iface1.node.get_iface_id(iface1), + iface1=iface1_data, + iface2=iface2_data, ) all_links.append(link_data) return all_links diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 555e0ec9..36bcb267 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -9,8 +9,8 @@ from typing import IO, TYPE_CHECKING, List, Optional, Tuple from core import utils from core.constants import MOUNT_BIN, UMOUNT_BIN +from core.emulator.data import InterfaceData, LinkOptions from core.emulator.distributed import DistributedServer -from core.emulator.emudata import InterfaceData, LinkOptions from core.emulator.enumerations import NodeTypes, TransportType from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNetworkBase, CoreNodeBase diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index fe596d7a..1f92502c 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -6,8 +6,7 @@ from lxml import etree import core.nodes.base import core.nodes.physical from core.emane.nodes import EmaneNet -from core.emulator.data import LinkData -from core.emulator.emudata import InterfaceData, LinkOptions, NodeOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import EventTypes, NodeTypes from core.errors import CoreXmlError from core.nodes.base import CoreNodeBase, NodeBase @@ -482,12 +481,10 @@ class CoreXmlWriter: # add link data for link_data in links: # skip basic range links - if link_data.iface1_id is None and link_data.iface2_id is None: + if link_data.iface1 is None and link_data.iface2 is None: continue - link_element = self.create_link_element(link_data) link_elements.append(link_element) - if link_elements.getchildren(): self.scenario.append(link_elements) @@ -496,36 +493,24 @@ class CoreXmlWriter: self.devices.append(device.element) def create_iface_element( - self, - element_name: str, - node_id: int, - iface_id: int, - mac: str, - ip4: str, - ip4_mask: int, - ip6: str, - ip6_mask: int, + self, element_name: str, node_id: int, iface_data: InterfaceData ) -> etree.Element: - iface = etree.Element(element_name) + iface_element = etree.Element(element_name) node = self.session.get_node(node_id, NodeBase) - iface_name = None if isinstance(node, CoreNodeBase): - node_iface = node.get_iface(iface_id) - iface_name = node_iface.name - + iface = node.get_iface(iface_data.id) # check if emane interface - if isinstance(node_iface.net, EmaneNet): - nem = node_iface.net.getnemid(node_iface) - add_attribute(iface, "nem", nem) - - add_attribute(iface, "id", iface_id) - add_attribute(iface, "name", iface_name) - add_attribute(iface, "mac", mac) - add_attribute(iface, "ip4", ip4) - add_attribute(iface, "ip4_mask", ip4_mask) - add_attribute(iface, "ip6", ip6) - add_attribute(iface, "ip6_mask", ip6_mask) - return iface + if isinstance(iface.net, EmaneNet): + nem = iface.net.getnemid(iface) + add_attribute(iface_element, "nem", nem) + add_attribute(iface_element, "id", iface_data.id) + add_attribute(iface_element, "name", iface_data.name) + add_attribute(iface_element, "mac", iface_data.mac) + add_attribute(iface_element, "ip4", iface_data.ip4) + add_attribute(iface_element, "ip4_mask", iface_data.ip4_mask) + add_attribute(iface_element, "ip6", iface_data.ip6) + add_attribute(iface_element, "ip6_mask", iface_data.ip6_mask) + return iface_element def create_link_element(self, link_data: LinkData) -> etree.Element: link_element = etree.Element("link") @@ -533,30 +518,16 @@ class CoreXmlWriter: add_attribute(link_element, "node2", link_data.node2_id) # check for interface one - if link_data.iface1_id is not None: + if link_data.iface1 is not None: iface1 = self.create_iface_element( - "interface1", - link_data.node1_id, - link_data.iface1_id, - link_data.iface1_mac, - link_data.iface1_ip4, - link_data.iface1_ip4_mask, - link_data.iface1_ip6, - link_data.iface1_ip6_mask, + "interface1", link_data.node1_id, link_data.iface1 ) link_element.append(iface1) # check for interface two - if link_data.iface2_id is not None: + if link_data.iface2 is not None: iface2 = self.create_iface_element( - "interface2", - link_data.node2_id, - link_data.iface2_id, - link_data.iface2_mac, - link_data.iface2_ip4, - link_data.iface2_ip4_mask, - link_data.iface2_ip6, - link_data.iface2_ip6_mask, + "interface2", link_data.node2_id, link_data.iface2 ) link_element.append(iface2) @@ -582,7 +553,6 @@ class CoreXmlWriter: add_attribute(options, "network_id", link_data.network_id) add_attribute(options, "key", link_data.key) add_attribute(options, "opaque", link_data.opaque) - add_attribute(options, "session", link_data.session) if options.items(): link_element.append(options) @@ -969,7 +939,6 @@ class CoreXmlReader: if options.loss is None: options.loss = get_float(options_element, "per") options.unidirectional = get_int(options_element, "unidirectional") - options.session = options_element.get("session") options.emulation_id = get_int(options_element, "emulation_id") options.network_id = get_int(options_element, "network_id") options.opaque = options_element.get("opaque") diff --git a/daemon/examples/configservices/testing.py b/daemon/examples/configservices/testing.py index 767d0f45..9706f2c9 100644 --- a/daemon/examples/configservices/testing.py +++ b/daemon/examples/configservices/testing.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/examples/docker/docker2core.py b/daemon/examples/docker/docker2core.py index c38f96af..ae7dae79 100644 --- a/daemon/examples/docker/docker2core.py +++ b/daemon/examples/docker/docker2core.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.docker import DockerNode diff --git a/daemon/examples/docker/docker2docker.py b/daemon/examples/docker/docker2docker.py index 5b62d433..308fd00f 100644 --- a/daemon/examples/docker/docker2docker.py +++ b/daemon/examples/docker/docker2docker.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.docker import DockerNode diff --git a/daemon/examples/docker/switch.py b/daemon/examples/docker/switch.py index 161cd823..fa9e4e40 100644 --- a/daemon/examples/docker/switch.py +++ b/daemon/examples/docker/switch.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.docker import DockerNode diff --git a/daemon/examples/lxd/lxd2core.py b/daemon/examples/lxd/lxd2core.py index 3d8eef6a..b41520d8 100644 --- a/daemon/examples/lxd/lxd2core.py +++ b/daemon/examples/lxd/lxd2core.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.lxd import LxcNode diff --git a/daemon/examples/lxd/lxd2lxd.py b/daemon/examples/lxd/lxd2lxd.py index a7209b5c..3a55e2e1 100644 --- a/daemon/examples/lxd/lxd2lxd.py +++ b/daemon/examples/lxd/lxd2lxd.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.lxd import LxcNode diff --git a/daemon/examples/lxd/switch.py b/daemon/examples/lxd/switch.py index 9b6801f5..12767e71 100644 --- a/daemon/examples/lxd/switch.py +++ b/daemon/examples/lxd/switch.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.lxd import LxcNode diff --git a/daemon/examples/python/distributed_emane.py b/daemon/examples/python/distributed_emane.py index 3ee56108..4421283f 100644 --- a/daemon/examples/python/distributed_emane.py +++ b/daemon/examples/python/distributed_emane.py @@ -9,7 +9,7 @@ import logging from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode diff --git a/daemon/examples/python/distributed_lxd.py b/daemon/examples/python/distributed_lxd.py index 1573836a..26f7caa6 100644 --- a/daemon/examples/python/distributed_lxd.py +++ b/daemon/examples/python/distributed_lxd.py @@ -7,7 +7,7 @@ import argparse import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.lxd import LxcNode diff --git a/daemon/examples/python/distributed_ptp.py b/daemon/examples/python/distributed_ptp.py index 1486c237..fe714e1d 100644 --- a/daemon/examples/python/distributed_ptp.py +++ b/daemon/examples/python/distributed_ptp.py @@ -7,7 +7,7 @@ import argparse import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode diff --git a/daemon/examples/python/distributed_switch.py b/daemon/examples/python/distributed_switch.py index e9eb1e81..35de1cad 100644 --- a/daemon/examples/python/distributed_switch.py +++ b/daemon/examples/python/distributed_switch.py @@ -7,7 +7,7 @@ import argparse import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index 322e569f..9d6def4a 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -10,7 +10,7 @@ import time from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode diff --git a/daemon/examples/python/switch.py b/daemon/examples/python/switch.py index 902e79e0..f05176a3 100644 --- a/daemon/examples/python/switch.py +++ b/daemon/examples/python/switch.py @@ -6,7 +6,7 @@ interact with the GUI. import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/examples/python/switch_inject.py b/daemon/examples/python/switch_inject.py index 89f70e05..18a75a49 100644 --- a/daemon/examples/python/switch_inject.py +++ b/daemon/examples/python/switch_inject.py @@ -8,7 +8,7 @@ same CoreEmu instance the GUI is using. import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/examples/python/wlan.py b/daemon/examples/python/wlan.py index 547a5860..de26ab97 100644 --- a/daemon/examples/python/wlan.py +++ b/daemon/examples/python/wlan.py @@ -6,7 +6,7 @@ interact with the GUI. import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import EventTypes from core.location.mobility import BasicRangeModel from core.nodes.base import CoreNode diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index c3315e7c..be62fc03 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -14,8 +14,8 @@ from core.api.grpc.server import CoreGrpcServer from core.api.tlv.corehandlers import CoreHandler from core.emane.emanemanager import EmaneManager from core.emulator.coreemu import CoreEmu +from core.emulator.data import IpPrefixes from core.emulator.distributed import DistributedServer -from core.emulator.emudata import IpPrefixes from core.emulator.enumerations import EventTypes from core.emulator.session import Session from core.nodes.base import CoreNode diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index e1c7938b..f51e30b9 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -15,7 +15,7 @@ from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emane.rfpipe import EmaneRfPipeModel from core.emane.tdma import EmaneTdmaModel -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.session import Session from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 5771f7ad..2623b0df 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -8,7 +8,7 @@ from typing import Type import pytest -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.enumerations import MessageFlags from core.emulator.session import Session from core.errors import CoreCommandError diff --git a/daemon/tests/test_distributed.py b/daemon/tests/test_distributed.py index 0f4b1731..01362cae 100644 --- a/daemon/tests/test_distributed.py +++ b/daemon/tests/test_distributed.py @@ -1,4 +1,4 @@ -from core.emulator.emudata import NodeOptions +from core.emulator.data import NodeOptions from core.emulator.session import Session from core.nodes.base import CoreNode from core.nodes.network import HubNode diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 23ff0301..b2a1c312 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -18,8 +18,7 @@ from core.api.tlv.dataconversion import ConfigShim from core.api.tlv.enumerations import ConfigFlags from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet -from core.emulator.data import EventData, NodeData -from core.emulator.emudata import IpPrefixes, NodeOptions +from core.emulator.data import EventData, IpPrefixes, NodeData, NodeOptions from core.emulator.enumerations import EventTypes, ExceptionLevels, NodeTypes from core.errors import CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index fea4f4f8..4078d8bc 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -1,6 +1,6 @@ from typing import Tuple -from core.emulator.emudata import IpPrefixes, LinkOptions +from core.emulator.data import IpPrefixes, LinkOptions from core.emulator.session import Session from core.nodes.base import CoreNode from core.nodes.network import SwitchNode diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index d7e435ab..327137d2 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -1,6 +1,6 @@ import pytest -from core.emulator.emudata import InterfaceData, NodeOptions +from core.emulator.data import InterfaceData, NodeOptions from core.emulator.session import Session from core.errors import CoreError from core.nodes.base import CoreNode diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 55f5a2ab..d81fe471 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -3,7 +3,7 @@ from xml.etree import ElementTree import pytest -from core.emulator.emudata import IpPrefixes, LinkOptions, NodeOptions +from core.emulator.data import IpPrefixes, LinkOptions, NodeOptions from core.emulator.enumerations import EventTypes from core.emulator.session import Session from core.errors import CoreError diff --git a/docs/scripting.md b/docs/scripting.md index 18666a9a..f65d66a3 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -36,7 +36,7 @@ interact with the GUI. import logging from core.emulator.coreemu import CoreEmu -from core.emulator.emudata import IpPrefixes +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode From a29a7a558277e9438e288dbeaa1bbd6839bc7dcf Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 14:18:19 -0700 Subject: [PATCH 058/146] refactored LinkOptions to be used within LinkData, instead of duplicating data, removed session from LinkOptions and LinkData --- daemon/core/api/grpc/grpcutils.py | 30 ++++++++++++---------- daemon/core/api/tlv/corehandlers.py | 31 +++++++++++----------- daemon/core/emulator/data.py | 25 ++++++------------ daemon/core/nodes/base.py | 20 ++++----------- daemon/core/nodes/interface.py | 29 +++++++++++++++++++++ daemon/core/nodes/network.py | 21 +++++---------- daemon/core/xml/corexml.py | 29 ++++++++++----------- daemon/tests/test_grpc.py | 4 +-- daemon/tests/test_gui.py | 4 +-- daemon/tests/test_xml.py | 40 ++++++++++++++--------------- 10 files changed, 120 insertions(+), 113 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 095c4d0c..5213a835 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -319,6 +319,22 @@ def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface: ) +def convert_link_options(options_data: LinkOptions) -> core_pb2.LinkOptions: + return core_pb2.LinkOptions( + opaque=options_data.opaque, + jitter=options_data.jitter, + key=options_data.key, + mburst=options_data.mburst, + mer=options_data.mer, + loss=options_data.loss, + bandwidth=options_data.bandwidth, + burst=options_data.burst, + delay=options_data.delay, + dup=options_data.dup, + unidirectional=options_data.unidirectional, + ) + + def convert_link(link_data: LinkData) -> core_pb2.Link: """ Convert link_data into core protobuf link. @@ -332,19 +348,7 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: iface2 = None if link_data.iface2 is not None: iface2 = convert_iface(link_data.iface2) - options = core_pb2.LinkOptions( - opaque=link_data.opaque, - jitter=link_data.jitter, - key=link_data.key, - mburst=link_data.mburst, - mer=link_data.mer, - loss=link_data.loss, - bandwidth=link_data.bandwidth, - burst=link_data.burst, - delay=link_data.delay, - dup=link_data.dup, - unidirectional=link_data.unidirectional, - ) + options = convert_link_options(link_data.options) return core_pb2.Link( type=link_data.link_type.value, node1_id=link_data.node1_id, diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 88906e0c..3a4351f1 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -343,12 +343,13 @@ class CoreHandler(socketserver.BaseRequestHandler): :return: nothing """ logging.debug("handling broadcast link: %s", link_data) + options_data = link_data.options loss = "" - if link_data.loss is not None: - loss = str(link_data.loss) + if options_data.loss is not None: + loss = str(options_data.loss) dup = "" - if link_data.dup is not None: - dup = str(link_data.dup) + if options_data.dup is not None: + dup = str(options_data.dup) iface1 = link_data.iface1 if iface1 is None: iface1 = InterfaceData() @@ -361,20 +362,20 @@ class CoreHandler(socketserver.BaseRequestHandler): [ (LinkTlvs.N1_NUMBER, link_data.node1_id), (LinkTlvs.N2_NUMBER, link_data.node2_id), - (LinkTlvs.DELAY, link_data.delay), - (LinkTlvs.BANDWIDTH, link_data.bandwidth), + (LinkTlvs.DELAY, options_data.delay), + (LinkTlvs.BANDWIDTH, options_data.bandwidth), (LinkTlvs.LOSS, loss), (LinkTlvs.DUP, dup), - (LinkTlvs.JITTER, link_data.jitter), - (LinkTlvs.MER, link_data.mer), - (LinkTlvs.BURST, link_data.burst), - (LinkTlvs.MBURST, link_data.mburst), + (LinkTlvs.JITTER, options_data.jitter), + (LinkTlvs.MER, options_data.mer), + (LinkTlvs.BURST, options_data.burst), + (LinkTlvs.MBURST, options_data.mburst), (LinkTlvs.TYPE, link_data.link_type.value), - (LinkTlvs.GUI_ATTRIBUTES, link_data.gui_attributes), - (LinkTlvs.UNIDIRECTIONAL, link_data.unidirectional), - (LinkTlvs.EMULATION_ID, link_data.emulation_id), + (LinkTlvs.GUI_ATTRIBUTES, options_data.gui_attributes), + (LinkTlvs.UNIDIRECTIONAL, options_data.unidirectional), + (LinkTlvs.EMULATION_ID, options_data.emulation_id), (LinkTlvs.NETWORK_ID, link_data.network_id), - (LinkTlvs.KEY, link_data.key), + (LinkTlvs.KEY, options_data.key), (LinkTlvs.IFACE1_NUMBER, iface1.id), (LinkTlvs.IFACE1_IP4, iface1.ip4), (LinkTlvs.IFACE1_IP4_MASK, iface1.ip4_mask), @@ -387,7 +388,7 @@ class CoreHandler(socketserver.BaseRequestHandler): (LinkTlvs.IFACE2_MAC, iface2.mac), (LinkTlvs.IFACE2_IP6, iface2.ip6), (LinkTlvs.IFACE2_IP6_MASK, iface2.ip6_mask), - (LinkTlvs.OPAQUE, link_data.opaque), + (LinkTlvs.OPAQUE, options_data.opaque), ], ) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index c08a70f0..0c263135 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -142,36 +142,27 @@ class LinkOptions: burst: int = None mburst: int = None gui_attributes: str = None - unidirectional: bool = None + unidirectional: int = None emulation_id: int = None - network_id: int = None key: int = None opaque: str = None @dataclass class LinkData: + """ + Represents all data associated with a link. + """ + message_type: MessageFlags = None + link_type: LinkTypes = None label: str = None node1_id: int = None node2_id: int = None - delay: float = None - bandwidth: float = None - loss: float = None - dup: float = None - jitter: float = None - mer: float = None - burst: float = None - mburst: float = None - link_type: LinkTypes = None - gui_attributes: str = None - unidirectional: int = None - emulation_id: int = None - network_id: int = None - key: int = None iface1: InterfaceData = None iface2: InterfaceData = None - opaque: str = None + options: LinkOptions = LinkOptions() + network_id: int = None color: str = None diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 3c754aa2..a6e4f147 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -224,9 +224,7 @@ class NodeBase(abc.ABC): def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ - Build CORE Link data for this object. There is no default - method for PyCoreObjs as PyCoreNodes do not implement this but - PyCoreNets do. + Build link data for this node. :param flags: message flags :return: list of link data @@ -1108,35 +1106,27 @@ class CoreNetworkBase(NodeBase): iface2.ip6 = ip iface2.ip6_mask = mask + options_data = iface.get_link_options(unidirectional) link_data = LinkData( message_type=flags, node1_id=self.id, node2_id=linked_node.id, link_type=self.linktype, - unidirectional=unidirectional, iface2=iface2, - delay=iface.getparam("delay"), - bandwidth=iface.getparam("bw"), - dup=iface.getparam("duplicate"), - jitter=iface.getparam("jitter"), - loss=iface.getparam("loss"), + options=options_data, ) all_links.append(link_data) if not uni: continue iface.swapparams("_params_up") + options_data = iface.get_link_options(unidirectional) link_data = LinkData( message_type=MessageFlags.NONE, node1_id=linked_node.id, node2_id=self.id, link_type=self.linktype, - unidirectional=1, - delay=iface.getparam("delay"), - bandwidth=iface.getparam("bw"), - dup=iface.getparam("duplicate"), - jitter=iface.getparam("jitter"), - loss=iface.getparam("loss"), + options=options_data, ) iface.swapparams("_params_up") all_links.append(link_data) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index dc16517f..1fb8b894 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -7,6 +7,7 @@ import time from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple from core import utils +from core.emulator.data import LinkOptions from core.emulator.enumerations import MessageFlags, TransportType from core.errors import CoreCommandError from core.nodes.netclient import LinuxNetClient, get_net_client @@ -169,6 +170,34 @@ class CoreInterface: """ return self._params.get(key) + def get_link_options(self, unidirectional: int) -> LinkOptions: + """ + Get currently set params as link options. + + :param unidirectional: unidirectional setting + :return: link options + """ + delay = self.getparam("delay") + if delay is not None: + delay = int(delay) + bandwidth = self.getparam("bw") + if bandwidth is not None: + bandwidth = int(bandwidth) + dup = self.getparam("duplicate") + if dup is not None: + dup = int(dup) + jitter = self.getparam("jitter") + if jitter is not None: + jitter = int(jitter) + return LinkOptions( + delay=delay, + bandwidth=bandwidth, + dup=dup, + jitter=jitter, + loss=self.getparam("loss"), + unidirectional=unidirectional, + ) + def getparams(self) -> List[Tuple[str, float]]: """ Return (key, value) pairs for parameters. diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index b2f6bbf3..972d54f9 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -884,11 +884,12 @@ class PtpNet(CoreNetwork): :return: list of link data """ all_links = [] - if len(self.ifaces) != 2: return all_links - iface1, iface2 = self.get_ifaces() + ifaces = self.get_ifaces() + iface1 = ifaces[0] + iface2 = ifaces[1] unidirectional = 0 if iface1.getparams() != iface2.getparams(): unidirectional = 1 @@ -919,19 +920,15 @@ class PtpNet(CoreNetwork): iface2.ip6 = ip iface2.ip6_mask = mask + options_data = iface1.get_link_options(unidirectional) link_data = LinkData( message_type=flags, node1_id=iface1.node.id, node2_id=iface2.node.id, link_type=self.linktype, - unidirectional=unidirectional, - delay=iface1.getparam("delay"), - bandwidth=iface1.getparam("bw"), - loss=iface1.getparam("loss"), - dup=iface1.getparam("duplicate"), - jitter=iface1.getparam("jitter"), iface1=iface1_data, iface2=iface2_data, + options=options_data, ) all_links.append(link_data) @@ -940,19 +937,15 @@ class PtpNet(CoreNetwork): if unidirectional: iface1_data = InterfaceData(id=iface2.node.get_iface_id(iface2)) iface2_data = InterfaceData(id=iface1.node.get_iface_id(iface1)) + options_data = iface2.get_link_options(unidirectional) link_data = LinkData( message_type=MessageFlags.NONE, link_type=self.linktype, node1_id=iface2.node.id, node2_id=iface1.node.id, - delay=iface2.getparam("delay"), - bandwidth=iface2.getparam("bw"), - loss=iface2.getparam("loss"), - dup=iface2.getparam("duplicate"), - jitter=iface2.getparam("jitter"), - unidirectional=1, iface1=iface1_data, iface2=iface2_data, + options=options_data, ) all_links.append(link_data) return all_links diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 1f92502c..4febe71f 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -537,22 +537,22 @@ class CoreXmlWriter: is_node1_wireless = isinstance(node1, (WlanNode, EmaneNet)) is_node2_wireless = isinstance(node2, (WlanNode, EmaneNet)) if not any([is_node1_wireless, is_node2_wireless]): + options_data = link_data.options options = etree.Element("options") - add_attribute(options, "delay", link_data.delay) - add_attribute(options, "bandwidth", link_data.bandwidth) - add_attribute(options, "loss", link_data.loss) - add_attribute(options, "dup", link_data.dup) - add_attribute(options, "jitter", link_data.jitter) - add_attribute(options, "mer", link_data.mer) - add_attribute(options, "burst", link_data.burst) - add_attribute(options, "mburst", link_data.mburst) - add_attribute(options, "type", link_data.link_type) - add_attribute(options, "gui_attributes", link_data.gui_attributes) - add_attribute(options, "unidirectional", link_data.unidirectional) - add_attribute(options, "emulation_id", link_data.emulation_id) + add_attribute(options, "delay", options_data.delay) + add_attribute(options, "bandwidth", options_data.bandwidth) + add_attribute(options, "loss", options_data.loss) + add_attribute(options, "dup", options_data.dup) + add_attribute(options, "jitter", options_data.jitter) + add_attribute(options, "mer", options_data.mer) + add_attribute(options, "burst", options_data.burst) + add_attribute(options, "mburst", options_data.mburst) + add_attribute(options, "gui_attributes", options_data.gui_attributes) + add_attribute(options, "unidirectional", options_data.unidirectional) + add_attribute(options, "emulation_id", options_data.emulation_id) add_attribute(options, "network_id", link_data.network_id) - add_attribute(options, "key", link_data.key) - add_attribute(options, "opaque", link_data.opaque) + add_attribute(options, "key", options_data.key) + add_attribute(options, "opaque", options_data.opaque) if options.items(): link_element.append(options) @@ -940,7 +940,6 @@ class CoreXmlReader: options.loss = get_float(options_element, "per") options.unidirectional = get_int(options_element, "unidirectional") options.emulation_id = get_int(options_element, "emulation_id") - options.network_id = get_int(options_element, "network_id") options.opaque = options_element.get("opaque") options.gui_attributes = options_element.get("gui_attributes") diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index b2a1c312..cff7cd85 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -590,7 +590,7 @@ class TestGrpc: session.add_link(node.id, switch.id, iface) options = core_pb2.LinkOptions(bandwidth=30000) link = switch.all_link_data()[0] - assert options.bandwidth != link.bandwidth + assert options.bandwidth != link.options.bandwidth # then with client.context_connect(): @@ -601,7 +601,7 @@ class TestGrpc: # then assert response.result is True link = switch.all_link_data()[0] - assert options.bandwidth == link.bandwidth + assert options.bandwidth == link.options.bandwidth def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # given diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index c413295a..8f01a2bf 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -201,7 +201,7 @@ class TestGui: all_links = switch_node.all_link_data() assert len(all_links) == 1 link = all_links[0] - assert link.bandwidth is None + assert link.options.bandwidth is None bandwidth = 50000 message = coreapi.CoreLinkMessage.create( @@ -219,7 +219,7 @@ class TestGui: all_links = switch_node.all_link_data() assert len(all_links) == 1 link = all_links[0] - assert link.bandwidth == bandwidth + assert link.options.bandwidth == bandwidth def test_link_delete_node_to_node(self, coretlv: CoreHandler): node1_id = 1 diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index d81fe471..91b598f3 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -347,11 +347,11 @@ class TestXml: node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert options.loss == link.loss - assert options.bandwidth == link.bandwidth - assert options.jitter == link.jitter - assert options.delay == link.delay - assert options.dup == link.dup + assert options.loss == link.options.loss + assert options.bandwidth == link.options.bandwidth + assert options.jitter == link.options.jitter + assert options.delay == link.options.delay + assert options.dup == link.options.dup def test_link_options_ptp( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -414,11 +414,11 @@ class TestXml: node = session.nodes[node_id] links += node.all_link_data() link = links[0] - assert options.loss == link.loss - assert options.bandwidth == link.bandwidth - assert options.jitter == link.jitter - assert options.delay == link.delay - assert options.dup == link.dup + assert options.loss == link.options.loss + assert options.bandwidth == link.options.bandwidth + assert options.jitter == link.options.jitter + assert options.delay == link.options.delay + assert options.dup == link.options.dup def test_link_options_bidirectional( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -494,13 +494,13 @@ class TestXml: assert len(links) == 2 link1 = links[0] link2 = links[1] - assert options1.bandwidth == link1.bandwidth - assert options1.delay == link1.delay - assert options1.loss == link1.loss - assert options1.dup == link1.dup - assert options1.jitter == link1.jitter - assert options2.bandwidth == link2.bandwidth - assert options2.delay == link2.delay - assert options2.loss == link2.loss - assert options2.dup == link2.dup - assert options2.jitter == link2.jitter + assert options1.bandwidth == link1.options.bandwidth + assert options1.delay == link1.options.delay + assert options1.loss == link1.options.loss + assert options1.dup == link1.options.dup + assert options1.jitter == link1.options.jitter + assert options2.bandwidth == link2.options.bandwidth + assert options2.delay == link2.options.delay + assert options2.loss == link2.options.loss + assert options2.dup == link2.options.dup + assert options2.jitter == link2.options.jitter From 351b99aae003b3852240dd9effb825677252574d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 21:53:12 -0700 Subject: [PATCH 059/146] daemon: renamed LinkData.link_type to LinkData.type and removed LinkOptions.type to remove redundant information, link_type param added to session.add_link, delete_link, and update_link functions --- daemon/core/api/grpc/grpcutils.py | 16 ++++++++-------- daemon/core/api/grpc/server.py | 8 +++++--- daemon/core/api/tlv/corehandlers.py | 14 +++++++++----- daemon/core/emane/emanemanager.py | 2 +- daemon/core/emane/linkmonitor.py | 2 +- daemon/core/emulator/data.py | 3 +-- daemon/core/emulator/session.py | 10 +++++++--- daemon/core/location/mobility.py | 2 +- daemon/core/nodes/base.py | 4 ++-- daemon/core/nodes/network.py | 4 ++-- 10 files changed, 37 insertions(+), 28 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 5213a835..a8e0a792 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -78,7 +78,7 @@ def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData: def add_link_data( link_proto: core_pb2.Link -) -> Tuple[InterfaceData, InterfaceData, LinkOptions]: +) -> Tuple[InterfaceData, InterfaceData, LinkOptions, LinkTypes]: """ Convert link proto to link interfaces and options data. @@ -88,7 +88,7 @@ def add_link_data( iface1_data = link_iface(link_proto.iface1) iface2_data = link_iface(link_proto.iface2) link_type = LinkTypes(link_proto.type) - options = LinkOptions(type=link_type) + options = LinkOptions() options_data = link_proto.options if options_data: options.delay = options_data.delay @@ -102,7 +102,7 @@ def add_link_data( options.unidirectional = options_data.unidirectional options.key = options_data.key options.opaque = options_data.opaque - return iface1_data, iface2_data, options + return iface1_data, iface2_data, options, link_type def create_nodes( @@ -142,8 +142,8 @@ def create_links( for link_proto in link_protos: node1_id = link_proto.node1_id node2_id = link_proto.node2_id - iface1, iface2, options = add_link_data(link_proto) - args = (node1_id, node2_id, iface1, iface2, options) + iface1, iface2, options, link_type = add_link_data(link_proto) + args = (node1_id, node2_id, iface1, iface2, options, link_type) funcs.append((session.add_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -166,8 +166,8 @@ def edit_links( for link_proto in link_protos: node1_id = link_proto.node1_id node2_id = link_proto.node2_id - iface1, iface2, options = add_link_data(link_proto) - args = (node1_id, node2_id, iface1.id, iface2.id, options) + iface1, iface2, options, link_type = add_link_data(link_proto) + args = (node1_id, node2_id, iface1.id, iface2.id, options, link_type) funcs.append((session.update_link, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) @@ -350,7 +350,7 @@ def convert_link(link_data: LinkData) -> core_pb2.Link: iface2 = convert_iface(link_data.iface2) options = convert_link_options(link_data.options) return core_pb2.Link( - type=link_data.link_type.value, + type=link_data.type.value, node1_id=link_data.node1_id, node2_id=link_data.node2_id, iface1=iface1, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 1be60116..b9e0e0aa 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -847,9 +847,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): node2_id = request.link.node2_id self.get_node(session, node1_id, context, NodeBase) self.get_node(session, node2_id, context, NodeBase) - iface1_data, iface2_data, options = grpcutils.add_link_data(request.link) + iface1_data, iface2_data, options, link_type = grpcutils.add_link_data( + request.link + ) node1_iface, node2_iface = session.add_link( - node1_id, node2_id, iface1_data, iface2_data, options=options + node1_id, node2_id, iface1_data, iface2_data, options, link_type ) iface1_proto = None iface2_proto = None @@ -1522,7 +1524,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): color = session.get_link_color(emane1.id) link = LinkData( message_type=flag, - link_type=LinkTypes.WIRELESS, + type=LinkTypes.WIRELESS, node1_id=node1.id, node2_id=node2.id, network_id=emane1.id, diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 3a4351f1..631cd538 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -370,7 +370,7 @@ class CoreHandler(socketserver.BaseRequestHandler): (LinkTlvs.MER, options_data.mer), (LinkTlvs.BURST, options_data.burst), (LinkTlvs.MBURST, options_data.mburst), - (LinkTlvs.TYPE, link_data.link_type.value), + (LinkTlvs.TYPE, link_data.type.value), (LinkTlvs.GUI_ATTRIBUTES, options_data.gui_attributes), (LinkTlvs.UNIDIRECTIONAL, options_data.unidirectional), (LinkTlvs.EMULATION_ID, options_data.emulation_id), @@ -784,7 +784,7 @@ class CoreHandler(socketserver.BaseRequestHandler): link_type_value = message.get_tlv(LinkTlvs.TYPE.value) if link_type_value is not None: link_type = LinkTypes(link_type_value) - options = LinkOptions(type=link_type) + options = LinkOptions() options.delay = message.get_tlv(LinkTlvs.DELAY.value) options.bandwidth = message.get_tlv(LinkTlvs.BANDWIDTH.value) options.loss = message.get_tlv(LinkTlvs.LOSS.value) @@ -801,12 +801,16 @@ class CoreHandler(socketserver.BaseRequestHandler): options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) if message.flags & MessageFlags.ADD.value: - self.session.add_link(node1_id, node2_id, iface1_data, iface2_data, options) + self.session.add_link( + node1_id, node2_id, iface1_data, iface2_data, options, link_type + ) elif message.flags & MessageFlags.DELETE.value: - self.session.delete_link(node1_id, node2_id, iface1_data.id, iface2_data.id) + self.session.delete_link( + node1_id, node2_id, iface1_data.id, iface2_data.id, link_type + ) else: self.session.update_link( - node1_id, node2_id, iface1_data.id, iface2_data.id, options + node1_id, node2_id, iface1_data.id, iface2_data.id, options, link_type ) return () diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 58b85080..fc561b5f 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -500,10 +500,10 @@ class EmaneManager(ModelManager): color = self.session.get_link_color(emane1.id) return LinkData( message_type=flags, + type=LinkTypes.WIRELESS, node1_id=node1.id, node2_id=node2.id, network_id=emane1.id, - link_type=LinkTypes.WIRELESS, color=color, ) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index 097080c3..1a9ac41a 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -305,11 +305,11 @@ class EmaneLinkMonitor: color = self.emane_manager.session.get_link_color(emane_id) link_data = LinkData( message_type=message_type, + type=LinkTypes.WIRELESS, label=label, node1_id=node1, node2_id=node2, network_id=emane_id, - link_type=LinkTypes.WIRELESS, color=color, ) self.emane_manager.session.broadcast_link(link_data) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 0c263135..899d32ae 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -132,7 +132,6 @@ class LinkOptions: Options for creating and updating links within core. """ - type: LinkTypes = LinkTypes.WIRED delay: int = None bandwidth: int = None loss: float = None @@ -155,7 +154,7 @@ class LinkData: """ message_type: MessageFlags = None - link_type: LinkTypes = None + type: LinkTypes = LinkTypes.WIRED label: str = None node1_id: int = None node2_id: int = None diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index f2514e67..814c89d9 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -224,6 +224,7 @@ class Session: iface1_data: InterfaceData = None, iface2_data: InterfaceData = None, options: LinkOptions = None, + link_type: LinkTypes = LinkTypes.WIRED, ) -> Tuple[CoreInterface, CoreInterface]: """ Add a link between nodes. @@ -236,6 +237,7 @@ class Session: data, defaults to none :param options: data for creating link, defaults to no options + :param link_type: type of link to add :return: tuple of created core interfaces, depending on link """ if not options: @@ -246,7 +248,7 @@ class Session: iface2 = None # wireless link - if options.type == LinkTypes.WIRELESS: + if link_type == LinkTypes.WIRELESS: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): self._link_wireless(node1, node2, connect=True) else: @@ -371,6 +373,7 @@ class Session: iface1_id: int = None, iface2_id: int = None, options: LinkOptions = None, + link_type: LinkTypes = LinkTypes.WIRED, ) -> None: """ Update link information between nodes. @@ -380,6 +383,7 @@ class Session: :param iface1_id: interface id for node one :param iface2_id: interface id for node two :param options: data to update link with + :param link_type: type of link to update :return: nothing :raises core.CoreError: when updating a wireless type link, when there is a unknown link between networks @@ -390,7 +394,7 @@ class Session: node2 = self.get_node(node2_id, NodeBase) logging.info( "update link(%s) node(%s):interface(%s) node(%s):interface(%s)", - options.type.name, + link_type.name, node1.name, iface1_id, node2.name, @@ -398,7 +402,7 @@ class Session: ) # wireless link - if options.type == LinkTypes.WIRELESS: + if link_type == LinkTypes.WIRELESS: raise CoreError("cannot update wireless link") else: if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 91a8baae..9bb2966e 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -487,10 +487,10 @@ class BasicRangeModel(WirelessModel): color = self.session.get_link_color(self.wlan.id) return LinkData( message_type=message_type, + type=LinkTypes.WIRELESS, node1_id=iface1.node.id, node2_id=iface2.node.id, network_id=self.wlan.id, - link_type=LinkTypes.WIRELESS, color=color, ) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index a6e4f147..97164cb6 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1109,9 +1109,9 @@ class CoreNetworkBase(NodeBase): options_data = iface.get_link_options(unidirectional) link_data = LinkData( message_type=flags, + type=self.linktype, node1_id=self.id, node2_id=linked_node.id, - link_type=self.linktype, iface2=iface2, options=options_data, ) @@ -1123,9 +1123,9 @@ class CoreNetworkBase(NodeBase): options_data = iface.get_link_options(unidirectional) link_data = LinkData( message_type=MessageFlags.NONE, + type=self.linktype, node1_id=linked_node.id, node2_id=self.id, - link_type=self.linktype, options=options_data, ) iface.swapparams("_params_up") diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 972d54f9..04d4e8f8 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -923,9 +923,9 @@ class PtpNet(CoreNetwork): options_data = iface1.get_link_options(unidirectional) link_data = LinkData( message_type=flags, + type=self.linktype, node1_id=iface1.node.id, node2_id=iface2.node.id, - link_type=self.linktype, iface1=iface1_data, iface2=iface2_data, options=options_data, @@ -940,7 +940,7 @@ class PtpNet(CoreNetwork): options_data = iface2.get_link_options(unidirectional) link_data = LinkData( message_type=MessageFlags.NONE, - link_type=self.linktype, + type=self.linktype, node1_id=iface2.node.id, node2_id=iface1.node.id, iface1=iface1_data, From a1734c3bc0cb8d4eaa17c414db72a3835d464ab7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 22:05:36 -0700 Subject: [PATCH 060/146] grpc: updated Interface proto fields to be more consistent with code, ip4mask to ip4_mask, ip6mask to ip6_mask, netid to net_id, flowid to flow_id --- daemon/core/api/grpc/client.py | 4 ++-- daemon/core/api/grpc/grpcutils.py | 26 +++++++++++++------------- daemon/core/gui/coreclient.py | 7 ++++++- daemon/core/gui/dialogs/nodeconfig.py | 20 ++++++++++---------- daemon/core/gui/graph/edges.py | 4 ++-- daemon/core/gui/interface.py | 6 +++--- daemon/proto/core/api/grpc/core.proto | 8 ++++---- 7 files changed, 40 insertions(+), 35 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 68bfc502..db908e05 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -127,9 +127,9 @@ class InterfaceHelper: id=iface_id, name=iface_data.name, ip4=iface_data.ip4, - ip4mask=iface_data.ip4_mask, + ip4_mask=iface_data.ip4_mask, ip6=iface_data.ip6, - ip6mask=iface_data.ip6_mask, + ip6_mask=iface_data.ip6_mask, mac=iface_data.mac, ) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index a8e0a792..9d26e4cf 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -69,9 +69,9 @@ def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData: name=name, mac=mac, ip4=ip4, - ip4_mask=iface_proto.ip4mask, + ip4_mask=iface_proto.ip4_mask, ip6=ip6, - ip6_mask=iface_proto.ip6mask, + ip6_mask=iface_proto.ip6_mask, ) return iface_data @@ -313,9 +313,9 @@ def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface: name=iface_data.name, mac=iface_data.mac, ip4=iface_data.ip4, - ip4mask=iface_data.ip4_mask, + ip4_mask=iface_data.ip4_mask, ip6=iface_data.ip6, - ip6mask=iface_data.ip6_mask, + ip6_mask=iface_data.ip6_mask, ) @@ -449,30 +449,30 @@ def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: if iface.net: net_id = iface.net.id ip4 = None - ip4mask = None + ip4_mask = None ip6 = None - ip6mask = None + ip6_mask = None for addr in iface.addrlist: network = netaddr.IPNetwork(addr) mask = network.prefixlen ip = str(network.ip) if netaddr.valid_ipv4(ip) and not ip4: ip4 = ip - ip4mask = mask + ip4_mask = mask elif netaddr.valid_ipv6(ip) and not ip6: ip6 = ip - ip6mask = mask + ip6_mask = mask return core_pb2.Interface( id=iface.node_id, - netid=net_id, + net_id=net_id, name=iface.name, - mac=str(iface.hwaddr), + mac=iface.hwaddr, mtu=iface.mtu, - flowid=iface.flow_id, + flow_id=iface.flow_id, ip4=ip4, - ip4mask=ip4mask, + ip4_mask=ip4_mask, ip6=ip6, - ip6mask=ip6mask, + ip6_mask=ip6_mask, ) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 3be58e17..8b0c423c 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -834,7 +834,12 @@ class CoreClient: iface_id = canvas_node.next_iface_id() name = f"eth{iface_id}" iface = core_pb2.Interface( - id=iface_id, name=name, ip4=ip4, ip4mask=ip4_mask, ip6=ip6, ip6mask=ip6_mask + id=iface_id, + name=name, + ip4=ip4, + ip4_mask=ip4_mask, + ip6=ip6, + ip6_mask=ip6_mask, ) logging.info( "create node(%s) interface(%s) IPv4(%s) IPv6(%s)", diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 29ce2010..cec9e9f9 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -248,7 +248,7 @@ class NodeConfigDialog(Dialog): label.grid(row=row, column=0, padx=PADX, pady=PADY) ip4_net = "" if iface.ip4: - ip4_net = f"{iface.ip4}/{iface.ip4mask}" + ip4_net = f"{iface.ip4}/{iface.ip4_mask}" ip4 = tk.StringVar(value=ip4_net) entry = ttk.Entry(tab, textvariable=ip4, state=state) entry.grid(row=row, column=1, columnspan=2, sticky="ew") @@ -258,7 +258,7 @@ class NodeConfigDialog(Dialog): label.grid(row=row, column=0, padx=PADX, pady=PADY) ip6_net = "" if iface.ip6: - ip6_net = f"{iface.ip6}/{iface.ip6mask}" + ip6_net = f"{iface.ip6}/{iface.ip6_mask}" ip6 = tk.StringVar(value=ip6_net) entry = ttk.Entry(tab, textvariable=ip6, state=state) entry.grid(row=row, column=1, columnspan=2, sticky="ew") @@ -318,12 +318,12 @@ class NodeConfigDialog(Dialog): error = True break if ip4_net: - ip4, ip4mask = ip4_net.split("/") - ip4mask = int(ip4mask) + ip4, ip4_mask = ip4_net.split("/") + ip4_mask = int(ip4_mask) else: - ip4, ip4mask = "", 0 + ip4, ip4_mask = "", 0 iface.ip4 = ip4 - iface.ip4mask = ip4mask + iface.ip4_mask = ip4_mask # validate ip6 ip6_net = data.ip6.get() @@ -331,12 +331,12 @@ class NodeConfigDialog(Dialog): error = True break if ip6_net: - ip6, ip6mask = ip6_net.split("/") - ip6mask = int(ip6mask) + ip6, ip6_mask = ip6_net.split("/") + ip6_mask = int(ip6_mask) else: - ip6, ip6mask = "", 0 + ip6, ip6_mask = "", 0 iface.ip6 = ip6 - iface.ip6mask = ip6mask + iface.ip6_mask = ip6_mask mac = data.mac.get() auto_mac = data.is_auto.get() diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 152e1a2f..ac637b28 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -289,10 +289,10 @@ class CanvasEdge(Edge): label = f"{iface.name}" if iface.ip4 and self.canvas.show_ip4s.get(): label = f"{label}\n" if label else "" - label += f"{iface.ip4}/{iface.ip4mask}" + label += f"{iface.ip4}/{iface.ip4_mask}" if iface.ip6 and self.canvas.show_ip6s.get(): label = f"{label}\n" if label else "" - label += f"{iface.ip6}/{iface.ip6mask}" + label += f"{iface.ip6}/{iface.ip6_mask}" return label def create_node_labels(self) -> Tuple[str, str]: diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 14cba024..6c82ca51 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: def get_index(iface: "core_pb2.Interface") -> Optional[int]: if not iface.ip4: return None - net = netaddr.IPNetwork(f"{iface.ip4}/{iface.ip4mask}") + net = netaddr.IPNetwork(f"{iface.ip4}/{iface.ip4_mask}") ip_value = net.value cidr_value = net.cidr.value return ip_value - cidr_value @@ -153,10 +153,10 @@ class InterfaceManager: def get_subnets(self, iface: "core_pb2.Interface") -> Subnets: ip4_subnet = self.ip4_subnets if iface.ip4: - ip4_subnet = IPNetwork(f"{iface.ip4}/{iface.ip4mask}").cidr + ip4_subnet = IPNetwork(f"{iface.ip4}/{iface.ip4_mask}").cidr ip6_subnet = self.ip6_subnets if iface.ip6: - ip6_subnet = IPNetwork(f"{iface.ip6}/{iface.ip6mask}").cidr + ip6_subnet = IPNetwork(f"{iface.ip6}/{iface.ip6_mask}").cidr subnets = Subnets(ip4_subnet, ip6_subnet) return self.used_subnets.get(subnets.key(), subnets) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index f691621a..2819c5ea 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -732,11 +732,11 @@ message Interface { string name = 2; string mac = 3; string ip4 = 4; - int32 ip4mask = 5; + int32 ip4_mask = 5; string ip6 = 6; - int32 ip6mask = 7; - int32 netid = 8; - int32 flowid = 9; + int32 ip6_mask = 7; + int32 net_id = 8; + int32 flow_id = 9; int32 mtu = 10; } From f4671ab2b894c82693484e5f2bcb52a9c707529e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 23:25:26 -0700 Subject: [PATCH 061/146] daemon: refactored usages of hwaddr to mac and be consistent everywhere --- daemon/core/api/grpc/grpcutils.py | 2 +- daemon/core/nodes/base.py | 22 +++++++++++----------- daemon/core/nodes/interface.py | 14 +++++++------- daemon/core/nodes/network.py | 4 ++-- daemon/core/nodes/physical.py | 18 +++++++++--------- daemon/core/services/xorp.py | 4 ++-- daemon/core/xml/emanexml.py | 6 +++--- daemon/tests/test_nodes.py | 10 +++++----- 8 files changed, 40 insertions(+), 40 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 9d26e4cf..6f2911a4 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -466,7 +466,7 @@ def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: id=iface.node_id, net_id=net_id, name=iface.name, - mac=iface.hwaddr, + mac=iface.mac, mtu=iface.mtu, flow_id=iface.flow_id, ip4=ip4, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 97164cb6..97da63a4 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -731,9 +731,9 @@ class CoreNode(CoreNodeBase): flow_id = self.node_net_client.get_ifindex(veth.name) veth.flow_id = int(flow_id) logging.debug("interface flow index: %s - %s", veth.name, veth.flow_id) - hwaddr = self.node_net_client.get_mac(veth.name) - logging.debug("interface mac: %s - %s", veth.name, hwaddr) - veth.sethwaddr(hwaddr) + mac = self.node_net_client.get_mac(veth.name) + logging.debug("interface mac: %s - %s", veth.name, mac) + veth.set_mac(mac) try: # add network interface to the node. If unsuccessful, destroy the @@ -775,20 +775,20 @@ class CoreNode(CoreNodeBase): return iface_id - def sethwaddr(self, iface_id: int, addr: str) -> None: + def set_mac(self, iface_id: int, mac: str) -> None: """ Set hardware address for an interface. :param iface_id: id of interface to set hardware address for - :param addr: hardware address to set + :param mac: mac address to set :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - addr = utils.validate_mac(addr) + mac = utils.validate_mac(mac) iface = self.get_iface(iface_id) - iface.sethwaddr(addr) + iface.set_mac(mac) if self.up: - self.node_net_client.device_mac(iface.name, addr) + self.node_net_client.device_mac(iface.name, mac) def addaddr(self, iface_id: int, addr: str) -> None: """ @@ -857,14 +857,14 @@ class CoreNode(CoreNodeBase): # save addresses with the interface now self.attachnet(iface_id, net) iface = self.get_iface(iface_id) - iface.sethwaddr(iface_data.mac) + iface.set_mac(iface_data.mac) for address in addresses: iface.addaddr(address) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) if iface_data.mac: - self.sethwaddr(iface_id, iface_data.mac) + self.set_mac(iface_id, iface_data.mac) for address in addresses: self.addaddr(iface_id, address) self.ifup(iface_id) @@ -1094,7 +1094,7 @@ class CoreNetworkBase(NodeBase): unidirectional = 1 iface2 = InterfaceData( - id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.hwaddr + id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.mac ) for address in iface.addrlist: ip, _sep, mask = address.partition("/") diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 1fb8b894..287723a7 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -53,7 +53,7 @@ class CoreInterface: self.othernet: Optional[CoreNetworkBase] = None self._params: Dict[str, float] = {} self.addrlist: List[str] = [] - self.hwaddr: Optional[str] = None + self.mac: Optional[str] = None # placeholder position hook self.poshook: Callable[[CoreInterface], None] = lambda x: None # used with EMANE @@ -150,16 +150,16 @@ class CoreInterface: """ self.addrlist.remove(addr) - def sethwaddr(self, addr: str) -> None: + def set_mac(self, mac: str) -> None: """ - Set hardware address. + Set mac address. - :param addr: hardware address to set to. + :param mac: mac address to set :return: nothing """ - if addr is not None: - addr = utils.validate_mac(addr) - self.hwaddr = addr + if mac is not None: + mac = utils.validate_mac(mac) + self.mac = mac def getparam(self, key: str) -> float: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 04d4e8f8..ef9456db 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -895,7 +895,7 @@ class PtpNet(CoreNetwork): unidirectional = 1 iface1_data = InterfaceData( - id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.hwaddr + id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.mac ) for address in iface1.addrlist: ip, _sep, mask = address.partition("/") @@ -908,7 +908,7 @@ class PtpNet(CoreNetwork): iface1.ip6_mask = mask iface2_data = InterfaceData( - id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.hwaddr + id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.mac ) for address in iface2.addrlist: ip, _sep, mask = address.partition("/") diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 36bcb267..0ce8946a 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -65,20 +65,20 @@ class PhysicalNode(CoreNodeBase): """ return sh - def sethwaddr(self, iface_id: int, addr: str) -> None: + def set_mac(self, iface_id: int, mac: str) -> None: """ - Set hardware address for an interface. + Set mac address for an interface. :param iface_id: index of interface to set hardware address for - :param addr: hardware address to set + :param mac: mac address to set :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - addr = utils.validate_mac(addr) + mac = utils.validate_mac(mac) iface = self.ifaces[iface_id] - iface.sethwaddr(addr) + iface.set_mac(mac) if self.up: - self.net_client.device_mac(iface.name, addr) + self.net_client.device_mac(iface.name, mac) def addaddr(self, iface_id: int, addr: str) -> None: """ @@ -111,7 +111,7 @@ class PhysicalNode(CoreNodeBase): self.net_client.delete_address(iface.name, addr) def adopt_iface( - self, iface: CoreInterface, iface_id: int, hwaddr: str, addrlist: List[str] + self, iface: CoreInterface, iface_id: int, mac: str, addrlist: List[str] ) -> None: """ When a link message is received linking this node to another part of @@ -126,8 +126,8 @@ class PhysicalNode(CoreNodeBase): self.net_client.device_down(iface.localname) self.net_client.device_name(iface.localname, iface.name) iface.localname = iface.name - if hwaddr: - self.sethwaddr(iface_id, hwaddr) + if mac: + self.set_mac(iface_id, mac) for addr in addrlist: self.addaddr(iface_id, addr) if self.up: diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 3dfef56a..10b4fd9f 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -69,7 +69,7 @@ class XorpRtrmgr(CoreService): """ helper for adding link-local address entries (required by OSPFv3) """ - cfg = "\t address %s {\n" % iface.hwaddr.tolinklocal() + cfg = "\t address %s {\n" % iface.mac.tolinklocal() cfg += "\t\tprefix-length: 64\n" cfg += "\t }\n" return cfg @@ -305,7 +305,7 @@ class XorpRipng(XorpService): for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - cfg += "\t\taddress %s {\n" % iface.hwaddr.tolinklocal() + cfg += "\t\taddress %s {\n" % iface.mac.tolinklocal() cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 4f511476..d716777b 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: from core.emane.emanemanager import EmaneManager from core.emane.emanemodel import EmaneModel -_hwaddr_prefix = "02:02" +_MAC_PREFIX = "02:02" def is_external(config: Dict[str, str]) -> bool: @@ -230,9 +230,9 @@ def build_node_platform_xml( platform_element.append(nem_element) node.setnemid(iface, nem_id) - macstr = _hwaddr_prefix + ":00:00:" + macstr = _MAC_PREFIX + ":00:00:" macstr += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" - iface.sethwaddr(macstr) + iface.set_mac(macstr) # increment nem id nem_id += 1 diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 327137d2..8af2e895 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -49,7 +49,7 @@ class TestNodes: with pytest.raises(CoreError): session.get_node(node.id, CoreNode) - def test_node_sethwaddr(self, session: Session): + def test_node_set_mac(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) @@ -58,12 +58,12 @@ class TestNodes: mac = "aa:aa:aa:ff:ff:ff" # when - node.sethwaddr(iface.node_id, mac) + node.set_mac(iface.node_id, mac) # then - assert iface.hwaddr == mac + assert iface.mac == mac - def test_node_sethwaddr_exception(self, session: Session): + def test_node_set_mac_exception(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) @@ -73,7 +73,7 @@ class TestNodes: # when with pytest.raises(CoreError): - node.sethwaddr(iface.node_id, mac) + node.set_mac(iface.node_id, mac) def test_node_addaddr(self, session: Session): # given From a64047e221cb83d3f27d9c3c75f4d81afbea4036 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Jun 2020 23:27:17 -0700 Subject: [PATCH 062/146] fixed issue with xorp service depending on old MacAddress class --- daemon/core/services/xorp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 10b4fd9f..776b1d16 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -69,7 +69,7 @@ class XorpRtrmgr(CoreService): """ helper for adding link-local address entries (required by OSPFv3) """ - cfg = "\t address %s {\n" % iface.mac.tolinklocal() + cfg = "\t address %s {\n" % netaddr.EUI(iface.mac).eui64() cfg += "\t\tprefix-length: 64\n" cfg += "\t }\n" return cfg @@ -305,7 +305,7 @@ class XorpRipng(XorpService): for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - cfg += "\t\taddress %s {\n" % iface.mac.tolinklocal() + cfg += "\t\taddress %s {\n" % netaddr.EUI(iface.mac).eui64() cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" From b92ff0586a6e04d1dae8a36f159e9f6449df7489 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 17 Jun 2020 22:43:13 -0700 Subject: [PATCH 063/146] daemon: renamed NodeData.node_type to type, removed NodeData/NodeOptions fields that were not being used for clarity --- daemon/core/api/grpc/grpcutils.py | 1 - daemon/core/api/tlv/corehandlers.py | 1 - daemon/core/api/tlv/dataconversion.py | 12 +-- daemon/core/emulator/data.py | 123 ++++++++++++-------------- daemon/core/emulator/session.py | 1 - daemon/core/nodes/base.py | 11 +-- daemon/proto/core/api/grpc/core.proto | 13 ++- 7 files changed, 70 insertions(+), 92 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 6f2911a4..7c517caf 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -34,7 +34,6 @@ def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOption name=node_proto.name, model=node_proto.model, icon=node_proto.icon, - opaque=node_proto.opaque, image=node_proto.image, services=node_proto.services, config_services=node_proto.config_services, diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 631cd538..981bdb15 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -722,7 +722,6 @@ class CoreHandler(socketserver.BaseRequestHandler): options.icon = message.get_tlv(NodeTlvs.ICON.value) options.canvas = message.get_tlv(NodeTlvs.CANVAS.value) - options.opaque = message.get_tlv(NodeTlvs.OPAQUE.value) options.server = message.get_tlv(NodeTlvs.EMULATION_SERVER.value) services = message.get_tlv(NodeTlvs.SERVICES.value) diff --git a/daemon/core/api/tlv/dataconversion.py b/daemon/core/api/tlv/dataconversion.py index cd10ef04..62b51d39 100644 --- a/daemon/core/api/tlv/dataconversion.py +++ b/daemon/core/api/tlv/dataconversion.py @@ -18,9 +18,6 @@ def convert_node(node_data): :param core.emulator.data.NodeData node_data: node data to convert :return: packed node message """ - session = None - if node_data.session is not None: - session = str(node_data.session) services = None if node_data.services is not None: services = "|".join([x for x in node_data.services]) @@ -28,25 +25,18 @@ def convert_node(node_data): coreapi.CoreNodeTlv, [ (NodeTlvs.NUMBER, node_data.id), - (NodeTlvs.TYPE, node_data.node_type.value), + (NodeTlvs.TYPE, node_data.type.value), (NodeTlvs.NAME, node_data.name), - (NodeTlvs.IP_ADDRESS, node_data.ip_address), - (NodeTlvs.MAC_ADDRESS, node_data.mac_address), - (NodeTlvs.IP6_ADDRESS, node_data.ip6_address), (NodeTlvs.MODEL, node_data.model), - (NodeTlvs.EMULATION_ID, node_data.emulation_id), (NodeTlvs.EMULATION_SERVER, node_data.server), - (NodeTlvs.SESSION, session), (NodeTlvs.X_POSITION, int(node_data.x_position)), (NodeTlvs.Y_POSITION, int(node_data.y_position)), (NodeTlvs.CANVAS, node_data.canvas), - (NodeTlvs.NETWORK_ID, node_data.network_id), (NodeTlvs.SERVICES, services), (NodeTlvs.LATITUDE, str(node_data.latitude)), (NodeTlvs.LONGITUDE, str(node_data.longitude)), (NodeTlvs.ALTITUDE, str(node_data.altitude)), (NodeTlvs.ICON, node_data.icon), - (NodeTlvs.OPAQUE, node_data.opaque), ], ) return coreapi.CoreNodeMessage.pack(node_data.message_type.value, tlv_data) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 899d32ae..1a7a6096 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -73,28 +73,71 @@ class FileData: @dataclass -class NodeData: - message_type: MessageFlags = None - id: int = None - node_type: NodeTypes = None +class NodeOptions: + """ + Options for creating and updating nodes within core. + """ + name: str = None - ip_address: str = None - mac_address: str = None - ip6_address: str = None - model: str = None - emulation_id: int = None + model: Optional[str] = "PC" + canvas: int = None + icon: str = None + services: List[str] = field(default_factory=list) + config_services: List[str] = field(default_factory=list) + x: float = None + y: float = None + lat: float = None + lon: float = None + alt: float = None server: str = None - session: int = None + image: str = None + emane: str = None + + def set_position(self, x: float, y: float) -> None: + """ + Convenience method for setting position. + + :param x: x position + :param y: y position + :return: nothing + """ + self.x = x + self.y = y + + def set_location(self, lat: float, lon: float, alt: float) -> None: + """ + Convenience method for setting location. + + :param lat: latitude + :param lon: longitude + :param alt: altitude + :return: nothing + """ + self.lat = lat + self.lon = lon + self.alt = alt + + +@dataclass +class NodeData: + """ + Used to represent nodes being broadcasted. + """ + + message_type: MessageFlags = None + type: NodeTypes = None + id: int = None + name: str = None + model: str = None + server: str = None + icon: str = None + canvas: int = None + services: List[str] = None x_position: float = None y_position: float = None - canvas: int = None - network_id: int = None - services: List[str] = None latitude: float = None longitude: float = None altitude: float = None - icon: str = None - opaque: str = None source: str = None @@ -158,10 +201,10 @@ class LinkData: label: str = None node1_id: int = None node2_id: int = None + network_id: int = None iface1: InterfaceData = None iface2: InterfaceData = None options: LinkOptions = LinkOptions() - network_id: int = None color: str = None @@ -259,51 +302,3 @@ class IpPrefixes: iface_data = self.gen_iface(node.id, name, mac) iface_data.id = node.next_iface_id() return iface_data - - -@dataclass -class NodeOptions: - """ - Options for creating and updating nodes within core. - """ - - name: str = None - model: Optional[str] = "PC" - canvas: int = None - icon: str = None - opaque: str = None - services: List[str] = field(default_factory=list) - config_services: List[str] = field(default_factory=list) - x: float = None - y: float = None - lat: float = None - lon: float = None - alt: float = None - emulation_id: int = None - server: str = None - image: str = None - emane: str = None - - def set_position(self, x: float, y: float) -> None: - """ - Convenience method for setting position. - - :param x: x position - :param y: y position - :return: nothing - """ - self.x = x - self.y = y - - def set_location(self, lat: float, lon: float, alt: float) -> None: - """ - Convenience method for setting location. - - :param lat: latitude - :param lon: longitude - :param alt: altitude - :return: nothing - """ - self.lat = lat - self.lon = lon - self.alt = alt diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 814c89d9..ccabeddb 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -523,7 +523,6 @@ class Session: # set node attributes node.icon = options.icon node.canvas = options.canvas - node.opaque = options.opaque # set node position and broadcast it self.set_node_position(node, options) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 97da63a4..0ecc9085 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -195,29 +195,26 @@ class NodeBase(abc.ABC): """ if self.apitype is None: return None - x, y, _ = self.getposition() model = self.type server = None if self.server is not None: server = self.server.name - services = [service.name for service in self.services] + services = [x.name for x in self.services] return NodeData( message_type=message_type, + type=self.apitype, id=self.id, - node_type=self.apitype, name=self.name, - emulation_id=self.id, + model=model, + server=server, canvas=self.canvas, icon=self.icon, - opaque=self.opaque, x_position=x, y_position=y, latitude=self.position.lat, longitude=self.position.lon, altitude=self.position.alt, - model=model, - server=server, services=services, source=source, ) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 2819c5ea..46e1da91 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -692,13 +692,12 @@ message Node { repeated string services = 6; string emane = 7; string icon = 8; - string opaque = 9; - string image = 10; - string server = 11; - repeated string config_services = 12; - Geo geo = 13; - string dir = 14; - string channel = 15; + string image = 9; + string server = 10; + repeated string config_services = 11; + Geo geo = 12; + string dir = 13; + string channel = 14; } message Link { From 5d34a2b752631580e4d5ca01daaf0714fc707e44 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 17 Jun 2020 22:59:50 -0700 Subject: [PATCH 064/146] daemon: removed opaque from NodeBase, since it is not used --- daemon/core/nodes/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 0ecc9085..378549ab 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -71,7 +71,6 @@ class NodeBase(abc.ABC): self.iface_id: int = 0 self.canvas: Optional[int] = None self.icon: Optional[str] = None - self.opaque: Optional[str] = None self.position: Position = Position() self.up: bool = False use_ovs = session.options.get_config("ovs") == "True" From 3d7d775bfbf5c673837baeecd25faa078e3906a6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 00:15:44 -0700 Subject: [PATCH 065/146] daemon: removed unused variables from LinkOptions --- daemon/core/api/grpc/grpcutils.py | 26 ++++++++++++-------------- daemon/core/api/grpc/server.py | 23 +++++++++++------------ daemon/core/api/tlv/corehandlers.py | 7 ------- daemon/core/emulator/data.py | 3 --- daemon/core/xml/corexml.py | 6 ------ 5 files changed, 23 insertions(+), 42 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 7c517caf..d95b7555 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -88,19 +88,18 @@ def add_link_data( iface2_data = link_iface(link_proto.iface2) link_type = LinkTypes(link_proto.type) options = LinkOptions() - options_data = link_proto.options - if options_data: - options.delay = options_data.delay - options.bandwidth = options_data.bandwidth - options.loss = options_data.loss - options.dup = options_data.dup - options.jitter = options_data.jitter - options.mer = options_data.mer - options.burst = options_data.burst - options.mburst = options_data.mburst - options.unidirectional = options_data.unidirectional - options.key = options_data.key - options.opaque = options_data.opaque + options_proto = link_proto.options + if options_proto: + options.delay = options_proto.delay + options.bandwidth = options_proto.bandwidth + options.loss = options_proto.loss + options.dup = options_proto.dup + options.jitter = options_proto.jitter + options.mer = options_proto.mer + options.burst = options_proto.burst + options.mburst = options_proto.mburst + options.unidirectional = options_proto.unidirectional + options.key = options_proto.key return iface1_data, iface2_data, options, link_type @@ -320,7 +319,6 @@ def convert_iface(iface_data: InterfaceData) -> core_pb2.Interface: def convert_link_options(options_data: LinkOptions) -> core_pb2.LinkOptions: return core_pb2.LinkOptions( - opaque=options_data.opaque, jitter=options_data.jitter, key=options_data.key, mburst=options_data.mburst, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index b9e0e0aa..1964b6e8 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -879,19 +879,18 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): node2_id = request.node2_id iface1_id = request.iface1_id iface2_id = request.iface2_id - options_data = request.options + options_proto = request.options options = LinkOptions( - delay=options_data.delay, - bandwidth=options_data.bandwidth, - loss=options_data.loss, - dup=options_data.dup, - jitter=options_data.jitter, - mer=options_data.mer, - burst=options_data.burst, - mburst=options_data.mburst, - unidirectional=options_data.unidirectional, - key=options_data.key, - opaque=options_data.opaque, + delay=options_proto.delay, + bandwidth=options_proto.bandwidth, + loss=options_proto.loss, + dup=options_proto.dup, + jitter=options_proto.jitter, + mer=options_proto.mer, + burst=options_proto.burst, + mburst=options_proto.mburst, + unidirectional=options_proto.unidirectional, + key=options_proto.key, ) session.update_link(node1_id, node2_id, iface1_id, iface2_id, options) return core_pb2.EditLinkResponse(result=True) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 981bdb15..379b739e 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -371,9 +371,7 @@ class CoreHandler(socketserver.BaseRequestHandler): (LinkTlvs.BURST, options_data.burst), (LinkTlvs.MBURST, options_data.mburst), (LinkTlvs.TYPE, link_data.type.value), - (LinkTlvs.GUI_ATTRIBUTES, options_data.gui_attributes), (LinkTlvs.UNIDIRECTIONAL, options_data.unidirectional), - (LinkTlvs.EMULATION_ID, options_data.emulation_id), (LinkTlvs.NETWORK_ID, link_data.network_id), (LinkTlvs.KEY, options_data.key), (LinkTlvs.IFACE1_NUMBER, iface1.id), @@ -388,7 +386,6 @@ class CoreHandler(socketserver.BaseRequestHandler): (LinkTlvs.IFACE2_MAC, iface2.mac), (LinkTlvs.IFACE2_IP6, iface2.ip6), (LinkTlvs.IFACE2_IP6_MASK, iface2.ip6_mask), - (LinkTlvs.OPAQUE, options_data.opaque), ], ) @@ -792,12 +789,8 @@ class CoreHandler(socketserver.BaseRequestHandler): options.mer = message.get_tlv(LinkTlvs.MER.value) options.burst = message.get_tlv(LinkTlvs.BURST.value) options.mburst = message.get_tlv(LinkTlvs.MBURST.value) - options.gui_attributes = message.get_tlv(LinkTlvs.GUI_ATTRIBUTES.value) options.unidirectional = message.get_tlv(LinkTlvs.UNIDIRECTIONAL.value) - options.emulation_id = message.get_tlv(LinkTlvs.EMULATION_ID.value) - options.network_id = message.get_tlv(LinkTlvs.NETWORK_ID.value) options.key = message.get_tlv(LinkTlvs.KEY.value) - options.opaque = message.get_tlv(LinkTlvs.OPAQUE.value) if message.flags & MessageFlags.ADD.value: self.session.add_link( diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 1a7a6096..c57f8b24 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -183,11 +183,8 @@ class LinkOptions: mer: int = None burst: int = None mburst: int = None - gui_attributes: str = None unidirectional: int = None - emulation_id: int = None key: int = None - opaque: str = None @dataclass diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 4febe71f..190cf8f7 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -547,12 +547,9 @@ class CoreXmlWriter: add_attribute(options, "mer", options_data.mer) add_attribute(options, "burst", options_data.burst) add_attribute(options, "mburst", options_data.mburst) - add_attribute(options, "gui_attributes", options_data.gui_attributes) add_attribute(options, "unidirectional", options_data.unidirectional) - add_attribute(options, "emulation_id", options_data.emulation_id) add_attribute(options, "network_id", link_data.network_id) add_attribute(options, "key", options_data.key) - add_attribute(options, "opaque", options_data.opaque) if options.items(): link_element.append(options) @@ -939,9 +936,6 @@ class CoreXmlReader: if options.loss is None: options.loss = get_float(options_element, "per") options.unidirectional = get_int(options_element, "unidirectional") - options.emulation_id = get_int(options_element, "emulation_id") - options.opaque = options_element.get("opaque") - options.gui_attributes = options_element.get("gui_attributes") if options.unidirectional == 1 and node_set in node_sets: logging.info("updating link node1(%s) node2(%s)", node1_id, node2_id) From 1702fe256f2ae153e65baabcb963c0d6638f1397 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 00:30:39 -0700 Subject: [PATCH 066/146] doc: updated refactored example in documentation --- docs/services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/services.md b/docs/services.md index 9f47ae48..2ce52e99 100644 --- a/docs/services.md +++ b/docs/services.md @@ -263,7 +263,7 @@ class MyService(CoreService): if filename == cls.configs[0]: cfg += "# auto-generated by MyService (sample.py)\n" - for ifc in node.netifs(): + for ifc in node.get_ifaces(): cfg += f'echo "Node {node.name} has interface {ifc.name}"\n' elif filename == cls.configs[1]: cfg += "echo hello" From ecc3eb1c891b8458e944158b9a14b0f43f9348d8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 09:06:31 -0700 Subject: [PATCH 067/146] daemon: refactored NodeData to reference a node instead of replicating fields as an intermediate passthrough, removed data() functions from nodes due to this change --- daemon/core/api/grpc/events.py | 22 +++++++++------- daemon/core/api/tlv/corehandlers.py | 1 - daemon/core/api/tlv/dataconversion.py | 36 ++++++++++++++----------- daemon/core/emulator/data.py | 19 +++----------- daemon/core/emulator/session.py | 4 +-- daemon/core/nodes/base.py | 38 +-------------------------- daemon/core/nodes/interface.py | 21 +-------------- daemon/core/nodes/network.py | 15 +---------- daemon/core/plugins/sdt.py | 18 +++++-------- daemon/tests/test_grpc.py | 7 ++--- 10 files changed, 52 insertions(+), 129 deletions(-) diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index ff65142d..75f9eb2e 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -15,24 +15,28 @@ from core.emulator.data import ( from core.emulator.session import Session -def handle_node_event(event: NodeData) -> core_pb2.NodeEvent: +def handle_node_event(node_data: NodeData) -> core_pb2.NodeEvent: """ Handle node event when there is a node event - :param event: node data + :param node_data: node data :return: node event that contains node id, name, model, position, and services """ - position = core_pb2.Position(x=event.x_position, y=event.y_position) - geo = core_pb2.Geo(lat=event.latitude, lon=event.longitude, alt=event.altitude) + node = node_data.node + x, y, _ = node.position.get() + position = core_pb2.Position(x=x, y=y) + lon, lat, alt = node.position.get_geo() + geo = core_pb2.Geo(lon=lon, lat=lat, alt=alt) + services = [x.name for x in node.services] node_proto = core_pb2.Node( - id=event.id, - name=event.name, - model=event.model, + id=node.id, + name=node.name, + model=node.type, position=position, geo=geo, - services=event.services, + services=services, ) - return core_pb2.NodeEvent(node=node_proto, source=event.source) + return core_pb2.NodeEvent(node=node_proto, source=node_data.source) def handle_link_event(event: LinkData) -> core_pb2.LinkEvent: diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 379b739e..d01f15a3 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -329,7 +329,6 @@ class CoreHandler(socketserver.BaseRequestHandler): """ logging.debug("handling broadcast node: %s", node_data) message = dataconversion.convert_node(node_data) - try: self.sendall(message) except IOError: diff --git a/daemon/core/api/tlv/dataconversion.py b/daemon/core/api/tlv/dataconversion.py index 62b51d39..8a26300a 100644 --- a/daemon/core/api/tlv/dataconversion.py +++ b/daemon/core/api/tlv/dataconversion.py @@ -8,35 +8,39 @@ from typing import Dict, List from core.api.tlv import coreapi, structutils from core.api.tlv.enumerations import ConfigTlvs, NodeTlvs from core.config import ConfigGroup, ConfigurableOptions -from core.emulator.data import ConfigData +from core.emulator.data import ConfigData, NodeData -def convert_node(node_data): +def convert_node(node_data: NodeData): """ Convenience method for converting NodeData to a packed TLV message. :param core.emulator.data.NodeData node_data: node data to convert :return: packed node message """ + node = node_data.node services = None - if node_data.services is not None: - services = "|".join([x for x in node_data.services]) + if node.services is not None: + services = "|".join([x.name for x in node.services]) + server = None + if node.server is not None: + server = node.server.name tlv_data = structutils.pack_values( coreapi.CoreNodeTlv, [ - (NodeTlvs.NUMBER, node_data.id), - (NodeTlvs.TYPE, node_data.type.value), - (NodeTlvs.NAME, node_data.name), - (NodeTlvs.MODEL, node_data.model), - (NodeTlvs.EMULATION_SERVER, node_data.server), - (NodeTlvs.X_POSITION, int(node_data.x_position)), - (NodeTlvs.Y_POSITION, int(node_data.y_position)), - (NodeTlvs.CANVAS, node_data.canvas), + (NodeTlvs.NUMBER, node.id), + (NodeTlvs.TYPE, node.apitype.value), + (NodeTlvs.NAME, node.name), + (NodeTlvs.MODEL, node.type), + (NodeTlvs.EMULATION_SERVER, server), + (NodeTlvs.X_POSITION, int(node.position.x)), + (NodeTlvs.Y_POSITION, int(node.position.y)), + (NodeTlvs.CANVAS, node.canvas), (NodeTlvs.SERVICES, services), - (NodeTlvs.LATITUDE, str(node_data.latitude)), - (NodeTlvs.LONGITUDE, str(node_data.longitude)), - (NodeTlvs.ALTITUDE, str(node_data.altitude)), - (NodeTlvs.ICON, node_data.icon), + (NodeTlvs.LATITUDE, str(node.position.lat)), + (NodeTlvs.LONGITUDE, str(node.position.lon)), + (NodeTlvs.ALTITUDE, str(node.position.alt)), + (NodeTlvs.ICON, node.icon), ], ) return coreapi.CoreNodeMessage.pack(node_data.message_type.value, tlv_data) diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index c57f8b24..5b6479ae 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -12,11 +12,10 @@ from core.emulator.enumerations import ( ExceptionLevels, LinkTypes, MessageFlags, - NodeTypes, ) if TYPE_CHECKING: - from core.nodes.base import CoreNode + from core.nodes.base import CoreNode, NodeBase @dataclass @@ -121,23 +120,11 @@ class NodeOptions: @dataclass class NodeData: """ - Used to represent nodes being broadcasted. + Node to broadcast. """ + node: "NodeBase" message_type: MessageFlags = None - type: NodeTypes = None - id: int = None - name: str = None - model: str = None - server: str = None - icon: str = None - canvas: int = None - services: List[str] = None - x_position: float = None - y_position: float = None - latitude: float = None - longitude: float = None - altitude: float = None source: str = None diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index ccabeddb..0b97da93 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -807,9 +807,9 @@ class Session: :param source: source of broadcast, None by default :return: nothing """ - node_data = node.data(message_type, source) - if not node_data: + if not node.apitype: return + node_data = NodeData(node=node, message_type=message_type, source=source) for handler in self.node_handlers: handler(node_data) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 378549ab..8a5c579a 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -14,7 +14,7 @@ import netaddr from core import utils from core.configservice.dependencies import ConfigServiceDependencies from core.constants import MOUNT_BIN, VNODED_BIN -from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeData +from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError from core.nodes.client import VnodeClient @@ -182,42 +182,6 @@ class NodeBase(abc.ABC): self.iface_id += 1 return iface_id - def data( - self, message_type: MessageFlags = MessageFlags.NONE, source: str = None - ) -> Optional[NodeData]: - """ - Build a data object for this node. - - :param message_type: purpose for the data object we are creating - :param source: source of node data - :return: node data object - """ - if self.apitype is None: - return None - x, y, _ = self.getposition() - model = self.type - server = None - if self.server is not None: - server = self.server.name - services = [x.name for x in self.services] - return NodeData( - message_type=message_type, - type=self.apitype, - id=self.id, - name=self.name, - model=model, - server=server, - canvas=self.canvas, - icon=self.icon, - x_position=x, - y_position=y, - latitude=self.position.lat, - longitude=self.position.lon, - altitude=self.position.alt, - services=services, - source=source, - ) - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build link data for this node. diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 287723a7..680def1b 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -8,7 +8,7 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple from core import utils from core.emulator.data import LinkOptions -from core.emulator.enumerations import MessageFlags, TransportType +from core.emulator.enumerations import TransportType from core.errors import CoreCommandError from core.nodes.netclient import LinuxNetClient, get_net_client @@ -561,23 +561,4 @@ class GreTap(CoreInterface): self.net_client.delete_device(self.localname) except CoreCommandError: logging.exception("error during shutdown") - self.localname = None - - def data(self, message_type: int) -> None: - """ - Data for a gre tap. - - :param message_type: message type for data - :return: None - """ - return None - - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List: - """ - Retrieve link data. - - :param flags: link flags - :return: link data - """ - return [] diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index ef9456db..f5baf326 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -11,7 +11,7 @@ import netaddr from core import utils from core.constants import EBTABLES_BIN, TC_BIN -from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeData +from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import ( LinkTypes, MessageFlags, @@ -862,19 +862,6 @@ class PtpNet(CoreNetwork): ) super().attach(iface) - def data( - self, message_type: MessageFlags = MessageFlags.NONE, source: str = None - ) -> Optional[NodeData]: - """ - Do not generate a Node Message for point-to-point links. They are - built using a link message instead. - - :param message_type: purpose for the data object we are creating - :param source: source of node data - :return: node data object - """ - return None - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build CORE API TLVs for a point-to-point link. One Link message diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 04fff3e4..84c90730 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -314,26 +314,22 @@ class Sdt: :param node_data: node data being updated :return: nothing """ - logging.debug("sdt handle node update: %s - %s", node_data.id, node_data.name) if not self.connect(): return - - # delete node + node = node_data.node + logging.debug("sdt handle node update: %s - %s", node.id, node.name) if node_data.message_type == MessageFlags.DELETE: - self.cmd(f"delete node,{node_data.id}") + self.cmd(f"delete node,{node.id}") else: - x = node_data.x_position - y = node_data.y_position - lat = node_data.latitude - lon = node_data.longitude - alt = node_data.altitude + x, y, _ = node.position.get() + lon, lat, alt = node.position.get_geo() if all([lat is not None, lon is not None, alt is not None]): pos = f"pos {lon:.6f},{lat:.6f},{alt:.6f}" - self.cmd(f"node {node_data.id} {pos}") + self.cmd(f"node {node.id} {pos}") elif node_data.message_type == 0: lat, lon, alt = self.session.location.getgeo(x, y, 0) pos = f"pos {lon:.6f},{lat:.6f},{alt:.6f}" - self.cmd(f"node {node_data.id} {pos}") + self.cmd(f"node {node.id} {pos}") def wireless_net_check(self, node_id: int) -> bool: """ diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index cff7cd85..8abf33aa 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -1198,9 +1198,10 @@ class TestGrpc: queue = Queue() def node_handler(node_data: NodeData): - assert node_data.longitude == lon - assert node_data.latitude == lat - assert node_data.altitude == alt + n = node_data.node + assert n.position.lon == lon + assert n.position.lat == lat + assert n.position.alt == alt queue.put(node_data) session.node_handlers.append(node_handler) From e46a072f744c198e702e5fce5347538aa0ac1456 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 09:33:54 -0700 Subject: [PATCH 068/146] daemon: removed missing params from python docs, updated node ValueErrors to CoreErrors --- daemon/core/nodes/interface.py | 4 ++-- daemon/core/nodes/network.py | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 680def1b..42522362 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple from core import utils from core.emulator.data import LinkOptions from core.emulator.enumerations import TransportType -from core.errors import CoreCommandError +from core.errors import CoreCommandError, CoreError from core.nodes.netclient import LinuxNetClient, get_net_client if TYPE_CHECKING: @@ -544,7 +544,7 @@ class GreTap(CoreInterface): if not start: return if remoteip is None: - raise ValueError("missing remote IP required for GRE TAP device") + raise CoreError("missing remote IP required for GRE TAP device") self.net_client.create_gretap(self.localname, remoteip, localip, ttl, key) self.net_client.device_up(self.localname) self.up = True diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index f5baf326..f20b6dfb 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -618,7 +618,6 @@ class GreTapBridge(CoreNetwork): :param localip: local address :param ttl: ttl value :param key: gre tap key - :param start: start flag :param server: remote server node will run on, default is None for localhost """ @@ -857,9 +856,7 @@ class PtpNet(CoreNetwork): :return: nothing """ if len(self.ifaces) >= 2: - raise ValueError( - "Point-to-point links support at most 2 network interfaces" - ) + raise CoreError("ptp links support at most 2 network interfaces") super().attach(iface) def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: @@ -992,7 +989,6 @@ class WlanNode(CoreNetwork): :param session: core session instance :param _id: node id :param name: node name - :param start: start flag :param server: remote server node will run on, default is None for localhost :param policy: wlan policy From cd74a44558596d259e0b878b49ac605edf389665 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 12:54:36 -0700 Subject: [PATCH 069/146] daemon: added type hinting throughout all services and made small tweaks/fixes that were ran across --- daemon/core/nodes/base.py | 4 +- daemon/core/services/bird.py | 86 +++++------ daemon/core/services/emaneservices.py | 25 ++-- daemon/core/services/frr.py | 202 ++++++++++++------------- daemon/core/services/nrl.py | 154 +++++++++---------- daemon/core/services/quagga.py | 196 ++++++++++++------------ daemon/core/services/sdn.py | 44 +++--- daemon/core/services/security.py | 112 +++++++------- daemon/core/services/ucarp.py | 42 +++--- daemon/core/services/utility.py | 206 ++++++++++++-------------- daemon/core/services/xorp.py | 125 +++++++--------- 11 files changed, 560 insertions(+), 636 deletions(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 8a5c579a..4fc6b873 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -7,7 +7,7 @@ import os import shutil import threading from threading import RLock -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union import netaddr @@ -27,7 +27,7 @@ if TYPE_CHECKING: from core.configservice.base import ConfigService from core.services.coreservices import CoreService - CoreServices = List[CoreService] + CoreServices = List[Union[CoreService, Type[CoreService]]] ConfigServiceType = Type[ConfigService] _DEFAULT_MTU = 1500 diff --git a/daemon/core/services/bird.py b/daemon/core/services/bird.py index 16f0bb84..a5052942 100644 --- a/daemon/core/services/bird.py +++ b/daemon/core/services/bird.py @@ -1,8 +1,11 @@ """ bird.py: defines routing services provided by the BIRD Internet Routing Daemon. """ +from typing import Optional, Tuple + import netaddr +from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -11,27 +14,27 @@ class Bird(CoreService): Bird router support """ - name = "bird" - executables = ("bird",) - group = "BIRD" - dirs = ("/etc/bird",) - configs = ("/etc/bird/bird.conf",) - startup = ("bird -c %s" % (configs[0]),) - shutdown = ("killall bird",) - validate = ("pidof bird",) + name: str = "bird" + group: str = "BIRD" + executables: Tuple[str, ...] = ("bird",) + dirs: Tuple[str, ...] = ("/etc/bird",) + configs: Tuple[str, ...] = ("/etc/bird/bird.conf",) + startup: Tuple[str, ...] = ("bird -c %s" % (configs[0]),) + shutdown: Tuple[str, ...] = ("killall bird",) + validate: Tuple[str, ...] = ("pidof bird",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the bird.conf file contents. """ if filename == cls.configs[0]: - return cls.generateBirdConf(node) + return cls.generate_bird_config(node) else: raise ValueError @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -40,15 +43,13 @@ class Bird(CoreService): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" return "0.0.0.0" @classmethod - def generateBirdConf(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on bird - will have generatebirdifcconfig() and generatebirdconfig() - hooks that are invoked here. + will have hooks that are invoked here. """ cfg = """\ /* Main configuration file for BIRD. This is ony a template, @@ -75,15 +76,16 @@ protocol device { """ % ( cls.name, - cls.routerid(node), + cls.router_id(node), ) - # Generate protocol specific configurations + # generate protocol specific configurations for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, BirdService) or issubclass(s, BirdService)): + continue cfg += s.generate_bird_config(node) - return cfg @@ -93,32 +95,26 @@ class BirdService(CoreService): common to Bird's routing daemons. """ - name = None - executables = ("bird",) - group = "BIRD" - dependencies = ("bird",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the bird service." + name: Optional[str] = None + group: str = "BIRD" + executables: Tuple[str, ...] = ("bird",) + dependencies: Tuple[str, ...] = ("bird",) + meta: str = "The config file for this service can be found in the bird service." @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: return "" @classmethod - def generate_bird_iface_config(cls, node): + def generate_bird_iface_config(cls, node: CoreNode) -> str: """ Use only bare interfaces descriptions in generated protocol configurations. This has the slight advantage of being the same everywhere. """ cfg = "" - for iface in node.get_ifaces(control=False): cfg += ' interface "%s";\n' % iface.name - return cfg @@ -127,11 +123,11 @@ class BirdBgp(BirdService): BGP BIRD Service (configuration generation) """ - name = "BIRD_BGP" - custom_needed = True + name: str = "BIRD_BGP" + custom_needed: bool = True @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: return """ /* This is a sample config that should be customized with appropriate AS numbers * and peers; add one section like this for each neighbor */ @@ -158,10 +154,10 @@ class BirdOspf(BirdService): OSPF BIRD Service (configuration generation) """ - name = "BIRD_OSPFv2" + name: str = "BIRD_OSPFv2" @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "protocol ospf {\n" cfg += " export filter {\n" cfg += " if source = RTS_BGP then {\n" @@ -174,7 +170,6 @@ class BirdOspf(BirdService): cfg += cls.generate_bird_iface_config(node) cfg += " };\n" cfg += "}\n\n" - return cfg @@ -183,12 +178,11 @@ class BirdRadv(BirdService): RADV BIRD Service (configuration generation) """ - name = "BIRD_RADV" + name: str = "BIRD_RADV" @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "/* This is a sample config that must be customized */\n" - cfg += "protocol radv {\n" cfg += " # auto configuration on all interfaces\n" cfg += cls.generate_bird_iface_config(node) @@ -202,7 +196,6 @@ class BirdRadv(BirdService): cfg += "# ns 2001:0DB8:1234::12;\n" cfg += " };\n" cfg += "}\n\n" - return cfg @@ -211,10 +204,10 @@ class BirdRip(BirdService): RIP BIRD Service (configuration generation) """ - name = "BIRD_RIP" + name: str = "BIRD_RIP" @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "protocol rip {\n" cfg += " period 10;\n" cfg += " garbage time 60;\n" @@ -224,7 +217,6 @@ class BirdRip(BirdService): cfg += " import all;\n" cfg += " export all;\n" cfg += "}\n\n" - return cfg @@ -233,11 +225,11 @@ class BirdStatic(BirdService): Static Bird Service (configuration generation) """ - name = "BIRD_static" - custom_needed = True + name: str = "BIRD_static" + custom_needed: bool = True @classmethod - def generate_bird_config(cls, node): + def generate_bird_config(cls, node: CoreNode) -> str: cfg = "/* This is a sample config that must be customized */\n" cfg += "protocol static {\n" cfg += "# route 0.0.0.0/0 via 198.51.100.130; # Default route. Do NOT advertise on BGP !\n" diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index da438bab..ef188fab 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -1,23 +1,26 @@ +from typing import Tuple + from core.emane.nodes import EmaneNet from core.errors import CoreError +from core.nodes.base import CoreNode from core.services.coreservices import CoreService from core.xml import emanexml class EmaneTransportService(CoreService): - name = "transportd" - executables = ("emanetransportd", "emanegentransportxml") - group = "EMANE" - dependencies = () - dirs = () - configs = ("emanetransport.sh",) - startup = ("sh %s" % configs[0],) - validate = ("pidof %s" % executables[0],) - validation_timer = 0.5 - shutdown = ("killall %s" % executables[0],) + name: str = "transportd" + group: str = "EMANE" + executables: Tuple[str, ...] = ("emanetransportd", "emanegentransportxml") + dependencies: Tuple[str, ...] = () + dirs: Tuple[str, ...] = () + configs: Tuple[str, ...] = ("emanetransport.sh",) + startup: Tuple[str, ...] = ("sh %s" % configs[0],) + validate: Tuple[str, ...] = ("pidof %s" % executables[0],) + validation_timer: float = 0.5 + shutdown: Tuple[str, ...] = ("killall %s" % executables[0],) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: if filename == cls.configs[0]: transport_commands = [] for iface in node.get_ifaces(): diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 97a8b334..e75d8f56 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -2,60 +2,63 @@ frr.py: defines routing services provided by FRRouting. Assumes installation of FRR via https://deb.frrouting.org/ """ +from typing import Optional, Tuple + import netaddr from core import constants from core.emane.nodes import EmaneNet +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.nodes.network import PtpNet, WlanNode from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService class FRRZebra(CoreService): - name = "FRRzebra" - group = "FRR" - dirs = ("/usr/local/etc/frr", "/var/run/frr", "/var/log/frr") - configs = ( + name: str = "FRRzebra" + group: str = "FRR" + dirs: Tuple[str, ...] = ("/usr/local/etc/frr", "/var/run/frr", "/var/log/frr") + configs: Tuple[str, ...] = ( "/usr/local/etc/frr/frr.conf", "frrboot.sh", "/usr/local/etc/frr/vtysh.conf", "/usr/local/etc/frr/daemons", ) - startup = ("sh frrboot.sh zebra",) - shutdown = ("killall zebra",) - validate = ("pidof zebra",) + startup: Tuple[str, ...] = ("sh frrboot.sh zebra",) + shutdown: Tuple[str, ...] = ("killall zebra",) + validate: Tuple[str, ...] = ("pidof zebra",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the frr.conf or frrboot.sh file contents. """ if filename == cls.configs[0]: - return cls.generateFrrConf(node) + return cls.generate_frr_conf(node) elif filename == cls.configs[1]: - return cls.generateFrrBoot(node) + return cls.generate_frr_boot(node) elif filename == cls.configs[2]: - return cls.generateVtyshConf(node) + return cls.generate_vtysh_conf(node) elif filename == cls.configs[3]: - return cls.generateFrrDaemons(node) + return cls.generate_frr_daemons(node) else: raise ValueError( "file name (%s) is not a known configuration: %s", filename, cls.configs ) @classmethod - def generateVtyshConf(cls, node): + def generate_vtysh_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. """ return "service integrated-vtysh-config\n" @classmethod - def generateFrrConf(cls, node): + def generate_frr_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on zebra - will have generatefrrifcconfig() and generatefrrconfig() - hooks that are invoked here. + will have hooks that are invoked here. """ # we could verify here that filename == frr.conf cfg = "" @@ -108,7 +111,7 @@ class FRRZebra(CoreService): return cfg @staticmethod - def addrstr(x): + def addrstr(x: str) -> str: """ helper for mapping IP addresses to zebra config statements """ @@ -121,7 +124,7 @@ class FRRZebra(CoreService): raise ValueError("invalid address: %s", x) @classmethod - def generateFrrBoot(cls, node): + def generate_frr_boot(cls, node: CoreNode) -> str: """ Generate a shell script used to boot the FRR daemons. """ @@ -244,7 +247,7 @@ bootfrr return cfg @classmethod - def generateFrrDaemons(cls, node): + def generate_frr_daemons(cls, node: CoreNode) -> str: """ Returns configuration file text. """ @@ -317,20 +320,15 @@ class FrrService(CoreService): common to FRR's routing daemons. """ - name = None - group = "FRR" - dependencies = ("FRRzebra",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the Zebra service." - - ipv4_routing = False - ipv6_routing = False + name: Optional[str] = None + group: str = "FRR" + dependencies: Tuple[str, ...] = ("FRRzebra",) + meta: str = "The config file for this service can be found in the Zebra service." + ipv4_routing: bool = False + ipv6_routing: bool = False @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -339,11 +337,10 @@ class FrrService(CoreService): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" return "0.0.0.0" @staticmethod - def rj45check(iface): + def rj45check(iface: CoreInterface) -> bool: """ Helper to detect whether interface is connected an external RJ45 link. @@ -357,15 +354,15 @@ class FrrService(CoreService): return False @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: return "" @@ -376,14 +373,13 @@ class FRROspfv2(FrrService): unified frr.conf file. """ - name = "FRROSPFv2" - startup = () - shutdown = ("killall ospfd",) - validate = ("pidof ospfd",) - ipv4_routing = True + name: str = "FRROSPFv2" + shutdown: Tuple[str, ...] = ("killall ospfd",) + validate: Tuple[str, ...] = ("pidof ospfd",) + ipv4_routing: bool = True @staticmethod - def mtucheck(iface): + def mtu_check(iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a @@ -401,7 +397,7 @@ class FRROspfv2(FrrService): return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -411,9 +407,9 @@ class FRROspfv2(FrrService): return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router ospf\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): @@ -426,8 +422,8 @@ class FRROspfv2(FrrService): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): - return cls.mtucheck(iface) + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + return cls.mtu_check(iface) class FRROspfv3(FrrService): @@ -437,15 +433,14 @@ class FRROspfv3(FrrService): unified frr.conf file. """ - name = "FRROSPFv3" - startup = () - shutdown = ("killall ospf6d",) - validate = ("pidof ospf6d",) - ipv4_routing = True - ipv6_routing = True + name: str = "FRROSPFv3" + shutdown: Tuple[str, ...] = ("killall ospf6d",) + validate: Tuple[str, ...] = ("pidof ospf6d",) + ipv4_routing: bool = True + ipv6_routing: bool = True @staticmethod - def minmtu(iface): + def min_mtu(iface: CoreInterface) -> int: """ Helper to discover the minimum MTU of interfaces linked with the given interface. @@ -459,20 +454,20 @@ class FRROspfv3(FrrService): return mtu @classmethod - def mtucheck(cls, iface): + def mtu_check(cls, iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(iface) + minmtu = cls.min_mtu(iface) if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -482,9 +477,9 @@ class FRROspfv3(FrrService): return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router ospf6\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid for iface in node.get_ifaces(control=False): cfg += " interface %s area 0.0.0.0\n" % iface.name @@ -492,14 +487,13 @@ class FRROspfv3(FrrService): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): - return cls.mtucheck(iface) + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + return cls.mtu_check(iface) # cfg = cls.mtucheck(ifc) # external RJ45 connections will use default OSPF timers # if cls.rj45check(ifc): # return cfg # cfg += cls.ptpcheck(ifc) - # return cfg + """\ @@ -516,21 +510,20 @@ class FRRBgp(FrrService): having the same AS number. """ - name = "FRRBGP" - startup = () - shutdown = ("killall bgpd",) - validate = ("pidof bgpd",) - custom_needed = True - ipv4_routing = True - ipv6_routing = True + name: str = "FRRBGP" + shutdown: Tuple[str, ...] = ("killall bgpd",) + validate: Tuple[str, ...] = ("pidof bgpd",) + custom_needed: bool = True + ipv4_routing: bool = True + ipv6_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" cfg += "router bgp %s\n" % node.id - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " bgp router-id %s\n" % rtrid cfg += " redistribute connected\n" cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n" @@ -542,14 +535,13 @@ class FRRRip(FrrService): The RIP service provides IPv4 routing for wired networks. """ - name = "FRRRIP" - startup = () - shutdown = ("killall ripd",) - validate = ("pidof ripd",) - ipv4_routing = True + name: str = "FRRRIP" + shutdown: Tuple[str, ...] = ("killall ripd",) + validate: Tuple[str, ...] = ("pidof ripd",) + ipv4_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = """\ router rip redistribute static @@ -566,14 +558,13 @@ class FRRRipng(FrrService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "FRRRIPNG" - startup = () - shutdown = ("killall ripngd",) - validate = ("pidof ripngd",) - ipv6_routing = True + name: str = "FRRRIPNG" + shutdown: Tuple[str, ...] = ("killall ripngd",) + validate: Tuple[str, ...] = ("pidof ripngd",) + ipv6_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = """\ router ripng redistribute static @@ -591,14 +582,13 @@ class FRRBabel(FrrService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "FRRBabel" - startup = () - shutdown = ("killall babeld",) - validate = ("pidof babeld",) - ipv6_routing = True + name: str = "FRRBabel" + shutdown: Tuple[str, ...] = ("killall babeld",) + validate: Tuple[str, ...] = ("pidof babeld",) + ipv6_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router babel\n" for iface in node.get_ifaces(control=False): cfg += " network %s\n" % iface.name @@ -606,7 +596,7 @@ class FRRBabel(FrrService): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: if iface.net and isinstance(iface.net, (EmaneNet, WlanNode)): return " babel wireless\n no babel split-horizon\n" else: @@ -618,14 +608,13 @@ class FRRpimd(FrrService): PIM multicast routing based on XORP. """ - name = "FRRpimd" - startup = () - shutdown = ("killall pimd",) - validate = ("pidof pimd",) - ipv4_routing = True + name: str = "FRRpimd" + shutdown: Tuple[str, ...] = ("killall pimd",) + validate: Tuple[str, ...] = ("pidof pimd",) + ipv4_routing: bool = True @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: ifname = "eth0" for iface in node.get_ifaces(): if iface.name != "lo": @@ -641,7 +630,7 @@ class FRRpimd(FrrService): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return " ip mfea\n ip igmp\n ip pim\n" @@ -652,15 +641,14 @@ class FRRIsis(FrrService): unified frr.conf file. """ - name = "FRRISIS" - startup = () - shutdown = ("killall isisd",) - validate = ("pidof isisd",) - ipv4_routing = True - ipv6_routing = True + name: str = "FRRISIS" + shutdown: Tuple[str, ...] = ("killall isisd",) + validate: Tuple[str, ...] = ("pidof isisd",) + ipv4_routing: bool = True + ipv6_routing: bool = True @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -670,7 +658,7 @@ class FRRIsis(FrrService): return "" @classmethod - def generate_frr_config(cls, node): + def generate_frr_config(cls, node: CoreNode) -> str: cfg = "router isis DEFAULT\n" cfg += " net 47.0001.0000.1900.%04x.00\n" % node.id cfg += " metric-style wide\n" @@ -679,9 +667,9 @@ class FRRIsis(FrrService): return cfg @classmethod - def generate_frr_iface_config(cls, node, iface): + def generate_frr_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: cfg = " ip router isis DEFAULT\n" cfg += " ipv6 router isis DEFAULT\n" cfg += " isis circuit-type level-2-only\n" - cfg += cls.ptpcheck(iface) + cfg += cls.ptp_check(iface) return cfg diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 38b90d48..9933b130 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -2,9 +2,12 @@ nrl.py: defines services provided by NRL protolib tools hosted here: http://www.nrl.navy.mil/itd/ncs/products """ +from typing import Optional, Tuple + import netaddr from core import utils +from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -14,19 +17,15 @@ class NrlService(CoreService): common to NRL's routing daemons. """ - name = None - group = "ProtoSvc" - dirs = () - configs = () - startup = () - shutdown = () + name: Optional[str] = None + group: str = "ProtoSvc" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @staticmethod - def firstipv4prefix(node, prefixlen=24): + def firstipv4prefix(node: CoreNode, prefixlen: int = 24) -> str: """ Similar to QuaggaService.routerid(). Helper to return the first IPv4 prefix of a node, using the supplied prefix length. This ignores the @@ -37,20 +36,19 @@ class NrlService(CoreService): a = a.split("/")[0] if netaddr.valid_ipv4(a): return f"{a}/{prefixlen}" - # raise ValueError, "no IPv4 address found" return "0.0.0.0/%s" % prefixlen class MgenSinkService(NrlService): - name = "MGEN_Sink" - executables = ("mgen",) - configs = ("sink.mgen",) - startup = ("mgen input sink.mgen",) - validate = ("pidof mgen",) - shutdown = ("killall mgen",) + name: str = "MGEN_Sink" + executables: Tuple[str, ...] = ("mgen",) + configs: Tuple[str, ...] = ("sink.mgen",) + startup: Tuple[str, ...] = ("mgen input sink.mgen",) + validate: Tuple[str, ...] = ("pidof mgen",) + shutdown: Tuple[str, ...] = ("killall mgen",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "0.0 LISTEN UDP 5000\n" for iface in node.get_ifaces(): name = utils.sysctl_devname(iface.name) @@ -58,7 +56,7 @@ class MgenSinkService(NrlService): return cfg @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: cmd = cls.startup[0] cmd += " output /tmp/mgen_%s.log" % node.name return (cmd,) @@ -69,32 +67,29 @@ class NrlNhdp(NrlService): NeighborHood Discovery Protocol for MANET networks. """ - name = "NHDP" - executables = ("nrlnhdp",) - startup = ("nrlnhdp",) - shutdown = ("killall nrlnhdp",) - validate = ("pidof nrlnhdp",) + name: str = "NHDP" + executables: Tuple[str, ...] = ("nrlnhdp",) + startup: Tuple[str, ...] = ("nrlnhdp",) + shutdown: Tuple[str, ...] = ("killall nrlnhdp",) + validate: Tuple[str, ...] = ("pidof nrlnhdp",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ cmd = cls.startup[0] cmd += " -l /var/log/nrlnhdp.log" cmd += " -rpipe %s_nhdp" % node.name - servicenames = map(lambda x: x.name, node.services) if "SMF" in servicenames: cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name - ifaces = node.get_ifaces(control=False) if len(ifaces) > 0: iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) - return (cmd,) @@ -103,15 +98,15 @@ class NrlSmf(NrlService): Simplified Multicast Forwarding for MANET networks. """ - name = "SMF" - executables = ("nrlsmf",) - startup = ("sh startsmf.sh",) - shutdown = ("killall nrlsmf",) - validate = ("pidof nrlsmf",) - configs = ("startsmf.sh",) + name: str = "SMF" + executables: Tuple[str, ...] = ("nrlsmf",) + startup: Tuple[str, ...] = ("sh startsmf.sh",) + shutdown: Tuple[str, ...] = ("killall nrlsmf",) + validate: Tuple[str, ...] = ("pidof nrlsmf",) + configs: Tuple[str, ...] = ("startsmf.sh",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startup script for SMF. Because nrlsmf does not daemonize, it can cause problems in some situations when launched @@ -146,7 +141,6 @@ class NrlSmf(NrlService): cmd += " hash MD5" cmd += " log /var/log/nrlsmf.log" - cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" return cfg @@ -156,14 +150,14 @@ class NrlOlsr(NrlService): Optimized Link State Routing protocol for MANET networks. """ - name = "OLSR" - executables = ("nrlolsrd",) - startup = ("nrlolsrd",) - shutdown = ("killall nrlolsrd",) - validate = ("pidof nrlolsrd",) + name: str = "OLSR" + executables: Tuple[str, ...] = ("nrlolsrd",) + startup: Tuple[str, ...] = ("nrlolsrd",) + shutdown: Tuple[str, ...] = ("killall nrlolsrd",) + validate: Tuple[str, ...] = ("pidof nrlolsrd",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ @@ -175,14 +169,12 @@ class NrlOlsr(NrlService): cmd += " -i %s" % iface.name cmd += " -l /var/log/nrlolsrd.log" cmd += " -rpipe %s_olsr" % node.name - servicenames = map(lambda x: x.name, node.services) if "SMF" in servicenames and "NHDP" not in servicenames: cmd += " -flooding s-mpr" cmd += " -smfClient %s_smf" % node.name if "zebra" in servicenames: cmd += " -z" - return (cmd,) @@ -191,34 +183,30 @@ class NrlOlsrv2(NrlService): Optimized Link State Routing protocol version 2 for MANET networks. """ - name = "OLSRv2" - executables = ("nrlolsrv2",) - startup = ("nrlolsrv2",) - shutdown = ("killall nrlolsrv2",) - validate = ("pidof nrlolsrv2",) + name: str = "OLSRv2" + executables: Tuple[str, ...] = ("nrlolsrv2",) + startup: Tuple[str, ...] = ("nrlolsrv2",) + shutdown: Tuple[str, ...] = ("killall nrlolsrv2",) + validate: Tuple[str, ...] = ("pidof nrlolsrv2",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ cmd = cls.startup[0] cmd += " -l /var/log/nrlolsrv2.log" cmd += " -rpipe %s_olsrv2" % node.name - servicenames = map(lambda x: x.name, node.services) if "SMF" in servicenames: cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name - cmd += " -p olsr" - ifaces = node.get_ifaces(control=False) if len(ifaces) > 0: iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) - return (cmd,) @@ -227,16 +215,16 @@ class OlsrOrg(NrlService): Optimized Link State Routing protocol from olsr.org for MANET networks. """ - name = "OLSRORG" - executables = ("olsrd",) - configs = ("/etc/olsrd/olsrd.conf",) - dirs = ("/etc/olsrd",) - startup = ("olsrd",) - shutdown = ("killall olsrd",) - validate = ("pidof olsrd",) + name: str = "OLSRORG" + executables: Tuple[str, ...] = ("olsrd",) + configs: Tuple[str, ...] = ("/etc/olsrd/olsrd.conf",) + dirs: Tuple[str, ...] = ("/etc/olsrd",) + startup: Tuple[str, ...] = ("olsrd",) + shutdown: Tuple[str, ...] = ("killall olsrd",) + validate: Tuple[str, ...] = ("pidof olsrd",) @classmethod - def get_startup(cls, node): + def get_startup(cls, node: CoreNode) -> Tuple[str, ...]: """ Generate the appropriate command-line based on node interfaces. """ @@ -246,13 +234,13 @@ class OlsrOrg(NrlService): iface_names = map(lambda x: x.name, ifaces) cmd += " -i " cmd += " -i ".join(iface_names) - return (cmd,) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ - Generate a default olsrd config file to use the broadcast address of 255.255.255.255. + Generate a default olsrd config file to use the broadcast address of + 255.255.255.255. """ cfg = """\ # @@ -577,24 +565,16 @@ class MgenActor(NrlService): """ # a unique name is required, without spaces - name = "MgenActor" - executables = ("mgen",) - # you can create your own group here - group = "ProtoSvc" - # per-node directories - dirs = () - # generated files (without a full path this file goes in the node's dir, - # e.g. /tmp/pycore.12345/n1.conf/) - configs = ("start_mgen_actor.sh",) - # list of startup commands, also may be generated during startup - startup = ("sh start_mgen_actor.sh",) - # list of validation commands - validate = ("pidof mgen",) - # list of shutdown commands - shutdown = ("killall mgen",) + name: str = "MgenActor" + group: str = "ProtoSvc" + executables: Tuple[str, ...] = ("mgen",) + configs: Tuple[str, ...] = ("start_mgen_actor.sh",) + startup: Tuple[str, ...] = ("sh start_mgen_actor.sh",) + validate: Tuple[str, ...] = ("pidof mgen",) + shutdown: Tuple[str, ...] = ("killall mgen",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startup script for MgenActor. Because mgenActor does not daemonize, it can cause problems in some situations when launched @@ -604,11 +584,9 @@ class MgenActor(NrlService): cfg += "# auto-generated by nrl.py:MgenActor.generateconfig()\n" comments = "" cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name - ifaces = node.get_ifaces(control=False) if len(ifaces) == 0: return "" - cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" return cfg @@ -618,15 +596,15 @@ class Arouted(NrlService): Adaptive Routing """ - name = "arouted" - executables = ("arouted",) - configs = ("startarouted.sh",) - startup = ("sh startarouted.sh",) - shutdown = ("pkill arouted",) - validate = ("pidof arouted",) + name: str = "arouted" + executables: Tuple[str, ...] = ("arouted",) + configs: Tuple[str, ...] = ("startarouted.sh",) + startup: Tuple[str, ...] = ("sh startarouted.sh",) + shutdown: Tuple[str, ...] = ("pkill arouted",) + validate: Tuple[str, ...] = ("pidof arouted",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the Quagga.conf or quaggaboot.sh file contents. """ diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index 41cfa3d8..30d14353 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -1,65 +1,68 @@ """ quagga.py: defines routing services provided by Quagga. """ +from typing import Optional, Tuple + import netaddr from core import constants from core.emane.nodes import EmaneNet from core.emulator.enumerations import LinkTypes +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.nodes.network import PtpNet, WlanNode from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService class Zebra(CoreService): - name = "zebra" - group = "Quagga" - dirs = ("/usr/local/etc/quagga", "/var/run/quagga") - configs = ( + name: str = "zebra" + group: str = "Quagga" + dirs: Tuple[str, ...] = ("/usr/local/etc/quagga", "/var/run/quagga") + configs: Tuple[str, ...] = ( "/usr/local/etc/quagga/Quagga.conf", "quaggaboot.sh", "/usr/local/etc/quagga/vtysh.conf", ) - startup = ("sh quaggaboot.sh zebra",) - shutdown = ("killall zebra",) - validate = ("pidof zebra",) + startup: Tuple[str, ...] = ("sh quaggaboot.sh zebra",) + shutdown: Tuple[str, ...] = ("killall zebra",) + validate: Tuple[str, ...] = ("pidof zebra",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the Quagga.conf or quaggaboot.sh file contents. """ if filename == cls.configs[0]: - return cls.generateQuaggaConf(node) + return cls.generate_quagga_conf(node) elif filename == cls.configs[1]: - return cls.generateQuaggaBoot(node) + return cls.generate_quagga_boot(node) elif filename == cls.configs[2]: - return cls.generateVtyshConf(node) + return cls.generate_vtysh_conf(node) else: raise ValueError( "file name (%s) is not a known configuration: %s", filename, cls.configs ) @classmethod - def generateVtyshConf(cls, node): + def generate_vtysh_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. """ return "service integrated-vtysh-config\n" @classmethod - def generateQuaggaConf(cls, node): + def generate_quagga_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. Other services that depend on zebra - will have generatequaggaifcconfig() and generatequaggaconfig() - hooks that are invoked here. + will have hooks that are invoked here. """ # we could verify here that filename == Quagga.conf cfg = "" for iface in node.get_ifaces(): cfg += "interface %s\n" % iface.name # include control interfaces in addressing but not routing daemons - if hasattr(iface, "control") and iface.control is True: + if getattr(iface, "control", False): cfg += " " cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) cfg += "\n" @@ -71,6 +74,8 @@ class Zebra(CoreService): for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)): + continue iface_config = s.generate_quagga_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True @@ -101,11 +106,13 @@ class Zebra(CoreService): for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, QuaggaService) or issubclass(s, QuaggaService)): + continue cfg += s.generate_quagga_config(node) return cfg @staticmethod - def addrstr(x): + def addrstr(x: str) -> str: """ helper for mapping IP addresses to zebra config statements """ @@ -118,7 +125,7 @@ class Zebra(CoreService): raise ValueError("invalid address: %s", x) @classmethod - def generateQuaggaBoot(cls, node): + def generate_quagga_boot(cls, node: CoreNode) -> str: """ Generate a shell script used to boot the Quagga daemons. """ @@ -235,20 +242,15 @@ class QuaggaService(CoreService): common to Quagga's routing daemons. """ - name = None - group = "Quagga" - dependencies = ("zebra",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the Zebra service." - - ipv4_routing = False - ipv6_routing = False + name: Optional[str] = None + group: str = "Quagga" + dependencies: Tuple[str, ...] = (Zebra.name,) + meta: str = "The config file for this service can be found in the Zebra service." + ipv4_routing: bool = False + ipv6_routing: bool = False @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -257,11 +259,10 @@ class QuaggaService(CoreService): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" - return "0.0.0.%d" % node.id + return f"0.0.0.{node.id:d}" @staticmethod - def rj45check(iface): + def rj45check(iface: CoreInterface) -> bool: """ Helper to detect whether interface is connected an external RJ45 link. @@ -275,15 +276,15 @@ class QuaggaService(CoreService): return False @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @classmethod - def generate_quagga_iface_config(cls, node, iface): + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return "" @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: return "" @@ -294,14 +295,13 @@ class Ospfv2(QuaggaService): unified Quagga.conf file. """ - name = "OSPFv2" - startup = () - shutdown = ("killall ospfd",) - validate = ("pidof ospfd",) - ipv4_routing = True + name: str = "OSPFv2" + shutdown: Tuple[str, ...] = ("killall ospfd",) + validate: Tuple[str, ...] = ("pidof ospfd",) + ipv4_routing: bool = True @staticmethod - def mtucheck(iface): + def mtu_check(iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPF mtu-ignore command. This is needed when e.g. a node is linked via a @@ -319,7 +319,7 @@ class Ospfv2(QuaggaService): return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -329,9 +329,9 @@ class Ospfv2(QuaggaService): return "" @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "router ospf\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): @@ -343,12 +343,12 @@ class Ospfv2(QuaggaService): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): - cfg = cls.mtucheck(iface) + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + cfg = cls.mtu_check(iface) # external RJ45 connections will use default OSPF timers if cls.rj45check(iface): return cfg - cfg += cls.ptpcheck(iface) + cfg += cls.ptp_check(iface) return ( cfg + """\ @@ -366,15 +366,14 @@ class Ospfv3(QuaggaService): unified Quagga.conf file. """ - name = "OSPFv3" - startup = () - shutdown = ("killall ospf6d",) - validate = ("pidof ospf6d",) - ipv4_routing = True - ipv6_routing = True + name: str = "OSPFv3" + shutdown: Tuple[str, ...] = ("killall ospf6d",) + validate: Tuple[str, ...] = ("pidof ospf6d",) + ipv4_routing: bool = True + ipv6_routing: bool = True @staticmethod - def minmtu(iface): + def min_mtu(iface: CoreInterface) -> int: """ Helper to discover the minimum MTU of interfaces linked with the given interface. @@ -388,20 +387,20 @@ class Ospfv3(QuaggaService): return mtu @classmethod - def mtucheck(cls, iface): + def mtu_check(cls, iface: CoreInterface) -> str: """ Helper to detect MTU mismatch and add the appropriate OSPFv3 ifmtu command. This is needed when e.g. a node is linked via a GreTap device. """ - minmtu = cls.minmtu(iface) + minmtu = cls.min_mtu(iface) if minmtu < iface.mtu: return " ipv6 ospf6 ifmtu %d\n" % minmtu else: return "" @staticmethod - def ptpcheck(iface): + def ptp_check(iface: CoreInterface) -> str: """ Helper to detect whether interface is connected to a notional point-to-point link. @@ -411,9 +410,9 @@ class Ospfv3(QuaggaService): return "" @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "router ospf6\n" - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " instance-id 65\n" cfg += " router-id %s\n" % rtrid for iface in node.get_ifaces(control=False): @@ -422,8 +421,8 @@ class Ospfv3(QuaggaService): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): - return cls.mtucheck(iface) + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + return cls.mtu_check(iface) class Ospfv3mdr(Ospfv3): @@ -434,12 +433,12 @@ class Ospfv3mdr(Ospfv3): unified Quagga.conf file. """ - name = "OSPFv3MDR" - ipv4_routing = True + name: str = "OSPFv3MDR" + ipv4_routing: bool = True @classmethod - def generate_quagga_iface_config(cls, node, iface): - cfg = cls.mtucheck(iface) + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: + cfg = cls.mtu_check(iface) if iface.net is not None and isinstance(iface.net, (WlanNode, EmaneNet)): return ( cfg @@ -464,21 +463,20 @@ class Bgp(QuaggaService): having the same AS number. """ - name = "BGP" - startup = () - shutdown = ("killall bgpd",) - validate = ("pidof bgpd",) - custom_needed = True - ipv4_routing = True - ipv6_routing = True + name: str = "BGP" + shutdown: Tuple[str, ...] = ("killall bgpd",) + validate: Tuple[str, ...] = ("pidof bgpd",) + custom_needed: bool = True + ipv4_routing: bool = True + ipv6_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" cfg += "router bgp %s\n" % node.id - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += " bgp router-id %s\n" % rtrid cfg += " redistribute connected\n" cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n" @@ -490,14 +488,13 @@ class Rip(QuaggaService): The RIP service provides IPv4 routing for wired networks. """ - name = "RIP" - startup = () - shutdown = ("killall ripd",) - validate = ("pidof ripd",) - ipv4_routing = True + name: str = "RIP" + shutdown: Tuple[str, ...] = ("killall ripd",) + validate: Tuple[str, ...] = ("pidof ripd",) + ipv4_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = """\ router rip redistribute static @@ -514,14 +511,13 @@ class Ripng(QuaggaService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "RIPNG" - startup = () - shutdown = ("killall ripngd",) - validate = ("pidof ripngd",) - ipv6_routing = True + name: str = "RIPNG" + shutdown: Tuple[str, ...] = ("killall ripngd",) + validate: Tuple[str, ...] = ("pidof ripngd",) + ipv6_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = """\ router ripng redistribute static @@ -539,14 +535,13 @@ class Babel(QuaggaService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "Babel" - startup = () - shutdown = ("killall babeld",) - validate = ("pidof babeld",) - ipv6_routing = True + name: str = "Babel" + shutdown: Tuple[str, ...] = ("killall babeld",) + validate: Tuple[str, ...] = ("pidof babeld",) + ipv6_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: cfg = "router babel\n" for iface in node.get_ifaces(control=False): cfg += " network %s\n" % iface.name @@ -554,7 +549,7 @@ class Babel(QuaggaService): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: if iface.net and iface.net.linktype == LinkTypes.WIRELESS: return " babel wireless\n no babel split-horizon\n" else: @@ -566,14 +561,13 @@ class Xpimd(QuaggaService): PIM multicast routing based on XORP. """ - name = "Xpimd" - startup = () - shutdown = ("killall xpimd",) - validate = ("pidof xpimd",) - ipv4_routing = True + name: str = "Xpimd" + shutdown: Tuple[str, ...] = ("killall xpimd",) + validate: Tuple[str, ...] = ("pidof xpimd",) + ipv4_routing: bool = True @classmethod - def generate_quagga_config(cls, node): + def generate_quagga_config(cls, node: CoreNode) -> str: ifname = "eth0" for iface in node.get_ifaces(): if iface.name != "lo": @@ -589,5 +583,5 @@ class Xpimd(QuaggaService): return cfg @classmethod - def generate_quagga_iface_config(cls, node, iface): + def generate_quagga_iface_config(cls, node: CoreNode, iface: CoreInterface) -> str: return " ip mfea\n ip igmp\n ip pim\n" diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index 71ab815f..1f17201d 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -3,9 +3,11 @@ sdn.py defines services to start Open vSwitch and the Ryu SDN Controller. """ import re +from typing import Tuple import netaddr +from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -14,24 +16,28 @@ class SdnService(CoreService): Parent class for SDN services. """ - group = "SDN" + group: str = "SDN" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" class OvsService(SdnService): - name = "OvsService" - executables = ("ovs-ofctl", "ovs-vsctl") - group = "SDN" - dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch") - configs = ("OvsService.sh",) - startup = ("sh OvsService.sh",) - shutdown = ("killall ovs-vswitchd", "killall ovsdb-server") + name: str = "OvsService" + group: str = "SDN" + executables: Tuple[str, ...] = ("ovs-ofctl", "ovs-vsctl") + dirs: Tuple[str, ...] = ( + "/etc/openvswitch", + "/var/run/openvswitch", + "/var/log/openvswitch", + ) + configs: Tuple[str, ...] = ("OvsService.sh",) + startup: Tuple[str, ...] = ("sh OvsService.sh",) + shutdown: Tuple[str, ...] = ("killall ovs-vswitchd", "killall ovsdb-server") @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: # Check whether the node is running zebra has_zebra = 0 for s in node.services: @@ -46,8 +52,8 @@ class OvsService(SdnService): cfg += "## this stops it from routing traffic without defined flows.\n" cfg += "## remove the -- and everything after if you want it to act as a regular switch\n" cfg += "ovs-vsctl add-br ovsbr0 -- set Bridge ovsbr0 fail-mode=secure\n" - cfg += "\n## Now add all our interfaces as ports to the switch\n" + portnum = 1 for iface in node.get_ifaces(control=False): ifnumstr = re.findall(r"\d+", iface.name) @@ -111,21 +117,19 @@ class OvsService(SdnService): % (portnum + 1, portnum) ) portnum += 2 - return cfg class RyuService(SdnService): - name = "ryuService" - executables = ("ryu-manager",) - group = "SDN" - dirs = () - configs = ("ryuService.sh",) - startup = ("sh ryuService.sh",) - shutdown = ("killall ryu-manager",) + name: str = "ryuService" + group: str = "SDN" + executables: Tuple[str, ...] = ("ryu-manager",) + configs: Tuple[str, ...] = ("ryuService.sh",) + startup: Tuple[str, ...] = ("sh ryuService.sh",) + shutdown: Tuple[str, ...] = ("killall ryu-manager",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return a string that will be written to filename, or sent to the GUI for user customization. diff --git a/daemon/core/services/security.py b/daemon/core/services/security.py index 91c942f1..b813579e 100644 --- a/daemon/core/services/security.py +++ b/daemon/core/services/security.py @@ -4,78 +4,79 @@ firewall) """ import logging +from typing import Tuple from core import constants +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService class VPNClient(CoreService): - name = "VPNClient" - group = "Security" - configs = ("vpnclient.sh",) - startup = ("sh vpnclient.sh",) - shutdown = ("killall openvpn",) - validate = ("pidof openvpn",) - custom_needed = True + name: str = "VPNClient" + group: str = "Security" + configs: Tuple[str, ...] = ("vpnclient.sh",) + startup: Tuple[str, ...] = ("sh vpnclient.sh",) + shutdown: Tuple[str, ...] = ("killall openvpn",) + validate: Tuple[str, ...] = ("pidof openvpn",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the client.conf and vpnclient.sh file contents to """ cfg = "#!/bin/sh\n" cfg += "# custom VPN Client configuration for service (security.py)\n" - fname = "%s/examples/services/sampleVPNClient" % constants.CORE_DATA_DIR - + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleVPNClient" try: - cfg += open(fname, "rb").read() + with open(fname, "r") as f: + cfg += f.read() except IOError: logging.exception( - "Error opening VPN client configuration template (%s)", fname + "error opening VPN client configuration template (%s)", fname ) - return cfg class VPNServer(CoreService): - name = "VPNServer" - group = "Security" - configs = ("vpnserver.sh",) - startup = ("sh vpnserver.sh",) - shutdown = ("killall openvpn",) - validate = ("pidof openvpn",) - custom_needed = True + name: str = "VPNServer" + group: str = "Security" + configs: Tuple[str, ...] = ("vpnserver.sh",) + startup: Tuple[str, ...] = ("sh vpnserver.sh",) + shutdown: Tuple[str, ...] = ("killall openvpn",) + validate: Tuple[str, ...] = ("pidof openvpn",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the sample server.conf and vpnserver.sh file contents to GUI for user customization. """ cfg = "#!/bin/sh\n" cfg += "# custom VPN Server Configuration for service (security.py)\n" - fname = "%s/examples/services/sampleVPNServer" % constants.CORE_DATA_DIR - + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleVPNServer" try: - cfg += open(fname, "rb").read() + with open(fname, "r") as f: + cfg += f.read() except IOError: logging.exception( "Error opening VPN server configuration template (%s)", fname ) - return cfg class IPsec(CoreService): - name = "IPsec" - group = "Security" - configs = ("ipsec.sh",) - startup = ("sh ipsec.sh",) - shutdown = ("killall racoon",) - custom_needed = True + name: str = "IPsec" + group: str = "Security" + configs: Tuple[str, ...] = ("ipsec.sh",) + startup: Tuple[str, ...] = ("sh ipsec.sh",) + shutdown: Tuple[str, ...] = ("killall racoon",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the ipsec.conf and racoon.conf file contents to GUI for user customization. @@ -83,7 +84,7 @@ class IPsec(CoreService): cfg = "#!/bin/sh\n" cfg += "# set up static tunnel mode security assocation for service " cfg += "(security.py)\n" - fname = "%s/examples/services/sampleIPsec" % constants.CORE_DATA_DIR + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleIPsec" try: with open(fname, "r") as f: cfg += f.read() @@ -93,28 +94,27 @@ class IPsec(CoreService): class Firewall(CoreService): - name = "Firewall" - group = "Security" - configs = ("firewall.sh",) - startup = ("sh firewall.sh",) - custom_needed = True + name: str = "Firewall" + group: str = "Security" + configs: Tuple[str, ...] = ("firewall.sh",) + startup: Tuple[str, ...] = ("sh firewall.sh",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the firewall rule examples to GUI for user customization. """ cfg = "#!/bin/sh\n" cfg += "# custom node firewall rules for service (security.py)\n" - fname = "%s/examples/services/sampleFirewall" % constants.CORE_DATA_DIR - + fname = f"{constants.CORE_DATA_DIR}/examples/services/sampleFirewall" try: - cfg += open(fname, "rb").read() + with open(fname, "r") as f: + cfg += f.read() except IOError: logging.exception( "Error opening Firewall configuration template (%s)", fname ) - return cfg @@ -123,30 +123,28 @@ class Nat(CoreService): IPv4 source NAT service. """ - name = "NAT" - executables = ("iptables",) - group = "Security" - configs = ("nat.sh",) - startup = ("sh nat.sh",) - custom_needed = False + name: str = "NAT" + group: str = "Security" + executables: Tuple[str, ...] = ("iptables",) + configs: Tuple[str, ...] = ("nat.sh",) + startup: Tuple[str, ...] = ("sh nat.sh",) + custom_needed: bool = False @classmethod - def generate_iface_nat_rule(cls, iface, line_prefix=""): + def generate_iface_nat_rule(cls, iface: CoreInterface, prefix: str = "") -> str: """ Generate a NAT line for one interface. """ - cfg = line_prefix + "iptables -t nat -A POSTROUTING -o " + cfg = prefix + "iptables -t nat -A POSTROUTING -o " cfg += iface.name + " -j MASQUERADE\n" - - cfg += line_prefix + "iptables -A FORWARD -i " + iface.name + cfg += prefix + "iptables -A FORWARD -i " + iface.name cfg += " -m state --state RELATED,ESTABLISHED -j ACCEPT\n" - - cfg += line_prefix + "iptables -A FORWARD -i " + cfg += prefix + "iptables -A FORWARD -i " cfg += iface.name + " -j DROP\n" return cfg @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ NAT out the first interface """ @@ -156,7 +154,7 @@ class Nat(CoreService): have_nat = False for iface in node.get_ifaces(control=False): if have_nat: - cfg += cls.generate_iface_nat_rule(iface, line_prefix="#") + cfg += cls.generate_iface_nat_rule(iface, prefix="#") else: have_nat = True cfg += "# NAT out the " + iface.name + " interface\n" diff --git a/daemon/core/services/ucarp.py b/daemon/core/services/ucarp.py index 1eb80179..8ac92dd3 100644 --- a/daemon/core/services/ucarp.py +++ b/daemon/core/services/ucarp.py @@ -1,52 +1,52 @@ """ ucarp.py: defines high-availability IP address controlled by ucarp """ +from typing import Tuple +from core.nodes.base import CoreNode from core.services.coreservices import CoreService UCARP_ETC = "/usr/local/etc/ucarp" class Ucarp(CoreService): - name = "ucarp" - group = "Utility" - dirs = (UCARP_ETC,) - configs = ( + name: str = "ucarp" + group: str = "Utility" + dirs: Tuple[str, ...] = (UCARP_ETC,) + configs: Tuple[str, ...] = ( UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh", ) - startup = ("sh ucarpboot.sh",) - shutdown = ("killall ucarp",) - validate = ("pidof ucarp",) + startup: Tuple[str, ...] = ("sh ucarpboot.sh",) + shutdown: Tuple[str, ...] = ("killall ucarp",) + validate: Tuple[str, ...] = ("pidof ucarp",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Return the default file contents """ if filename == cls.configs[0]: - return cls.generateUcarpConf(node) + return cls.generate_ucarp_conf(node) elif filename == cls.configs[1]: - return cls.generateVipUp(node) + return cls.generate_vip_up(node) elif filename == cls.configs[2]: - return cls.generateVipDown(node) + return cls.generate_vip_down(node) elif filename == cls.configs[3]: - return cls.generateUcarpBoot(node) + return cls.generate_ucarp_boot(node) else: raise ValueError @classmethod - def generateUcarpConf(cls, node): + def generate_ucarp_conf(cls, node: CoreNode) -> str: """ Returns configuration file text. """ - try: - ucarp_bin = node.session.cfg["ucarp_bin"] - except KeyError: - ucarp_bin = "/usr/sbin/ucarp" - + ucarp_bin = node.session.options.get_config( + "ucarp_bin", default="/usr/sbin/ucarp" + ) return """\ #!/bin/sh # Location of UCARP executable @@ -110,7 +110,7 @@ ${UCARP_EXEC} -B ${UCARP_OPTS} ) @classmethod - def generateUcarpBoot(cls, node): + def generate_ucarp_boot(cls, node: CoreNode) -> str: """ Generate a shell script used to boot the Ucarp daemons. """ @@ -130,7 +130,7 @@ ${UCARP_CFGDIR}/default.sh ) @classmethod - def generateVipUp(cls, node): + def generate_vip_up(cls, node: CoreNode) -> str: """ Generate a shell script used to start the virtual ip """ @@ -152,7 +152,7 @@ fi """ @classmethod - def generateVipDown(cls, node): + def generate_vip_down(cls, node: CoreNode) -> str: """ Generate a shell script used to stop the virtual ip """ diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 273318e1..a44037f6 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -1,12 +1,13 @@ """ utility.py: defines miscellaneous utility services. """ -import os +from typing import Optional, Tuple import netaddr from core import constants, utils from core.errors import CoreCommandError +from core.nodes.base import CoreNode from core.services.coreservices import CoreService, ServiceMode @@ -15,32 +16,25 @@ class UtilService(CoreService): Parent class for utility services. """ - name = None - group = "Utility" - dirs = () - configs = () - startup = () - shutdown = () + name: Optional[str] = None + group: str = "Utility" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" class IPForwardService(UtilService): - name = "IPForward" - configs = ("ipforward.sh",) - startup = ("sh ipforward.sh",) + name: str = "IPForward" + configs: Tuple[str, ...] = ("ipforward.sh",) + startup: Tuple[str, ...] = ("sh ipforward.sh",) @classmethod - def generate_config(cls, node, filename): - if os.uname()[0] == "Linux": - return cls.generateconfiglinux(node, filename) - else: - raise Exception("unknown platform") + def generate_config(cls, node: CoreNode, filename: str) -> str: + return cls.generateconfiglinux(node, filename) @classmethod - def generateconfiglinux(cls, node, filename): + def generateconfiglinux(cls, node: CoreNode, filename: str) -> str: cfg = """\ #!/bin/sh # auto-generated by IPForward service (utility.py) @@ -70,12 +64,12 @@ class IPForwardService(UtilService): class DefaultRouteService(UtilService): - name = "DefaultRoute" - configs = ("defaultroute.sh",) - startup = ("sh defaultroute.sh",) + name: str = "DefaultRoute" + configs: Tuple[str, ...] = ("defaultroute.sh",) + startup: Tuple[str, ...] = ("sh defaultroute.sh",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: routes = [] ifaces = node.get_ifaces() if ifaces: @@ -93,22 +87,18 @@ class DefaultRouteService(UtilService): class DefaultMulticastRouteService(UtilService): - name = "DefaultMulticastRoute" - configs = ("defaultmroute.sh",) - startup = ("sh defaultmroute.sh",) + name: str = "DefaultMulticastRoute" + configs: Tuple[str, ...] = ("defaultmroute.sh",) + startup: Tuple[str, ...] = ("sh defaultmroute.sh",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "#!/bin/sh\n" cfg += "# auto-generated by DefaultMulticastRoute service (utility.py)\n" cfg += "# the first interface is chosen below; please change it " cfg += "as needed\n" - for iface in node.get_ifaces(control=False): - if os.uname()[0] == "Linux": - rtcmd = "ip route add 224.0.0.0/4 dev" - else: - raise Exception("unknown platform") + rtcmd = "ip route add 224.0.0.0/4 dev" cfg += "%s %s\n" % (rtcmd, iface.name) cfg += "\n" break @@ -116,13 +106,13 @@ class DefaultMulticastRouteService(UtilService): class StaticRouteService(UtilService): - name = "StaticRoute" - configs = ("staticroute.sh",) - startup = ("sh staticroute.sh",) - custom_needed = True + name: str = "StaticRoute" + configs: Tuple[str, ...] = ("staticroute.sh",) + startup: Tuple[str, ...] = ("sh staticroute.sh",) + custom_needed: bool = True @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: cfg = "#!/bin/sh\n" cfg += "# auto-generated by StaticRoute service (utility.py)\n#\n" cfg += "# NOTE: this service must be customized to be of any use\n" @@ -133,7 +123,7 @@ class StaticRouteService(UtilService): return cfg @staticmethod - def routestr(x): + def routestr(x: str) -> str: addr = x.split("/")[0] if netaddr.valid_ipv6(addr): dst = "3ffe:4::/64" @@ -143,24 +133,20 @@ class StaticRouteService(UtilService): if net[-2] == net[1]: return "" else: - if os.uname()[0] == "Linux": - rtcmd = "#/sbin/ip route add %s via" % dst - else: - raise Exception("unknown platform") + rtcmd = "#/sbin/ip route add %s via" % dst return "%s %s" % (rtcmd, net[1]) class SshService(UtilService): - name = "SSH" - configs = ("startsshd.sh", "/etc/ssh/sshd_config") - dirs = ("/etc/ssh", "/var/run/sshd") - startup = ("sh startsshd.sh",) - shutdown = ("killall sshd",) - validate = () - validation_mode = ServiceMode.BLOCKING + name: str = "SSH" + configs: Tuple[str, ...] = ("startsshd.sh", "/etc/ssh/sshd_config") + dirs: Tuple[str, ...] = ("/etc/ssh", "/var/run/sshd") + startup: Tuple[str, ...] = ("sh startsshd.sh",) + shutdown: Tuple[str, ...] = ("killall sshd",) + validation_mode: ServiceMode = ServiceMode.BLOCKING @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Use a startup script for launching sshd in order to wait for host key generation. @@ -228,15 +214,15 @@ UseDNS no class DhcpService(UtilService): - name = "DHCP" - configs = ("/etc/dhcp/dhcpd.conf",) - dirs = ("/etc/dhcp", "/var/lib/dhcp") - startup = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd") - shutdown = ("killall dhcpd",) - validate = ("pidof dhcpd",) + name: str = "DHCP" + configs: Tuple[str, ...] = ("/etc/dhcp/dhcpd.conf",) + dirs: Tuple[str, ...] = ("/etc/dhcp", "/var/lib/dhcp") + startup: Tuple[str, ...] = ("touch /var/lib/dhcp/dhcpd.leases", "dhcpd") + shutdown: Tuple[str, ...] = ("killall dhcpd",) + validate: Tuple[str, ...] = ("pidof dhcpd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a dhcpd config file using the network address of each interface. @@ -261,7 +247,7 @@ ddns-update-style none; return cfg @staticmethod - def subnetentry(x): + def subnetentry(x: str) -> str: """ Generate a subnet declaration block given an IPv4 prefix string for inclusion in the dhcpd3 config file. @@ -297,14 +283,14 @@ class DhcpClientService(UtilService): Use a DHCP client for all interfaces for addressing. """ - name = "DHCPClient" - configs = ("startdhcpclient.sh",) - startup = ("sh startdhcpclient.sh",) - shutdown = ("killall dhclient",) - validate = ("pidof dhclient",) + name: str = "DHCPClient" + configs: Tuple[str, ...] = ("startdhcpclient.sh",) + startup: Tuple[str, ...] = ("sh startdhcpclient.sh",) + shutdown: Tuple[str, ...] = ("killall dhclient",) + validate: Tuple[str, ...] = ("pidof dhclient",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a script to invoke dhclient on all interfaces. """ @@ -313,7 +299,6 @@ class DhcpClientService(UtilService): cfg += "# uncomment this mkdir line and symlink line to enable client-" cfg += "side DNS\n# resolution based on the DHCP server response.\n" cfg += "#mkdir -p /var/run/resolvconf/interface\n" - for iface in node.get_ifaces(control=False): cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % iface.name cfg += " /var/run/resolvconf/resolv.conf\n" @@ -327,15 +312,15 @@ class FtpService(UtilService): Start a vsftpd server. """ - name = "FTP" - configs = ("vsftpd.conf",) - dirs = ("/var/run/vsftpd/empty", "/var/ftp") - startup = ("vsftpd ./vsftpd.conf",) - shutdown = ("killall vsftpd",) - validate = ("pidof vsftpd",) + name: str = "FTP" + configs: Tuple[str, ...] = ("vsftpd.conf",) + dirs: Tuple[str, ...] = ("/var/run/vsftpd/empty", "/var/ftp") + startup: Tuple[str, ...] = ("vsftpd ./vsftpd.conf",) + shutdown: Tuple[str, ...] = ("killall vsftpd",) + validate: Tuple[str, ...] = ("pidof vsftpd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a vsftpd.conf configuration file. """ @@ -360,13 +345,13 @@ class HttpService(UtilService): Start an apache server. """ - name = "HTTP" - configs = ( + name: str = "HTTP" + configs: Tuple[str, ...] = ( "/etc/apache2/apache2.conf", "/etc/apache2/envvars", "/var/www/index.html", ) - dirs = ( + dirs: Tuple[str, ...] = ( "/etc/apache2", "/var/run/apache2", "/var/log/apache2", @@ -374,14 +359,14 @@ class HttpService(UtilService): "/var/lock/apache2", "/var/www", ) - startup = ("chown www-data /var/lock/apache2", "apache2ctl start") - shutdown = ("apache2ctl stop",) - validate = ("pidof apache2",) - - APACHEVER22, APACHEVER24 = (22, 24) + startup: Tuple[str, ...] = ("chown www-data /var/lock/apache2", "apache2ctl start") + shutdown: Tuple[str, ...] = ("apache2ctl stop",) + validate: Tuple[str, ...] = ("pidof apache2",) + APACHEVER22: int = 22 + APACHEVER24: int = 24 @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate an apache2.conf configuration file. """ @@ -395,7 +380,7 @@ class HttpService(UtilService): return "" @classmethod - def detectversionfromcmd(cls): + def detectversionfromcmd(cls) -> int: """ Detect the apache2 version using the 'a2query' command. """ @@ -405,14 +390,12 @@ class HttpService(UtilService): except CoreCommandError as e: status = e.returncode result = e.stderr - if status == 0 and result[:3] == "2.4": return cls.APACHEVER24 - return cls.APACHEVER22 @classmethod - def generateapache2conf(cls, node, filename): + def generateapache2conf(cls, node: CoreNode, filename: str) -> str: lockstr = { cls.APACHEVER22: "LockFile ${APACHE_LOCK_DIR}/accept.lock\n", cls.APACHEVER24: "Mutex file:${APACHE_LOCK_DIR} default\n", @@ -421,22 +404,18 @@ class HttpService(UtilService): cls.APACHEVER22: "", cls.APACHEVER24: "LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n", } - permstr = { cls.APACHEVER22: " Order allow,deny\n Deny from all\n Satisfy all\n", cls.APACHEVER24: " Require all denied\n", } - authstr = { cls.APACHEVER22: "LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n", cls.APACHEVER24: "LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n", } - permstr2 = { cls.APACHEVER22: "\t\tOrder allow,deny\n\t\tallow from all\n", cls.APACHEVER24: "\t\tRequire all granted\n", } - version = cls.detectversionfromcmd() cfg = "# apache2.conf generated by utility.py:HttpService\n" cfg += lockstr[version] @@ -552,7 +531,7 @@ TraceEnable Off return cfg @classmethod - def generateenvvars(cls, node, filename): + def generateenvvars(cls, node: CoreNode, filename: str) -> str: return """\ # this file is used by apache2ctl - generated by utility.py:HttpService # these settings come from a default Ubuntu apache2 installation @@ -567,7 +546,7 @@ export LANG """ @classmethod - def generatehtml(cls, node, filename): + def generatehtml(cls, node: CoreNode, filename: str) -> str: body = ( """\ @@ -587,16 +566,15 @@ class PcapService(UtilService): Pcap service for logging packets. """ - name = "pcap" - configs = ("pcap.sh",) - dirs = () - startup = ("sh pcap.sh start",) - shutdown = ("sh pcap.sh stop",) - validate = ("pidof tcpdump",) - meta = "logs network traffic to pcap packet capture files" + name: str = "pcap" + configs: Tuple[str, ...] = ("pcap.sh",) + startup: Tuple[str, ...] = ("sh pcap.sh start",) + shutdown: Tuple[str, ...] = ("sh pcap.sh stop",) + validate: Tuple[str, ...] = ("pidof tcpdump",) + meta: str = "logs network traffic to pcap packet capture files" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a startpcap.sh traffic logging script. """ @@ -630,15 +608,17 @@ fi; class RadvdService(UtilService): - name = "radvd" - configs = ("/etc/radvd/radvd.conf",) - dirs = ("/etc/radvd",) - startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",) - shutdown = ("pkill radvd",) - validate = ("pidof radvd",) + name: str = "radvd" + configs: Tuple[str, ...] = ("/etc/radvd/radvd.conf",) + dirs: Tuple[str, ...] = ("/etc/radvd",) + startup: Tuple[str, ...] = ( + "radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log", + ) + shutdown: Tuple[str, ...] = ("pkill radvd",) + validate: Tuple[str, ...] = ("pidof radvd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Generate a RADVD router advertisement daemon config file using the network address of each interface. @@ -678,7 +658,7 @@ interface %s return cfg @staticmethod - def subnetentry(x): + def subnetentry(x: str) -> str: """ Generate a subnet declaration block given an IPv6 prefix string for inclusion in the RADVD config file. @@ -695,14 +675,14 @@ class AtdService(UtilService): Atd service for scheduling at jobs """ - name = "atd" - configs = ("startatd.sh",) - dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") - startup = ("sh startatd.sh",) - shutdown = ("pkill atd",) + name: str = "atd" + configs: Tuple[str, ...] = ("startatd.sh",) + dirs: Tuple[str, ...] = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") + startup: Tuple[str, ...] = ("sh startatd.sh",) + shutdown: Tuple[str, ...] = ("pkill atd",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return """ #!/bin/sh echo 00001 > /var/spool/cron/atjobs/.SEQ @@ -717,5 +697,5 @@ class UserDefinedService(UtilService): Dummy service allowing customization of anything. """ - name = "UserDefined" - meta = "Customize this service to do anything upon startup." + name: str = "UserDefined" + meta: str = "Customize this service to do anything upon startup." diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 776b1d16..42082377 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -2,10 +2,12 @@ xorp.py: defines routing services provided by the XORP routing suite. """ -import logging +from typing import Optional, Tuple import netaddr +from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService @@ -15,20 +17,20 @@ class XorpRtrmgr(CoreService): enabled XORP services, and launches necessary daemons upon startup. """ - name = "xorp_rtrmgr" - executables = ("xorp_rtrmgr",) - group = "XORP" - dirs = ("/etc/xorp",) - configs = ("/etc/xorp/config.boot",) - startup = ( + name: str = "xorp_rtrmgr" + group: str = "XORP" + executables: Tuple[str, ...] = ("xorp_rtrmgr",) + dirs: Tuple[str, ...] = ("/etc/xorp",) + configs: Tuple[str, ...] = ("/etc/xorp/config.boot",) + startup: Tuple[str, ...] = ( "xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (configs[0], name, name), ) - shutdown = ("killall xorp_rtrmgr",) - validate = ("pidof xorp_rtrmgr",) + shutdown: Tuple[str, ...] = ("killall xorp_rtrmgr",) + validate: Tuple[str, ...] = ("pidof xorp_rtrmgr",) @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: """ Returns config.boot configuration file text. Other services that depend on this will have generatexorpconfig() hooks that are @@ -45,16 +47,15 @@ class XorpRtrmgr(CoreService): cfg += "}\n\n" for s in node.services: - try: - s.dependencies.index(cls.name) - cfg += s.generatexorpconfig(node) - except ValueError: - logging.exception("error getting value from service: %s", cls.name) - + if cls.name not in s.dependencies: + continue + if not (isinstance(s, XorpService) or issubclass(s, XorpService)): + continue + cfg += s.generate_xorp_config(node) return cfg @staticmethod - def addrstr(x): + def addrstr(x: str) -> str: """ helper for mapping IP addresses to XORP config statements """ @@ -65,7 +66,7 @@ class XorpRtrmgr(CoreService): return cfg @staticmethod - def lladdrstr(iface): + def lladdrstr(iface: CoreInterface) -> str: """ helper for adding link-local address entries (required by OSPFv3) """ @@ -81,18 +82,16 @@ class XorpService(CoreService): common to XORP's routing daemons. """ - name = None - executables = ("xorp_rtrmgr",) - group = "XORP" - dependencies = ("xorp_rtrmgr",) - dirs = () - configs = () - startup = () - shutdown = () - meta = "The config file for this service can be found in the xorp_rtrmgr service." + name: Optional[str] = None + group: str = "XORP" + executables: Tuple[str, ...] = ("xorp_rtrmgr",) + dependencies: Tuple[str, ...] = ("xorp_rtrmgr",) + meta: str = ( + "The config file for this service can be found in the xorp_rtrmgr service." + ) @staticmethod - def fea(forwarding): + def fea(forwarding: str) -> str: """ Helper to add a forwarding engine entry to the config file. """ @@ -104,17 +103,14 @@ class XorpService(CoreService): return cfg @staticmethod - def mfea(forwarding, ifaces): + def mfea(forwarding, node: CoreNode) -> str: """ Helper to add a multicast forwarding engine entry to the config file. """ names = [] - for iface in ifaces: - if hasattr(iface, "control") and iface.control is True: - continue + for iface in node.get_ifaces(control=False): names.append(iface.name) names.append("register_vif") - cfg = "plumbing {\n" cfg += " %s {\n" % forwarding for name in names: @@ -128,7 +124,7 @@ class XorpService(CoreService): return cfg @staticmethod - def policyexportconnected(): + def policyexportconnected() -> str: """ Helper to add a policy statement for exporting connected routes. """ @@ -144,7 +140,7 @@ class XorpService(CoreService): return cfg @staticmethod - def routerid(node): + def router_id(node: CoreNode) -> str: """ Helper to return the first IPv4 address of a node as its router ID. """ @@ -153,15 +149,14 @@ class XorpService(CoreService): a = a.split("/")[0] if netaddr.valid_ipv4(a): return a - # raise ValueError, "no IPv4 address found for router ID" return "0.0.0.0" @classmethod - def generate_config(cls, node, filename): + def generate_config(cls, node: CoreNode, filename: str) -> str: return "" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: return "" @@ -172,12 +167,12 @@ class XorpOspfv2(XorpService): unified XORP configuration file. """ - name = "XORP_OSPFv2" + name: str = "XORP_OSPFv2" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding4") - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " ospf4 {\n" cfg += "\trouter-id: %s\n" % rtrid @@ -206,12 +201,12 @@ class XorpOspfv3(XorpService): unified XORP configuration file. """ - name = "XORP_OSPFv3" + name: str = "XORP_OSPFv3" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding6") - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " ospf6 0 { /* Instance ID 0 */\n" cfg += "\trouter-id: %s\n" % rtrid @@ -232,16 +227,16 @@ class XorpBgp(XorpService): IPv4 inter-domain routing. AS numbers and peers must be customized. """ - name = "XORP_BGP" - custom_needed = True + name: str = "XORP_BGP" + custom_needed: bool = True @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = "/* This is a sample config that should be customized with\n" cfg += " appropriate AS numbers and peers */\n" cfg += cls.fea("unicast-forwarding4") cfg += cls.policyexportconnected() - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " bgp {\n" cfg += "\tbgp-id: %s\n" % rtrid @@ -262,10 +257,10 @@ class XorpRip(XorpService): RIP IPv4 unicast routing. """ - name = "XORP_RIP" + name: str = "XORP_RIP" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding4") cfg += cls.policyexportconnected() cfg += "\nprotocols {\n" @@ -293,10 +288,10 @@ class XorpRipng(XorpService): RIP NG IPv6 unicast routing. """ - name = "XORP_RIPNG" + name: str = "XORP_RIPNG" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding6") cfg += cls.policyexportconnected() cfg += "\nprotocols {\n" @@ -320,12 +315,11 @@ class XorpPimSm4(XorpService): PIM Sparse Mode IPv4 multicast routing. """ - name = "XORP_PIMSM4" + name: str = "XORP_PIMSM4" @classmethod - def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea4", node.get_ifaces()) - + def generate_xorp_config(cls, node: CoreNode) -> str: + cfg = cls.mfea("mfea4", node) cfg += "\nprotocols {\n" cfg += " igmp {\n" names = [] @@ -338,7 +332,6 @@ class XorpPimSm4(XorpService): cfg += "\t}\n" cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " pimsm4 {\n" @@ -361,10 +354,8 @@ class XorpPimSm4(XorpService): cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" - cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " fib2mrib {\n" cfg += "\tdisable: false\n" @@ -378,12 +369,11 @@ class XorpPimSm6(XorpService): PIM Sparse Mode IPv6 multicast routing. """ - name = "XORP_PIMSM6" + name: str = "XORP_PIMSM6" @classmethod - def generatexorpconfig(cls, node): - cfg = cls.mfea("mfea6", node.get_ifaces()) - + def generate_xorp_config(cls, node: CoreNode) -> str: + cfg = cls.mfea("mfea6", node) cfg += "\nprotocols {\n" cfg += " mld {\n" names = [] @@ -396,7 +386,6 @@ class XorpPimSm6(XorpService): cfg += "\t}\n" cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " pimsm6 {\n" @@ -419,10 +408,8 @@ class XorpPimSm6(XorpService): cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" - cfg += " }\n" cfg += "}\n" - cfg += "\nprotocols {\n" cfg += " fib2mrib {\n" cfg += "\tdisable: false\n" @@ -436,12 +423,12 @@ class XorpOlsr(XorpService): OLSR IPv4 unicast MANET routing. """ - name = "XORP_OLSR" + name: str = "XORP_OLSR" @classmethod - def generatexorpconfig(cls, node): + def generate_xorp_config(cls, node: CoreNode) -> str: cfg = cls.fea("unicast-forwarding4") - rtrid = cls.routerid(node) + rtrid = cls.router_id(node) cfg += "\nprotocols {\n" cfg += " olsr4 {\n" cfg += "\tmain-address: %s\n" % rtrid From b2ea8cbbf65c316ef2f06cd48f478df82028eb6d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 14:15:45 -0700 Subject: [PATCH 070/146] daemon: added type hinting throughout config services --- daemon/core/configservice/base.py | 16 +- daemon/core/configservice/dependencies.py | 14 +- daemon/core/configservice/manager.py | 11 +- .../configservices/frrservices/services.py | 122 +++---- .../configservices/nrlservices/services.py | 197 ++++++------ .../configservices/quaggaservices/services.py | 119 +++---- .../sercurityservices/services.py | 124 ++++---- daemon/core/configservices/simpleservice.py | 26 +- .../configservices/utilservices/services.py | 297 +++++++++--------- 9 files changed, 471 insertions(+), 455 deletions(-) diff --git a/daemon/core/configservice/base.py b/daemon/core/configservice/base.py index 82598988..bb97e321 100644 --- a/daemon/core/configservice/base.py +++ b/daemon/core/configservice/base.py @@ -14,7 +14,7 @@ from core.config import Configuration from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode -TEMPLATES_DIR = "templates" +TEMPLATES_DIR: str = "templates" class ConfigServiceMode(enum.Enum): @@ -33,10 +33,10 @@ class ConfigService(abc.ABC): """ # validation period in seconds, how frequent validation is attempted - validation_period = 0.5 + validation_period: float = 0.5 # time to wait in seconds for determining if service started successfully - validation_timer = 5 + validation_timer: int = 5 def __init__(self, node: CoreNode) -> None: """ @@ -44,13 +44,13 @@ class ConfigService(abc.ABC): :param node: node this service is assigned to """ - self.node = node + self.node: CoreNode = node class_file = inspect.getfile(self.__class__) templates_path = pathlib.Path(class_file).parent.joinpath(TEMPLATES_DIR) - self.templates = TemplateLookup(directories=templates_path) - self.config = {} - self.custom_templates = {} - self.custom_config = {} + self.templates: TemplateLookup = TemplateLookup(directories=templates_path) + self.config: Dict[str, Configuration] = {} + self.custom_templates: Dict[str, str] = {} + self.custom_config: Dict[str, str] = {} configs = self.default_configs[:] self._define_config(configs) diff --git a/daemon/core/configservice/dependencies.py b/daemon/core/configservice/dependencies.py index 92eede79..be1c45e7 100644 --- a/daemon/core/configservice/dependencies.py +++ b/daemon/core/configservice/dependencies.py @@ -1,5 +1,5 @@ import logging -from typing import TYPE_CHECKING, Dict, List +from typing import TYPE_CHECKING, Dict, List, Set if TYPE_CHECKING: from core.configservice.base import ConfigService @@ -17,9 +17,9 @@ class ConfigServiceDependencies: :param services: services for determining dependency sets """ # helpers to check validity - self.dependents = {} - self.started = set() - self.node_services = {} + self.dependents: Dict[str, Set[str]] = {} + self.started: Set[str] = set() + self.node_services: Dict[str, "ConfigService"] = {} for service in services.values(): self.node_services[service.name] = service for dependency in service.dependencies: @@ -27,9 +27,9 @@ class ConfigServiceDependencies: dependents.add(service.name) # used to find paths - self.path = [] - self.visited = set() - self.visiting = set() + self.path: List["ConfigService"] = [] + self.visited: Set[str] = set() + self.visiting: Set[str] = set() def startup_paths(self) -> List[List["ConfigService"]]: """ diff --git a/daemon/core/configservice/manager.py b/daemon/core/configservice/manager.py index 1f806f7b..ecea6e68 100644 --- a/daemon/core/configservice/manager.py +++ b/daemon/core/configservice/manager.py @@ -1,6 +1,6 @@ import logging import pathlib -from typing import List, Type +from typing import Dict, List, Type from core import utils from core.configservice.base import ConfigService @@ -16,7 +16,7 @@ class ConfigServiceManager: """ Create a ConfigServiceManager instance. """ - self.services = {} + self.services: Dict[str, Type[ConfigService]] = {} def get_service(self, name: str) -> Type[ConfigService]: """ @@ -31,7 +31,7 @@ class ConfigServiceManager: raise CoreError(f"service does not exit {name}") return service_class - def add(self, service: ConfigService) -> None: + def add(self, service: Type[ConfigService]) -> None: """ Add service to manager, checking service requirements have been met. @@ -40,7 +40,9 @@ class ConfigServiceManager: :raises CoreError: when service is a duplicate or has unmet executables """ name = service.name - logging.debug("loading service: class(%s) name(%s)", service.__class__, name) + logging.debug( + "loading service: class(%s) name(%s)", service.__class__.__name__, name + ) # avoid duplicate services if name in self.services: @@ -73,7 +75,6 @@ class ConfigServiceManager: logging.debug("loading config services from: %s", subdir) services = utils.load_classes(str(subdir), ConfigService) for service in services: - logging.debug("found service: %s", service) try: self.add(service) except CoreError as e: diff --git a/daemon/core/configservices/frrservices/services.py b/daemon/core/configservices/frrservices/services.py index 8764e32c..2e24b40a 100644 --- a/daemon/core/configservices/frrservices/services.py +++ b/daemon/core/configservices/frrservices/services.py @@ -1,16 +1,17 @@ import abc -from typing import Any, Dict +from typing import Any, Dict, List import netaddr from core import constants +from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emane.nodes import EmaneNet from core.nodes.base import CoreNodeBase from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode -GROUP = "FRR" +GROUP: str = "FRR" def has_mtu_mismatch(iface: CoreInterface) -> bool: @@ -29,7 +30,7 @@ def has_mtu_mismatch(iface: CoreInterface) -> bool: return False -def get_min_mtu(iface): +def get_min_mtu(iface: CoreInterface) -> int: """ Helper to discover the minimum MTU of interfaces linked with the given interface. @@ -56,23 +57,23 @@ def get_router_id(node: CoreNodeBase) -> str: class FRRZebra(ConfigService): - name = "FRRzebra" - group = GROUP - directories = ["/usr/local/etc/frr", "/var/run/frr", "/var/log/frr"] - files = [ + name: str = "FRRzebra" + group: str = GROUP + directories: List[str] = ["/usr/local/etc/frr", "/var/run/frr", "/var/log/frr"] + files: List[str] = [ "/usr/local/etc/frr/frr.conf", "frrboot.sh", "/usr/local/etc/frr/vtysh.conf", "/usr/local/etc/frr/daemons", ] - executables = ["zebra"] - dependencies = [] - startup = ["sh frrboot.sh zebra"] - validate = ["pidof zebra"] - shutdown = ["killall zebra"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + executables: List[str] = ["zebra"] + dependencies: List[str] = [] + startup: List[str] = ["sh frrboot.sh zebra"] + validate: List[str] = ["pidof zebra"] + shutdown: List[str] = ["killall zebra"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: frr_conf = self.files[0] @@ -89,6 +90,8 @@ class FRRZebra(ConfigService): for service in self.node.config_services.values(): if self.name not in service.dependencies: continue + if not isinstance(service, FrrService): + continue if service.ipv4_routing: want_ip4 = True if service.ipv6_routing: @@ -121,19 +124,19 @@ class FRRZebra(ConfigService): class FrrService(abc.ABC): - group = GROUP - directories = [] - files = [] - executables = [] - dependencies = ["FRRzebra"] - startup = [] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} - ipv4_routing = False - ipv6_routing = False + group: str = GROUP + directories: List[str] = [] + files: List[str] = [] + executables: List[str] = [] + dependencies: List[str] = ["FRRzebra"] + startup: List[str] = [] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} + ipv4_routing: bool = False + ipv6_routing: bool = False @abc.abstractmethod def frr_iface_config(self, iface: CoreInterface) -> str: @@ -151,11 +154,10 @@ class FRROspfv2(FrrService, ConfigService): unified frr.conf file. """ - name = "FRROSPFv2" - startup = () - shutdown = ["killall ospfd"] - validate = ["pidof ospfd"] - ipv4_routing = True + name: str = "FRROSPFv2" + shutdown: List[str] = ["killall ospfd"] + validate: List[str] = ["pidof ospfd"] + ipv4_routing: bool = True def frr_config(self) -> str: router_id = get_router_id(self.node) @@ -190,11 +192,11 @@ class FRROspfv3(FrrService, ConfigService): unified frr.conf file. """ - name = "FRROSPFv3" - shutdown = ["killall ospf6d"] - validate = ["pidof ospf6d"] - ipv4_routing = True - ipv6_routing = True + name: str = "FRROSPFv3" + shutdown: List[str] = ["killall ospf6d"] + validate: List[str] = ["pidof ospf6d"] + ipv4_routing: bool = True + ipv6_routing: bool = True def frr_config(self) -> str: router_id = get_router_id(self.node) @@ -227,12 +229,12 @@ class FRRBgp(FrrService, ConfigService): having the same AS number. """ - name = "FRRBGP" - shutdown = ["killall bgpd"] - validate = ["pidof bgpd"] - custom_needed = True - ipv4_routing = True - ipv6_routing = True + name: str = "FRRBGP" + shutdown: List[str] = ["killall bgpd"] + validate: List[str] = ["pidof bgpd"] + custom_needed: bool = True + ipv4_routing: bool = True + ipv6_routing: bool = True def frr_config(self) -> str: router_id = get_router_id(self.node) @@ -257,10 +259,10 @@ class FRRRip(FrrService, ConfigService): The RIP service provides IPv4 routing for wired networks. """ - name = "FRRRIP" - shutdown = ["killall ripd"] - validate = ["pidof ripd"] - ipv4_routing = True + name: str = "FRRRIP" + shutdown: List[str] = ["killall ripd"] + validate: List[str] = ["pidof ripd"] + ipv4_routing: bool = True def frr_config(self) -> str: text = """ @@ -282,10 +284,10 @@ class FRRRipng(FrrService, ConfigService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "FRRRIPNG" - shutdown = ["killall ripngd"] - validate = ["pidof ripngd"] - ipv6_routing = True + name: str = "FRRRIPNG" + shutdown: List[str] = ["killall ripngd"] + validate: List[str] = ["pidof ripngd"] + ipv6_routing: bool = True def frr_config(self) -> str: text = """ @@ -308,10 +310,10 @@ class FRRBabel(FrrService, ConfigService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "FRRBabel" - shutdown = ["killall babeld"] - validate = ["pidof babeld"] - ipv6_routing = True + name: str = "FRRBabel" + shutdown: List[str] = ["killall babeld"] + validate: List[str] = ["pidof babeld"] + ipv6_routing: bool = True def frr_config(self) -> str: ifnames = [] @@ -348,10 +350,10 @@ class FRRpimd(FrrService, ConfigService): PIM multicast routing based on XORP. """ - name = "FRRpimd" - shutdown = ["killall pimd"] - validate = ["pidof pimd"] - ipv4_routing = True + name: str = "FRRpimd" + shutdown: List[str] = ["killall pimd"] + validate: List[str] = ["pidof pimd"] + ipv4_routing: bool = True def frr_config(self) -> str: ifname = "eth0" diff --git a/daemon/core/configservices/nrlservices/services.py b/daemon/core/configservices/nrlservices/services.py index ca95b8f6..0a5e8baf 100644 --- a/daemon/core/configservices/nrlservices/services.py +++ b/daemon/core/configservices/nrlservices/services.py @@ -1,26 +1,27 @@ -from typing import Any, Dict +from typing import Any, Dict, List import netaddr from core import utils +from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode -GROUP = "ProtoSvc" +GROUP: str = "ProtoSvc" class MgenSinkService(ConfigService): - name = "MGEN_Sink" - group = GROUP - directories = [] - files = ["mgensink.sh", "sink.mgen"] - executables = ["mgen"] - dependencies = [] - startup = ["sh mgensink.sh"] - validate = ["pidof mgen"] - shutdown = ["killall mgen"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "MGEN_Sink" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["mgensink.sh", "sink.mgen"] + executables: List[str] = ["mgen"] + dependencies: List[str] = [] + startup: List[str] = ["sh mgensink.sh"] + validate: List[str] = ["pidof mgen"] + shutdown: List[str] = ["killall mgen"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifnames = [] @@ -31,18 +32,18 @@ class MgenSinkService(ConfigService): class NrlNhdp(ConfigService): - name = "NHDP" - group = GROUP - directories = [] - files = ["nrlnhdp.sh"] - executables = ["nrlnhdp"] - dependencies = [] - startup = ["sh nrlnhdp.sh"] - validate = ["pidof nrlnhdp"] - shutdown = ["killall nrlnhdp"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "NHDP" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["nrlnhdp.sh"] + executables: List[str] = ["nrlnhdp"] + dependencies: List[str] = [] + startup: List[str] = ["sh nrlnhdp.sh"] + validate: List[str] = ["pidof nrlnhdp"] + shutdown: List[str] = ["killall nrlnhdp"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services @@ -53,18 +54,18 @@ class NrlNhdp(ConfigService): class NrlSmf(ConfigService): - name = "SMF" - group = GROUP - directories = [] - files = ["startsmf.sh"] - executables = ["nrlsmf", "killall"] - dependencies = [] - startup = ["sh startsmf.sh"] - validate = ["pidof nrlsmf"] - shutdown = ["killall nrlsmf"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "SMF" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["startsmf.sh"] + executables: List[str] = ["nrlsmf", "killall"] + dependencies: List[str] = [] + startup: List[str] = ["sh startsmf.sh"] + validate: List[str] = ["pidof nrlsmf"] + shutdown: List[str] = ["killall nrlsmf"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_arouted = "arouted" in self.node.config_services @@ -91,18 +92,18 @@ class NrlSmf(ConfigService): class NrlOlsr(ConfigService): - name = "OLSR" - group = GROUP - directories = [] - files = ["nrlolsrd.sh"] - executables = ["nrlolsrd"] - dependencies = [] - startup = ["sh nrlolsrd.sh"] - validate = ["pidof nrlolsrd"] - shutdown = ["killall nrlolsrd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "OLSR" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["nrlolsrd.sh"] + executables: List[str] = ["nrlolsrd"] + dependencies: List[str] = [] + startup: List[str] = ["sh nrlolsrd.sh"] + validate: List[str] = ["pidof nrlolsrd"] + shutdown: List[str] = ["killall nrlolsrd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services @@ -115,18 +116,18 @@ class NrlOlsr(ConfigService): class NrlOlsrv2(ConfigService): - name = "OLSRv2" - group = GROUP - directories = [] - files = ["nrlolsrv2.sh"] - executables = ["nrlolsrv2"] - dependencies = [] - startup = ["sh nrlolsrv2.sh"] - validate = ["pidof nrlolsrv2"] - shutdown = ["killall nrlolsrv2"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "OLSRv2" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["nrlolsrv2.sh"] + executables: List[str] = ["nrlolsrv2"] + dependencies: List[str] = [] + startup: List[str] = ["sh nrlolsrv2.sh"] + validate: List[str] = ["pidof nrlolsrv2"] + shutdown: List[str] = ["killall nrlolsrv2"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services @@ -137,18 +138,18 @@ class NrlOlsrv2(ConfigService): class OlsrOrg(ConfigService): - name = "OLSRORG" - group = GROUP - directories = ["/etc/olsrd"] - files = ["olsrd.sh", "/etc/olsrd/olsrd.conf"] - executables = ["olsrd"] - dependencies = [] - startup = ["sh olsrd.sh"] - validate = ["pidof olsrd"] - shutdown = ["killall olsrd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "OLSRORG" + group: str = GROUP + directories: List[str] = ["/etc/olsrd"] + files: List[str] = ["olsrd.sh", "/etc/olsrd/olsrd.conf"] + executables: List[str] = ["olsrd"] + dependencies: List[str] = [] + startup: List[str] = ["sh olsrd.sh"] + validate: List[str] = ["pidof olsrd"] + shutdown: List[str] = ["killall olsrd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: has_smf = "SMF" in self.node.config_services @@ -159,33 +160,33 @@ class OlsrOrg(ConfigService): class MgenActor(ConfigService): - name = "MgenActor" - group = GROUP - directories = [] - files = ["start_mgen_actor.sh"] - executables = ["mgen"] - dependencies = [] - startup = ["sh start_mgen_actor.sh"] - validate = ["pidof mgen"] - shutdown = ["killall mgen"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "MgenActor" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["start_mgen_actor.sh"] + executables: List[str] = ["mgen"] + dependencies: List[str] = [] + startup: List[str] = ["sh start_mgen_actor.sh"] + validate: List[str] = ["pidof mgen"] + shutdown: List[str] = ["killall mgen"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class Arouted(ConfigService): - name = "arouted" - group = GROUP - directories = [] - files = ["startarouted.sh"] - executables = ["arouted"] - dependencies = [] - startup = ["sh startarouted.sh"] - validate = ["pidof arouted"] - shutdown = ["pkill arouted"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "arouted" + group: str = GROUP + directories: List[str] = [] + files: List[str] = ["startarouted.sh"] + executables: List[str] = ["arouted"] + dependencies: List[str] = [] + startup: List[str] = ["sh startarouted.sh"] + validate: List[str] = ["pidof arouted"] + shutdown: List[str] = ["pkill arouted"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ip4_prefix = None diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 19e21476..40a1d7d3 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -1,17 +1,18 @@ import abc import logging -from typing import Any, Dict +from typing import Any, Dict, List import netaddr from core import constants +from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emane.nodes import EmaneNet from core.nodes.base import CoreNodeBase from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode -GROUP = "Quagga" +GROUP: str = "Quagga" def has_mtu_mismatch(iface: CoreInterface) -> bool: @@ -57,22 +58,22 @@ def get_router_id(node: CoreNodeBase) -> str: class Zebra(ConfigService): - name = "zebra" - group = GROUP - directories = ["/usr/local/etc/quagga", "/var/run/quagga"] - files = [ + name: str = "zebra" + group: str = GROUP + directories: List[str] = ["/usr/local/etc/quagga", "/var/run/quagga"] + files: List[str] = [ "/usr/local/etc/quagga/Quagga.conf", "quaggaboot.sh", "/usr/local/etc/quagga/vtysh.conf", ] - executables = ["zebra"] - dependencies = [] - startup = ["sh quaggaboot.sh zebra"] - validate = ["pidof zebra"] - shutdown = ["killall zebra"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + executables: List[str] = ["zebra"] + dependencies: List[str] = [] + startup: List[str] = ["sh quaggaboot.sh zebra"] + validate: List[str] = ["pidof zebra"] + shutdown: List[str] = ["killall zebra"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: quagga_bin_search = self.node.session.options.get_config( @@ -90,6 +91,8 @@ class Zebra(ConfigService): for service in self.node.config_services.values(): if self.name not in service.dependencies: continue + if not isinstance(service, QuaggaService): + continue if service.ipv4_routing: want_ip4 = True if service.ipv6_routing: @@ -122,19 +125,19 @@ class Zebra(ConfigService): class QuaggaService(abc.ABC): - group = GROUP - directories = [] - files = [] - executables = [] - dependencies = ["zebra"] - startup = [] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} - ipv4_routing = False - ipv6_routing = False + group: str = GROUP + directories: List[str] = [] + files: List[str] = [] + executables: List[str] = [] + dependencies: List[str] = ["zebra"] + startup: List[str] = [] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} + ipv4_routing: bool = False + ipv6_routing: bool = False @abc.abstractmethod def quagga_iface_config(self, iface: CoreInterface) -> str: @@ -152,10 +155,10 @@ class Ospfv2(QuaggaService, ConfigService): unified Quagga.conf file. """ - name = "OSPFv2" - validate = ["pidof ospfd"] - shutdown = ["killall ospfd"] - ipv4_routing = True + name: str = "OSPFv2" + validate: List[str] = ["pidof ospfd"] + shutdown: List[str] = ["killall ospfd"] + ipv4_routing: bool = True def quagga_iface_config(self, iface: CoreInterface) -> str: if has_mtu_mismatch(iface): @@ -190,11 +193,11 @@ class Ospfv3(QuaggaService, ConfigService): unified Quagga.conf file. """ - name = "OSPFv3" - shutdown = ("killall ospf6d",) - validate = ("pidof ospf6d",) - ipv4_routing = True - ipv6_routing = True + name: str = "OSPFv3" + shutdown: List[str] = ["killall ospf6d"] + validate: List[str] = ["pidof ospf6d"] + ipv4_routing: bool = True + ipv6_routing: bool = True def quagga_iface_config(self, iface: CoreInterface) -> str: mtu = get_min_mtu(iface) @@ -229,7 +232,7 @@ class Ospfv3mdr(Ospfv3): unified Quagga.conf file. """ - name = "OSPFv3MDR" + name: str = "OSPFv3MDR" def data(self) -> Dict[str, Any]: for iface in self.node.get_ifaces(): @@ -262,11 +265,11 @@ class Bgp(QuaggaService, ConfigService): having the same AS number. """ - name = "BGP" - shutdown = ["killall bgpd"] - validate = ["pidof bgpd"] - ipv4_routing = True - ipv6_routing = True + name: str = "BGP" + shutdown: List[str] = ["killall bgpd"] + validate: List[str] = ["pidof bgpd"] + ipv4_routing: bool = True + ipv6_routing: bool = True def quagga_config(self) -> str: return "" @@ -291,10 +294,10 @@ class Rip(QuaggaService, ConfigService): The RIP service provides IPv4 routing for wired networks. """ - name = "RIP" - shutdown = ["killall ripd"] - validate = ["pidof ripd"] - ipv4_routing = True + name: str = "RIP" + shutdown: List[str] = ["killall ripd"] + validate: List[str] = ["pidof ripd"] + ipv4_routing: bool = True def quagga_config(self) -> str: text = """ @@ -316,10 +319,10 @@ class Ripng(QuaggaService, ConfigService): The RIP NG service provides IPv6 routing for wired networks. """ - name = "RIPNG" - shutdown = ["killall ripngd"] - validate = ["pidof ripngd"] - ipv6_routing = True + name: str = "RIPNG" + shutdown: List[str] = ["killall ripngd"] + validate: List[str] = ["pidof ripngd"] + ipv6_routing: bool = True def quagga_config(self) -> str: text = """ @@ -342,10 +345,10 @@ class Babel(QuaggaService, ConfigService): protocol for IPv6 and IPv4 with fast convergence properties. """ - name = "Babel" - shutdown = ["killall babeld"] - validate = ["pidof babeld"] - ipv6_routing = True + name: str = "Babel" + shutdown: List[str] = ["killall babeld"] + validate: List[str] = ["pidof babeld"] + ipv6_routing: bool = True def quagga_config(self) -> str: ifnames = [] @@ -382,10 +385,10 @@ class Xpimd(QuaggaService, ConfigService): PIM multicast routing based on XORP. """ - name = "Xpimd" - shutdown = ["killall xpimd"] - validate = ["pidof xpimd"] - ipv4_routing = True + name: str = "Xpimd" + shutdown: List[str] = ["killall xpimd"] + validate: List[str] = ["pidof xpimd"] + ipv4_routing: bool = True def quagga_config(self) -> str: ifname = "eth0" diff --git a/daemon/core/configservices/sercurityservices/services.py b/daemon/core/configservices/sercurityservices/services.py index 6e92bf62..5766b0db 100644 --- a/daemon/core/configservices/sercurityservices/services.py +++ b/daemon/core/configservices/sercurityservices/services.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any, Dict, List import netaddr @@ -6,21 +6,21 @@ from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emulator.enumerations import ConfigDataTypes -GROUP_NAME = "Security" +GROUP_NAME: str = "Security" class VpnClient(ConfigService): - name = "VPNClient" - group = GROUP_NAME - directories = [] - files = ["vpnclient.sh"] - executables = ["openvpn", "ip", "killall"] - dependencies = [] - startup = ["sh vpnclient.sh"] - validate = ["pidof openvpn"] - shutdown = ["killall openvpn"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [ + name: str = "VPNClient" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["vpnclient.sh"] + executables: List[str] = ["openvpn", "ip", "killall"] + dependencies: List[str] = [] + startup: List[str] = ["sh vpnclient.sh"] + validate: List[str] = ["pidof openvpn"] + shutdown: List[str] = ["killall openvpn"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [ Configuration( _id="keydir", _type=ConfigDataTypes.STRING, @@ -40,21 +40,21 @@ class VpnClient(ConfigService): default="10.0.2.10", ), ] - modes = {} + modes: Dict[str, Dict[str, str]] = {} class VpnServer(ConfigService): - name = "VPNServer" - group = GROUP_NAME - directories = [] - files = ["vpnserver.sh"] - executables = ["openvpn", "ip", "killall"] - dependencies = [] - startup = ["sh vpnserver.sh"] - validate = ["pidof openvpn"] - shutdown = ["killall openvpn"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [ + name: str = "VPNServer" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["vpnserver.sh"] + executables: List[str] = ["openvpn", "ip", "killall"] + dependencies: List[str] = [] + startup: List[str] = ["sh vpnserver.sh"] + validate: List[str] = ["pidof openvpn"] + shutdown: List[str] = ["killall openvpn"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [ Configuration( _id="keydir", _type=ConfigDataTypes.STRING, @@ -74,7 +74,7 @@ class VpnServer(ConfigService): default="10.0.200.0", ), ] - modes = {} + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: address = None @@ -87,48 +87,48 @@ class VpnServer(ConfigService): class IPsec(ConfigService): - name = "IPsec" - group = GROUP_NAME - directories = [] - files = ["ipsec.sh"] - executables = ["racoon", "ip", "setkey", "killall"] - dependencies = [] - startup = ["sh ipsec.sh"] - validate = ["pidof racoon"] - shutdown = ["killall racoon"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "IPsec" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["ipsec.sh"] + executables: List[str] = ["racoon", "ip", "setkey", "killall"] + dependencies: List[str] = [] + startup: List[str] = ["sh ipsec.sh"] + validate: List[str] = ["pidof racoon"] + shutdown: List[str] = ["killall racoon"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class Firewall(ConfigService): - name = "Firewall" - group = GROUP_NAME - directories = [] - files = ["firewall.sh"] - executables = ["iptables"] - dependencies = [] - startup = ["sh firewall.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "Firewall" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["firewall.sh"] + executables: List[str] = ["iptables"] + dependencies: List[str] = [] + startup: List[str] = ["sh firewall.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class Nat(ConfigService): - name = "NAT" - group = GROUP_NAME - directories = [] - files = ["nat.sh"] - executables = ["iptables"] - dependencies = [] - startup = ["sh nat.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "NAT" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["nat.sh"] + executables: List[str] = ["iptables"] + dependencies: List[str] = [] + startup: List[str] = ["sh nat.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifnames = [] diff --git a/daemon/core/configservices/simpleservice.py b/daemon/core/configservices/simpleservice.py index e727fe82..c2e7242f 100644 --- a/daemon/core/configservices/simpleservice.py +++ b/daemon/core/configservices/simpleservice.py @@ -1,20 +1,22 @@ +from typing import Dict, List + from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emulator.enumerations import ConfigDataTypes class SimpleService(ConfigService): - name = "Simple" - group = "SimpleGroup" - directories = ["/etc/quagga", "/usr/local/lib"] - files = ["test1.sh", "test2.sh"] - executables = [] - dependencies = [] - startup = [] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [ + name: str = "Simple" + group: str = "SimpleGroup" + directories: List[str] = ["/etc/quagga", "/usr/local/lib"] + files: List[str] = ["test1.sh", "test2.sh"] + executables: List[str] = [] + dependencies: List[str] = [] + startup: List[str] = [] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [ Configuration(_id="value1", _type=ConfigDataTypes.STRING, label="Text"), Configuration(_id="value2", _type=ConfigDataTypes.BOOL, label="Boolean"), Configuration( @@ -24,7 +26,7 @@ class SimpleService(ConfigService): options=["value1", "value2", "value3"], ), ] - modes = { + modes: Dict[str, Dict[str, str]] = { "mode1": {"value1": "value1", "value2": "0", "value3": "value2"}, "mode2": {"value1": "value2", "value2": "1", "value3": "value3"}, "mode3": {"value1": "value3", "value2": "0", "value3": "value1"}, diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index 5aa3bb54..983f6cff 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -1,26 +1,27 @@ -from typing import Any, Dict +from typing import Any, Dict, List import netaddr from core import utils +from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode GROUP_NAME = "Utility" class DefaultRouteService(ConfigService): - name = "DefaultRoute" - group = GROUP_NAME - directories = [] - files = ["defaultroute.sh"] - executables = ["ip"] - dependencies = [] - startup = ["sh defaultroute.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "DefaultRoute" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["defaultroute.sh"] + executables: List[str] = ["ip"] + dependencies: List[str] = [] + startup: List[str] = ["sh defaultroute.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: # only add default routes for linked routing nodes @@ -37,18 +38,18 @@ class DefaultRouteService(ConfigService): class DefaultMulticastRouteService(ConfigService): - name = "DefaultMulticastRoute" - group = GROUP_NAME - directories = [] - files = ["defaultmroute.sh"] - executables = [] - dependencies = [] - startup = ["sh defaultmroute.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "DefaultMulticastRoute" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["defaultmroute.sh"] + executables: List[str] = [] + dependencies: List[str] = [] + startup: List[str] = ["sh defaultmroute.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifname = None @@ -59,18 +60,18 @@ class DefaultMulticastRouteService(ConfigService): class StaticRouteService(ConfigService): - name = "StaticRoute" - group = GROUP_NAME - directories = [] - files = ["staticroute.sh"] - executables = [] - dependencies = [] - startup = ["sh staticroute.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "StaticRoute" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["staticroute.sh"] + executables: List[str] = [] + dependencies: List[str] = [] + startup: List[str] = ["sh staticroute.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: routes = [] @@ -88,18 +89,18 @@ class StaticRouteService(ConfigService): class IpForwardService(ConfigService): - name = "IPForward" - group = GROUP_NAME - directories = [] - files = ["ipforward.sh"] - executables = ["sysctl"] - dependencies = [] - startup = ["sh ipforward.sh"] - validate = [] - shutdown = [] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "IPForward" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["ipforward.sh"] + executables: List[str] = ["sysctl"] + dependencies: List[str] = [] + startup: List[str] = ["sh ipforward.sh"] + validate: List[str] = [] + shutdown: List[str] = [] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: devnames = [] @@ -110,18 +111,18 @@ class IpForwardService(ConfigService): class SshService(ConfigService): - name = "SSH" - group = GROUP_NAME - directories = ["/etc/ssh", "/var/run/sshd"] - files = ["startsshd.sh", "/etc/ssh/sshd_config"] - executables = ["sshd"] - dependencies = [] - startup = ["sh startsshd.sh"] - validate = [] - shutdown = ["killall sshd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "SSH" + group: str = GROUP_NAME + directories: List[str] = ["/etc/ssh", "/var/run/sshd"] + files: List[str] = ["startsshd.sh", "/etc/ssh/sshd_config"] + executables: List[str] = ["sshd"] + dependencies: List[str] = [] + startup: List[str] = ["sh startsshd.sh"] + validate: List[str] = [] + shutdown: List[str] = ["killall sshd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: return dict( @@ -132,18 +133,18 @@ class SshService(ConfigService): class DhcpService(ConfigService): - name = "DHCP" - group = GROUP_NAME - directories = ["/etc/dhcp", "/var/lib/dhcp"] - files = ["/etc/dhcp/dhcpd.conf"] - executables = ["dhcpd"] - dependencies = [] - startup = ["touch /var/lib/dhcp/dhcpd.leases", "dhcpd"] - validate = ["pidof dhcpd"] - shutdown = ["killall dhcpd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "DHCP" + group: str = GROUP_NAME + directories: List[str] = ["/etc/dhcp", "/var/lib/dhcp"] + files: List[str] = ["/etc/dhcp/dhcpd.conf"] + executables: List[str] = ["dhcpd"] + dependencies: List[str] = [] + startup: List[str] = ["touch /var/lib/dhcp/dhcpd.leases", "dhcpd"] + validate: List[str] = ["pidof dhcpd"] + shutdown: List[str] = ["killall dhcpd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: subnets = [] @@ -161,18 +162,18 @@ class DhcpService(ConfigService): class DhcpClientService(ConfigService): - name = "DHCPClient" - group = GROUP_NAME - directories = [] - files = ["startdhcpclient.sh"] - executables = ["dhclient"] - dependencies = [] - startup = ["sh startdhcpclient.sh"] - validate = ["pidof dhclient"] - shutdown = ["killall dhclient"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "DHCPClient" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["startdhcpclient.sh"] + executables: List[str] = ["dhclient"] + dependencies: List[str] = [] + startup: List[str] = ["sh startdhcpclient.sh"] + validate: List[str] = ["pidof dhclient"] + shutdown: List[str] = ["killall dhclient"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifnames = [] @@ -182,33 +183,33 @@ class DhcpClientService(ConfigService): class FtpService(ConfigService): - name = "FTP" - group = GROUP_NAME - directories = ["/var/run/vsftpd/empty", "/var/ftp"] - files = ["vsftpd.conf"] - executables = ["vsftpd"] - dependencies = [] - startup = ["vsftpd ./vsftpd.conf"] - validate = ["pidof vsftpd"] - shutdown = ["killall vsftpd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "FTP" + group: str = GROUP_NAME + directories: List[str] = ["/var/run/vsftpd/empty", "/var/ftp"] + files: List[str] = ["vsftpd.conf"] + executables: List[str] = ["vsftpd"] + dependencies: List[str] = [] + startup: List[str] = ["vsftpd ./vsftpd.conf"] + validate: List[str] = ["pidof vsftpd"] + shutdown: List[str] = ["killall vsftpd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class PcapService(ConfigService): - name = "pcap" - group = GROUP_NAME - directories = [] - files = ["pcap.sh"] - executables = ["tcpdump"] - dependencies = [] - startup = ["sh pcap.sh start"] - validate = ["pidof tcpdump"] - shutdown = ["sh pcap.sh stop"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "pcap" + group: str = GROUP_NAME + directories: List[str] = [] + files: List[str] = ["pcap.sh"] + executables: List[str] = ["tcpdump"] + dependencies: List[str] = [] + startup: List[str] = ["sh pcap.sh start"] + validate: List[str] = ["pidof tcpdump"] + shutdown: List[str] = ["sh pcap.sh stop"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifnames = [] @@ -218,18 +219,20 @@ class PcapService(ConfigService): class RadvdService(ConfigService): - name = "radvd" - group = GROUP_NAME - directories = ["/etc/radvd"] - files = ["/etc/radvd/radvd.conf"] - executables = ["radvd"] - dependencies = [] - startup = ["radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log"] - validate = ["pidof radvd"] - shutdown = ["pkill radvd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "radvd" + group: str = GROUP_NAME + directories: List[str] = ["/etc/radvd"] + files: List[str] = ["/etc/radvd/radvd.conf"] + executables: List[str] = ["radvd"] + dependencies: List[str] = [] + startup: List[str] = [ + "radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log" + ] + validate: List[str] = ["pidof radvd"] + shutdown: List[str] = ["pkill radvd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifaces = [] @@ -246,24 +249,24 @@ class RadvdService(ConfigService): class AtdService(ConfigService): - name = "atd" - group = GROUP_NAME - directories = ["/var/spool/cron/atjobs", "/var/spool/cron/atspool"] - files = ["startatd.sh"] - executables = ["atd"] - dependencies = [] - startup = ["sh startatd.sh"] - validate = ["pidof atd"] - shutdown = ["pkill atd"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + name: str = "atd" + group: str = GROUP_NAME + directories: List[str] = ["/var/spool/cron/atjobs", "/var/spool/cron/atspool"] + files: List[str] = ["startatd.sh"] + executables: List[str] = ["atd"] + dependencies: List[str] = [] + startup: List[str] = ["sh startatd.sh"] + validate: List[str] = ["pidof atd"] + shutdown: List[str] = ["pkill atd"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} class HttpService(ConfigService): - name = "HTTP" - group = GROUP_NAME - directories = [ + name: str = "HTTP" + group: str = GROUP_NAME + directories: List[str] = [ "/etc/apache2", "/var/run/apache2", "/var/log/apache2", @@ -271,15 +274,19 @@ class HttpService(ConfigService): "/var/lock/apache2", "/var/www", ] - files = ["/etc/apache2/apache2.conf", "/etc/apache2/envvars", "/var/www/index.html"] - executables = ["apache2ctl"] - dependencies = [] - startup = ["chown www-data /var/lock/apache2", "apache2ctl start"] - validate = ["pidof apache2"] - shutdown = ["apache2ctl stop"] - validation_mode = ConfigServiceMode.BLOCKING - default_configs = [] - modes = {} + files: List[str] = [ + "/etc/apache2/apache2.conf", + "/etc/apache2/envvars", + "/var/www/index.html", + ] + executables: List[str] = ["apache2ctl"] + dependencies: List[str] = [] + startup: List[str] = ["chown www-data /var/lock/apache2", "apache2ctl start"] + validate: List[str] = ["pidof apache2"] + shutdown: List[str] = ["apache2ctl stop"] + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + default_configs: List[Configuration] = [] + modes: Dict[str, Dict[str, str]] = {} def data(self) -> Dict[str, Any]: ifaces = [] From ca2b1c9e4cb90f82380492bbea61dd59a06f987a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 18 Jun 2020 21:33:28 -0700 Subject: [PATCH 071/146] daemon: refactored all_link_data to links --- daemon/core/api/grpc/grpcutils.py | 6 +++--- daemon/core/api/tlv/corehandlers.py | 12 ++++++------ daemon/core/emane/nodes.py | 4 ++-- daemon/core/location/mobility.py | 4 ++-- daemon/core/nodes/base.py | 4 ++-- daemon/core/nodes/network.py | 12 ++++++------ daemon/core/plugins/sdt.py | 2 +- daemon/core/xml/corexml.py | 2 +- daemon/tests/test_core.py | 2 +- daemon/tests/test_grpc.py | 14 +++++++------- daemon/tests/test_gui.py | 22 +++++++++++----------- daemon/tests/test_links.py | 6 +++--- daemon/tests/test_xml.py | 8 ++++---- 13 files changed, 49 insertions(+), 49 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index d95b7555..2c13315c 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -270,9 +270,9 @@ def get_links(node: NodeBase): :return: protobuf links """ links = [] - for link_data in node.all_link_data(): - link = convert_link(link_data) - links.append(link) + for link in node.links(): + link_proto = convert_link(link) + links.append(link_proto) return links diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index d01f15a3..bb4f2ecd 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -1824,16 +1824,16 @@ class CoreHandler(socketserver.BaseRequestHandler): Return API messages that describe the current session. """ # find all nodes and links - links_data = [] + all_links = [] with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] self.session.broadcast_node(node, MessageFlags.ADD) - node_links = node.all_link_data(flags=MessageFlags.ADD) - links_data.extend(node_links) + links = node.links(flags=MessageFlags.ADD) + all_links.extend(links) - for link_data in links_data: - self.session.broadcast_link(link_data) + for link in all_links: + self.session.broadcast_link(link) # send mobility model info for node_id in self.session.mobility.nodes(): @@ -1940,7 +1940,7 @@ class CoreHandler(socketserver.BaseRequestHandler): node_count = self.session.get_node_count() logging.info( - "informed GUI about %d nodes and %d links", node_count, len(links_data) + "informed GUI about %d nodes and %d links", node_count, len(all_links) ) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index c28f1382..9173fbfc 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -241,8 +241,8 @@ class EmaneNet(CoreNetworkBase): event.append(nemid, latitude=lat, longitude=lon, altitude=alt) self.session.emane.service.publish(0, event) - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: - links = super().all_link_data(flags) + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + links = super().links(flags) # gather current emane links nem_ids = set(self.nemidmap.values()) emane_manager = self.session.emane diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 9bb2966e..f2e0f470 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -217,7 +217,7 @@ class WirelessModel(ConfigurableOptions): self.session: "Session" = session self.id: int = _id - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ May be used if the model can populate the GUI with wireless (green) link lines. @@ -509,7 +509,7 @@ class BasicRangeModel(WirelessModel): link_data = self.create_link_data(iface, iface2, message_type) self.session.broadcast_link(link_data) - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Return a list of wireless link messages for when the GUI reconnects. diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 4fc6b873..2c8ca06c 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -182,7 +182,7 @@ class NodeBase(abc.ABC): self.iface_id += 1 return iface_id - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build link data for this node. @@ -1021,7 +1021,7 @@ class CoreNetworkBase(NodeBase): with self._linked_lock: del self._linked[iface] - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build link data objects for this network. Each link object describes a link between this network and a node. diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index f20b6dfb..62443fb8 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -831,7 +831,7 @@ class CtrlNet(CoreNetwork): super().shutdown() - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Do not include CtrlNet in link messages describing this session. @@ -859,7 +859,7 @@ class PtpNet(CoreNetwork): raise CoreError("ptp links support at most 2 network interfaces") super().attach(iface) - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Build CORE API TLVs for a point-to-point link. One Link message describes this network. @@ -1054,17 +1054,17 @@ class WlanNode(CoreNetwork): for iface in self.get_ifaces(): iface.setposition() - def all_link_data(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: + def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ Retrieve all link data. :param flags: message flags :return: list of link data """ - all_links = super().all_link_data(flags) + links = super().links(flags) if self.model: - all_links.extend(self.model.all_link_data(flags)) - return all_links + links.extend(self.model.links(flags)) + return links class TunnelNode(GreTapBridge): diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 84c90730..ef36b0a4 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -225,7 +225,7 @@ class Sdt: self.add_node(node) for net in nets: - all_links = net.all_link_data(flags=MessageFlags.ADD) + all_links = net.links(flags=MessageFlags.ADD) for link_data in all_links: is_wireless = isinstance(net, (WlanNode, EmaneNet)) if is_wireless and link_data.node1_id == net.id: diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 190cf8f7..d3cc85d8 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -465,7 +465,7 @@ class CoreXmlWriter: self.write_device(node) # add known links - links.extend(node.all_link_data()) + links.extend(node.links()) return links def write_network(self, node: NodeBase) -> None: diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 2623b0df..c4465863 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -120,7 +120,7 @@ class TestCore: session.instantiate() # check link data gets generated - assert ptp_node.all_link_data(MessageFlags.ADD) + assert ptp_node.links(MessageFlags.ADD) # check common nets exist between linked nodes assert node1.commonnets(node2) diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 8abf33aa..a4efd6d9 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -555,7 +555,7 @@ class TestGrpc: session = grpc_server.coreemu.create_session() switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) - assert len(switch.all_link_data()) == 0 + assert len(switch.links()) == 0 # then iface = iface_helper.create_iface(node.id, 0) @@ -564,7 +564,7 @@ class TestGrpc: # then assert response.result is True - assert len(switch.all_link_data()) == 1 + assert len(switch.links()) == 1 def test_add_link_exception( self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelper @@ -589,7 +589,7 @@ class TestGrpc: iface = ip_prefixes.create_iface(node) session.add_link(node.id, switch.id, iface) options = core_pb2.LinkOptions(bandwidth=30000) - link = switch.all_link_data()[0] + link = switch.links()[0] assert options.bandwidth != link.options.bandwidth # then @@ -600,7 +600,7 @@ class TestGrpc: # then assert response.result is True - link = switch.all_link_data()[0] + link = switch.links()[0] assert options.bandwidth == link.options.bandwidth def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): @@ -618,7 +618,7 @@ class TestGrpc: if node.id not in {node1.id, node2.id}: link_node = node break - assert len(link_node.all_link_data()) == 1 + assert len(link_node.links()) == 1 # then with client.context_connect(): @@ -628,7 +628,7 @@ class TestGrpc: # then assert response.result is True - assert len(link_node.all_link_data()) == 0 + assert len(link_node.links()) == 0 def test_get_wlan_config(self, grpc_server: CoreGrpcServer): # given @@ -1029,7 +1029,7 @@ class TestGrpc: node = session.add_node(CoreNode) iface = ip_prefixes.create_iface(node) session.add_link(node.id, wlan.id, iface) - link_data = wlan.all_link_data()[0] + link_data = wlan.links()[0] queue = Queue() def handle_event(event_data): diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 8f01a2bf..a0b3bd8a 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -122,7 +122,7 @@ class TestGui: coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 def test_link_add_net_to_node(self, coretlv: CoreHandler): @@ -146,7 +146,7 @@ class TestGui: coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 def test_link_add_node_to_node(self, coretlv: CoreHandler): @@ -176,7 +176,7 @@ class TestGui: all_links = [] for node_id in coretlv.session.nodes: node = coretlv.session.nodes[node_id] - all_links += node.all_link_data() + all_links += node.links() assert len(all_links) == 1 def test_link_update(self, coretlv: CoreHandler): @@ -198,7 +198,7 @@ class TestGui: ) coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 link = all_links[0] assert link.options.bandwidth is None @@ -216,7 +216,7 @@ class TestGui: coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 link = all_links[0] assert link.options.bandwidth == bandwidth @@ -245,7 +245,7 @@ class TestGui: all_links = [] for node_id in coretlv.session.nodes: node = coretlv.session.nodes[node_id] - all_links += node.all_link_data() + all_links += node.links() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( @@ -262,7 +262,7 @@ class TestGui: all_links = [] for node_id in coretlv.session.nodes: node = coretlv.session.nodes[node_id] - all_links += node.all_link_data() + all_links += node.links() assert len(all_links) == 0 def test_link_delete_node_to_net(self, coretlv: CoreHandler): @@ -284,7 +284,7 @@ class TestGui: ) coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( @@ -298,7 +298,7 @@ class TestGui: coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 0 def test_link_delete_net_to_node(self, coretlv: CoreHandler): @@ -320,7 +320,7 @@ class TestGui: ) coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 1 message = coreapi.CoreLinkMessage.create( @@ -334,7 +334,7 @@ class TestGui: coretlv.handle_message(message) switch_node = coretlv.session.get_node(switch_id, SwitchNode) - all_links = switch_node.all_link_data() + all_links = switch_node.links() assert len(all_links) == 0 def test_session_update(self, coretlv: CoreHandler): diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 4078d8bc..535ad837 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -49,7 +49,7 @@ class TestLinks: session.add_link(node1.id, node2.id, iface1_data=iface1_data) # then - assert node2.all_link_data() + assert node2.links() assert node1.get_iface(iface1_data.id) def test_add_net_to_node(self, session: Session, ip_prefixes: IpPrefixes): @@ -62,7 +62,7 @@ class TestLinks: session.add_link(node1.id, node2.id, iface2_data=iface2_data) # then - assert node1.all_link_data() + assert node1.links() assert node2.get_iface(iface2_data.id) def test_add_net_to_net(self, session): @@ -74,7 +74,7 @@ class TestLinks: session.add_link(node1.id, node2.id) # then - assert node1.all_link_data() + assert node1.links() def test_update_node_to_net(self, session: Session, ip_prefixes: IpPrefixes): # given diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 91b598f3..fb8bc4d9 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -285,7 +285,7 @@ class TestXml: switch2 = session.get_node(node2_id, SwitchNode) assert switch1 assert switch2 - assert len(switch1.all_link_data() + switch2.all_link_data()) == 1 + assert len(switch1.links() + switch2.links()) == 1 def test_link_options( self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes @@ -345,7 +345,7 @@ class TestXml: links = [] for node_id in session.nodes: node = session.nodes[node_id] - links += node.all_link_data() + links += node.links() link = links[0] assert options.loss == link.options.loss assert options.bandwidth == link.options.bandwidth @@ -412,7 +412,7 @@ class TestXml: links = [] for node_id in session.nodes: node = session.nodes[node_id] - links += node.all_link_data() + links += node.links() link = links[0] assert options.loss == link.options.loss assert options.bandwidth == link.options.bandwidth @@ -490,7 +490,7 @@ class TestXml: links = [] for node_id in session.nodes: node = session.nodes[node_id] - links += node.all_link_data() + links += node.links() assert len(links) == 2 link1 = links[0] link2 = links[1] From d88f3a253548ff08f9204488b2c36f32b6d35a97 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 08:50:36 -0700 Subject: [PATCH 072/146] daemon: refactored CoreInterface.addrlist storing strings into CoreInterface.ip4s and ip6s, stored as netaddr.IPNetwork objects --- daemon/core/api/grpc/grpcutils.py | 19 +++---- .../configservices/frrservices/services.py | 25 ++++----- .../configservices/nrlservices/services.py | 24 +++------ .../configservices/quaggaservices/services.py | 25 ++++----- .../sercurityservices/services.py | 10 ++-- .../configservices/utilservices/services.py | 36 ++++++------- daemon/core/emane/linkmonitor.py | 12 ++--- daemon/core/emulator/session.py | 5 +- daemon/core/nodes/base.py | 21 ++++---- daemon/core/nodes/interface.py | 46 ++++++++++++---- daemon/core/nodes/network.py | 34 ++++++------ daemon/core/services/bird.py | 9 ++-- daemon/core/services/frr.py | 40 ++++++-------- daemon/core/services/nrl.py | 9 ++-- daemon/core/services/quagga.py | 39 ++++++-------- daemon/core/services/sdn.py | 22 +++----- daemon/core/services/utility.py | 53 ++++++++++--------- daemon/core/services/xorp.py | 37 +++++-------- daemon/core/xml/corexmldeployment.py | 3 +- daemon/tests/test_nodes.py | 2 +- 20 files changed, 209 insertions(+), 262 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 2c13315c..adaf2549 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -3,7 +3,6 @@ import time from typing import Any, Dict, List, Tuple, Type, Union import grpc -import netaddr from grpc import ServicerContext from core import utils @@ -447,18 +446,16 @@ def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: net_id = iface.net.id ip4 = None ip4_mask = None + ip4_net = iface.get_ip4() + if ip4_net: + ip4 = str(ip4_net.ip) + ip4_mask = ip4_net.prefixlen ip6 = None ip6_mask = None - for addr in iface.addrlist: - network = netaddr.IPNetwork(addr) - mask = network.prefixlen - ip = str(network.ip) - if netaddr.valid_ipv4(ip) and not ip4: - ip4 = ip - ip4_mask = mask - elif netaddr.valid_ipv6(ip) and not ip6: - ip6 = ip - ip6_mask = mask + ip6_net = iface.get_ip6() + if ip6_net: + ip6 = str(ip6_net.ip) + ip6_mask = ip6_net.prefixlen return core_pb2.Interface( id=iface.node_id, net_id=net_id, diff --git a/daemon/core/configservices/frrservices/services.py b/daemon/core/configservices/frrservices/services.py index 2e24b40a..ce8c305c 100644 --- a/daemon/core/configservices/frrservices/services.py +++ b/daemon/core/configservices/frrservices/services.py @@ -1,8 +1,6 @@ import abc from typing import Any, Dict, List -import netaddr - from core import constants from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode @@ -49,10 +47,9 @@ def get_router_id(node: CoreNodeBase) -> str: Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @@ -102,12 +99,10 @@ class FRRZebra(ConfigService): for iface in self.node.get_ifaces(): ip4s = [] ip6s = [] - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - ip4s.append(x) - else: - ip6s.append(x) + for ip4 in iface.ip4s: + ip4s.append(str(ip4.ip)) + for ip6 in iface.ip6s: + ip6s.append(str(ip6.ip)) is_control = getattr(iface, "control", False) ifaces.append((iface, ip4s, ip6s, is_control)) @@ -163,10 +158,8 @@ class FRROspfv2(FrrService, ConfigService): router_id = get_router_id(self.node) addresses = [] for iface in self.node.get_ifaces(control=False): - for a in iface.addrlist: - addr = a.split("/")[0] - if netaddr.valid_ipv4(addr): - addresses.append(a) + for ip4 in iface.ip4s: + addresses.append(str(ip4.ip)) data = dict(router_id=router_id, addresses=addresses) text = """ router ospf diff --git a/daemon/core/configservices/nrlservices/services.py b/daemon/core/configservices/nrlservices/services.py index 0a5e8baf..cf9b4c88 100644 --- a/daemon/core/configservices/nrlservices/services.py +++ b/daemon/core/configservices/nrlservices/services.py @@ -1,7 +1,5 @@ from typing import Any, Dict, List -import netaddr - from core import utils from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode @@ -75,13 +73,10 @@ class NrlSmf(ConfigService): ip4_prefix = None for iface in self.node.get_ifaces(control=False): ifnames.append(iface.name) - if ip4_prefix: - continue - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - ip4_prefix = f"{a}/{24}" - break + ip4 = iface.get_ip4() + if ip4: + ip4_prefix = f"{ip4.ip}/{24}" + break return dict( has_arouted=has_arouted, has_nhdp=has_nhdp, @@ -191,11 +186,8 @@ class Arouted(ConfigService): def data(self) -> Dict[str, Any]: ip4_prefix = None for iface in self.node.get_ifaces(control=False): - if ip4_prefix: - continue - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - ip4_prefix = f"{a}/{24}" - break + ip4 = iface.get_ip4() + if ip4: + ip4_prefix = f"{ip4.ip}/{24}" + break return dict(ip4_prefix=ip4_prefix) diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 40a1d7d3..e18e8a1a 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -2,8 +2,6 @@ import abc import logging from typing import Any, Dict, List -import netaddr - from core import constants from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode @@ -50,10 +48,9 @@ def get_router_id(node: CoreNodeBase) -> str: Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @@ -103,12 +100,10 @@ class Zebra(ConfigService): for iface in self.node.get_ifaces(): ip4s = [] ip6s = [] - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - ip4s.append(x) - else: - ip6s.append(x) + for ip4 in iface.ip4s: + ip4s.append(str(ip4.ip)) + for ip6 in iface.ip6s: + ip6s.append(str(ip6.ip)) is_control = getattr(iface, "control", False) ifaces.append((iface, ip4s, ip6s, is_control)) @@ -170,10 +165,8 @@ class Ospfv2(QuaggaService, ConfigService): router_id = get_router_id(self.node) addresses = [] for iface in self.node.get_ifaces(control=False): - for a in iface.addrlist: - addr = a.split("/")[0] - if netaddr.valid_ipv4(addr): - addresses.append(a) + for ip4 in iface.ip4s: + addresses.append(str(ip4.ip)) data = dict(router_id=router_id, addresses=addresses) text = """ router ospf diff --git a/daemon/core/configservices/sercurityservices/services.py b/daemon/core/configservices/sercurityservices/services.py index 5766b0db..4a58fd8c 100644 --- a/daemon/core/configservices/sercurityservices/services.py +++ b/daemon/core/configservices/sercurityservices/services.py @@ -1,7 +1,5 @@ from typing import Any, Dict, List -import netaddr - from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emulator.enumerations import ConfigDataTypes @@ -79,10 +77,10 @@ class VpnServer(ConfigService): def data(self) -> Dict[str, Any]: address = None for iface in self.node.get_ifaces(control=False): - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - address = addr + ip4 = iface.get_ip4() + if ip4: + address = str(ip4.ip) + break return dict(address=address) diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index 983f6cff..8013bc41c 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -29,8 +29,8 @@ class DefaultRouteService(ConfigService): ifaces = self.node.get_ifaces() if ifaces: iface = ifaces[0] - for x in iface.addrlist: - net = netaddr.IPNetwork(x).cidr + for ip in iface.all_ips(): + net = ip.cidr if net.size > 1: router = net[1] routes.append(str(router)) @@ -76,15 +76,14 @@ class StaticRouteService(ConfigService): def data(self) -> Dict[str, Any]: routes = [] for iface in self.node.get_ifaces(control=False): - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): + for ip in iface.all_ips(): + address = str(ip.ip) + if netaddr.valid_ipv6(address): dst = "3ffe:4::/64" else: dst = "10.9.8.0/24" - net = netaddr.IPNetwork(x) - if net[-2] != net[1]: - routes.append((dst, net[1])) + if ip[-2] != ip[1]: + routes.append((dst, ip[1])) return dict(routes=routes) @@ -149,15 +148,12 @@ class DhcpService(ConfigService): def data(self) -> Dict[str, Any]: subnets = [] for iface in self.node.get_ifaces(control=False): - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - net = netaddr.IPNetwork(x) - # divide the address space in half - index = (net.size - 2) / 2 - rangelow = net[index] - rangehigh = net[-2] - subnets.append((net.ip, net.netmask, rangelow, rangehigh, addr)) + for ip4 in iface.ip4s: + # divide the address space in half + index = (ip4.size - 2) / 2 + rangelow = ip4[index] + rangehigh = ip4[-2] + subnets.append((ip4.ip, ip4.netmask, rangelow, rangehigh, str(ip4.ip))) return dict(subnets=subnets) @@ -238,10 +234,8 @@ class RadvdService(ConfigService): ifaces = [] for iface in self.node.get_ifaces(control=False): prefixes = [] - for x in iface.addrlist: - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): - prefixes.append(x) + for ip6 in iface.ip6s: + prefixes.append(str(ip6)) if not prefixes: continue ifaces.append((iface.name, prefixes)) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index 1a9ac41a..295aaa1e 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -4,7 +4,6 @@ import threading import time from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple -import netaddr from lxml import etree from core.emulator.data import LinkData @@ -214,13 +213,12 @@ class EmaneLinkMonitor: for node in nodes: for iface in node.get_ifaces(): if isinstance(iface.net, CtrlNet): - ip4 = None - for x in iface.addrlist: - address, prefix = x.split("/") - if netaddr.valid_ipv4(address): - ip4 = address + address = None + ip4 = iface.get_ip4() if ip4: - addresses.append(ip4) + address = str(ip4.ip) + if address: + addresses.append(address) break return addresses diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 0b97da93..b0507269 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1548,9 +1548,8 @@ class Session: entries = [] for iface in control_net.get_ifaces(): name = iface.node.name - for address in iface.addrlist: - address = address.split("/")[0] - entries.append(f"{address} {name}") + for ip in iface.all_ips(): + entries.append(f"{ip.ip} {name}") logging.info("Adding %d /etc/hosts file entries.", len(entries)) utils.file_munge("/etc/hosts", header, "\n".join(entries) + "\n") diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 2c8ca06c..90be59af 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1053,18 +1053,17 @@ class CoreNetworkBase(NodeBase): if uni: unidirectional = 1 - iface2 = InterfaceData( + iface2_data = InterfaceData( id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.mac ) - for address in iface.addrlist: - ip, _sep, mask = address.partition("/") - mask = int(mask) - if netaddr.valid_ipv4(ip): - iface2.ip4 = ip - iface2.ip4_mask = mask - else: - iface2.ip6 = ip - iface2.ip6_mask = mask + ip4 = iface.get_ip4() + if ip4: + iface2_data.ip4 = str(ip4.ip) + iface2_data.ip4_mask = ip4.prefixlen + ip6 = iface.get_ip6() + if ip6: + iface2_data.ip6 = str(ip6.ip) + iface2_data.ip6_mask = ip6.prefixlen options_data = iface.get_link_options(unidirectional) link_data = LinkData( @@ -1072,7 +1071,7 @@ class CoreNetworkBase(NodeBase): type=self.linktype, node1_id=self.id, node2_id=linked_node.id, - iface2=iface2, + iface2=iface2_data, options=options_data, ) all_links.append(link_data) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 42522362..c1603a21 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -6,6 +6,8 @@ import logging import time from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple +import netaddr + from core import utils from core.emulator.data import LinkOptions from core.emulator.enumerations import TransportType @@ -52,7 +54,8 @@ class CoreInterface: self.net: Optional[CoreNetworkBase] = None self.othernet: Optional[CoreNetworkBase] = None self._params: Dict[str, float] = {} - self.addrlist: List[str] = [] + self.ip4s: List[netaddr.IPNetwork] = [] + self.ip6s: List[netaddr.IPNetwork] = [] self.mac: Optional[str] = None # placeholder position hook self.poshook: Callable[[CoreInterface], None] = lambda x: None @@ -131,15 +134,22 @@ class CoreInterface: if self.net is not None: self.net.detach(self) - def addaddr(self, addr: str) -> None: + def addaddr(self, address: str) -> None: """ - Add address. + Add ip address in the format "10.0.0.1/24". - :param addr: address to add + :param address: address to add :return: nothing """ - addr = utils.validate_ip(addr) - self.addrlist.append(addr) + try: + ip = netaddr.IPNetwork(address) + value = str(ip.ip) + if netaddr.valid_ipv4(value): + self.ip4s.append(ip) + else: + self.ip6s.append(ip) + except netaddr.AddrFormatError: + raise CoreError(f"adding invalid address {address}") def deladdr(self, addr: str) -> None: """ @@ -148,7 +158,23 @@ class CoreInterface: :param addr: address to delete :return: nothing """ - self.addrlist.remove(addr) + if netaddr.valid_ipv4(addr): + ip4 = netaddr.IPNetwork(addr) + self.ip4s.remove(ip4) + elif netaddr.valid_ipv6(addr): + ip6 = netaddr.IPNetwork(addr) + self.ip6s.remove(ip6) + else: + raise CoreError(f"deleting invalid address {addr}") + + def get_ip4(self) -> Optional[netaddr.IPNetwork]: + return next(iter(self.ip4s), None) + + def get_ip6(self) -> Optional[netaddr.IPNetwork]: + return next(iter(self.ip6s), None) + + def all_ips(self) -> List[netaddr.IPNetwork]: + return self.ip4s + self.ip6s def set_mac(self, mac: str) -> None: """ @@ -487,13 +513,13 @@ class TunTap(CoreInterface): def setaddrs(self) -> None: """ - Set interface addresses based on self.addrlist. + Set interface addresses. :return: nothing """ self.waitfordevicenode() - for addr in self.addrlist: - self.node.node_net_client.create_address(self.name, str(addr)) + for ip in self.all_ips(): + self.node.node_net_client.create_address(self.name, str(ip)) class GreTap(CoreInterface): diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 62443fb8..3f4ebfba 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -881,28 +881,26 @@ class PtpNet(CoreNetwork): iface1_data = InterfaceData( id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.mac ) - for address in iface1.addrlist: - ip, _sep, mask = address.partition("/") - mask = int(mask) - if netaddr.valid_ipv4(ip): - iface1.ip4 = ip - iface1.ip4_mask = mask - else: - iface1.ip6 = ip - iface1.ip6_mask = mask + ip4 = iface1.get_ip4() + if ip4: + iface1_data.ip4 = str(ip4.ip) + iface1_data.ip4_mask = ip4.prefixlen + ip6 = iface1.get_ip6() + if ip6: + iface1_data.ip6 = str(ip6.ip) + iface1_data.ip6_mask = ip6.prefixlen iface2_data = InterfaceData( id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.mac ) - for address in iface2.addrlist: - ip, _sep, mask = address.partition("/") - mask = int(mask) - if netaddr.valid_ipv4(ip): - iface2.ip4 = ip - iface2.ip4_mask = mask - else: - iface2.ip6 = ip - iface2.ip6_mask = mask + ip4 = iface2.get_ip4() + if ip4: + iface2_data.ip4 = str(ip4.ip) + iface2_data.ip4_mask = ip4.prefixlen + ip6 = iface2.get_ip6() + if ip6: + iface2_data.ip6 = str(ip6.ip) + iface2_data.ip6_mask = ip6.prefixlen options_data = iface1.get_link_options(unidirectional) link_data = LinkData( diff --git a/daemon/core/services/bird.py b/daemon/core/services/bird.py index a5052942..ffb177f3 100644 --- a/daemon/core/services/bird.py +++ b/daemon/core/services/bird.py @@ -3,8 +3,6 @@ bird.py: defines routing services provided by the BIRD Internet Routing Daemon. """ from typing import Optional, Tuple -import netaddr - from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -39,10 +37,9 @@ class Bird(CoreService): Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @classmethod diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index e75d8f56..6b9ada3c 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -67,7 +67,7 @@ class FRRZebra(CoreService): # include control interfaces in addressing but not routing daemons if hasattr(iface, "control") and iface.control is True: cfg += " " - cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) + cfg += "\n ".join(map(cls.addrstr, iface.all_ips())) cfg += "\n" continue cfgv4 = "" @@ -87,19 +87,13 @@ class FRRZebra(CoreService): cfgv4 += iface_config if want_ipv4: - ipv4list = filter( - lambda x: netaddr.valid_ipv4(x.split("/")[0]), iface.addrlist - ) cfg += " " - cfg += "\n ".join(map(cls.addrstr, ipv4list)) + cfg += "\n ".join(map(cls.addrstr, iface.ip4s)) cfg += "\n" cfg += cfgv4 if want_ipv6: - ipv6list = filter( - lambda x: netaddr.valid_ipv6(x.split("/")[0]), iface.addrlist - ) cfg += " " - cfg += "\n ".join(map(cls.addrstr, ipv6list)) + cfg += "\n ".join(map(cls.addrstr, iface.ip6s)) cfg += "\n" cfg += cfgv6 cfg += "!\n" @@ -111,17 +105,17 @@ class FRRZebra(CoreService): return cfg @staticmethod - def addrstr(x: str) -> str: + def addrstr(ip: netaddr.IPNetwork) -> str: """ helper for mapping IP addresses to zebra config statements """ - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - return "ip address %s" % x - elif netaddr.valid_ipv6(addr): - return "ipv6 address %s" % x + address = str(ip.ip) + if netaddr.valid_ipv4(address): + return "ip address %s" % ip + elif netaddr.valid_ipv6(address): + return "ipv6 address %s" % ip else: - raise ValueError("invalid address: %s", x) + raise ValueError("invalid address: %s", ip) @classmethod def generate_frr_boot(cls, node: CoreNode) -> str: @@ -333,10 +327,9 @@ class FrrService(CoreService): Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @staticmethod @@ -413,11 +406,8 @@ class FRROspfv2(FrrService): cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - addr = a.split("/")[0] - if not netaddr.valid_ipv4(addr): - continue - cfg += " network %s area 0\n" % a + for ip4 in iface.ip4s: + cfg += f" network {ip4} area 0\n" cfg += "!\n" return cfg diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 9933b130..697f4eee 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -4,8 +4,6 @@ nrl.py: defines services provided by NRL protolib tools hosted here: """ from typing import Optional, Tuple -import netaddr - from core import utils from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -32,10 +30,9 @@ class NrlService(CoreService): interface's prefix length, so e.g. '/32' can turn into '/24'. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return f"{a}/{prefixlen}" + ip4 = iface.get_ip4() + if ip4: + return f"{ip4.ip}/{prefixlen}" return "0.0.0.0/%s" % prefixlen diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index 30d14353..7f717e59 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -64,7 +64,7 @@ class Zebra(CoreService): # include control interfaces in addressing but not routing daemons if getattr(iface, "control", False): cfg += " " - cfg += "\n ".join(map(cls.addrstr, iface.addrlist)) + cfg += "\n ".join(map(cls.addrstr, iface.all_ips())) cfg += "\n" continue cfgv4 = "" @@ -86,19 +86,13 @@ class Zebra(CoreService): cfgv4 += iface_config if want_ipv4: - ipv4list = filter( - lambda x: netaddr.valid_ipv4(x.split("/")[0]), iface.addrlist - ) cfg += " " - cfg += "\n ".join(map(cls.addrstr, ipv4list)) + cfg += "\n ".join(map(cls.addrstr, iface.ip4s)) cfg += "\n" cfg += cfgv4 if want_ipv6: - ipv6list = filter( - lambda x: netaddr.valid_ipv6(x.split("/")[0]), iface.addrlist - ) cfg += " " - cfg += "\n ".join(map(cls.addrstr, ipv6list)) + cfg += "\n ".join(map(cls.addrstr, iface.ip6s)) cfg += "\n" cfg += cfgv6 cfg += "!\n" @@ -112,17 +106,17 @@ class Zebra(CoreService): return cfg @staticmethod - def addrstr(x: str) -> str: + def addrstr(ip: netaddr.IPNetwork) -> str: """ helper for mapping IP addresses to zebra config statements """ - addr = x.split("/")[0] - if netaddr.valid_ipv4(addr): - return "ip address %s" % x - elif netaddr.valid_ipv6(addr): - return "ipv6 address %s" % x + address = str(ip.ip) + if netaddr.valid_ipv4(address): + return "ip address %s" % ip + elif netaddr.valid_ipv6(address): + return "ipv6 address %s" % ip else: - raise ValueError("invalid address: %s", x) + raise ValueError("invalid address: %s", ip) @classmethod def generate_quagga_boot(cls, node: CoreNode) -> str: @@ -255,10 +249,9 @@ class QuaggaService(CoreService): Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return f"0.0.0.{node.id:d}" @staticmethod @@ -335,10 +328,8 @@ class Ospfv2(QuaggaService): cfg += " router-id %s\n" % rtrid # network 10.0.0.0/24 area 0 for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - addr = a.split("/")[0] - if netaddr.valid_ipv4(addr): - cfg += " network %s area 0\n" % a + for ip4 in iface.ip4s: + cfg += f" network {ip4} area 0\n" cfg += "!\n" return cfg diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index 1f17201d..ef077662 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -5,8 +5,6 @@ sdn.py defines services to start Open vSwitch and the Ryu SDN Controller. import re from typing import Tuple -import netaddr - from core.nodes.base import CoreNode from core.services.coreservices import CoreService @@ -65,18 +63,14 @@ class OvsService(SdnService): # remove ip address of eths because quagga/zebra will assign same IPs to rtr interfaces # or assign them manually to rtr interfaces if zebra is not running - for addr in iface.addrlist: - addr = addr.split("/")[0] - if netaddr.valid_ipv4(addr): - cfg += "ip addr del %s dev %s\n" % (addr, iface.name) - if has_zebra == 0: - cfg += "ip addr add %s dev rtr%s\n" % (addr, ifnum) - elif netaddr.valid_ipv6(addr): - cfg += "ip -6 addr del %s dev %s\n" % (addr, iface.name) - if has_zebra == 0: - cfg += "ip -6 addr add %s dev rtr%s\n" % (addr, ifnum) - else: - raise ValueError("invalid address: %s" % addr) + for ip4 in iface.ip4s: + cfg += "ip addr del %s dev %s\n" % (ip4.ip, iface.name) + if has_zebra == 0: + cfg += "ip addr add %s dev rtr%s\n" % (ip4.ip, ifnum) + for ip6 in iface.ip6s: + cfg += "ip -6 addr del %s dev %s\n" % (ip6.ip, iface.name) + if has_zebra == 0: + cfg += "ip -6 addr add %s dev rtr%s\n" % (ip6.ip, ifnum) # add interfaces to bridge # Make port numbers explicit so they're easier to follow in reading the script diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index a44037f6..5efade1a 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -74,8 +74,8 @@ class DefaultRouteService(UtilService): ifaces = node.get_ifaces() if ifaces: iface = ifaces[0] - for x in iface.addrlist: - net = netaddr.IPNetwork(x).cidr + for ip in iface.all_ips(): + net = ip.cidr if net.size > 1: router = net[1] routes.append(str(router)) @@ -118,23 +118,22 @@ class StaticRouteService(UtilService): cfg += "# NOTE: this service must be customized to be of any use\n" cfg += "# Below are samples that you can uncomment and edit.\n#\n" for iface in node.get_ifaces(control=False): - cfg += "\n".join(map(cls.routestr, iface.addrlist)) + cfg += "\n".join(map(cls.routestr, iface.all_ips())) cfg += "\n" return cfg @staticmethod - def routestr(x: str) -> str: - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): + def routestr(ip: netaddr.IPNetwork) -> str: + address = str(ip.ip) + if netaddr.valid_ipv6(address): dst = "3ffe:4::/64" else: dst = "10.9.8.0/24" - net = netaddr.IPNetwork(x) - if net[-2] == net[1]: + if ip[-2] == ip[1]: return "" else: rtcmd = "#/sbin/ip route add %s via" % dst - return "%s %s" % (rtcmd, net[1]) + return "%s %s" % (rtcmd, ip[1]) class SshService(UtilService): @@ -242,25 +241,24 @@ max-lease-time 7200; ddns-update-style none; """ for iface in node.get_ifaces(control=False): - cfg += "\n".join(map(cls.subnetentry, iface.addrlist)) + cfg += "\n".join(map(cls.subnetentry, iface.all_ips())) cfg += "\n" return cfg @staticmethod - def subnetentry(x: str) -> str: + def subnetentry(ip: netaddr.IPNetwork) -> str: """ Generate a subnet declaration block given an IPv4 prefix string for inclusion in the dhcpd3 config file. """ - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): + address = str(ip.ip) + if netaddr.valid_ipv6(address): return "" else: - net = netaddr.IPNetwork(x) # divide the address space in half - index = (net.size - 2) / 2 - rangelow = net[index] - rangehigh = net[-2] + index = (ip.size - 2) / 2 + rangelow = ip[index] + rangehigh = ip[-2] return """ subnet %s netmask %s { pool { @@ -270,11 +268,11 @@ subnet %s netmask %s { } } """ % ( - net.ip, - net.netmask, + ip.ip, + ip.netmask, rangelow, rangehigh, - addr, + address, ) @@ -557,7 +555,10 @@ export LANG % node.name ) for iface in node.get_ifaces(control=False): - body += "
  • %s - %s
  • \n" % (iface.name, iface.addrlist) + body += "
  • %s - %s
  • \n" % ( + iface.name, + [str(x) for x in iface.all_ips()], + ) return "%s" % body @@ -625,7 +626,7 @@ class RadvdService(UtilService): """ cfg = "# auto-generated by RADVD service (utility.py)\n" for iface in node.get_ifaces(control=False): - prefixes = list(map(cls.subnetentry, iface.addrlist)) + prefixes = list(map(cls.subnetentry, iface.all_ips())) if len(prefixes) < 1: continue cfg += ( @@ -658,14 +659,14 @@ interface %s return cfg @staticmethod - def subnetentry(x: str) -> str: + def subnetentry(ip: netaddr.IPNetwork) -> str: """ Generate a subnet declaration block given an IPv6 prefix string for inclusion in the RADVD config file. """ - addr = x.split("/")[0] - if netaddr.valid_ipv6(addr): - return x + address = str(ip.ip) + if netaddr.valid_ipv6(address): + return str(ip) else: return "" diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 42082377..7c24478a 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -40,7 +40,7 @@ class XorpRtrmgr(CoreService): for iface in node.get_ifaces(): cfg += " interface %s {\n" % iface.name cfg += "\tvif %s {\n" % iface.name - cfg += "".join(map(cls.addrstr, iface.addrlist)) + cfg += "".join(map(cls.addrstr, iface.all_ips())) cfg += cls.lladdrstr(iface) cfg += "\t}\n" cfg += " }\n" @@ -55,13 +55,12 @@ class XorpRtrmgr(CoreService): return cfg @staticmethod - def addrstr(x: str) -> str: + def addrstr(ip: netaddr.IPNetwork) -> str: """ helper for mapping IP addresses to XORP config statements """ - addr, plen = x.split("/") - cfg = "\t address %s {\n" % addr - cfg += "\t\tprefix-length: %s\n" % plen + cfg = "\t address %s {\n" % ip.ip + cfg += "\t\tprefix-length: %s\n" % ip.prefixlen cfg += "\t }\n" return cfg @@ -145,10 +144,9 @@ class XorpService(CoreService): Helper to return the first IPv4 address of a node as its router ID. """ for iface in node.get_ifaces(control=False): - for a in iface.addrlist: - a = a.split("/")[0] - if netaddr.valid_ipv4(a): - return a + ip4 = iface.get_ip4() + if ip4: + return str(ip4.ip) return "0.0.0.0" @classmethod @@ -180,11 +178,8 @@ class XorpOspfv2(XorpService): for iface in node.get_ifaces(control=False): cfg += "\t interface %s {\n" % iface.name cfg += "\t\tvif %s {\n" % iface.name - for a in iface.addrlist: - addr = a.split("/")[0] - if not netaddr.valid_ipv4(addr): - continue - cfg += "\t\t address %s {\n" % addr + for ip4 in iface.ip4s: + cfg += "\t\t address %s {\n" % ip4.ip cfg += "\t\t }\n" cfg += "\t\t}\n" cfg += "\t }\n" @@ -269,11 +264,8 @@ class XorpRip(XorpService): for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - for a in iface.addrlist: - addr = a.split("/")[0] - if not netaddr.valid_ipv4(addr): - continue - cfg += "\t\taddress %s {\n" % addr + for ip4 in iface.ip4s: + cfg += "\t\taddress %s {\n" % ip4.ip cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" @@ -435,11 +427,8 @@ class XorpOlsr(XorpService): for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - for a in iface.addrlist: - addr = a.split("/")[0] - if not netaddr.valid_ipv4(addr): - continue - cfg += "\t\taddress %s {\n" % addr + for ip4 in iface.ip4s: + cfg += "\t\taddress %s {\n" % ip4.ip cfg += "\t\t}\n" cfg += "\t }\n" cfg += "\t}\n" diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 7954b71a..d84f2246 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -164,6 +164,7 @@ class CoreXmlDeployment: if emane_element is not None: parent_element = emane_element - for address in iface.addrlist: + for ip in iface.all_ips(): + address = str(ip.ip) address_type = get_address_type(address) add_address(parent_element, address_type, address, iface.name) diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 8af2e895..1e89f5e4 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -87,7 +87,7 @@ class TestNodes: node.addaddr(iface.node_id, addr) # then - assert iface.addrlist[0] == addr + assert str(iface.get_ip4()) == addr def test_node_addaddr_exception(self, session): # given From 20feea8f12fe3abaf598d485f9c38395c0ab6299 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 10:54:58 -0700 Subject: [PATCH 073/146] daemon: refactored usages of addr to ip and updated functions to align --- .../configservices/utilservices/services.py | 4 +- daemon/core/emulator/session.py | 2 +- daemon/core/nodes/base.py | 65 +++++++++-------- daemon/core/nodes/interface.py | 56 ++++++++++----- daemon/core/nodes/network.py | 23 +++--- daemon/core/nodes/physical.py | 71 ++++++++++--------- daemon/core/services/frr.py | 2 +- daemon/core/services/quagga.py | 2 +- daemon/core/services/utility.py | 13 ++-- daemon/core/services/xorp.py | 2 +- daemon/core/xml/corexmldeployment.py | 2 +- daemon/tests/test_nodes.py | 14 ++-- 12 files changed, 138 insertions(+), 118 deletions(-) diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index 8013bc41c..b6bc0eb5 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -29,7 +29,7 @@ class DefaultRouteService(ConfigService): ifaces = self.node.get_ifaces() if ifaces: iface = ifaces[0] - for ip in iface.all_ips(): + for ip in iface.ips(): net = ip.cidr if net.size > 1: router = net[1] @@ -76,7 +76,7 @@ class StaticRouteService(ConfigService): def data(self) -> Dict[str, Any]: routes = [] for iface in self.node.get_ifaces(control=False): - for ip in iface.all_ips(): + for ip in iface.ips(): address = str(ip.ip) if netaddr.valid_ipv6(address): dst = "3ffe:4::/64" diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index b0507269..630e1a0f 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1548,7 +1548,7 @@ class Session: entries = [] for iface in control_net.get_ifaces(): name = iface.node.name - for ip in iface.all_ips(): + for ip in iface.ips(): entries.append(f"{ip.ip} {name}") logging.info("Adding %d /etc/hosts file entries.", len(entries)) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 90be59af..7eff9b12 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -7,7 +7,7 @@ import os import shutil import threading from threading import RLock -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union import netaddr @@ -138,6 +138,13 @@ class NodeBase(abc.ABC): return self.position.get() def get_iface(self, iface_id: int) -> CoreInterface: + """ + Retrieve interface based on id. + + :param iface_id: id of interface to retrieve + :return: interface + :raises CoreError: when interface does not exist + """ if iface_id not in self.ifaces: raise CoreError(f"node({self.name}) does not have interface({iface_id})") return self.ifaces[iface_id] @@ -436,7 +443,6 @@ class CoreNode(CoreNodeBase): """ apitype: NodeTypes = NodeTypes.DEFAULT - valid_address_types: Set[str] = {"inet", "inet6", "inet6link"} def __init__( self, @@ -750,40 +756,39 @@ class CoreNode(CoreNodeBase): if self.up: self.node_net_client.device_mac(iface.name, mac) - def addaddr(self, iface_id: int, addr: str) -> None: + def add_ip(self, iface_id: int, ip: str) -> None: """ - Add interface address. + Add an ip address to an interface in the format "10.0.0.1/24". :param iface_id: id of interface to add address to - :param addr: address to add to interface - :return: nothing - """ - addr = utils.validate_ip(addr) - iface = self.get_iface(iface_id) - iface.addaddr(addr) - if self.up: - # ipv4 check - broadcast = None - if netaddr.valid_ipv4(addr): - broadcast = "+" - self.node_net_client.create_address(iface.name, addr, broadcast) - - def deladdr(self, iface_id: int, addr: str) -> None: - """ - Delete address from an interface. - - :param iface_id: id of interface to delete address from - :param addr: address to delete from interface + :param ip: address to add to interface :return: nothing + :raises CoreError: when ip address provided is invalid :raises CoreCommandError: when a non-zero exit status occurs """ iface = self.get_iface(iface_id) - try: - iface.deladdr(addr) - except ValueError: - logging.exception("trying to delete unknown address: %s", addr) + iface.add_ip(ip) if self.up: - self.node_net_client.delete_address(iface.name, addr) + # ipv4 check + broadcast = None + if netaddr.valid_ipv4(ip): + broadcast = "+" + self.node_net_client.create_address(iface.name, ip, broadcast) + + def remove_ip(self, iface_id: int, ip: str) -> None: + """ + Remove an ip address from an interface in the format "10.0.0.1/24". + + :param iface_id: id of interface to delete address from + :param ip: ip address to remove from interface + :return: nothing + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs + """ + iface = self.get_iface(iface_id) + iface.remove_ip(ip) + if self.up: + self.node_net_client.delete_address(iface.name, ip) def ifup(self, iface_id: int) -> None: """ @@ -819,14 +824,14 @@ class CoreNode(CoreNodeBase): iface = self.get_iface(iface_id) iface.set_mac(iface_data.mac) for address in addresses: - iface.addaddr(address) + iface.add_ip(address) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) if iface_data.mac: self.set_mac(iface_id, iface_data.mac) for address in addresses: - self.addaddr(iface_id, address) + self.add_ip(iface_id, address) self.ifup(iface_id) iface = self.get_iface(iface_id) return iface diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index c1603a21..c613f0cd 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -134,46 +134,64 @@ class CoreInterface: if self.net is not None: self.net.detach(self) - def addaddr(self, address: str) -> None: + def add_ip(self, ip: str) -> None: """ Add ip address in the format "10.0.0.1/24". - :param address: address to add + :param ip: ip address to add :return: nothing + :raises CoreError: when ip address provided is invalid """ try: - ip = netaddr.IPNetwork(address) - value = str(ip.ip) - if netaddr.valid_ipv4(value): + ip = netaddr.IPNetwork(ip) + address = str(ip.ip) + if netaddr.valid_ipv4(address): self.ip4s.append(ip) else: self.ip6s.append(ip) except netaddr.AddrFormatError: - raise CoreError(f"adding invalid address {address}") + raise CoreError(f"adding invalid address {ip}") - def deladdr(self, addr: str) -> None: + def remove_ip(self, ip: str) -> None: """ - Delete address. + Remove ip address in the format "10.0.0.1/24". - :param addr: address to delete + :param ip: ip address to delete :return: nothing + :raises CoreError: when ip address provided is invalid """ - if netaddr.valid_ipv4(addr): - ip4 = netaddr.IPNetwork(addr) - self.ip4s.remove(ip4) - elif netaddr.valid_ipv6(addr): - ip6 = netaddr.IPNetwork(addr) - self.ip6s.remove(ip6) - else: - raise CoreError(f"deleting invalid address {addr}") + try: + ip = netaddr.IPNetwork(ip) + address = str(ip.ip) + if netaddr.valid_ipv4(address): + self.ip4s.remove(ip) + else: + self.ip6s.remove(ip) + except (netaddr.AddrFormatError, ValueError): + raise CoreError(f"deleting invalid address {ip}") def get_ip4(self) -> Optional[netaddr.IPNetwork]: + """ + Looks for the first ip4 address. + + :return: ip4 address, None otherwise + """ return next(iter(self.ip4s), None) def get_ip6(self) -> Optional[netaddr.IPNetwork]: + """ + Looks for the first ip6 address. + + :return: ip6 address, None otherwise + """ return next(iter(self.ip6s), None) - def all_ips(self) -> List[netaddr.IPNetwork]: + def ips(self) -> List[netaddr.IPNetwork]: + """ + Retrieve a list of all ip4 and ip6 addresses combined. + + :return: ip4 and ip6 addresses + """ return self.ip4s + self.ip6s def set_mac(self, mac: str) -> None: @@ -518,7 +536,7 @@ class TunTap(CoreInterface): :return: nothing """ self.waitfordevicenode() - for ip in self.all_ips(): + for ip in self.ips(): self.node.node_net_client.create_address(self.name, str(ip)) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 3f4ebfba..559b7ece 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -575,18 +575,17 @@ class CoreNetwork(CoreNetworkBase): return iface return None - def addrconfig(self, addrlist: List[str]) -> None: + def add_ips(self, ips: List[str]) -> None: """ - Set addresses on the bridge. + Add ip addresses on the bridge in the format "10.0.0.1/24". - :param addrlist: address list + :param ips: ip address to add :return: nothing """ if not self.up: return - - for addr in addrlist: - self.net_client.create_address(self.brname, str(addr)) + for ip in ips: + self.net_client.create_address(self.brname, ip) class GreTapBridge(CoreNetwork): @@ -663,22 +662,22 @@ class GreTapBridge(CoreNetwork): self.gretap = None super().shutdown() - def addrconfig(self, addrlist: List[str]) -> None: + def add_ips(self, ips: List[str]) -> None: """ Set the remote tunnel endpoint. This is a one-time method for creating the GreTap device, which requires the remoteip at startup. The 1st address in the provided list is remoteip, 2nd optionally specifies localip. - :param addrlist: address list + :param ips: address list :return: nothing """ if self.gretap: raise ValueError(f"gretap already exists for {self.name}") - remoteip = addrlist[0].split("/")[0] + remoteip = ips[0].split("/")[0] localip = None - if len(addrlist) > 1: - localip = addrlist[1].split("/")[0] + if len(ips) > 1: + localip = ips[1].split("/")[0] self.gretap = GreTap( session=self.session, remoteip=remoteip, @@ -700,7 +699,7 @@ class GreTapBridge(CoreNetwork): self.grekey = key addresses = iface_data.get_addresses() if addresses: - self.addrconfig(addresses) + self.add_ips(addresses) class CtrlNet(CoreNetwork): diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 0ce8946a..96440bcb 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -75,43 +75,43 @@ class PhysicalNode(CoreNodeBase): :raises CoreCommandError: when a non-zero exit status occurs """ mac = utils.validate_mac(mac) - iface = self.ifaces[iface_id] + iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: self.net_client.device_mac(iface.name, mac) - def addaddr(self, iface_id: int, addr: str) -> None: + def add_ip(self, iface_id: int, ip: str) -> None: """ - Add an address to an interface. + Add an ip address to an interface in the format "10.0.0.1/24". - :param iface_id: index of interface to add address to - :param addr: address to add + :param iface_id: id of interface to add address to + :param ip: address to add to interface :return: nothing + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs """ - addr = utils.validate_ip(addr) iface = self.get_iface(iface_id) + iface.add_ip(ip) if self.up: - self.net_client.create_address(iface.name, addr) - iface.addaddr(addr) + self.net_client.create_address(iface.name, ip) - def deladdr(self, iface_id: int, addr: str) -> None: + def remove_ip(self, iface_id: int, ip: str) -> None: """ - Delete an address from an interface. + Remove an ip address from an interface in the format "10.0.0.1/24". - :param iface_id: index of interface to delete - :param addr: address to delete + :param iface_id: id of interface to delete address from + :param ip: ip address to remove from interface :return: nothing + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs """ - iface = self.ifaces[iface_id] - try: - iface.deladdr(addr) - except ValueError: - logging.exception("trying to delete unknown address: %s", addr) + iface = self.get_iface(iface_id) + iface.remove_ip(ip) if self.up: - self.net_client.delete_address(iface.name, addr) + self.net_client.delete_address(iface.name, ip) def adopt_iface( - self, iface: CoreInterface, iface_id: int, mac: str, addrlist: List[str] + self, iface: CoreInterface, iface_id: int, mac: str, ips: List[str] ) -> None: """ When a link message is received linking this node to another part of @@ -128,8 +128,8 @@ class PhysicalNode(CoreNodeBase): iface.localname = iface.name if mac: self.set_mac(iface_id, mac) - for addr in addrlist: - self.addaddr(iface_id, addr) + for ip in ips: + self.add_ip(iface_id, ip) if self.up: self.net_client.device_up(iface.localname) @@ -317,7 +317,7 @@ class Rj45Node(CoreNodeBase): if net is not None: self.iface.attachnet(net) for addr in iface_data.get_addresses(): - self.addaddr(addr) + self.add_ip(addr) return self.iface def delete_iface(self, iface_id: int) -> None: @@ -348,30 +348,31 @@ class Rj45Node(CoreNodeBase): raise CoreError(f"node({self.name}) does not have interface({iface.name})") return self.iface_id - def addaddr(self, addr: str) -> None: + def add_ip(self, ip: str) -> None: """ - Add address to to network interface. + Add an ip address to an interface in the format "10.0.0.1/24". - :param addr: address to add + :param ip: address to add to interface :return: nothing - :raises CoreCommandError: when there is a command exception + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs """ - addr = utils.validate_ip(addr) + self.iface.add_ip(ip) if self.up: - self.net_client.create_address(self.name, addr) - self.iface.addaddr(addr) + self.net_client.create_address(self.name, ip) - def deladdr(self, addr: str) -> None: + def remove_ip(self, ip: str) -> None: """ - Delete address from network interface. + Remove an ip address from an interface in the format "10.0.0.1/24". - :param addr: address to delete + :param ip: ip address to remove from interface :return: nothing - :raises CoreCommandError: when there is a command exception + :raises CoreError: when ip address provided is invalid + :raises CoreCommandError: when a non-zero exit status occurs """ + self.iface.remove_ip(ip) if self.up: - self.net_client.delete_address(self.name, addr) - self.iface.deladdr(addr) + self.net_client.delete_address(self.name, ip) def savestate(self) -> None: """ diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 6b9ada3c..632b4557 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -67,7 +67,7 @@ class FRRZebra(CoreService): # include control interfaces in addressing but not routing daemons if hasattr(iface, "control") and iface.control is True: cfg += " " - cfg += "\n ".join(map(cls.addrstr, iface.all_ips())) + cfg += "\n ".join(map(cls.addrstr, iface.ips())) cfg += "\n" continue cfgv4 = "" diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index 7f717e59..cb9e6b08 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -64,7 +64,7 @@ class Zebra(CoreService): # include control interfaces in addressing but not routing daemons if getattr(iface, "control", False): cfg += " " - cfg += "\n ".join(map(cls.addrstr, iface.all_ips())) + cfg += "\n ".join(map(cls.addrstr, iface.ips())) cfg += "\n" continue cfgv4 = "" diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 5efade1a..414f994e 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -74,7 +74,7 @@ class DefaultRouteService(UtilService): ifaces = node.get_ifaces() if ifaces: iface = ifaces[0] - for ip in iface.all_ips(): + for ip in iface.ips(): net = ip.cidr if net.size > 1: router = net[1] @@ -118,7 +118,7 @@ class StaticRouteService(UtilService): cfg += "# NOTE: this service must be customized to be of any use\n" cfg += "# Below are samples that you can uncomment and edit.\n#\n" for iface in node.get_ifaces(control=False): - cfg += "\n".join(map(cls.routestr, iface.all_ips())) + cfg += "\n".join(map(cls.routestr, iface.ips())) cfg += "\n" return cfg @@ -241,7 +241,7 @@ max-lease-time 7200; ddns-update-style none; """ for iface in node.get_ifaces(control=False): - cfg += "\n".join(map(cls.subnetentry, iface.all_ips())) + cfg += "\n".join(map(cls.subnetentry, iface.ips())) cfg += "\n" return cfg @@ -555,10 +555,7 @@ export LANG % node.name ) for iface in node.get_ifaces(control=False): - body += "
  • %s - %s
  • \n" % ( - iface.name, - [str(x) for x in iface.all_ips()], - ) + body += "
  • %s - %s
  • \n" % (iface.name, [str(x) for x in iface.ips()]) return "%s" % body @@ -626,7 +623,7 @@ class RadvdService(UtilService): """ cfg = "# auto-generated by RADVD service (utility.py)\n" for iface in node.get_ifaces(control=False): - prefixes = list(map(cls.subnetentry, iface.all_ips())) + prefixes = list(map(cls.subnetentry, iface.ips())) if len(prefixes) < 1: continue cfg += ( diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 7c24478a..a9687d45 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -40,7 +40,7 @@ class XorpRtrmgr(CoreService): for iface in node.get_ifaces(): cfg += " interface %s {\n" % iface.name cfg += "\tvif %s {\n" % iface.name - cfg += "".join(map(cls.addrstr, iface.all_ips())) + cfg += "".join(map(cls.addrstr, iface.ips())) cfg += cls.lladdrstr(iface) cfg += "\t}\n" cfg += " }\n" diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index d84f2246..6035bd26 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -164,7 +164,7 @@ class CoreXmlDeployment: if emane_element is not None: parent_element = emane_element - for ip in iface.all_ips(): + for ip in iface.ips(): address = str(ip.ip) address_type = get_address_type(address) add_address(parent_element, address_type, address, iface.name) diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 1e89f5e4..25a62c5f 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -75,31 +75,31 @@ class TestNodes: with pytest.raises(CoreError): node.set_mac(iface.node_id, mac) - def test_node_addaddr(self, session: Session): + def test_node_add_ip(self, session: Session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - addr = "192.168.0.1/24" + ip = "192.168.0.1/24" # when - node.addaddr(iface.node_id, addr) + node.add_ip(iface.node_id, ip) # then - assert str(iface.get_ip4()) == addr + assert str(iface.get_ip4()) == ip - def test_node_addaddr_exception(self, session): + def test_node_add_ip_exception(self, session): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - addr = "256.168.0.1/24" + ip = "256.168.0.1/24" # when with pytest.raises(CoreError): - node.addaddr(iface.node_id, addr) + node.add_ip(iface.node_id, ip) @pytest.mark.parametrize("net_type", NET_TYPES) def test_net(self, session, net_type): From 9e4429fbbc0212b2810a4ff4892f88a300c7739e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 11:11:45 -0700 Subject: [PATCH 074/146] daemon: refactored InterfaceData.get_addresses to InterfaceData.get_ips --- daemon/core/emane/linkmonitor.py | 2 -- daemon/core/emulator/data.py | 12 ++++++------ daemon/core/nodes/base.py | 10 +++++----- daemon/core/nodes/network.py | 6 +++--- daemon/core/nodes/physical.py | 10 +++++----- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index 295aaa1e..56473f62 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -213,11 +213,9 @@ class EmaneLinkMonitor: for node in nodes: for iface in node.get_ifaces(): if isinstance(iface.net, CtrlNet): - address = None ip4 = iface.get_ip4() if ip4: address = str(ip4.ip) - if address: addresses.append(address) break return addresses diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 5b6479ae..22d10d2d 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -142,18 +142,18 @@ class InterfaceData: ip6: str = None ip6_mask: int = None - def get_addresses(self) -> List[str]: + def get_ips(self) -> List[str]: """ Returns a list of ip4 and ip6 addresses when present. - :return: list of addresses + :return: list of ip addresses """ - addresses = [] + ips = [] if self.ip4 and self.ip4_mask: - addresses.append(f"{self.ip4}/{self.ip4_mask}") + ips.append(f"{self.ip4}/{self.ip4_mask}") if self.ip6 and self.ip6_mask: - addresses.append(f"{self.ip6}/{self.ip6_mask}") - return addresses + ips.append(f"{self.ip6}/{self.ip6_mask}") + return ips @dataclass diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 7eff9b12..50f19a82 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -811,7 +811,7 @@ class CoreNode(CoreNodeBase): :param iface_data: interface data for new interface :return: interface index """ - addresses = iface_data.get_addresses() + ips = iface_data.get_ips() with self.lock: # TODO: emane specific code if net.is_emane is True: @@ -823,15 +823,15 @@ class CoreNode(CoreNodeBase): self.attachnet(iface_id, net) iface = self.get_iface(iface_id) iface.set_mac(iface_data.mac) - for address in addresses: - iface.add_ip(address) + for ip in ips: + iface.add_ip(ip) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) if iface_data.mac: self.set_mac(iface_id, iface_data.mac) - for address in addresses: - self.add_ip(iface_id, address) + for ip in ips: + self.add_ip(iface_id, ip) self.ifup(iface_id) iface = self.get_iface(iface_id) return iface diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 559b7ece..5b95c3b2 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -697,9 +697,9 @@ class GreTapBridge(CoreNetwork): :return: nothing """ self.grekey = key - addresses = iface_data.get_addresses() - if addresses: - self.add_ips(addresses) + ips = iface_data.get_ips() + if ips: + self.add_ips(ips) class CtrlNet(CoreNetwork): diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 96440bcb..8fd828d8 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -156,7 +156,7 @@ class PhysicalNode(CoreNodeBase): self, net: CoreNetworkBase, iface_data: InterfaceData ) -> CoreInterface: logging.info("creating interface") - addresses = iface_data.get_addresses() + ips = iface_data.get_ips() iface_id = iface_data.id if iface_id is None: iface_id = self.next_iface_id() @@ -167,12 +167,12 @@ class PhysicalNode(CoreNodeBase): # this is reached when this node is linked to a network node # tunnel to net not built yet, so build it now and adopt it _, remote_tap = self.session.distributed.create_gre_tunnel(net, self.server) - self.adopt_iface(remote_tap, iface_id, iface_data.mac, addresses) + self.adopt_iface(remote_tap, iface_id, iface_data.mac, ips) return remote_tap else: # this is reached when configuring services (self.up=False) iface = GreTap(node=self, name=name, session=self.session, start=False) - self.adopt_iface(iface, iface_id, iface_data.mac, addresses) + self.adopt_iface(iface, iface_id, iface_data.mac, ips) return iface def privatedir(self, path: str) -> None: @@ -316,8 +316,8 @@ class Rj45Node(CoreNodeBase): self.iface_id = iface_id if net is not None: self.iface.attachnet(net) - for addr in iface_data.get_addresses(): - self.add_ip(addr) + for ip in iface_data.get_ips(): + self.add_ip(ip) return self.iface def delete_iface(self, iface_id: int) -> None: From 19af9c3f51d7b088fcdaa2f4e7dd3ce670f99b0d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 11:18:39 -0700 Subject: [PATCH 075/146] daemon: added proper checks for FRRService calls --- daemon/core/services/frr.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 632b4557..13569772 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -77,6 +77,8 @@ class FRRZebra(CoreService): for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, FrrService) or issubclass(s, FrrService)): + continue iface_config = s.generate_frr_iface_config(node, iface) if s.ipv4_routing: want_ipv4 = True @@ -101,6 +103,8 @@ class FRRZebra(CoreService): for s in node.services: if cls.name not in s.dependencies: continue + if not (isinstance(s, FrrService) or issubclass(s, FrrService)): + continue cfg += s.generate_frr_config(node) return cfg From 88fe860f97dd519dbfbfca3f07bdcf20b16ccb58 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:25:47 -0700 Subject: [PATCH 076/146] fixed examples using IpPrefixes class --- daemon/examples/python/emane80211.py | 2 +- daemon/examples/python/switch.py | 2 +- daemon/examples/python/wlan.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index 9d6def4a..48133ce0 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -55,7 +55,7 @@ def main(): # get nodes to run example first_node = session.get_node(1, CoreNode) last_node = session.get_node(NODES, CoreNode) - address = prefixes.ip4_address(first_node) + address = prefixes.ip4_address(first_node.id) logging.info("node %s pinging %s", last_node.name, address) output = last_node.cmd(f"ping -c 3 {address}") logging.info(output) diff --git a/daemon/examples/python/switch.py b/daemon/examples/python/switch.py index f05176a3..c5e62e4a 100644 --- a/daemon/examples/python/switch.py +++ b/daemon/examples/python/switch.py @@ -40,7 +40,7 @@ def main(): # get nodes to run example first_node = session.get_node(1, CoreNode) last_node = session.get_node(NODES, CoreNode) - address = prefixes.ip4_address(first_node) + address = prefixes.ip4_address(first_node.id) logging.info("node %s pinging %s", last_node.name, address) output = last_node.cmd(f"ping -c 3 {address}") logging.info(output) diff --git a/daemon/examples/python/wlan.py b/daemon/examples/python/wlan.py index de26ab97..7c16bad8 100644 --- a/daemon/examples/python/wlan.py +++ b/daemon/examples/python/wlan.py @@ -44,7 +44,7 @@ def main(): # get nodes for example run first_node = session.get_node(1, CoreNode) last_node = session.get_node(NODES, CoreNode) - address = prefixes.ip4_address(first_node) + address = prefixes.ip4_address(first_node.id) logging.info("node %s pinging %s", last_node.name, address) output = last_node.cmd(f"ping -c 3 {address}") logging.info(output) From df9216e0f0cba624137a19e64832a05fdef0fd47 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:28:11 -0700 Subject: [PATCH 077/146] updated scripting docs to use new naming and fixed out bad example --- docs/scripting.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/scripting.md b/docs/scripting.md index f65d66a3..06ca483a 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -61,8 +61,8 @@ def main(): # create nodes for _ in range(NODES): node = session.add_node(CoreNode) - interface = prefixes.create_iface(node) - session.add_link(node.id, switch.id, iface1_data=interface) + iface_data = prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface1_data=iface_data) # instantiate session session.instantiate() @@ -137,7 +137,7 @@ session = coreemu.create_session() # create node with custom services options = NodeOptions(services=["ServiceName"]) -node = session.add_node(options=options) +node = session.add_node(CoreNode, options=options) # set custom file data session.services.set_service_file(node.id, "ServiceName", "FileName", "custom file data") From cd6083aed95d2587923b039fcc426fdda506ed9d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:44:28 -0700 Subject: [PATCH 078/146] daemon: fixed issue not checking if an emane interface is a TunTap before using a specific function, fixed issue not looking for possible iface specific configuration for external --- daemon/core/emane/nodes.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 9173fbfc..19d5a9e1 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -17,7 +17,7 @@ from core.emulator.enumerations import ( ) from core.errors import CoreError from core.nodes.base import CoreNetworkBase -from core.nodes.interface import CoreInterface +from core.nodes.interface import CoreInterface, TunTap if TYPE_CHECKING: from core.emane.emanemodel import EmaneModel @@ -151,18 +151,16 @@ class EmaneNet(CoreNetworkBase): warntxt = "unable to publish EMANE events because the eventservice " warntxt += "Python bindings failed to load" logging.error(warntxt) - for iface in self.get_ifaces(): - external = self.session.emane.get_config( - "external", self.id, self.model.name + config = self.session.emane.get_iface_config( + self.id, iface, self.model.name ) - if external == "0": + external = config["external"] + if isinstance(iface, TunTap) and external == "0": iface.setaddrs() - if not self.session.emane.genlocationevents(): iface.poshook = None continue - # at this point we register location handlers for generating # EMANE location events iface.poshook = self.setnemposition From f07176dd43aa06ad6724af5b82dda7b82c5f62f4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:51:11 -0700 Subject: [PATCH 079/146] daemon: provide safe fallback for emane install ifaces, in case external configuration does not exist --- daemon/core/emane/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 19d5a9e1..1186f928 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -155,7 +155,7 @@ class EmaneNet(CoreNetworkBase): config = self.session.emane.get_iface_config( self.id, iface, self.model.name ) - external = config["external"] + external = config.get("external", "0") if isinstance(iface, TunTap) and external == "0": iface.setaddrs() if not self.session.emane.genlocationevents(): From cfda9509a2020b1cca86c2f7ba016a2ca0eced94 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 13:52:59 -0700 Subject: [PATCH 080/146] daemon: refactored TunTap setaddrs to set_ips to be more consistent with new naming --- daemon/core/emane/nodes.py | 2 +- daemon/core/nodes/interface.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 1186f928..8cc9cd87 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -157,7 +157,7 @@ class EmaneNet(CoreNetworkBase): ) external = config.get("external", "0") if isinstance(iface, TunTap) and external == "0": - iface.setaddrs() + iface.set_ips() if not self.session.emane.genlocationevents(): iface.poshook = None continue diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index c613f0cd..d0e55c7e 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -529,9 +529,9 @@ class TunTap(CoreInterface): self.node.node_net_client.device_name(self.localname, self.name) self.node.node_net_client.device_up(self.name) - def setaddrs(self) -> None: + def set_ips(self) -> None: """ - Set interface addresses. + Set interface ip addresses. :return: nothing """ From 1829a8e2f8dac16d3fd891f5d44194725c5cc1e0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 15:21:45 -0700 Subject: [PATCH 081/146] daemon: refactored CoreInterface.mac from a string to a netaddr.EUI object, providing more functionality --- daemon/core/api/grpc/grpcutils.py | 2 +- daemon/core/nodes/base.py | 3 +-- daemon/core/nodes/interface.py | 25 +++++++++++++++---------- daemon/core/nodes/network.py | 4 ++-- daemon/core/nodes/physical.py | 2 -- daemon/core/services/xorp.py | 4 ++-- daemon/core/xml/emanexml.py | 6 +++--- daemon/tests/test_nodes.py | 2 +- 8 files changed, 25 insertions(+), 23 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index adaf2549..b63cb895 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -460,7 +460,7 @@ def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: id=iface.node_id, net_id=net_id, name=iface.name, - mac=iface.mac, + mac=str(iface.mac), mtu=iface.mtu, flow_id=iface.flow_id, ip4=ip4, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 50f19a82..aae59b70 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -750,7 +750,6 @@ class CoreNode(CoreNodeBase): :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - mac = utils.validate_mac(mac) iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: @@ -1059,7 +1058,7 @@ class CoreNetworkBase(NodeBase): unidirectional = 1 iface2_data = InterfaceData( - id=linked_node.get_iface_id(iface), name=iface.name, mac=iface.mac + id=linked_node.get_iface_id(iface), name=iface.name, mac=str(iface.mac) ) ip4 = iface.get_ip4() if ip4: diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index d0e55c7e..22ecb620 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -56,7 +56,7 @@ class CoreInterface: self._params: Dict[str, float] = {} self.ip4s: List[netaddr.IPNetwork] = [] self.ip6s: List[netaddr.IPNetwork] = [] - self.mac: Optional[str] = None + self.mac: Optional[netaddr.EUI] = None # placeholder position hook self.poshook: Callable[[CoreInterface], None] = lambda x: None # used with EMANE @@ -149,8 +149,8 @@ class CoreInterface: self.ip4s.append(ip) else: self.ip6s.append(ip) - except netaddr.AddrFormatError: - raise CoreError(f"adding invalid address {ip}") + except netaddr.AddrFormatError as e: + raise CoreError(f"adding invalid address {ip}: {e}") def remove_ip(self, ip: str) -> None: """ @@ -167,8 +167,8 @@ class CoreInterface: self.ip4s.remove(ip) else: self.ip6s.remove(ip) - except (netaddr.AddrFormatError, ValueError): - raise CoreError(f"deleting invalid address {ip}") + except (netaddr.AddrFormatError, ValueError) as e: + raise CoreError(f"deleting invalid address {ip}: {e}") def get_ip4(self) -> Optional[netaddr.IPNetwork]: """ @@ -194,16 +194,21 @@ class CoreInterface: """ return self.ip4s + self.ip6s - def set_mac(self, mac: str) -> None: + def set_mac(self, mac: Optional[str]) -> None: """ Set mac address. - :param mac: mac address to set + :param mac: mac address to set, None for random mac :return: nothing + :raises CoreError: when there is an invalid mac address """ - if mac is not None: - mac = utils.validate_mac(mac) - self.mac = mac + if mac is None: + self.mac = mac + else: + try: + self.mac = netaddr.EUI(mac, dialect=netaddr.mac_unix_expanded) + except netaddr.AddrFormatError as e: + raise CoreError(f"invalid mac address({mac}): {e}") def getparam(self, key: str) -> float: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 5b95c3b2..7d8f805e 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -878,7 +878,7 @@ class PtpNet(CoreNetwork): unidirectional = 1 iface1_data = InterfaceData( - id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=iface1.mac + id=iface1.node.get_iface_id(iface1), name=iface1.name, mac=str(iface1.mac) ) ip4 = iface1.get_ip4() if ip4: @@ -890,7 +890,7 @@ class PtpNet(CoreNetwork): iface1_data.ip6_mask = ip6.prefixlen iface2_data = InterfaceData( - id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=iface2.mac + id=iface2.node.get_iface_id(iface2), name=iface2.name, mac=str(iface2.mac) ) ip4 = iface2.get_ip4() if ip4: diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 8fd828d8..3751d9ee 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -7,7 +7,6 @@ import os import threading from typing import IO, TYPE_CHECKING, List, Optional, Tuple -from core import utils from core.constants import MOUNT_BIN, UMOUNT_BIN from core.emulator.data import InterfaceData, LinkOptions from core.emulator.distributed import DistributedServer @@ -74,7 +73,6 @@ class PhysicalNode(CoreNodeBase): :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - mac = utils.validate_mac(mac) iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index a9687d45..485fe159 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -69,7 +69,7 @@ class XorpRtrmgr(CoreService): """ helper for adding link-local address entries (required by OSPFv3) """ - cfg = "\t address %s {\n" % netaddr.EUI(iface.mac).eui64() + cfg = "\t address %s {\n" % iface.mac.eui64() cfg += "\t\tprefix-length: 64\n" cfg += "\t }\n" return cfg @@ -292,7 +292,7 @@ class XorpRipng(XorpService): for iface in node.get_ifaces(control=False): cfg += "\tinterface %s {\n" % iface.name cfg += "\t vif %s {\n" % iface.name - cfg += "\t\taddress %s {\n" % netaddr.EUI(iface.mac).eui64() + cfg += "\t\taddress %s {\n" % iface.mac.eui64() cfg += "\t\t disable: false\n" cfg += "\t\t}\n" cfg += "\t }\n" diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index d716777b..eece57c9 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -230,9 +230,9 @@ def build_node_platform_xml( platform_element.append(nem_element) node.setnemid(iface, nem_id) - macstr = _MAC_PREFIX + ":00:00:" - macstr += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" - iface.set_mac(macstr) + mac = _MAC_PREFIX + ":00:00:" + mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" + iface.set_mac(mac) # increment nem id nem_id += 1 diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 25a62c5f..a827fe25 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -61,7 +61,7 @@ class TestNodes: node.set_mac(iface.node_id, mac) # then - assert iface.mac == mac + assert str(iface.mac) == mac def test_node_set_mac_exception(self, session: Session): # given From 0d4a360e89319f568b9d10515da0f04781db1d0c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 15:32:17 -0700 Subject: [PATCH 082/146] daemon: removed utils.validate_ip and shifted tests to test_nodes --- daemon/core/utils.py | 14 -------------- daemon/tests/test_nodes.py | 17 ++++++++++++++--- daemon/tests/test_utils.py | 18 ------------------ 3 files changed, 14 insertions(+), 35 deletions(-) diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 3b1ea46a..4b932485 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -444,17 +444,3 @@ def validate_mac(value: str) -> str: return str(mac) except netaddr.AddrFormatError as e: raise CoreError(f"invalid mac address {value}: {e}") - - -def validate_ip(value: str) -> str: - """ - Validate ip address with prefix and return formatted version. - - :param value: address to validate - :return: formatted ip address - """ - try: - ip = netaddr.IPNetwork(value) - return str(ip) - except (ValueError, netaddr.AddrFormatError) as e: - raise CoreError(f"invalid ip address {value}: {e}") diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index a827fe25..1741622e 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -75,19 +75,30 @@ class TestNodes: with pytest.raises(CoreError): node.set_mac(iface.node_id, mac) - def test_node_add_ip(self, session: Session): + @pytest.mark.parametrize( + "ip,expected,is_ip6", + [ + ("127", "127.0.0.0/32", False), + ("10.0.0.1/24", "10.0.0.1/24", False), + ("2001::", "2001::/128", True), + ("2001::/64", "2001::/64", True), + ], + ) + def test_node_add_ip(self, session: Session, ip: str, expected: str, is_ip6: bool): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - ip = "192.168.0.1/24" # when node.add_ip(iface.node_id, ip) # then - assert str(iface.get_ip4()) == ip + if is_ip6: + assert str(iface.get_ip6()) == expected + else: + assert str(iface.get_ip4()) == expected def test_node_add_ip_exception(self, session): # given diff --git a/daemon/tests/test_utils.py b/daemon/tests/test_utils.py index 3e43b789..22bf0ee5 100644 --- a/daemon/tests/test_utils.py +++ b/daemon/tests/test_utils.py @@ -25,24 +25,6 @@ class TestUtils: assert len(two_args) == 2 assert len(unicode_args) == 3 - @pytest.mark.parametrize( - "data,expected", - [ - ("127", "127.0.0.0/32"), - ("10.0.0.1/24", "10.0.0.1/24"), - ("2001::", "2001::/128"), - ("2001::/64", "2001::/64"), - ], - ) - def test_validate_ip(self, data: str, expected: str): - value = utils.validate_ip(data) - assert value == expected - - @pytest.mark.parametrize("data", ["256", "1270.0.0.1", "127.0.0.0.1"]) - def test_validate_ip_exception(self, data: str): - with pytest.raises(CoreError): - utils.validate_ip("") - @pytest.mark.parametrize( "data,expected", [ From adfce5263232e89971173b864d6609ef99a1af64 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 15:41:41 -0700 Subject: [PATCH 083/146] daemon: removed utils.validate_mac and shifted tests to test_nodes --- daemon/core/utils.py | 16 +--------------- daemon/tests/test_nodes.py | 18 +++++++++++++----- daemon/tests/test_utils.py | 20 -------------------- 3 files changed, 14 insertions(+), 40 deletions(-) diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 4b932485..0e082187 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -33,7 +33,7 @@ from typing import ( import netaddr -from core.errors import CoreCommandError, CoreError +from core.errors import CoreCommandError if TYPE_CHECKING: from core.emulator.session import Session @@ -430,17 +430,3 @@ def random_mac() -> str: value |= 0x00163E << 24 mac = netaddr.EUI(value, dialect=netaddr.mac_unix_expanded) return str(mac) - - -def validate_mac(value: str) -> str: - """ - Validate mac and return unix formatted version. - - :param value: address to validate - :return: unix formatted mac - """ - try: - mac = netaddr.EUI(value, dialect=netaddr.mac_unix_expanded) - return str(mac) - except netaddr.AddrFormatError as e: - raise CoreError(f"invalid mac address {value}: {e}") diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 1741622e..8ed21f27 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -49,27 +49,35 @@ class TestNodes: with pytest.raises(CoreError): session.get_node(node.id, CoreNode) - def test_node_set_mac(self, session: Session): + @pytest.mark.parametrize( + "mac,expected", + [ + ("AA-AA-AA-FF-FF-FF", "aa:aa:aa:ff:ff:ff"), + ("00:00:00:FF:FF:FF", "00:00:00:ff:ff:ff"), + ], + ) + def test_node_set_mac(self, session: Session, mac: str, expected: str): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - mac = "aa:aa:aa:ff:ff:ff" # when node.set_mac(iface.node_id, mac) # then - assert str(iface.mac) == mac + assert str(iface.mac) == expected - def test_node_set_mac_exception(self, session: Session): + @pytest.mark.parametrize( + "mac", ["AAA:AA:AA:FF:FF:FF", "AA:AA:AA:FF:FF", "AA/AA/AA/FF/FF/FF"] + ) + def test_node_set_mac_exception(self, session: Session, mac: str): # given node = session.add_node(CoreNode) switch = session.add_node(SwitchNode) iface_data = InterfaceData() iface = node.new_iface(switch, iface_data) - mac = "aa:aa:aa:ff:ff:fff" # when with pytest.raises(CoreError): diff --git a/daemon/tests/test_utils.py b/daemon/tests/test_utils.py index 22bf0ee5..5a4f25a4 100644 --- a/daemon/tests/test_utils.py +++ b/daemon/tests/test_utils.py @@ -1,8 +1,6 @@ import netaddr -import pytest from core import utils -from core.errors import CoreError class TestUtils: @@ -25,24 +23,6 @@ class TestUtils: assert len(two_args) == 2 assert len(unicode_args) == 3 - @pytest.mark.parametrize( - "data,expected", - [ - ("AA-AA-AA-FF-FF-FF", "aa:aa:aa:ff:ff:ff"), - ("00:00:00:FF:FF:FF", "00:00:00:ff:ff:ff"), - ], - ) - def test_validate_mac(self, data: str, expected: str): - value = utils.validate_mac(data) - assert value == expected - - @pytest.mark.parametrize( - "data", ["AAA:AA:AA:FF:FF:FF", "AA:AA:AA:FF:FF", "AA/AA/AA/FF/FF/FF"] - ) - def test_validate_mac_exception(self, data: str): - with pytest.raises(CoreError): - utils.validate_mac(data) - def test_random_mac(self): value = utils.random_mac() assert netaddr.EUI(value) is not None From 0356f3b19c637a3a8f43fccc56e78418d34c63c0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 22:08:24 -0700 Subject: [PATCH 084/146] pygui: added type hinting to everything under base core.gui --- daemon/core/api/grpc/client.py | 4 +- daemon/core/gui/app.py | 32 ++--- daemon/core/gui/appconfig.py | 136 ++++++++++---------- daemon/core/gui/coreclient.py | 225 ++++++++++++++++++--------------- daemon/core/gui/graph/graph.py | 4 +- daemon/core/gui/images.py | 56 ++++---- daemon/core/gui/interface.py | 40 +++--- daemon/core/gui/menubar.py | 18 +-- daemon/core/gui/nodeutils.py | 60 +++++---- daemon/core/gui/observers.py | 10 +- daemon/core/gui/statusbar.py | 28 ++-- daemon/core/gui/task.py | 20 +-- daemon/core/gui/themes.py | 61 ++++----- daemon/core/gui/toolbar.py | 68 +++++----- daemon/core/gui/tooltip.py | 18 +-- daemon/core/gui/validation.py | 21 ++- daemon/core/gui/widgets.py | 102 ++++++++------- 17 files changed, 473 insertions(+), 430 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index db908e05..5aa6713d 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -436,7 +436,7 @@ class CoreGrpcClient: session_id: int, handler: Callable[[core_pb2.Event], None], events: List[core_pb2.Event] = None, - ) -> Any: + ) -> grpc.Channel: """ Listen for session events. @@ -453,7 +453,7 @@ class CoreGrpcClient: def throughputs( self, session_id: int, handler: Callable[[core_pb2.ThroughputsEvent], None] - ) -> Any: + ) -> grpc.Channel: """ Listen for throughput events with information for interfaces and bridges. diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index c795a46a..cb385e9e 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -3,10 +3,12 @@ import math import tkinter as tk from tkinter import PhotoImage, font, ttk from tkinter.ttk import Progressbar +from typing import Dict, Optional import grpc from core.gui import appconfig, themes +from core.gui.appconfig import GuiConfig from core.gui.coreclient import CoreClient from core.gui.dialogs.error import ErrorDialog from core.gui.graph.graph import CanvasGraph @@ -16,8 +18,8 @@ from core.gui.nodeutils import NodeUtils from core.gui.statusbar import StatusBar from core.gui.toolbar import Toolbar -WIDTH = 1000 -HEIGHT = 800 +WIDTH: int = 1000 +HEIGHT: int = 800 class Application(ttk.Frame): @@ -27,25 +29,25 @@ class Application(ttk.Frame): NodeUtils.setup() # widgets - self.menubar = None - self.toolbar = None - self.right_frame = None - self.canvas = None - self.statusbar = None - self.progress = None + self.menubar: Optional[Menubar] = None + self.toolbar: Optional[Toolbar] = None + self.right_frame: Optional[ttk.Frame] = None + self.canvas: Optional[CanvasGraph] = None + self.statusbar: Optional[StatusBar] = None + self.progress: Optional[Progressbar] = None # fonts - self.fonts_size = None - self.icon_text_font = None - self.edge_font = None + self.fonts_size: Dict[str, int] = {} + self.icon_text_font: Optional[font.Font] = None + self.edge_font: Optional[font.Font] = None # setup - self.guiconfig = appconfig.read() - self.app_scale = self.guiconfig.scale + self.guiconfig: GuiConfig = appconfig.read() + self.app_scale: float = self.guiconfig.scale self.setup_scaling() - self.style = ttk.Style() + self.style: ttk.Style = ttk.Style() self.setup_theme() - self.core = CoreClient(self, proxy) + self.core: CoreClient = CoreClient(self, proxy) self.setup_app() self.draw() self.core.setup() diff --git a/daemon/core/gui/appconfig.py b/daemon/core/gui/appconfig.py index 077f938d..6bc213eb 100644 --- a/daemon/core/gui/appconfig.py +++ b/daemon/core/gui/appconfig.py @@ -1,32 +1,32 @@ import os import shutil from pathlib import Path -from typing import List, Optional +from typing import Dict, List, Optional, Type import yaml from core.gui import themes -HOME_PATH = Path.home().joinpath(".coregui") -BACKGROUNDS_PATH = HOME_PATH.joinpath("backgrounds") -CUSTOM_EMANE_PATH = HOME_PATH.joinpath("custom_emane") -CUSTOM_SERVICE_PATH = HOME_PATH.joinpath("custom_services") -ICONS_PATH = HOME_PATH.joinpath("icons") -MOBILITY_PATH = HOME_PATH.joinpath("mobility") -XMLS_PATH = HOME_PATH.joinpath("xmls") -CONFIG_PATH = HOME_PATH.joinpath("config.yaml") -LOG_PATH = HOME_PATH.joinpath("gui.log") -SCRIPT_PATH = HOME_PATH.joinpath("scripts") +HOME_PATH: Path = Path.home().joinpath(".coregui") +BACKGROUNDS_PATH: Path = HOME_PATH.joinpath("backgrounds") +CUSTOM_EMANE_PATH: Path = HOME_PATH.joinpath("custom_emane") +CUSTOM_SERVICE_PATH: Path = HOME_PATH.joinpath("custom_services") +ICONS_PATH: Path = HOME_PATH.joinpath("icons") +MOBILITY_PATH: Path = HOME_PATH.joinpath("mobility") +XMLS_PATH: Path = HOME_PATH.joinpath("xmls") +CONFIG_PATH: Path = HOME_PATH.joinpath("config.yaml") +LOG_PATH: Path = HOME_PATH.joinpath("gui.log") +SCRIPT_PATH: Path = HOME_PATH.joinpath("scripts") # local paths -DATA_PATH = Path(__file__).parent.joinpath("data") -LOCAL_ICONS_PATH = DATA_PATH.joinpath("icons").absolute() -LOCAL_BACKGROUND_PATH = DATA_PATH.joinpath("backgrounds").absolute() -LOCAL_XMLS_PATH = DATA_PATH.joinpath("xmls").absolute() -LOCAL_MOBILITY_PATH = DATA_PATH.joinpath("mobility").absolute() +DATA_PATH: Path = Path(__file__).parent.joinpath("data") +LOCAL_ICONS_PATH: Path = DATA_PATH.joinpath("icons").absolute() +LOCAL_BACKGROUND_PATH: Path = DATA_PATH.joinpath("backgrounds").absolute() +LOCAL_XMLS_PATH: Path = DATA_PATH.joinpath("xmls").absolute() +LOCAL_MOBILITY_PATH: Path = DATA_PATH.joinpath("mobility").absolute() # configuration data -TERMINALS = { +TERMINALS: Dict[str, str] = { "xterm": "xterm -e", "aterm": "aterm -e", "eterm": "eterm -e", @@ -36,45 +36,45 @@ TERMINALS = { "xfce4-terminal": "xfce4-terminal -x", "gnome-terminal": "gnome-terminal --window --", } -EDITORS = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"] +EDITORS: List[str] = ["$EDITOR", "vim", "emacs", "gedit", "nano", "vi"] class IndentDumper(yaml.Dumper): - def increase_indent(self, flow=False, indentless=False): - return super().increase_indent(flow, False) + def increase_indent(self, flow: bool = False, indentless: bool = False) -> None: + super().increase_indent(flow, False) class CustomNode(yaml.YAMLObject): - yaml_tag = "!CustomNode" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!CustomNode" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__(self, name: str, image: str, services: List[str]) -> None: - self.name = name - self.image = image - self.services = services + self.name: str = name + self.image: str = image + self.services: List[str] = services class CoreServer(yaml.YAMLObject): - yaml_tag = "!CoreServer" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!CoreServer" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__(self, name: str, address: str) -> None: - self.name = name - self.address = address + self.name: str = name + self.address: str = address class Observer(yaml.YAMLObject): - yaml_tag = "!Observer" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!Observer" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__(self, name: str, cmd: str) -> None: - self.name = name - self.cmd = cmd + self.name: str = name + self.cmd: str = cmd class PreferencesConfig(yaml.YAMLObject): - yaml_tag = "!PreferencesConfig" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!PreferencesConfig" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__( self, @@ -85,17 +85,17 @@ class PreferencesConfig(yaml.YAMLObject): width: int = 1000, height: int = 750, ) -> None: - self.theme = theme - self.editor = editor - self.terminal = terminal - self.gui3d = gui3d - self.width = width - self.height = height + self.theme: str = theme + self.editor: str = editor + self.terminal: str = terminal + self.gui3d: str = gui3d + self.width: int = width + self.height: int = height class LocationConfig(yaml.YAMLObject): - yaml_tag = "!LocationConfig" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!LocationConfig" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__( self, @@ -107,18 +107,18 @@ class LocationConfig(yaml.YAMLObject): alt: float = 2.0, scale: float = 150.0, ) -> None: - self.x = x - self.y = y - self.z = z - self.lat = lat - self.lon = lon - self.alt = alt - self.scale = scale + self.x: float = x + self.y: float = y + self.z: float = z + self.lat: float = lat + self.lon: float = lon + self.alt: float = alt + self.scale: float = scale class IpConfigs(yaml.YAMLObject): - yaml_tag = "!IpConfigs" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!IpConfigs" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__( self, @@ -129,21 +129,21 @@ class IpConfigs(yaml.YAMLObject): ) -> None: if ip4s is None: ip4s = ["10.0.0.0", "192.168.0.0", "172.16.0.0"] - self.ip4s = ip4s + self.ip4s: List[str] = ip4s if ip6s is None: ip6s = ["2001::", "2002::", "a::"] - self.ip6s = ip6s + self.ip6s: List[str] = ip6s if ip4 is None: ip4 = self.ip4s[0] - self.ip4 = ip4 + self.ip4: str = ip4 if ip6 is None: ip6 = self.ip6s[0] - self.ip6 = ip6 + self.ip6: str = ip6 class GuiConfig(yaml.YAMLObject): - yaml_tag = "!GuiConfig" - yaml_loader = yaml.SafeLoader + yaml_tag: str = "!GuiConfig" + yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader def __init__( self, @@ -159,30 +159,30 @@ class GuiConfig(yaml.YAMLObject): ) -> None: if preferences is None: preferences = PreferencesConfig() - self.preferences = preferences + self.preferences: PreferencesConfig = preferences if location is None: location = LocationConfig() - self.location = location + self.location: LocationConfig = location if servers is None: servers = [] - self.servers = servers + self.servers: List[CoreServer] = servers if nodes is None: nodes = [] - self.nodes = nodes + self.nodes: List[CustomNode] = nodes if recentfiles is None: recentfiles = [] - self.recentfiles = recentfiles + self.recentfiles: List[str] = recentfiles if observers is None: observers = [] - self.observers = observers - self.scale = scale + self.observers: List[Observer] = observers + self.scale: float = scale if ips is None: ips = IpConfigs() - self.ips = ips - self.mac = mac + self.ips: IpConfigs = ips + self.mac: str = mac -def copy_files(current_path, new_path) -> None: +def copy_files(current_path: Path, new_path: Path) -> None: for current_file in current_path.glob("*"): new_file = new_path.joinpath(current_file.name) shutil.copy(current_file, new_file) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 8b0c423c..24708769 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -4,18 +4,41 @@ Incorporate grpc into python tkinter GUI import json import logging import os +import tkinter as tk from pathlib import Path from tkinter import messagebox -from typing import TYPE_CHECKING, Dict, Iterable, List, Optional +from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple import grpc -from core.api.grpc import client, common_pb2, configservices_pb2, core_pb2 +from core.api.grpc import client +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.configservices_pb2 import ConfigService, ConfigServiceConfig +from core.api.grpc.core_pb2 import ( + Event, + ExceptionEvent, + Hook, + Interface, + Link, + LinkEvent, + LinkType, + MessageType, + Node, + NodeEvent, + NodeType, + Position, + SessionLocation, + SessionState, + StartSessionResponse, + StopSessionResponse, + ThroughputsEvent, +) from core.api.grpc.emane_pb2 import EmaneModelConfig from core.api.grpc.mobility_pb2 import MobilityConfig from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig from core.api.grpc.wlan_pb2 import WlanConfig from core.gui import appconfig +from core.gui.appconfig import CoreServer from core.gui.dialogs.emaneinstall import EmaneInstallDialog from core.gui.dialogs.error import ErrorDialog from core.gui.dialogs.mobilityplayer import MobilityPlayer @@ -34,47 +57,46 @@ GUI_SOURCE = "gui" class CoreClient: - def __init__(self, app: "Application", proxy: bool): + def __init__(self, app: "Application", proxy: bool) -> None: """ Create a CoreGrpc instance """ - self._client = client.CoreGrpcClient(proxy=proxy) - self.session_id = None - self.node_ids = [] - self.app = app - self.master = app.master - self.services = {} - self.config_services_groups = {} - self.config_services = {} - self.default_services = {} - self.emane_models = [] - self.observer = None + self.app: "Application" = app + self.master: tk.Tk = app.master + self._client: client.CoreGrpcClient = client.CoreGrpcClient(proxy=proxy) + self.session_id: Optional[int] = None + self.services: Dict[str, Set[str]] = {} + self.config_services_groups: Dict[str, Set[str]] = {} + self.config_services: Dict[str, ConfigService] = {} + self.default_services: Dict[NodeType, Set[str]] = {} + self.emane_models: List[str] = [] + self.observer: Optional[str] = None # loaded configuration data - self.servers = {} - self.custom_nodes = {} - self.custom_observers = {} + self.servers: Dict[str, CoreServer] = {} + self.custom_nodes: Dict[str, NodeDraw] = {} + self.custom_observers: Dict[str, str] = {} self.read_config() # helpers - self.iface_to_edge = {} - self.ifaces_manager = InterfaceManager(self.app) + self.iface_to_edge: Dict[Tuple[int, int], Tuple[int, int]] = {} + self.ifaces_manager: InterfaceManager = InterfaceManager(self.app) # session data - self.state = None - self.canvas_nodes = {} - self.location = None - self.links = {} - self.hooks = {} - self.emane_config = None - self.mobility_players = {} - self.handling_throughputs = None - self.handling_events = None - self.xml_dir = None - self.xml_file = None + self.state: Optional[SessionState] = None + self.canvas_nodes: Dict[int, CanvasNode] = {} + self.location: Optional[SessionLocation] = None + self.links: Dict[Tuple[int, int], CanvasEdge] = {} + self.hooks: Dict[str, Hook] = {} + self.emane_config: Dict[str, ConfigOption] = {} + self.mobility_players: Dict[int, MobilityPlayer] = {} + self.handling_throughputs: Optional[grpc.Channel] = None + self.handling_events: Optional[grpc.Channel] = None + self.xml_dir: Optional[str] = None + self.xml_file: Optional[str] = None @property - def client(self): + def client(self) -> client.CoreGrpcClient: if self.session_id: response = self._client.check_session(self.session_id) if not response.result: @@ -89,7 +111,7 @@ class CoreClient: self.enable_throughputs() return self._client - def reset(self): + def reset(self) -> None: # helpers self.ifaces_manager.reset() self.iface_to_edge.clear() @@ -104,14 +126,14 @@ class CoreClient: self.cancel_throughputs() self.cancel_events() - def close_mobility_players(self): + def close_mobility_players(self) -> None: for mobility_player in self.mobility_players.values(): mobility_player.close() - def set_observer(self, value: str): + def set_observer(self, value: Optional[str]) -> None: self.observer = value - def read_config(self): + def read_config(self) -> None: # read distributed servers for server in self.app.guiconfig.servers: self.servers[server.name] = server @@ -125,7 +147,7 @@ class CoreClient: for observer in self.app.guiconfig.observers: self.custom_observers[observer.name] = observer - def handle_events(self, event: core_pb2.Event): + def handle_events(self, event: Event) -> None: if event.session_id != self.session_id: logging.warning( "ignoring event session(%s) current(%s)", @@ -139,7 +161,7 @@ class CoreClient: elif event.HasField("session_event"): logging.info("session event: %s", event) session_event = event.session_event - if session_event.event <= core_pb2.SessionState.SHUTDOWN: + if session_event.event <= SessionState.SHUTDOWN: self.state = event.session_event.event elif session_event.event in {7, 8, 9}: node_id = session_event.node_id @@ -162,7 +184,7 @@ class CoreClient: else: logging.info("unhandled event: %s", event) - def handle_link_event(self, event: core_pb2.LinkEvent): + def handle_link_event(self, event: LinkEvent) -> None: logging.debug("Link event: %s", event) node1_id = event.link.node1_id node2_id = event.link.node2_id @@ -171,16 +193,16 @@ class CoreClient: return canvas_node1 = self.canvas_nodes[node1_id] canvas_node2 = self.canvas_nodes[node2_id] - if event.message_type == core_pb2.MessageType.ADD: + if event.message_type == MessageType.ADD: self.app.canvas.add_wireless_edge(canvas_node1, canvas_node2, event.link) - elif event.message_type == core_pb2.MessageType.DELETE: + elif event.message_type == MessageType.DELETE: self.app.canvas.delete_wireless_edge(canvas_node1, canvas_node2, event.link) - elif event.message_type == core_pb2.MessageType.NONE: + elif event.message_type == MessageType.NONE: self.app.canvas.update_wireless_edge(canvas_node1, canvas_node2, event.link) else: logging.warning("unknown link event: %s", event) - def handle_node_event(self, event: core_pb2.NodeEvent): + def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) if event.source == GUI_SOURCE: return @@ -190,22 +212,22 @@ class CoreClient: canvas_node = self.canvas_nodes[node_id] canvas_node.move(x, y) - def enable_throughputs(self): + def enable_throughputs(self) -> None: self.handling_throughputs = self.client.throughputs( self.session_id, self.handle_throughputs ) - def cancel_throughputs(self): + def cancel_throughputs(self) -> None: if self.handling_throughputs: self.handling_throughputs.cancel() self.handling_throughputs = None - def cancel_events(self): + def cancel_events(self) -> None: if self.handling_events: self.handling_events.cancel() self.handling_events = None - def handle_throughputs(self, event: core_pb2.ThroughputsEvent): + def handle_throughputs(self, event: ThroughputsEvent) -> None: if event.session_id != self.session_id: logging.warning( "ignoring throughput event session(%s) current(%s)", @@ -216,11 +238,11 @@ class CoreClient: logging.debug("handling throughputs event: %s", event) self.app.after(0, self.app.canvas.set_throughputs, event) - def handle_exception_event(self, event: core_pb2.ExceptionEvent): + def handle_exception_event(self, event: ExceptionEvent) -> None: logging.info("exception event: %s", event) self.app.statusbar.core_alarms.append(event) - def join_session(self, session_id: int, query_location: bool = True): + def join_session(self, session_id: int, query_location: bool = True) -> None: logging.info("join session(%s)", session_id) # update session and title self.session_id = session_id @@ -331,9 +353,9 @@ class CoreClient: self.app.after(0, self.app.joined_session_update) def is_runtime(self) -> bool: - return self.state == core_pb2.SessionState.RUNTIME + return self.state == SessionState.RUNTIME - def parse_metadata(self, config: Dict[str, str]): + def parse_metadata(self, config: Dict[str, str]) -> None: # canvas setting canvas_config = config.get("canvas") logging.debug("canvas metadata: %s", canvas_config) @@ -386,7 +408,7 @@ class CoreClient: except ValueError: logging.exception("unknown shape: %s", shape_type) - def create_new_session(self): + def create_new_session(self) -> None: """ Create a new session """ @@ -394,7 +416,7 @@ class CoreClient: response = self.client.create_session() logging.info("created session: %s", response) location_config = self.app.guiconfig.location - self.location = core_pb2.SessionLocation( + self.location = SessionLocation( x=location_config.x, y=location_config.y, z=location_config.z, @@ -407,7 +429,7 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("New Session Error", e) - def delete_session(self, session_id: int = None): + def delete_session(self, session_id: int = None) -> None: if session_id is None: session_id = self.session_id try: @@ -416,7 +438,7 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("Delete Session Error", e) - def setup(self): + def setup(self) -> None: """ Query sessions, if there exist any, prompt whether to join one """ @@ -451,7 +473,7 @@ class CoreClient: dialog.show() self.app.close() - def edit_node(self, core_node: core_pb2.Node): + def edit_node(self, core_node: Node) -> None: try: self.client.edit_node( self.session_id, core_node.id, core_node.position, source=GUI_SOURCE @@ -459,12 +481,12 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("Edit Node Error", e) - def start_session(self) -> core_pb2.StartSessionResponse: + def start_session(self) -> StartSessionResponse: self.ifaces_manager.reset_mac() nodes = [x.core_node for x in self.canvas_nodes.values()] links = [] for edge in self.links.values(): - link = core_pb2.Link() + link = Link() link.CopyFrom(edge.link) if link.HasField("iface1") and not link.iface1.mac: link.iface1.mac = self.ifaces_manager.next_mac() @@ -485,7 +507,7 @@ class CoreClient: emane_config = {x: self.emane_config[x].value for x in self.emane_config} else: emane_config = None - response = core_pb2.StartSessionResponse(result=False) + response = StartSessionResponse(result=False) try: response = self.client.start_session( self.session_id, @@ -511,10 +533,10 @@ class CoreClient: self.app.show_grpc_exception("Start Session Error", e) return response - def stop_session(self, session_id: int = None) -> core_pb2.StartSessionResponse: + def stop_session(self, session_id: int = None) -> StopSessionResponse: if not session_id: session_id = self.session_id - response = core_pb2.StopSessionResponse(result=False) + response = StopSessionResponse(result=False) try: response = self.client.stop_session(session_id) logging.info("stopped session(%s), result: %s", session_id, response) @@ -522,9 +544,9 @@ class CoreClient: self.app.show_grpc_exception("Stop Session Error", e) return response - def show_mobility_players(self): + def show_mobility_players(self) -> None: for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != core_pb2.NodeType.WIRELESS_LAN: + if canvas_node.core_node.type != NodeType.WIRELESS_LAN: continue if canvas_node.mobility_config: mobility_player = MobilityPlayer( @@ -534,7 +556,7 @@ class CoreClient: self.mobility_players[node_id] = mobility_player mobility_player.show() - def set_metadata(self): + def set_metadata(self) -> None: # create canvas data wallpaper = None if self.app.canvas.wallpaper_file: @@ -558,7 +580,7 @@ class CoreClient: response = self.client.set_session_metadata(self.session_id, metadata) logging.info("set session metadata %s, result: %s", metadata, response) - def launch_terminal(self, node_id: int): + def launch_terminal(self, node_id: int) -> None: try: terminal = self.app.guiconfig.preferences.terminal if not terminal: @@ -575,12 +597,12 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("Node Terminal Error", e) - def save_xml(self, file_path: str): + def save_xml(self, file_path: str) -> None: """ Save core session as to an xml file """ try: - if self.state != core_pb2.SessionState.RUNTIME: + if self.state != SessionState.RUNTIME: logging.debug("Send session data to the daemon") self.send_data() response = self.client.save_xml(self.session_id, file_path) @@ -588,7 +610,7 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("Save XML Error", e) - def open_xml(self, file_path: str): + def open_xml(self, file_path: str) -> None: """ Open core xml """ @@ -627,7 +649,8 @@ class CoreClient: shutdown=shutdowns, ) logging.info( - "Set %s service for node(%s), files: %s, Startup: %s, Validation: %s, Shutdown: %s, Result: %s", + "Set %s service for node(%s), files: %s, Startup: %s, " + "Validation: %s, Shutdown: %s, Result: %s", service_name, node_id, files, @@ -656,7 +679,7 @@ class CoreClient: def set_node_service_file( self, node_id: int, service_name: str, file_name: str, data: str - ): + ) -> None: response = self.client.set_node_service_file( self.session_id, node_id, service_name, file_name, data ) @@ -669,18 +692,16 @@ class CoreClient: response, ) - def create_nodes_and_links(self): + def create_nodes_and_links(self) -> None: """ create nodes and links that have not been created yet """ node_protos = [x.core_node for x in self.canvas_nodes.values()] link_protos = [x.link for x in self.links.values()] - if self.state != core_pb2.SessionState.DEFINITION: - self.client.set_session_state( - self.session_id, core_pb2.SessionState.DEFINITION - ) + if self.state != SessionState.DEFINITION: + self.client.set_session_state(self.session_id, SessionState.DEFINITION) - self.client.set_session_state(self.session_id, core_pb2.SessionState.DEFINITION) + self.client.set_session_state(self.session_id, SessionState.DEFINITION) for node_proto in node_protos: response = self.client.add_node(self.session_id, node_proto) logging.debug("create node: %s", response) @@ -695,7 +716,7 @@ class CoreClient: ) logging.debug("create link: %s", response) - def send_data(self): + def send_data(self) -> None: """ send to daemon all session info, but don't start the session """ @@ -738,10 +759,9 @@ class CoreClient: if self.emane_config: config = {x: self.emane_config[x].value for x in self.emane_config} self.client.set_emane_config(self.session_id, config) - self.set_metadata() - def close(self): + def close(self) -> None: """ Clean ups when done using grpc """ @@ -760,31 +780,31 @@ class CoreClient: return i def create_node( - self, x: float, y: float, node_type: core_pb2.NodeType, model: str - ) -> Optional[core_pb2.Node]: + self, x: float, y: float, node_type: NodeType, model: str + ) -> Optional[Node]: """ Add node, with information filled in, to grpc manager """ node_id = self.next_node_id() - position = core_pb2.Position(x=x, y=y) + position = Position(x=x, y=y) image = None if NodeUtils.is_image_node(node_type): image = "ubuntu:latest" emane = None - if node_type == core_pb2.NodeType.EMANE: + if node_type == NodeType.EMANE: if not self.emane_models: dialog = EmaneInstallDialog(self.app) dialog.show() return emane = self.emane_models[0] name = f"EMANE{node_id}" - elif node_type == core_pb2.NodeType.WIRELESS_LAN: + elif node_type == NodeType.WIRELESS_LAN: name = f"WLAN{node_id}" - elif node_type in [core_pb2.NodeType.RJ45, core_pb2.NodeType.TUNNEL]: + elif node_type in [NodeType.RJ45, NodeType.TUNNEL]: name = "UNASSIGNED" else: name = f"n{node_id}" - node = core_pb2.Node( + node = Node( id=node_id, type=node_type, name=name, @@ -810,7 +830,7 @@ class CoreClient: ) return node - def deleted_graph_nodes(self, canvas_nodes: List[core_pb2.Node]): + def deleted_graph_nodes(self, canvas_nodes: List[Node]) -> None: """ remove the nodes selected by the user and anything related to that node such as link, configurations, interfaces @@ -826,14 +846,14 @@ class CoreClient: links.append(edge.link) self.ifaces_manager.removed(links) - def create_iface(self, canvas_node: CanvasNode) -> core_pb2.Interface: + def create_iface(self, canvas_node: CanvasNode) -> Interface: node = canvas_node.core_node ip4, ip6 = self.ifaces_manager.get_ips(node) ip4_mask = self.ifaces_manager.ip4_mask ip6_mask = self.ifaces_manager.ip6_mask iface_id = canvas_node.next_iface_id() name = f"eth{iface_id}" - iface = core_pb2.Interface( + iface = Interface( id=iface_id, name=name, ip4=ip4, @@ -852,7 +872,7 @@ class CoreClient: def create_link( self, edge: CanvasEdge, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode - ): + ) -> None: """ Create core link for a pair of canvas nodes, with token referencing the canvas edge. @@ -873,8 +893,8 @@ class CoreClient: dst_iface = self.create_iface(canvas_dst_node) self.iface_to_edge[(dst_node.id, dst_iface.id)] = edge.token - link = core_pb2.Link( - type=core_pb2.LinkType.WIRED, + link = Link( + type=LinkType.WIRED, node1_id=src_node.id, node2_id=dst_node.id, iface1=src_iface, @@ -896,7 +916,7 @@ class CoreClient: def get_wlan_configs_proto(self) -> List[WlanConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != core_pb2.NodeType.WIRELESS_LAN: + if canvas_node.core_node.type != NodeType.WIRELESS_LAN: continue if not canvas_node.wlan_config: continue @@ -910,7 +930,7 @@ class CoreClient: def get_mobility_configs_proto(self) -> List[MobilityConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != core_pb2.NodeType.WIRELESS_LAN: + if canvas_node.core_node.type != NodeType.WIRELESS_LAN: continue if not canvas_node.mobility_config: continue @@ -924,7 +944,7 @@ class CoreClient: def get_emane_model_configs_proto(self) -> List[EmaneModelConfig]: configs = [] for canvas_node in self.canvas_nodes.values(): - if canvas_node.core_node.type != core_pb2.NodeType.EMANE: + if canvas_node.core_node.type != NodeType.EMANE: continue node_id = canvas_node.core_node.id for key, config in canvas_node.emane_model_configs.items(): @@ -975,9 +995,7 @@ class CoreClient: configs.append(config_proto) return configs - def get_config_service_configs_proto( - self - ) -> List[configservices_pb2.ConfigServiceConfig]: + def get_config_service_configs_proto(self) -> List[ConfigServiceConfig]: config_service_protos = [] for canvas_node in self.canvas_nodes.values(): if not NodeUtils.is_container_node(canvas_node.core_node.type): @@ -987,7 +1005,7 @@ class CoreClient: node_id = canvas_node.core_node.id for name, service_config in canvas_node.config_service_configs.items(): config = service_config.get("config", {}) - config_proto = configservices_pb2.ConfigServiceConfig( + config_proto = ConfigServiceConfig( node_id=node_id, name=name, templates=service_config["templates"], @@ -1000,7 +1018,7 @@ class CoreClient: logging.info("running node(%s) cmd: %s", node_id, self.observer) return self.client.node_command(self.session_id, node_id, self.observer).output - def get_wlan_config(self, node_id: int) -> Dict[str, common_pb2.ConfigOption]: + def get_wlan_config(self, node_id: int) -> Dict[str, ConfigOption]: response = self.client.get_wlan_config(self.session_id, node_id) config = response.config logging.debug( @@ -1010,7 +1028,7 @@ class CoreClient: ) return dict(config) - def get_mobility_config(self, node_id: int) -> Dict[str, common_pb2.ConfigOption]: + def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]: response = self.client.get_mobility_config(self.session_id, node_id) config = response.config logging.debug( @@ -1022,7 +1040,7 @@ class CoreClient: def get_emane_model_config( self, node_id: int, model: str, iface_id: int = None - ) -> Dict[str, common_pb2.ConfigOption]: + ) -> Dict[str, ConfigOption]: if iface_id is None: iface_id = -1 response = self.client.get_emane_model_config( @@ -1030,7 +1048,8 @@ class CoreClient: ) config = response.config logging.debug( - "get emane model config: node id: %s, EMANE model: %s, interface: %s, config: %s", + "get emane model config: node id: %s, EMANE model: %s, " + "interface: %s, config: %s", node_id, model, iface_id, @@ -1038,7 +1057,7 @@ class CoreClient: ) return dict(config) - def execute_script(self, script): + def execute_script(self, script) -> None: response = self.client.execute_script(script) logging.info("execute python script %s", response) if response.session_id != -1: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 269e3973..834220ea 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -2,7 +2,7 @@ import logging import tkinter as tk from copy import deepcopy from tkinter import BooleanVar -from typing import TYPE_CHECKING, Tuple +from typing import TYPE_CHECKING, Optional, Tuple from PIL import Image, ImageTk @@ -864,7 +864,7 @@ class CanvasGraph(tk.Canvas): for tag in tags.ORGANIZE_TAGS: self.tag_raise(tag) - def set_wallpaper(self, filename: str): + def set_wallpaper(self, filename: Optional[str]): logging.debug("setting wallpaper: %s", filename) if filename: img = Image.open(filename) diff --git a/daemon/core/gui/images.py b/daemon/core/gui/images.py index 3a953054..22719457 100644 --- a/daemon/core/gui/images.py +++ b/daemon/core/gui/images.py @@ -1,46 +1,44 @@ from enum import Enum from tkinter import messagebox +from typing import Dict, Optional, Tuple -from PIL import Image, ImageTk +from PIL import Image +from PIL.ImageTk import PhotoImage -from core.api.grpc import core_pb2 +from core.api.grpc.core_pb2 import NodeType from core.gui.appconfig import LOCAL_ICONS_PATH class Images: - images = {} + images: Dict[str, str] = {} @classmethod - def create(cls, file_path: str, width: int, height: int = None): + def create(cls, file_path: str, width: int, height: int = None) -> PhotoImage: if height is None: height = width image = Image.open(file_path) image = image.resize((width, height), Image.ANTIALIAS) - return ImageTk.PhotoImage(image) + return PhotoImage(image) @classmethod - def load_all(cls): + def load_all(cls) -> None: for image in LOCAL_ICONS_PATH.glob("*"): cls.images[image.stem] = str(image) @classmethod - def get( - cls, image_enum: Enum, width: int, height: int = None - ) -> ImageTk.PhotoImage: + def get(cls, image_enum: Enum, width: int, height: int = None) -> PhotoImage: file_path = cls.images[image_enum.value] return cls.create(file_path, width, height) @classmethod def get_with_image_file( cls, stem: str, width: int, height: int = None - ) -> ImageTk.PhotoImage: + ) -> PhotoImage: file_path = cls.images[stem] return cls.create(file_path, width, height) @classmethod - def get_custom( - cls, name: str, width: int, height: int = None - ) -> ImageTk.PhotoImage: + def get_custom(cls, name: str, width: int, height: int = None) -> PhotoImage: try: file_path = cls.images[name] return cls.create(file_path, width, height) @@ -95,22 +93,22 @@ class ImageEnum(Enum): class TypeToImage: - type_to_image = { - (core_pb2.NodeType.DEFAULT, "router"): ImageEnum.ROUTER, - (core_pb2.NodeType.DEFAULT, "PC"): ImageEnum.PC, - (core_pb2.NodeType.DEFAULT, "host"): ImageEnum.HOST, - (core_pb2.NodeType.DEFAULT, "mdr"): ImageEnum.MDR, - (core_pb2.NodeType.DEFAULT, "prouter"): ImageEnum.PROUTER, - (core_pb2.NodeType.HUB, ""): ImageEnum.HUB, - (core_pb2.NodeType.SWITCH, ""): ImageEnum.SWITCH, - (core_pb2.NodeType.WIRELESS_LAN, ""): ImageEnum.WLAN, - (core_pb2.NodeType.EMANE, ""): ImageEnum.EMANE, - (core_pb2.NodeType.RJ45, ""): ImageEnum.RJ45, - (core_pb2.NodeType.TUNNEL, ""): ImageEnum.TUNNEL, - (core_pb2.NodeType.DOCKER, ""): ImageEnum.DOCKER, - (core_pb2.NodeType.LXC, ""): ImageEnum.LXC, + type_to_image: Dict[Tuple[NodeType, str], ImageEnum] = { + (NodeType.DEFAULT, "router"): ImageEnum.ROUTER, + (NodeType.DEFAULT, "PC"): ImageEnum.PC, + (NodeType.DEFAULT, "host"): ImageEnum.HOST, + (NodeType.DEFAULT, "mdr"): ImageEnum.MDR, + (NodeType.DEFAULT, "prouter"): ImageEnum.PROUTER, + (NodeType.HUB, ""): ImageEnum.HUB, + (NodeType.SWITCH, ""): ImageEnum.SWITCH, + (NodeType.WIRELESS_LAN, ""): ImageEnum.WLAN, + (NodeType.EMANE, ""): ImageEnum.EMANE, + (NodeType.RJ45, ""): ImageEnum.RJ45, + (NodeType.TUNNEL, ""): ImageEnum.TUNNEL, + (NodeType.DOCKER, ""): ImageEnum.DOCKER, + (NodeType.LXC, ""): ImageEnum.LXC, } @classmethod - def get(cls, node_type, model): - return cls.type_to_image.get((node_type, model), None) + def get(cls, node_type, model) -> Optional[ImageEnum]: + return cls.type_to_image.get((node_type, model)) diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 6c82ca51..f4f2e3cc 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -1,18 +1,18 @@ import logging -from typing import TYPE_CHECKING, Any, List, Optional, Set, Tuple +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple import netaddr from netaddr import EUI, IPNetwork +from core.api.grpc.core_pb2 import Interface, Link, Node +from core.gui.graph.node import CanvasNode 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 get_index(iface: "core_pb2.Interface") -> Optional[int]: +def get_index(iface: Interface) -> Optional[int]: if not iface.ip4: return None net = netaddr.IPNetwork(f"{iface.ip4}/{iface.ip4_mask}") @@ -44,18 +44,18 @@ class Subnets: class InterfaceManager: def __init__(self, app: "Application") -> None: - self.app = app + self.app: "Application" = app ip4 = self.app.guiconfig.ips.ip4 ip6 = self.app.guiconfig.ips.ip6 - self.ip4_mask = 24 - self.ip6_mask = 64 - self.ip4_subnets = IPNetwork(f"{ip4}/{self.ip4_mask}") - self.ip6_subnets = IPNetwork(f"{ip6}/{self.ip6_mask}") + self.ip4_mask: int = 24 + self.ip6_mask: int = 64 + self.ip4_subnets: IPNetwork = IPNetwork(f"{ip4}/{self.ip4_mask}") + self.ip6_subnets: IPNetwork = IPNetwork(f"{ip6}/{self.ip6_mask}") mac = self.app.guiconfig.mac - self.mac = EUI(mac, dialect=netaddr.mac_unix_expanded) - self.current_mac = None - self.current_subnets = None - self.used_subnets = {} + self.mac: EUI = EUI(mac, dialect=netaddr.mac_unix_expanded) + self.current_mac: Optional[EUI] = None + self.current_subnets: Optional[Subnets] = None + self.used_subnets: Dict[Tuple[IPNetwork, IPNetwork], Subnets] = {} def update_ips(self, ip4: str, ip6: str) -> None: self.reset() @@ -84,7 +84,7 @@ class InterfaceManager: self.current_subnets = None self.used_subnets.clear() - def removed(self, links: List["core_pb2.Link"]) -> None: + def removed(self, links: List[Link]) -> None: # get remaining subnets remaining_subnets = set() for edge in self.app.core.links.values(): @@ -114,7 +114,7 @@ class InterfaceManager: subnets.used_indexes.discard(index) self.current_subnets = None - def joined(self, links: List["core_pb2.Link"]) -> None: + def joined(self, links: List[Link]) -> None: ifaces = [] for link in links: if link.HasField("iface1"): @@ -132,7 +132,7 @@ class InterfaceManager: if subnets.key() not in self.used_subnets: self.used_subnets[subnets.key()] = subnets - def next_index(self, node: "core_pb2.Node") -> int: + def next_index(self, node: Node) -> int: if NodeUtils.is_router_node(node): index = 1 else: @@ -144,13 +144,13 @@ class InterfaceManager: index += 1 return index - def get_ips(self, node: "core_pb2.Node") -> [str, str]: + def get_ips(self, node: Node) -> [str, str]: index = self.next_index(node) ip4 = self.current_subnets.ip4[index] ip6 = self.current_subnets.ip6[index] return str(ip4), str(ip6) - def get_subnets(self, iface: "core_pb2.Interface") -> Subnets: + def get_subnets(self, iface: Interface) -> Subnets: ip4_subnet = self.ip4_subnets if iface.ip4: ip4_subnet = IPNetwork(f"{iface.ip4}/{iface.ip4_mask}").cidr @@ -161,7 +161,7 @@ class InterfaceManager: return self.used_subnets.get(subnets.key(), subnets) def determine_subnets( - self, canvas_src_node: "CanvasNode", canvas_dst_node: "CanvasNode" + self, canvas_src_node: CanvasNode, canvas_dst_node: CanvasNode ) -> None: src_node = canvas_src_node.core_node dst_node = canvas_dst_node.core_node @@ -185,7 +185,7 @@ class InterfaceManager: logging.info("ignoring subnet change for link between network nodes") def find_subnets( - self, canvas_node: "CanvasNode", visited: Set[int] = None + self, canvas_node: CanvasNode, visited: Set[int] = None ) -> Optional[IPNetwork]: logging.info("finding subnet for node: %s", canvas_node.core_node.name) canvas = self.app.canvas diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index cf4216d8..523f8f11 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -4,9 +4,10 @@ import tkinter as tk import webbrowser from functools import partial from tkinter import filedialog, messagebox -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.appconfig import XMLS_PATH +from core.gui.coreclient import CoreClient from core.gui.dialogs.about import AboutDialog from core.gui.dialogs.canvassizeandscale import SizeAndScaleDialog from core.gui.dialogs.canvaswallpaper import CanvasWallpaperDialog @@ -22,6 +23,7 @@ from core.gui.dialogs.servers import ServersDialog from core.gui.dialogs.sessionoptions import SessionOptionsDialog from core.gui.dialogs.sessions import SessionsDialog from core.gui.dialogs.throughput import ThroughputDialog +from core.gui.graph.graph import CanvasGraph from core.gui.nodeutils import ICON_SIZE from core.gui.observers import ObserversMenu from core.gui.task import ProgressTask @@ -29,7 +31,7 @@ from core.gui.task import ProgressTask if TYPE_CHECKING: from core.gui.app import Application -MAX_FILES = 3 +MAX_FILES: int = 3 class Menubar(tk.Menu): @@ -42,12 +44,12 @@ class Menubar(tk.Menu): Create a CoreMenubar instance """ super().__init__(app) - self.app = app - self.core = app.core - self.canvas = app.canvas - self.recent_menu = None - self.edit_menu = None - self.observers_menu = None + self.app: "Application" = app + self.core: CoreClient = app.core + self.canvas: CanvasGraph = app.canvas + self.recent_menu: Optional[tk.Menu] = None + self.edit_menu: Optional[tk.Menu] = None + self.observers_menu: Optional[tk.Menu] = None self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 40204662..402eca4d 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -1,38 +1,36 @@ import logging -from typing import TYPE_CHECKING, List, Optional, Set +from typing import List, Optional, Set + +from PIL.ImageTk import PhotoImage from core.api.grpc.core_pb2 import Node, NodeType from core.gui.appconfig import CustomNode, GuiConfig from core.gui.images import ImageEnum, Images, TypeToImage -if TYPE_CHECKING: - from core.api.grpc import core_pb2 - from PIL import ImageTk - -ICON_SIZE = 48 -ANTENNA_SIZE = 32 +ICON_SIZE: int = 48 +ANTENNA_SIZE: int = 32 class NodeDraw: - def __init__(self): + def __init__(self) -> None: self.custom: bool = False - self.image = None + self.image: Optional[str] = None self.image_enum: Optional[ImageEnum] = None - self.image_file = None - self.node_type: core_pb2.NodeType = None + self.image_file: Optional[str] = None + self.node_type: NodeType = None self.model: Optional[str] = None self.services: Set[str] = set() - self.label = None + self.label: Optional[str] = None @classmethod def from_setup( cls, image_enum: ImageEnum, - node_type: "core_pb2.NodeType", + node_type: NodeType, label: str, model: str = None, - tooltip=None, - ): + tooltip: str = None, + ) -> "NodeDraw": node_draw = NodeDraw() node_draw.image_enum = image_enum node_draw.image = Images.get(image_enum, ICON_SIZE) @@ -43,7 +41,7 @@ class NodeDraw: return node_draw @classmethod - def from_custom(cls, custom_node: CustomNode): + def from_custom(cls, custom_node: CustomNode) -> "NodeDraw": node_draw = NodeDraw() node_draw.custom = True node_draw.image_file = custom_node.image @@ -57,17 +55,17 @@ class NodeDraw: class NodeUtils: - NODES = [] - NETWORK_NODES = [] + NODES: List[NodeDraw] = [] + NETWORK_NODES: List[NodeDraw] = [] NODE_ICONS = {} - CONTAINER_NODES = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC} - IMAGE_NODES = {NodeType.DOCKER, NodeType.LXC} - WIRELESS_NODES = {NodeType.WIRELESS_LAN, NodeType.EMANE} - RJ45_NODES = {NodeType.RJ45} - IGNORE_NODES = {NodeType.CONTROL_NET, NodeType.PEER_TO_PEER} - NODE_MODELS = {"router", "host", "PC", "mdr", "prouter"} - ROUTER_NODES = {"router", "mdr"} - ANTENNA_ICON = None + CONTAINER_NODES: Set[NodeType] = {NodeType.DEFAULT, NodeType.DOCKER, NodeType.LXC} + IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC} + WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE} + RJ45_NODES: Set[NodeType] = {NodeType.RJ45} + IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET, NodeType.PEER_TO_PEER} + NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"} + ROUTER_NODES: Set[str] = {"router", "mdr"} + ANTENNA_ICON: PhotoImage = None @classmethod def is_router_node(cls, node: Node) -> bool: @@ -99,8 +97,8 @@ class NodeUtils: @classmethod def node_icon( - cls, node_type: NodeType, model: str, gui_config: GuiConfig, scale=1.0 - ) -> "ImageTk.PhotoImage": + cls, node_type: NodeType, model: str, gui_config: GuiConfig, scale: float = 1.0 + ) -> PhotoImage: image_enum = TypeToImage.get(node_type, model) if image_enum: @@ -112,8 +110,8 @@ class NodeUtils: @classmethod def node_image( - cls, core_node: "core_pb2.Node", gui_config: GuiConfig, scale=1.0 - ) -> "ImageTk.PhotoImage": + cls, core_node: Node, gui_config: GuiConfig, scale: float = 1.0 + ) -> PhotoImage: image = cls.node_icon(core_node.type, core_node.model, gui_config, scale) if core_node.icon: try: @@ -141,7 +139,7 @@ class NodeUtils: return None @classmethod - def setup(cls): + def setup(cls) -> None: nodes = [ (ImageEnum.ROUTER, NodeType.DEFAULT, "Router", "router"), (ImageEnum.HOST, NodeType.DEFAULT, "Host", "host"), diff --git a/daemon/core/gui/observers.py b/daemon/core/gui/observers.py index 27d0a26e..7879494b 100644 --- a/daemon/core/gui/observers.py +++ b/daemon/core/gui/observers.py @@ -1,13 +1,13 @@ import tkinter as tk from functools import partial -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict from core.gui.dialogs.observers import ObserverDialog if TYPE_CHECKING: from core.gui.app import Application -OBSERVERS = { +OBSERVERS: Dict[str, str] = { "List Processes": "ps", "Show Interfaces": "ip address", "IPV4 Routes": "ip -4 route", @@ -23,9 +23,9 @@ OBSERVERS = { class ObserversMenu(tk.Menu): def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(master) - self.app = app - self.observer = tk.StringVar(value=tk.NONE) - self.custom_index = 0 + self.app: "Application" = app + self.observer: tk.StringVar = tk.StringVar(value=tk.NONE) + self.custom_index: int = 0 self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/statusbar.py b/daemon/core/gui/statusbar.py index 3f58e7a0..2b597b63 100644 --- a/daemon/core/gui/statusbar.py +++ b/daemon/core/gui/statusbar.py @@ -3,8 +3,9 @@ status bar """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional +from core.api.grpc.core_pb2 import ExceptionEvent from core.gui.dialogs.alerts import AlertsDialog from core.gui.themes import Styles @@ -13,20 +14,19 @@ if TYPE_CHECKING: class StatusBar(ttk.Frame): - def __init__(self, master: tk.Widget, app: "Application"): + def __init__(self, master: tk.Widget, app: "Application") -> None: super().__init__(master) - self.app = app - self.status = None - self.statusvar = tk.StringVar() - self.zoom = None - self.cpu_usage = None - self.memory = None - self.alerts_button = None - self.running = False - self.core_alarms = [] + self.app: "Application" = app + self.status: Optional[ttk.Label] = None + self.statusvar: tk.StringVar = tk.StringVar() + self.zoom: Optional[ttk.Label] = None + self.cpu_usage: Optional[ttk.Label] = None + self.alerts_button: Optional[ttk.Button] = None + self.running: bool = False + self.core_alarms: List[ExceptionEvent] = [] self.draw() - def draw(self): + def draw(self) -> None: self.columnconfigure(0, weight=7) self.columnconfigure(1, weight=1) self.columnconfigure(2, weight=1) @@ -64,9 +64,9 @@ class StatusBar(ttk.Frame): ) self.alerts_button.grid(row=0, column=3, sticky="ew") - def click_alerts(self): + def click_alerts(self) -> None: dialog = AlertsDialog(self.app) dialog.show() - def set_status(self, message: str): + def set_status(self, message: str) -> None: self.statusvar.set(message) diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py index 2f055a90..b4a5f68f 100644 --- a/daemon/core/gui/task.py +++ b/daemon/core/gui/task.py @@ -1,7 +1,7 @@ import logging import threading import time -from typing import TYPE_CHECKING, Any, Callable, Tuple +from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple if TYPE_CHECKING: from core.gui.app import Application @@ -16,14 +16,14 @@ class ProgressTask: callback: Callable = None, args: Tuple[Any] = None, ): - self.app = app - self.title = title - self.task = task - self.callback = callback - self.args = args - if self.args is None: - self.args = () - self.time = None + self.app: "Application" = app + self.title: str = title + self.task: Callable = task + self.callback: Callable = callback + if args is None: + args = () + self.args: Tuple[Any] = args + self.time: Optional[float] = None def start(self) -> None: self.app.progress.grid(sticky="ew") @@ -49,7 +49,7 @@ class ProgressTask: finally: self.app.after(0, self.complete) - def complete(self): + def complete(self) -> None: self.app.progress.stop() self.app.progress.grid_forget() total = time.perf_counter() - self.time diff --git a/daemon/core/gui/themes.py b/daemon/core/gui/themes.py index 141a7a5c..93a0a599 100644 --- a/daemon/core/gui/themes.py +++ b/daemon/core/gui/themes.py @@ -1,39 +1,40 @@ import tkinter as tk from tkinter import font, ttk +from typing import Dict, Tuple -THEME_DARK = "black" -PADX = (0, 5) -PADY = (0, 5) -FRAME_PAD = 5 -DIALOG_PAD = 5 +THEME_DARK: str = "black" +PADX: Tuple[int, int] = (0, 5) +PADY: Tuple[int, int] = (0, 5) +FRAME_PAD: int = 5 +DIALOG_PAD: int = 5 class Styles: - tooltip = "Tooltip.TLabel" - tooltip_frame = "Tooltip.TFrame" - service_checkbutton = "Service.TCheckbutton" - picker_button = "Picker.TButton" - green_alert = "GAlert.TButton" - red_alert = "RAlert.TButton" - yellow_alert = "YAlert.TButton" + tooltip: str = "Tooltip.TLabel" + tooltip_frame: str = "Tooltip.TFrame" + service_checkbutton: str = "Service.TCheckbutton" + picker_button: str = "Picker.TButton" + green_alert: str = "GAlert.TButton" + red_alert: str = "RAlert.TButton" + yellow_alert: str = "YAlert.TButton" class Colors: - disabledfg = "DarkGrey" - frame = "#424242" - dark = "#222222" - darker = "#121212" - darkest = "black" - lighter = "#626262" - lightest = "#ffffff" - selectbg = "#4a6984" - selectfg = "#ffffff" - white = "white" - black = "black" - listboxbg = "#f2f1f0" + disabledfg: str = "DarkGrey" + frame: str = "#424242" + dark: str = "#222222" + darker: str = "#121212" + darkest: str = "black" + lighter: str = "#626262" + lightest: str = "#ffffff" + selectbg: str = "#4a6984" + selectfg: str = "#ffffff" + white: str = "white" + black: str = "black" + listboxbg: str = "#f2f1f0" -def load(style: ttk.Style): +def load(style: ttk.Style) -> None: style.theme_create( THEME_DARK, "clam", @@ -139,13 +140,13 @@ def load(style: ttk.Style): ) -def theme_change_menu(event: tk.Event): +def theme_change_menu(event: tk.Event) -> None: if not isinstance(event.widget, tk.Menu): return style_menu(event.widget) -def style_menu(widget: tk.Widget): +def style_menu(widget: tk.Widget) -> None: style = ttk.Style() bg = style.lookup(".", "background") fg = style.lookup(".", "foreground") @@ -157,7 +158,7 @@ def style_menu(widget: tk.Widget): ) -def style_listbox(widget: tk.Widget): +def style_listbox(widget: tk.Widget) -> None: style = ttk.Style() bg = style.lookup(".", "background") fg = style.lookup(".", "foreground") @@ -174,7 +175,7 @@ def style_listbox(widget: tk.Widget): ) -def theme_change(event: tk.Event): +def theme_change(event: tk.Event) -> None: style = ttk.Style() style.configure(Styles.picker_button, font="TkSmallCaptionFont") style.configure( @@ -203,7 +204,7 @@ def theme_change(event: tk.Event): ) -def scale_fonts(fonts_size, scale): +def scale_fonts(fonts_size: Dict[str, int], scale: float) -> None: for name in font.names(): f = font.nametofont(name) if name in fonts_size: diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 54fac126..c3e9067f 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -3,7 +3,7 @@ import tkinter as tk from enum import Enum from functools import partial from tkinter import ttk -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, List, Optional from PIL.ImageTk import PhotoImage @@ -23,8 +23,8 @@ from core.gui.tooltip import Tooltip if TYPE_CHECKING: from core.gui.app import Application -TOOLBAR_SIZE = 32 -PICKER_SIZE = 24 +TOOLBAR_SIZE: int = 32 +PICKER_SIZE: int = 24 class NodeTypeEnum(Enum): @@ -42,8 +42,8 @@ def enable_buttons(frame: ttk.Frame, enabled: bool) -> None: class PickerFrame(ttk.Frame): def __init__(self, app: "Application", button: ttk.Button) -> None: super().__init__(app) - self.app = app - self.button = button + self.app: "Application" = app + self.button: ttk.Button = button def create_node_button(self, node_draw: NodeDraw, func: Callable) -> None: self.create_button( @@ -85,10 +85,10 @@ class PickerFrame(ttk.Frame): class ButtonBar(ttk.Frame): - def __init__(self, master: tk.Widget, app: "Application"): + def __init__(self, master: tk.Widget, app: "Application") -> None: super().__init__(master) - self.app = app - self.radio_buttons = [] + self.app: "Application" = app + self.radio_buttons: List[ttk.Button] = [] def create_button( self, image_enum: ImageEnum, func: Callable, tooltip: str, radio: bool = False @@ -109,14 +109,14 @@ class ButtonBar(ttk.Frame): class MarkerFrame(ttk.Frame): - PAD = 3 + PAD: int = 3 def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(master, padding=self.PAD) - self.app = app - self.color = "#000000" - self.size = tk.DoubleVar() - self.color_frame = None + self.app: "Application" = app + self.color: str = "#000000" + self.size: tk.DoubleVar = tk.DoubleVar() + self.color_frame: Optional[tk.Frame] = None self.draw() def draw(self) -> None: @@ -144,7 +144,7 @@ class MarkerFrame(ttk.Frame): self.color_frame.bind("", self.click_color) Tooltip(self.color_frame, "Marker Color") - def click_clear(self): + def click_clear(self) -> None: self.app.canvas.delete(tags.MARKER) def click_color(self, _event: tk.Event) -> None: @@ -163,37 +163,37 @@ class Toolbar(ttk.Frame): Create a CoreToolbar instance """ super().__init__(app) - self.app = app + self.app: "Application" = app # design buttons - self.play_button = None - self.select_button = None - self.link_button = None - self.node_button = None - self.network_button = None - self.annotation_button = None + self.play_button: Optional[ttk.Button] = None + self.select_button: Optional[ttk.Button] = None + self.link_button: Optional[ttk.Button] = None + self.node_button: Optional[ttk.Button] = None + self.network_button: Optional[ttk.Button] = None + self.annotation_button: Optional[ttk.Button] = None # runtime buttons - self.runtime_select_button = None - self.stop_button = None - self.runtime_marker_button = None - self.run_command_button = None + self.runtime_select_button: Optional[ttk.Button] = None + self.stop_button: Optional[ttk.Button] = None + self.runtime_marker_button: Optional[ttk.Button] = None + self.run_command_button: Optional[ttk.Button] = None # frames - self.design_frame = None - self.runtime_frame = None - self.marker_frame = None - self.picker = None + self.design_frame: Optional[ButtonBar] = None + self.runtime_frame: Optional[ButtonBar] = None + self.marker_frame: Optional[MarkerFrame] = None + self.picker: Optional[PickerFrame] = None # observers - self.observers_menu = None + self.observers_menu: Optional[ObserversMenu] = None # these variables help keep track of what images being drawn so that scaling # is possible since PhotoImage does not have resize method - self.current_node = NodeUtils.NODES[0] - self.current_network = NodeUtils.NETWORK_NODES[0] - self.current_annotation = ShapeType.MARKER - self.annotation_enum = ImageEnum.MARKER + self.current_node: NodeDraw = NodeUtils.NODES[0] + self.current_network: NodeDraw = NodeUtils.NETWORK_NODES[0] + self.current_annotation: ShapeType = ShapeType.MARKER + self.annotation_enum: ImageEnum = ImageEnum.MARKER # draw components self.draw() diff --git a/daemon/core/gui/tooltip.py b/daemon/core/gui/tooltip.py index bc1ed9b5..c2978510 100644 --- a/daemon/core/gui/tooltip.py +++ b/daemon/core/gui/tooltip.py @@ -1,5 +1,6 @@ import tkinter as tk from tkinter import ttk +from typing import Optional from core.gui.themes import Styles @@ -9,19 +10,19 @@ class Tooltip(object): Create tool tip for a given widget """ - def __init__(self, widget: tk.Widget, text: str = "widget info"): - self.widget = widget - self.text = text + def __init__(self, widget: tk.BaseWidget, text: str = "widget info") -> None: + self.widget: tk.BaseWidget = widget + self.text: str = text self.widget.bind("", self.on_enter) self.widget.bind("", self.on_leave) - self.waittime = 400 - self.id = None - self.tw = None + self.waittime: int = 400 + self.id: Optional[str] = None + self.tw: Optional[tk.Toplevel] = None - def on_enter(self, event: tk.Event = None): + def on_enter(self, event: tk.Event = None) -> None: self.schedule() - def on_leave(self, event: tk.Event = None): + def on_leave(self, event: tk.Event = None) -> None: self.unschedule() self.close(event) @@ -39,7 +40,6 @@ class Tooltip(object): x, y, cx, cy = self.widget.bbox("insert") x += self.widget.winfo_rootx() y += self.widget.winfo_rooty() + 32 - self.tw = tk.Toplevel(self.widget) self.tw.wm_overrideredirect(True) self.tw.wm_geometry("+%d+%d" % (x, y)) diff --git a/daemon/core/gui/validation.py b/daemon/core/gui/validation.py index 873db189..22f12bb8 100644 --- a/daemon/core/gui/validation.py +++ b/daemon/core/gui/validation.py @@ -4,16 +4,23 @@ input validation import re import tkinter as tk from tkinter import ttk +from typing import Any, Optional, Pattern -SMALLEST_SCALE = 0.5 -LARGEST_SCALE = 5.0 -HEX_REGEX = re.compile("^([#]([0-9]|[a-f])+)$|^[#]$") +SMALLEST_SCALE: float = 0.5 +LARGEST_SCALE: float = 5.0 +HEX_REGEX: Pattern = re.compile("^([#]([0-9]|[a-f])+)$|^[#]$") class ValidationEntry(ttk.Entry): - empty = None + empty: Optional[str] = None - def __init__(self, master=None, widget=None, empty_enabled=True, **kwargs) -> None: + def __init__( + self, + master: tk.BaseWidget = None, + widget: tk.BaseWidget = None, + empty_enabled: bool = True, + **kwargs: Any + ) -> None: super().__init__(master, widget, **kwargs) cmd = self.register(self.is_valid) self.configure(validate="key", validatecommand=(cmd, "%P")) @@ -30,7 +37,7 @@ class ValidationEntry(ttk.Entry): class PositiveIntEntry(ValidationEntry): - empty = "0" + empty: str = "0" def is_valid(self, s: str) -> bool: if not s: @@ -92,7 +99,7 @@ class HexEntry(ValidationEntry): class NodeNameEntry(ValidationEntry): - empty = "noname" + empty: str = "noname" def is_valid(self, s: str) -> bool: if len(s) < 0: diff --git a/daemon/core/gui/widgets.py b/daemon/core/gui/widgets.py index 6f51bd8c..2eded212 100644 --- a/daemon/core/gui/widgets.py +++ b/daemon/core/gui/widgets.py @@ -1,53 +1,63 @@ import logging import tkinter as tk from functools import partial -from pathlib import PosixPath +from pathlib import Path from tkinter import filedialog, font, ttk -from typing import TYPE_CHECKING, Dict +from typing import TYPE_CHECKING, Any, Callable, Dict, Set, Type -from core.api.grpc import common_pb2, core_pb2 +from core.api.grpc import core_pb2 +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import ConfigOptionType from core.gui import themes, validation +from core.gui.dialogs.dialog import Dialog 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 = { - core_pb2.ConfigOptionType.UINT8, - core_pb2.ConfigOptionType.UINT16, - core_pb2.ConfigOptionType.UINT32, - core_pb2.ConfigOptionType.UINT64, - core_pb2.ConfigOptionType.INT8, - core_pb2.ConfigOptionType.INT16, - core_pb2.ConfigOptionType.INT32, - core_pb2.ConfigOptionType.INT64, +INT_TYPES: Set[ConfigOptionType] = { + ConfigOptionType.UINT8, + ConfigOptionType.UINT16, + ConfigOptionType.UINT32, + ConfigOptionType.UINT64, + ConfigOptionType.INT8, + ConfigOptionType.INT16, + ConfigOptionType.INT32, + ConfigOptionType.INT64, } -def file_button_click(value: tk.StringVar, parent: tk.Widget): +def file_button_click(value: tk.StringVar, parent: tk.Widget) -> None: file_path = filedialog.askopenfilename(title="Select File", parent=parent) if file_path: value.set(file_path) class FrameScroll(ttk.Frame): - def __init__(self, master: tk.Widget, app: "Application", _cls=ttk.Frame, **kw): + def __init__( + self, + master: tk.Widget, + app: "Application", + _cls: Type[ttk.Frame] = ttk.Frame, + **kw: Any + ) -> None: super().__init__(master, **kw) - self.app = app + self.app: "Application" = app self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) bg = self.app.style.lookup(".", "background") - self.canvas = tk.Canvas(self, highlightthickness=0, background=bg) + self.canvas: tk.Canvas = tk.Canvas(self, highlightthickness=0, background=bg) self.canvas.grid(row=0, sticky="nsew", padx=2, pady=2) self.canvas.columnconfigure(0, weight=1) self.canvas.rowconfigure(0, weight=1) - self.scrollbar = ttk.Scrollbar( + self.scrollbar: ttk.Scrollbar = ttk.Scrollbar( self, orient="vertical", command=self.canvas.yview ) self.scrollbar.grid(row=0, column=1, sticky="ns") - self.frame = _cls(self.canvas) - self.frame_id = self.canvas.create_window(0, 0, anchor="nw", window=self.frame) + self.frame: ttk.Frame = _cls(self.canvas) + self.frame_id: int = self.canvas.create_window( + 0, 0, anchor="nw", window=self.frame + ) self.canvas.update_idletasks() self.canvas.configure( scrollregion=self.canvas.bbox("all"), yscrollcommand=self.scrollbar.set @@ -55,16 +65,16 @@ class FrameScroll(ttk.Frame): self.frame.bind("", self._configure_frame) self.canvas.bind("", self._configure_canvas) - def _configure_frame(self, event: tk.Event): + def _configure_frame(self, event: tk.Event) -> None: req_width = self.frame.winfo_reqwidth() if req_width != self.canvas.winfo_reqwidth(): self.canvas.configure(width=req_width) self.canvas.configure(scrollregion=self.canvas.bbox("all")) - def _configure_canvas(self, event: tk.Event): + def _configure_canvas(self, event: tk.Event) -> None: self.canvas.itemconfig(self.frame_id, width=event.width) - def clear(self): + def clear(self) -> None: for widget in self.frame.winfo_children(): widget.destroy() @@ -74,15 +84,15 @@ class ConfigFrame(ttk.Notebook): self, master: tk.Widget, app: "Application", - config: Dict[str, common_pb2.ConfigOption], - **kw - ): + config: Dict[str, ConfigOption], + **kw: Any + ) -> None: super().__init__(master, **kw) - self.app = app - self.config = config - self.values = {} + self.app: "Application" = app + self.config: Dict[str, ConfigOption] = config + self.values: Dict[str, tk.StringVar] = {} - def draw_config(self): + def draw_config(self) -> None: group_mapping = {} for key in self.config: option = self.config[key] @@ -142,7 +152,7 @@ class ConfigFrame(ttk.Notebook): logging.error("unhandled config option type: %s", option.type) self.values[option.name] = value - def parse_config(self): + def parse_config(self) -> Dict[str, str]: for key in self.config: option = self.config[key] value = self.values[key] @@ -169,13 +179,13 @@ class ConfigFrame(ttk.Notebook): class ListboxScroll(ttk.Frame): - def __init__(self, master: tk.Widget = None, **kw): + def __init__(self, master: tk.BaseWidget = None, **kw: Any) -> None: super().__init__(master, **kw) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL) + self.scrollbar: ttk.Scrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL) self.scrollbar.grid(row=0, column=1, sticky="ns") - self.listbox = tk.Listbox( + self.listbox: tk.Listbox = tk.Listbox( self, selectmode=tk.BROWSE, yscrollcommand=self.scrollbar.set, @@ -187,12 +197,18 @@ class ListboxScroll(ttk.Frame): class CheckboxList(FrameScroll): - def __init__(self, master: ttk.Widget, app: "Application", clicked=None, **kw): + def __init__( + self, + master: ttk.Widget, + app: "Application", + clicked: Callable = None, + **kw: Any + ) -> None: super().__init__(master, app, **kw) - self.clicked = clicked + self.clicked: Callable = clicked self.frame.columnconfigure(0, weight=1) - def add(self, name: str, checked: bool): + def add(self, name: str, checked: bool) -> None: var = tk.BooleanVar(value=checked) func = partial(self.clicked, name, var) checkbox = ttk.Checkbutton(self.frame, text=name, variable=var, command=func) @@ -200,16 +216,16 @@ class CheckboxList(FrameScroll): class CodeFont(font.Font): - def __init__(self): + def __init__(self) -> None: super().__init__(font="TkFixedFont", color="green") class CodeText(ttk.Frame): - def __init__(self, master: tk.Widget, **kwargs): + def __init__(self, master: tk.BaseWidget, **kwargs: Any) -> None: super().__init__(master, **kwargs) self.rowconfigure(0, weight=1) self.columnconfigure(0, weight=1) - self.text = tk.Text( + self.text: tk.Text = tk.Text( self, bd=0, bg="black", @@ -229,14 +245,14 @@ class CodeText(ttk.Frame): class Spinbox(ttk.Entry): - def __init__(self, master: tk.Widget = None, **kwargs): + def __init__(self, master: tk.BaseWidget = None, **kwargs: Any) -> None: super().__init__(master, "ttk::spinbox", **kwargs) - def set(self, value): + def set(self, value: str) -> None: self.tk.call(self._w, "set", value) -def image_chooser(parent: "Dialog", path: PosixPath): +def image_chooser(parent: Dialog, path: Path) -> str: return filedialog.askopenfilename( parent=parent, initialdir=str(path), From 11be40bc90135040897da6a2b0e2372f1fb7f097 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Jun 2020 23:24:07 -0700 Subject: [PATCH 085/146] pygui: added class variable type hinting to core.gui.graph --- daemon/core/gui/coreclient.py | 2 +- daemon/core/gui/graph/edges.py | 69 +++++----- daemon/core/gui/graph/graph.py | 190 ++++++++++++++-------------- daemon/core/gui/graph/node.py | 105 ++++++++------- daemon/core/gui/graph/shape.py | 64 +++++----- daemon/core/gui/graph/shapeutils.py | 3 +- daemon/core/gui/graph/tags.py | 32 ++--- daemon/core/gui/graph/tooltip.py | 40 +++--- 8 files changed, 256 insertions(+), 249 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 24708769..5e1bf4c2 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -79,7 +79,7 @@ class CoreClient: self.read_config() # helpers - self.iface_to_edge: Dict[Tuple[int, int], Tuple[int, int]] = {} + self.iface_to_edge: Dict[Tuple[int, ...], Tuple[int, ...]] = {} self.ifaces_manager: InterfaceManager = InterfaceManager(self.app) # session data diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index ac637b28..e9ac2587 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -1,9 +1,10 @@ import logging import math import tkinter as tk -from typing import TYPE_CHECKING, Any, Tuple +from typing import TYPE_CHECKING, Optional, Tuple from core.api.grpc import core_pb2 +from core.api.grpc.core_pb2 import Interface, Link from core.gui import themes from core.gui.dialogs.linkconfig import LinkConfigurationDialog from core.gui.graph import tags @@ -12,12 +13,12 @@ from core.gui.nodeutils import NodeUtils if TYPE_CHECKING: from core.gui.graph.graph import CanvasGraph -TEXT_DISTANCE = 0.30 -EDGE_WIDTH = 3 -EDGE_COLOR = "#ff0000" -WIRELESS_WIDTH = 1.5 -WIRELESS_COLOR = "#009933" -ARC_DISTANCE = 50 +TEXT_DISTANCE: float = 0.30 +EDGE_WIDTH: int = 3 +EDGE_COLOR: str = "#ff0000" +WIRELESS_WIDTH: float = 1.5 +WIRELESS_COLOR: str = "#009933" +ARC_DISTANCE: int = 50 def create_edge_token(src: int, dst: int, network: int = None) -> Tuple[int, ...]: @@ -57,20 +58,20 @@ def arc_edges(edges) -> None: class Edge: - tag = tags.EDGE + tag: str = tags.EDGE def __init__(self, canvas: "CanvasGraph", src: int, dst: int = None) -> None: self.canvas = canvas - self.id = None - self.src = src - self.dst = dst - self.arc = 0 - self.token = None - self.src_label = None - self.middle_label = None - self.dst_label = None - self.color = EDGE_COLOR - self.width = EDGE_WIDTH + self.id: Optional[int] = None + self.src: int = src + self.dst: int = dst + self.arc: int = 0 + self.token: Optional[Tuple[int, ...]] = None + self.src_label: Optional[int] = None + self.middle_label: Optional[int] = None + self.dst_label: Optional[int] = None + self.color: str = EDGE_COLOR + self.width: int = EDGE_WIDTH @classmethod def create_token(cls, src: int, dst: int) -> Tuple[int, ...]: @@ -120,7 +121,7 @@ class Edge: fill=self.color, ) - def redraw(self): + def redraw(self) -> None: self.canvas.itemconfig(self.id, width=self.scaled_width(), fill=self.color) src_x, src_y, _, _, _, _ = self.canvas.coords(self.id) src_pos = src_x, src_y @@ -233,13 +234,13 @@ class CanvasWirelessEdge(Edge): dst: int, src_pos: Tuple[float, float], dst_pos: Tuple[float, float], - token: Tuple[Any, ...], + token: Tuple[int, ...], ) -> None: logging.debug("drawing wireless link from node %s to node %s", src, dst) super().__init__(canvas, src, dst) - self.token = token - self.width = WIRELESS_WIDTH - self.color = WIRELESS_COLOR + self.token: Tuple[int, ...] = token + self.width: float = WIRELESS_WIDTH + self.color: str = WIRELESS_COLOR self.draw(src_pos, dst_pos) @@ -259,19 +260,19 @@ class CanvasEdge(Edge): Create an instance of canvas edge object """ super().__init__(canvas, src) - self.src_iface = None - self.dst_iface = None - self.text_src = None - self.text_dst = None - self.link = None - self.asymmetric_link = None - self.throughput = None + self.src_iface: Optional[Interface] = None + self.dst_iface: Optional[Interface] = None + self.text_src: Optional[int] = None + self.text_dst: Optional[int] = None + self.link: Optional[Link] = None + self.asymmetric_link: Optional[Link] = None + self.throughput: Optional[float] = None self.draw(src_pos, dst_pos) self.set_binding() - self.context = tk.Menu(self.canvas) + self.context: tk.Menu = tk.Menu(self.canvas) self.create_context() - def create_context(self): + def create_context(self) -> None: themes.style_menu(self.context) self.context.add_command(label="Configure", command=self.click_configure) self.context.add_command(label="Delete", command=self.click_delete) @@ -279,7 +280,7 @@ class CanvasEdge(Edge): def set_binding(self) -> None: self.canvas.tag_bind(self.id, "", self.show_context) - def set_link(self, link) -> None: + def set_link(self, link: Link) -> None: self.link = link self.draw_labels() @@ -383,7 +384,7 @@ class CanvasEdge(Edge): self.context.entryconfigure(1, state=state) self.context.tk_popup(event.x_root, event.y_root) - def click_delete(self): + def click_delete(self) -> None: self.canvas.delete_edge(self) def click_configure(self) -> None: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 834220ea..53115750 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -2,11 +2,12 @@ import logging import tkinter as tk from copy import deepcopy from tkinter import BooleanVar -from typing import TYPE_CHECKING, Optional, Tuple +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple -from PIL import Image, ImageTk +from PIL import Image +from PIL.ImageTk import PhotoImage -from core.api.grpc import core_pb2 +from core.api.grpc.core_pb2 import Interface, Link, LinkType, Session, ThroughputsEvent from core.gui.dialogs.shapemod import ShapeDialog from core.gui.graph import tags from core.gui.graph.edges import ( @@ -21,7 +22,7 @@ from core.gui.graph.node import CanvasNode from core.gui.graph.shape import Shape from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker from core.gui.images import ImageEnum, TypeToImage -from core.gui.nodeutils import NodeUtils +from core.gui.nodeutils import NodeDraw, NodeUtils if TYPE_CHECKING: from core.gui.app import Application @@ -48,58 +49,59 @@ class ShowVar(BooleanVar): class CanvasGraph(tk.Canvas): - def __init__(self, master: tk.Widget, app: "Application", core: "CoreClient"): + def __init__( + self, master: tk.BaseWidget, app: "Application", core: "CoreClient" + ) -> None: super().__init__(master, highlightthickness=0, background="#cccccc") - self.app = app - self.core = core - self.mode = GraphMode.SELECT - self.annotation_type = None - self.selection = {} - self.select_box = None - self.selected = None - self.node_draw = None - self.nodes = {} - self.edges = {} - self.shapes = {} - self.wireless_edges = {} + self.app: "Application" = app + self.core: "CoreClient" = core + self.mode: GraphMode = GraphMode.SELECT + self.annotation_type: Optional[ShapeType] = None + self.selection: Dict[int, int] = {} + self.select_box: Optional[Shape] = None + self.selected: Optional[int] = None + self.node_draw: Optional[NodeDraw] = None + self.nodes: Dict[int, CanvasNode] = {} + self.edges: Dict[int, CanvasEdge] = {} + self.shapes: Dict[int, Shape] = {} + self.wireless_edges: Dict[Tuple[int, ...], CanvasWirelessEdge] = {} # map wireless/EMANE node to the set of MDRs connected to that node - self.wireless_network = {} + self.wireless_network: Dict[int, Set[int]] = {} - self.drawing_edge = None - self.rect = None - self.shape_drawing = False + self.drawing_edge: Optional[CanvasEdge] = None + self.rect: Optional[int] = None + self.shape_drawing: bool = False width = self.app.guiconfig.preferences.width height = self.app.guiconfig.preferences.height - self.default_dimensions = (width, height) - self.current_dimensions = self.default_dimensions - self.ratio = 1.0 - self.offset = (0, 0) - self.cursor = (0, 0) - self.marker_tool = None - self.to_copy = [] + self.default_dimensions: Tuple[int, int] = (width, height) + self.current_dimensions: Tuple[int, int] = self.default_dimensions + self.ratio: float = 1.0 + self.offset: Tuple[int, int] = (0, 0) + self.cursor: Tuple[int, int] = (0, 0) + self.to_copy: List[CanvasNode] = [] # background related - self.wallpaper_id = None - self.wallpaper = None - self.wallpaper_drawn = None - self.wallpaper_file = "" - self.scale_option = tk.IntVar(value=1) - self.adjust_to_dim = tk.BooleanVar(value=False) + self.wallpaper_id: Optional[int] = None + self.wallpaper: Optional[Image.Image] = None + self.wallpaper_drawn: Optional[PhotoImage] = None + self.wallpaper_file: str = "" + self.scale_option: tk.IntVar = tk.IntVar(value=1) + self.adjust_to_dim: tk.BooleanVar = tk.BooleanVar(value=False) # throughput related - self.throughput_threshold = 250.0 - self.throughput_width = 10 - self.throughput_color = "#FF0000" + self.throughput_threshold: float = 250.0 + self.throughput_width: int = 10 + self.throughput_color: str = "#FF0000" # drawing related - self.show_node_labels = ShowVar(self, tags.NODE_LABEL, value=True) - self.show_link_labels = ShowVar(self, tags.LINK_LABEL, value=True) - self.show_grid = ShowVar(self, tags.GRIDLINE, value=True) - self.show_annotations = ShowVar(self, tags.ANNOTATION, value=True) - self.show_iface_names = BooleanVar(value=False) - self.show_ip4s = BooleanVar(value=True) - self.show_ip6s = BooleanVar(value=True) + self.show_node_labels: ShowVar = ShowVar(self, tags.NODE_LABEL, value=True) + self.show_link_labels: ShowVar = ShowVar(self, tags.LINK_LABEL, value=True) + self.show_grid: ShowVar = ShowVar(self, tags.GRIDLINE, value=True) + self.show_annotations: ShowVar = ShowVar(self, tags.ANNOTATION, value=True) + self.show_iface_names: BooleanVar = BooleanVar(value=False) + self.show_ip4s: BooleanVar = BooleanVar(value=True) + self.show_ip6s: BooleanVar = BooleanVar(value=True) # bindings self.setup_bindings() @@ -108,7 +110,7 @@ class CanvasGraph(tk.Canvas): self.draw_canvas() self.draw_grid() - def draw_canvas(self, dimensions: Tuple[int, int] = None): + def draw_canvas(self, dimensions: Tuple[int, int] = None) -> None: if self.rect is not None: self.delete(self.rect) if not dimensions: @@ -125,7 +127,7 @@ class CanvasGraph(tk.Canvas): ) self.configure(scrollregion=self.bbox(tk.ALL)) - def reset_and_redraw(self, session: core_pb2.Session): + def reset_and_redraw(self, session: Session) -> None: """ Reset the private variables CanvasGraph object, redraw nodes given the new grpc client. @@ -157,7 +159,7 @@ class CanvasGraph(tk.Canvas): self.drawing_edge = None self.draw_session(session) - def setup_bindings(self): + def setup_bindings(self) -> None: """ Bind any mouse events or hot keys to the matching action """ @@ -173,28 +175,28 @@ class CanvasGraph(tk.Canvas): self.bind("", lambda e: self.scan_mark(e.x, e.y)) self.bind("", lambda e: self.scan_dragto(e.x, e.y, gain=1)) - def get_actual_coords(self, x: float, y: float) -> [float, float]: + def get_actual_coords(self, x: float, y: float) -> Tuple[float, float]: actual_x = (x - self.offset[0]) / self.ratio actual_y = (y - self.offset[1]) / self.ratio return actual_x, actual_y - def get_scaled_coords(self, x: float, y: float) -> [float, float]: + def get_scaled_coords(self, x: float, y: float) -> Tuple[float, float]: scaled_x = (x * self.ratio) + self.offset[0] scaled_y = (y * self.ratio) + self.offset[1] return scaled_x, scaled_y - def inside_canvas(self, x: float, y: float) -> [bool, bool]: + def inside_canvas(self, x: float, y: float) -> Tuple[bool, bool]: x1, y1, x2, y2 = self.bbox(self.rect) valid_x = x1 <= x <= x2 valid_y = y1 <= y <= y2 return valid_x and valid_y - def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> [bool, bool]: + def valid_position(self, x1: int, y1: int, x2: int, y2: int) -> Tuple[bool, bool]: valid_topleft = self.inside_canvas(x1, y1) valid_bottomright = self.inside_canvas(x2, y2) return valid_topleft and valid_bottomright - def set_throughputs(self, throughputs_event: core_pb2.ThroughputsEvent): + def set_throughputs(self, throughputs_event: ThroughputsEvent) -> None: for iface_throughput in throughputs_event.iface_throughputs: node_id = iface_throughput.node_id iface_id = iface_throughput.iface_id @@ -209,7 +211,7 @@ class CanvasGraph(tk.Canvas): else: del self.core.iface_to_edge[iface_to_edge_id] - def draw_grid(self): + def draw_grid(self) -> None: """ Create grid. """ @@ -223,9 +225,7 @@ class CanvasGraph(tk.Canvas): self.tag_lower(tags.GRIDLINE) self.tag_lower(self.rect) - def add_wireless_edge( - self, src: CanvasNode, dst: CanvasNode, link: core_pb2.Link - ) -> None: + def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: network_id = link.network_id if link.network_id else None token = create_edge_token(src.id, dst.id, network_id) if token in self.wireless_edges: @@ -248,7 +248,7 @@ class CanvasGraph(tk.Canvas): arc_edges(common_edges) def delete_wireless_edge( - self, src: CanvasNode, dst: CanvasNode, link: core_pb2.Link + self, src: CanvasNode, dst: CanvasNode, link: Link ) -> None: network_id = link.network_id if link.network_id else None token = create_edge_token(src.id, dst.id, network_id) @@ -263,7 +263,7 @@ class CanvasGraph(tk.Canvas): arc_edges(common_edges) def update_wireless_edge( - self, src: CanvasNode, dst: CanvasNode, link: core_pb2.Link + self, src: CanvasNode, dst: CanvasNode, link: Link ) -> None: if not link.label: return @@ -275,7 +275,7 @@ class CanvasGraph(tk.Canvas): edge = self.wireless_edges[token] edge.middle_label_text(link.label) - def draw_session(self, session: core_pb2.Session): + def draw_session(self, session: Session) -> None: """ Draw existing session. """ @@ -306,7 +306,7 @@ class CanvasGraph(tk.Canvas): node2 = canvas_node2.core_node token = create_edge_token(canvas_node1.id, canvas_node2.id) - if link.type == core_pb2.LinkType.WIRELESS: + if link.type == LinkType.WIRELESS: self.add_wireless_edge(canvas_node1, canvas_node2, link) else: if token not in self.edges: @@ -337,7 +337,7 @@ class CanvasGraph(tk.Canvas): else: logging.error("duplicate link received: %s", link) - def stopped_session(self): + def stopped_session(self) -> None: # clear wireless edges for edge in self.wireless_edges.values(): edge.delete() @@ -351,7 +351,7 @@ class CanvasGraph(tk.Canvas): for edge in self.edges.values(): edge.reset() - def canvas_xy(self, event: tk.Event) -> [float, float]: + def canvas_xy(self, event: tk.Event) -> Tuple[float, float]: """ Convert window coordinate to canvas coordinate """ @@ -379,7 +379,7 @@ class CanvasGraph(tk.Canvas): return selected - def click_release(self, event: tk.Event): + def click_release(self, event: tk.Event) -> None: """ Draw a node or finish drawing an edge according to the current graph mode """ @@ -418,7 +418,7 @@ class CanvasGraph(tk.Canvas): self.mode = GraphMode.NODE self.selected = None - def handle_edge_release(self, _event: tk.Event): + def handle_edge_release(self, _event: tk.Event) -> None: edge = self.drawing_edge self.drawing_edge = None @@ -454,7 +454,7 @@ class CanvasGraph(tk.Canvas): node_dst.edges.add(edge) self.core.create_link(edge, node_src, node_dst) - def select_object(self, object_id: int, choose_multiple: bool = False): + def select_object(self, object_id: int, choose_multiple: bool = False) -> None: """ create a bounding box when a node is selected """ @@ -475,7 +475,7 @@ class CanvasGraph(tk.Canvas): selection_id = self.selection.pop(object_id) self.delete(selection_id) - def clear_selection(self): + def clear_selection(self) -> None: """ Clear current selection boxes. """ @@ -483,7 +483,7 @@ class CanvasGraph(tk.Canvas): self.delete(_id) self.selection.clear() - def move_selection(self, object_id: int, x_offset: float, y_offset: float): + def move_selection(self, object_id: int, x_offset: float, y_offset: float) -> None: select_id = self.selection.get(object_id) if select_id is not None: self.move(select_id, x_offset, y_offset) @@ -531,7 +531,7 @@ class CanvasGraph(tk.Canvas): self.core.deleted_graph_nodes(nodes) self.core.deleted_graph_edges(edges) - def delete_edge(self, edge: CanvasEdge): + def delete_edge(self, edge: CanvasEdge) -> None: edge.delete() del self.edges[edge.token] src_node = self.nodes[edge.src] @@ -550,7 +550,7 @@ class CanvasGraph(tk.Canvas): src_node.delete_antenna() self.core.deleted_graph_edges([edge]) - def zoom(self, event: tk.Event, factor: float = None): + def zoom(self, event: tk.Event, factor: float = None) -> None: if not factor: factor = ZOOM_IN if event.delta > 0 else ZOOM_OUT event.x, event.y = self.canvasx(event.x), self.canvasy(event.y) @@ -568,7 +568,7 @@ class CanvasGraph(tk.Canvas): if self.wallpaper: self.redraw_wallpaper() - def click_press(self, event: tk.Event): + def click_press(self, event: tk.Event) -> None: """ Start drawing an edge if mouse click is on a node """ @@ -630,7 +630,7 @@ class CanvasGraph(tk.Canvas): self.select_box = shape self.clear_selection() - def ctrl_click(self, event: tk.Event): + def ctrl_click(self, event: tk.Event) -> None: # update cursor location x, y = self.canvas_xy(event) if not self.inside_canvas(x, y): @@ -648,7 +648,7 @@ class CanvasGraph(tk.Canvas): ): self.select_object(selected, choose_multiple=True) - def click_motion(self, event: tk.Event): + def click_motion(self, event: tk.Event) -> None: x, y = self.canvas_xy(event) if not self.inside_canvas(x, y): if self.select_box: @@ -701,7 +701,7 @@ class CanvasGraph(tk.Canvas): if self.select_box and self.mode == GraphMode.SELECT: self.select_box.shape_motion(x, y) - def press_delete(self, _event: tk.Event): + def press_delete(self, _event: tk.Event) -> None: """ delete selected nodes and any data that relates to it """ @@ -711,7 +711,7 @@ class CanvasGraph(tk.Canvas): else: logging.debug("node deletion is disabled during runtime state") - def double_click(self, event: tk.Event): + def double_click(self, event: tk.Event) -> None: selected = self.get_selected(event) if selected is not None and selected in self.shapes: shape = self.shapes[selected] @@ -737,7 +737,7 @@ class CanvasGraph(tk.Canvas): self.core.canvas_nodes[core_node.id] = node self.nodes[node.id] = node - def width_and_height(self): + def width_and_height(self) -> Tuple[int, int]: """ retrieve canvas width and height in pixels """ @@ -753,8 +753,8 @@ class CanvasGraph(tk.Canvas): return image def draw_wallpaper( - self, image: ImageTk.PhotoImage, x: float = None, y: float = None - ): + self, image: PhotoImage, x: float = None, y: float = None + ) -> None: if x is None and y is None: x1, y1, x2, y2 = self.bbox(self.rect) x = (x1 + x2) / 2 @@ -762,7 +762,7 @@ class CanvasGraph(tk.Canvas): self.wallpaper_id = self.create_image((x, y), image=image, tags=tags.WALLPAPER) self.wallpaper_drawn = image - def wallpaper_upper_left(self): + def wallpaper_upper_left(self) -> None: self.delete(self.wallpaper_id) # create new scaled image, cropped if needed @@ -775,7 +775,7 @@ class CanvasGraph(tk.Canvas): if image.height > height: cropy = image.height cropped = image.crop((0, 0, cropx, cropy)) - image = ImageTk.PhotoImage(cropped) + image = PhotoImage(cropped) # draw on canvas x1, y1, _, _ = self.bbox(self.rect) @@ -783,7 +783,7 @@ class CanvasGraph(tk.Canvas): y = (cropy / 2) + y1 self.draw_wallpaper(image, x, y) - def wallpaper_center(self): + def wallpaper_center(self) -> None: """ place the image at the center of canvas """ @@ -803,26 +803,26 @@ class CanvasGraph(tk.Canvas): x2 = image.width - cropx y2 = image.height - cropy cropped = image.crop((x1, y1, x2, y2)) - image = ImageTk.PhotoImage(cropped) + image = PhotoImage(cropped) self.draw_wallpaper(image) - def wallpaper_scaled(self): + def wallpaper_scaled(self) -> None: """ scale image based on canvas dimension """ self.delete(self.wallpaper_id) canvas_w, canvas_h = self.width_and_height() image = self.wallpaper.resize((int(canvas_w), int(canvas_h)), Image.ANTIALIAS) - image = ImageTk.PhotoImage(image) + image = PhotoImage(image) self.draw_wallpaper(image) - def resize_to_wallpaper(self): + def resize_to_wallpaper(self) -> None: self.delete(self.wallpaper_id) - image = ImageTk.PhotoImage(self.wallpaper) + image = PhotoImage(self.wallpaper) self.redraw_canvas((image.width(), image.height())) self.draw_wallpaper(image) - def redraw_canvas(self, dimensions: Tuple[int, int] = None): + def redraw_canvas(self, dimensions: Tuple[int, int] = None) -> None: logging.debug("redrawing canvas to dimensions: %s", dimensions) # reset scale and move back to original position @@ -843,7 +843,7 @@ class CanvasGraph(tk.Canvas): self.draw_grid() self.app.canvas.show_grid.click_handler() - def redraw_wallpaper(self): + def redraw_wallpaper(self) -> None: if self.adjust_to_dim.get(): logging.debug("drawing wallpaper to canvas dimensions") self.resize_to_wallpaper() @@ -864,7 +864,7 @@ class CanvasGraph(tk.Canvas): for tag in tags.ORGANIZE_TAGS: self.tag_raise(tag) - def set_wallpaper(self, filename: Optional[str]): + def set_wallpaper(self, filename: Optional[str]) -> None: logging.debug("setting wallpaper: %s", filename) if filename: img = Image.open(filename) @@ -880,7 +880,7 @@ class CanvasGraph(tk.Canvas): def is_selection_mode(self) -> bool: return self.mode == GraphMode.SELECT - def create_edge(self, source: CanvasNode, dest: CanvasNode): + def create_edge(self, source: CanvasNode, dest: CanvasNode) -> None: """ create an edge between source node and destination node """ @@ -894,7 +894,7 @@ class CanvasGraph(tk.Canvas): self.nodes[dest.id].edges.add(edge) self.core.create_link(edge, source, dest) - def copy(self): + def copy(self) -> None: if self.core.is_runtime(): logging.debug("copy is disabled during runtime state") return @@ -905,7 +905,7 @@ class CanvasGraph(tk.Canvas): canvas_node = self.nodes[node_id] self.to_copy.append(canvas_node) - def paste(self): + def paste(self) -> None: if self.core.is_runtime(): logging.debug("paste is disabled during runtime state") return @@ -972,11 +972,11 @@ class CanvasGraph(tk.Canvas): else: asym_iface1 = None if iface1_id: - asym_iface1 = core_pb2.Interface(id=iface1_id) + asym_iface1 = Interface(id=iface1_id) asym_iface2 = None if iface2_id: - asym_iface2 = core_pb2.Interface(id=iface2_id) - copy_edge.asymmetric_link = core_pb2.Link( + asym_iface2 = Interface(id=iface2_id) + copy_edge.asymmetric_link = Link( node1_id=copy_link.node2_id, node2_id=copy_link.node1_id, iface1=asym_iface1, @@ -990,7 +990,7 @@ class CanvasGraph(tk.Canvas): ) self.tag_raise(tags.NODE) - def scale_graph(self): + def scale_graph(self) -> None: for nid, canvas_node in self.nodes.items(): img = None if NodeUtils.is_custom( diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 3ba4b3f7..f936bc79 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -1,12 +1,14 @@ import functools import logging import tkinter as tk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple import grpc +from PIL.ImageTk import PhotoImage -from core.api.grpc import core_pb2 -from core.api.grpc.core_pb2 import NodeType +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Interface, Node, NodeType +from core.api.grpc.services_pb2 import NodeServiceData from core.gui import themes from core.gui.dialogs.emaneconfig import EmaneConfigDialog from core.gui.dialogs.mobilityconfig import MobilityConfigDialog @@ -15,36 +17,31 @@ from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog from core.gui.dialogs.nodeservice import NodeServiceDialog from core.gui.dialogs.wlanconfig import WlanConfigDialog from core.gui.graph import tags -from core.gui.graph.edges import CanvasEdge +from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum from core.gui.nodeutils import ANTENNA_SIZE, NodeUtils if TYPE_CHECKING: from core.gui.app import Application - from PIL.ImageTk import PhotoImage + from core.gui.graph.graph import CanvasGraph -NODE_TEXT_OFFSET = 5 +NODE_TEXT_OFFSET: int = 5 class CanvasNode: def __init__( - self, - app: "Application", - x: float, - y: float, - core_node: core_pb2.Node, - image: "PhotoImage", + self, app: "Application", x: float, y: float, core_node: Node, image: PhotoImage ): - self.app = app - self.canvas = app.canvas - self.image = image - self.core_node = core_node - self.id = self.canvas.create_image( + self.app: "Application" = app + self.canvas: "CanvasGraph" = app.canvas + self.image: PhotoImage = image + self.core_node: Node = core_node + self.id: int = self.canvas.create_image( x, y, anchor=tk.CENTER, image=self.image, tags=tags.NODE ) label_y = self._get_label_y() - self.text_id = self.canvas.create_text( + self.text_id: int = self.canvas.create_text( x, label_y, text=self.core_node.name, @@ -53,21 +50,21 @@ class CanvasNode: fill="#0000CD", state=self.canvas.show_node_labels.state(), ) - self.tooltip = CanvasTooltip(self.canvas) - self.edges = set() - self.ifaces = {} - self.wireless_edges = set() - self.antennas = [] - self.antenna_images = {} + self.tooltip: CanvasTooltip = CanvasTooltip(self.canvas) + self.edges: Set[CanvasEdge] = set() + self.ifaces: Dict[int, Interface] = {} + self.wireless_edges: Set[CanvasWirelessEdge] = set() + self.antennas: List[int] = [] + self.antenna_images: Dict[int, PhotoImage] = {} # possible configurations - self.emane_model_configs = {} - self.wlan_config = {} - self.mobility_config = {} - self.service_configs = {} - self.service_file_configs = {} - self.config_service_configs = {} + self.emane_model_configs: Dict[Tuple[str, Optional[int]], ConfigOption] = {} + self.wlan_config: Dict[str, ConfigOption] = {} + self.mobility_config: Dict[str, ConfigOption] = {} + self.service_configs: Dict[str, NodeServiceData] = {} + self.service_file_configs: Dict[str, Dict[str, str]] = {} + self.config_service_configs: Dict[str, Any] = {} self.setup_bindings() - self.context = tk.Menu(self.canvas) + self.context: tk.Menu = tk.Menu(self.canvas) themes.style_menu(self.context) def next_iface_id(self) -> int: @@ -76,19 +73,19 @@ class CanvasNode: i += 1 return i - def setup_bindings(self): + def setup_bindings(self) -> None: self.canvas.tag_bind(self.id, "", self.double_click) self.canvas.tag_bind(self.id, "", self.on_enter) self.canvas.tag_bind(self.id, "", self.on_leave) self.canvas.tag_bind(self.id, "", self.show_context) - def delete(self): + def delete(self) -> None: logging.debug("Delete canvas node for %s", self.core_node) self.canvas.delete(self.id) self.canvas.delete(self.text_id) self.delete_antennas() - def add_antenna(self): + def add_antenna(self) -> None: x, y = self.canvas.coords(self.id) offset = len(self.antennas) * 8 * self.app.app_scale img = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE) @@ -102,7 +99,7 @@ class CanvasNode: self.antennas.append(antenna_id) self.antenna_images[antenna_id] = img - def delete_antenna(self): + def delete_antenna(self) -> None: """ delete one antenna """ @@ -112,7 +109,7 @@ class CanvasNode: self.canvas.delete(antenna_id) self.antenna_images.pop(antenna_id, None) - def delete_antennas(self): + def delete_antennas(self) -> None: """ delete all antennas """ @@ -122,30 +119,30 @@ class CanvasNode: self.antennas.clear() self.antenna_images.clear() - def redraw(self): + def redraw(self) -> None: self.canvas.itemconfig(self.id, image=self.image) self.canvas.itemconfig(self.text_id, text=self.core_node.name) for edge in self.edges: edge.redraw() - def _get_label_y(self): + def _get_label_y(self) -> int: image_box = self.canvas.bbox(self.id) return image_box[3] + NODE_TEXT_OFFSET - def scale_text(self): + def scale_text(self) -> None: text_bound = self.canvas.bbox(self.text_id) prev_y = (text_bound[3] + text_bound[1]) / 2 new_y = self._get_label_y() self.canvas.move(self.text_id, 0, new_y - prev_y) - def move(self, x: int, y: int): + def move(self, x: int, y: int) -> None: x, y = self.canvas.get_scaled_coords(x, y) current_x, current_y = self.canvas.coords(self.id) x_offset = x - current_x y_offset = y - current_y self.motion(x_offset, y_offset, update=False) - def motion(self, x_offset: int, y_offset: int, update: bool = True): + def motion(self, x_offset: float, y_offset: float, update: bool = True) -> None: original_position = self.canvas.coords(self.id) self.canvas.move(self.id, x_offset, y_offset) pos = self.canvas.coords(self.id) @@ -177,7 +174,7 @@ class CanvasNode: if self.app.core.is_runtime() and update: self.app.core.edit_node(self.core_node) - def on_enter(self, event: tk.Event): + def on_enter(self, event: tk.Event) -> None: if self.app.core.is_runtime() and self.app.core.observer: self.tooltip.text.set("waiting...") self.tooltip.on_enter(event) @@ -187,10 +184,10 @@ class CanvasNode: except grpc.RpcError as e: self.app.show_grpc_exception("Observer Error", e) - def on_leave(self, event: tk.Event): + def on_leave(self, event: tk.Event) -> None: self.tooltip.on_leave(event) - def double_click(self, event: tk.Event): + def double_click(self, event: tk.Event) -> None: if self.app.core.is_runtime(): self.canvas.core.launch_terminal(self.core_node.id) else: @@ -270,37 +267,37 @@ class CanvasNode: self.canvas.selection[self.id] = self self.canvas.copy() - def show_config(self): + def show_config(self) -> None: dialog = NodeConfigDialog(self.app, self) dialog.show() - def show_wlan_config(self): + def show_wlan_config(self) -> None: dialog = WlanConfigDialog(self.app, self) if not dialog.has_error: dialog.show() - def show_mobility_config(self): + def show_mobility_config(self) -> None: dialog = MobilityConfigDialog(self.app, self) if not dialog.has_error: dialog.show() - def show_mobility_player(self): + def show_mobility_player(self) -> None: mobility_player = self.app.core.mobility_players[self.core_node.id] mobility_player.show() - def show_emane_config(self): + def show_emane_config(self) -> None: dialog = EmaneConfigDialog(self.app, self) dialog.show() - def show_services(self): + def show_services(self) -> None: dialog = NodeServiceDialog(self.app, self) dialog.show() - def show_config_services(self): + def show_config_services(self) -> None: dialog = NodeConfigServiceDialog(self.app, self) dialog.show() - def has_emane_link(self, iface_id: int) -> core_pb2.Node: + def has_emane_link(self, iface_id: int) -> Node: result = None for edge in self.edges: if self.id == edge.src: @@ -317,14 +314,14 @@ class CanvasNode: break return result - def wireless_link_selected(self): + def wireless_link_selected(self) -> None: nodes = [x for x in self.canvas.selection if x in self.canvas.nodes] for node_id in nodes: canvas_node = self.canvas.nodes[node_id] self.canvas.create_edge(self, canvas_node) self.canvas.clear_selection() - def scale_antennas(self): + def scale_antennas(self) -> None: for i in range(len(self.antennas)): antenna_id = self.antennas[i] image = self.app.get_icon(ImageEnum.ANTENNA, ANTENNA_SIZE) diff --git a/daemon/core/gui/graph/shape.py b/daemon/core/gui/graph/shape.py index 70f67d14..36298655 100644 --- a/daemon/core/gui/graph/shape.py +++ b/daemon/core/gui/graph/shape.py @@ -1,5 +1,5 @@ import logging -from typing import TYPE_CHECKING, Dict, List, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Union from core.gui.dialogs.shapemod import ShapeDialog from core.gui.graph import tags @@ -23,17 +23,17 @@ class AnnotationData: bold: bool = False, italic: bool = False, underline: bool = False, - ): - self.text = text - self.font = font - self.font_size = font_size - self.text_color = text_color - self.fill_color = fill_color - self.border_color = border_color - self.border_width = border_width - self.bold = bold - self.italic = italic - self.underline = underline + ) -> None: + self.text: str = text + self.font: str = font + self.font_size: int = font_size + self.text_color: str = text_color + self.fill_color: str = fill_color + self.border_color: str = border_color + self.border_width: int = border_width + self.bold: bool = bold + self.italic: bool = italic + self.underline: bool = underline class Shape: @@ -47,29 +47,29 @@ class Shape: x2: float = None, y2: float = None, data: AnnotationData = None, - ): - self.app = app - self.canvas = canvas - self.shape_type = shape_type - self.id = None - self.text_id = None - self.x1 = x1 - self.y1 = y1 + ) -> None: + self.app: "Application" = app + self.canvas: "CanvasGraph" = canvas + self.shape_type: ShapeType = shape_type + self.id: Optional[int] = None + self.text_id: Optional[int] = None + self.x1: float = x1 + self.y1: float = y1 if x2 is None: x2 = x1 - self.x2 = x2 + self.x2: float = x2 if y2 is None: y2 = y1 - self.y2 = y2 + self.y2: float = y2 if data is None: - self.created = False - self.shape_data = AnnotationData() + self.created: bool = False + self.shape_data: AnnotationData = AnnotationData() else: - self.created = True + self.created: bool = True self.shape_data = data self.draw() - def draw(self): + def draw(self) -> None: if self.created: dash = None else: @@ -127,7 +127,7 @@ class Shape: font.append("underline") return font - def draw_shape_text(self): + def draw_shape_text(self) -> None: if self.shape_data.text: x = (self.x1 + self.x2) / 2 y = self.y1 + 1.5 * self.shape_data.font_size @@ -142,18 +142,18 @@ class Shape: state=self.canvas.show_annotations.state(), ) - def shape_motion(self, x1: float, y1: float): + def shape_motion(self, x1: float, y1: float) -> None: self.canvas.coords(self.id, self.x1, self.y1, x1, y1) - def shape_complete(self, x: float, y: float): + def shape_complete(self, x: float, y: float) -> None: self.canvas.organize() s = ShapeDialog(self.app, self) s.show() - def disappear(self): + def disappear(self) -> None: self.canvas.delete(self.id) - def motion(self, x_offset: float, y_offset: float): + def motion(self, x_offset: float, y_offset: float) -> None: original_position = self.canvas.coords(self.id) self.canvas.move(self.id, x_offset, y_offset) coords = self.canvas.coords(self.id) @@ -166,7 +166,7 @@ class Shape: if self.text_id is not None: self.canvas.move(self.text_id, x_offset, y_offset) - def delete(self): + def delete(self) -> None: logging.debug("Delete shape, id(%s)", self.id) self.canvas.delete(self.id) self.canvas.delete(self.text_id) diff --git a/daemon/core/gui/graph/shapeutils.py b/daemon/core/gui/graph/shapeutils.py index ce2b7f96..2b62a46c 100644 --- a/daemon/core/gui/graph/shapeutils.py +++ b/daemon/core/gui/graph/shapeutils.py @@ -1,4 +1,5 @@ import enum +from typing import Set class ShapeType(enum.Enum): @@ -8,7 +9,7 @@ class ShapeType(enum.Enum): TEXT = "text" -SHAPES = {ShapeType.OVAL, ShapeType.RECTANGLE} +SHAPES: Set[ShapeType] = {ShapeType.OVAL, ShapeType.RECTANGLE} def is_draw_shape(shape_type: ShapeType) -> bool: diff --git a/daemon/core/gui/graph/tags.py b/daemon/core/gui/graph/tags.py index c0721193..b7b35517 100644 --- a/daemon/core/gui/graph/tags.py +++ b/daemon/core/gui/graph/tags.py @@ -1,17 +1,19 @@ -ANNOTATION = "annotation" -GRIDLINE = "gridline" -SHAPE = "shape" -SHAPE_TEXT = "shapetext" -EDGE = "edge" -LINK_LABEL = "linklabel" -WIRELESS_EDGE = "wireless" -ANTENNA = "antenna" -NODE_LABEL = "nodename" -NODE = "node" -WALLPAPER = "wallpaper" -SELECTION = "selectednodes" -MARKER = "marker" -ORGANIZE_TAGS = [ +from typing import List + +ANNOTATION: str = "annotation" +GRIDLINE: str = "gridline" +SHAPE: str = "shape" +SHAPE_TEXT: str = "shapetext" +EDGE: str = "edge" +LINK_LABEL: str = "linklabel" +WIRELESS_EDGE: str = "wireless" +ANTENNA: str = "antenna" +NODE_LABEL: str = "nodename" +NODE: str = "node" +WALLPAPER: str = "wallpaper" +SELECTION: str = "selectednodes" +MARKER: str = "marker" +ORGANIZE_TAGS: List[str] = [ WALLPAPER, GRIDLINE, SHAPE, @@ -25,7 +27,7 @@ ORGANIZE_TAGS = [ SELECTION, MARKER, ] -RESET_TAGS = [ +RESET_TAGS: List[str] = [ EDGE, NODE, NODE_LABEL, diff --git a/daemon/core/gui/graph/tooltip.py b/daemon/core/gui/graph/tooltip.py index a2193901..6e4aa62f 100644 --- a/daemon/core/gui/graph/tooltip.py +++ b/daemon/core/gui/graph/tooltip.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Tuple from core.gui.themes import Styles @@ -27,39 +27,45 @@ class CanvasTooltip: self, canvas: "CanvasGraph", *, - pad=(5, 3, 5, 3), + pad: Tuple[int, int, int, int] = (5, 3, 5, 3), waittime: int = 400, wraplength: int = 600 - ): + ) -> None: # in miliseconds, originally 500 - self.waittime = waittime + self.waittime: int = waittime # in pixels, originally 180 - self.wraplength = wraplength - self.canvas = canvas - self.text = tk.StringVar() - self.pad = pad - self.id = None - self.tw = None + self.wraplength: int = wraplength + self.canvas: "CanvasGraph" = canvas + self.text: tk.StringVar = tk.StringVar() + self.pad: Tuple[int, int, int, int] = pad + self.id: Optional[str] = None + self.tw: Optional[tk.Toplevel] = None - def on_enter(self, event: tk.Event = None): + def on_enter(self, event: tk.Event = None) -> None: self.schedule() - def on_leave(self, event: tk.Event = None): + def on_leave(self, event: tk.Event = None) -> None: self.unschedule() self.hide() - def schedule(self): + def schedule(self) -> None: self.unschedule() self.id = self.canvas.after(self.waittime, self.show) - def unschedule(self): + def unschedule(self) -> None: id_ = self.id self.id = None if id_: self.canvas.after_cancel(id_) - def show(self, event: tk.Event = None): - def tip_pos_calculator(canvas, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)): + def show(self, event: tk.Event = None) -> None: + def tip_pos_calculator( + canvas: "CanvasGraph", + label: ttk.Label, + *, + tip_delta: Tuple[int, int] = (10, 5), + pad: Tuple[int, int, int, int] = (5, 3, 5, 3) + ): c = canvas s_width, s_height = c.winfo_screenwidth(), c.winfo_screenheight() width, height = ( @@ -108,7 +114,7 @@ class CanvasTooltip: x, y = tip_pos_calculator(canvas, label, pad=pad) self.tw.wm_geometry("+%d+%d" % (x, y)) - def hide(self): + def hide(self) -> None: if self.tw: self.tw.destroy() self.tw = None From 527d34e3746d781908abe6a256b90db89dd77d9a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 11:04:33 -0700 Subject: [PATCH 086/146] pygui: added type hinting to class variables for core.gui.dialogs --- daemon/core/gui/coreclient.py | 4 +- daemon/core/gui/dialogs/about.py | 4 +- daemon/core/gui/dialogs/alerts.py | 18 +-- daemon/core/gui/dialogs/canvassizeandscale.py | 49 +++--- daemon/core/gui/dialogs/canvaswallpaper.py | 43 ++--- daemon/core/gui/dialogs/colorpicker.py | 52 +++--- .../core/gui/dialogs/configserviceconfig.py | 113 +++++++------ daemon/core/gui/dialogs/copyserviceconfig.py | 15 +- daemon/core/gui/dialogs/customnodes.py | 72 +++++---- daemon/core/gui/dialogs/dialog.py | 12 +- daemon/core/gui/dialogs/emaneconfig.py | 78 ++++----- daemon/core/gui/dialogs/emaneinstall.py | 4 +- daemon/core/gui/dialogs/error.py | 8 +- daemon/core/gui/dialogs/executepython.py | 20 +-- daemon/core/gui/dialogs/find.py | 6 +- daemon/core/gui/dialogs/hooks.py | 40 ++--- daemon/core/gui/dialogs/ipdialog.py | 18 +-- daemon/core/gui/dialogs/linkconfig.py | 57 +++---- daemon/core/gui/dialogs/macdialog.py | 2 +- daemon/core/gui/dialogs/mobilityconfig.py | 29 ++-- daemon/core/gui/dialogs/mobilityplayer.py | 70 ++++---- daemon/core/gui/dialogs/nodeconfig.py | 50 +++--- daemon/core/gui/dialogs/nodeconfigservice.py | 32 ++-- daemon/core/gui/dialogs/nodeservice.py | 28 ++-- daemon/core/gui/dialogs/observers.py | 38 ++--- daemon/core/gui/dialogs/preferences.py | 28 ++-- daemon/core/gui/dialogs/runtool.py | 10 +- daemon/core/gui/dialogs/servers.py | 42 ++--- daemon/core/gui/dialogs/serviceconfig.py | 149 +++++++++--------- daemon/core/gui/dialogs/sessionoptions.py | 17 +- daemon/core/gui/dialogs/sessions.py | 19 +-- daemon/core/gui/dialogs/shapemod.py | 63 ++++---- daemon/core/gui/dialogs/throughput.py | 31 ++-- daemon/core/gui/dialogs/wlanconfig.py | 46 +++--- daemon/core/gui/graph/node.py | 6 +- daemon/core/gui/menubar.py | 2 +- daemon/core/gui/nodeutils.py | 2 +- 37 files changed, 664 insertions(+), 613 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 5e1bf4c2..39ee486a 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -38,7 +38,7 @@ from core.api.grpc.mobility_pb2 import MobilityConfig from core.api.grpc.services_pb2 import NodeServiceData, ServiceConfig, ServiceFileConfig from core.api.grpc.wlan_pb2 import WlanConfig from core.gui import appconfig -from core.gui.appconfig import CoreServer +from core.gui.appconfig import CoreServer, Observer from core.gui.dialogs.emaneinstall import EmaneInstallDialog from core.gui.dialogs.error import ErrorDialog from core.gui.dialogs.mobilityplayer import MobilityPlayer @@ -75,7 +75,7 @@ class CoreClient: # loaded configuration data self.servers: Dict[str, CoreServer] = {} self.custom_nodes: Dict[str, NodeDraw] = {} - self.custom_observers: Dict[str, str] = {} + self.custom_observers: Dict[str, Observer] = {} self.read_config() # helpers diff --git a/daemon/core/gui/dialogs/about.py b/daemon/core/gui/dialogs/about.py index 2e649169..fa96e218 100644 --- a/daemon/core/gui/dialogs/about.py +++ b/daemon/core/gui/dialogs/about.py @@ -35,11 +35,11 @@ THE POSSIBILITY OF SUCH DAMAGE.\ class AboutDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "About CORE") self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) diff --git a/daemon/core/gui/dialogs/alerts.py b/daemon/core/gui/dialogs/alerts.py index a0c3e68b..00ef1e8c 100644 --- a/daemon/core/gui/dialogs/alerts.py +++ b/daemon/core/gui/dialogs/alerts.py @@ -3,9 +3,9 @@ check engine light """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional -from core.api.grpc.core_pb2 import ExceptionLevel +from core.api.grpc.core_pb2 import ExceptionEvent, ExceptionLevel from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import CodeText @@ -15,14 +15,14 @@ if TYPE_CHECKING: class AlertsDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Alerts") - self.tree = None - self.codetext = None - self.alarm_map = {} + self.tree: Optional[ttk.Treeview] = None + self.codetext: Optional[CodeText] = None + self.alarm_map: Dict[int, ExceptionEvent] = {} self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.top.rowconfigure(1, weight=1) @@ -97,13 +97,13 @@ class AlertsDialog(Dialog): button = ttk.Button(frame, text="Close", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def reset_alerts(self): + def reset_alerts(self) -> None: self.codetext.text.delete("1.0", tk.END) for item in self.tree.get_children(): self.tree.delete(item) self.app.statusbar.core_alarms.clear() - def click_select(self, event: tk.Event): + def click_select(self, event: tk.Event) -> None: current = self.tree.selection()[0] alarm = self.alarm_map[current] self.codetext.text.config(state=tk.NORMAL) diff --git a/daemon/core/gui/dialogs/canvassizeandscale.py b/daemon/core/gui/dialogs/canvassizeandscale.py index 6a63a1ae..b93bd920 100644 --- a/daemon/core/gui/dialogs/canvassizeandscale.py +++ b/daemon/core/gui/dialogs/canvassizeandscale.py @@ -7,38 +7,43 @@ from typing import TYPE_CHECKING from core.gui import validation from core.gui.dialogs.dialog import Dialog +from core.gui.graph.graph import CanvasGraph from core.gui.themes import FRAME_PAD, PADX, PADY if TYPE_CHECKING: from core.gui.app import Application -PIXEL_SCALE = 100 +PIXEL_SCALE: int = 100 class SizeAndScaleDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: """ create an instance for size and scale object """ super().__init__(app, "Canvas Size and Scale") - self.canvas = self.app.canvas - self.section_font = font.Font(weight="bold") + self.canvas: CanvasGraph = self.app.canvas + self.section_font: font.Font = font.Font(weight="bold") width, height = self.canvas.current_dimensions - self.pixel_width = tk.IntVar(value=width) - self.pixel_height = tk.IntVar(value=height) + self.pixel_width: tk.IntVar = tk.IntVar(value=width) + self.pixel_height: tk.IntVar = tk.IntVar(value=height) location = self.app.core.location - self.x = tk.DoubleVar(value=location.x) - self.y = tk.DoubleVar(value=location.y) - self.lat = tk.DoubleVar(value=location.lat) - self.lon = tk.DoubleVar(value=location.lon) - self.alt = tk.DoubleVar(value=location.alt) - self.scale = tk.DoubleVar(value=location.scale) - self.meters_width = tk.IntVar(value=width / PIXEL_SCALE * location.scale) - self.meters_height = tk.IntVar(value=height / PIXEL_SCALE * location.scale) - self.save_default = tk.BooleanVar(value=False) + self.x: tk.DoubleVar = tk.DoubleVar(value=location.x) + self.y: tk.DoubleVar = tk.DoubleVar(value=location.y) + self.lat: tk.DoubleVar = tk.DoubleVar(value=location.lat) + self.lon: tk.DoubleVar = tk.DoubleVar(value=location.lon) + self.alt: tk.DoubleVar = tk.DoubleVar(value=location.alt) + self.scale: tk.DoubleVar = tk.DoubleVar(value=location.scale) + self.meters_width: tk.IntVar = tk.IntVar( + value=width / PIXEL_SCALE * location.scale + ) + self.meters_height: tk.IntVar = tk.IntVar( + value=height / PIXEL_SCALE * location.scale + ) + self.save_default: tk.BooleanVar = tk.BooleanVar(value=False) self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.draw_size() self.draw_scale() @@ -47,7 +52,7 @@ class SizeAndScaleDialog(Dialog): self.draw_spacer() self.draw_buttons() - def draw_size(self): + def draw_size(self) -> None: label_frame = ttk.Labelframe(self.top, text="Size", padding=FRAME_PAD) label_frame.grid(sticky="ew") label_frame.columnconfigure(0, weight=1) @@ -84,7 +89,7 @@ class SizeAndScaleDialog(Dialog): label = ttk.Label(frame, text="Meters") label.grid(row=0, column=4, sticky="w") - def draw_scale(self): + def draw_scale(self) -> None: label_frame = ttk.Labelframe(self.top, text="Scale", padding=FRAME_PAD) label_frame.grid(sticky="ew") label_frame.columnconfigure(0, weight=1) @@ -99,7 +104,7 @@ class SizeAndScaleDialog(Dialog): label = ttk.Label(frame, text="Meters") label.grid(row=0, column=2, sticky="w") - def draw_reference_point(self): + def draw_reference_point(self) -> None: label_frame = ttk.Labelframe( self.top, text="Reference Point", padding=FRAME_PAD ) @@ -150,13 +155,13 @@ class SizeAndScaleDialog(Dialog): entry = validation.FloatEntry(frame, textvariable=self.alt) entry.grid(row=0, column=5, sticky="ew") - def draw_save_as_default(self): + def draw_save_as_default(self) -> None: button = ttk.Checkbutton( self.top, text="Save as default?", variable=self.save_default ) button.grid(sticky="w", pady=PADY) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) @@ -168,7 +173,7 @@ class SizeAndScaleDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: width, height = self.pixel_width.get(), self.pixel_height.get() self.canvas.redraw_canvas((width, height)) if self.canvas.wallpaper: diff --git a/daemon/core/gui/dialogs/canvaswallpaper.py b/daemon/core/gui/dialogs/canvaswallpaper.py index 5e8460be..8a1e71d8 100644 --- a/daemon/core/gui/dialogs/canvaswallpaper.py +++ b/daemon/core/gui/dialogs/canvaswallpaper.py @@ -4,10 +4,11 @@ set wallpaper import logging import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional from core.gui.appconfig import BACKGROUNDS_PATH from core.gui.dialogs.dialog import Dialog +from core.gui.graph.graph import CanvasGraph from core.gui.images import Images from core.gui.themes import PADX, PADY from core.gui.widgets import image_chooser @@ -17,20 +18,22 @@ if TYPE_CHECKING: class CanvasWallpaperDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: """ create an instance of CanvasWallpaper object """ super().__init__(app, "Canvas Background") - self.canvas = self.app.canvas - self.scale_option = tk.IntVar(value=self.canvas.scale_option.get()) - self.adjust_to_dim = tk.BooleanVar(value=self.canvas.adjust_to_dim.get()) - self.filename = tk.StringVar(value=self.canvas.wallpaper_file) - self.image_label = None - self.options = [] + self.canvas: CanvasGraph = self.app.canvas + self.scale_option: tk.IntVar = tk.IntVar(value=self.canvas.scale_option.get()) + self.adjust_to_dim: tk.BooleanVar = tk.BooleanVar( + value=self.canvas.adjust_to_dim.get() + ) + self.filename: tk.StringVar = tk.StringVar(value=self.canvas.wallpaper_file) + self.image_label: Optional[ttk.Label] = None + self.options: List[ttk.Radiobutton] = [] self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.draw_image() self.draw_image_label() @@ -40,19 +43,19 @@ class CanvasWallpaperDialog(Dialog): self.draw_spacer() self.draw_buttons() - def draw_image(self): + def draw_image(self) -> None: self.image_label = ttk.Label( self.top, text="(image preview)", width=32, anchor=tk.CENTER ) self.image_label.grid(pady=PADY) - def draw_image_label(self): + def draw_image_label(self) -> None: label = ttk.Label(self.top, text="Image filename: ") label.grid(sticky="ew") if self.filename.get(): self.draw_preview() - def draw_image_selection(self): + def draw_image_selection(self) -> None: frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=2) frame.columnconfigure(1, weight=1) @@ -69,7 +72,7 @@ class CanvasWallpaperDialog(Dialog): button = ttk.Button(frame, text="Clear", command=self.click_clear) button.grid(row=0, column=2, sticky="ew") - def draw_options(self): + def draw_options(self) -> None: frame = ttk.Frame(self.top) frame.columnconfigure(0, weight=1) frame.columnconfigure(1, weight=1) @@ -101,7 +104,7 @@ class CanvasWallpaperDialog(Dialog): button.grid(row=0, column=3, sticky="ew") self.options.append(button) - def draw_additional_options(self): + def draw_additional_options(self) -> None: checkbutton = ttk.Checkbutton( self.top, text="Adjust canvas size to image dimensions", @@ -110,7 +113,7 @@ class CanvasWallpaperDialog(Dialog): ) checkbutton.grid(sticky="ew", padx=PADX) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(pady=PADY, sticky="ew") frame.columnconfigure(0, weight=1) @@ -122,18 +125,18 @@ class CanvasWallpaperDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_open_image(self): + def click_open_image(self) -> None: filename = image_chooser(self, BACKGROUNDS_PATH) if filename: self.filename.set(filename) self.draw_preview() - def draw_preview(self): + def draw_preview(self) -> None: image = Images.create(self.filename.get(), 250, 135) self.image_label.config(image=image) self.image_label.image = image - def click_clear(self): + def click_clear(self) -> None: """ delete like shown in image link entry if there is any """ @@ -143,7 +146,7 @@ class CanvasWallpaperDialog(Dialog): self.image_label.config(image="", width=32) self.image_label.image = None - def click_adjust_canvas(self): + def click_adjust_canvas(self) -> None: # deselect all radio buttons and grey them out if self.adjust_to_dim.get(): self.scale_option.set(0) @@ -155,7 +158,7 @@ class CanvasWallpaperDialog(Dialog): for option in self.options: option.config(state=tk.NORMAL) - def click_apply(self): + def click_apply(self) -> None: self.canvas.scale_option.set(self.scale_option.get()) self.canvas.adjust_to_dim.set(self.adjust_to_dim.get()) self.canvas.show_grid.click_handler() diff --git a/daemon/core/gui/dialogs/colorpicker.py b/daemon/core/gui/dialogs/colorpicker.py index b1968cd4..908b8acb 100644 --- a/daemon/core/gui/dialogs/colorpicker.py +++ b/daemon/core/gui/dialogs/colorpicker.py @@ -3,7 +3,7 @@ custom color picker """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Tuple from core.gui import validation from core.gui.dialogs.dialog import Dialog @@ -18,23 +18,23 @@ class ColorPickerDialog(Dialog): self, master: tk.BaseWidget, app: "Application", initcolor: str = "#000000" ): super().__init__(app, "Color Picker", master=master) - self.red_entry = None - self.blue_entry = None - self.green_entry = None - self.hex_entry = None - self.red_label = None - self.green_label = None - self.blue_label = None - self.display = None - self.color = initcolor + self.red_entry: Optional[validation.RgbEntry] = None + self.blue_entry: Optional[validation.RgbEntry] = None + self.green_entry: Optional[validation.RgbEntry] = None + self.hex_entry: Optional[validation.HexEntry] = None + self.red_label: Optional[ttk.Label] = None + self.green_label: Optional[ttk.Label] = None + self.blue_label: Optional[ttk.Label] = None + self.display: Optional[tk.Frame] = None + self.color: str = initcolor red, green, blue = self.get_rgb(initcolor) - self.red = tk.IntVar(value=red) - self.blue = tk.IntVar(value=blue) - self.green = tk.IntVar(value=green) - self.hex = tk.StringVar(value=initcolor) - self.red_scale = tk.IntVar(value=red) - self.green_scale = tk.IntVar(value=green) - self.blue_scale = tk.IntVar(value=blue) + self.red: tk.IntVar = tk.IntVar(value=red) + self.blue: tk.IntVar = tk.IntVar(value=blue) + self.green: tk.IntVar = tk.IntVar(value=green) + self.hex: tk.StringVar = tk.StringVar(value=initcolor) + self.red_scale: tk.IntVar = tk.IntVar(value=red) + self.green_scale: tk.IntVar = tk.IntVar(value=green) + self.blue_scale: tk.IntVar = tk.IntVar(value=blue) self.draw() self.set_bindings() @@ -42,7 +42,7 @@ class ColorPickerDialog(Dialog): self.show() return self.color - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(3, weight=1) @@ -136,7 +136,7 @@ class ColorPickerDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def set_bindings(self): + def set_bindings(self) -> None: self.red_entry.bind("", lambda x: self.current_focus("rgb")) self.green_entry.bind("", lambda x: self.current_focus("rgb")) self.blue_entry.bind("", lambda x: self.current_focus("rgb")) @@ -146,7 +146,7 @@ class ColorPickerDialog(Dialog): self.blue.trace_add("write", self.update_color) self.hex.trace_add("write", self.update_color) - def button_ok(self): + def button_ok(self) -> None: self.color = self.hex.get() self.destroy() @@ -159,10 +159,10 @@ class ColorPickerDialog(Dialog): green = self.green_entry.get() return "#%02x%02x%02x" % (int(red), int(green), int(blue)) - def current_focus(self, focus: str): + def current_focus(self, focus: str) -> None: self.focus = focus - def update_color(self, arg1=None, arg2=None, arg3=None): + def update_color(self, arg1=None, arg2=None, arg3=None) -> None: if self.focus == "rgb": red = self.red_entry.get() blue = self.blue_entry.get() @@ -184,7 +184,7 @@ class ColorPickerDialog(Dialog): self.display.config(background=hex_code) self.set_label(str(red), str(green), str(blue)) - def scale_callback(self, var: tk.IntVar, color_var: tk.IntVar): + def scale_callback(self, var: tk.IntVar, color_var: tk.IntVar) -> None: color_var.set(var.get()) self.focus = "rgb" self.update_color() @@ -194,17 +194,17 @@ class ColorPickerDialog(Dialog): self.green_scale.set(green) self.blue_scale.set(blue) - def set_entry(self, red: int, green: int, blue: int): + def set_entry(self, red: int, green: int, blue: int) -> None: self.red.set(red) self.green.set(green) self.blue.set(blue) - def set_label(self, red: str, green: str, blue: str): + def set_label(self, red: str, green: str, blue: str) -> None: 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.blue_label.configure(background="#%02x%02x%02x" % (0, 0, int(blue))) - def get_rgb(self, hex_code: str) -> [int, int, int]: + def get_rgb(self, hex_code: str) -> Tuple[int, int, int]: """ convert a valid hex code to RGB values """ diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 42041a8e..c2d42ee4 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -4,10 +4,11 @@ Service configuration dialog import logging import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Dict, List, Optional, Set import grpc +from core.api.grpc.common_pb2 import ConfigOption from core.api.grpc.services_pb2 import ServiceValidationMode from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY @@ -16,6 +17,7 @@ from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.node import CanvasNode + from core.gui.coreclient import CoreClient class ConfigServiceConfigDialog(Dialog): @@ -26,56 +28,53 @@ class ConfigServiceConfigDialog(Dialog): service_name: str, canvas_node: "CanvasNode", node_id: int, - ): + ) -> None: title = f"{service_name} Config Service" super().__init__(app, title, master=master) - self.core = app.core - self.canvas_node = canvas_node - self.node_id = node_id - self.service_name = service_name - self.radiovar = tk.IntVar() + self.core: "CoreClient" = app.core + self.canvas_node: "CanvasNode" = canvas_node + self.node_id: int = node_id + self.service_name: str = service_name + self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(2) - self.directories = [] - self.templates = [] - self.dependencies = [] - self.executables = [] - self.startup_commands = [] - self.validation_commands = [] - self.shutdown_commands = [] - self.default_startup = [] - self.default_validate = [] - self.default_shutdown = [] - self.validation_mode = None - self.validation_time = None - self.validation_period = tk.StringVar() - self.modes = [] - self.mode_configs = {} - - self.notebook = None - self.templates_combobox = None - self.modes_combobox = None - self.startup_commands_listbox = None - self.shutdown_commands_listbox = None - self.validate_commands_listbox = None - self.validation_time_entry = None - self.validation_mode_entry = None - self.template_text = None - self.validation_period_entry = None - self.original_service_files = {} - self.temp_service_files = {} - self.modified_files = set() - self.config_frame = None - self.default_config = None - self.config = None - - self.has_error = False + self.directories: List[str] = [] + self.templates: List[str] = [] + self.dependencies: List[str] = [] + self.executables: List[str] = [] + self.startup_commands: List[str] = [] + self.validation_commands: List[str] = [] + self.shutdown_commands: List[str] = [] + self.default_startup: List[str] = [] + self.default_validate: List[str] = [] + self.default_shutdown: List[str] = [] + self.validation_mode: Optional[ServiceValidationMode] = None + self.validation_time: Optional[int] = None + self.validation_period: tk.StringVar = tk.StringVar() + self.modes: List[str] = [] + self.mode_configs: Dict[str, str] = {} + self.notebook: Optional[ttk.Notebook] = None + self.templates_combobox: Optional[ttk.Combobox] = None + self.modes_combobox: Optional[ttk.Combobox] = None + self.startup_commands_listbox: Optional[tk.Listbox] = None + self.shutdown_commands_listbox: Optional[tk.Listbox] = None + self.validate_commands_listbox: Optional[tk.Listbox] = None + self.validation_time_entry: Optional[ttk.Entry] = None + self.validation_mode_entry: Optional[ttk.Entry] = None + self.template_text: Optional[CodeText] = None + self.validation_period_entry: Optional[ttk.Entry] = None + self.original_service_files: Dict[str, str] = {} + self.temp_service_files: Dict[str, str] = {} + self.modified_files: Set[str] = set() + self.config_frame: Optional[ConfigFrame] = None + self.default_config: Dict[str, str] = {} + self.config: Dict[str, ConfigOption] = {} + self.has_error: bool = False self.load() - if not self.has_error: self.draw() - def load(self): + def load(self) -> None: try: self.core.create_nodes_and_links() service = self.core.config_services[self.service_name] @@ -116,7 +115,7 @@ class ConfigServiceConfigDialog(Dialog): self.app.show_grpc_exception("Get Config Service Error", e) self.has_error = True - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -130,7 +129,7 @@ class ConfigServiceConfigDialog(Dialog): self.draw_tab_validation() self.draw_buttons() - def draw_tab_files(self): + def draw_tab_files(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -174,7 +173,7 @@ class ConfigServiceConfigDialog(Dialog): ) self.template_text.text.bind("", self.update_template_file_data) - def draw_tab_config(self): + def draw_tab_config(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -198,7 +197,7 @@ class ConfigServiceConfigDialog(Dialog): self.config_frame.grid(sticky="nsew", pady=PADY) tab.rowconfigure(self.config_frame.grid_info()["row"], weight=1) - def draw_tab_startstop(self): + def draw_tab_startstop(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -239,7 +238,7 @@ class ConfigServiceConfigDialog(Dialog): elif i == 2: self.validate_commands_listbox = listbox_scroll.listbox - def draw_tab_validation(self): + def draw_tab_validation(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="ew") tab.columnconfigure(0, weight=1) @@ -298,7 +297,7 @@ class ConfigServiceConfigDialog(Dialog): for dependency in self.dependencies: listbox_scroll.listbox.insert("end", dependency) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(4): @@ -312,7 +311,7 @@ class ConfigServiceConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=3, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: current_listbox = self.master.current.listbox if not self.is_custom(): self.canvas_node.config_service_configs.pop(self.service_name, None) @@ -333,18 +332,18 @@ class ConfigServiceConfigDialog(Dialog): current_listbox.itemconfig(all_current.index(self.service_name), bg="green") self.destroy() - def handle_template_changed(self, event: tk.Event): + def handle_template_changed(self, event: tk.Event) -> None: template = self.templates_combobox.get() self.template_text.text.delete(1.0, "end") self.template_text.text.insert("end", self.temp_service_files[template]) - def handle_mode_changed(self, event: tk.Event): + def handle_mode_changed(self, event: tk.Event) -> None: mode = self.modes_combobox.get() config = self.mode_configs[mode] logging.info("mode config: %s", config) self.config_frame.set_values(config) - def update_template_file_data(self, event: tk.Event): + def update_template_file_data(self, event: tk.Event) -> None: scrolledtext = event.widget template = self.templates_combobox.get() self.temp_service_files[template] = scrolledtext.get(1.0, "end") @@ -353,7 +352,7 @@ class ConfigServiceConfigDialog(Dialog): else: self.modified_files.discard(template) - def is_custom(self): + def is_custom(self) -> bool: has_custom_templates = len(self.modified_files) > 0 has_custom_config = False if self.config_frame: @@ -361,7 +360,7 @@ class ConfigServiceConfigDialog(Dialog): has_custom_config = self.default_config != current return has_custom_templates or has_custom_config - def click_defaults(self): + def click_defaults(self) -> None: self.canvas_node.config_service_configs.pop(self.service_name, None) logging.info( "cleared config service config: %s", self.canvas_node.config_service_configs @@ -374,12 +373,12 @@ class ConfigServiceConfigDialog(Dialog): logging.info("resetting defaults: %s", self.default_config) self.config_frame.set_values(self.default_config) - def click_copy(self): + def click_copy(self) -> None: pass def append_commands( self, commands: List[str], listbox: tk.Listbox, to_add: List[str] - ): + ) -> None: for cmd in to_add: commands.append(cmd) listbox.insert(tk.END, cmd) diff --git a/daemon/core/gui/dialogs/copyserviceconfig.py b/daemon/core/gui/dialogs/copyserviceconfig.py index ff75a59a..35559cb9 100644 --- a/daemon/core/gui/dialogs/copyserviceconfig.py +++ b/daemon/core/gui/dialogs/copyserviceconfig.py @@ -15,17 +15,16 @@ if TYPE_CHECKING: class CopyServiceConfigDialog(Dialog): - def __init__(self, master: tk.BaseWidget, app: "Application", node_id: int): + def __init__(self, master: tk.BaseWidget, app: "Application", node_id: int) -> None: super().__init__(app, f"Copy services to node {node_id}", master=master) self.parent = master self.node_id = node_id self.service_configs = app.core.service_configs self.file_configs = app.core.file_configs - self.tree = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.tree = ttk.Treeview(self.top) self.tree.grid(row=0, column=0, sticky="ew", padx=PADX) @@ -88,7 +87,7 @@ class CopyServiceConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=2, sticky="ew", padx=PADX) - def click_copy(self): + def click_copy(self) -> None: selected = self.tree.selection() if selected: item = self.tree.item(selected[0]) @@ -127,7 +126,7 @@ class CopyServiceConfigDialog(Dialog): ) self.destroy() - def click_view(self): + def click_view(self) -> None: selected = self.tree.selection() data = "" if selected: @@ -159,7 +158,7 @@ class CopyServiceConfigDialog(Dialog): ) dialog.show() - def get_node_service(self, selected: Tuple[str]) -> [int, str]: + def get_node_service(self, selected: Tuple[str]) -> Tuple[int, str]: service_tree_id = self.tree.parent(selected[0]) service_name = self.tree.item(service_tree_id)["text"] node_tree_id = self.tree.parent(service_tree_id) @@ -175,14 +174,14 @@ class ViewConfigDialog(Dialog): node_id: int, data: str, filename: str = None, - ): + ) -> None: super().__init__(app, f"n{node_id} config data", master=master) self.data = data self.service_data = None self.filepath = tk.StringVar(value=f"/tmp/services.tmp-n{node_id}-{filename}") self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) frame = ttk.Frame(self.top, padding=FRAME_PAD) frame.columnconfigure(0, weight=1) diff --git a/daemon/core/gui/dialogs/customnodes.py b/daemon/core/gui/dialogs/customnodes.py index 56012780..df3bafa7 100644 --- a/daemon/core/gui/dialogs/customnodes.py +++ b/daemon/core/gui/dialogs/customnodes.py @@ -2,7 +2,9 @@ import logging import tkinter as tk from pathlib import Path from tkinter import ttk -from typing import TYPE_CHECKING, Set +from typing import TYPE_CHECKING, Optional, Set + +from PIL.ImageTk import PhotoImage from core.gui import nodeutils from core.gui.appconfig import ICONS_PATH, CustomNode @@ -19,15 +21,15 @@ if TYPE_CHECKING: class ServicesSelectDialog(Dialog): def __init__( self, master: tk.BaseWidget, app: "Application", current_services: Set[str] - ): + ) -> None: super().__init__(app, "Node Services", master=master) - self.groups = None - self.services = None - self.current = None - self.current_services = set(current_services) + self.groups: Optional[ListboxScroll] = None + self.services: Optional[CheckboxList] = None + self.current: Optional[ListboxScroll] = None + self.current_services: Set[str] = current_services self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -77,7 +79,7 @@ class ServicesSelectDialog(Dialog): # trigger group change self.groups.listbox.event_generate("<>") - def handle_group_change(self, event: tk.Event): + def handle_group_change(self, event: tk.Event) -> None: selection = self.groups.listbox.curselection() if selection: index = selection[0] @@ -87,7 +89,7 @@ class ServicesSelectDialog(Dialog): checked = name in self.current_services self.services.add(name, checked) - def service_clicked(self, name: str, var: tk.BooleanVar): + def service_clicked(self, name: str, var: tk.BooleanVar) -> None: if var.get() and name not in self.current_services: self.current_services.add(name) elif not var.get() and name in self.current_services: @@ -96,34 +98,34 @@ class ServicesSelectDialog(Dialog): for name in sorted(self.current_services): self.current.listbox.insert(tk.END, name) - def click_cancel(self): + def click_cancel(self) -> None: self.current_services = None self.destroy() class CustomNodesDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Custom Nodes") - self.edit_button = None - self.delete_button = None - self.nodes_list = None - self.name = tk.StringVar() - self.image_button = None - self.image = None - self.image_file = None - self.services = set() - self.selected = None - self.selected_index = None + self.edit_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None + self.nodes_list: Optional[ListboxScroll] = None + self.name: tk.StringVar = tk.StringVar() + self.image_button: Optional[ttk.Button] = None + self.image: Optional[PhotoImage] = None + self.image_file: Optional[str] = None + self.services: Set[str] = set() + self.selected: Optional[str] = None + self.selected_index: Optional[int] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.draw_node_config() self.draw_node_buttons() self.draw_buttons() - def draw_node_config(self): + def draw_node_config(self) -> None: frame = ttk.LabelFrame(self.top, text="Nodes", padding=FRAME_PAD) frame.grid(sticky="nsew", pady=PADY) frame.columnconfigure(0, weight=1) @@ -147,7 +149,7 @@ class CustomNodesDialog(Dialog): button = ttk.Button(frame, text="Services", command=self.click_services) button.grid(sticky="ew") - def draw_node_buttons(self): + def draw_node_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew", pady=PADY) for i in range(3): @@ -166,7 +168,7 @@ class CustomNodesDialog(Dialog): ) self.delete_button.grid(row=0, column=2, sticky="ew") - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -178,14 +180,14 @@ class CustomNodesDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def reset_values(self): + def reset_values(self) -> None: self.name.set("") self.image = None self.image_file = None self.services = set() self.image_button.config(image="") - def click_icon(self): + def click_icon(self) -> None: file_path = image_chooser(self, ICONS_PATH) if file_path: image = Images.create(file_path, nodeutils.ICON_SIZE) @@ -193,24 +195,26 @@ class CustomNodesDialog(Dialog): self.image_file = file_path self.image_button.config(image=self.image) - def click_services(self): + def click_services(self) -> None: dialog = ServicesSelectDialog(self, self.app, self.services) dialog.show() if dialog.current_services is not None: self.services.clear() self.services.update(dialog.current_services) - def click_save(self): + def click_save(self) -> None: self.app.guiconfig.nodes.clear() for name in self.app.core.custom_nodes: node_draw = self.app.core.custom_nodes[name] - custom_node = CustomNode(name, node_draw.image_file, node_draw.services) + custom_node = CustomNode( + name, node_draw.image_file, list(node_draw.services) + ) self.app.guiconfig.nodes.append(custom_node) logging.info("saving custom nodes: %s", self.app.guiconfig.nodes) self.app.save_config() self.destroy() - def click_create(self): + def click_create(self) -> None: name = self.name.get() if name not in self.app.core.custom_nodes: image_file = Path(self.image_file).stem @@ -226,7 +230,7 @@ class CustomNodesDialog(Dialog): self.nodes_list.listbox.insert(tk.END, name) self.reset_values() - def click_edit(self): + def click_edit(self) -> None: name = self.name.get() if self.selected: previous_name = self.selected @@ -247,7 +251,7 @@ class CustomNodesDialog(Dialog): self.nodes_list.listbox.insert(self.selected_index, name) self.nodes_list.listbox.selection_set(self.selected_index) - def click_delete(self): + def click_delete(self) -> None: if self.selected and self.selected in self.app.core.custom_nodes: self.nodes_list.listbox.delete(self.selected_index) del self.app.core.custom_nodes[self.selected] @@ -255,7 +259,7 @@ class CustomNodesDialog(Dialog): self.nodes_list.listbox.selection_clear(0, tk.END) self.nodes_list.listbox.event_generate("<>") - def handle_node_select(self, event: tk.Event): + def handle_node_select(self, event: tk.Event) -> None: selection = self.nodes_list.listbox.curselection() if selection: self.selected_index = selection[0] diff --git a/daemon/core/gui/dialogs/dialog.py b/daemon/core/gui/dialogs/dialog.py index f3742c50..962170e7 100644 --- a/daemon/core/gui/dialogs/dialog.py +++ b/daemon/core/gui/dialogs/dialog.py @@ -16,23 +16,23 @@ class Dialog(tk.Toplevel): title: str, modal: bool = True, master: tk.BaseWidget = None, - ): + ) -> None: if master is None: master = app super().__init__(master) self.withdraw() - self.app = app - self.modal = modal + self.app: "Application" = app + self.modal: bool = modal self.title(title) self.protocol("WM_DELETE_WINDOW", self.destroy) image = Images.get(ImageEnum.CORE, 16) self.tk.call("wm", "iconphoto", self._w, image) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) - self.top = ttk.Frame(self, padding=DIALOG_PAD) + self.top: ttk.Frame = ttk.Frame(self, padding=DIALOG_PAD) self.top.grid(sticky="nsew") - def show(self): + def show(self) -> None: self.transient(self.master) self.focus_force() self.update() @@ -42,7 +42,7 @@ class Dialog(tk.Toplevel): self.grab_set() self.wait_window() - def draw_spacer(self, row: int = None): + def draw_spacer(self, row: int = None) -> None: frame = ttk.Frame(self.top) frame.grid(row=row, sticky="nsew") frame.rowconfigure(0, weight=1) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index 8f7ca089..df6c6125 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -4,10 +4,12 @@ emane configuration import tkinter as tk import webbrowser from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, List, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Node from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images from core.gui.themes import PADX, PADY @@ -19,12 +21,12 @@ if TYPE_CHECKING: class GlobalEmaneDialog(Dialog): - def __init__(self, master: tk.BaseWidget, app: "Application"): + def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(app, "EMANE Configuration", master=master) - self.config_frame = None + self.config_frame: Optional[ConfigFrame] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.config_frame = ConfigFrame(self.top, self.app, self.app.core.emane_config) @@ -33,7 +35,7 @@ class GlobalEmaneDialog(Dialog): self.draw_spacer() self.draw_buttons() - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -44,7 +46,7 @@ class GlobalEmaneDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: self.config_frame.parse_config() self.destroy() @@ -57,31 +59,32 @@ class EmaneModelDialog(Dialog): canvas_node: "CanvasNode", model: str, iface_id: int = None, - ): + ) -> None: super().__init__( app, f"{canvas_node.core_node.name} {model} Configuration", master=master ) - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.model = f"emane_{model}" - self.iface_id = iface_id - self.config_frame = None - self.has_error = False + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.model: str = f"emane_{model}" + self.iface_id: int = iface_id + self.config_frame: Optional[ConfigFrame] = None + self.has_error: bool = False try: - self.config = self.canvas_node.emane_model_configs.get( + config = self.canvas_node.emane_model_configs.get( (self.model, self.iface_id) ) - if not self.config: - self.config = self.app.core.get_emane_model_config( + if not config: + config = self.app.core.get_emane_model_config( self.node.id, self.model, self.iface_id ) + self.config: Dict[str, ConfigOption] = config self.draw() except grpc.RpcError as e: self.app.show_grpc_exception("Get EMANE Config Error", e) - self.has_error = True + self.has_error: bool = True self.destroy() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.config_frame = ConfigFrame(self.top, self.app, self.config) @@ -90,7 +93,7 @@ class EmaneModelDialog(Dialog): self.draw_spacer() self.draw_buttons() - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -101,7 +104,7 @@ class EmaneModelDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: self.config_frame.parse_config() key = (self.model, self.iface_id) self.canvas_node.emane_model_configs[key] = self.config @@ -109,18 +112,21 @@ class EmaneModelDialog(Dialog): class EmaneConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: super().__init__(app, f"{canvas_node.core_node.name} EMANE Configuration") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.radiovar = tk.IntVar() + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(1) - self.emane_models = [x.split("_")[1] for x in self.app.core.emane_models] - self.emane_model = tk.StringVar(value=self.node.emane.split("_")[1]) - self.emane_model_button = None + self.emane_models: List[str] = [ + x.split("_")[1] for x in self.app.core.emane_models + ] + model = self.node.emane.split("_")[1] + self.emane_model: tk.StringVar = tk.StringVar(value=model) + self.emane_model_button: Optional[ttk.Button] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.draw_emane_configuration() self.draw_emane_models() @@ -128,7 +134,7 @@ class EmaneConfigDialog(Dialog): self.draw_spacer() self.draw_apply_and_cancel() - def draw_emane_configuration(self): + def draw_emane_configuration(self) -> None: """ draw the main frame for emane configuration """ @@ -153,7 +159,7 @@ class EmaneConfigDialog(Dialog): button.image = image button.grid(sticky="ew", pady=PADY) - def draw_emane_models(self): + def draw_emane_models(self) -> None: """ create a combobox that has all the known emane models """ @@ -174,7 +180,7 @@ class EmaneConfigDialog(Dialog): combobox.grid(row=0, column=1, sticky="ew") combobox.bind("<>", self.emane_model_change) - def draw_emane_buttons(self): + def draw_emane_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew", pady=PADY) for i in range(2): @@ -202,7 +208,7 @@ class EmaneConfigDialog(Dialog): button.image = image button.grid(row=0, column=1, sticky="ew") - def draw_apply_and_cancel(self): + def draw_apply_and_cancel(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -214,11 +220,11 @@ class EmaneConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_emane_config(self): + def click_emane_config(self) -> None: dialog = GlobalEmaneDialog(self, self.app) dialog.show() - def click_model_config(self): + def click_model_config(self) -> None: """ draw emane model configuration """ @@ -227,13 +233,13 @@ class EmaneConfigDialog(Dialog): if not dialog.has_error: dialog.show() - def emane_model_change(self, event: tk.Event): + def emane_model_change(self, event: tk.Event) -> None: """ update emane model options button """ model_name = self.emane_model.get() self.emane_model_button.config(text=f"{model_name} options") - def click_apply(self): + def click_apply(self) -> None: self.node.emane = f"emane_{self.emane_model.get()}" self.destroy() diff --git a/daemon/core/gui/dialogs/emaneinstall.py b/daemon/core/gui/dialogs/emaneinstall.py index 93cf2ac4..3ad9396b 100644 --- a/daemon/core/gui/dialogs/emaneinstall.py +++ b/daemon/core/gui/dialogs/emaneinstall.py @@ -10,7 +10,7 @@ class EmaneInstallDialog(Dialog): super().__init__(app, "EMANE Error") self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) label = ttk.Label(self.top, text="EMANE needs to be installed!") label.grid(sticky="ew", pady=PADY) @@ -21,5 +21,5 @@ class EmaneInstallDialog(Dialog): button = ttk.Button(self.top, text="Close", command=self.destroy) button.grid(sticky="ew") - def click_doc(self): + def click_doc(self) -> None: webbrowser.open_new("https://coreemu.github.io/core/emane.html") diff --git a/daemon/core/gui/dialogs/error.py b/daemon/core/gui/dialogs/error.py index 5ff1dbc5..7fb81077 100644 --- a/daemon/core/gui/dialogs/error.py +++ b/daemon/core/gui/dialogs/error.py @@ -1,5 +1,5 @@ from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images @@ -13,9 +13,9 @@ if TYPE_CHECKING: class ErrorDialog(Dialog): def __init__(self, app: "Application", title: str, details: str) -> None: super().__init__(app, "CORE Exception") - self.title = title - self.details = details - self.error_message = None + self.title: str = title + self.details: str = details + self.error_message: Optional[CodeText] = None self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/dialogs/executepython.py b/daemon/core/gui/dialogs/executepython.py index dd60c778..a4516df1 100644 --- a/daemon/core/gui/dialogs/executepython.py +++ b/daemon/core/gui/dialogs/executepython.py @@ -1,7 +1,7 @@ import logging import tkinter as tk from tkinter import filedialog, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.appconfig import SCRIPT_PATH from core.gui.dialogs.dialog import Dialog @@ -12,15 +12,15 @@ if TYPE_CHECKING: class ExecutePythonDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Execute Python Script") - self.with_options = tk.IntVar(value=0) - self.options = tk.StringVar(value="") - self.option_entry = None - self.file_entry = None + self.with_options: tk.IntVar = tk.IntVar(value=0) + self.options: tk.StringVar = tk.StringVar(value="") + self.option_entry: Optional[ttk.Entry] = None + self.file_entry: Optional[ttk.Entry] = None self.draw() - def draw(self): + def draw(self) -> None: i = 0 frame = ttk.Frame(self.top, padding=FRAME_PAD) frame.columnconfigure(0, weight=1) @@ -63,13 +63,13 @@ class ExecutePythonDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew", padx=PADX) - def add_options(self): + def add_options(self) -> None: if self.with_options.get(): self.option_entry.configure(state="normal") else: self.option_entry.configure(state="disabled") - def select_file(self): + def select_file(self) -> None: file = filedialog.askopenfilename( parent=self.top, initialdir=str(SCRIPT_PATH), @@ -80,7 +80,7 @@ class ExecutePythonDialog(Dialog): self.file_entry.delete(0, "end") self.file_entry.insert("end", file) - def script_execute(self): + def script_execute(self) -> None: file = self.file_entry.get() options = self.option_entry.get() logging.info("Execute %s with options %s", file, options) diff --git a/daemon/core/gui/dialogs/find.py b/daemon/core/gui/dialogs/find.py index 25da4b19..328f673e 100644 --- a/daemon/core/gui/dialogs/find.py +++ b/daemon/core/gui/dialogs/find.py @@ -1,7 +1,7 @@ import logging import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY @@ -13,8 +13,8 @@ if TYPE_CHECKING: class FindDialog(Dialog): def __init__(self, app: "Application") -> None: super().__init__(app, "Find", modal=False) - self.find_text = tk.StringVar(value="") - self.tree = None + self.find_text: tk.StringVar = tk.StringVar(value="") + self.tree: Optional[ttk.Treeview] = None self.draw() self.protocol("WM_DELETE_WINDOW", self.close_dialog) self.bind("", self.find_node) diff --git a/daemon/core/gui/dialogs/hooks.py b/daemon/core/gui/dialogs/hooks.py index 5895a2e1..08d666ba 100644 --- a/daemon/core/gui/dialogs/hooks.py +++ b/daemon/core/gui/dialogs/hooks.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.api.grpc import core_pb2 from core.gui.dialogs.dialog import Dialog @@ -12,15 +12,15 @@ if TYPE_CHECKING: class HookDialog(Dialog): - def __init__(self, master: tk.BaseWidget, app: "Application"): + def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(app, "Hook", master=master) - self.name = tk.StringVar() - self.codetext = None - self.hook = core_pb2.Hook() - self.state = tk.StringVar() + self.name: tk.StringVar = tk.StringVar() + self.codetext: Optional[CodeText] = None + self.hook: core_pb2.Hook = core_pb2.Hook() + self.state: tk.StringVar = tk.StringVar() self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(1, weight=1) @@ -66,11 +66,11 @@ class HookDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy()) button.grid(row=0, column=1, sticky="ew") - def state_change(self, event: tk.Event): + def state_change(self, event: tk.Event) -> None: state_name = self.state.get() self.name.set(f"{state_name.lower()}_hook.sh") - def set(self, hook: core_pb2.Hook): + def set(self, hook: core_pb2.Hook) -> None: self.hook = hook self.name.set(hook.file) self.codetext.text.delete(1.0, tk.END) @@ -78,7 +78,7 @@ class HookDialog(Dialog): state_name = core_pb2.SessionState.Enum.Name(hook.state) self.state.set(state_name) - def save(self): + def save(self) -> None: data = self.codetext.text.get("1.0", tk.END).strip() state_value = core_pb2.SessionState.Enum.Value(self.state.get()) self.hook.file = self.name.get() @@ -88,15 +88,15 @@ class HookDialog(Dialog): class HooksDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Hooks") - self.listbox = None - self.edit_button = None - self.delete_button = None - self.selected = None + self.listbox: Optional[tk.Listbox] = None + self.edit_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None + self.selected: Optional[str] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -124,7 +124,7 @@ class HooksDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=lambda: self.destroy()) button.grid(row=0, column=3, sticky="ew") - def click_create(self): + def click_create(self) -> None: dialog = HookDialog(self, self.app) dialog.show() hook = dialog.hook @@ -132,19 +132,19 @@ class HooksDialog(Dialog): self.app.core.hooks[hook.file] = hook self.listbox.insert(tk.END, hook.file) - def click_edit(self): + def click_edit(self) -> None: hook = self.app.core.hooks[self.selected] dialog = HookDialog(self, self.app) dialog.set(hook) dialog.show() - def click_delete(self): + def click_delete(self) -> None: del self.app.core.hooks[self.selected] self.listbox.delete(tk.ANCHOR) self.edit_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED) - def select(self, event: tk.Event): + def select(self, event: tk.Event) -> None: if self.listbox.curselection(): index = self.listbox.curselection()[0] self.selected = self.listbox.get(index) diff --git a/daemon/core/gui/dialogs/ipdialog.py b/daemon/core/gui/dialogs/ipdialog.py index d31dcdff..351bfffc 100644 --- a/daemon/core/gui/dialogs/ipdialog.py +++ b/daemon/core/gui/dialogs/ipdialog.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List, Optional import netaddr @@ -15,14 +15,14 @@ if TYPE_CHECKING: class IpConfigDialog(Dialog): def __init__(self, app: "Application") -> None: super().__init__(app, "IP Configuration") - self.ip4 = self.app.guiconfig.ips.ip4 - self.ip6 = self.app.guiconfig.ips.ip6 - self.ip4s = self.app.guiconfig.ips.ip4s - self.ip6s = self.app.guiconfig.ips.ip6s - self.ip4_entry = None - self.ip4_listbox = None - self.ip6_entry = None - self.ip6_listbox = None + self.ip4: str = self.app.guiconfig.ips.ip4 + self.ip6: str = self.app.guiconfig.ips.ip6 + self.ip4s: List[str] = self.app.guiconfig.ips.ip4s + self.ip6s: List[str] = self.app.guiconfig.ips.ip6s + self.ip4_entry: Optional[ttk.Entry] = None + self.ip4_listbox: Optional[ListboxScroll] = None + self.ip6_entry: Optional[ttk.Entry] = None + self.ip6_listbox: Optional[ListboxScroll] = None self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index adf8156f..b7c618a3 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -3,7 +3,7 @@ link configuration """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, Union +from typing import TYPE_CHECKING, Optional from core.api.grpc import core_pb2 from core.gui import validation @@ -16,7 +16,7 @@ if TYPE_CHECKING: from core.gui.graph.graph import CanvasEdge -def get_int(var: tk.StringVar) -> Union[int, None]: +def get_int(var: tk.StringVar) -> Optional[int]: value = var.get() if value != "": return int(value) @@ -24,7 +24,7 @@ def get_int(var: tk.StringVar) -> Union[int, None]: return None -def get_float(var: tk.StringVar) -> Union[float, None]: +def get_float(var: tk.StringVar) -> Optional[float]: value = var.get() if value != "": return float(value) @@ -33,38 +33,39 @@ def get_float(var: tk.StringVar) -> Union[float, None]: class LinkConfigurationDialog(Dialog): - def __init__(self, app: "Application", edge: "CanvasEdge"): + def __init__(self, app: "Application", edge: "CanvasEdge") -> None: super().__init__(app, "Link Configuration") - self.edge = edge - self.is_symmetric = edge.link.options.unidirectional is False + self.edge: "CanvasEdge" = edge + self.is_symmetric: bool = edge.link.options.unidirectional is False if self.is_symmetric: - self.symmetry_var = tk.StringVar(value=">>") + symmetry_var = tk.StringVar(value=">>") else: - self.symmetry_var = tk.StringVar(value="<<") + symmetry_var = tk.StringVar(value="<<") + self.symmetry_var: tk.StringVar = symmetry_var - self.bandwidth = tk.StringVar() - self.delay = tk.StringVar() - self.jitter = tk.StringVar() - self.loss = tk.StringVar() - self.duplicate = tk.StringVar() + self.bandwidth: tk.StringVar = tk.StringVar() + self.delay: tk.StringVar = tk.StringVar() + self.jitter: tk.StringVar = tk.StringVar() + self.loss: tk.StringVar = tk.StringVar() + self.duplicate: tk.StringVar = tk.StringVar() - self.down_bandwidth = tk.StringVar() - self.down_delay = tk.StringVar() - self.down_jitter = tk.StringVar() - self.down_loss = tk.StringVar() - self.down_duplicate = tk.StringVar() + self.down_bandwidth: tk.StringVar = tk.StringVar() + self.down_delay: tk.StringVar = tk.StringVar() + self.down_jitter: tk.StringVar = tk.StringVar() + self.down_loss: tk.StringVar = tk.StringVar() + self.down_duplicate: tk.StringVar = tk.StringVar() - self.color = tk.StringVar(value="#000000") - self.color_button = None - self.width = tk.DoubleVar() + self.color: tk.StringVar = tk.StringVar(value="#000000") + self.color_button: Optional[tk.Button] = None + self.width: tk.DoubleVar = tk.DoubleVar() self.load_link_config() - self.symmetric_frame = None - self.asymmetric_frame = None + self.symmetric_frame: Optional[ttk.Frame] = None + self.asymmetric_frame: Optional[ttk.Frame] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) source_name = self.app.canvas.nodes[self.edge.src].core_node.name dest_name = self.app.canvas.nodes[self.edge.dst].core_node.name @@ -207,13 +208,13 @@ class LinkConfigurationDialog(Dialog): return frame - def click_color(self): + def click_color(self) -> None: dialog = ColorPickerDialog(self, self.app, self.color.get()) color = dialog.askcolor() self.color.set(color) self.color_button.config(background=color) - def click_apply(self): + def click_apply(self) -> None: self.app.canvas.itemconfigure(self.edge.id, width=self.width.get()) self.app.canvas.itemconfigure(self.edge.id, fill=self.color.get()) link = self.edge.link @@ -288,7 +289,7 @@ class LinkConfigurationDialog(Dialog): self.destroy() - def change_symmetry(self): + def change_symmetry(self) -> None: if self.is_symmetric: self.is_symmetric = False self.symmetry_var.set("<<") @@ -304,7 +305,7 @@ class LinkConfigurationDialog(Dialog): self.asymmetric_frame.grid_forget() self.symmetric_frame.grid(row=2, column=0) - def load_link_config(self): + def load_link_config(self) -> None: """ populate link config to the table """ diff --git a/daemon/core/gui/dialogs/macdialog.py b/daemon/core/gui/dialogs/macdialog.py index 46414cf9..4d89439b 100644 --- a/daemon/core/gui/dialogs/macdialog.py +++ b/daemon/core/gui/dialogs/macdialog.py @@ -15,7 +15,7 @@ class MacConfigDialog(Dialog): def __init__(self, app: "Application") -> None: super().__init__(app, "MAC Configuration") mac = self.app.guiconfig.mac - self.mac_var = tk.StringVar(value=mac) + self.mac_var: tk.StringVar = tk.StringVar(value=mac) self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/dialogs/mobilityconfig.py b/daemon/core/gui/dialogs/mobilityconfig.py index dced5e44..daaf9ea5 100644 --- a/daemon/core/gui/dialogs/mobilityconfig.py +++ b/daemon/core/gui/dialogs/mobilityconfig.py @@ -2,10 +2,12 @@ mobility configuration """ from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Node from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame @@ -16,23 +18,24 @@ if TYPE_CHECKING: class MobilityConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: super().__init__(app, f"{canvas_node.core_node.name} Mobility Configuration") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.config_frame = None - self.has_error = False + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.config_frame: Optional[ConfigFrame] = None + self.has_error: bool = False try: - self.config = self.canvas_node.mobility_config - if not self.config: - self.config = self.app.core.get_mobility_config(self.node.id) + config = self.canvas_node.mobility_config + if not config: + config = self.app.core.get_mobility_config(self.node.id) + self.config: Dict[str, ConfigOption] = config self.draw() except grpc.RpcError as e: self.app.show_grpc_exception("Get Mobility Config Error", e) - self.has_error = True + self.has_error: bool = True self.destroy() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.config_frame = ConfigFrame(self.top, self.app, self.config) @@ -40,7 +43,7 @@ class MobilityConfigDialog(Dialog): self.config_frame.grid(sticky="nsew", pady=PADY) self.draw_apply_buttons() - def draw_apply_buttons(self): + def draw_apply_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -52,7 +55,7 @@ class MobilityConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: self.config_frame.parse_config() self.canvas_node.mobility_config = self.config self.destroy() diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index b4801bcf..e6ef62ea 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -1,9 +1,11 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Node from core.api.grpc.mobility_pb2 import MobilityAction from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum @@ -13,18 +15,23 @@ if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.node import CanvasNode -ICON_SIZE = 16 +ICON_SIZE: int = 16 class MobilityPlayer: - def __init__(self, app: "Application", canvas_node: "CanvasNode", config): - self.app = app - self.canvas_node = canvas_node - self.config = config - self.dialog = None - self.state = None + def __init__( + self, + app: "Application", + canvas_node: "CanvasNode", + config: Dict[str, ConfigOption], + ) -> None: + self.app: "Application" = app + self.canvas_node: "CanvasNode" = canvas_node + self.config: Dict[str, ConfigOption] = config + self.dialog: Optional[MobilityPlayerDialog] = None + self.state: Optional[MobilityAction] = None - def show(self): + def show(self) -> None: if self.dialog: self.dialog.destroy() self.dialog = MobilityPlayerDialog(self.app, self.canvas_node, self.config) @@ -37,44 +44,49 @@ class MobilityPlayer: self.set_stop() self.dialog.show() - def close(self): + def close(self) -> None: if self.dialog: self.dialog.destroy() self.dialog = None - def set_play(self): + def set_play(self) -> None: self.state = MobilityAction.START if self.dialog: self.dialog.set_play() - def set_pause(self): + def set_pause(self) -> None: self.state = MobilityAction.PAUSE if self.dialog: self.dialog.set_pause() - def set_stop(self): + def set_stop(self) -> None: self.state = MobilityAction.STOP if self.dialog: self.dialog.set_stop() class MobilityPlayerDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode", config): + def __init__( + self, + app: "Application", + canvas_node: "CanvasNode", + config: Dict[str, ConfigOption], + ) -> None: super().__init__( app, f"{canvas_node.core_node.name} Mobility Player", modal=False ) self.resizable(False, False) self.geometry("") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.config = config - self.play_button = None - self.pause_button = None - self.stop_button = None - self.progressbar = None + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.config: Dict[str, ConfigOption] = config + self.play_button: Optional[ttk.Button] = None + self.pause_button: Optional[ttk.Button] = None + self.stop_button: Optional[ttk.Button] = None + self.progressbar: Optional[ttk.Progressbar] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) file_name = self.config["file"].value @@ -114,27 +126,27 @@ class MobilityPlayerDialog(Dialog): label = ttk.Label(frame, text=f"rate {rate} ms") label.grid(row=0, column=4) - def clear_buttons(self): + def clear_buttons(self) -> None: self.play_button.state(["!pressed"]) self.pause_button.state(["!pressed"]) self.stop_button.state(["!pressed"]) - def set_play(self): + def set_play(self) -> None: self.clear_buttons() self.play_button.state(["pressed"]) self.progressbar.start() - def set_pause(self): + def set_pause(self) -> None: self.clear_buttons() self.pause_button.state(["pressed"]) self.progressbar.stop() - def set_stop(self): + def set_stop(self) -> None: self.clear_buttons() self.stop_button.state(["pressed"]) self.progressbar.stop() - def click_play(self): + def click_play(self) -> None: self.set_play() session_id = self.app.core.session_id try: @@ -144,7 +156,7 @@ class MobilityPlayerDialog(Dialog): except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) - def click_pause(self): + def click_pause(self) -> None: self.set_pause() session_id = self.app.core.session_id try: @@ -154,7 +166,7 @@ class MobilityPlayerDialog(Dialog): except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) - def click_stop(self): + def click_stop(self) -> None: self.set_stop() session_id = self.app.core.session_id try: diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index cec9e9f9..9e958283 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -2,10 +2,12 @@ import logging import tkinter as tk from functools import partial from tkinter import messagebox, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import netaddr +from PIL.ImageTk import PhotoImage +from core.api.grpc.core_pb2 import Node from core.gui import nodeutils, validation from core.gui.appconfig import ICONS_PATH from core.gui.dialogs.dialog import Dialog @@ -86,35 +88,35 @@ class InterfaceData: mac: tk.StringVar, ip4: tk.StringVar, ip6: tk.StringVar, - ): - self.is_auto = is_auto - self.mac = mac - self.ip4 = ip4 - self.ip6 = ip6 + ) -> None: + self.is_auto: tk.BooleanVar = is_auto + self.mac: tk.StringVar = mac + self.ip4: tk.StringVar = ip4 + self.ip6: tk.StringVar = ip6 class NodeConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: """ create an instance of node configuration """ super().__init__(app, f"{canvas_node.core_node.name} Configuration") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.image = canvas_node.image - self.image_file = None - self.image_button = None - self.name = tk.StringVar(value=self.node.name) - self.type = tk.StringVar(value=self.node.model) - self.container_image = tk.StringVar(value=self.node.image) + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.image: PhotoImage = canvas_node.image + self.image_file: Optional[str] = None + self.image_button: Optional[ttk.Button] = None + self.name: tk.StringVar = tk.StringVar(value=self.node.name) + self.type: tk.StringVar = tk.StringVar(value=self.node.model) + self.container_image: tk.StringVar = tk.StringVar(value=self.node.image) server = "localhost" if self.node.server: server = self.node.server - self.server = tk.StringVar(value=server) - self.ifaces = {} + self.server: tk.StringVar = tk.StringVar(value=server) + self.ifaces: Dict[int, InterfaceData] = {} self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) row = 0 @@ -202,7 +204,7 @@ class NodeConfigDialog(Dialog): self.draw_spacer() self.draw_buttons() - def draw_ifaces(self): + def draw_ifaces(self) -> None: notebook = ttk.Notebook(self.top) notebook.grid(sticky="nsew", pady=PADY) self.top.rowconfigure(notebook.grid_info()["row"], weight=1) @@ -265,7 +267,7 @@ class NodeConfigDialog(Dialog): self.ifaces[iface.id] = InterfaceData(is_auto, mac, ip4, ip6) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") frame.columnconfigure(0, weight=1) @@ -277,20 +279,20 @@ class NodeConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_emane_config(self, emane_model: str, iface_id: int): + def click_emane_config(self, emane_model: str, iface_id: int) -> None: dialog = EmaneModelDialog( self, self.app, self.canvas_node, emane_model, iface_id ) dialog.show() - def click_icon(self): + def click_icon(self) -> None: file_path = image_chooser(self, ICONS_PATH) if file_path: self.image = Images.create(file_path, nodeutils.ICON_SIZE) self.image_button.config(image=self.image) self.image_file = file_path - def click_apply(self): + def click_apply(self) -> None: error = False # update core node @@ -354,7 +356,7 @@ class NodeConfigDialog(Dialog): self.canvas_node.redraw() self.destroy() - def iface_select(self, event: tk.Event): + def iface_select(self, event: tk.Event) -> None: listbox = event.widget cur = listbox.curselection() if cur: diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index 5f77ece3..b5250eba 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -4,7 +4,7 @@ core node services import logging import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING, Set +from typing import TYPE_CHECKING, Optional, Set from core.gui.dialogs.configserviceconfig import ConfigServiceConfigDialog from core.gui.dialogs.dialog import Dialog @@ -19,20 +19,20 @@ if TYPE_CHECKING: class NodeConfigServiceDialog(Dialog): def __init__( self, app: "Application", canvas_node: "CanvasNode", services: Set[str] = None - ): + ) -> None: title = f"{canvas_node.core_node.name} Config Services" super().__init__(app, title) - self.canvas_node = canvas_node - self.node_id = canvas_node.core_node.id - self.groups = None - self.services = None - self.current = None + self.canvas_node: "CanvasNode" = canvas_node + self.node_id: int = canvas_node.core_node.id + self.groups: Optional[ListboxScroll] = None + self.services: Optional[CheckboxList] = None + self.current: Optional[ListboxScroll] = None if services is None: services = set(canvas_node.core_node.config_services) - self.current_services = services + self.current_services: Set[str] = services self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -86,7 +86,7 @@ class NodeConfigServiceDialog(Dialog): # trigger group change self.groups.listbox.event_generate("<>") - def handle_group_change(self, event: tk.Event = None): + def handle_group_change(self, event: tk.Event = None) -> None: selection = self.groups.listbox.curselection() if selection: index = selection[0] @@ -96,7 +96,7 @@ class NodeConfigServiceDialog(Dialog): checked = name in self.current_services self.services.add(name, checked) - def service_clicked(self, name: str, var: tk.IntVar): + def service_clicked(self, name: str, var: tk.IntVar) -> None: if var.get() and name not in self.current_services: self.current_services.add(name) elif not var.get() and name in self.current_services: @@ -104,7 +104,7 @@ class NodeConfigServiceDialog(Dialog): self.draw_current_services() self.canvas_node.core_node.config_services[:] = self.current_services - def click_configure(self): + def click_configure(self) -> None: current_selection = self.current.listbox.curselection() if len(current_selection): dialog = ConfigServiceConfigDialog( @@ -124,25 +124,25 @@ class NodeConfigServiceDialog(Dialog): parent=self, ) - def draw_current_services(self): + def draw_current_services(self) -> None: self.current.listbox.delete(0, tk.END) for name in sorted(self.current_services): self.current.listbox.insert(tk.END, name) if self.is_custom_service(name): self.current.listbox.itemconfig(tk.END, bg="green") - def click_save(self): + def click_save(self) -> None: self.canvas_node.core_node.config_services[:] = self.current_services logging.info( "saved node config services: %s", self.canvas_node.core_node.config_services ) self.destroy() - def click_cancel(self): + def click_cancel(self) -> None: self.current_services = None self.destroy() - def click_remove(self): + def click_remove(self) -> None: cur = self.current.listbox.curselection() if cur: service = self.current.listbox.get(cur[0]) diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index 13490d8c..f6f5e5cf 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -3,7 +3,7 @@ core node services """ import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional, Set from core.gui.dialogs.dialog import Dialog from core.gui.dialogs.serviceconfig import ServiceConfigDialog @@ -16,19 +16,19 @@ if TYPE_CHECKING: class NodeServiceDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: title = f"{canvas_node.core_node.name} Services" super().__init__(app, title) - self.canvas_node = canvas_node - self.node_id = canvas_node.core_node.id - self.groups = None - self.services = None - self.current = None + self.canvas_node: "CanvasNode" = canvas_node + self.node_id: int = canvas_node.core_node.id + self.groups: Optional[ListboxScroll] = None + self.services: Optional[CheckboxList] = None + self.current: Optional[ListboxScroll] = None services = set(canvas_node.core_node.services) - self.current_services = services + self.current_services: Set[str] = services self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -84,7 +84,7 @@ class NodeServiceDialog(Dialog): # trigger group change self.groups.listbox.event_generate("<>") - def handle_group_change(self, event: tk.Event = None): + def handle_group_change(self, event: tk.Event = None) -> None: selection = self.groups.listbox.curselection() if selection: index = selection[0] @@ -94,7 +94,7 @@ class NodeServiceDialog(Dialog): checked = name in self.current_services self.services.add(name, checked) - def service_clicked(self, name: str, var: tk.IntVar): + def service_clicked(self, name: str, var: tk.IntVar) -> None: if var.get() and name not in self.current_services: self.current_services.add(name) elif not var.get() and name in self.current_services: @@ -106,7 +106,7 @@ class NodeServiceDialog(Dialog): self.current.listbox.itemconfig(tk.END, bg="green") self.canvas_node.core_node.services[:] = self.current_services - def click_configure(self): + def click_configure(self) -> None: current_selection = self.current.listbox.curselection() if len(current_selection): dialog = ServiceConfigDialog( @@ -127,12 +127,12 @@ class NodeServiceDialog(Dialog): "Service Configuration", "Select a service to configure", parent=self ) - def click_save(self): + def click_save(self) -> None: core_node = self.canvas_node.core_node core_node.services[:] = self.current_services self.destroy() - def click_remove(self): + def click_remove(self) -> None: cur = self.current.listbox.curselection() if cur: service = self.current.listbox.get(cur[0]) diff --git a/daemon/core/gui/dialogs/observers.py b/daemon/core/gui/dialogs/observers.py index d1812b64..286fc2c9 100644 --- a/daemon/core/gui/dialogs/observers.py +++ b/daemon/core/gui/dialogs/observers.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.appconfig import Observer from core.gui.dialogs.dialog import Dialog @@ -12,18 +12,18 @@ if TYPE_CHECKING: class ObserverDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Observer Widgets") - self.observers = None - self.save_button = None - self.delete_button = None - self.selected = None - self.selected_index = None - self.name = tk.StringVar() - self.cmd = tk.StringVar() + self.observers: Optional[tk.Listbox] = None + self.save_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None + self.selected: Optional[str] = None + self.selected_index: Optional[int] = None + self.name: tk.StringVar = tk.StringVar() + self.cmd: tk.StringVar = tk.StringVar() self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.draw_listbox() @@ -31,7 +31,7 @@ class ObserverDialog(Dialog): self.draw_config_buttons() self.draw_apply_buttons() - def draw_listbox(self): + def draw_listbox(self) -> None: listbox_scroll = ListboxScroll(self.top) listbox_scroll.grid(sticky="nsew", pady=PADY) listbox_scroll.columnconfigure(0, weight=1) @@ -42,7 +42,7 @@ class ObserverDialog(Dialog): for name in sorted(self.app.core.custom_observers): self.observers.insert(tk.END, name) - def draw_form_fields(self): + def draw_form_fields(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew", pady=PADY) frame.columnconfigure(1, weight=1) @@ -57,7 +57,7 @@ class ObserverDialog(Dialog): entry = ttk.Entry(frame, textvariable=self.cmd) entry.grid(row=1, column=1, sticky="ew") - def draw_config_buttons(self): + def draw_config_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew", pady=PADY) for i in range(3): @@ -76,7 +76,7 @@ class ObserverDialog(Dialog): ) self.delete_button.grid(row=0, column=2, sticky="ew") - def draw_apply_buttons(self): + def draw_apply_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -88,14 +88,14 @@ class ObserverDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_save_config(self): + def click_save_config(self) -> None: self.app.guiconfig.observers.clear() for observer in self.app.core.custom_observers.values(): self.app.guiconfig.observers.append(observer) self.app.save_config() self.destroy() - def click_create(self): + def click_create(self) -> None: name = self.name.get() if name not in self.app.core.custom_observers: cmd = self.cmd.get() @@ -109,7 +109,7 @@ class ObserverDialog(Dialog): else: messagebox.showerror("Observer Error", f"{name} already exists") - def click_save(self): + def click_save(self) -> None: name = self.name.get() if self.selected: previous_name = self.selected @@ -122,7 +122,7 @@ class ObserverDialog(Dialog): self.observers.insert(self.selected_index, name) self.observers.selection_set(self.selected_index) - def click_delete(self): + def click_delete(self) -> None: if self.selected: self.observers.delete(self.selected_index) del self.app.core.custom_observers[self.selected] @@ -136,7 +136,7 @@ class ObserverDialog(Dialog): self.app.menubar.observers_menu.draw_custom() self.app.toolbar.observers_menu.draw_custom() - def handle_observer_change(self, event: tk.Event): + def handle_observer_change(self, event: tk.Event) -> None: selection = self.observers.curselection() if selection: self.selected_index = selection[0] diff --git a/daemon/core/gui/dialogs/preferences.py b/daemon/core/gui/dialogs/preferences.py index 11d1ba95..839ebd3b 100644 --- a/daemon/core/gui/dialogs/preferences.py +++ b/daemon/core/gui/dialogs/preferences.py @@ -12,27 +12,27 @@ from core.gui.validation import LARGEST_SCALE, SMALLEST_SCALE if TYPE_CHECKING: from core.gui.app import Application -SCALE_INTERVAL = 0.01 +SCALE_INTERVAL: float = 0.01 class PreferencesDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Preferences") - self.gui_scale = tk.DoubleVar(value=self.app.app_scale) + self.gui_scale: tk.DoubleVar = tk.DoubleVar(value=self.app.app_scale) preferences = self.app.guiconfig.preferences - self.editor = tk.StringVar(value=preferences.editor) - self.theme = tk.StringVar(value=preferences.theme) - self.terminal = tk.StringVar(value=preferences.terminal) - self.gui3d = tk.StringVar(value=preferences.gui3d) + self.editor: tk.StringVar = tk.StringVar(value=preferences.editor) + self.theme: tk.StringVar = tk.StringVar(value=preferences.theme) + self.terminal: tk.StringVar = tk.StringVar(value=preferences.terminal) + self.gui3d: tk.StringVar = tk.StringVar(value=preferences.gui3d) self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.draw_preferences() self.draw_buttons() - def draw_preferences(self): + def draw_preferences(self) -> None: frame = ttk.LabelFrame(self.top, text="Preferences", padding=FRAME_PAD) frame.grid(sticky="nsew", pady=PADY) frame.columnconfigure(1, weight=1) @@ -88,7 +88,7 @@ class PreferencesDialog(Dialog): scrollbar = ttk.Scrollbar(scale_frame, command=self.adjust_scale) scrollbar.grid(row=0, column=2) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -100,12 +100,12 @@ class PreferencesDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def theme_change(self, event: tk.Event): + def theme_change(self, event: tk.Event) -> None: theme = self.theme.get() logging.info("changing theme: %s", theme) self.app.style.theme_use(theme) - def click_save(self): + def click_save(self) -> None: preferences = self.app.guiconfig.preferences preferences.terminal = self.terminal.get() preferences.editor = self.editor.get() @@ -118,7 +118,7 @@ class PreferencesDialog(Dialog): self.scale_adjust() self.destroy() - def scale_adjust(self): + def scale_adjust(self) -> None: app_scale = self.gui_scale.get() self.app.app_scale = app_scale self.app.master.tk.call("tk", "scaling", app_scale) @@ -136,7 +136,7 @@ class PreferencesDialog(Dialog): self.app.toolbar.scale() self.app.canvas.scale_graph() - def adjust_scale(self, arg1: str, arg2: str, arg3: str): + def adjust_scale(self, arg1: str, arg2: str, arg3: str) -> None: scale_value = self.gui_scale.get() if arg2 == "-1": if scale_value <= LARGEST_SCALE - SCALE_INTERVAL: diff --git a/daemon/core/gui/dialogs/runtool.py b/daemon/core/gui/dialogs/runtool.py index 98be730f..c66fea8f 100644 --- a/daemon/core/gui/dialogs/runtool.py +++ b/daemon/core/gui/dialogs/runtool.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional from core.gui.dialogs.dialog import Dialog from core.gui.nodeutils import NodeUtils @@ -14,10 +14,10 @@ if TYPE_CHECKING: class RunToolDialog(Dialog): def __init__(self, app: "Application") -> None: super().__init__(app, "Run Tool") - self.cmd = tk.StringVar(value="ps ax") - self.result = None - self.node_list = None - self.executable_nodes = {} + self.cmd: tk.StringVar = tk.StringVar(value="ps ax") + self.result: Optional[CodeText] = None + self.node_list: Optional[ListboxScroll] = None + self.executable_nodes: Dict[str, int] = {} self.store_nodes() self.draw() diff --git a/daemon/core/gui/dialogs/servers.py b/daemon/core/gui/dialogs/servers.py index 7ca96e9f..45121a20 100644 --- a/daemon/core/gui/dialogs/servers.py +++ b/daemon/core/gui/dialogs/servers.py @@ -1,6 +1,6 @@ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.appconfig import CoreServer from core.gui.dialogs.dialog import Dialog @@ -10,24 +10,24 @@ from core.gui.widgets import ListboxScroll if TYPE_CHECKING: from core.gui.app import Application -DEFAULT_NAME = "example" -DEFAULT_ADDRESS = "127.0.0.1" -DEFAULT_PORT = 50051 +DEFAULT_NAME: str = "example" +DEFAULT_ADDRESS: str = "127.0.0.1" +DEFAULT_PORT: int = 50051 class ServersDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "CORE Servers") - self.name = tk.StringVar(value=DEFAULT_NAME) - self.address = tk.StringVar(value=DEFAULT_ADDRESS) - self.servers = None - self.selected_index = None - self.selected = None - self.save_button = None - self.delete_button = None + self.name: tk.StringVar = tk.StringVar(value=DEFAULT_NAME) + self.address: tk.StringVar = tk.StringVar(value=DEFAULT_ADDRESS) + self.servers: Optional[tk.Listbox] = None + self.selected_index: Optional[int] = None + self.selected: Optional[str] = None + self.save_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.draw_servers() @@ -35,7 +35,7 @@ class ServersDialog(Dialog): self.draw_server_configuration() self.draw_apply_buttons() - def draw_servers(self): + def draw_servers(self) -> None: listbox_scroll = ListboxScroll(self.top) listbox_scroll.grid(pady=PADY, sticky="nsew") listbox_scroll.columnconfigure(0, weight=1) @@ -48,7 +48,7 @@ class ServersDialog(Dialog): for server in self.app.core.servers: self.servers.insert(tk.END, server) - def draw_server_configuration(self): + def draw_server_configuration(self) -> None: frame = ttk.LabelFrame(self.top, text="Server Configuration", padding=FRAME_PAD) frame.grid(pady=PADY, sticky="ew") frame.columnconfigure(1, weight=1) @@ -64,7 +64,7 @@ class ServersDialog(Dialog): entry = ttk.Entry(frame, textvariable=self.address) entry.grid(row=0, column=3, sticky="ew") - def draw_servers_buttons(self): + def draw_servers_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(pady=PADY, sticky="ew") for i in range(3): @@ -83,7 +83,7 @@ class ServersDialog(Dialog): ) self.delete_button.grid(row=0, column=2, sticky="ew") - def draw_apply_buttons(self): + def draw_apply_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(2): @@ -104,7 +104,7 @@ class ServersDialog(Dialog): self.app.save_config() self.destroy() - def click_create(self): + def click_create(self) -> None: name = self.name.get() if name not in self.app.core.servers: address = self.address.get() @@ -112,7 +112,7 @@ class ServersDialog(Dialog): self.app.core.servers[name] = server self.servers.insert(tk.END, name) - def click_save(self): + def click_save(self) -> None: name = self.name.get() if self.selected: previous_name = self.selected @@ -125,7 +125,7 @@ class ServersDialog(Dialog): self.servers.insert(self.selected_index, name) self.servers.selection_set(self.selected_index) - def click_delete(self): + def click_delete(self) -> None: if self.selected: self.servers.delete(self.selected_index) del self.app.core.servers[self.selected] @@ -137,7 +137,7 @@ class ServersDialog(Dialog): self.save_button.config(state=tk.DISABLED) self.delete_button.config(state=tk.DISABLED) - def handle_server_change(self, event: tk.Event): + def handle_server_change(self, event: tk.Event) -> None: selection = self.servers.curselection() if selection: self.selected_index = selection[0] diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index efeefa09..5faface7 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -2,11 +2,12 @@ import logging import os import tkinter as tk from tkinter import filedialog, ttk -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple import grpc +from PIL.ImageTk import PhotoImage -from core.api.grpc.services_pb2 import ServiceValidationMode +from core.api.grpc.services_pb2 import NodeServiceData, ServiceValidationMode from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images @@ -16,8 +17,9 @@ from core.gui.widgets import CodeText, ListboxScroll if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.node import CanvasNode + from core.gui.coreclient import CoreClient -ICON_SIZE = 16 +ICON_SIZE: int = 16 class ServiceConfigDialog(Dialog): @@ -28,54 +30,57 @@ class ServiceConfigDialog(Dialog): service_name: str, canvas_node: "CanvasNode", node_id: int, - ): + ) -> None: title = f"{service_name} Service" super().__init__(app, title, master=master) - self.core = app.core - self.canvas_node = canvas_node - self.node_id = node_id - self.service_name = service_name - self.radiovar = tk.IntVar() - self.radiovar.set(2) - self.metadata = "" - self.filenames = [] - self.dependencies = [] - self.executables = [] - self.startup_commands = [] - self.validation_commands = [] - self.shutdown_commands = [] - self.default_startup = [] - self.default_validate = [] - self.default_shutdown = [] - self.validation_mode = None - self.validation_time = None - self.validation_period = None - self.directory_entry = None - self.default_directories = [] - self.temp_directories = [] - self.documentnew_img = self.app.get_icon(ImageEnum.DOCUMENTNEW, ICON_SIZE) - self.editdelete_img = self.app.get_icon(ImageEnum.EDITDELETE, ICON_SIZE) - self.notebook = None - self.metadata_entry = None - self.filename_combobox = None - self.dir_list = None - self.startup_commands_listbox = None - self.shutdown_commands_listbox = None - self.validate_commands_listbox = None - self.validation_time_entry = None - self.validation_mode_entry = None - self.service_file_data = None - self.validation_period_entry = None - self.original_service_files = {} - self.default_config = None - self.temp_service_files = {} - self.modified_files = set() - self.has_error = False + self.core: "CoreClient" = app.core + self.canvas_node: "CanvasNode" = canvas_node + self.node_id: int = node_id + self.service_name: str = service_name + self.radiovar: tk.IntVar = tk.IntVar(value=2) + self.metadata: str = "" + self.filenames: List[str] = [] + self.dependencies: List[str] = [] + self.executables: List[str] = [] + self.startup_commands: List[str] = [] + self.validation_commands: List[str] = [] + self.shutdown_commands: List[str] = [] + self.default_startup: List[str] = [] + self.default_validate: List[str] = [] + self.default_shutdown: List[str] = [] + self.validation_mode: Optional[ServiceValidationMode] = None + self.validation_time: Optional[int] = None + self.validation_period: Optional[float] = None + self.directory_entry: Optional[ttk.Entry] = None + self.default_directories: List[str] = [] + self.temp_directories: List[str] = [] + self.documentnew_img: PhotoImage = self.app.get_icon( + ImageEnum.DOCUMENTNEW, ICON_SIZE + ) + self.editdelete_img: PhotoImage = self.app.get_icon( + ImageEnum.EDITDELETE, ICON_SIZE + ) + self.notebook: Optional[ttk.Notebook] = None + self.metadata_entry: Optional[ttk.Entry] = None + self.filename_combobox: Optional[ttk.Combobox] = None + self.dir_list: Optional[ListboxScroll] = None + self.startup_commands_listbox: Optional[tk.Listbox] = None + self.shutdown_commands_listbox: Optional[tk.Listbox] = None + self.validate_commands_listbox: Optional[tk.Listbox] = None + self.validation_time_entry: Optional[ttk.Entry] = None + self.validation_mode_entry: Optional[ttk.Entry] = None + self.service_file_data: Optional[CodeText] = None + self.validation_period_entry: Optional[ttk.Entry] = None + self.original_service_files: Dict[str, str] = {} + self.default_config: NodeServiceData = None + self.temp_service_files: Dict[str, str] = {} + self.modified_files: Set[str] = set() + self.has_error: bool = False self.load() if not self.has_error: self.draw() - def load(self): + def load(self) -> None: try: self.app.core.create_nodes_and_links() default_config = self.app.core.get_node_service( @@ -119,7 +124,7 @@ class ServiceConfigDialog(Dialog): self.app.show_grpc_exception("Get Node Service Error", e) self.has_error = True - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(1, weight=1) @@ -142,7 +147,7 @@ class ServiceConfigDialog(Dialog): self.draw_buttons() - def draw_tab_files(self): + def draw_tab_files(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -222,7 +227,7 @@ class ServiceConfigDialog(Dialog): "", self.update_temp_service_file_data ) - def draw_tab_directories(self): + def draw_tab_directories(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -257,7 +262,7 @@ class ServiceConfigDialog(Dialog): button = ttk.Button(frame, text="Remove", command=self.remove_directory) button.grid(row=0, column=1, sticky="ew") - def draw_tab_startstop(self): + def draw_tab_startstop(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -311,7 +316,7 @@ class ServiceConfigDialog(Dialog): elif i == 2: self.validate_commands_listbox = listbox_scroll.listbox - def draw_tab_configuration(self): + def draw_tab_configuration(self) -> None: tab = ttk.Frame(self.notebook, padding=FRAME_PAD) tab.grid(sticky="nsew") tab.columnconfigure(0, weight=1) @@ -370,7 +375,7 @@ class ServiceConfigDialog(Dialog): for dependency in self.dependencies: listbox_scroll.listbox.insert("end", dependency) - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="ew") for i in range(4): @@ -384,7 +389,7 @@ class ServiceConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=3, sticky="ew") - def add_filename(self): + def add_filename(self) -> None: filename = self.filename_combobox.get() if filename not in self.filename_combobox["values"]: self.filename_combobox["values"] += (filename,) @@ -395,7 +400,7 @@ class ServiceConfigDialog(Dialog): else: logging.debug("file already existed") - def delete_filename(self): + def delete_filename(self) -> None: cbb = self.filename_combobox filename = cbb.get() if filename in cbb["values"]: @@ -407,7 +412,7 @@ class ServiceConfigDialog(Dialog): self.modified_files.remove(filename) @classmethod - def add_command(cls, event: tk.Event): + def add_command(cls, event: tk.Event) -> None: frame_contains_button = event.widget.master 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() @@ -419,7 +424,7 @@ class ServiceConfigDialog(Dialog): listbox.insert(tk.END, command_to_add) @classmethod - def update_entry(cls, event: tk.Event): + def update_entry(cls, event: tk.Event) -> None: listbox = event.widget current_selection = listbox.curselection() if len(current_selection) > 0: @@ -431,7 +436,7 @@ class ServiceConfigDialog(Dialog): entry.insert(0, cmd) @classmethod - def delete_command(cls, event: tk.Event): + def delete_command(cls, event: tk.Event) -> None: button = event.widget frame_contains_button = button.master listbox = frame_contains_button.master.grid_slaves(row=1, column=0)[0].listbox @@ -441,7 +446,7 @@ class ServiceConfigDialog(Dialog): entry = frame_contains_button.grid_slaves(row=0, column=0)[0] entry.delete(0, tk.END) - def click_apply(self): + def click_apply(self) -> None: if ( not self.is_custom_command() and not self.is_custom_service_file() @@ -484,12 +489,12 @@ class ServiceConfigDialog(Dialog): self.app.show_grpc_exception("Save Service Config Error", e) self.destroy() - def display_service_file_data(self, event: tk.Event): + def display_service_file_data(self, event: tk.Event) -> None: filename = self.filename_combobox.get() self.service_file_data.text.delete(1.0, "end") self.service_file_data.text.insert("end", self.temp_service_files[filename]) - def update_temp_service_file_data(self, event: tk.Event): + def update_temp_service_file_data(self, event: tk.Event) -> None: filename = self.filename_combobox.get() self.temp_service_files[filename] = self.service_file_data.text.get(1.0, "end") if self.temp_service_files[filename] != self.original_service_files.get( @@ -499,7 +504,7 @@ class ServiceConfigDialog(Dialog): else: self.modified_files.discard(filename) - def is_custom_command(self): + def is_custom_command(self) -> bool: startup, validate, shutdown = self.get_commands() return ( set(self.default_startup) != set(startup) @@ -507,16 +512,16 @@ class ServiceConfigDialog(Dialog): or set(self.default_shutdown) != set(shutdown) ) - def has_new_files(self): + def has_new_files(self) -> bool: return set(self.filenames) != set(self.filename_combobox["values"]) - def is_custom_service_file(self): + def is_custom_service_file(self) -> bool: return len(self.modified_files) > 0 - def is_custom_directory(self): + def is_custom_directory(self) -> bool: return set(self.default_directories) != set(self.dir_list.listbox.get(0, "end")) - def click_defaults(self): + def click_defaults(self) -> None: """ clears out any custom configuration permanently """ @@ -557,37 +562,37 @@ class ServiceConfigDialog(Dialog): self.current_service_color("") - def click_copy(self): + def click_copy(self) -> None: dialog = CopyServiceConfigDialog(self, self.app, self.node_id) dialog.show() @classmethod def append_commands( cls, commands: List[str], listbox: tk.Listbox, to_add: List[str] - ): + ) -> None: for cmd in to_add: commands.append(cmd) listbox.insert(tk.END, cmd) - def get_commands(self): + def get_commands(self) -> Tuple[List[str], List[str], List[str]]: startup = self.startup_commands_listbox.get(0, "end") shutdown = self.shutdown_commands_listbox.get(0, "end") validate = self.validate_commands_listbox.get(0, "end") return startup, validate, shutdown - def find_directory_button(self): + def find_directory_button(self) -> None: d = filedialog.askdirectory(initialdir="/") self.directory_entry.delete(0, "end") self.directory_entry.insert("end", d) - def add_directory(self): + def add_directory(self) -> None: d = self.directory_entry.get() if os.path.isdir(d): if d not in self.temp_directories: self.dir_list.listbox.insert("end", d) self.temp_directories.append(d) - def remove_directory(self): + def remove_directory(self) -> None: d = self.directory_entry.get() dirs = self.dir_list.listbox.get(0, "end") if d and d in self.temp_directories: @@ -599,14 +604,14 @@ class ServiceConfigDialog(Dialog): logging.debug("directory is not in the list") self.directory_entry.delete(0, "end") - def directory_select(self, event): + def directory_select(self, event) -> None: i = self.dir_list.listbox.curselection() if i: d = self.dir_list.listbox.get(i) self.directory_entry.delete(0, "end") self.directory_entry.insert("end", d) - def current_service_color(self, color=""): + def current_service_color(self, color="") -> None: """ change the current service label color """ diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index d31a5fb5..8138d854 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -1,9 +1,10 @@ import logging from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame @@ -13,15 +14,15 @@ if TYPE_CHECKING: class SessionOptionsDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Session Options") - self.config_frame = None - self.has_error = False - self.config = self.get_config() + self.config_frame: Optional[ConfigFrame] = None + self.has_error: bool = False + self.config: Dict[str, ConfigOption] = self.get_config() if not self.has_error: self.draw() - def get_config(self): + def get_config(self) -> Dict[str, ConfigOption]: try: session_id = self.app.core.session_id response = self.app.core.client.get_session_options(session_id) @@ -31,7 +32,7 @@ class SessionOptionsDialog(Dialog): self.has_error = True self.destroy() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) @@ -48,7 +49,7 @@ class SessionOptionsDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def save(self): + def save(self) -> None: config = self.config_frame.parse_config() try: session_id = self.app.core.session_id diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index 9aa71a13..a7d702eb 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -1,11 +1,12 @@ import logging import tkinter as tk from tkinter import messagebox, ttk -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Optional import grpc from core.api.grpc import core_pb2 +from core.api.grpc.core_pb2 import SessionSummary from core.gui.dialogs.dialog import Dialog from core.gui.images import ImageEnum, Images from core.gui.task import ProgressTask @@ -18,17 +19,17 @@ if TYPE_CHECKING: class SessionsDialog(Dialog): def __init__(self, app: "Application", is_start_app: bool = False) -> None: super().__init__(app, "Sessions") - self.is_start_app = is_start_app - self.selected_session = None - self.selected_id = None - self.tree = None - self.sessions = self.get_sessions() - self.connect_button = None - self.delete_button = None + self.is_start_app: bool = is_start_app + self.selected_session: Optional[int] = None + self.selected_id: Optional[int] = None + self.tree: Optional[ttk.Treeview] = None + self.sessions: List[SessionSummary] = self.get_sessions() + self.connect_button: Optional[ttk.Button] = None + self.delete_button: Optional[ttk.Button] = None self.protocol("WM_DELETE_WINDOW", self.on_closing) self.draw() - def get_sessions(self) -> List[core_pb2.SessionSummary]: + def get_sessions(self) -> List[SessionSummary]: try: response = self.app.core.client.get_sessions() logging.info("sessions: %s", response) diff --git a/daemon/core/gui/dialogs/shapemod.py b/daemon/core/gui/dialogs/shapemod.py index 4c84991b..2ca06772 100644 --- a/daemon/core/gui/dialogs/shapemod.py +++ b/daemon/core/gui/dialogs/shapemod.py @@ -3,7 +3,7 @@ shape input dialog """ import tkinter as tk from tkinter import font, ttk -from typing import TYPE_CHECKING, List, Union +from typing import TYPE_CHECKING, List, Optional, Union from core.gui.dialogs.colorpicker import ColorPickerDialog from core.gui.dialogs.dialog import Dialog @@ -13,40 +13,41 @@ from core.gui.themes import FRAME_PAD, PADX, PADY if TYPE_CHECKING: from core.gui.app import Application + from core.gui.graph.graph import CanvasGraph 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] -BORDER_WIDTH = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +FONT_SIZES: List[int] = [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72] +BORDER_WIDTH: List[int] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] class ShapeDialog(Dialog): - def __init__(self, app: "Application", shape: "Shape"): + def __init__(self, app: "Application", shape: "Shape") -> None: if is_draw_shape(shape.shape_type): title = "Add Shape" else: title = "Add Text" super().__init__(app, title) - self.canvas = app.canvas - self.fill = None - self.border = None - self.shape = shape + self.canvas: "CanvasGraph" = app.canvas + self.fill: Optional[ttk.Label] = None + self.border: Optional[ttk.Label] = None + self.shape: "Shape" = shape data = shape.shape_data - self.shape_text = tk.StringVar(value=data.text) - self.font = tk.StringVar(value=data.font) - self.font_size = tk.IntVar(value=data.font_size) - self.text_color = data.text_color + self.shape_text: tk.StringVar = tk.StringVar(value=data.text) + self.font: tk.StringVar = tk.StringVar(value=data.font) + self.font_size: tk.IntVar = tk.IntVar(value=data.font_size) + self.text_color: str = data.text_color fill_color = data.fill_color if not fill_color: fill_color = "#CFCFFF" - self.fill_color = fill_color - self.border_color = data.border_color - self.border_width = tk.IntVar(value=0) - self.bold = tk.BooleanVar(value=data.bold) - self.italic = tk.BooleanVar(value=data.italic) - self.underline = tk.BooleanVar(value=data.underline) + self.fill_color: str = fill_color + self.border_color: str = data.border_color + self.border_width: tk.IntVar = tk.IntVar(value=0) + self.bold: tk.BooleanVar = tk.BooleanVar(value=data.bold) + self.italic: tk.BooleanVar = tk.BooleanVar(value=data.italic) + self.underline: tk.BooleanVar = tk.BooleanVar(value=data.underline) self.draw() - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.draw_label_options() if is_draw_shape(self.shape.shape_type): @@ -54,7 +55,7 @@ class ShapeDialog(Dialog): self.draw_spacer() self.draw_buttons() - def draw_label_options(self): + def draw_label_options(self) -> None: label_frame = ttk.LabelFrame(self.top, text="Label", padding=FRAME_PAD) label_frame.grid(sticky="ew") label_frame.columnconfigure(0, weight=1) @@ -94,7 +95,7 @@ class ShapeDialog(Dialog): button = ttk.Checkbutton(frame, variable=self.underline, text="Underline") button.grid(row=0, column=2, sticky="ew") - def draw_shape_options(self): + def draw_shape_options(self) -> None: label_frame = ttk.LabelFrame(self.top, text="Shape", padding=FRAME_PAD) label_frame.grid(sticky="ew", pady=PADY) label_frame.columnconfigure(0, weight=1) @@ -129,7 +130,7 @@ class ShapeDialog(Dialog): ) combobox.grid(row=0, column=1, sticky="nsew") - def draw_buttons(self): + def draw_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky="nsew") frame.columnconfigure(0, weight=1) @@ -139,28 +140,28 @@ class ShapeDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.cancel) button.grid(row=0, column=1, sticky="ew") - def choose_text_color(self): + def choose_text_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.text_color) self.text_color = color_picker.askcolor() - def choose_fill_color(self): + def choose_fill_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.fill_color) color = color_picker.askcolor() self.fill_color = color self.fill.config(background=color, text=color) - def choose_border_color(self): + def choose_border_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.border_color) color = color_picker.askcolor() self.border_color = color self.border.config(background=color, text=color) - def cancel(self): + def cancel(self) -> None: self.shape.delete() self.canvas.shapes.pop(self.shape.id) self.destroy() - def click_add(self): + def click_add(self) -> None: if is_draw_shape(self.shape.shape_type): self.add_shape() elif is_shape_text(self.shape.shape_type): @@ -181,7 +182,7 @@ class ShapeDialog(Dialog): text_font.append("underline") return text_font - def save_text(self): + def save_text(self) -> None: """ save info related to text or shape label """ @@ -194,7 +195,7 @@ class ShapeDialog(Dialog): data.italic = self.italic.get() data.underline = self.underline.get() - def save_shape(self): + def save_shape(self) -> None: """ save info related to shape """ @@ -203,7 +204,7 @@ class ShapeDialog(Dialog): data.border_color = self.border_color data.border_width = int(self.border_width.get()) - def add_text(self): + def add_text(self) -> None: """ add text to canvas """ @@ -214,7 +215,7 @@ class ShapeDialog(Dialog): ) self.save_text() - def add_shape(self): + def add_shape(self) -> None: self.canvas.itemconfig( self.shape.id, fill=self.fill_color, diff --git a/daemon/core/gui/dialogs/throughput.py b/daemon/core/gui/dialogs/throughput.py index 5210fe59..5b3cc9b3 100644 --- a/daemon/core/gui/dialogs/throughput.py +++ b/daemon/core/gui/dialogs/throughput.py @@ -3,10 +3,11 @@ throughput dialog """ import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from core.gui.dialogs.colorpicker import ColorPickerDialog from core.gui.dialogs.dialog import Dialog +from core.gui.graph.graph import CanvasGraph from core.gui.themes import FRAME_PAD, PADX, PADY if TYPE_CHECKING: @@ -14,21 +15,23 @@ if TYPE_CHECKING: class ThroughputDialog(Dialog): - def __init__(self, app: "Application"): + def __init__(self, app: "Application") -> None: super().__init__(app, "Throughput Config") - self.canvas = app.canvas - self.show_throughput = tk.IntVar(value=1) - self.exponential_weight = tk.IntVar(value=1) - self.transmission = tk.IntVar(value=1) - self.reception = tk.IntVar(value=1) - self.threshold = tk.DoubleVar(value=self.canvas.throughput_threshold) - self.width = tk.IntVar(value=self.canvas.throughput_width) - self.color = self.canvas.throughput_color - self.color_button = None + self.canvas: CanvasGraph = app.canvas + self.show_throughput: tk.IntVar = tk.IntVar(value=1) + self.exponential_weight: tk.IntVar = tk.IntVar(value=1) + self.transmission: tk.IntVar = tk.IntVar(value=1) + self.reception: tk.IntVar = tk.IntVar(value=1) + self.threshold: tk.DoubleVar = tk.DoubleVar( + value=self.canvas.throughput_threshold + ) + self.width: tk.IntVar = tk.IntVar(value=self.canvas.throughput_width) + self.color: str = self.canvas.throughput_color + self.color_button: Optional[tk.Button] = None self.top.columnconfigure(0, weight=1) self.draw() - def draw(self): + def draw(self) -> None: button = ttk.Checkbutton( self.top, variable=self.show_throughput, @@ -97,12 +100,12 @@ class ThroughputDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_color(self): + def click_color(self) -> None: color_picker = ColorPickerDialog(self, self.app, self.color) self.color = color_picker.askcolor() self.color_button.config(bg=self.color, text=self.color, bd=0) - def click_save(self): + def click_save(self) -> None: self.canvas.throughput_threshold = self.threshold.get() self.canvas.throughput_width = self.width.get() self.canvas.throughput_color = self.color diff --git a/daemon/core/gui/dialogs/wlanconfig.py b/daemon/core/gui/dialogs/wlanconfig.py index b0435a2f..326b3195 100644 --- a/daemon/core/gui/dialogs/wlanconfig.py +++ b/daemon/core/gui/dialogs/wlanconfig.py @@ -1,8 +1,10 @@ from tkinter import ttk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Optional import grpc +from core.api.grpc.common_pb2 import ConfigOption +from core.api.grpc.core_pb2 import Node from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame @@ -10,34 +12,36 @@ from core.gui.widgets import ConfigFrame if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.node import CanvasNode + from core.gui.graph.graph import CanvasGraph -RANGE_COLOR = "#009933" -RANGE_WIDTH = 3 +RANGE_COLOR: str = "#009933" +RANGE_WIDTH: int = 3 class WlanConfigDialog(Dialog): - def __init__(self, app: "Application", canvas_node: "CanvasNode"): + def __init__(self, app: "Application", canvas_node: "CanvasNode") -> None: super().__init__(app, f"{canvas_node.core_node.name} WLAN Configuration") - self.canvas_node = canvas_node - self.node = canvas_node.core_node - self.config_frame = None - self.range_entry = None - self.has_error = False - self.canvas = app.canvas - self.ranges = {} - self.positive_int = self.app.master.register(self.validate_and_update) + self.canvas: "CanvasGraph" = app.canvas + self.canvas_node: "CanvasNode" = canvas_node + self.node: Node = canvas_node.core_node + self.config_frame: Optional[ConfigFrame] = None + self.range_entry: Optional[ttk.Entry] = None + self.has_error: bool = False + self.ranges: Dict[int, int] = {} + self.positive_int: int = self.app.master.register(self.validate_and_update) try: - self.config = self.canvas_node.wlan_config - if not self.config: - self.config = self.app.core.get_wlan_config(self.node.id) + config = self.canvas_node.wlan_config + if not config: + config = self.app.core.get_wlan_config(self.node.id) + self.config: Dict[str, ConfigOption] = config self.init_draw_range() self.draw() except grpc.RpcError as e: self.app.show_grpc_exception("WLAN Config Error", e) - self.has_error = True + self.has_error: bool = True self.destroy() - def init_draw_range(self): + def init_draw_range(self) -> None: if self.canvas_node.id in self.canvas.wireless_network: for cid in self.canvas.wireless_network[self.canvas_node.id]: x, y = self.canvas.coords(cid) @@ -46,7 +50,7 @@ class WlanConfigDialog(Dialog): ) self.ranges[cid] = range_id - def draw(self): + def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) self.config_frame = ConfigFrame(self.top, self.app, self.config) @@ -55,7 +59,7 @@ class WlanConfigDialog(Dialog): self.draw_apply_buttons() self.top.bind("", self.remove_ranges) - def draw_apply_buttons(self): + def draw_apply_buttons(self) -> None: """ create node configuration options """ @@ -75,7 +79,7 @@ class WlanConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") - def click_apply(self): + def click_apply(self) -> None: """ retrieve user's wlan configuration and store the new configuration values """ @@ -87,7 +91,7 @@ class WlanConfigDialog(Dialog): self.remove_ranges() self.destroy() - def remove_ranges(self, event=None): + def remove_ranges(self, event=None) -> None: for cid in self.canvas.find_withtag("range"): self.canvas.delete(cid) self.ranges.clear() diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index f936bc79..833011d8 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -57,7 +57,9 @@ class CanvasNode: self.antennas: List[int] = [] self.antenna_images: Dict[int, PhotoImage] = {} # possible configurations - self.emane_model_configs: Dict[Tuple[str, Optional[int]], ConfigOption] = {} + self.emane_model_configs: Dict[ + Tuple[str, Optional[int]], Dict[str, ConfigOption] + ] = {} self.wlan_config: Dict[str, ConfigOption] = {} self.mobility_config: Dict[str, ConfigOption] = {} self.service_configs: Dict[str, NodeServiceData] = {} @@ -135,7 +137,7 @@ class CanvasNode: new_y = self._get_label_y() self.canvas.move(self.text_id, 0, new_y - prev_y) - def move(self, x: int, y: int) -> None: + def move(self, x: float, y: float) -> None: x, y = self.canvas.get_scaled_coords(x, y) current_x, current_y = self.canvas.coords(self.id) x_offset = x - current_x diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 523f8f11..75312e95 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -49,7 +49,7 @@ class Menubar(tk.Menu): self.canvas: CanvasGraph = app.canvas self.recent_menu: Optional[tk.Menu] = None self.edit_menu: Optional[tk.Menu] = None - self.observers_menu: Optional[tk.Menu] = None + self.observers_menu: Optional[ObserversMenu] = None self.draw() def draw(self) -> None: diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 402eca4d..dbb403df 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -14,7 +14,7 @@ ANTENNA_SIZE: int = 32 class NodeDraw: def __init__(self) -> None: self.custom: bool = False - self.image: Optional[str] = None + self.image: Optional[PhotoImage] = None self.image_enum: Optional[ImageEnum] = None self.image_file: Optional[str] = None self.node_type: NodeType = None From 344f35e93e8ab9a4f5e988b652a344f77a1af774 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 19:04:55 -0700 Subject: [PATCH 087/146] pygui: updated ConfigFrame to have a disabled display option, updated nodes to stil show emane config during runtime, updated emane dialog and config dialogs to be in a viewable but disabled state during runtime --- daemon/core/gui/dialogs/emaneconfig.py | 33 ++++++++++++++------------ daemon/core/gui/graph/node.py | 4 ++++ daemon/core/gui/widgets.py | 28 +++++++++++++++------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index df6c6125..bb334757 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -24,12 +24,15 @@ class GlobalEmaneDialog(Dialog): def __init__(self, master: tk.BaseWidget, app: "Application") -> None: super().__init__(app, "EMANE Configuration", master=master) self.config_frame: Optional[ConfigFrame] = None + self.enabled: bool = not self.app.core.is_runtime() self.draw() def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) - self.config_frame = ConfigFrame(self.top, self.app, self.app.core.emane_config) + self.config_frame = ConfigFrame( + self.top, self.app, self.app.core.emane_config, self.enabled + ) self.config_frame.draw_config() self.config_frame.grid(sticky="nsew", pady=PADY) self.draw_spacer() @@ -40,9 +43,9 @@ class GlobalEmaneDialog(Dialog): frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = ttk.Button(frame, text="Apply", command=self.click_apply) + state = tk.NORMAL if self.enabled else tk.DISABLED + button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state) button.grid(row=0, column=0, sticky="ew", padx=PADX) - button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") @@ -68,6 +71,7 @@ class EmaneModelDialog(Dialog): self.model: str = f"emane_{model}" self.iface_id: int = iface_id self.config_frame: Optional[ConfigFrame] = None + self.enabled: bool = not self.app.core.is_runtime() self.has_error: bool = False try: config = self.canvas_node.emane_model_configs.get( @@ -87,7 +91,7 @@ class EmaneModelDialog(Dialog): def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) - self.config_frame = ConfigFrame(self.top, self.app, self.config) + self.config_frame = ConfigFrame(self.top, self.app, self.config, self.enabled) self.config_frame.draw_config() self.config_frame.grid(sticky="nsew", pady=PADY) self.draw_spacer() @@ -98,9 +102,9 @@ class EmaneModelDialog(Dialog): frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = ttk.Button(frame, text="Apply", command=self.click_apply) + state = tk.NORMAL if self.enabled else tk.DISABLED + button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state) button.grid(row=0, column=0, sticky="ew", padx=PADX) - button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") @@ -124,6 +128,7 @@ class EmaneConfigDialog(Dialog): model = self.node.emane.split("_")[1] self.emane_model: tk.StringVar = tk.StringVar(value=model) self.emane_model_button: Optional[ttk.Button] = None + self.enabled: bool = not self.app.core.is_runtime() self.draw() def draw(self) -> None: @@ -140,8 +145,9 @@ class EmaneConfigDialog(Dialog): """ label = ttk.Label( self.top, - text="The EMANE emulation system provides more complex wireless radio emulation " - "\nusing pluggable MAC and PHY modules. Refer to the wiki for configuration option details", + text="The EMANE emulation system provides more complex wireless radio " + "emulation \nusing pluggable MAC and PHY modules. Refer to the wiki " + "for configuration option details", justify=tk.CENTER, ) label.grid(pady=PADY) @@ -171,11 +177,9 @@ class EmaneConfigDialog(Dialog): label.grid(row=0, column=0, sticky="w") # create combo box and its binding + state = "readonly" if self.enabled else tk.DISABLED combobox = ttk.Combobox( - frame, - textvariable=self.emane_model, - values=self.emane_models, - state="readonly", + frame, textvariable=self.emane_model, values=self.emane_models, state=state ) combobox.grid(row=0, column=1, sticky="ew") combobox.bind("<>", self.emane_model_change) @@ -213,10 +217,9 @@ class EmaneConfigDialog(Dialog): frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - - button = ttk.Button(frame, text="Apply", command=self.click_apply) + state = tk.NORMAL if self.enabled else tk.DISABLED + button = ttk.Button(frame, text="Apply", command=self.click_apply, state=state) button.grid(row=0, column=0, padx=PADX, sticky="ew") - button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 833011d8..a86ce4a3 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -202,6 +202,10 @@ class CanvasNode: is_emane = self.core_node.type == NodeType.EMANE if self.app.core.is_runtime(): self.context.add_command(label="Configure", command=self.show_config) + if is_emane: + self.context.add_command( + label="EMANE Config", command=self.show_emane_config + ) if is_wlan: self.context.add_command( label="WLAN Config", command=self.show_wlan_config diff --git a/daemon/core/gui/widgets.py b/daemon/core/gui/widgets.py index 2eded212..81bad0f5 100644 --- a/daemon/core/gui/widgets.py +++ b/daemon/core/gui/widgets.py @@ -85,12 +85,14 @@ class ConfigFrame(ttk.Notebook): master: tk.Widget, app: "Application", config: Dict[str, ConfigOption], + enabled: bool = True, **kw: Any ) -> None: super().__init__(master, **kw) self.app: "Application" = app self.config: Dict[str, ConfigOption] = config self.values: Dict[str, tk.StringVar] = {} + self.enabled: bool = enabled def draw_config(self) -> None: group_mapping = {} @@ -110,8 +112,9 @@ class ConfigFrame(ttk.Notebook): value = tk.StringVar() if option.type == core_pb2.ConfigOptionType.BOOL: select = ("On", "Off") + state = "readonly" if self.enabled else tk.DISABLED combobox = ttk.Combobox( - tab.frame, textvariable=value, values=select, state="readonly" + tab.frame, textvariable=value, values=select, state=state ) combobox.grid(row=index, column=1, sticky="ew") if option.value == "1": @@ -121,32 +124,41 @@ class ConfigFrame(ttk.Notebook): elif option.select: value.set(option.value) select = tuple(option.select) + state = "readonly" if self.enabled else tk.DISABLED combobox = ttk.Combobox( - tab.frame, textvariable=value, values=select, state="readonly" + tab.frame, textvariable=value, values=select, state=state ) combobox.grid(row=index, column=1, sticky="ew") elif option.type == core_pb2.ConfigOptionType.STRING: value.set(option.value) + state = tk.NORMAL if self.enabled else tk.DISABLED if "file" in option.label: file_frame = ttk.Frame(tab.frame) file_frame.grid(row=index, column=1, sticky="ew") file_frame.columnconfigure(0, weight=1) - entry = ttk.Entry(file_frame, textvariable=value) + entry = ttk.Entry(file_frame, textvariable=value, state=state) entry.grid(row=0, column=0, sticky="ew", padx=PADX) func = partial(file_button_click, value, self) - button = ttk.Button(file_frame, text="...", command=func) + button = ttk.Button( + file_frame, text="...", command=func, state=state + ) button.grid(row=0, column=1) else: - entry = ttk.Entry(tab.frame, textvariable=value) + entry = ttk.Entry(tab.frame, textvariable=value, state=state) entry.grid(row=index, column=1, sticky="ew") - elif option.type in INT_TYPES: value.set(option.value) - entry = validation.PositiveIntEntry(tab.frame, textvariable=value) + state = tk.NORMAL if self.enabled else tk.DISABLED + entry = validation.PositiveIntEntry( + tab.frame, textvariable=value, state=state + ) entry.grid(row=index, column=1, sticky="ew") elif option.type == core_pb2.ConfigOptionType.FLOAT: value.set(option.value) - entry = validation.PositiveFloatEntry(tab.frame, textvariable=value) + state = tk.NORMAL if self.enabled else tk.DISABLED + entry = validation.PositiveFloatEntry( + tab.frame, textvariable=value, state=state + ) entry.grid(row=index, column=1, sticky="ew") else: logging.error("unhandled config option type: %s", option.type) From 27e35a52135795c4b3e6da7cf968db08a924cad3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 19:40:42 -0700 Subject: [PATCH 088/146] pygui: session options dialog is disabled during runtime --- daemon/core/gui/dialogs/sessionoptions.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index 8138d854..fd021fee 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -1,4 +1,5 @@ import logging +import tkinter as tk from tkinter import ttk from typing import TYPE_CHECKING, Dict, Optional @@ -19,6 +20,7 @@ class SessionOptionsDialog(Dialog): self.config_frame: Optional[ConfigFrame] = None self.has_error: bool = False self.config: Dict[str, ConfigOption] = self.get_config() + self.enabled: bool = not self.app.core.is_runtime() if not self.has_error: self.draw() @@ -35,8 +37,7 @@ class SessionOptionsDialog(Dialog): def draw(self) -> None: self.top.columnconfigure(0, weight=1) self.top.rowconfigure(0, weight=1) - - self.config_frame = ConfigFrame(self.top, self.app, config=self.config) + self.config_frame = ConfigFrame(self.top, self.app, self.config, self.enabled) self.config_frame.draw_config() self.config_frame.grid(sticky="nsew", pady=PADY) @@ -44,7 +45,8 @@ class SessionOptionsDialog(Dialog): frame.grid(sticky="ew") for i in range(2): frame.columnconfigure(i, weight=1) - button = ttk.Button(frame, text="Save", command=self.save) + state = tk.NORMAL if self.enabled else tk.DISABLED + button = ttk.Button(frame, text="Save", command=self.save, state=state) button.grid(row=0, column=0, padx=PADX, sticky="ew") button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") From f39ab1dee65d811aa6aa077c4378b18a1434f830 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:13:24 -0700 Subject: [PATCH 089/146] pygui: limit rj45 node to 1 link --- daemon/core/gui/graph/graph.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 53115750..fdf9ba21 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -428,8 +428,9 @@ class CanvasGraph(tk.Canvas): # edge dst must be a node logging.debug("current selected: %s", self.selected) + src_node = self.nodes.get(edge.src) dst_node = self.nodes.get(self.selected) - if not dst_node: + if not dst_node or not src_node: edge.delete() return @@ -444,15 +445,21 @@ class CanvasGraph(tk.Canvas): edge.delete() return + # rj45 nodes can only support one link + if NodeUtils.is_rj45_node(src_node.core_node.type) and src_node.edges: + edge.delete() + return + if NodeUtils.is_rj45_node(dst_node.core_node.type) and dst_node.edges: + edge.delete() + return + # set dst node and snap edge to center edge.complete(self.selected) self.edges[edge.token] = edge - node_src = self.nodes[edge.src] - node_src.edges.add(edge) - node_dst = self.nodes[edge.dst] - node_dst.edges.add(edge) - self.core.create_link(edge, node_src, node_dst) + src_node.edges.add(edge) + dst_node.edges.add(edge) + self.core.create_link(edge, src_node, dst_node) def select_object(self, object_id: int, choose_multiple: bool = False) -> None: """ From 2145c07cb797f4c74bf84f0114d237c84737e8a4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:36:39 -0700 Subject: [PATCH 090/146] daemon: moved FRR_STATE_DIR from constants.py to frr service files --- daemon/core/configservices/frrservices/services.py | 4 ++-- daemon/core/constants.py.in | 1 - daemon/core/services/frr.py | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/daemon/core/configservices/frrservices/services.py b/daemon/core/configservices/frrservices/services.py index ce8c305c..72050077 100644 --- a/daemon/core/configservices/frrservices/services.py +++ b/daemon/core/configservices/frrservices/services.py @@ -1,7 +1,6 @@ import abc from typing import Any, Dict, List -from core import constants from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emane.nodes import EmaneNet @@ -10,6 +9,7 @@ from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode GROUP: str = "FRR" +FRR_STATE_DIR: str = "/var/run/frr" def has_mtu_mismatch(iface: CoreInterface) -> bool: @@ -110,7 +110,7 @@ class FRRZebra(ConfigService): frr_conf=frr_conf, frr_sbin_search=frr_sbin_search, frr_bin_search=frr_bin_search, - frr_state_dir=constants.FRR_STATE_DIR, + frr_state_dir=FRR_STATE_DIR, ifaces=ifaces, want_ip4=want_ip4, want_ip6=want_ip6, diff --git a/daemon/core/constants.py.in b/daemon/core/constants.py.in index 54f3a1c3..4bf600f3 100644 --- a/daemon/core/constants.py.in +++ b/daemon/core/constants.py.in @@ -4,7 +4,6 @@ COREDPY_VERSION = "@PACKAGE_VERSION@" CORE_CONF_DIR = "@CORE_CONF_DIR@" CORE_DATA_DIR = "@CORE_DATA_DIR@" QUAGGA_STATE_DIR = "@CORE_STATE_DIR@/run/quagga" -FRR_STATE_DIR = "@CORE_STATE_DIR@/run/frr" VNODED_BIN = which("vnoded", required=True) VCMD_BIN = which("vcmd", required=True) diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 13569772..ceb04f93 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -6,7 +6,6 @@ from typing import Optional, Tuple import netaddr -from core import constants from core.emane.nodes import EmaneNet from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface @@ -14,6 +13,8 @@ from core.nodes.network import PtpNet, WlanNode from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService +FRR_STATE_DIR: str = "/var/run/frr" + class FRRZebra(CoreService): name: str = "FRRzebra" @@ -236,7 +237,7 @@ bootfrr cls.configs[0], frr_sbin_search, frr_bin_search, - constants.FRR_STATE_DIR, + FRR_STATE_DIR, ) for iface in node.get_ifaces(): cfg += f"ip link set dev {iface.name} down\n" From 1ef66181c6674c5352693141395053a9f137ca07 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:39:29 -0700 Subject: [PATCH 091/146] daemon: moved QUAGGA_STATE_DIR from constants.py to quagga service files --- daemon/core/configservices/quaggaservices/services.py | 4 ++-- daemon/core/constants.py.in | 1 - daemon/core/services/quagga.py | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index e18e8a1a..19430664 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -2,7 +2,6 @@ import abc import logging from typing import Any, Dict, List -from core import constants from core.config import Configuration from core.configservice.base import ConfigService, ConfigServiceMode from core.emane.nodes import EmaneNet @@ -11,6 +10,7 @@ from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode GROUP: str = "Quagga" +QUAGGA_STATE_DIR: str = "/var/run/quagga" def has_mtu_mismatch(iface: CoreInterface) -> bool: @@ -79,7 +79,7 @@ class Zebra(ConfigService): quagga_sbin_search = self.node.session.options.get_config( "quagga_sbin_search", default="/usr/local/sbin /usr/sbin /usr/lib/quagga" ).strip('"') - quagga_state_dir = constants.QUAGGA_STATE_DIR + quagga_state_dir = QUAGGA_STATE_DIR quagga_conf = self.files[0] services = [] diff --git a/daemon/core/constants.py.in b/daemon/core/constants.py.in index 4bf600f3..dfefb128 100644 --- a/daemon/core/constants.py.in +++ b/daemon/core/constants.py.in @@ -3,7 +3,6 @@ from core.utils import which COREDPY_VERSION = "@PACKAGE_VERSION@" CORE_CONF_DIR = "@CORE_CONF_DIR@" CORE_DATA_DIR = "@CORE_DATA_DIR@" -QUAGGA_STATE_DIR = "@CORE_STATE_DIR@/run/quagga" VNODED_BIN = which("vnoded", required=True) VCMD_BIN = which("vcmd", required=True) diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index cb9e6b08..9e2c7cc0 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -5,7 +5,6 @@ from typing import Optional, Tuple import netaddr -from core import constants from core.emane.nodes import EmaneNet from core.emulator.enumerations import LinkTypes from core.nodes.base import CoreNode @@ -14,6 +13,8 @@ from core.nodes.network import PtpNet, WlanNode from core.nodes.physical import Rj45Node from core.services.coreservices import CoreService +QUAGGA_STATE_DIR: str = "/var/run/quagga" + class Zebra(CoreService): name: str = "zebra" @@ -226,7 +227,7 @@ bootquagga cls.configs[0], quagga_sbin_search, quagga_bin_search, - constants.QUAGGA_STATE_DIR, + QUAGGA_STATE_DIR, ) From c43dd60a42c36bc2a2516ff54deeda0ec2438136 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 22 Jun 2020 21:47:03 -0700 Subject: [PATCH 092/146] daemon: small adjustment in sdt.py --- daemon/core/plugins/sdt.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index ef36b0a4..27e54ff3 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -8,8 +8,7 @@ import threading from typing import IO, TYPE_CHECKING, Dict, Optional, Set, Tuple from urllib.parse import urlparse -from core import constants -from core.constants import CORE_DATA_DIR +from core.constants import CORE_CONF_DIR, CORE_DATA_DIR from core.emane.nodes import EmaneNet from core.emulator.data import LinkData, NodeData from core.emulator.enumerations import EventTypes, MessageFlags @@ -264,8 +263,8 @@ class Sdt: icon = node.icon if icon: node_type = node.name - icon = icon.replace("$CORE_DATA_DIR", constants.CORE_DATA_DIR) - icon = icon.replace("$CORE_CONF_DIR", constants.CORE_CONF_DIR) + icon = icon.replace("$CORE_DATA_DIR", CORE_DATA_DIR) + icon = icon.replace("$CORE_CONF_DIR", CORE_CONF_DIR) self.cmd(f"sprite {node_type} image {icon}") self.cmd( f'node {node.id} nodeLayer "{NODE_LAYER}" ' From e0c9f9c8326228aed4134a51cb391fb5cb650d42 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 09:11:37 -0700 Subject: [PATCH 093/146] daemon: moved executable check to CoreEmu and separated them into their own module core.executables --- daemon/core/configservice/manager.py | 6 ++---- daemon/core/constants.py.in | 14 -------------- daemon/core/emulator/coreemu.py | 28 +++++++++++++++++++++++++++- daemon/core/executables.py | 24 ++++++++++++++++++++++++ daemon/core/nodes/base.py | 4 ++-- daemon/core/nodes/client.py | 2 +- daemon/core/nodes/netclient.py | 2 +- daemon/core/nodes/network.py | 2 +- daemon/core/nodes/physical.py | 4 ++-- daemon/core/services/coreservices.py | 10 ++++++---- daemon/core/services/utility.py | 17 ++++++----------- daemon/core/utils.py | 4 ++-- daemon/core/xml/corexmldeployment.py | 2 +- 13 files changed, 75 insertions(+), 44 deletions(-) create mode 100644 daemon/core/executables.py diff --git a/daemon/core/configservice/manager.py b/daemon/core/configservice/manager.py index ecea6e68..83657655 100644 --- a/daemon/core/configservice/manager.py +++ b/daemon/core/configservice/manager.py @@ -52,10 +52,8 @@ class ConfigServiceManager: for executable in service.executables: try: utils.which(executable, required=True) - except ValueError: - raise CoreError( - f"service({service.name}) missing executable {executable}" - ) + except CoreError as e: + raise CoreError(f"config service({service.name}): {e}") # make service available self.services[name] = service diff --git a/daemon/core/constants.py.in b/daemon/core/constants.py.in index dfefb128..cb566e40 100644 --- a/daemon/core/constants.py.in +++ b/daemon/core/constants.py.in @@ -1,17 +1,3 @@ -from core.utils import which - COREDPY_VERSION = "@PACKAGE_VERSION@" CORE_CONF_DIR = "@CORE_CONF_DIR@" CORE_DATA_DIR = "@CORE_DATA_DIR@" - -VNODED_BIN = which("vnoded", required=True) -VCMD_BIN = which("vcmd", required=True) -SYSCTL_BIN = which("sysctl", required=True) -IP_BIN = which("ip", required=True) -ETHTOOL_BIN = which("ethtool", required=True) -TC_BIN = which("tc", required=True) -EBTABLES_BIN = which("ebtables", required=True) -MOUNT_BIN = which("mount", required=True) -UMOUNT_BIN = which("umount", required=True) -OVS_BIN = which("ovs-vsctl", required=False) -OVS_FLOW_BIN = which("ovs-ofctl", required=False) diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 6a7f8b80..86652013 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -6,9 +6,10 @@ import sys from typing import Dict, List, Type import core.services -from core import configservices +from core import configservices, utils from core.configservice.manager import ConfigServiceManager from core.emulator.session import Session +from core.executables import COMMON_REQUIREMENTS, OVS_REQUIREMENTS, VCMD_REQUIREMENTS from core.services.coreservices import ServiceManager @@ -65,10 +66,35 @@ class CoreEmu: if custom_dir: self.service_manager.load(custom_dir) + # check executables exist on path + self._validate_env() + # catch exit event atexit.register(self.shutdown) + def _validate_env(self) -> None: + """ + Validates executables CORE depends on exist on path. + + :return: nothing + :raises core.errors.CoreError: when an executable does not exist on path + """ + for requirement in COMMON_REQUIREMENTS: + utils.which(requirement, required=True) + use_ovs = self.config.get("ovs") == "True" + if use_ovs: + for requirement in OVS_REQUIREMENTS: + utils.which(requirement, required=True) + else: + for requirement in VCMD_REQUIREMENTS: + utils.which(requirement, required=True) + def load_services(self) -> None: + """ + Loads default and custom services for use within CORE. + + :return: nothing + """ # load default services self.service_errors = core.services.load() diff --git a/daemon/core/executables.py b/daemon/core/executables.py new file mode 100644 index 00000000..00d9b40f --- /dev/null +++ b/daemon/core/executables.py @@ -0,0 +1,24 @@ +from typing import List + +VNODED_BIN: str = "vnoded" +VCMD_BIN: str = "vcmd" +SYSCTL_BIN: str = "sysctl" +IP_BIN: str = "ip" +ETHTOOL_BIN: str = "ethtool" +TC_BIN: str = "tc" +EBTABLES_BIN: str = "ebtables" +MOUNT_BIN: str = "mount" +UMOUNT_BIN: str = "umount" +OVS_BIN: str = "ovs-vsctl" + +COMMON_REQUIREMENTS: List[str] = [ + SYSCTL_BIN, + IP_BIN, + ETHTOOL_BIN, + TC_BIN, + EBTABLES_BIN, + MOUNT_BIN, + UMOUNT_BIN, +] +VCMD_REQUIREMENTS: List[str] = [VNODED_BIN, VCMD_BIN] +OVS_REQUIREMENTS: List[str] = [OVS_BIN] diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index aae59b70..a691e4f5 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -13,10 +13,10 @@ import netaddr from core import utils from core.configservice.dependencies import ConfigServiceDependencies -from core.constants import MOUNT_BIN, VNODED_BIN from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError +from core.executables import MOUNT_BIN, VNODED_BIN from core.nodes.client import VnodeClient from core.nodes.interface import CoreInterface, TunTap, Veth from core.nodes.netclient import LinuxNetClient, get_net_client @@ -753,7 +753,7 @@ class CoreNode(CoreNodeBase): iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: - self.node_net_client.device_mac(iface.name, mac) + self.node_net_client.device_mac(iface.name, str(iface.mac)) def add_ip(self, iface_id: int, ip: str) -> None: """ diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index c004b814..f8cd3813 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -5,7 +5,7 @@ The control channel can be accessed via calls using the vcmd shell. """ from core import utils -from core.constants import VCMD_BIN +from core.executables import VCMD_BIN class VnodeClient: diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index b6c164b5..4486bd8f 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -5,7 +5,7 @@ from typing import Callable import netaddr -from core.constants import ETHTOOL_BIN, IP_BIN, OVS_BIN, SYSCTL_BIN, TC_BIN +from core.executables import ETHTOOL_BIN, IP_BIN, OVS_BIN, SYSCTL_BIN, TC_BIN class LinuxNetClient: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 7d8f805e..2c0c1cca 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -10,7 +10,6 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type import netaddr from core import utils -from core.constants import EBTABLES_BIN, TC_BIN from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import ( LinkTypes, @@ -20,6 +19,7 @@ from core.emulator.enumerations import ( RegisterTlvs, ) from core.errors import CoreCommandError, CoreError +from core.executables import EBTABLES_BIN, TC_BIN from core.nodes.base import CoreNetworkBase from core.nodes.interface import CoreInterface, GreTap, Veth from core.nodes.netclient import get_net_client diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 3751d9ee..a025a496 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -7,11 +7,11 @@ import os import threading from typing import IO, TYPE_CHECKING, List, Optional, Tuple -from core.constants import MOUNT_BIN, UMOUNT_BIN from core.emulator.data import InterfaceData, LinkOptions from core.emulator.distributed import DistributedServer from core.emulator.enumerations import NodeTypes, TransportType from core.errors import CoreCommandError, CoreError +from core.executables import MOUNT_BIN, UMOUNT_BIN from core.nodes.base import CoreNetworkBase, CoreNodeBase from core.nodes.interface import CoreInterface from core.nodes.network import CoreNetwork, GreTap @@ -76,7 +76,7 @@ class PhysicalNode(CoreNodeBase): iface = self.get_iface(iface_id) iface.set_mac(mac) if self.up: - self.net_client.device_mac(iface.name, mac) + self.net_client.device_mac(iface.name, str(iface.mac)) def add_ip(self, iface_id: int, ip: str) -> None: """ diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index d22bc7a5..8c41c57d 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -13,10 +13,9 @@ import time from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple, Type from core import utils -from core.constants import which from core.emulator.data import FileData from core.emulator.enumerations import ExceptionLevels, MessageFlags, RegisterTlvs -from core.errors import CoreCommandError +from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode if TYPE_CHECKING: @@ -262,7 +261,10 @@ class ServiceManager: # validate dependent executables are present for executable in service.executables: - which(executable, required=True) + try: + utils.which(executable, required=True) + except CoreError as e: + raise CoreError(f"service({name}): {e}") # validate service on load succeeds try: @@ -300,7 +302,7 @@ class ServiceManager: try: cls.add(service) - except ValueError as e: + except (CoreError, ValueError) as e: service_errors.append(service.name) logging.debug("not loading service(%s): %s", service.name, e) return service_errors diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 414f994e..cf76b092 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -5,8 +5,9 @@ from typing import Optional, Tuple import netaddr -from core import constants, utils +from core import utils from core.errors import CoreCommandError +from core.executables import SYSCTL_BIN from core.nodes.base import CoreNode from core.services.coreservices import CoreService, ServiceMode @@ -47,19 +48,13 @@ class IPForwardService(UtilService): %(sysctl)s -w net.ipv4.conf.all.rp_filter=0 %(sysctl)s -w net.ipv4.conf.default.rp_filter=0 """ % { - "sysctl": constants.SYSCTL_BIN + "sysctl": SYSCTL_BIN } for iface in node.get_ifaces(): name = utils.sysctl_devname(iface.name) - cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % ( - constants.SYSCTL_BIN, - name, - ) - cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % ( - constants.SYSCTL_BIN, - name, - ) - cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (constants.SYSCTL_BIN, name) + cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (SYSCTL_BIN, name) + cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % (SYSCTL_BIN, name) + cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (SYSCTL_BIN, name) return cfg diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 0e082187..459b7d56 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -33,7 +33,7 @@ from typing import ( import netaddr -from core.errors import CoreCommandError +from core.errors import CoreCommandError, CoreError if TYPE_CHECKING: from core.emulator.session import Session @@ -154,7 +154,7 @@ def which(command: str, required: bool) -> str: """ found_path = shutil.which(command) if found_path is None and required: - raise ValueError(f"failed to find required executable({command}) in path") + raise CoreError(f"failed to find required executable({command}) in path") return found_path diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 6035bd26..2235a798 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -6,8 +6,8 @@ import netaddr from lxml import etree from core import utils -from core.constants import IP_BIN from core.emane.nodes import EmaneNet +from core.executables import IP_BIN from core.nodes.base import CoreNodeBase, NodeBase from core.nodes.interface import CoreInterface From 8f19ad057c5305c9e24975a4462e80f11c08228a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 09:24:40 -0700 Subject: [PATCH 094/146] daemon: cleaned up requirement check, updated github workflow to modify correct file --- .github/workflows/daemon-checks.yml | 2 +- daemon/core/emulator/coreemu.py | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/daemon-checks.yml b/.github/workflows/daemon-checks.yml index 85409568..d955ee58 100644 --- a/.github/workflows/daemon-checks.yml +++ b/.github/workflows/daemon-checks.yml @@ -18,7 +18,7 @@ jobs: cd daemon cp setup.py.in setup.py cp core/constants.py.in core/constants.py - sed -i 's/True/False/g' core/constants.py + sed -i 's/required=True/required=False/g' core/emulator/coreemu.py pipenv sync --dev - name: isort run: | diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 86652013..71723268 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -79,15 +79,14 @@ class CoreEmu: :return: nothing :raises core.errors.CoreError: when an executable does not exist on path """ - for requirement in COMMON_REQUIREMENTS: - utils.which(requirement, required=True) + requirements = COMMON_REQUIREMENTS use_ovs = self.config.get("ovs") == "True" if use_ovs: - for requirement in OVS_REQUIREMENTS: - utils.which(requirement, required=True) + requirements += OVS_REQUIREMENTS else: - for requirement in VCMD_REQUIREMENTS: - utils.which(requirement, required=True) + requirements += VCMD_REQUIREMENTS + for requirement in requirements: + utils.which(requirement, required=True) def load_services(self) -> None: """ From 6dd6bc87abcd0045e0a729ee871499c60a31f3cc Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 09:35:11 -0700 Subject: [PATCH 095/146] daemon: renamed executable variables to be simpler --- daemon/core/executables.py | 34 +++++------- daemon/core/nodes/base.py | 6 +-- daemon/core/nodes/client.py | 4 +- daemon/core/nodes/netclient.py | 78 ++++++++++++++-------------- daemon/core/nodes/network.py | 18 +++---- daemon/core/nodes/physical.py | 6 +-- daemon/core/services/utility.py | 10 ++-- daemon/core/xml/corexmldeployment.py | 4 +- 8 files changed, 74 insertions(+), 86 deletions(-) diff --git a/daemon/core/executables.py b/daemon/core/executables.py index 00d9b40f..17aecc1d 100644 --- a/daemon/core/executables.py +++ b/daemon/core/executables.py @@ -1,24 +1,16 @@ from typing import List -VNODED_BIN: str = "vnoded" -VCMD_BIN: str = "vcmd" -SYSCTL_BIN: str = "sysctl" -IP_BIN: str = "ip" -ETHTOOL_BIN: str = "ethtool" -TC_BIN: str = "tc" -EBTABLES_BIN: str = "ebtables" -MOUNT_BIN: str = "mount" -UMOUNT_BIN: str = "umount" -OVS_BIN: str = "ovs-vsctl" +VNODED: str = "vnoded" +VCMD: str = "vcmd" +SYSCTL: str = "sysctl" +IP: str = "ip" +ETHTOOL: str = "ethtool" +TC: str = "tc" +EBTABLES: str = "ebtables" +MOUNT: str = "mount" +UMOUNT: str = "umount" +OVS_VSCTL: str = "ovs-vsctl" -COMMON_REQUIREMENTS: List[str] = [ - SYSCTL_BIN, - IP_BIN, - ETHTOOL_BIN, - TC_BIN, - EBTABLES_BIN, - MOUNT_BIN, - UMOUNT_BIN, -] -VCMD_REQUIREMENTS: List[str] = [VNODED_BIN, VCMD_BIN] -OVS_REQUIREMENTS: List[str] = [OVS_BIN] +COMMON_REQUIREMENTS: List[str] = [SYSCTL, IP, ETHTOOL, TC, EBTABLES, MOUNT, UMOUNT] +VCMD_REQUIREMENTS: List[str] = [VNODED, VCMD] +OVS_REQUIREMENTS: List[str] = [OVS_VSCTL] diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index a691e4f5..3999046d 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -16,7 +16,7 @@ from core.configservice.dependencies import ConfigServiceDependencies from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes from core.errors import CoreCommandError, CoreError -from core.executables import MOUNT_BIN, VNODED_BIN +from core.executables import MOUNT, VNODED from core.nodes.client import VnodeClient from core.nodes.interface import CoreInterface, TunTap, Veth from core.nodes.netclient import LinuxNetClient, get_net_client @@ -511,7 +511,7 @@ class CoreNode(CoreNodeBase): # create a new namespace for this node using vnoded vnoded = ( - f"{VNODED_BIN} -v -c {self.ctrlchnlname} -l {self.ctrlchnlname}.log " + f"{VNODED} -v -c {self.ctrlchnlname} -l {self.ctrlchnlname}.log " f"-p {self.ctrlchnlname}.pid" ) if self.nodedir: @@ -640,7 +640,7 @@ class CoreNode(CoreNodeBase): source = os.path.abspath(source) logging.debug("node(%s) mounting: %s at %s", self.name, source, target) self.cmd(f"mkdir -p {target}") - self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}") + self.cmd(f"{MOUNT} -n --bind {source} {target}") self._mounts.append((source, target)) def next_iface_id(self) -> int: diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index f8cd3813..93e099cf 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -5,7 +5,7 @@ The control channel can be accessed via calls using the vcmd shell. """ from core import utils -from core.executables import VCMD_BIN +from core.executables import VCMD class VnodeClient: @@ -50,7 +50,7 @@ class VnodeClient: pass def create_cmd(self, args: str) -> str: - return f"{VCMD_BIN} -c {self.ctrlchnlname} -- {args}" + return f"{VCMD} -c {self.ctrlchnlname} -- {args}" def check_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: """ diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 4486bd8f..96a1f4be 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -5,7 +5,7 @@ from typing import Callable import netaddr -from core.executables import ETHTOOL_BIN, IP_BIN, OVS_BIN, SYSCTL_BIN, TC_BIN +from core.executables import ETHTOOL, IP, OVS_VSCTL, SYSCTL, TC class LinuxNetClient: @@ -38,7 +38,7 @@ class LinuxNetClient: :param device: device to add route to :return: nothing """ - self.run(f"{IP_BIN} route add {route} dev {device}") + self.run(f"{IP} route add {route} dev {device}") def device_up(self, device: str) -> None: """ @@ -47,7 +47,7 @@ class LinuxNetClient: :param device: device to bring up :return: nothing """ - self.run(f"{IP_BIN} link set {device} up") + self.run(f"{IP} link set {device} up") def device_down(self, device: str) -> None: """ @@ -56,7 +56,7 @@ class LinuxNetClient: :param device: device to bring down :return: nothing """ - self.run(f"{IP_BIN} link set {device} down") + self.run(f"{IP} link set {device} down") def device_name(self, device: str, name: str) -> None: """ @@ -66,7 +66,7 @@ class LinuxNetClient: :param name: name to set :return: nothing """ - self.run(f"{IP_BIN} link set {device} name {name}") + self.run(f"{IP} link set {device} name {name}") def device_show(self, device: str) -> str: """ @@ -75,7 +75,7 @@ class LinuxNetClient: :param device: device to get information for :return: device information """ - return self.run(f"{IP_BIN} link show {device}") + return self.run(f"{IP} link show {device}") def address_show(self, device: str) -> str: """ @@ -84,7 +84,7 @@ class LinuxNetClient: :param device: device name :return: address information """ - return self.run(f"{IP_BIN} address show {device}") + return self.run(f"{IP} address show {device}") def get_mac(self, device: str) -> str: """ @@ -112,7 +112,7 @@ class LinuxNetClient: :param namespace: namespace to set device to :return: nothing """ - self.run(f"{IP_BIN} link set {device} netns {namespace}") + self.run(f"{IP} link set {device} netns {namespace}") def device_flush(self, device: str) -> None: """ @@ -123,7 +123,7 @@ class LinuxNetClient: """ self.run( f"[ -e /sys/class/net/{device} ] && " - f"{IP_BIN} address flush dev {device} || true", + f"{IP} address flush dev {device} || true", shell=True, ) @@ -135,7 +135,7 @@ class LinuxNetClient: :param mac: mac to set :return: nothing """ - self.run(f"{IP_BIN} link set dev {device} address {mac}") + self.run(f"{IP} link set dev {device} address {mac}") def delete_device(self, device: str) -> None: """ @@ -144,7 +144,7 @@ class LinuxNetClient: :param device: device to delete :return: nothing """ - self.run(f"{IP_BIN} link delete {device}") + self.run(f"{IP} link delete {device}") def delete_tc(self, device: str) -> None: """ @@ -153,7 +153,7 @@ class LinuxNetClient: :param device: device to remove tc :return: nothing """ - self.run(f"{TC_BIN} qdisc delete dev {device} root") + self.run(f"{TC} qdisc delete dev {device} root") def checksums_off(self, iface_name: str) -> None: """ @@ -162,7 +162,7 @@ class LinuxNetClient: :param iface_name: interface to update :return: nothing """ - self.run(f"{ETHTOOL_BIN} -K {iface_name} rx off tx off") + self.run(f"{ETHTOOL} -K {iface_name} rx off tx off") def create_address(self, device: str, address: str, broadcast: str = None) -> None: """ @@ -174,15 +174,13 @@ class LinuxNetClient: :return: nothing """ if broadcast is not None: - self.run( - f"{IP_BIN} address add {address} broadcast {broadcast} dev {device}" - ) + self.run(f"{IP} address add {address} broadcast {broadcast} dev {device}") else: - self.run(f"{IP_BIN} address add {address} dev {device}") + self.run(f"{IP} address add {address} dev {device}") if netaddr.valid_ipv6(address.split("/")[0]): # IPv6 addresses are removed by default on interface down. # Make sure that the IPv6 address we add is not removed - self.run(f"{SYSCTL_BIN} -w net.ipv6.conf.{device}.keep_addr_on_down=1") + self.run(f"{SYSCTL} -w net.ipv6.conf.{device}.keep_addr_on_down=1") def delete_address(self, device: str, address: str) -> None: """ @@ -192,7 +190,7 @@ class LinuxNetClient: :param address: address to remove :return: nothing """ - self.run(f"{IP_BIN} address delete {address} dev {device}") + self.run(f"{IP} address delete {address} dev {device}") def create_veth(self, name: str, peer: str) -> None: """ @@ -202,7 +200,7 @@ class LinuxNetClient: :param peer: peer name :return: nothing """ - self.run(f"{IP_BIN} link add name {name} type veth peer name {peer}") + self.run(f"{IP} link add name {name} type veth peer name {peer}") def create_gretap( self, device: str, address: str, local: str, ttl: int, key: int @@ -217,7 +215,7 @@ class LinuxNetClient: :param key: key for tap :return: nothing """ - cmd = f"{IP_BIN} link add {device} type gretap remote {address}" + cmd = f"{IP} link add {device} type gretap remote {address}" if local is not None: cmd += f" local {local}" if ttl is not None: @@ -233,11 +231,11 @@ class LinuxNetClient: :param name: bridge name :return: nothing """ - self.run(f"{IP_BIN} link add name {name} type bridge") - self.run(f"{IP_BIN} link set {name} type bridge stp_state 0") - self.run(f"{IP_BIN} link set {name} type bridge forward_delay 0") - self.run(f"{IP_BIN} link set {name} type bridge mcast_snooping 0") - self.run(f"{IP_BIN} link set {name} type bridge group_fwd_mask 65528") + self.run(f"{IP} link add name {name} type bridge") + self.run(f"{IP} link set {name} type bridge stp_state 0") + self.run(f"{IP} link set {name} type bridge forward_delay 0") + self.run(f"{IP} link set {name} type bridge mcast_snooping 0") + self.run(f"{IP} link set {name} type bridge group_fwd_mask 65528") self.device_up(name) def delete_bridge(self, name: str) -> None: @@ -248,7 +246,7 @@ class LinuxNetClient: :return: nothing """ self.device_down(name) - self.run(f"{IP_BIN} link delete {name} type bridge") + self.run(f"{IP} link delete {name} type bridge") def set_iface_master(self, bridge_name: str, iface_name: str) -> None: """ @@ -258,7 +256,7 @@ class LinuxNetClient: :param iface_name: interface name :return: nothing """ - self.run(f"{IP_BIN} link set dev {iface_name} master {bridge_name}") + self.run(f"{IP} link set dev {iface_name} master {bridge_name}") self.device_up(iface_name) def delete_iface(self, bridge_name: str, iface_name: str) -> None: @@ -269,7 +267,7 @@ class LinuxNetClient: :param iface_name: interface name :return: nothing """ - self.run(f"{IP_BIN} link set dev {iface_name} nomaster") + self.run(f"{IP} link set dev {iface_name} nomaster") def existing_bridges(self, _id: int) -> bool: """ @@ -278,7 +276,7 @@ class LinuxNetClient: :param _id: node id to check bridges for :return: True if there are existing bridges, False otherwise """ - output = self.run(f"{IP_BIN} -o link show type bridge") + output = self.run(f"{IP} -o link show type bridge") lines = output.split("\n") for line in lines: values = line.split(":") @@ -299,7 +297,7 @@ class LinuxNetClient: :param name: bridge name :return: nothing """ - self.run(f"{IP_BIN} link set {name} type bridge ageing_time 0") + self.run(f"{IP} link set {name} type bridge ageing_time 0") class OvsNetClient(LinuxNetClient): @@ -314,10 +312,10 @@ class OvsNetClient(LinuxNetClient): :param name: bridge name :return: nothing """ - self.run(f"{OVS_BIN} add-br {name}") - self.run(f"{OVS_BIN} set bridge {name} stp_enable=false") - self.run(f"{OVS_BIN} set bridge {name} other_config:stp-max-age=6") - self.run(f"{OVS_BIN} set bridge {name} other_config:stp-forward-delay=4") + self.run(f"{OVS_VSCTL} add-br {name}") + self.run(f"{OVS_VSCTL} set bridge {name} stp_enable=false") + self.run(f"{OVS_VSCTL} set bridge {name} other_config:stp-max-age=6") + self.run(f"{OVS_VSCTL} set bridge {name} other_config:stp-forward-delay=4") self.device_up(name) def delete_bridge(self, name: str) -> None: @@ -328,7 +326,7 @@ class OvsNetClient(LinuxNetClient): :return: nothing """ self.device_down(name) - self.run(f"{OVS_BIN} del-br {name}") + self.run(f"{OVS_VSCTL} del-br {name}") def set_iface_master(self, bridge_name: str, iface_name: str) -> None: """ @@ -338,7 +336,7 @@ class OvsNetClient(LinuxNetClient): :param iface_name: interface name :return: nothing """ - self.run(f"{OVS_BIN} add-port {bridge_name} {iface_name}") + self.run(f"{OVS_VSCTL} add-port {bridge_name} {iface_name}") self.device_up(iface_name) def delete_iface(self, bridge_name: str, iface_name: str) -> None: @@ -349,7 +347,7 @@ class OvsNetClient(LinuxNetClient): :param iface_name: interface name :return: nothing """ - self.run(f"{OVS_BIN} del-port {bridge_name} {iface_name}") + self.run(f"{OVS_VSCTL} del-port {bridge_name} {iface_name}") def existing_bridges(self, _id: int) -> bool: """ @@ -358,7 +356,7 @@ class OvsNetClient(LinuxNetClient): :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_VSCTL} list-br") if output: for line in output.split("\n"): fields = line.split(".") @@ -373,7 +371,7 @@ class OvsNetClient(LinuxNetClient): :param name: bridge name :return: nothing """ - self.run(f"{OVS_BIN} set bridge {name} other_config:mac-aging-time=0") + self.run(f"{OVS_VSCTL} set bridge {name} other_config:mac-aging-time=0") def get_net_client(use_ovs: bool, run: Callable[..., str]) -> LinuxNetClient: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 2c0c1cca..d418a42c 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -19,7 +19,7 @@ from core.emulator.enumerations import ( RegisterTlvs, ) from core.errors import CoreCommandError, CoreError -from core.executables import EBTABLES_BIN, TC_BIN +from core.executables import EBTABLES, TC from core.nodes.base import CoreNetworkBase from core.nodes.interface import CoreInterface, GreTap, Veth from core.nodes.netclient import get_net_client @@ -104,7 +104,7 @@ class EbtablesQueue: :param cmd: ebtable command :return: ebtable atomic command """ - return f"{EBTABLES_BIN} --atomic-file {self.atomic_file} {cmd}" + return f"{EBTABLES} --atomic-file {self.atomic_file} {cmd}" def lastupdate(self, wlan: "CoreNetwork") -> float: """ @@ -338,8 +338,8 @@ class CoreNetwork(CoreNetworkBase): self.net_client.delete_bridge(self.brname) if self.has_ebtables_chain: cmds = [ - f"{EBTABLES_BIN} -D FORWARD --logical-in {self.brname} -j {self.brname}", - f"{EBTABLES_BIN} -X {self.brname}", + f"{EBTABLES} -D FORWARD --logical-in {self.brname} -j {self.brname}", + f"{EBTABLES} -X {self.brname}", ] ebtablescmds(self.host_cmd, cmds) except CoreCommandError: @@ -448,7 +448,7 @@ class CoreNetwork(CoreNetworkBase): :return: nothing """ devname = iface.localname - tc = f"{TC_BIN} qdisc replace dev {devname}" + tc = f"{TC} qdisc replace dev {devname}" parent = "root" changed = False bw = options.bandwidth @@ -466,7 +466,7 @@ class CoreNetwork(CoreNetworkBase): changed = True elif iface.getparam("has_tbf") and bw <= 0: if self.up: - cmd = f"{TC_BIN} qdisc delete dev {devname} {parent}" + cmd = f"{TC} qdisc delete dev {devname} {parent}" iface.host_cmd(cmd) iface.setparam("has_tbf", False) # removing the parent removes the child @@ -512,14 +512,12 @@ class CoreNetwork(CoreNetworkBase): if not iface.getparam("has_netem"): return if self.up: - cmd = f"{TC_BIN} qdisc delete dev {devname} {parent} handle 10:" + cmd = f"{TC} qdisc delete dev {devname} {parent} handle 10:" iface.host_cmd(cmd) iface.setparam("has_netem", False) elif len(netem) > 1: if self.up: - cmd = ( - f"{TC_BIN} qdisc replace dev {devname} {parent} handle 10: {netem}" - ) + cmd = f"{TC} qdisc replace dev {devname} {parent} handle 10: {netem}" iface.host_cmd(cmd) iface.setparam("has_netem", True) diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index a025a496..f48a0d10 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -11,7 +11,7 @@ from core.emulator.data import InterfaceData, LinkOptions from core.emulator.distributed import DistributedServer from core.emulator.enumerations import NodeTypes, TransportType from core.errors import CoreCommandError, CoreError -from core.executables import MOUNT_BIN, UMOUNT_BIN +from core.executables import MOUNT, UMOUNT from core.nodes.base import CoreNetworkBase, CoreNodeBase from core.nodes.interface import CoreInterface from core.nodes.network import CoreNetwork, GreTap @@ -186,13 +186,13 @@ class PhysicalNode(CoreNodeBase): source = os.path.abspath(source) logging.info("mounting %s at %s", source, target) os.makedirs(target) - self.host_cmd(f"{MOUNT_BIN} --bind {source} {target}", cwd=self.nodedir) + self.host_cmd(f"{MOUNT} --bind {source} {target}", cwd=self.nodedir) self._mounts.append((source, target)) def umount(self, target: str) -> None: logging.info("unmounting '%s'", target) try: - self.host_cmd(f"{UMOUNT_BIN} -l {target}", cwd=self.nodedir) + self.host_cmd(f"{UMOUNT} -l {target}", cwd=self.nodedir) except CoreCommandError: logging.exception("unmounting failed for %s", target) diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index cf76b092..774c4104 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -7,7 +7,7 @@ import netaddr from core import utils from core.errors import CoreCommandError -from core.executables import SYSCTL_BIN +from core.executables import SYSCTL from core.nodes.base import CoreNode from core.services.coreservices import CoreService, ServiceMode @@ -48,13 +48,13 @@ class IPForwardService(UtilService): %(sysctl)s -w net.ipv4.conf.all.rp_filter=0 %(sysctl)s -w net.ipv4.conf.default.rp_filter=0 """ % { - "sysctl": SYSCTL_BIN + "sysctl": SYSCTL } for iface in node.get_ifaces(): name = utils.sysctl_devname(iface.name) - cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (SYSCTL_BIN, name) - cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % (SYSCTL_BIN, name) - cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (SYSCTL_BIN, name) + cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (SYSCTL, name) + cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % (SYSCTL, name) + cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (SYSCTL, name) return cfg diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 2235a798..51201787 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -7,7 +7,7 @@ from lxml import etree from core import utils from core.emane.nodes import EmaneNet -from core.executables import IP_BIN +from core.executables import IP from core.nodes.base import CoreNodeBase, NodeBase from core.nodes.interface import CoreInterface @@ -83,7 +83,7 @@ def get_address_type(address: str) -> str: def get_ipv4_addresses(hostname: str) -> List[Tuple[str, str]]: if hostname == "localhost": addresses = [] - args = f"{IP_BIN} -o -f inet address show" + args = f"{IP} -o -f inet address show" output = utils.cmd(args) for line in output.split(os.linesep): split = line.split() From 8e2cfa61c90413b9bfbb560dbcde89134d6c0380 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 10:09:16 -0700 Subject: [PATCH 096/146] pygui: size and scale meter width and height are no longer editable, but will dynamically update with changes to related size/scale values --- daemon/core/gui/dialogs/canvassizeandscale.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/dialogs/canvassizeandscale.py b/daemon/core/gui/dialogs/canvassizeandscale.py index b93bd920..38cecc83 100644 --- a/daemon/core/gui/dialogs/canvassizeandscale.py +++ b/daemon/core/gui/dialogs/canvassizeandscale.py @@ -66,10 +66,12 @@ class SizeAndScaleDialog(Dialog): label.grid(row=0, column=0, sticky="w", padx=PADX) entry = validation.PositiveIntEntry(frame, textvariable=self.pixel_width) entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.bind("", self.size_scale_keyup) label = ttk.Label(frame, text="x Height") label.grid(row=0, column=2, sticky="w", padx=PADX) entry = validation.PositiveIntEntry(frame, textvariable=self.pixel_height) entry.grid(row=0, column=3, sticky="ew", padx=PADX) + entry.bind("", self.size_scale_keyup) label = ttk.Label(frame, text="Pixels") label.grid(row=0, column=4, sticky="w") @@ -80,11 +82,15 @@ class SizeAndScaleDialog(Dialog): frame.columnconfigure(3, weight=1) label = ttk.Label(frame, text="Width") label.grid(row=0, column=0, sticky="w", padx=PADX) - entry = validation.PositiveFloatEntry(frame, textvariable=self.meters_width) + entry = validation.PositiveFloatEntry( + frame, textvariable=self.meters_width, state=tk.DISABLED + ) entry.grid(row=0, column=1, sticky="ew", padx=PADX) label = ttk.Label(frame, text="x Height") label.grid(row=0, column=2, sticky="w", padx=PADX) - entry = validation.PositiveFloatEntry(frame, textvariable=self.meters_height) + entry = validation.PositiveFloatEntry( + frame, textvariable=self.meters_height, state=tk.DISABLED + ) entry.grid(row=0, column=3, sticky="ew", padx=PADX) label = ttk.Label(frame, text="Meters") label.grid(row=0, column=4, sticky="w") @@ -101,6 +107,7 @@ class SizeAndScaleDialog(Dialog): label.grid(row=0, column=0, sticky="w", padx=PADX) entry = validation.PositiveFloatEntry(frame, textvariable=self.scale) entry.grid(row=0, column=1, sticky="ew", padx=PADX) + entry.bind("", self.size_scale_keyup) label = ttk.Label(frame, text="Meters") label.grid(row=0, column=2, sticky="w") @@ -173,6 +180,13 @@ class SizeAndScaleDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky="ew") + def size_scale_keyup(self, _event: tk.Event) -> None: + scale = self.scale.get() + width = self.pixel_width.get() + height = self.pixel_height.get() + self.meters_width.set(width / PIXEL_SCALE * scale) + self.meters_height.set(height / PIXEL_SCALE * scale) + def click_apply(self) -> None: width, height = self.pixel_width.get(), self.pixel_height.get() self.canvas.redraw_canvas((width, height)) From 14573184e01ea727ce958640a92be41025433ed3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 13:28:41 -0700 Subject: [PATCH 097/146] pygui: fixed syning session location settings when not in runtime mode, for saving xml --- daemon/core/gui/coreclient.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 39ee486a..8050d7f0 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -718,7 +718,7 @@ class CoreClient: def send_data(self) -> None: """ - send to daemon all session info, but don't start the session + Send to daemon all session info, but don't start the session """ self.create_nodes_and_links() for config_proto in self.get_wlan_configs_proto(): @@ -759,6 +759,17 @@ class CoreClient: if self.emane_config: config = {x: self.emane_config[x].value for x in self.emane_config} self.client.set_emane_config(self.session_id, config) + if self.location: + self.client.set_session_location( + self.session_id, + self.location.x, + self.location.y, + self.location.z, + self.location.lat, + self.location.lon, + self.location.alt, + self.location.scale, + ) self.set_metadata() def close(self) -> None: From 9649337f185af4e7cd90ef93029871b2dcb92e60 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 13:31:31 -0700 Subject: [PATCH 098/146] daemon: updated xml to save links using consistent iface1/2 naming, still fallback to reading interface_one/two --- daemon/core/xml/corexml.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index d3cc85d8..340d81d0 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -520,14 +520,14 @@ class CoreXmlWriter: # check for interface one if link_data.iface1 is not None: iface1 = self.create_iface_element( - "interface1", link_data.node1_id, link_data.iface1 + "iface1", link_data.node1_id, link_data.iface1 ) link_element.append(iface1) # check for interface two if link_data.iface2 is not None: iface2 = self.create_iface_element( - "interface2", link_data.node2_id, link_data.iface2 + "iface2", link_data.node2_id, link_data.iface2 ) link_element.append(iface2) @@ -907,14 +907,14 @@ class CoreXmlReader: node2_id = get_int(link_element, "node_two") node_set = frozenset((node1_id, node2_id)) - iface1_element = link_element.find("interface1") + iface1_element = link_element.find("iface1") if iface1_element is None: iface1_element = link_element.find("interface_one") iface1_data = None if iface1_element is not None: iface1_data = create_iface_data(iface1_element) - iface2_element = link_element.find("interface2") + iface2_element = link_element.find("iface2") if iface2_element is None: iface2_element = link_element.find("interface_two") iface2_data = None From 7215f852b8b591d20b4eaab39fbb8582ad46557e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 13:34:40 -0700 Subject: [PATCH 099/146] grpc: added check for emane pathloss when nem id is None and throw an error --- daemon/core/api/grpc/grpcutils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index b63cb895..8df545cd 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -487,4 +487,8 @@ def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int: if not isinstance(net, EmaneNet): message = f"{node.name} interface {iface_id} is not an EMANE network" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) - return net.getnemid(iface) + nem_id = net.getnemid(iface) + if nem_id is None: + message = f"{node.name} interface {iface_id} nem id does not exist" + context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) + return nem_id From 60d9fe2026add397e9bf004a9ee7dd7057ad21c1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 14:48:27 -0700 Subject: [PATCH 100/146] pygui: clear throughput labels when disabling throughput --- daemon/core/gui/coreclient.py | 1 + daemon/core/gui/graph/edges.py | 7 +++++-- daemon/core/gui/graph/graph.py | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 8050d7f0..d35f62e5 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -221,6 +221,7 @@ class CoreClient: if self.handling_throughputs: self.handling_throughputs.cancel() self.handling_throughputs = None + self.app.canvas.clear_throughputs() def cancel_events(self) -> None: if self.handling_events: diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index e9ac2587..29632086 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -145,6 +145,10 @@ class Edge: else: self.canvas.itemconfig(self.middle_label, text=text) + def clear_middle_label(self) -> None: + self.canvas.delete(self.middle_label) + self.middle_label = None + def node_label_positions(self) -> Tuple[Tuple[float, float], Tuple[float, float]]: src_x, src_y, _, _, dst_x, dst_y = self.canvas.coords(self.id) v1 = dst_x - src_x @@ -216,11 +220,10 @@ class Edge: logging.debug("deleting canvas edge, id: %s", self.id) self.canvas.delete(self.id) self.canvas.delete(self.src_label) - self.canvas.delete(self.middle_label) self.canvas.delete(self.dst_label) + self.clear_middle_label() self.id = None self.src_label = None - self.middle_label = None self.dst_label = None diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index fdf9ba21..07519c3f 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -997,6 +997,10 @@ class CanvasGraph(tk.Canvas): ) self.tag_raise(tags.NODE) + def clear_throughputs(self) -> None: + for edge in self.edges.values(): + edge.clear_middle_label() + def scale_graph(self) -> None: for nid, canvas_node in self.nodes.items(): img = None From 6490b5b9cbe6e39adb57aeb00675d6b828355746 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 16:11:39 -0700 Subject: [PATCH 101/146] pygui: fixed and changed custom service copy to focus only on copying the current file displayed from any other nodes with a customized version --- daemon/core/gui/dialogs/copyserviceconfig.py | 226 ++++++------------- daemon/core/gui/dialogs/serviceconfig.py | 6 +- 2 files changed, 77 insertions(+), 155 deletions(-) diff --git a/daemon/core/gui/dialogs/copyserviceconfig.py b/daemon/core/gui/dialogs/copyserviceconfig.py index 35559cb9..2a01249d 100644 --- a/daemon/core/gui/dialogs/copyserviceconfig.py +++ b/daemon/core/gui/dialogs/copyserviceconfig.py @@ -4,80 +4,58 @@ copy service config dialog import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, Tuple +from typing import TYPE_CHECKING, Dict, Optional from core.gui.dialogs.dialog import Dialog -from core.gui.themes import FRAME_PAD, PADX -from core.gui.widgets import CodeText +from core.gui.themes import PADX, PADY +from core.gui.widgets import CodeText, ListboxScroll if TYPE_CHECKING: from core.gui.app import Application + from core.gui.dialogs.serviceconfig import ServiceConfigDialog class CopyServiceConfigDialog(Dialog): - def __init__(self, master: tk.BaseWidget, app: "Application", node_id: int) -> None: - super().__init__(app, f"Copy services to node {node_id}", master=master) - self.parent = master - self.node_id = node_id - self.service_configs = app.core.service_configs - self.file_configs = app.core.file_configs - self.tree = None + def __init__( + self, + app: "Application", + dialog: "ServiceConfigDialog", + name: str, + service: str, + file_name: str, + ) -> None: + super().__init__(app, f"Copy Custom File to {name}", master=dialog) + self.dialog: "ServiceConfigDialog" = dialog + self.service: str = service + self.file_name: str = file_name + self.listbox: Optional[tk.Listbox] = None + self.nodes: Dict[str, int] = {} self.draw() def draw(self) -> None: self.top.columnconfigure(0, weight=1) - self.tree = ttk.Treeview(self.top) - self.tree.grid(row=0, column=0, sticky="ew", padx=PADX) - self.tree["columns"] = () - self.tree.column("#0", width=270, minwidth=270, stretch=tk.YES) - self.tree.heading("#0", text="Service configuration items", anchor=tk.CENTER) - custom_nodes = set(self.service_configs).union(set(self.file_configs)) - for nid in custom_nodes: - treeid = self.tree.insert("", "end", text=f"n{nid}", tags="node") - services = self.service_configs.get(nid, None) - files = self.file_configs.get(nid, None) - tree_ids = {} - if services: - for service, config in services.items(): - serviceid = self.tree.insert( - treeid, "end", text=service, tags="service" - ) - tree_ids[service] = serviceid - cmdup = config.startup[:] - cmddown = config.shutdown[:] - cmdval = config.validate[:] - self.tree.insert( - serviceid, - "end", - text=f"cmdup=({str(cmdup)[1:-1]})", - tags=("cmd", "up"), - ) - self.tree.insert( - serviceid, - "end", - text=f"cmddown=({str(cmddown)[1:-1]})", - tags=("cmd", "down"), - ) - self.tree.insert( - serviceid, - "end", - text=f"cmdval=({str(cmdval)[1:-1]})", - tags=("cmd", "val"), - ) - if files: - for service, configs in files.items(): - if service in tree_ids: - serviceid = tree_ids[service] - else: - serviceid = self.tree.insert( - treeid, "end", text=service, tags="service" - ) - tree_ids[service] = serviceid - for filename, data in configs.items(): - self.tree.insert(serviceid, "end", text=filename, tags="file") + self.top.rowconfigure(1, weight=1) + label = ttk.Label( + self.top, text=f"{self.service} - {self.file_name}", anchor=tk.CENTER + ) + label.grid(sticky="ew", pady=PADY) + + listbox_scroll = ListboxScroll(self.top) + listbox_scroll.grid(sticky="nsew", pady=PADY) + self.listbox = listbox_scroll.listbox + for canvas_node in self.app.canvas.nodes.values(): + file_configs = canvas_node.service_file_configs.get(self.service) + if not file_configs: + continue + data = file_configs.get(self.file_name) + if not data: + continue + name = canvas_node.core_node.name + self.nodes[name] = canvas_node.id + self.listbox.insert(tk.END, name) frame = ttk.Frame(self.top) - frame.grid(row=1, column=0) + frame.grid(sticky="ew") for i in range(3): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Copy", command=self.click_copy) @@ -85,118 +63,58 @@ class CopyServiceConfigDialog(Dialog): button = ttk.Button(frame, text="View", command=self.click_view) button.grid(row=0, column=1, sticky="ew", padx=PADX) button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=2, sticky="ew", padx=PADX) + button.grid(row=0, column=2, sticky="ew") def click_copy(self) -> None: - selected = self.tree.selection() - if selected: - item = self.tree.item(selected[0]) - if "file" in item["tags"]: - filename = item["text"] - nid, service = self.get_node_service(selected) - data = self.file_configs[nid][service][filename] - if service == self.parent.service_name: - self.parent.temp_service_files[filename] = data - self.parent.modified_files.add(filename) - if self.parent.filename_combobox.get() == filename: - self.parent.service_file_data.text.delete(1.0, "end") - self.parent.service_file_data.text.insert("end", data) - if "cmd" in item["tags"]: - nid, service = self.get_node_service(selected) - if service == self.master.service_name: - cmds = self.service_configs[nid][service] - if "up" in item["tags"]: - self.master.append_commands( - self.master.startup_commands, - self.master.startup_commands_listbox, - cmds.startup, - ) - elif "down" in item["tags"]: - self.master.append_commands( - self.master.shutdown_commands, - self.master.shutdown_commands_listbox, - cmds.shutdown, - ) - - elif "val" in item["tags"]: - self.master.append_commands( - self.master.validate_commands, - self.master.validate_commands_listbox, - cmds.validate, - ) + selection = self.listbox.curselection() + if not selection: + return + name = self.listbox.get(selection) + canvas_node_id = self.nodes[name] + canvas_node = self.app.canvas.nodes[canvas_node_id] + data = canvas_node.service_file_configs[self.service][self.file_name] + self.dialog.temp_service_files[self.file_name] = data + self.dialog.modified_files.add(self.file_name) + self.dialog.service_file_data.text.delete(1.0, tk.END) + self.dialog.service_file_data.text.insert(tk.END, data) self.destroy() def click_view(self) -> None: - selected = self.tree.selection() - data = "" - if selected: - item = self.tree.item(selected[0]) - if "file" in item["tags"]: - nid, service = self.get_node_service(selected) - data = self.file_configs[nid][service][item["text"]] - dialog = ViewConfigDialog( - self, self.app, nid, data, item["text"].split("/")[-1] - ) - dialog.show() - if "cmd" in item["tags"]: - nid, service = self.get_node_service(selected) - cmds = self.service_configs[nid][service] - if "up" in item["tags"]: - data = f"({str(cmds.startup[:])[1:-1]})" - dialog = ViewConfigDialog( - self, self.app, self.node_id, data, "cmdup" - ) - elif "down" in item["tags"]: - data = f"({str(cmds.shutdown[:])[1:-1]})" - dialog = ViewConfigDialog( - self, self.app, self.node_id, data, "cmdup" - ) - elif "val" in item["tags"]: - data = f"({str(cmds.validate[:])[1:-1]})" - dialog = ViewConfigDialog( - self, self.app, self.node_id, data, "cmdup" - ) - dialog.show() - - def get_node_service(self, selected: Tuple[str]) -> Tuple[int, str]: - service_tree_id = self.tree.parent(selected[0]) - service_name = self.tree.item(service_tree_id)["text"] - node_tree_id = self.tree.parent(service_tree_id) - node_id = int(self.tree.item(node_tree_id)["text"][1:]) - return node_id, service_name + selection = self.listbox.curselection() + if not selection: + return + name = self.listbox.get(selection) + canvas_node_id = self.nodes[name] + canvas_node = self.app.canvas.nodes[canvas_node_id] + data = canvas_node.service_file_configs[self.service][self.file_name] + dialog = ViewConfigDialog( + self.app, self, name, self.service, self.file_name, data + ) + dialog.show() class ViewConfigDialog(Dialog): def __init__( self, - master: tk.BaseWidget, app: "Application", - node_id: int, + master: tk.BaseWidget, + name: str, + service: str, + file_name: str, data: str, - filename: str = None, ) -> None: - super().__init__(app, f"n{node_id} config data", master=master) + title = f"{name} Service({service}) File({file_name})" + super().__init__(app, title, master=master) self.data = data self.service_data = None - self.filepath = tk.StringVar(value=f"/tmp/services.tmp-n{node_id}-{filename}") self.draw() def draw(self) -> None: self.top.columnconfigure(0, weight=1) - frame = ttk.Frame(self.top, padding=FRAME_PAD) - frame.columnconfigure(0, weight=1) - frame.columnconfigure(1, weight=10) - frame.grid(row=0, column=0, sticky="ew") - label = ttk.Label(frame, text="File: ") - label.grid(row=0, column=0, sticky="ew", padx=PADX) - entry = ttk.Entry(frame, textvariable=self.filepath) - entry.config(state="disabled") - entry.grid(row=0, column=1, sticky="ew") - + self.top.rowconfigure(0, weight=1) self.service_data = CodeText(self.top) - self.service_data.grid(row=1, column=0, sticky="nsew") - self.service_data.text.insert("end", self.data) - self.service_data.text.config(state="disabled") - + self.service_data.grid(sticky="nsew", pady=PADY) + self.service_data.text.insert(tk.END, self.data) + self.service_data.text.config(state=tk.DISABLED) button = ttk.Button(self.top, text="Close", command=self.destroy) - button.grid(row=2, column=0, sticky="ew", padx=PADX) + button.grid(sticky="ew") diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 5faface7..4e615db0 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -563,7 +563,11 @@ class ServiceConfigDialog(Dialog): self.current_service_color("") def click_copy(self) -> None: - dialog = CopyServiceConfigDialog(self, self.app, self.node_id) + file_name = self.filename_combobox.get() + name = self.canvas_node.core_node.name + dialog = CopyServiceConfigDialog( + self.app, self, name, self.service_name, file_name + ) dialog.show() @classmethod From bb2ceaf99307ca75e21018712faf658c29187064 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 23 Jun 2020 22:53:48 -0700 Subject: [PATCH 102/146] pygui: draw link options on edges --- daemon/core/gui/dialogs/linkconfig.py | 2 ++ daemon/core/gui/graph/edges.py | 35 +++++++++++++++++++++++++++ daemon/core/gui/graph/graph.py | 1 + 3 files changed, 38 insertions(+) diff --git a/daemon/core/gui/dialogs/linkconfig.py b/daemon/core/gui/dialogs/linkconfig.py index b7c618a3..28798ec1 100644 --- a/daemon/core/gui/dialogs/linkconfig.py +++ b/daemon/core/gui/dialogs/linkconfig.py @@ -287,6 +287,8 @@ class LinkConfigurationDialog(Dialog): iface2_id, ) + # update edge label + self.edge.draw_link_options() self.destroy() def change_symmetry(self) -> None: diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 29632086..6c79787f 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -57,6 +57,18 @@ def arc_edges(edges) -> None: edge.redraw() +def bandwidth_label(bandwidth: int) -> str: + size = {0: "bps", 1: "Kbps", 2: "Mbps", 3: "Gbps"} + unit = 1000 + i = 0 + while bandwidth > unit: + bandwidth /= unit + i += 1 + if i == 3: + break + return f"{bandwidth} {size[i]}" + + class Edge: tag: str = tags.EDGE @@ -140,6 +152,7 @@ class Edge: font=self.canvas.app.edge_font, text=text, tags=tags.LINK_LABEL, + justify=tk.CENTER, state=self.canvas.show_link_labels.state(), ) else: @@ -312,6 +325,7 @@ class CanvasEdge(Edge): src_text, dst_text = self.create_node_labels() self.src_label_text(src_text) self.dst_label_text(dst_text) + self.draw_link_options() def redraw(self) -> None: super().redraw() @@ -393,3 +407,24 @@ class CanvasEdge(Edge): def click_configure(self) -> None: dialog = LinkConfigurationDialog(self.canvas.app, self) dialog.show() + + def draw_link_options(self): + options = self.link.options + lines = [] + bandwidth = options.bandwidth + if bandwidth > 0: + lines.append(bandwidth_label(bandwidth)) + delay = options.delay + jitter = options.jitter + if delay > 0 and jitter > 0: + lines.append(f"{delay} us (\u00B1{jitter} us)") + elif jitter > 0: + lines.append(f"0 us (\u00B1{jitter} us)") + loss = options.loss + if loss > 0: + lines.append(f"loss={loss}%") + dup = options.dup + if dup > 0: + lines.append(f"dup={dup}%") + label = "\n".join(lines) + self.middle_label_text(label) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 07519c3f..7d8ec019 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -1000,6 +1000,7 @@ class CanvasGraph(tk.Canvas): def clear_throughputs(self) -> None: for edge in self.edges.values(): edge.clear_middle_label() + edge.draw_link_options() def scale_graph(self) -> None: for nid, canvas_node in self.nodes.items(): From f582306bb97e5f2281093b9dfe3cd00e4e1285d3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 10:35:01 -0700 Subject: [PATCH 103/146] pygui: added support for a details pane, can be toggled on/off, can be used to quickly view details for nodes or links --- daemon/core/gui/app.py | 47 ++++++++++++++++++++++-- daemon/core/gui/frames/__init__.py | 0 daemon/core/gui/frames/base.py | 36 +++++++++++++++++++ daemon/core/gui/frames/default.py | 19 ++++++++++ daemon/core/gui/frames/link.py | 58 ++++++++++++++++++++++++++++++ daemon/core/gui/frames/node.py | 33 +++++++++++++++++ daemon/core/gui/graph/edges.py | 20 ++++------- daemon/core/gui/graph/graph.py | 1 + daemon/core/gui/graph/node.py | 6 ++++ daemon/core/gui/menubar.py | 11 ++++++ daemon/core/gui/task.py | 2 +- daemon/core/gui/utils.py | 10 ++++++ 12 files changed, 226 insertions(+), 17 deletions(-) create mode 100644 daemon/core/gui/frames/__init__.py create mode 100644 daemon/core/gui/frames/base.py create mode 100644 daemon/core/gui/frames/default.py create mode 100644 daemon/core/gui/frames/link.py create mode 100644 daemon/core/gui/frames/node.py create mode 100644 daemon/core/gui/utils.py diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index cb385e9e..e0121d14 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -3,7 +3,7 @@ import math import tkinter as tk from tkinter import PhotoImage, font, ttk from tkinter.ttk import Progressbar -from typing import Dict, Optional +from typing import Any, Dict, Optional, Type import grpc @@ -11,11 +11,14 @@ from core.gui import appconfig, themes from core.gui.appconfig import GuiConfig from core.gui.coreclient import CoreClient from core.gui.dialogs.error import ErrorDialog +from core.gui.frames.base import InfoFrameBase +from core.gui.frames.default import DefaultInfoFrame from core.gui.graph.graph import CanvasGraph from core.gui.images import ImageEnum, Images from core.gui.menubar import Menubar from core.gui.nodeutils import NodeUtils from core.gui.statusbar import StatusBar +from core.gui.themes import PADY from core.gui.toolbar import Toolbar WIDTH: int = 1000 @@ -35,6 +38,9 @@ class Application(ttk.Frame): self.canvas: Optional[CanvasGraph] = None self.statusbar: Optional[StatusBar] = None self.progress: Optional[Progressbar] = None + self.infobar: Optional[ttk.Frame] = None + self.info_frame: Optional[InfoFrameBase] = None + self.show_infobar: tk.BooleanVar = tk.BooleanVar(value=False) # fonts self.fonts_size: Dict[str, int] = {} @@ -113,16 +119,27 @@ class Application(ttk.Frame): self.right_frame.rowconfigure(0, weight=1) self.right_frame.grid(row=0, column=1, sticky="nsew") self.draw_canvas() + self.draw_infobar() self.draw_status() self.progress = Progressbar(self.right_frame, mode="indeterminate") self.menubar = Menubar(self) self.master.config(menu=self.menubar) + def draw_infobar(self) -> None: + self.infobar = ttk.Frame(self.right_frame, padding=5, relief=tk.RAISED) + self.infobar.columnconfigure(0, weight=1) + self.infobar.rowconfigure(1, weight=1) + label_font = font.Font(weight=font.BOLD, underline=tk.TRUE) + label = ttk.Label( + self.infobar, text="Details", anchor=tk.CENTER, font=label_font + ) + label.grid(sticky=tk.EW, pady=PADY) + def draw_canvas(self) -> None: canvas_frame = ttk.Frame(self.right_frame) canvas_frame.rowconfigure(0, weight=1) canvas_frame.columnconfigure(0, weight=1) - canvas_frame.grid(sticky="nsew", pady=1) + canvas_frame.grid(row=0, column=0, sticky="nsew", pady=1) self.canvas = CanvasGraph(canvas_frame, self, self.core) self.canvas.grid(sticky="nsew") scroll_y = ttk.Scrollbar(canvas_frame, command=self.canvas.yview) @@ -136,7 +153,31 @@ class Application(ttk.Frame): def draw_status(self) -> None: self.statusbar = StatusBar(self.right_frame, self) - self.statusbar.grid(sticky="ew") + self.statusbar.grid(sticky="ew", columnspan=2) + + def display_info(self, frame_class: Type[InfoFrameBase], **kwargs: Any) -> None: + if not self.show_infobar.get(): + return + self.clear_info() + self.info_frame = frame_class(self.infobar, **kwargs) + self.info_frame.draw() + self.info_frame.grid(sticky="nsew") + + def clear_info(self) -> None: + if self.info_frame: + self.info_frame.destroy() + self.info_frame = None + + def default_info(self) -> None: + self.clear_info() + self.display_info(DefaultInfoFrame, app=self) + + def show_info(self) -> None: + self.default_info() + self.infobar.grid(row=0, column=1, sticky="nsew") + + def hide_info(self) -> None: + self.infobar.grid_forget() def show_grpc_exception(self, title: str, e: grpc.RpcError) -> None: logging.exception("app grpc exception", exc_info=e) diff --git a/daemon/core/gui/frames/__init__.py b/daemon/core/gui/frames/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/daemon/core/gui/frames/base.py b/daemon/core/gui/frames/base.py new file mode 100644 index 00000000..8db952f1 --- /dev/null +++ b/daemon/core/gui/frames/base.py @@ -0,0 +1,36 @@ +import tkinter as tk +from tkinter import ttk +from typing import TYPE_CHECKING + +from core.gui.themes import FRAME_PAD, PADX, PADY + +if TYPE_CHECKING: + from core.gui.app import Application + + +class InfoFrameBase(ttk.Frame): + def __init__(self, master: tk.BaseWidget, app: "Application") -> None: + super().__init__(master, padding=FRAME_PAD) + self.app: "Application" = app + + def draw(self) -> None: + raise NotImplementedError + + +class DetailsFrame(ttk.Frame): + def __init__(self, master: tk.BaseWidget) -> None: + super().__init__(master) + self.columnconfigure(1, weight=1) + self.row = 0 + + def add_detail(self, label: str, value: str) -> None: + label = ttk.Label(self, text=label, anchor=tk.W) + label.grid(row=self.row, sticky=tk.EW, column=0, padx=PADX) + label = ttk.Label(self, text=value, anchor=tk.W, state=tk.DISABLED) + label.grid(row=self.row, sticky=tk.EW, column=1) + self.row += 1 + + def add_separator(self) -> None: + separator = ttk.Separator(self) + separator.grid(row=self.row, sticky=tk.EW, columnspan=2, pady=PADY) + self.row += 1 diff --git a/daemon/core/gui/frames/default.py b/daemon/core/gui/frames/default.py new file mode 100644 index 00000000..e84edb87 --- /dev/null +++ b/daemon/core/gui/frames/default.py @@ -0,0 +1,19 @@ +import tkinter as tk +from tkinter import ttk +from typing import TYPE_CHECKING + +from core.gui.frames.base import InfoFrameBase + +if TYPE_CHECKING: + from core.gui.app import Application + + +class DefaultInfoFrame(InfoFrameBase): + def __init__(self, master: tk.BaseWidget, app: "Application") -> None: + super().__init__(master, app) + + def draw(self) -> None: + label = ttk.Label(self, text="Click a Node/Link", anchor=tk.CENTER) + label.grid(sticky=tk.EW) + label = ttk.Label(self, text="to see details", anchor=tk.CENTER) + label.grid(sticky=tk.EW) diff --git a/daemon/core/gui/frames/link.py b/daemon/core/gui/frames/link.py new file mode 100644 index 00000000..29b3df45 --- /dev/null +++ b/daemon/core/gui/frames/link.py @@ -0,0 +1,58 @@ +import tkinter as tk +from typing import TYPE_CHECKING + +from core.gui.frames.base import DetailsFrame, InfoFrameBase +from core.gui.utils import bandwidth_text + +if TYPE_CHECKING: + from core.gui.app import Application + from core.gui.graph.edges import CanvasEdge + + +class EdgeInfoFrame(InfoFrameBase): + def __init__( + self, master: tk.BaseWidget, app: "Application", edge: "CanvasEdge" + ) -> None: + super().__init__(master, app) + self.edge: "CanvasEdge" = edge + + def draw(self) -> None: + self.columnconfigure(0, weight=1) + link = self.edge.link + options = link.options + src_canvas_node = self.app.core.canvas_nodes[link.node1_id] + src_node = src_canvas_node.core_node + dst_canvas_node = self.app.core.canvas_nodes[link.node2_id] + dst_node = dst_canvas_node.core_node + + frame = DetailsFrame(self) + frame.grid(sticky="ew") + frame.add_detail("Source", src_node.name) + iface1 = link.iface1 + if iface1: + mac = iface1.mac if iface1.mac else "auto" + frame.add_detail("MAC", mac) + ip4 = f"{iface1.ip4}/{iface1.ip4_mask}" if iface1.ip4 else "" + frame.add_detail("IP4", ip4) + ip6 = f"{iface1.ip6}/{iface1.ip6_mask}" if iface1.ip6 else "" + frame.add_detail("IP6", ip6) + + frame.add_separator() + frame.add_detail("Destination", dst_node.name) + iface2 = link.iface2 + if iface2: + mac = iface2.mac if iface2.mac else "auto" + frame.add_detail("MAC", mac) + ip4 = f"{iface2.ip4}/{iface2.ip4_mask}" if iface2.ip4 else "" + frame.add_detail("IP4", ip4) + ip6 = f"{iface2.ip6}/{iface2.ip6_mask}" if iface2.ip6 else "" + frame.add_detail("IP6", ip6) + + if link.HasField("options"): + frame.add_separator() + bandwidth = bandwidth_text(options.bandwidth) + frame.add_detail("Bandwidth", bandwidth) + frame.add_detail("Delay", f"{options.delay} us") + frame.add_detail("Jitter", f"\u00B1{options.jitter} us") + frame.add_detail("Loss", f"{options.loss}%") + frame.add_detail("Duplicate", f"{options.dup}%") diff --git a/daemon/core/gui/frames/node.py b/daemon/core/gui/frames/node.py new file mode 100644 index 00000000..44724f36 --- /dev/null +++ b/daemon/core/gui/frames/node.py @@ -0,0 +1,33 @@ +from typing import TYPE_CHECKING + +from core.api.grpc.core_pb2 import NodeType +from core.gui.frames.base import DetailsFrame, InfoFrameBase +from core.gui.nodeutils import NodeUtils + +if TYPE_CHECKING: + from core.gui.app import Application + from core.gui.graph.node import CanvasNode + + +class NodeInfoFrame(InfoFrameBase): + def __init__(self, master, app: "Application", canvas_node: "CanvasNode") -> None: + super().__init__(master, app) + self.canvas_node: "CanvasNode" = canvas_node + + def draw(self) -> None: + self.columnconfigure(0, weight=1) + node = self.canvas_node.core_node + frame = DetailsFrame(self) + frame.grid(sticky="ew") + frame.add_detail("ID", node.id) + frame.add_detail("Name", node.name) + if NodeUtils.is_model_node(node.type): + frame.add_detail("Type", node.model) + if node.type == NodeType.EMANE: + emane = node.emane.split("_")[1:] + frame.add_detail("EMANE", emane) + if NodeUtils.is_image_node(node.type): + frame.add_detail("Image", node.image) + if NodeUtils.is_container_node(node.type): + server = node.server if node.server else "localhost" + frame.add_detail("Server", server) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 6c79787f..de063bac 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -7,8 +7,10 @@ from core.api.grpc import core_pb2 from core.api.grpc.core_pb2 import Interface, Link from core.gui import themes from core.gui.dialogs.linkconfig import LinkConfigurationDialog +from core.gui.frames.link import EdgeInfoFrame from core.gui.graph import tags from core.gui.nodeutils import NodeUtils +from core.gui.utils import bandwidth_text if TYPE_CHECKING: from core.gui.graph.graph import CanvasGraph @@ -57,18 +59,6 @@ def arc_edges(edges) -> None: edge.redraw() -def bandwidth_label(bandwidth: int) -> str: - size = {0: "bps", 1: "Kbps", 2: "Mbps", 3: "Gbps"} - unit = 1000 - i = 0 - while bandwidth > unit: - bandwidth /= unit - i += 1 - if i == 3: - break - return f"{bandwidth} {size[i]}" - - class Edge: tag: str = tags.EDGE @@ -295,6 +285,7 @@ class CanvasEdge(Edge): def set_binding(self) -> None: self.canvas.tag_bind(self.id, "", self.show_context) + self.canvas.tag_bind(self.id, "", self.show_info) def set_link(self, link: Link) -> None: self.link = link @@ -396,6 +387,9 @@ class CanvasEdge(Edge): self.middle_label = None self.canvas.itemconfig(self.id, fill=self.color, width=self.scaled_width()) + def show_info(self, _event: tk.Event) -> None: + self.canvas.app.display_info(EdgeInfoFrame, app=self.canvas.app, edge=self) + def show_context(self, event: tk.Event) -> None: state = tk.DISABLED if self.canvas.core.is_runtime() else tk.NORMAL self.context.entryconfigure(1, state=state) @@ -413,7 +407,7 @@ class CanvasEdge(Edge): lines = [] bandwidth = options.bandwidth if bandwidth > 0: - lines.append(bandwidth_label(bandwidth)) + lines.append(bandwidth_text(bandwidth)) delay = options.delay jitter = options.jitter if delay > 0 and jitter > 0: diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 7d8ec019..1588f920 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -715,6 +715,7 @@ class CanvasGraph(tk.Canvas): logging.debug("press delete key") if not self.app.core.is_runtime(): self.delete_selected_objects() + self.app.default_info() else: logging.debug("node deletion is disabled during runtime state") diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index a86ce4a3..d98c4e48 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -16,6 +16,7 @@ from core.gui.dialogs.nodeconfig import NodeConfigDialog from core.gui.dialogs.nodeconfigservice import NodeConfigServiceDialog from core.gui.dialogs.nodeservice import NodeServiceDialog from core.gui.dialogs.wlanconfig import WlanConfigDialog +from core.gui.frames.node import NodeInfoFrame from core.gui.graph import tags from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip @@ -80,6 +81,7 @@ class CanvasNode: self.canvas.tag_bind(self.id, "", self.on_enter) self.canvas.tag_bind(self.id, "", self.on_leave) self.canvas.tag_bind(self.id, "", self.show_context) + self.canvas.tag_bind(self.id, "", self.show_info) def delete(self) -> None: logging.debug("Delete canvas node for %s", self.core_node) @@ -195,6 +197,9 @@ class CanvasNode: else: self.show_config() + def show_info(self, _event: tk.Event) -> None: + self.app.display_info(NodeInfoFrame, app=self.app, canvas_node=self) + def show_context(self, event: tk.Event) -> None: # clear existing menu self.context.delete(0, tk.END) @@ -262,6 +267,7 @@ class CanvasNode: def click_unlink(self, edge: CanvasEdge) -> None: self.canvas.delete_edge(edge) + self.app.default_info() def canvas_delete(self) -> None: self.canvas.clear_selection() diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 75312e95..3b85ac6f 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -138,6 +138,11 @@ class Menubar(tk.Menu): Create view menu """ menu = tk.Menu(self) + menu.add_checkbutton( + label="Details Panel", + command=self.click_infobar_change, + variable=self.app.show_infobar, + ) menu.add_checkbutton( label="Interface Names", command=self.click_edge_label_change, @@ -443,6 +448,12 @@ class Menubar(tk.Menu): y = (row * layout_size) + padding node.move(x, y) + def click_infobar_change(self) -> None: + if self.app.show_infobar.get(): + self.app.show_info() + else: + self.app.hide_info() + def click_edge_label_change(self) -> None: for edge in self.canvas.edges.values(): edge.draw_labels() diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py index b4a5f68f..c60350f9 100644 --- a/daemon/core/gui/task.py +++ b/daemon/core/gui/task.py @@ -26,7 +26,7 @@ class ProgressTask: self.time: Optional[float] = None def start(self) -> None: - self.app.progress.grid(sticky="ew") + self.app.progress.grid(sticky="ew", columnspan=2) self.app.progress.start() self.time = time.perf_counter() thread = threading.Thread(target=self.run, daemon=True) diff --git a/daemon/core/gui/utils.py b/daemon/core/gui/utils.py new file mode 100644 index 00000000..ee5ad8cb --- /dev/null +++ b/daemon/core/gui/utils.py @@ -0,0 +1,10 @@ +def bandwidth_text(bandwidth: int) -> str: + size = {0: "bps", 1: "Kbps", 2: "Mbps", 3: "Gbps"} + unit = 1000 + i = 0 + while bandwidth > unit: + bandwidth /= unit + i += 1 + if i == 3: + break + return f"{bandwidth} {size[i]}" From 98e4baca046f4e8ea0b4ba254bd1dec444d067c2 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 15:05:24 -0700 Subject: [PATCH 104/146] pygui: added services to node info panel --- daemon/core/gui/frames/node.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/daemon/core/gui/frames/node.py b/daemon/core/gui/frames/node.py index 44724f36..7480e056 100644 --- a/daemon/core/gui/frames/node.py +++ b/daemon/core/gui/frames/node.py @@ -23,6 +23,12 @@ class NodeInfoFrame(InfoFrameBase): frame.add_detail("Name", node.name) if NodeUtils.is_model_node(node.type): frame.add_detail("Type", node.model) + if NodeUtils.is_container_node(node.type): + for index, service in enumerate(sorted(node.services)): + if index == 0: + frame.add_detail("Services", service) + else: + frame.add_detail("", service) if node.type == NodeType.EMANE: emane = node.emane.split("_")[1:] frame.add_detail("EMANE", emane) From 3bfc299bfd96343b0a4afed6c7d19c95fec0c700 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 16:22:56 -0700 Subject: [PATCH 105/146] daemon: fixed typo in core.configservices.securityservices --- .../{sercurityservices => securityservices}/__init__.py | 0 .../{sercurityservices => securityservices}/services.py | 0 .../{sercurityservices => securityservices}/templates/firewall.sh | 0 .../{sercurityservices => securityservices}/templates/ipsec.sh | 0 .../{sercurityservices => securityservices}/templates/nat.sh | 0 .../templates/vpnclient.sh | 0 .../templates/vpnserver.sh | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename daemon/core/configservices/{sercurityservices => securityservices}/__init__.py (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/services.py (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/firewall.sh (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/ipsec.sh (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/nat.sh (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/vpnclient.sh (100%) rename daemon/core/configservices/{sercurityservices => securityservices}/templates/vpnserver.sh (100%) diff --git a/daemon/core/configservices/sercurityservices/__init__.py b/daemon/core/configservices/securityservices/__init__.py similarity index 100% rename from daemon/core/configservices/sercurityservices/__init__.py rename to daemon/core/configservices/securityservices/__init__.py diff --git a/daemon/core/configservices/sercurityservices/services.py b/daemon/core/configservices/securityservices/services.py similarity index 100% rename from daemon/core/configservices/sercurityservices/services.py rename to daemon/core/configservices/securityservices/services.py diff --git a/daemon/core/configservices/sercurityservices/templates/firewall.sh b/daemon/core/configservices/securityservices/templates/firewall.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/firewall.sh rename to daemon/core/configservices/securityservices/templates/firewall.sh diff --git a/daemon/core/configservices/sercurityservices/templates/ipsec.sh b/daemon/core/configservices/securityservices/templates/ipsec.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/ipsec.sh rename to daemon/core/configservices/securityservices/templates/ipsec.sh diff --git a/daemon/core/configservices/sercurityservices/templates/nat.sh b/daemon/core/configservices/securityservices/templates/nat.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/nat.sh rename to daemon/core/configservices/securityservices/templates/nat.sh diff --git a/daemon/core/configservices/sercurityservices/templates/vpnclient.sh b/daemon/core/configservices/securityservices/templates/vpnclient.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/vpnclient.sh rename to daemon/core/configservices/securityservices/templates/vpnclient.sh diff --git a/daemon/core/configservices/sercurityservices/templates/vpnserver.sh b/daemon/core/configservices/securityservices/templates/vpnserver.sh similarity index 100% rename from daemon/core/configservices/sercurityservices/templates/vpnserver.sh rename to daemon/core/configservices/securityservices/templates/vpnserver.sh From b94d4d35071b8e07f01fbf4469ada87df054ad18 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 21:34:45 -0700 Subject: [PATCH 106/146] daemon: updated open xml with start flag to set instantiation state before running instantiate to be consistent with other cases --- daemon/core/emulator/session.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 630e1a0f..92d4b5e1 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -637,19 +637,16 @@ class Session: # clear out existing session self.clear() - if start: - state = EventTypes.CONFIGURATION_STATE - else: - state = EventTypes.DEFINITION_STATE + # set state and read xml + state = EventTypes.CONFIGURATION_STATE if start else EventTypes.DEFINITION_STATE self.set_state(state) self.name = os.path.basename(file_name) self.file_name = file_name - - # write out xml file CoreXmlReader(self).read(file_name) # start session if needed if start: + self.set_state(EventTypes.INSTANTIATION_STATE) self.instantiate() def save_xml(self, file_name: str) -> None: From f4224d1b80060fd7e5c1bc4fd771be29c01f23b5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 25 Jun 2020 22:05:10 -0700 Subject: [PATCH 107/146] daemon: updated ovs option to be a formal session option, will now display within gui, save to and be read from xml --- daemon/core/emulator/coreemu.py | 2 +- daemon/core/emulator/session.py | 3 +++ daemon/core/emulator/sessionconfig.py | 3 +++ daemon/core/nodes/base.py | 10 ++++++---- daemon/core/nodes/interface.py | 5 +++-- daemon/core/nodes/network.py | 2 +- daemon/scripts/core-daemon | 3 +++ 7 files changed, 20 insertions(+), 8 deletions(-) diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 71723268..016f2e5b 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -80,7 +80,7 @@ class CoreEmu: :raises core.errors.CoreError: when an executable does not exist on path """ requirements = COMMON_REQUIREMENTS - use_ovs = self.config.get("ovs") == "True" + use_ovs = self.config.get("ovs") == "1" if use_ovs: requirements += OVS_REQUIREMENTS else: diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 92d4b5e1..c2573578 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -217,6 +217,9 @@ class Session: else: common_network.unlink(iface1, iface2) + def use_ovs(self) -> bool: + return self.options.get_config("ovs") == "1" + def add_link( self, node1_id: int, diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py index e22e852e..9b22bcc7 100644 --- a/daemon/core/emulator/sessionconfig.py +++ b/daemon/core/emulator/sessionconfig.py @@ -56,6 +56,9 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions): default=Sdt.DEFAULT_SDT_URL, label="SDT3D URL", ), + Configuration( + _id="ovs", _type=ConfigDataTypes.BOOL, default="0", label="Enable OVS" + ), ] config_type: RegisterTlvs = RegisterTlvs.UTILITY diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 3999046d..05ec87dc 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -73,8 +73,9 @@ class NodeBase(abc.ABC): self.icon: Optional[str] = None self.position: Position = Position() self.up: bool = False - use_ovs = session.options.get_config("ovs") == "True" - self.net_client: LinuxNetClient = get_net_client(use_ovs, self.host_cmd) + self.net_client: LinuxNetClient = get_net_client( + self.session.use_ovs(), self.host_cmd + ) @abc.abstractmethod def startup(self) -> None: @@ -471,8 +472,9 @@ class CoreNode(CoreNodeBase): self.pid: Optional[int] = None self.lock: RLock = RLock() self._mounts: List[Tuple[str, str]] = [] - use_ovs = session.options.get_config("ovs") == "True" - self.node_net_client: LinuxNetClient = self.create_node_net_client(use_ovs) + self.node_net_client: LinuxNetClient = self.create_node_net_client( + self.session.use_ovs() + ) def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: """ diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 22ecb620..e4d4d0ac 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -68,8 +68,9 @@ class CoreInterface: # id used to find flow data self.flow_id: Optional[int] = None self.server: Optional["DistributedServer"] = server - use_ovs = session.options.get_config("ovs") == "True" - self.net_client: LinuxNetClient = get_net_client(use_ovs, self.host_cmd) + self.net_client: LinuxNetClient = get_net_client( + self.session.use_ovs(), self.host_cmd + ) def host_cmd( self, diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index d418a42c..a55de4cf 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -756,7 +756,7 @@ class CtrlNet(CoreNetwork): :param index: starting address index :return: nothing """ - use_ovs = self.session.options.get_config("ovs") == "True" + use_ovs = self.session.use_ovs() address = self.prefix[index] current = f"{address}/{self.prefix.prefixlen}" net_client = get_net_client(use_ovs, utils.cmd) diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index a95e59fa..16b0ac59 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -118,6 +118,9 @@ def get_merged_config(filename): # parse command line options args = parser.parse_args() + # convert ovs to internal format + args.ovs = "1" if args.ovs else "0" + # read the config file if args.configfile is not None: filename = args.configfile From eac941ce7265eb850ae9f153fd9ea0a88f8eedf1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 09:13:38 -0700 Subject: [PATCH 108/146] pygui: updates to show wireless edges in details panel, increased edge thickness to be the same as normal edges for selection to be easier --- daemon/core/gui/frames/link.py | 57 +++++++++++++++++++++++++++++++++- daemon/core/gui/graph/edges.py | 20 ++++++++++-- daemon/core/gui/graph/graph.py | 6 +--- 3 files changed, 74 insertions(+), 9 deletions(-) diff --git a/daemon/core/gui/frames/link.py b/daemon/core/gui/frames/link.py index 29b3df45..57b1bf66 100644 --- a/daemon/core/gui/frames/link.py +++ b/daemon/core/gui/frames/link.py @@ -1,12 +1,26 @@ import tkinter as tk -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional +from core.api.grpc.core_pb2 import Interface from core.gui.frames.base import DetailsFrame, InfoFrameBase from core.gui.utils import bandwidth_text if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.edges import CanvasEdge + from core.gui.graph.node import CanvasNode + from core.gui.graph.edges import CanvasWirelessEdge + + +def get_iface(canvas_node: "CanvasNode", net_id: int) -> Optional[Interface]: + iface = None + for edge in canvas_node.edges: + link = edge.link + if link.node1_id == net_id: + iface = link.iface2 + elif link.node2_id == net_id: + iface = link.iface1 + return iface class EdgeInfoFrame(InfoFrameBase): @@ -56,3 +70,44 @@ class EdgeInfoFrame(InfoFrameBase): frame.add_detail("Jitter", f"\u00B1{options.jitter} us") frame.add_detail("Loss", f"{options.loss}%") frame.add_detail("Duplicate", f"{options.dup}%") + + +class WirelessEdgeInfoFrame(InfoFrameBase): + def __init__( + self, master: tk.BaseWidget, app: "Application", edge: "CanvasWirelessEdge" + ) -> None: + super().__init__(master, app) + self.edge: "CanvasWirelessEdge" = edge + + def draw(self) -> None: + link = self.edge.link + src_canvas_node = self.app.core.canvas_nodes[link.node1_id] + src_node = src_canvas_node.core_node + dst_canvas_node = self.app.core.canvas_nodes[link.node2_id] + dst_node = dst_canvas_node.core_node + + # find interface for each node connected to network + net_id = link.network_id + iface1 = get_iface(src_canvas_node, net_id) + iface2 = get_iface(dst_canvas_node, net_id) + + frame = DetailsFrame(self) + frame.grid(sticky="ew") + frame.add_detail("Source", src_node.name) + if iface1: + mac = iface1.mac if iface1.mac else "auto" + frame.add_detail("MAC", mac) + ip4 = f"{iface1.ip4}/{iface1.ip4_mask}" if iface1.ip4 else "" + frame.add_detail("IP4", ip4) + ip6 = f"{iface1.ip6}/{iface1.ip6_mask}" if iface1.ip6 else "" + frame.add_detail("IP6", ip6) + + frame.add_separator() + frame.add_detail("Destination", dst_node.name) + if iface2: + mac = iface2.mac if iface2.mac else "auto" + frame.add_detail("MAC", mac) + ip4 = f"{iface2.ip4}/{iface2.ip4_mask}" if iface2.ip4 else "" + frame.add_detail("IP4", ip4) + ip6 = f"{iface2.ip6}/{iface2.ip6_mask}" if iface2.ip6 else "" + frame.add_detail("IP6", ip6) diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index de063bac..d9085910 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -7,7 +7,7 @@ from core.api.grpc import core_pb2 from core.api.grpc.core_pb2 import Interface, Link from core.gui import themes from core.gui.dialogs.linkconfig import LinkConfigurationDialog -from core.gui.frames.link import EdgeInfoFrame +from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame from core.gui.graph import tags from core.gui.nodeutils import NodeUtils from core.gui.utils import bandwidth_text @@ -18,7 +18,7 @@ if TYPE_CHECKING: TEXT_DISTANCE: float = 0.30 EDGE_WIDTH: int = 3 EDGE_COLOR: str = "#ff0000" -WIRELESS_WIDTH: float = 1.5 +WIRELESS_WIDTH: float = 3 WIRELESS_COLOR: str = "#009933" ARC_DISTANCE: int = 50 @@ -241,13 +241,27 @@ class CanvasWirelessEdge(Edge): src_pos: Tuple[float, float], dst_pos: Tuple[float, float], token: Tuple[int, ...], + link: Link, ) -> None: logging.debug("drawing wireless link from node %s to node %s", src, dst) super().__init__(canvas, src, dst) + self.link: Link = link self.token: Tuple[int, ...] = token self.width: float = WIRELESS_WIDTH - self.color: str = WIRELESS_COLOR + color = link.color if link.color else WIRELESS_COLOR + self.color: str = color self.draw(src_pos, dst_pos) + if link.label: + self.middle_label_text(link.label) + self.set_binding() + + def set_binding(self) -> None: + self.canvas.tag_bind(self.id, "", self.show_info) + + def show_info(self, _event: tk.Event) -> None: + self.canvas.app.display_info( + WirelessEdgeInfoFrame, app=self.canvas.app, edge=self + ) class CanvasEdge(Edge): diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 1588f920..a3520d22 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -233,11 +233,7 @@ class CanvasGraph(tk.Canvas): return src_pos = self.coords(src.id) dst_pos = self.coords(dst.id) - edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token) - if link.label: - edge.middle_label_text(link.label) - if link.color: - edge.color = link.color + edge = CanvasWirelessEdge(self, src.id, dst.id, src_pos, dst_pos, token, link) self.wireless_edges[token] = edge src.wireless_edges.add(edge) dst.wireless_edges.add(edge) From aebbff8c224a90a818417e5a512ad43204c6a493 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 14:39:12 -0700 Subject: [PATCH 109/146] grpc/pygui: shifted source field in node events to base event message to apply to all events, updated add_link/delete_link rpc calls to broadcast events, updated pygui to handle these events --- daemon/core/api/grpc/events.py | 132 ++++++++++++++------------ daemon/core/api/grpc/grpcutils.py | 18 ++++ daemon/core/api/grpc/server.py | 30 +++++- daemon/core/emulator/data.py | 1 + daemon/core/emulator/session.py | 3 +- daemon/core/gui/coreclient.py | 33 +++++-- daemon/core/gui/graph/graph.py | 69 ++++++++------ daemon/proto/core/api/grpc/core.proto | 4 +- 8 files changed, 185 insertions(+), 105 deletions(-) diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 75f9eb2e..5c873a43 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -1,6 +1,6 @@ import logging from queue import Empty, Queue -from typing import Iterable +from typing import Iterable, Optional from core.api.grpc import core_pb2 from core.api.grpc.grpcutils import convert_link @@ -15,7 +15,7 @@ from core.emulator.data import ( from core.emulator.session import Session -def handle_node_event(node_data: NodeData) -> core_pb2.NodeEvent: +def handle_node_event(node_data: NodeData) -> core_pb2.Event: """ Handle node event when there is a node event @@ -36,98 +36,105 @@ def handle_node_event(node_data: NodeData) -> core_pb2.NodeEvent: geo=geo, services=services, ) - return core_pb2.NodeEvent(node=node_proto, source=node_data.source) + node_event = core_pb2.NodeEvent(node=node_proto) + return core_pb2.Event(node_event=node_event, source=node_data.source) -def handle_link_event(event: LinkData) -> core_pb2.LinkEvent: +def handle_link_event(link_data: LinkData) -> core_pb2.Event: """ Handle link event when there is a link event - :param event: link data + :param link_data: link data :return: link event that has message type and link information """ - link = convert_link(event) - return core_pb2.LinkEvent(message_type=event.message_type.value, link=link) + link = convert_link(link_data) + message_type = link_data.message_type.value + link_event = core_pb2.LinkEvent(message_type=message_type, link=link) + return core_pb2.Event(link_event=link_event, source=link_data.source) -def handle_session_event(event: EventData) -> core_pb2.SessionEvent: +def handle_session_event(event_data: EventData) -> core_pb2.Event: """ Handle session event when there is a session event - :param event: event data + :param event_data: event data :return: session event """ - event_time = event.time + event_time = event_data.time if event_time is not None: event_time = float(event_time) - return core_pb2.SessionEvent( - node_id=event.node, - event=event.event_type.value, - name=event.name, - data=event.data, + session_event = core_pb2.SessionEvent( + node_id=event_data.node, + event=event_data.event_type.value, + name=event_data.name, + data=event_data.data, time=event_time, ) + return core_pb2.Event(session_event=session_event) -def handle_config_event(event: ConfigData) -> core_pb2.ConfigEvent: +def handle_config_event(config_data: ConfigData) -> core_pb2.Event: """ Handle configuration event when there is configuration event - :param event: configuration data + :param config_data: configuration data :return: configuration event """ - return core_pb2.ConfigEvent( - message_type=event.message_type, - node_id=event.node, - object=event.object, - type=event.type, - captions=event.captions, - bitmap=event.bitmap, - data_values=event.data_values, - possible_values=event.possible_values, - groups=event.groups, - iface_id=event.iface_id, - network_id=event.network_id, - opaque=event.opaque, - data_types=event.data_types, + config_event = core_pb2.ConfigEvent( + message_type=config_data.message_type, + node_id=config_data.node, + object=config_data.object, + type=config_data.type, + captions=config_data.captions, + bitmap=config_data.bitmap, + data_values=config_data.data_values, + possible_values=config_data.possible_values, + groups=config_data.groups, + iface_id=config_data.iface_id, + network_id=config_data.network_id, + opaque=config_data.opaque, + data_types=config_data.data_types, ) + return core_pb2.Event(config_event=config_event) -def handle_exception_event(event: ExceptionData) -> core_pb2.ExceptionEvent: +def handle_exception_event(exception_data: ExceptionData) -> core_pb2.Event: """ Handle exception event when there is exception event - :param event: exception data + :param exception_data: exception data :return: exception event """ - return core_pb2.ExceptionEvent( - node_id=event.node, - level=event.level.value, - source=event.source, - date=event.date, - text=event.text, - opaque=event.opaque, + exception_event = core_pb2.ExceptionEvent( + node_id=exception_data.node, + level=exception_data.level.value, + source=exception_data.source, + date=exception_data.date, + text=exception_data.text, + opaque=exception_data.opaque, ) + return core_pb2.Event(exception_event=exception_event) -def handle_file_event(event: FileData) -> core_pb2.FileEvent: +def handle_file_event(file_data: FileData) -> core_pb2.Event: """ Handle file event - :param event: file data + :param file_data: file data :return: file event """ - return core_pb2.FileEvent( - message_type=event.message_type.value, - node_id=event.node, - name=event.name, - mode=event.mode, - number=event.number, - type=event.type, - source=event.source, - data=event.data, - compressed_data=event.compressed_data, + file_event = core_pb2.FileEvent( + message_type=file_data.message_type.value, + node_id=file_data.node, + name=file_data.name, + mode=file_data.mode, + number=file_data.number, + type=file_data.type, + source=file_data.source, + data=file_data.data, + compressed_data=file_data.compressed_data, ) + return core_pb2.Event(file_event=file_event) class EventStreamer: @@ -168,32 +175,33 @@ class EventStreamer: if core_pb2.EventType.SESSION in self.event_types: self.session.event_handlers.append(self.queue.put) - def process(self) -> core_pb2.Event: + def process(self) -> Optional[core_pb2.Event]: """ Process the next event in the queue. :return: grpc event, or None when invalid event or queue timeout """ - event = core_pb2.Event(session_id=self.session.id) + event = None try: data = self.queue.get(timeout=1) if isinstance(data, NodeData): - event.node_event.CopyFrom(handle_node_event(data)) + event = handle_node_event(data) elif isinstance(data, LinkData): - event.link_event.CopyFrom(handle_link_event(data)) + event = handle_link_event(data) elif isinstance(data, EventData): - event.session_event.CopyFrom(handle_session_event(data)) + event = handle_session_event(data) elif isinstance(data, ConfigData): - event.config_event.CopyFrom(handle_config_event(data)) + event = handle_config_event(data) elif isinstance(data, ExceptionData): - event.exception_event.CopyFrom(handle_exception_event(data)) + event = handle_exception_event(data) elif isinstance(data, FileData): - event.file_event.CopyFrom(handle_file_event(data)) + event = handle_file_event(data) else: logging.error("unknown event: %s", data) - event = None except Empty: - event = None + pass + if event: + event.session_id = self.session.id return event def remove_handlers(self) -> None: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 8df545cd..ed40a75b 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -435,6 +435,24 @@ def get_service_configuration(service: CoreService) -> NodeServiceData: ) +def iface_to_data(iface: CoreInterface) -> InterfaceData: + ip4 = iface.get_ip4() + ip4_addr = str(ip4.ip) if ip4 else None + ip4_mask = ip4.prefixlen if ip4 else None + ip6 = iface.get_ip6() + ip6_addr = str(ip6.ip) if ip6 else None + ip6_mask = ip6.prefixlen if ip6 else None + return InterfaceData( + id=iface.node_id, + name=iface.name, + mac=str(iface.mac), + ip4=ip4_addr, + ip4_mask=ip4_mask, + ip6=ip6_addr, + ip6_mask=ip6_mask, + ) + + def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: """ Convenience for converting a core interface to the protobuf representation. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 1964b6e8..2883103d 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -108,7 +108,7 @@ from core.api.grpc.wlan_pb2 import ( WlanLinkResponse, ) from core.emulator.coreemu import CoreEmu -from core.emulator.data import LinkData, LinkOptions, NodeOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError @@ -853,6 +853,22 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): node1_iface, node2_iface = session.add_link( node1_id, node2_id, iface1_data, iface2_data, options, link_type ) + iface1_data = None + if node1_iface: + iface1_data = grpcutils.iface_to_data(node1_iface) + iface2_data = None + if node2_iface: + iface2_data = grpcutils.iface_to_data(node2_iface) + source = request.source if request.source else None + link_data = LinkData( + message_type=MessageFlags.ADD, + node1_id=node1_id, + node2_id=node2_id, + iface1=iface1_data, + iface2=iface2_data, + source=source, + ) + session.broadcast_link(link_data) iface1_proto = None iface2_proto = None if node1_iface: @@ -912,6 +928,18 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): iface1_id = request.iface1_id iface2_id = request.iface2_id session.delete_link(node1_id, node2_id, iface1_id, iface2_id) + iface1 = InterfaceData(id=iface1_id) + iface2 = InterfaceData(id=iface2_id) + source = request.source if request.source else None + link_data = LinkData( + message_type=MessageFlags.DELETE, + node1_id=node1_id, + node2_id=node2_id, + iface1=iface1, + iface2=iface2, + source=source, + ) + session.broadcast_link(link_data) return core_pb2.DeleteLinkResponse(result=True) def GetHooks( diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 22d10d2d..15d922a9 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -190,6 +190,7 @@ class LinkData: iface2: InterfaceData = None options: LinkOptions = LinkOptions() color: str = None + source: str = None class IpPrefixes: diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index c2573578..d2f64dde 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -833,11 +833,12 @@ class Session: for handler in self.config_handlers: handler(config_data) - def broadcast_link(self, link_data: LinkData) -> None: + def broadcast_link(self, link_data: LinkData, source: str = None) -> None: """ Handle link data that should be provided to link handlers. :param link_data: link data to send out + :param source: source of broadcast, None by default :return: nothing """ for handler in self.link_handlers: diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index d35f62e5..a5b96e17 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -148,6 +148,8 @@ class CoreClient: self.custom_observers[observer.name] = observer def handle_events(self, event: Event) -> None: + if event.source == GUI_SOURCE: + return if event.session_id != self.session_id: logging.warning( "ignoring event session(%s) current(%s)", @@ -193,19 +195,32 @@ class CoreClient: return canvas_node1 = self.canvas_nodes[node1_id] canvas_node2 = self.canvas_nodes[node2_id] - if event.message_type == MessageType.ADD: - self.app.canvas.add_wireless_edge(canvas_node1, canvas_node2, event.link) - elif event.message_type == MessageType.DELETE: - self.app.canvas.delete_wireless_edge(canvas_node1, canvas_node2, event.link) - elif event.message_type == MessageType.NONE: - self.app.canvas.update_wireless_edge(canvas_node1, canvas_node2, event.link) + if event.link.type == LinkType.WIRELESS: + if event.message_type == MessageType.ADD: + self.app.canvas.add_wireless_edge( + canvas_node1, canvas_node2, event.link + ) + elif event.message_type == MessageType.DELETE: + self.app.canvas.delete_wireless_edge( + canvas_node1, canvas_node2, event.link + ) + elif event.message_type == MessageType.NONE: + self.app.canvas.update_wireless_edge( + canvas_node1, canvas_node2, event.link + ) + else: + logging.warning("unknown link event: %s", event) else: - logging.warning("unknown link event: %s", event) + if event.message_type == MessageType.ADD: + self.app.canvas.add_wired_edge(canvas_node1, canvas_node2, event.link) + self.app.canvas.organize() + elif event.message_type == MessageType.DELETE: + self.app.canvas.delete_wired_edge(canvas_node1, canvas_node2) + else: + logging.warning("unknown link event: %s", event) def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) - if event.source == GUI_SOURCE: - return node_id = event.node.id x = event.node.position.x y = event.node.position.y diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index a3520d22..4e0358a5 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -225,6 +225,43 @@ class CanvasGraph(tk.Canvas): self.tag_lower(tags.GRIDLINE) self.tag_lower(self.rect) + def add_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: + token = create_edge_token(src.id, dst.id) + if token in self.edges and link.options.unidirectional: + edge = self.edges[token] + edge.asymmetric_link = link + elif token not in self.edges: + node1 = src.core_node + node2 = dst.core_node + src_pos = (node1.position.x, node1.position.y) + dst_pos = (node2.position.x, node2.position.y) + edge = CanvasEdge(self, src.id, src_pos, dst_pos) + edge.token = token + edge.dst = dst.id + edge.set_link(link) + edge.check_wireless() + src.edges.add(edge) + dst.edges.add(edge) + self.edges[edge.token] = edge + self.core.links[edge.token] = edge + if link.HasField("iface1"): + iface1 = link.iface1 + self.core.iface_to_edge[(node1.id, iface1.id)] = token + src.ifaces[iface1.id] = iface1 + edge.src_iface = iface1 + if link.HasField("iface2"): + iface2 = link.iface2 + self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token + dst.ifaces[iface2.id] = iface2 + edge.dst_iface = iface2 + + def delete_wired_edge(self, src: CanvasNode, dst: CanvasNode) -> None: + token = create_edge_token(src.id, dst.id) + edge = self.edges.get(token) + if not edge: + return + self.delete_edge(edge) + def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: network_id = link.network_id if link.network_id else None token = create_edge_token(src.id, dst.id, network_id) @@ -297,41 +334,11 @@ class CanvasGraph(tk.Canvas): for link in session.links: logging.debug("drawing link: %s", link) canvas_node1 = self.core.canvas_nodes[link.node1_id] - node1 = canvas_node1.core_node canvas_node2 = self.core.canvas_nodes[link.node2_id] - node2 = canvas_node2.core_node - token = create_edge_token(canvas_node1.id, canvas_node2.id) - if link.type == LinkType.WIRELESS: self.add_wireless_edge(canvas_node1, canvas_node2, link) else: - if token not in self.edges: - src_pos = (node1.position.x, node1.position.y) - dst_pos = (node2.position.x, node2.position.y) - edge = CanvasEdge(self, canvas_node1.id, src_pos, dst_pos) - edge.token = token - edge.dst = canvas_node2.id - edge.set_link(link) - edge.check_wireless() - canvas_node1.edges.add(edge) - canvas_node2.edges.add(edge) - self.edges[edge.token] = edge - self.core.links[edge.token] = edge - if link.HasField("iface1"): - iface1 = link.iface1 - self.core.iface_to_edge[(node1.id, iface1.id)] = token - canvas_node1.ifaces[iface1.id] = iface1 - edge.src_iface = iface1 - if link.HasField("iface2"): - iface2 = link.iface2 - self.core.iface_to_edge[(node2.id, iface2.id)] = edge.token - canvas_node2.ifaces[iface2.id] = iface2 - edge.dst_iface = iface2 - elif link.options.unidirectional: - edge = self.edges[token] - edge.asymmetric_link = link - else: - logging.error("duplicate link received: %s", link) + self.add_wired_edge(canvas_node1, canvas_node2, link) def stopped_session(self) -> None: # clear wireless edges diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 46e1da91..6b1b304c 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -343,11 +343,11 @@ message Event { FileEvent file_event = 6; } int32 session_id = 7; + string source = 8; } message NodeEvent { Node node = 1; - string source = 2; } message LinkEvent { @@ -488,6 +488,7 @@ message GetNodeLinksResponse { message AddLinkRequest { int32 session_id = 1; Link link = 2; + string source = 3; } message AddLinkResponse { @@ -515,6 +516,7 @@ message DeleteLinkRequest { int32 node2_id = 3; int32 iface1_id = 4; int32 iface2_id = 5; + string source = 6; } message DeleteLinkResponse { From f921fa45c549ea95932a28497f12094c7a18d92a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 14:44:13 -0700 Subject: [PATCH 110/146] grpc: updated client methods to allow passing source for add_link/delete_link, None by default --- daemon/core/api/grpc/client.py | 9 ++++++++- daemon/core/emulator/session.py | 3 +-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 5aa6713d..939d7eef 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -614,6 +614,7 @@ class CoreGrpcClient: iface1: core_pb2.Interface = None, iface2: core_pb2.Interface = None, options: core_pb2.LinkOptions = None, + source: str = None, ) -> core_pb2.AddLinkResponse: """ Add a link between nodes. @@ -624,6 +625,7 @@ class CoreGrpcClient: :param iface1: node one interface data :param iface2: node two interface data :param options: options for link (jitter, bandwidth, etc) + :param source: application source adding link :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ @@ -635,7 +637,9 @@ class CoreGrpcClient: iface2=iface2, options=options, ) - request = core_pb2.AddLinkRequest(session_id=session_id, link=link) + request = core_pb2.AddLinkRequest( + session_id=session_id, link=link, source=source + ) return self.stub.AddLink(request) def edit_link( @@ -676,6 +680,7 @@ class CoreGrpcClient: node2_id: int, iface1_id: int = None, iface2_id: int = None, + source: str = None, ) -> core_pb2.DeleteLinkResponse: """ Delete a link between nodes. @@ -685,6 +690,7 @@ class CoreGrpcClient: :param node2_id: node two id :param iface1_id: node one interface id :param iface2_id: node two interface id + :param source: application source deleting link :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ @@ -694,6 +700,7 @@ class CoreGrpcClient: node2_id=node2_id, iface1_id=iface1_id, iface2_id=iface2_id, + source=source, ) return self.stub.DeleteLink(request) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index d2f64dde..c2573578 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -833,12 +833,11 @@ class Session: for handler in self.config_handlers: handler(config_data) - def broadcast_link(self, link_data: LinkData, source: str = None) -> None: + def broadcast_link(self, link_data: LinkData) -> None: """ Handle link data that should be provided to link handlers. :param link_data: link data to send out - :param source: source of broadcast, None by default :return: nothing """ for handler in self.link_handlers: From f4a3fe6b7b0ee2dc060cb8758cc4d1c4414c454e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 15:14:30 -0700 Subject: [PATCH 111/146] grpc/pygui: edit_link will now broadcast link changes, pygui now handles receiving this data --- daemon/core/api/grpc/client.py | 9 ++++++--- daemon/core/api/grpc/server.py | 14 ++++++++++++++ daemon/core/gui/coreclient.py | 4 ++++ daemon/core/gui/graph/graph.py | 12 +++++++++--- daemon/proto/core/api/grpc/core.proto | 1 + 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 939d7eef..e73b9fc2 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -509,7 +509,7 @@ class CoreGrpcClient: :param node_id: node id :param position: position to set node to :param icon: path to icon for gui to use for node - :param source: application source editing node + :param source: application source :param geo: lon,lat,alt location for node :return: response with result of success or failure :raises grpc.RpcError: when session or node doesn't exist @@ -625,7 +625,7 @@ class CoreGrpcClient: :param iface1: node one interface data :param iface2: node two interface data :param options: options for link (jitter, bandwidth, etc) - :param source: application source adding link + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ @@ -650,6 +650,7 @@ class CoreGrpcClient: options: core_pb2.LinkOptions, iface1_id: int = None, iface2_id: int = None, + source: str = None, ) -> core_pb2.EditLinkResponse: """ Edit a link between nodes. @@ -660,6 +661,7 @@ class CoreGrpcClient: :param options: options for link (jitter, bandwidth, etc) :param iface1_id: node one interface id :param iface2_id: node two interface id + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session or one of the nodes don't exist """ @@ -670,6 +672,7 @@ class CoreGrpcClient: options=options, iface1_id=iface1_id, iface2_id=iface2_id, + source=source, ) return self.stub.EditLink(request) @@ -690,7 +693,7 @@ class CoreGrpcClient: :param node2_id: node two id :param iface1_id: node one interface id :param iface2_id: node two interface id - :param source: application source deleting link + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 2883103d..65c7281e 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -866,6 +866,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): node2_id=node2_id, iface1=iface1_data, iface2=iface2_data, + options=options, source=source, ) session.broadcast_link(link_data) @@ -909,6 +910,19 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): key=options_proto.key, ) session.update_link(node1_id, node2_id, iface1_id, iface2_id, options) + iface1 = InterfaceData(id=iface1_id) + iface2 = InterfaceData(id=iface2_id) + source = request.source if request.source else None + link_data = LinkData( + message_type=MessageFlags.NONE, + node1_id=node1_id, + node2_id=node2_id, + iface1=iface1, + iface2=iface2, + options=options, + source=source, + ) + session.broadcast_link(link_data) return core_pb2.EditLinkResponse(result=True) def DeleteLink( diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index a5b96e17..b29c044e 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -216,6 +216,10 @@ class CoreClient: self.app.canvas.organize() elif event.message_type == MessageType.DELETE: self.app.canvas.delete_wired_edge(canvas_node1, canvas_node2) + elif event.message_type == MessageType.NONE: + self.app.canvas.update_wired_edge( + canvas_node1, canvas_node2, event.link + ) else: logging.warning("unknown link event: %s", event) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 4e0358a5..436de383 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -262,6 +262,13 @@ class CanvasGraph(tk.Canvas): return self.delete_edge(edge) + def update_wired_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: + token = create_edge_token(src.id, dst.id) + edge = self.edges.get(token) + if not edge: + return + edge.link.options.CopyFrom(link.options) + def add_wireless_edge(self, src: CanvasNode, dst: CanvasNode, link: Link) -> None: network_id = link.network_id if link.network_id else None token = create_edge_token(src.id, dst.id, network_id) @@ -350,9 +357,8 @@ class CanvasGraph(tk.Canvas): dst_node.wireless_edges.remove(edge) self.wireless_edges.clear() - # clear all middle edge labels - for edge in self.edges.values(): - edge.reset() + # clear throughputs + self.clear_throughputs() def canvas_xy(self, event: tk.Event) -> Tuple[float, float]: """ diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 6b1b304c..7d7592b1 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -504,6 +504,7 @@ message EditLinkRequest { int32 iface1_id = 4; int32 iface2_id = 5; LinkOptions options = 6; + string source = 7; } message EditLinkResponse { From e79645013be7733fabb9e0aaa68ce6873805e89b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 21:45:29 -0700 Subject: [PATCH 112/146] grpc/pygui: updated delete_node to use the source, updated pygui to support delete node events --- daemon/core/api/grpc/client.py | 9 +++++++-- daemon/core/api/grpc/events.py | 3 ++- daemon/core/api/grpc/server.py | 9 ++++++++- daemon/core/gui/coreclient.py | 13 ++++++++++--- daemon/core/gui/graph/node.py | 4 ++-- daemon/proto/core/api/grpc/core.proto | 2 ++ 6 files changed, 31 insertions(+), 9 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index e73b9fc2..2740e770 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -536,16 +536,21 @@ class CoreGrpcClient: """ return self.stub.MoveNodes(move_iterator) - def delete_node(self, session_id: int, node_id: int) -> core_pb2.DeleteNodeResponse: + def delete_node( + self, session_id: int, node_id: int, source: str = None + ) -> core_pb2.DeleteNodeResponse: """ Delete node from session. :param session_id: session id :param node_id: node id + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.DeleteNodeRequest(session_id=session_id, node_id=node_id) + request = core_pb2.DeleteNodeRequest( + session_id=session_id, node_id=node_id, source=source + ) return self.stub.DeleteNode(request) def node_command( diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 5c873a43..fb6eaff8 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -36,7 +36,8 @@ def handle_node_event(node_data: NodeData) -> core_pb2.Event: geo=geo, services=services, ) - node_event = core_pb2.NodeEvent(node=node_proto) + message_type = node_data.message_type.value + node_event = core_pb2.NodeEvent(message_type=message_type, node=node_proto) return core_pb2.Event(node_event=node_event, source=node_data.source) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 65c7281e..8851116d 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -775,7 +775,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("delete node: %s", request) session = self.get_session(request.session_id, context) - result = session.delete_node(request.node_id) + result = False + try: + node = self.get_node(session, request.node_id, context, NodeBase) + result = session.delete_node(node.id) + source = request.source if request.source else None + session.broadcast_node(node, MessageFlags.DELETE, source) + except grpc.RpcError: + pass return core_pb2.DeleteNodeResponse(result=result) def NodeCommand( diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index b29c044e..cf870cf2 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -226,10 +226,17 @@ class CoreClient: def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) node_id = event.node.id - x = event.node.position.x - y = event.node.position.y canvas_node = self.canvas_nodes[node_id] - canvas_node.move(x, y) + if event.message_type == MessageType.NONE: + x = event.node.position.x + y = event.node.position.y + canvas_node.move(x, y) + elif event.message_type == MessageType.DELETE: + self.app.canvas.clear_selection() + self.app.canvas.select_object(canvas_node.id) + self.app.canvas.delete_selected_objects() + else: + logging.warning("unknown node event: %s", event) def enable_throughputs(self) -> None: self.handling_throughputs = self.client.throughputs( diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index d98c4e48..6e8185b8 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -271,12 +271,12 @@ class CanvasNode: def canvas_delete(self) -> None: self.canvas.clear_selection() - self.canvas.selection[self.id] = self + self.canvas.select_object(self.id) self.canvas.delete_selected_objects() def canvas_copy(self) -> None: self.canvas.clear_selection() - self.canvas.selection[self.id] = self + self.canvas.select_object(self.id) self.canvas.copy() def show_config(self) -> None: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 7d7592b1..22cd9c54 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -348,6 +348,7 @@ message Event { message NodeEvent { Node node = 1; + MessageType.Enum message_type = 2; } message LinkEvent { @@ -435,6 +436,7 @@ message EditNodeResponse { message DeleteNodeRequest { int32 session_id = 1; int32 node_id = 2; + string source = 3; } message DeleteNodeResponse { From 5eae67aac59fb6e93259750517588901acb651bb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 22:11:36 -0700 Subject: [PATCH 113/146] grpc/pygui: updated add_node source support, updated pygui to handle add_node events --- daemon/core/api/grpc/client.py | 7 +++-- daemon/core/api/grpc/server.py | 2 ++ daemon/core/gui/coreclient.py | 6 +++-- daemon/core/gui/graph/graph.py | 39 +++++++++++++++++---------- daemon/proto/core/api/grpc/core.proto | 1 + 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 2740e770..82164fe3 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -468,17 +468,20 @@ class CoreGrpcClient: return stream def add_node( - self, session_id: int, node: core_pb2.Node + self, session_id: int, node: core_pb2.Node, source: str = None ) -> core_pb2.AddNodeResponse: """ Add node to session. :param session_id: session id :param node: node to add + :param source: source application :return: response with node id :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.AddNodeRequest(session_id=session_id, node=node) + request = core_pb2.AddNodeRequest( + session_id=session_id, node=node, source=source + ) return self.stub.AddNode(request) def get_node(self, session_id: int, node_id: int) -> core_pb2.GetNodeResponse: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 8851116d..27702629 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -668,6 +668,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): _type, _id, options = grpcutils.add_node_data(request.node) _class = session.get_node_class(_type) node = session.add_node(_class, _id, options) + source = request.source if request.source else None + session.broadcast_node(node, MessageFlags.ADD, source) return core_pb2.AddNodeResponse(node_id=node.id) def GetNode( diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index cf870cf2..cf331676 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -225,16 +225,18 @@ class CoreClient: def handle_node_event(self, event: NodeEvent) -> None: logging.debug("node event: %s", event) - node_id = event.node.id - canvas_node = self.canvas_nodes[node_id] if event.message_type == MessageType.NONE: + canvas_node = self.canvas_nodes[event.node.id] x = event.node.position.x y = event.node.position.y canvas_node.move(x, y) elif event.message_type == MessageType.DELETE: + canvas_node = self.canvas_nodes[event.node.id] self.app.canvas.clear_selection() self.app.canvas.select_object(canvas_node.id) self.app.canvas.delete_selected_objects() + elif event.message_type == MessageType.ADD: + self.app.canvas.add_core_node(event.node) else: logging.warning("unknown node event: %s", event) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 436de383..9cb3b109 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -7,7 +7,14 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from PIL import Image from PIL.ImageTk import PhotoImage -from core.api.grpc.core_pb2 import Interface, Link, LinkType, Session, ThroughputsEvent +from core.api.grpc.core_pb2 import ( + Interface, + Link, + LinkType, + Node, + Session, + ThroughputsEvent, +) from core.gui.dialogs.shapemod import ShapeDialog from core.gui.graph import tags from core.gui.graph.edges import ( @@ -315,29 +322,33 @@ class CanvasGraph(tk.Canvas): edge = self.wireless_edges[token] edge.middle_label_text(link.label) + def add_core_node(self, core_node: Node) -> None: + if core_node.id in self.core.canvas_nodes: + logging.error("core node already exists: %s", core_node) + return + logging.debug("adding node %s", core_node) + # if the gui can't find node's image, default to the "edit-node" image + image = NodeUtils.node_image(core_node, self.app.guiconfig, self.app.app_scale) + if not image: + image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE) + x = core_node.position.x + y = core_node.position.y + node = CanvasNode(self.app, x, y, core_node, image) + self.nodes[node.id] = node + self.core.canvas_nodes[core_node.id] = node + def draw_session(self, session: Session) -> None: """ Draw existing session. """ # draw existing nodes for core_node in session.nodes: - logging.debug("drawing node %s", core_node) # peer to peer node is not drawn on the GUI if NodeUtils.is_ignore_node(core_node.type): continue - image = NodeUtils.node_image( - core_node, self.app.guiconfig, self.app.app_scale - ) - # if the gui can't find node's image, default to the "edit-node" image - if not image: - image = self.app.get_icon(ImageEnum.EDITNODE, ICON_SIZE) - x = core_node.position.x - y = core_node.position.y - node = CanvasNode(self.app, x, y, core_node, image) - self.nodes[node.id] = node - self.core.canvas_nodes[core_node.id] = node + self.add_core_node(core_node) - # draw existing links + # draw existing links for link in session.links: logging.debug("drawing link: %s", link) canvas_node1 = self.core.canvas_nodes[link.node1_id] diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 22cd9c54..8112c9d1 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -404,6 +404,7 @@ message FileEvent { message AddNodeRequest { int32 session_id = 1; Node node = 2; + string source = 3; } message AddNodeResponse { From c8daeb02d82db91914e13753db3d785ad145df1a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Jun 2020 22:29:17 -0700 Subject: [PATCH 114/146] grpc: fixed issue with not catching error in delete_node from broadcast changes --- daemon/core/api/grpc/server.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 27702629..e8469177 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -778,13 +778,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("delete node: %s", request) session = self.get_session(request.session_id, context) result = False - try: + if request.node_id in session.nodes: node = self.get_node(session, request.node_id, context, NodeBase) result = session.delete_node(node.id) source = request.source if request.source else None session.broadcast_node(node, MessageFlags.DELETE, source) - except grpc.RpcError: - pass return core_pb2.DeleteNodeResponse(result=result) def NodeCommand( From 59e7395a4f30cccbaaac0e693922c11333bd7f5a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 29 Jun 2020 23:00:33 -0700 Subject: [PATCH 115/146] initial addition of core-cli script that can be used to run commands and query information with sessions using grpc, similar in concept to coresendmsg --- daemon/scripts/core-cli | 447 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100755 daemon/scripts/core-cli diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli new file mode 100755 index 00000000..ca9011b5 --- /dev/null +++ b/daemon/scripts/core-cli @@ -0,0 +1,447 @@ +#!/usr/bin/env python3 +import sys +from argparse import ( + ArgumentDefaultsHelpFormatter, + ArgumentParser, + ArgumentTypeError, + Namespace, + _SubParsersAction, +) +from typing import Tuple + +import netaddr +from google.protobuf.json_format import MessageToJson + +from core.api.grpc.client import CoreGrpcClient +from core.api.grpc.core_pb2 import ( + Geo, + Interface, + LinkOptions, + Node, + NodeType, + Position, + SessionState, +) + +NODE_TYPES = [k for k, v in NodeType.Enum.items() if v != NodeType.PEER_TO_PEER] + + +def mac_type(value: str) -> str: + if not netaddr.valid_mac(value): + raise ArgumentTypeError("invalid mac address") + return value + + +def ip4_type(value: str) -> str: + if not netaddr.valid_ipv4(value): + raise ArgumentTypeError("invalid ip4 address") + return value + + +def ip6_type(value: str) -> str: + if not netaddr.valid_ipv6(value): + raise ArgumentTypeError("invalid ip6 address") + return value + + +def position_type(value: str) -> Tuple[float, float]: + error = "invalid position, must be in the format: float,float" + try: + values = [float(x) for x in value.split(",")] + except ValueError: + raise ArgumentTypeError(error) + if len(values) != 2: + raise ArgumentTypeError(error) + x, y = values + return x, y + + +def geo_type(value: str) -> Tuple[float, float, float]: + error = "invalid geo, must be in the format: float,float,float" + try: + values = [float(x) for x in value.split(",")] + except ValueError: + raise ArgumentTypeError(error) + if len(values) != 3: + raise ArgumentTypeError(error) + lon, lat, alt = values + return lon, lat, alt + + +def get_current_session() -> int: + core = CoreGrpcClient() + with core.context_connect(): + response = core.get_sessions() + if not response.sessions: + print("no current session to interact with") + sys.exit(1) + return response.sessions[0].id + + +def print_interface_header() -> None: + print("ID | MAC Address | IP4 Address | IP6 Address") + + +def print_interface(iface: Interface) -> None: + iface_ip4 = f"{iface.ip4}/{iface.ip4_mask}" if iface.ip4 else None + iface_ip6 = f"{iface.ip6}/{iface.ip6_mask}" if iface.ip6 else None + print(f"{iface.id:<3} | {iface.mac:<11} | {iface_ip4:<18} | {iface_ip6}") + + +def query_sessions(args: Namespace) -> None: + core = CoreGrpcClient() + with core.context_connect(): + response = core.get_sessions() + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print("Session ID | Session State | Nodes") + for s in response.sessions: + state = SessionState.Enum.Name(s.state) + print(f"{s.id:<10} | {state:<13} | {s.nodes}") + + +def query_session(args: Namespace) -> None: + core = CoreGrpcClient() + with core.context_connect(): + response = core.get_session(args.id) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print("Nodes") + print("Node ID | Node Name | Node Type") + names = {} + for node in response.session.nodes: + names[node.id] = node.name + node_type = NodeType.Enum.Name(node.type) + print(f"{node.id:<7} | {node.name:<9} | {node_type}") + + print("\nLinks") + for link in response.session.links: + n1 = names[link.node1_id] + n2 = names[link.node2_id] + print(f"Node | ", end="") + print_interface_header() + if link.HasField("iface1"): + print(f"{n1:<6} | ", end="") + print_interface(link.iface1) + if link.HasField("iface2"): + print(f"{n2:<6} | ", end="") + print_interface(link.iface2) + print() + + +def query_node(args: Namespace) -> None: + core = CoreGrpcClient() + with core.context_connect(): + response = core.get_node(args.id, args.node) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + node = response.node + node_type = NodeType.Enum.Name(node.type) + print("ID | Name | Type") + print(f"{node.id:<4} | {node.name:<7} | {node_type}") + print("Interfaces") + print_interface_header() + for iface in response.ifaces: + print_interface(iface) + + +def add_node(args: Namespace) -> None: + session_id = get_current_session() + node_type = NodeType.Enum.Value(args.type) + pos = None + if args.pos: + x, y = args.pos + pos = Position(x=x, y=y) + geo = None + if args.geo: + lon, lat, alt = args.geo + geo = Geo(lon=lon, lat=lat, alt=alt) + core = CoreGrpcClient() + with core.context_connect(): + node = Node( + id=args.id, + name=args.name, + type=node_type, + model=args.model, + emane=args.emane, + icon=args.icon, + image=args.image, + position=pos, + geo=geo, + ) + response = core.add_node(session_id, node) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"created node: {response.node_id}") + + +def edit_node(args: Namespace) -> None: + session_id = get_current_session() + pos = None + if args.pos: + x, y = args.pos + pos = Position(x=x, y=y) + geo = None + if args.geo: + lon, lat, alt = args.geo + geo = Geo(lon=lon, lat=lat, alt=alt) + core = CoreGrpcClient() + with core.context_connect(): + response = core.edit_node(session_id, args.id, pos, args.icon, geo=geo) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"edit node: {response.result}") + + +def delete_node(args: Namespace) -> None: + session_id = get_current_session() + core = CoreGrpcClient() + with core.context_connect(): + response = core.delete_node(session_id, args.id) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"deleted node: {response.result}") + + +def add_link(args: Namespace) -> None: + session_id = get_current_session() + iface1 = None + if args.iface1_id is not None: + iface1 = Interface( + id=args.iface1_id, + mac=args.iface1_mac, + ip4=args.iface1_ip4, + ip4_mask=args.iface1_ip4_mask, + ip6=args.iface1_ip4, + ip6_mask=args.iface1_ip6_mask, + ) + iface2 = None + if args.iface2_id is not None: + iface2 = Interface( + id=args.iface2_id, + mac=args.iface2_mac, + ip4=args.iface2_ip4, + ip4_mask=args.iface2_ip4_mask, + ip6=args.iface2_ip4, + ip6_mask=args.iface2_ip6_mask, + ) + options = LinkOptions( + bandwidth=args.bandwidth, + loss=args.loss, + jitter=args.jitter, + delay=args.delay, + dup=args.dup, + unidirectional=args.uni, + ) + core = CoreGrpcClient() + with core.context_connect(): + response = core.add_link(session_id, args.node1, args.node2, iface1, iface2, options) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"edit link: {response.result}") + + +def edit_link(args: Namespace) -> None: + session_id = get_current_session() + options = LinkOptions( + bandwidth=args.bandwidth, + loss=args.loss, + jitter=args.jitter, + delay=args.delay, + dup=args.dup, + unidirectional=args.uni, + ) + core = CoreGrpcClient() + with core.context_connect(): + response = core.edit_link( + session_id, args.node1, args.node2, options, args.iface1, args.iface2 + ) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"edit link: {response.result}") + + +def delete_link(args: Namespace) -> None: + session_id = get_current_session() + core = CoreGrpcClient() + with core.context_connect(): + response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) + if args.json: + json = MessageToJson(response, preserving_proto_field_name=True) + print(json) + else: + print(f"delete link: {response.result}") + + +def setup_node_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("node", help="node interactions") + parser.add_argument("--session", type=int, help="session to interact with") + subparsers = parser.add_subparsers(help="node commands") + subparsers.required = True + subparsers.dest = "command" + + add_parser = subparsers.add_parser("add", help="add a node") + add_parser.formatter_class = ArgumentDefaultsHelpFormatter + add_parser.add_argument("--id", type=int, help="id to use, optional") + add_parser.add_argument("--name", help="name to use, optional") + add_parser.add_argument( + "--type", choices=NODE_TYPES, default="DEFAULT", help="type of node" + ) + add_parser.add_argument("--model", help="used to determine services, optional") + group = add_parser.add_mutually_exclusive_group(required=True) + group.add_argument("--pos", type=position_type, help="x,y position") + group.add_argument("--geo", type=geo_type, help="lon,lat,alt position") + add_parser.add_argument("--icon", help="icon to use, optional") + add_parser.add_argument("--image", help="container image, optional") + add_parser.add_argument( + "--emane", help="emane model, only required for emane nodes" + ) + add_parser.set_defaults(func=add_node) + + edit_parser = subparsers.add_parser("edit", help="edit a node") + edit_parser.formatter_class = ArgumentDefaultsHelpFormatter + edit_parser.add_argument("--id", type=int, help="id to use, optional") + group = edit_parser.add_mutually_exclusive_group(required=True) + group.add_argument("--pos", type=position_type, help="x,y position") + group.add_argument("--geo", type=geo_type, help="lon,lat,alt position") + edit_parser.add_argument("--icon", help="icon to use, optional") + edit_parser.set_defaults(func=edit_node) + + delete_parser = subparsers.add_parser("delete", help="delete a node") + delete_parser.formatter_class = ArgumentDefaultsHelpFormatter + delete_parser.add_argument( + "--id", type=int, help="node id to delete", required=True + ) + delete_parser.set_defaults(func=delete_node) + + +def setup_link_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("link", help="link interactions") + parser.add_argument("--session", type=int, help="session to interact with") + subparsers = parser.add_subparsers(help="link commands") + subparsers.required = True + subparsers.dest = "command" + + add_parser = subparsers.add_parser("add", help="add a node") + add_parser.formatter_class = ArgumentDefaultsHelpFormatter + add_parser.add_argument( + "--node1", type=int, help="node1 id for link", required=True + ) + add_parser.add_argument( + "--node2", type=int, help="node1 id for link", required=True + ) + add_parser.add_argument("--iface1-id", type=int, help="node1 interface id for link") + add_parser.add_argument("--iface1-mac", type=mac_type, help="node1 interface mac") + add_parser.add_argument("--iface1-ip4", type=ip4_type, help="node1 interface ip4") + add_parser.add_argument( + "--iface1-ip4-mask", type=int, help="node1 interface ip4 mask" + ) + add_parser.add_argument("--iface1-ip6", type=ip6_type, help="node1 interface ip6") + add_parser.add_argument( + "--iface1-ip6-mask", type=int, help="node1 interface ip6 mask" + ) + add_parser.add_argument("--iface2-id", type=int, help="node1 interface id for link") + add_parser.add_argument("--iface2-mac", type=mac_type, help="node1 interface mac") + add_parser.add_argument("--iface2-ip4", type=ip4_type, help="node1 interface ip4") + add_parser.add_argument( + "--iface2-ip4-mask", type=int, help="node1 interface ip4 mask" + ) + add_parser.add_argument("--iface2-ip6", type=ip6_type, help="node1 interface ip6") + add_parser.add_argument( + "--iface2-ip6-mask", type=int, help="node1 interface ip6 mask" + ) + add_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") + add_parser.add_argument("--loss", type=float, help="loss (%) for link") + add_parser.add_argument("--jitter", type=int, help="jitter (us) for link") + add_parser.add_argument("--delay", type=int, help="delay (us) for link") + add_parser.add_argument("--dup", type=int, help="duplicate (%) for link") + add_parser.add_argument( + "--uni", action="store_true", help="is link unidirectional?" + ) + add_parser.set_defaults(func=add_link) + + edit_parser = subparsers.add_parser("edit", help="edit a link") + edit_parser.formatter_class = ArgumentDefaultsHelpFormatter + edit_parser.add_argument( + "--node1", type=int, help="node1 id for link", required=True + ) + edit_parser.add_argument( + "--node2", type=int, help="node1 id for link", required=True + ) + edit_parser.add_argument("--iface1", type=int, help="node1 interface id for link") + edit_parser.add_argument("--iface2", type=int, help="node2 interface id for link") + edit_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") + edit_parser.add_argument("--loss", type=float, help="loss (%) for link") + edit_parser.add_argument("--jitter", type=int, help="jitter (us) for link") + edit_parser.add_argument("--delay", type=int, help="delay (us) for link") + edit_parser.add_argument("--dup", type=int, help="duplicate (%) for link") + edit_parser.add_argument( + "--uni", action="store_true", help="is link unidirectional?" + ) + edit_parser.set_defaults(func=edit_link) + + delete_parser = subparsers.add_parser("delete", help="delete a link") + delete_parser.formatter_class = ArgumentDefaultsHelpFormatter + delete_parser.add_argument( + "--node1", type=int, help="node1 id for link", required=True + ) + delete_parser.add_argument( + "--node2", type=int, help="node1 id for link", required=True + ) + delete_parser.add_argument("--iface1", type=int, help="node1 interface id for link") + delete_parser.add_argument("--iface2", type=int, help="node2 interface id for link") + delete_parser.set_defaults(func=delete_link) + + +def setup_query_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("query", help="query interactions") + subparsers = parser.add_subparsers(help="query commands") + subparsers.required = True + subparsers.dest = "command" + + sessions_parser = subparsers.add_parser("sessions", help="query current sessions") + sessions_parser.set_defaults(func=query_sessions) + + session_parser = subparsers.add_parser("session", help="query session") + session_parser.add_argument("--id", type=int, help="session to query", required=True) + session_parser.set_defaults(func=query_session) + + node_parser = subparsers.add_parser("node", help="query node") + node_parser.add_argument("--id", type=int, help="session to query", required=True) + node_parser.add_argument("--node", type=int, help="node to query", required=True) + node_parser.set_defaults(func=query_node) + + +def main() -> None: + parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) + parser.add_argument( + "-j", "--json", action="store_true", help="print responses to terminal as json" + ) + subparsers = parser.add_subparsers(help="supported commands") + subparsers.required = True + subparsers.dest = "command" + setup_node_parser(subparsers) + setup_link_parser(subparsers) + setup_query_parser(subparsers) + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() From ec845b920c4b55340fb579dbafa849b60470b6e4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 08:27:40 -0700 Subject: [PATCH 116/146] removed ip mask options from core-cli add link, combined with ip and will parse input to provide simpler interface --- daemon/scripts/core-cli | 92 ++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index ca9011b5..a8354f65 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -11,6 +11,7 @@ from typing import Tuple import netaddr from google.protobuf.json_format import MessageToJson +from netaddr import EUI, AddrFormatError, IPNetwork from core.api.grpc.client import CoreGrpcClient from core.api.grpc.core_pb2 import ( @@ -27,21 +28,31 @@ NODE_TYPES = [k for k, v in NodeType.Enum.items() if v != NodeType.PEER_TO_PEER] def mac_type(value: str) -> str: - if not netaddr.valid_mac(value): - raise ArgumentTypeError("invalid mac address") - return value + try: + mac = EUI(value, dialect=netaddr.mac_unix_expanded) + return str(mac) + except AddrFormatError: + raise ArgumentTypeError(f"invalid mac address: {value}") -def ip4_type(value: str) -> str: - if not netaddr.valid_ipv4(value): - raise ArgumentTypeError("invalid ip4 address") - return value +def ip4_type(value: str) -> IPNetwork: + try: + ip = IPNetwork(value) + if not netaddr.valid_ipv4(str(ip.ip)): + raise ArgumentTypeError(f"invalid ip4 address: {value}") + return ip + except AddrFormatError: + raise ArgumentTypeError(f"invalid ip4 address: {value}") -def ip6_type(value: str) -> str: - if not netaddr.valid_ipv6(value): - raise ArgumentTypeError("invalid ip6 address") - return value +def ip6_type(value: str) -> IPNetwork: + try: + ip = IPNetwork(value) + if not netaddr.valid_ipv6(str(ip.ip)): + raise ArgumentTypeError(f"invalid ip6 address: {value}") + return ip + except AddrFormatError: + raise ArgumentTypeError(f"invalid ip6 address: {value}") def position_type(value: str) -> Tuple[float, float]: @@ -78,11 +89,26 @@ def get_current_session() -> int: return response.sessions[0].id -def print_interface_header() -> None: +def create_iface(iface_id: int, mac: str, ip4_net: IPNetwork, ip6_net: IPNetwork) -> Interface: + ip4 = str(ip4_net.ip) if ip4_net else None + ip4_mask = ip4_net.prefixlen if ip4_net else None + ip6 = str(ip6_net.ip) if ip6_net else None + ip6_mask = ip6_net.prefixlen if ip6_net else None + return Interface( + id=iface_id, + mac=mac, + ip4=ip4, + ip4_mask=ip4_mask, + ip6=ip6, + ip6_mask=ip6_mask, + ) + + +def print_iface_header() -> None: print("ID | MAC Address | IP4 Address | IP6 Address") -def print_interface(iface: Interface) -> None: +def print_iface(iface: Interface) -> None: iface_ip4 = f"{iface.ip4}/{iface.ip4_mask}" if iface.ip4 else None iface_ip6 = f"{iface.ip6}/{iface.ip6_mask}" if iface.ip6 else None print(f"{iface.id:<3} | {iface.mac:<11} | {iface_ip4:<18} | {iface_ip6}") @@ -123,13 +149,13 @@ def query_session(args: Namespace) -> None: n1 = names[link.node1_id] n2 = names[link.node2_id] print(f"Node | ", end="") - print_interface_header() + print_iface_header() if link.HasField("iface1"): print(f"{n1:<6} | ", end="") - print_interface(link.iface1) + print_iface(link.iface1) if link.HasField("iface2"): print(f"{n2:<6} | ", end="") - print_interface(link.iface2) + print_iface(link.iface2) print() @@ -146,9 +172,9 @@ def query_node(args: Namespace) -> None: print("ID | Name | Type") print(f"{node.id:<4} | {node.name:<7} | {node_type}") print("Interfaces") - print_interface_header() + print_iface_header() for iface in response.ifaces: - print_interface(iface) + print_iface(iface) def add_node(args: Namespace) -> None: @@ -219,24 +245,10 @@ def add_link(args: Namespace) -> None: session_id = get_current_session() iface1 = None if args.iface1_id is not None: - iface1 = Interface( - id=args.iface1_id, - mac=args.iface1_mac, - ip4=args.iface1_ip4, - ip4_mask=args.iface1_ip4_mask, - ip6=args.iface1_ip4, - ip6_mask=args.iface1_ip6_mask, - ) + iface1 = create_iface(args.iface1_id, args.iface1_mac, args.iface1_ip4, args.iface1_ip6) iface2 = None if args.iface2_id is not None: - iface2 = Interface( - id=args.iface2_id, - mac=args.iface2_mac, - ip4=args.iface2_ip4, - ip4_mask=args.iface2_ip4_mask, - ip6=args.iface2_ip4, - ip6_mask=args.iface2_ip6_mask, - ) + iface2 = create_iface(args.iface2_id, args.iface2_mac, args.iface2_ip4, args.iface2_ip6) options = LinkOptions( bandwidth=args.bandwidth, loss=args.loss, @@ -349,23 +361,11 @@ def setup_link_parser(parent: _SubParsersAction) -> None: add_parser.add_argument("--iface1-id", type=int, help="node1 interface id for link") add_parser.add_argument("--iface1-mac", type=mac_type, help="node1 interface mac") add_parser.add_argument("--iface1-ip4", type=ip4_type, help="node1 interface ip4") - add_parser.add_argument( - "--iface1-ip4-mask", type=int, help="node1 interface ip4 mask" - ) add_parser.add_argument("--iface1-ip6", type=ip6_type, help="node1 interface ip6") - add_parser.add_argument( - "--iface1-ip6-mask", type=int, help="node1 interface ip6 mask" - ) add_parser.add_argument("--iface2-id", type=int, help="node1 interface id for link") add_parser.add_argument("--iface2-mac", type=mac_type, help="node1 interface mac") add_parser.add_argument("--iface2-ip4", type=ip4_type, help="node1 interface ip4") - add_parser.add_argument( - "--iface2-ip4-mask", type=int, help="node1 interface ip4 mask" - ) add_parser.add_argument("--iface2-ip6", type=ip6_type, help="node1 interface ip6") - add_parser.add_argument( - "--iface2-ip6-mask", type=int, help="node1 interface ip6 mask" - ) add_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") add_parser.add_argument("--loss", type=float, help="loss (%) for link") add_parser.add_argument("--jitter", type=int, help="jitter (us) for link") From aef3fe8d50e47089abf974e4a28f6bafa65e14c4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:25:36 -0700 Subject: [PATCH 117/146] updated core-cli to use consistent shorthand options and existing longform options --- daemon/scripts/core-cli | 122 +++++++++++++++++----------------------- 1 file changed, 52 insertions(+), 70 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index a8354f65..65a92994 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -254,7 +254,7 @@ def add_link(args: Namespace) -> None: loss=args.loss, jitter=args.jitter, delay=args.delay, - dup=args.dup, + dup=args.duplicate, unidirectional=args.uni, ) core = CoreGrpcClient() @@ -274,7 +274,7 @@ def edit_link(args: Namespace) -> None: loss=args.loss, jitter=args.jitter, delay=args.delay, - dup=args.dup, + dup=args.duplicate, unidirectional=args.uni, ) core = CoreGrpcClient() @@ -303,109 +303,91 @@ def delete_link(args: Namespace) -> None: def setup_node_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("node", help="node interactions") - parser.add_argument("--session", type=int, help="session to interact with") + parser.add_argument("-s", "--session", type=int, help="session to interact with") subparsers = parser.add_subparsers(help="node commands") subparsers.required = True subparsers.dest = "command" add_parser = subparsers.add_parser("add", help="add a node") add_parser.formatter_class = ArgumentDefaultsHelpFormatter - add_parser.add_argument("--id", type=int, help="id to use, optional") - add_parser.add_argument("--name", help="name to use, optional") + add_parser.add_argument("-i", "--id", type=int, help="id to use, optional") + add_parser.add_argument("-n", "--name", help="name to use, optional") add_parser.add_argument( - "--type", choices=NODE_TYPES, default="DEFAULT", help="type of node" + "-t", "--type", choices=NODE_TYPES, default="DEFAULT", help="type of node" ) - add_parser.add_argument("--model", help="used to determine services, optional") + add_parser.add_argument("-m", "--model", help="used to determine services, optional") group = add_parser.add_mutually_exclusive_group(required=True) - group.add_argument("--pos", type=position_type, help="x,y position") - group.add_argument("--geo", type=geo_type, help="lon,lat,alt position") - add_parser.add_argument("--icon", help="icon to use, optional") - add_parser.add_argument("--image", help="container image, optional") - add_parser.add_argument( - "--emane", help="emane model, only required for emane nodes" - ) + group.add_argument("-p", "--pos", type=position_type, help="x,y position") + group.add_argument("-g", "--geo", type=geo_type, help="lon,lat,alt position") + add_parser.add_argument("-ic", "--icon", help="icon to use, optional") + add_parser.add_argument("-im", "--image", help="container image, optional") + add_parser.add_argument("-e", "--emane", help="emane model, only required for emane nodes") add_parser.set_defaults(func=add_node) edit_parser = subparsers.add_parser("edit", help="edit a node") edit_parser.formatter_class = ArgumentDefaultsHelpFormatter - edit_parser.add_argument("--id", type=int, help="id to use, optional") + edit_parser.add_argument("-i", "--id", type=int, help="id to use, optional") group = edit_parser.add_mutually_exclusive_group(required=True) - group.add_argument("--pos", type=position_type, help="x,y position") - group.add_argument("--geo", type=geo_type, help="lon,lat,alt position") - edit_parser.add_argument("--icon", help="icon to use, optional") + group.add_argument("-p", "--pos", type=position_type, help="x,y position") + group.add_argument("-g", "--geo", type=geo_type, help="lon,lat,alt position") + edit_parser.add_argument("-ic", "--icon", help="icon to use, optional") edit_parser.set_defaults(func=edit_node) delete_parser = subparsers.add_parser("delete", help="delete a node") delete_parser.formatter_class = ArgumentDefaultsHelpFormatter - delete_parser.add_argument( - "--id", type=int, help="node id to delete", required=True - ) + delete_parser.add_argument("-i", "--id", type=int, help="node id", required=True) delete_parser.set_defaults(func=delete_node) def setup_link_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("link", help="link interactions") - parser.add_argument("--session", type=int, help="session to interact with") + parser.add_argument("-s", "--session", type=int, help="session to interact with") subparsers = parser.add_subparsers(help="link commands") subparsers.required = True subparsers.dest = "command" add_parser = subparsers.add_parser("add", help="add a node") add_parser.formatter_class = ArgumentDefaultsHelpFormatter - add_parser.add_argument( - "--node1", type=int, help="node1 id for link", required=True - ) - add_parser.add_argument( - "--node2", type=int, help="node1 id for link", required=True - ) - add_parser.add_argument("--iface1-id", type=int, help="node1 interface id for link") - add_parser.add_argument("--iface1-mac", type=mac_type, help="node1 interface mac") - add_parser.add_argument("--iface1-ip4", type=ip4_type, help="node1 interface ip4") - add_parser.add_argument("--iface1-ip6", type=ip6_type, help="node1 interface ip6") - add_parser.add_argument("--iface2-id", type=int, help="node1 interface id for link") - add_parser.add_argument("--iface2-mac", type=mac_type, help="node1 interface mac") - add_parser.add_argument("--iface2-ip4", type=ip4_type, help="node1 interface ip4") - add_parser.add_argument("--iface2-ip6", type=ip6_type, help="node1 interface ip6") - add_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") - add_parser.add_argument("--loss", type=float, help="loss (%) for link") - add_parser.add_argument("--jitter", type=int, help="jitter (us) for link") - add_parser.add_argument("--delay", type=int, help="delay (us) for link") - add_parser.add_argument("--dup", type=int, help="duplicate (%) for link") - add_parser.add_argument( - "--uni", action="store_true", help="is link unidirectional?" - ) + add_parser.add_argument("-n1", "--node1", type=int, help="node1 id", required=True) + add_parser.add_argument("-n2", "--node2", type=int, help="node2 id", required=True) + add_parser.add_argument("-i1-i", "--iface1-id", type=int, help="node1 interface id") + add_parser.add_argument("-i1-m", "--iface1-mac", type=mac_type, help="node1 interface mac") + add_parser.add_argument("-i1-4", "--iface1-ip4", type=ip4_type, help="node1 interface ip4") + add_parser.add_argument("-i1-6", "--iface1-ip6", type=ip6_type, help="node1 interface ip6") + add_parser.add_argument("-i2-i", "--iface2-id", type=int, help="node2 interface id") + add_parser.add_argument("-i2-m", "--iface2-mac", type=mac_type, help="node2 interface mac") + add_parser.add_argument("-i2-4", "--iface2-ip4", type=ip4_type, help="node2 interface ip4") + add_parser.add_argument("-i2-6", "--iface2-ip6", type=ip6_type, help="node2 interface ip6") + add_parser.add_argument("-b", "--bandwidth", type=int, help="bandwidth (bps)") + add_parser.add_argument("-l", "--loss", type=float, help="loss (%%)") + add_parser.add_argument("-j", "--jitter", type=int, help="jitter (us)") + add_parser.add_argument("-de", "--delay", type=int, help="delay (us)") + add_parser.add_argument("-du", "--duplicate", type=int, help="duplicate (%%)") + add_parser.add_argument("-u", "--uni", action="store_true", help="is link unidirectional?") add_parser.set_defaults(func=add_link) edit_parser = subparsers.add_parser("edit", help="edit a link") edit_parser.formatter_class = ArgumentDefaultsHelpFormatter + edit_parser.add_argument("-n1", "--node1", type=int, help="node1 id", required=True) + edit_parser.add_argument("-n2", "--node2", type=int, help="node2 id", required=True) + edit_parser.add_argument("-i1", "--iface1", type=int, help="node1 interface id") + edit_parser.add_argument("-i2", "--iface2", type=int, help="node2 interface id") + edit_parser.add_argument("-b", "--bandwidth", type=int, help="bandwidth (bps)") + edit_parser.add_argument("-l", "--loss", type=float, help="loss (%%)") + edit_parser.add_argument("-j", "--jitter", type=int, help="jitter (us)") + edit_parser.add_argument("-de", "--delay", type=int, help="delay (us)") + edit_parser.add_argument("-du", "--duplicate", type=int, help="duplicate (%%)") edit_parser.add_argument( - "--node1", type=int, help="node1 id for link", required=True - ) - edit_parser.add_argument( - "--node2", type=int, help="node1 id for link", required=True - ) - edit_parser.add_argument("--iface1", type=int, help="node1 interface id for link") - edit_parser.add_argument("--iface2", type=int, help="node2 interface id for link") - edit_parser.add_argument("--bandwidth", type=int, help="bandwidth (bps) for link") - edit_parser.add_argument("--loss", type=float, help="loss (%) for link") - edit_parser.add_argument("--jitter", type=int, help="jitter (us) for link") - edit_parser.add_argument("--delay", type=int, help="delay (us) for link") - edit_parser.add_argument("--dup", type=int, help="duplicate (%) for link") - edit_parser.add_argument( - "--uni", action="store_true", help="is link unidirectional?" + "-u", "--uni", action="store_true", help="is link unidirectional?" ) edit_parser.set_defaults(func=edit_link) delete_parser = subparsers.add_parser("delete", help="delete a link") delete_parser.formatter_class = ArgumentDefaultsHelpFormatter - delete_parser.add_argument( - "--node1", type=int, help="node1 id for link", required=True - ) - delete_parser.add_argument( - "--node2", type=int, help="node1 id for link", required=True - ) - delete_parser.add_argument("--iface1", type=int, help="node1 interface id for link") - delete_parser.add_argument("--iface2", type=int, help="node2 interface id for link") + delete_parser.add_argument("-n1", "--node1", type=int, help="node1 id", required=True) + delete_parser.add_argument("-n2", "--node2", type=int, help="node1 id", required=True) + delete_parser.add_argument("-i1", "--iface1", type=int, help="node1 interface id") + delete_parser.add_argument("-i2", "--iface2", type=int, help="node2 interface id") delete_parser.set_defaults(func=delete_link) @@ -419,19 +401,19 @@ def setup_query_parser(parent: _SubParsersAction) -> None: sessions_parser.set_defaults(func=query_sessions) session_parser = subparsers.add_parser("session", help="query session") - session_parser.add_argument("--id", type=int, help="session to query", required=True) + session_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) session_parser.set_defaults(func=query_session) node_parser = subparsers.add_parser("node", help="query node") - node_parser.add_argument("--id", type=int, help="session to query", required=True) - node_parser.add_argument("--node", type=int, help="node to query", required=True) + node_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) + node_parser.add_argument("-n", "--node", type=int, help="node to query", required=True) node_parser.set_defaults(func=query_node) def main() -> None: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument( - "-j", "--json", action="store_true", help="print responses to terminal as json" + "-js", "--json", action="store_true", help="print responses to terminal as json" ) subparsers = parser.add_subparsers(help="supported commands") subparsers.required = True From 69721dc1290a53340df120e4bb27af3993911f5b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:32:56 -0700 Subject: [PATCH 118/146] grpc: updated client edit_node to have source as last parameter to be consistent with source placement on all other functions --- daemon/core/api/grpc/client.py | 4 ++-- daemon/scripts/core-cli | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 82164fe3..20e193eb 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -502,8 +502,8 @@ class CoreGrpcClient: node_id: int, position: core_pb2.Position = None, icon: str = None, - source: str = None, geo: core_pb2.Geo = None, + source: str = None, ) -> core_pb2.EditNodeResponse: """ Edit a node, currently only changes position. @@ -512,8 +512,8 @@ class CoreGrpcClient: :param node_id: node id :param position: position to set node to :param icon: path to icon for gui to use for node - :param source: application source :param geo: lon,lat,alt location for node + :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session or node doesn't exist """ diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 65a92994..df4535ac 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -221,7 +221,7 @@ def edit_node(args: Namespace) -> None: geo = Geo(lon=lon, lat=lat, alt=alt) core = CoreGrpcClient() with core.context_connect(): - response = core.edit_node(session_id, args.id, pos, args.icon, geo=geo) + response = core.edit_node(session_id, args.id, pos, args.icon, geo) if args.json: json = MessageToJson(response, preserving_proto_field_name=True) print(json) From d480a1dd4c92458bf3236fa1dd03dedfeb088146 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:38:22 -0700 Subject: [PATCH 119/146] grpc: removed LinkOptions opaque as it was not being used --- daemon/proto/core/api/grpc/core.proto | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 8112c9d1..3828d474 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -719,17 +719,16 @@ message Link { } message LinkOptions { - string opaque = 1; - int64 jitter = 2; - int32 key = 3; - int32 mburst = 4; - int32 mer = 5; - float loss = 6; - int64 bandwidth = 7; - int32 burst = 8; - int64 delay = 9; - int32 dup = 10; - bool unidirectional = 11; + int64 jitter = 1; + int32 key = 2; + int32 mburst = 3; + int32 mer = 4; + float loss = 5; + int64 bandwidth = 6; + int32 burst = 7; + int64 delay = 8; + int32 dup = 9; + bool unidirectional = 10; } message Interface { From ab17cb1053facc612ed22b2547d0f77686cae304 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 09:50:28 -0700 Subject: [PATCH 120/146] grpc: grpc get_session will no longer return peer to peer nodes, they should be invisible to users, updated core-cli to print human readable links better --- daemon/core/api/grpc/server.py | 4 ++-- daemon/core/gui/nodeutils.py | 2 +- daemon/scripts/core-cli | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index e8469177..4f741b22 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -114,7 +114,7 @@ from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.nodes.base import CoreNode, CoreNodeBase, NodeBase -from core.nodes.network import WlanNode +from core.nodes.network import PtpNet, WlanNode from core.services.coreservices import ServiceManager _ONE_DAY_IN_SECONDS: int = 60 * 60 * 24 @@ -543,7 +543,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): nodes = [] for _id in session.nodes: node = session.nodes[_id] - if not isinstance(node.id, int): + if isinstance(node, PtpNet): continue node_proto = grpcutils.get_node_proto(session, node) nodes.append(node_proto) diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index dbb403df..08c8f31c 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -62,7 +62,7 @@ class NodeUtils: IMAGE_NODES: Set[NodeType] = {NodeType.DOCKER, NodeType.LXC} WIRELESS_NODES: Set[NodeType] = {NodeType.WIRELESS_LAN, NodeType.EMANE} RJ45_NODES: Set[NodeType] = {NodeType.RJ45} - IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET, NodeType.PEER_TO_PEER} + IGNORE_NODES: Set[NodeType] = {NodeType.CONTROL_NET} NODE_MODELS: Set[str] = {"router", "host", "PC", "mdr", "prouter"} ROUTER_NODES: Set[str] = {"router", "mdr"} ANTENNA_ICON: PhotoImage = None diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index df4535ac..05e495e5 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -150,12 +150,16 @@ def query_session(args: Namespace) -> None: n2 = names[link.node2_id] print(f"Node | ", end="") print_iface_header() + print(f"{n1:<6} | ", end="") if link.HasField("iface1"): - print(f"{n1:<6} | ", end="") print_iface(link.iface1) + else: + print() + print(f"{n2:<6} | ", end="") if link.HasField("iface2"): - print(f"{n2:<6} | ", end="") print_iface(link.iface2) + else: + print() print() From beaebcfa2496703308bfc7d7fa732155e5d11020 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 12:34:20 -0700 Subject: [PATCH 121/146] grpc: added node_id and net2_id data to interface protos to allow querying a node to provide the node and networks an interface is associated with --- daemon/core/api/grpc/grpcutils.py | 35 +++++++++++++++------------ daemon/core/api/grpc/server.py | 6 ++--- daemon/proto/core/api/grpc/core.proto | 2 ++ daemon/scripts/core-cli | 22 ++++++++++++++--- 4 files changed, 42 insertions(+), 23 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index ed40a75b..bd9e808d 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -453,32 +453,35 @@ def iface_to_data(iface: CoreInterface) -> InterfaceData: ) -def iface_to_proto(iface: CoreInterface) -> core_pb2.Interface: +def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface: """ Convenience for converting a core interface to the protobuf representation. + + :param node_id: id of node to convert interface for :param iface: interface to convert :return: interface proto """ - net_id = None - if iface.net: - net_id = iface.net.id - ip4 = None - ip4_mask = None + if iface.node and iface.node.id == node_id: + _id = iface.node_id + else: + _id = iface.net_id + net_id = iface.net.id if iface.net else None + node_id = iface.node.id if iface.node else None + net2_id = iface.othernet.id if iface.othernet else None ip4_net = iface.get_ip4() - if ip4_net: - ip4 = str(ip4_net.ip) - ip4_mask = ip4_net.prefixlen - ip6 = None - ip6_mask = None + ip4 = str(ip4_net.ip) if ip4_net else None + ip4_mask = ip4_net.prefixlen if ip4_net else None ip6_net = iface.get_ip6() - if ip6_net: - ip6 = str(ip6_net.ip) - ip6_mask = ip6_net.prefixlen + ip6 = str(ip6_net.ip) if ip6_net else None + ip6_mask = ip6_net.prefixlen if ip6_net else None + mac = str(iface.mac) if iface.mac else None return core_pb2.Interface( - id=iface.node_id, + id=_id, net_id=net_id, + net2_id=net2_id, + node_id=node_id, name=iface.name, - mac=str(iface.mac), + mac=mac, mtu=iface.mtu, flow_id=iface.flow_id, ip4=ip4, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 4f741b22..c447ee7c 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -688,7 +688,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): ifaces = [] for iface_id in node.ifaces: iface = node.ifaces[iface_id] - iface_proto = grpcutils.iface_to_proto(iface) + iface_proto = grpcutils.iface_to_proto(request.node_id, iface) ifaces.append(iface_proto) node_proto = grpcutils.get_node_proto(session, node) return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces) @@ -880,9 +880,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): iface1_proto = None iface2_proto = None if node1_iface: - iface1_proto = grpcutils.iface_to_proto(node1_iface) + iface1_proto = grpcutils.iface_to_proto(node1_id, node1_iface) if node2_iface: - iface2_proto = grpcutils.iface_to_proto(node2_iface) + iface2_proto = grpcutils.iface_to_proto(node2_id, node2_iface) return core_pb2.AddLinkResponse( result=True, iface1=iface1_proto, iface2=iface2_proto ) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 3828d474..f01fca50 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -742,6 +742,8 @@ message Interface { int32 net_id = 8; int32 flow_id = 9; int32 mtu = 10; + int32 node_id = 11; + int32 net2_id = 12; } message SessionLocation { diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 05e495e5..c4d97a8a 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -109,9 +109,9 @@ def print_iface_header() -> None: def print_iface(iface: Interface) -> None: - iface_ip4 = f"{iface.ip4}/{iface.ip4_mask}" if iface.ip4 else None - iface_ip6 = f"{iface.ip6}/{iface.ip6_mask}" if iface.ip6 else None - print(f"{iface.id:<3} | {iface.mac:<11} | {iface_ip4:<18} | {iface_ip6}") + iface_ip4 = f"{iface.ip4}/{iface.ip4_mask}" if iface.ip4 else "" + iface_ip6 = f"{iface.ip6}/{iface.ip6_mask}" if iface.ip6 else "" + print(f"{iface.id:<3} | {iface.mac:<17} | {iface_ip4:<18} | {iface_ip6}") def query_sessions(args: Namespace) -> None: @@ -166,6 +166,11 @@ def query_session(args: Namespace) -> None: def query_node(args: Namespace) -> None: core = CoreGrpcClient() with core.context_connect(): + names = {} + response = core.get_session(args.id) + for node in response.session.nodes: + names[node.id] = node.name + response = core.get_node(args.id, args.node) if args.json: json = MessageToJson(response, preserving_proto_field_name=True) @@ -176,8 +181,17 @@ def query_node(args: Namespace) -> None: print("ID | Name | Type") print(f"{node.id:<4} | {node.name:<7} | {node_type}") print("Interfaces") + print("Connected To | ", end="") print_iface_header() for iface in response.ifaces: + if iface.net_id == node.id: + if iface.node_id: + name = names[iface.node_id] + else: + name = names[iface.net2_id] + else: + name = names[iface.net_id] + print(f"{name:<12} | ", end="") print_iface(iface) @@ -268,7 +282,7 @@ def add_link(args: Namespace) -> None: json = MessageToJson(response, preserving_proto_field_name=True) print(json) else: - print(f"edit link: {response.result}") + print(f"add link: {response.result}") def edit_link(args: Namespace) -> None: From 4a0fdf3307347065cb41052581efa673af08b106 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 15:21:33 -0700 Subject: [PATCH 122/146] core-cli: add function for printing protobuf responses as json --- daemon/scripts/core-cli | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index c4d97a8a..6fe83cf0 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -7,7 +7,7 @@ from argparse import ( Namespace, _SubParsersAction, ) -from typing import Tuple +from typing import Any, Tuple import netaddr from google.protobuf.json_format import MessageToJson @@ -114,13 +114,17 @@ def print_iface(iface: Interface) -> None: print(f"{iface.id:<3} | {iface.mac:<17} | {iface_ip4:<18} | {iface_ip6}") +def print_json(message: Any) -> None: + json = MessageToJson(message, preserving_proto_field_name=True) + print(json) + + def query_sessions(args: Namespace) -> None: core = CoreGrpcClient() with core.context_connect(): response = core.get_sessions() if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print("Session ID | Session State | Nodes") for s in response.sessions: @@ -133,8 +137,7 @@ def query_session(args: Namespace) -> None: with core.context_connect(): response = core.get_session(args.id) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print("Nodes") print("Node ID | Node Name | Node Type") @@ -173,8 +176,7 @@ def query_node(args: Namespace) -> None: response = core.get_node(args.id, args.node) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: node = response.node node_type = NodeType.Enum.Name(node.type) @@ -221,8 +223,7 @@ def add_node(args: Namespace) -> None: ) response = core.add_node(session_id, node) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"created node: {response.node_id}") @@ -241,8 +242,7 @@ def edit_node(args: Namespace) -> None: with core.context_connect(): response = core.edit_node(session_id, args.id, pos, args.icon, geo) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"edit node: {response.result}") @@ -253,8 +253,7 @@ def delete_node(args: Namespace) -> None: with core.context_connect(): response = core.delete_node(session_id, args.id) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"deleted node: {response.result}") @@ -279,8 +278,7 @@ def add_link(args: Namespace) -> None: with core.context_connect(): response = core.add_link(session_id, args.node1, args.node2, iface1, iface2, options) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"add link: {response.result}") @@ -301,8 +299,7 @@ def edit_link(args: Namespace) -> None: session_id, args.node1, args.node2, options, args.iface1, args.iface2 ) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"edit link: {response.result}") @@ -313,8 +310,7 @@ def delete_link(args: Namespace) -> None: with core.context_connect(): response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) if args.json: - json = MessageToJson(response, preserving_proto_field_name=True) - print(json) + print_json(response) else: print(f"delete link: {response.result}") From f22edd1d25b9d003385751e491c693989bdc956e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 16:16:58 -0700 Subject: [PATCH 123/146] grpc: fixed accidental breakage for get_session ptp links --- daemon/core/api/grpc/server.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index c447ee7c..50b15771 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -543,10 +543,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): nodes = [] for _id in session.nodes: node = session.nodes[_id] - if isinstance(node, PtpNet): - continue - node_proto = grpcutils.get_node_proto(session, node) - nodes.append(node_proto) + if not isinstance(node, PtpNet): + node_proto = grpcutils.get_node_proto(session, node) + nodes.append(node_proto) node_links = get_links(node) links.extend(node_links) From 537291b219e731454423fdd2c63775d8ce7c01ba Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 30 Jun 2020 22:16:00 -0700 Subject: [PATCH 124/146] core-cli: added open xml command to a session xml and optionally start it --- daemon/scripts/core-cli | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 6fe83cf0..e4c72996 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -7,6 +7,7 @@ from argparse import ( Namespace, _SubParsersAction, ) +from pathlib import Path from typing import Any, Tuple import netaddr @@ -79,6 +80,13 @@ def geo_type(value: str) -> Tuple[float, float, float]: return lon, lat, alt +def file_type(value: str) -> str: + path = Path(value) + if not path.is_file(): + raise ArgumentTypeError(f"invalid file: {value}") + return str(path.absolute()) + + def get_current_session() -> int: core = CoreGrpcClient() with core.context_connect(): @@ -119,6 +127,16 @@ def print_json(message: Any) -> None: print(json) +def open_xml(args: Namespace) -> None: + core = CoreGrpcClient() + with core.context_connect(): + response = core.open_xml(args.file, args.start) + if args.json: + print_json(response) + else: + print(f"opened xml: {response.result}") + + def query_sessions(args: Namespace) -> None: core = CoreGrpcClient() with core.context_connect(): @@ -317,6 +335,7 @@ def delete_link(args: Namespace) -> None: def setup_node_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("node", help="node interactions") + parser.formatter_class = ArgumentDefaultsHelpFormatter parser.add_argument("-s", "--session", type=int, help="session to interact with") subparsers = parser.add_subparsers(help="node commands") subparsers.required = True @@ -355,6 +374,7 @@ def setup_node_parser(parent: _SubParsersAction) -> None: def setup_link_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("link", help="link interactions") + parser.formatter_class = ArgumentDefaultsHelpFormatter parser.add_argument("-s", "--session", type=int, help="session to interact with") subparsers = parser.add_subparsers(help="link commands") subparsers.required = True @@ -412,18 +432,29 @@ def setup_query_parser(parent: _SubParsersAction) -> None: subparsers.dest = "command" sessions_parser = subparsers.add_parser("sessions", help="query current sessions") + sessions_parser.formatter_class = ArgumentDefaultsHelpFormatter sessions_parser.set_defaults(func=query_sessions) session_parser = subparsers.add_parser("session", help="query session") + session_parser.formatter_class = ArgumentDefaultsHelpFormatter session_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) session_parser.set_defaults(func=query_session) node_parser = subparsers.add_parser("node", help="query node") + node_parser.formatter_class = ArgumentDefaultsHelpFormatter node_parser.add_argument("-i", "--id", type=int, help="session to query", required=True) node_parser.add_argument("-n", "--node", type=int, help="node to query", required=True) node_parser.set_defaults(func=query_node) +def setup_xml_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("xml", help="open session xml") + parser.formatter_class = ArgumentDefaultsHelpFormatter + parser.add_argument("file", type=file_type, help="xml file to open") + parser.add_argument("-s", "--start", action="store_true", help="start the session?") + parser.set_defaults(func=open_xml) + + def main() -> None: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument( @@ -435,6 +466,7 @@ def main() -> None: setup_node_parser(subparsers) setup_link_parser(subparsers) setup_query_parser(subparsers) + setup_xml_parser(subparsers) args = parser.parse_args() args.func(args) From 3477e84e9d029bed25fe4d6398e2a515cccb79bb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 09:30:05 -0700 Subject: [PATCH 125/146] core-cli: added wlan set/get config, fixed session option for node/link interactions --- daemon/core/api/grpc/server.py | 11 ++--- daemon/scripts/core-cli | 88 ++++++++++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 14 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 50b15771..aa5ec539 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -1343,13 +1343,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("set wlan config: %s", request) session = self.get_session(request.session_id, context) - wlan_config = request.wlan_config - session.mobility.set_model_config( - wlan_config.node_id, BasicRangeModel.name, wlan_config.config - ) + node_id = request.wlan_config.node_id + config = request.wlan_config.config + session.mobility.set_model_config(node_id, BasicRangeModel.name, config) if session.state == EventTypes.RUNTIME_STATE: - node = self.get_node(session, wlan_config.node_id, context, WlanNode) - node.updatemodel(wlan_config.config) + node = self.get_node(session, node_id, context, WlanNode) + node.updatemodel(config) return SetWlanConfigResponse(result=True) def GetEmaneConfig( diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index e4c72996..9dfadfcc 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -8,8 +8,9 @@ from argparse import ( _SubParsersAction, ) from pathlib import Path -from typing import Any, Tuple +from typing import Any, Optional, Tuple +import grpc import netaddr from google.protobuf.json_format import MessageToJson from netaddr import EUI, AddrFormatError, IPNetwork @@ -87,7 +88,9 @@ def file_type(value: str) -> str: return str(path.absolute()) -def get_current_session() -> int: +def get_current_session(session_id: Optional[int]) -> int: + if session_id: + return session_id core = CoreGrpcClient() with core.context_connect(): response = core.get_sessions() @@ -127,6 +130,50 @@ def print_json(message: Any) -> None: print(json) +def get_wlan_config(args: Namespace) -> None: + session_id = get_current_session(args.session) + core = CoreGrpcClient() + try: + with core.context_connect(): + response = core.get_wlan_config(session_id, args.node) + if args.json: + print_json(response) + else: + size = 0 + for option in response.config.values(): + size = max(size, len(option.name)) + print(f"{'Name':<{size}.{size}} | Value") + for option in response.config.values(): + print(f"{option.name:<{size}.{size}} | {option.value}") + except grpc.RpcError as e: + print(f"grpc error: {e.details()}") + + +def set_wlan_config(args: Namespace) -> None: + session_id = get_current_session(args.session) + config = {} + if args.bandwidth: + config["bandwidth"] = str(args.bandwidth) + if args.delay: + config["delay"] = str(args.delay) + if args.loss: + config["error"] = str(args.loss) + if args.jitter: + config["jitter"] = str(args.jitter) + if args.range: + config["range"] = str(args.range) + core = CoreGrpcClient() + try: + with core.context_connect(): + response = core.set_wlan_config(session_id, args.node, config) + if args.json: + print_json(response) + else: + print(f"set wlan config: {response.result}") + except grpc.RpcError as e: + print(f"grpc error: {e.details()}") + + def open_xml(args: Namespace) -> None: core = CoreGrpcClient() with core.context_connect(): @@ -216,7 +263,7 @@ def query_node(args: Namespace) -> None: def add_node(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) node_type = NodeType.Enum.Value(args.type) pos = None if args.pos: @@ -247,7 +294,7 @@ def add_node(args: Namespace) -> None: def edit_node(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) pos = None if args.pos: x, y = args.pos @@ -266,7 +313,7 @@ def edit_node(args: Namespace) -> None: def delete_node(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) core = CoreGrpcClient() with core.context_connect(): response = core.delete_node(session_id, args.id) @@ -277,7 +324,7 @@ def delete_node(args: Namespace) -> None: def add_link(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) iface1 = None if args.iface1_id is not None: iface1 = create_iface(args.iface1_id, args.iface1_mac, args.iface1_ip4, args.iface1_ip6) @@ -302,7 +349,7 @@ def add_link(args: Namespace) -> None: def edit_link(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) options = LinkOptions( bandwidth=args.bandwidth, loss=args.loss, @@ -323,7 +370,7 @@ def edit_link(args: Namespace) -> None: def delete_link(args: Namespace) -> None: - session_id = get_current_session() + session_id = get_current_session(args.session) core = CoreGrpcClient() with core.context_connect(): response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) @@ -455,6 +502,30 @@ def setup_xml_parser(parent: _SubParsersAction) -> None: parser.set_defaults(func=open_xml) +def setup_wlan_parser(parent: _SubParsersAction) -> None: + parser = parent.add_parser("wlan", help="wlan specific interactions") + parser.formatter_class = ArgumentDefaultsHelpFormatter + parser.add_argument("-s", "--session", type=int, help="session to interact with") + subparsers = parser.add_subparsers(help="link commands") + subparsers.required = True + subparsers.dest = "command" + + get_parser = subparsers.add_parser("get", help="get wlan configuration") + get_parser.formatter_class = ArgumentDefaultsHelpFormatter + get_parser.add_argument("-n", "--node", type=int, help="wlan node", required=True) + get_parser.set_defaults(func=get_wlan_config) + + set_parser = subparsers.add_parser("set", help="set wlan configuration") + set_parser.formatter_class = ArgumentDefaultsHelpFormatter + set_parser.add_argument("-n", "--node", type=int, help="wlan node", required=True) + set_parser.add_argument("-b", "--bandwidth", type=int, help="bandwidth (bps)") + set_parser.add_argument("-d", "--delay", type=int, help="delay (us)") + set_parser.add_argument("-l", "--loss", type=float, help="loss (%%)") + set_parser.add_argument("-j", "--jitter", type=int, help="jitter (us)") + set_parser.add_argument("-r", "--range", type=int, help="range (pixels)") + set_parser.set_defaults(func=set_wlan_config) + + def main() -> None: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument( @@ -467,6 +538,7 @@ def main() -> None: setup_link_parser(subparsers) setup_query_parser(subparsers) setup_xml_parser(subparsers) + setup_wlan_parser(subparsers) args = parser.parse_args() args.func(args) From 7a6c602369ebb44777df7502e63dac348b693b45 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 11:01:44 -0700 Subject: [PATCH 126/146] core-cli: cleaned up core client usage by way of a decorator, helps provide convenient grpc error catching --- daemon/scripts/core-cli | 346 ++++++++++++++++++++-------------------- 1 file changed, 170 insertions(+), 176 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 9dfadfcc..61b47ae4 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -7,6 +7,7 @@ from argparse import ( Namespace, _SubParsersAction, ) +from functools import wraps from pathlib import Path from typing import Any, Optional, Tuple @@ -29,6 +30,19 @@ from core.api.grpc.core_pb2 import ( NODE_TYPES = [k for k, v in NodeType.Enum.items() if v != NodeType.PEER_TO_PEER] +def coreclient(func): + @wraps(func) + def wrapper(*args, **kwargs): + core = CoreGrpcClient() + try: + with core.context_connect(): + return func(core, *args, **kwargs) + except grpc.RpcError as e: + print(f"grpc error: {e.details()}") + + return wrapper + + def mac_type(value: str) -> str: try: mac = EUI(value, dialect=netaddr.mac_unix_expanded) @@ -88,12 +102,10 @@ def file_type(value: str) -> str: return str(path.absolute()) -def get_current_session(session_id: Optional[int]) -> int: +def get_current_session(core: CoreGrpcClient, session_id: Optional[int]) -> int: if session_id: return session_id - core = CoreGrpcClient() - with core.context_connect(): - response = core.get_sessions() + response = core.get_sessions() if not response.sessions: print("no current session to interact with") sys.exit(1) @@ -130,27 +142,24 @@ def print_json(message: Any) -> None: print(json) -def get_wlan_config(args: Namespace) -> None: - session_id = get_current_session(args.session) - core = CoreGrpcClient() - try: - with core.context_connect(): - response = core.get_wlan_config(session_id, args.node) - if args.json: - print_json(response) - else: - size = 0 - for option in response.config.values(): - size = max(size, len(option.name)) - print(f"{'Name':<{size}.{size}} | Value") - for option in response.config.values(): - print(f"{option.name:<{size}.{size}} | {option.value}") - except grpc.RpcError as e: - print(f"grpc error: {e.details()}") +@coreclient +def get_wlan_config(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) + response = core.get_wlan_config(session_id, args.node) + if args.json: + print_json(response) + else: + size = 0 + for option in response.config.values(): + size = max(size, len(option.name)) + print(f"{'Name':<{size}.{size}} | Value") + for option in response.config.values(): + print(f"{option.name:<{size}.{size}} | {option.value}") -def set_wlan_config(args: Namespace) -> None: - session_id = get_current_session(args.session) +@coreclient +def set_wlan_config(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) config = {} if args.bandwidth: config["bandwidth"] = str(args.bandwidth) @@ -162,108 +171,100 @@ def set_wlan_config(args: Namespace) -> None: config["jitter"] = str(args.jitter) if args.range: config["range"] = str(args.range) - core = CoreGrpcClient() - try: - with core.context_connect(): - response = core.set_wlan_config(session_id, args.node, config) - if args.json: - print_json(response) - else: - print(f"set wlan config: {response.result}") - except grpc.RpcError as e: - print(f"grpc error: {e.details()}") + response = core.set_wlan_config(session_id, args.node, config) + if args.json: + print_json(response) + else: + print(f"set wlan config: {response.result}") -def open_xml(args: Namespace) -> None: - core = CoreGrpcClient() - with core.context_connect(): - response = core.open_xml(args.file, args.start) - if args.json: - print_json(response) - else: - print(f"opened xml: {response.result}") +@coreclient +def open_xml(core: CoreGrpcClient, args: Namespace) -> None: + response = core.open_xml(args.file, args.start) + if args.json: + print_json(response) + else: + print(f"opened xml: {response.result}") -def query_sessions(args: Namespace) -> None: - core = CoreGrpcClient() - with core.context_connect(): - response = core.get_sessions() - if args.json: - print_json(response) - else: - print("Session ID | Session State | Nodes") - for s in response.sessions: - state = SessionState.Enum.Name(s.state) - print(f"{s.id:<10} | {state:<13} | {s.nodes}") +@coreclient +def query_sessions(core: CoreGrpcClient, args: Namespace) -> None: + response = core.get_sessions() + if args.json: + print_json(response) + else: + print("Session ID | Session State | Nodes") + for s in response.sessions: + state = SessionState.Enum.Name(s.state) + print(f"{s.id:<10} | {state:<13} | {s.nodes}") -def query_session(args: Namespace) -> None: - core = CoreGrpcClient() - with core.context_connect(): - response = core.get_session(args.id) - if args.json: - print_json(response) - else: - print("Nodes") - print("Node ID | Node Name | Node Type") - names = {} - for node in response.session.nodes: - names[node.id] = node.name - node_type = NodeType.Enum.Name(node.type) - print(f"{node.id:<7} | {node.name:<9} | {node_type}") - - print("\nLinks") - for link in response.session.links: - n1 = names[link.node1_id] - n2 = names[link.node2_id] - print(f"Node | ", end="") - print_iface_header() - print(f"{n1:<6} | ", end="") - if link.HasField("iface1"): - print_iface(link.iface1) - else: - print() - print(f"{n2:<6} | ", end="") - if link.HasField("iface2"): - print_iface(link.iface2) - else: - print() - print() - - -def query_node(args: Namespace) -> None: - core = CoreGrpcClient() - with core.context_connect(): +@coreclient +def query_session(core: CoreGrpcClient, args: Namespace) -> None: + response = core.get_session(args.id) + if args.json: + print_json(response) + else: + print("Nodes") + print("Node ID | Node Name | Node Type") names = {} - response = core.get_session(args.id) for node in response.session.nodes: names[node.id] = node.name - - response = core.get_node(args.id, args.node) - if args.json: - print_json(response) - else: - node = response.node node_type = NodeType.Enum.Name(node.type) - print("ID | Name | Type") - print(f"{node.id:<4} | {node.name:<7} | {node_type}") - print("Interfaces") - print("Connected To | ", end="") + print(f"{node.id:<7} | {node.name:<9} | {node_type}") + + print("\nLinks") + for link in response.session.links: + n1 = names[link.node1_id] + n2 = names[link.node2_id] + print(f"Node | ", end="") print_iface_header() - for iface in response.ifaces: - if iface.net_id == node.id: - if iface.node_id: - name = names[iface.node_id] - else: - name = names[iface.net2_id] + print(f"{n1:<6} | ", end="") + if link.HasField("iface1"): + print_iface(link.iface1) + else: + print() + print(f"{n2:<6} | ", end="") + if link.HasField("iface2"): + print_iface(link.iface2) + else: + print() + print() + + +@coreclient +def query_node(core: CoreGrpcClient, args: Namespace) -> None: + names = {} + response = core.get_session(args.id) + for node in response.session.nodes: + names[node.id] = node.name + + response = core.get_node(args.id, args.node) + if args.json: + print_json(response) + else: + node = response.node + node_type = NodeType.Enum.Name(node.type) + print("ID | Name | Type") + print(f"{node.id:<4} | {node.name:<7} | {node_type}") + print("Interfaces") + print("Connected To | ", end="") + print_iface_header() + for iface in response.ifaces: + if iface.net_id == node.id: + if iface.node_id: + name = names[iface.node_id] else: - name = names[iface.net_id] - print(f"{name:<12} | ", end="") - print_iface(iface) + name = names[iface.net2_id] + else: + name = names[iface.net_id] + print(f"{name:<12} | ", end="") + print_iface(iface) -def add_node(args: Namespace) -> None: - session_id = get_current_session(args.session) +@coreclient +def add_node(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) node_type = NodeType.Enum.Value(args.type) pos = None if args.pos: @@ -273,28 +274,27 @@ def add_node(args: Namespace) -> None: if args.geo: lon, lat, alt = args.geo geo = Geo(lon=lon, lat=lat, alt=alt) - core = CoreGrpcClient() - with core.context_connect(): - node = Node( - id=args.id, - name=args.name, - type=node_type, - model=args.model, - emane=args.emane, - icon=args.icon, - image=args.image, - position=pos, - geo=geo, - ) - response = core.add_node(session_id, node) - if args.json: - print_json(response) - else: - print(f"created node: {response.node_id}") + node = Node( + id=args.id, + name=args.name, + type=node_type, + model=args.model, + emane=args.emane, + icon=args.icon, + image=args.image, + position=pos, + geo=geo, + ) + response = core.add_node(session_id, node) + if args.json: + print_json(response) + else: + print(f"created node: {response.node_id}") -def edit_node(args: Namespace) -> None: - session_id = get_current_session(args.session) +@coreclient +def edit_node(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) pos = None if args.pos: x, y = args.pos @@ -303,28 +303,26 @@ def edit_node(args: Namespace) -> None: if args.geo: lon, lat, alt = args.geo geo = Geo(lon=lon, lat=lat, alt=alt) - core = CoreGrpcClient() - with core.context_connect(): - response = core.edit_node(session_id, args.id, pos, args.icon, geo) - if args.json: - print_json(response) - else: - print(f"edit node: {response.result}") + response = core.edit_node(session_id, args.id, pos, args.icon, geo) + if args.json: + print_json(response) + else: + print(f"edit node: {response.result}") -def delete_node(args: Namespace) -> None: - session_id = get_current_session(args.session) - core = CoreGrpcClient() - with core.context_connect(): - response = core.delete_node(session_id, args.id) - if args.json: - print_json(response) - else: - print(f"deleted node: {response.result}") +@coreclient +def delete_node(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) + response = core.delete_node(session_id, args.id) + if args.json: + print_json(response) + else: + print(f"deleted node: {response.result}") -def add_link(args: Namespace) -> None: - session_id = get_current_session(args.session) +@coreclient +def add_link(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) iface1 = None if args.iface1_id is not None: iface1 = create_iface(args.iface1_id, args.iface1_mac, args.iface1_ip4, args.iface1_ip6) @@ -339,17 +337,16 @@ def add_link(args: Namespace) -> None: dup=args.duplicate, unidirectional=args.uni, ) - core = CoreGrpcClient() - with core.context_connect(): - response = core.add_link(session_id, args.node1, args.node2, iface1, iface2, options) - if args.json: - print_json(response) - else: - print(f"add link: {response.result}") + response = core.add_link(session_id, args.node1, args.node2, iface1, iface2, options) + if args.json: + print_json(response) + else: + print(f"add link: {response.result}") -def edit_link(args: Namespace) -> None: - session_id = get_current_session(args.session) +@coreclient +def edit_link(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) options = LinkOptions( bandwidth=args.bandwidth, loss=args.loss, @@ -358,26 +355,23 @@ def edit_link(args: Namespace) -> None: dup=args.duplicate, unidirectional=args.uni, ) - core = CoreGrpcClient() - with core.context_connect(): - response = core.edit_link( - session_id, args.node1, args.node2, options, args.iface1, args.iface2 - ) - if args.json: - print_json(response) - else: - print(f"edit link: {response.result}") + response = core.edit_link( + session_id, args.node1, args.node2, options, args.iface1, args.iface2 + ) + if args.json: + print_json(response) + else: + print(f"edit link: {response.result}") -def delete_link(args: Namespace) -> None: - session_id = get_current_session(args.session) - core = CoreGrpcClient() - with core.context_connect(): - response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) - if args.json: - print_json(response) - else: - print(f"delete link: {response.result}") +@coreclient +def delete_link(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) + response = core.delete_link(session_id, args.node1, args.node2, args.iface1, args.iface2) + if args.json: + print_json(response) + else: + print(f"delete link: {response.result}") def setup_node_parser(parent: _SubParsersAction) -> None: From 08bbaf463b576270b617e16fbaa58996d3f1311d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 11:06:09 -0700 Subject: [PATCH 127/146] core-cli: updated xml command to use a flag argument to be consistent for now --- daemon/scripts/core-cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 61b47ae4..471facb3 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -491,7 +491,7 @@ def setup_query_parser(parent: _SubParsersAction) -> None: def setup_xml_parser(parent: _SubParsersAction) -> None: parser = parent.add_parser("xml", help="open session xml") parser.formatter_class = ArgumentDefaultsHelpFormatter - parser.add_argument("file", type=file_type, help="xml file to open") + parser.add_argument("-f", "--file", type=file_type, help="xml file to open", required=True) parser.add_argument("-s", "--start", action="store_true", help="start the session?") parser.set_defaults(func=open_xml) From a870c15b43a386a439ee6ef7815714d1a9bf4f07 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 12:11:34 -0700 Subject: [PATCH 128/146] pygui: fixed joining sessions with mobility players --- daemon/core/gui/coreclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index cf331676..7cf8b123 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -377,7 +377,7 @@ class CoreClient: # organize canvas self.app.canvas.organize() - + self.show_mobility_players() # update ui to represent current state self.app.after(0, self.app.joined_session_update) From da9c0d066083e580482822cbe2c42ad08d55c036 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 14:40:19 -0700 Subject: [PATCH 129/146] daemon: initial changes to breakout custom interface creation for networks that require it, without being emane specific --- daemon/core/emane/nodes.py | 36 +++++++++++++++++++++++++++++++++--- daemon/core/nodes/base.py | 36 +++++++++++++++++++----------------- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 8cc9cd87..be95e6d0 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -6,9 +6,10 @@ share the same MAC+PHY model. import logging from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type -from core.emulator.data import LinkData, LinkOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.distributed import DistributedServer from core.emulator.enumerations import ( + EventTypes, LinkTypes, MessageFlags, NodeTypes, @@ -16,7 +17,7 @@ from core.emulator.enumerations import ( TransportType, ) from core.errors import CoreError -from core.nodes.base import CoreNetworkBase +from core.nodes.base import CoreNetworkBase, CoreNode from core.nodes.interface import CoreInterface, TunTap if TYPE_CHECKING: @@ -47,7 +48,7 @@ class EmaneNet(CoreNetworkBase): apitype: NodeTypes = NodeTypes.EMANE linktype: LinkTypes = LinkTypes.WIRED type: str = "wlan" - is_emane: bool = True + has_custom_iface: bool = True def __init__( self, @@ -262,3 +263,32 @@ class EmaneNet(CoreNetworkBase): if link: links.append(link) return links + + def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface: + # TUN/TAP is not ready for addressing yet; the device may + # take some time to appear, and installing it into a + # namespace after it has been bound removes addressing; + # save addresses with the interface now + iface_id = node.newtuntap(iface_data.id, iface_data.name) + node.attachnet(iface_id, self) + iface = node.get_iface(iface_id) + iface.set_mac(iface_data.mac) + for ip in iface_data.get_ips(): + iface.add_ip(ip) + # TODO: if added during runtime start EMANE + if self.session.state == EventTypes.RUNTIME_STATE: + logging.info("startup emane for node: %s", node.name) + # create specific xml if needed + config = self.session.emane.get_iface_config( + self.model.id, iface, self.model.name + ) + if config: + self.model.build_xml_files(config, iface) + + # start emane daemon + + # install netif + + # add nem to nemfile + + return iface diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 05ec87dc..039008ef 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -812,30 +812,29 @@ class CoreNode(CoreNodeBase): :param iface_data: interface data for new interface :return: interface index """ - ips = iface_data.get_ips() with self.lock: - # TODO: emane specific code - if net.is_emane is True: - iface_id = self.newtuntap(iface_data.id, iface_data.name) - # TUN/TAP is not ready for addressing yet; the device may - # take some time to appear, and installing it into a - # namespace after it has been bound removes addressing; - # save addresses with the interface now - self.attachnet(iface_id, net) - iface = self.get_iface(iface_id) - iface.set_mac(iface_data.mac) - for ip in ips: - iface.add_ip(ip) + if net.has_custom_iface: + return net.custom_iface(self, iface_data) + # if net.is_emane is True: + # iface_id = self.newtuntap(iface_data.id, iface_data.name) + # # TUN/TAP is not ready for addressing yet; the device may + # # take some time to appear, and installing it into a + # # namespace after it has been bound removes addressing; + # # save addresses with the interface now + # self.attachnet(iface_id, net) + # iface = self.get_iface(iface_id) + # iface.set_mac(iface_data.mac) + # for ip in ips: + # iface.add_ip(ip) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) if iface_data.mac: self.set_mac(iface_id, iface_data.mac) - for ip in ips: + for ip in iface_data.get_ips(): self.add_ip(iface_id, ip) self.ifup(iface_id) - iface = self.get_iface(iface_id) - return iface + return self.get_iface(iface_id) def addfile(self, srcname: str, filename: str) -> None: """ @@ -925,7 +924,7 @@ class CoreNetworkBase(NodeBase): """ linktype: LinkTypes = LinkTypes.WIRED - is_emane: bool = False + has_custom_iface: bool = False def __init__( self, @@ -990,6 +989,9 @@ class CoreNetworkBase(NodeBase): """ raise NotImplementedError + def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface: + raise NotImplementedError + def get_linked_iface(self, net: "CoreNetworkBase") -> Optional[CoreInterface]: """ Return the interface that links this net with another net. From e549830e3342effb9ad5aaeea9d2972fa355902b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 1 Jul 2020 15:20:53 -0700 Subject: [PATCH 130/146] core-cli: fix to avoid errors for querying nodes with peer to peer links, until there is a proper way to get the other ends node name --- daemon/scripts/core-cli | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 471facb3..a7571471 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -257,7 +257,7 @@ def query_node(core: CoreGrpcClient, args: Namespace) -> None: else: name = names[iface.net2_id] else: - name = names[iface.net_id] + name = names.get(iface.net_id, "") print(f"{name:<12} | ", end="") print_iface(iface) From bd48e14348c05800930618aaa02f1bf616fb393c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 15:37:51 -0700 Subject: [PATCH 131/146] daemon: initial changes to rework logic to start emane for a given interface --- daemon/core/emane/commeffect.py | 19 +- daemon/core/emane/emanemanager.py | 279 +++++++++-------------- daemon/core/emane/emanemodel.py | 27 +-- daemon/core/emane/nodes.py | 66 +----- daemon/core/emulator/session.py | 4 +- daemon/core/xml/emanexml.py | 363 +++++++++++------------------- 6 files changed, 262 insertions(+), 496 deletions(-) diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 610099f1..100af9a7 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -62,9 +62,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): def config_groups(cls) -> List[ConfigGroup]: return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))] - def build_xml_files( - self, config: Dict[str, str], iface: CoreInterface = None - ) -> None: + def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None: """ Build the necessary nem and commeffect XMLs in the given path. If an individual NEM has a nonstandard config, we need to build @@ -75,22 +73,25 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): :param iface: interface for the emane node :return: nothing """ + # interface node + node = iface.node + # retrieve xml names - nem_name = emanexml.nem_file_name(self, iface) - shim_name = emanexml.shim_file_name(self, iface) + nem_name = emanexml.nem_file_name(iface) + shim_name = emanexml.shim_file_name(iface) # create and write nem document nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured") transport_type = TransportType.VIRTUAL - if iface and iface.transport_type == TransportType.RAW: + if iface.transport_type == TransportType.RAW: transport_type = TransportType.RAW - transport_file = emanexml.transport_file_name(self.id, transport_type) + transport_file = emanexml.transport_file_name(iface, transport_type) etree.SubElement(nem_element, "transport", definition=transport_file) # set shim configuration etree.SubElement(nem_element, "shim", definition=shim_name) - nem_file = os.path.join(self.session.session_dir, nem_name) + nem_file = os.path.join(node.nodedir, nem_name) emanexml.create_file(nem_element, "nem", nem_file) # create and write shim document @@ -111,7 +112,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): if ff.strip() != "": emanexml.add_param(shim_element, "filterfile", ff) - shim_file = os.path.join(self.session.session_dir, shim_name) + shim_file = os.path.join(node.nodedir, shim_name) emanexml.create_file(shim_element, "shim", shim_file) def linkconfig( diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index fc561b5f..3317a5db 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -6,6 +6,7 @@ import logging import os import threading from collections import OrderedDict +from enum import Enum from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type from core import utils @@ -25,11 +26,11 @@ from core.emulator.enumerations import ( LinkTypes, MessageFlags, RegisterTlvs, + TransportType, ) from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode, NodeBase -from core.nodes.interface import CoreInterface -from core.nodes.network import CtrlNet +from core.nodes.interface import CoreInterface, TunTap from core.nodes.physical import Rj45Node from core.xml import emanexml @@ -63,6 +64,12 @@ DEFAULT_EMANE_PREFIX = "/usr" DEFAULT_DEV = "ctrl0" +class EmaneState(Enum): + SUCCESS = 0 + NOT_NEEDED = 1 + NOT_READY = 2 + + class EmaneManager(ModelManager): """ EMANE controller object. Lives in a Session instance and is used for @@ -72,8 +79,6 @@ class EmaneManager(ModelManager): name: str = "emane" config_type: RegisterTlvs = RegisterTlvs.EMULATION_SERVER - SUCCESS: int = 0 - NOT_NEEDED: int = 1 NOT_READY: int = 2 EVENTCFGVAR: str = "LIBEMANEEVENTSERVICECONFIG" DEFAULT_LOG_LEVEL: int = 3 @@ -87,6 +92,7 @@ class EmaneManager(ModelManager): """ super().__init__() self.session: "Session" = session + self.nems: Dict[int, CoreInterface] = {} self._emane_nets: Dict[int, EmaneNet] = {} self._emane_node_lock: threading.Lock = threading.Lock() # port numbers are allocated from these counters @@ -111,46 +117,47 @@ class EmaneManager(ModelManager): self.event_device: Optional[str] = None self.emane_check() + def next_nem_id(self) -> int: + nem_id = int(self.get_config("nem_id_start")) + while nem_id in self.nems: + nem_id += 1 + return nem_id + def get_iface_config( - self, node_id: int, iface: CoreInterface, model_name: str + self, emane_net: EmaneNet, iface: CoreInterface ) -> Dict[str, str]: """ - Retrieve interface configuration or node configuration if not provided. + Retrieve configuration for a given interface. - :param node_id: node id - :param iface: node interface - :param model_name: model to get configuration for - :return: node/interface model configuration + :param emane_net: emane network the interface is connected to + :param iface: interface running emane + :return: net, node, or interface model configuration """ + model_name = emane_net.model.name # use the network-wide config values or interface(NEM)-specific values? if iface is None: - return self.get_configs(node_id=node_id, config_type=model_name) + return self.get_configs(node_id=emane_net.id, config_type=model_name) else: # don"t use default values when interface config is the same as net # note here that using iface.node.id as key allows for only one type # of each model per node; # TODO: use both node and interface as key - # Adamson change: first check for iface config keyed by "node:iface.name" # (so that nodes w/ multiple interfaces of same conftype can have # different configs for each separate interface) key = 1000 * iface.node.id if iface.node_id is not None: key += iface.node_id - # try retrieve interface specific configuration, avoid getting defaults config = self.get_configs(node_id=key, config_type=model_name) - # otherwise retrieve the interfaces node configuration, avoid using defaults if not config: config = self.get_configs(node_id=iface.node.id, config_type=model_name) - # get non interface config, when none found if not config: # with EMANE 0.9.2+, we need an extra NEM XML from # model.buildnemxmlfiles(), so defaults are returned here - config = self.get_configs(node_id=node_id, config_type=model_name) - + config = self.get_configs(node_id=emane_net.id, config_type=model_name) return config def config_reset(self, node_id: int = None) -> None: @@ -260,14 +267,13 @@ class EmaneManager(ModelManager): Return a set of CoreNodes that are linked to an EMANE network, e.g. containers having one or more radio interfaces. """ - # assumes self._objslock already held nodes = set() for emane_net in self._emane_nets.values(): for iface in emane_net.get_ifaces(): nodes.add(iface.node) return nodes - def setup(self) -> int: + def setup(self) -> EmaneState: """ Setup duties for EMANE manager. @@ -288,7 +294,7 @@ class EmaneManager(ModelManager): if not self._emane_nets: logging.debug("no emane nodes in session") - return EmaneManager.NOT_NEEDED + return EmaneState.NOT_NEEDED # check if bindings were installed if EventService is None: @@ -304,7 +310,7 @@ class EmaneManager(ModelManager): "EMANE cannot start, check core config. invalid OTA device provided: %s", otadev, ) - return EmaneManager.NOT_READY + return EmaneState.NOT_READY self.session.add_remove_control_net( net_index=netidx, remove=False, conf_required=False @@ -319,16 +325,16 @@ class EmaneManager(ModelManager): "EMANE cannot start, check core config. invalid event service device: %s", eventdev, ) - return EmaneManager.NOT_READY + return EmaneState.NOT_READY self.session.add_remove_control_net( net_index=netidx, remove=False, conf_required=False ) self.check_node_models() - return EmaneManager.SUCCESS + return EmaneState.SUCCESS - def startup(self) -> int: + def startup(self) -> EmaneState: """ After all the EMANE networks have been added, build XML files and start the daemons. @@ -337,39 +343,49 @@ class EmaneManager(ModelManager): instantiation """ self.reset() - r = self.setup() - - # NOT_NEEDED or NOT_READY - if r != EmaneManager.SUCCESS: - return r - - nems = [] + status = self.setup() + if status != EmaneState.SUCCESS: + return status + self.starteventmonitor() + self.buildeventservicexml() with self._emane_node_lock: - self.buildxml() - self.starteventmonitor() - - if self.numnems() > 0: - self.startdaemons() - self.install_ifaces() - - for node_id in self._emane_nets: - emane_node = self._emane_nets[node_id] - for iface in emane_node.get_ifaces(): - nems.append( - (iface.node.name, iface.name, emane_node.getnemid(iface)) + # on master, control network bridge added earlier in startup() + control_net = self.session.add_remove_control_net( + 0, remove=False, conf_required=False + ) + logging.info("emane building xmls...") + for node_id in sorted(self._emane_nets): + emane_net = self._emane_nets[node_id] + if not emane_net.model: + logging.error("emane net(%s) has no model", emane_net.name) + continue + for iface in emane_net.get_ifaces(): + if not iface.node: + logging.error( + "emane net(%s) connected interface missing node", + emane_net.name, + ) + continue + nem_id = self.next_nem_id() + self.nems[nem_id] = iface + self.write_nem(iface, nem_id) + emanexml.build_platform_xml( + self, control_net, emane_net, iface, nem_id ) - - if nems: - emane_nems_filename = os.path.join(self.session.session_dir, "emane_nems") - try: - with open(emane_nems_filename, "w") as f: - for nodename, ifname, nemid in nems: - f.write(f"{nodename} {ifname} {nemid}\n") - except IOError: - logging.exception("Error writing EMANE NEMs file: %s") + emanexml.build_model_xmls(self, emane_net, iface) + self.start_daemon(iface) + self.install_iface(emane_net, iface) if self.links_enabled(): self.link_monitor.start() - return EmaneManager.SUCCESS + return EmaneState.SUCCESS + + def write_nem(self, iface: CoreInterface, nem_id: int) -> None: + path = os.path.join(self.session.session_dir, "emane_nems") + try: + with open(path, "a") as f: + f.write(f"{iface.node.name} {iface.name} {nem_id}\n") + except IOError: + logging.exception("error writing to emane nem file") def links_enabled(self) -> bool: return self.get_config("link_enabled") == "1" @@ -380,17 +396,14 @@ class EmaneManager(ModelManager): """ if not self.genlocationevents(): return - with self._emane_node_lock: - for key in sorted(self._emane_nets.keys()): - emane_node = self._emane_nets[key] + for node_id in sorted(self._emane_nets): + emane_net = self._emane_nets[node_id] logging.debug( - "post startup for emane node: %s - %s", - emane_node.id, - emane_node.name, + "post startup for emane node: %s - %s", emane_net.id, emane_net.name ) - emane_node.model.post_startup() - for iface in emane_node.get_ifaces(): + emane_net.model.post_startup() + for iface in emane_net.get_ifaces(): iface.setposition() def reset(self) -> None: @@ -400,13 +413,7 @@ class EmaneManager(ModelManager): """ with self._emane_node_lock: self._emane_nets.clear() - - self.platformport = self.session.options.get_config_int( - "emane_platform_port", 8100 - ) - self.transformport = self.session.options.get_config_int( - "emane_transform_port", 8200 - ) + self.nems.clear() def shutdown(self) -> None: """ @@ -422,40 +429,23 @@ class EmaneManager(ModelManager): self.stopdaemons() self.stopeventmonitor() - def buildxml(self) -> None: - """ - Build XML files required to run EMANE on each node. - NEMs run inside containers using the control network for passing - events and data. - """ - # assume self._objslock is already held here - logging.info("emane building xml...") - # on master, control network bridge added earlier in startup() - ctrlnet = self.session.add_remove_control_net( - net_index=0, remove=False, conf_required=False - ) - self.buildplatformxml(ctrlnet) - self.buildnemxml() - self.buildeventservicexml() - def check_node_models(self) -> None: """ Associate EMANE model classes with EMANE network nodes. """ for node_id in self._emane_nets: - emane_node = self._emane_nets[node_id] + emane_net = self._emane_nets[node_id] logging.debug("checking emane model for node: %s", node_id) # skip nodes that already have a model set - if emane_node.model: + if emane_net.model: logging.debug( - "node(%s) already has model(%s)", - emane_node.id, - emane_node.model.name, + "node(%s) already has model(%s)", emane_net.id, emane_net.model.name ) continue - # set model configured for node, due to legacy messaging configuration before nodes exist + # set model configured for node, due to legacy messaging configuration + # before nodes exist model_name = self.node_models.get(node_id) if not model_name: logging.error("emane node(%s) has no node model", node_id) @@ -464,7 +454,7 @@ class EmaneManager(ModelManager): config = self.get_model_config(node_id=node_id, model_name=model_name) logging.debug("setting emane model(%s) config(%s)", model_name, config) model_class = self.models[model_name] - emane_node.setmodel(model_class, config) + emane_net.setmodel(model_class, config) def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]]: """ @@ -473,7 +463,6 @@ class EmaneManager(ModelManager): """ emane_node = None iface = None - for node_id in self._emane_nets: emane_node = self._emane_nets[node_id] iface = emane_node.get_nem_iface(nemid) @@ -481,7 +470,6 @@ class EmaneManager(ModelManager): break else: emane_node = None - return emane_node, iface def get_nem_link( @@ -507,38 +495,6 @@ class EmaneManager(ModelManager): color=color, ) - def numnems(self) -> int: - """ - Return the number of NEMs emulated locally. - """ - count = 0 - for node_id in self._emane_nets: - emane_node = self._emane_nets[node_id] - count += len(emane_node.ifaces) - return count - - def buildplatformxml(self, ctrlnet: CtrlNet) -> None: - """ - Build a platform.xml file now that all nodes are configured. - """ - nemid = int(self.get_config("nem_id_start")) - platform_xmls = {} - - # assume self._objslock is already held here - for key in sorted(self._emane_nets.keys()): - emane_node = self._emane_nets[key] - nemid = emanexml.build_node_platform_xml( - self, ctrlnet, emane_node, nemid, platform_xmls - ) - - def buildnemxml(self) -> None: - """ - Builds the nem, mac, and phy xml files for each EMANE network. - """ - for key in sorted(self._emane_nets): - emane_net = self._emane_nets[key] - emanexml.build_xml_files(self, emane_net) - def buildeventservicexml(self) -> None: """ Build the libemaneeventservice.xml file if event service options @@ -571,7 +527,7 @@ class EmaneManager(ModelManager): ) ) - def startdaemons(self) -> None: + def start_daemon(self, iface: CoreInterface) -> None: """ Start one EMANE daemon per node having a radio. Add a control network even if the user has not configured one. @@ -583,69 +539,51 @@ class EmaneManager(ModelManager): if cfgloglevel: logging.info("setting user-defined EMANE log level: %d", cfgloglevel) loglevel = str(cfgloglevel) - emanecmd = f"emane -d -l {loglevel}" if realtime: emanecmd += " -r" - otagroup, _otaport = self.get_config("otamanagergroup").split(":") otadev = self.get_config("otamanagerdevice") otanetidx = self.session.get_control_net_index(otadev) - eventgroup, _eventport = self.get_config("eventservicegroup").split(":") eventdev = self.get_config("eventservicedevice") eventservicenetidx = self.session.get_control_net_index(eventdev) - - run_emane_on_host = False - for node in self.getnodes(): - if isinstance(node, Rj45Node): - run_emane_on_host = True - continue - path = self.session.session_dir - n = node.id - + node = iface.node + if not isinstance(node, Rj45Node): # control network not yet started here self.session.add_remove_control_iface( node, 0, remove=False, conf_required=False ) - if otanetidx > 0: logging.info("adding ota device ctrl%d", otanetidx) self.session.add_remove_control_iface( node, otanetidx, remove=False, conf_required=False ) - if eventservicenetidx >= 0: logging.info("adding event service device ctrl%d", eventservicenetidx) self.session.add_remove_control_iface( node, eventservicenetidx, remove=False, conf_required=False ) - # multicast route is needed for OTA data node.node_net_client.create_route(otagroup, otadev) - # multicast route is also needed for event data if on control network if eventservicenetidx >= 0 and eventgroup != otagroup: node.node_net_client.create_route(eventgroup, eventdev) - # start emane - log_file = os.path.join(path, f"emane{n}.log") - platform_xml = os.path.join(path, f"platform{n}.xml") + log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log") + platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml") args = f"{emanecmd} -f {log_file} {platform_xml}" output = node.cmd(args) logging.info("node(%s) emane daemon running: %s", node.name, args) logging.debug("node(%s) emane daemon output: %s", node.name, output) - - if not run_emane_on_host: - return - - path = self.session.session_dir - log_file = os.path.join(path, "emane.log") - platform_xml = os.path.join(path, "platform.xml") - emanecmd += f" -f {log_file} {platform_xml}" - utils.cmd(emanecmd, cwd=path) - self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path)) - logging.info("host emane daemon running: %s", emanecmd) + else: + path = self.session.session_dir + log_file = os.path.join(path, f"{iface.name}-emane.log") + platform_xml = os.path.join(path, f"{iface.name}-platform.xml") + emanecmd += f" -f {log_file} {platform_xml}" + utils.cmd(emanecmd, cwd=path) + self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path)) + logging.info("host emane daemon running: %s", emanecmd) def stopdaemons(self) -> None: """ @@ -674,23 +612,27 @@ class EmaneManager(ModelManager): except CoreCommandError: logging.exception("error shutting down emane daemons") - def install_ifaces(self) -> None: - """ - Install TUN/TAP virtual interfaces into their proper namespaces - now that the EMANE daemons are running. - """ - for key in sorted(self._emane_nets.keys()): - node = self._emane_nets[key] - logging.info("emane install interface for node(%s): %d", node.name, key) - node.install_ifaces() + def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: + config = self.get_iface_config(emane_net, iface) + external = config.get("external", "0") + if isinstance(iface, TunTap) and external == "0": + iface.set_ips() + # at this point we register location handlers for generating + # EMANE location events + if self.genlocationevents(): + iface.poshook = emane_net.setnemposition + iface.setposition() def deinstall_ifaces(self) -> None: """ Uninstall TUN/TAP virtual interfaces. """ - for key in sorted(self._emane_nets.keys()): - emane_node = self._emane_nets[key] - emane_node.deinstall_ifaces() + for key in sorted(self._emane_nets): + emane_net = self._emane_nets[key] + for iface in emane_net.get_ifaces(): + if iface.transport_type == TransportType.VIRTUAL: + iface.shutdown() + iface.poshook = None def doeventmonitor(self) -> bool: """ @@ -718,7 +660,6 @@ class EmaneManager(ModelManager): logging.info("emane start event monitor") if not self.doeventmonitor(): return - if self.service is None: logging.error( "Warning: EMANE events will not be generated " diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 43fbc0fb..0576d6c3 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -96,9 +96,7 @@ class EmaneModel(WirelessModel): ConfigGroup("External Parameters", phy_len + 1, config_len), ] - def build_xml_files( - self, config: Dict[str, str], iface: CoreInterface = None - ) -> None: + def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None: """ Builds xml files for this emane model. Creates a nem.xml file that points to both mac.xml and phy.xml definitions. @@ -107,33 +105,30 @@ class EmaneModel(WirelessModel): :param iface: interface for the emane node :return: nothing """ - nem_name = emanexml.nem_file_name(self, iface) - mac_name = emanexml.mac_file_name(self, iface) - phy_name = emanexml.phy_file_name(self, iface) - - # remote server for file - server = None - if iface is not None: - server = iface.node.server + nem_name = emanexml.nem_file_name(iface) + mac_name = emanexml.mac_file_name(iface) + phy_name = emanexml.phy_file_name(iface) # check if this is external transport_type = TransportType.VIRTUAL - if iface and iface.transport_type == TransportType.RAW: + if iface.transport_type == TransportType.RAW: transport_type = TransportType.RAW - transport_name = emanexml.transport_file_name(self.id, transport_type) + transport_name = emanexml.transport_file_name(iface, transport_type) + node = iface.node + server = node.server # create nem xml file - nem_file = os.path.join(self.session.session_dir, nem_name) + nem_file = os.path.join(node.nodedir, nem_name) emanexml.create_nem_xml( self, config, nem_file, transport_name, mac_name, phy_name, server ) # create mac xml file - mac_file = os.path.join(self.session.session_dir, mac_name) + mac_file = os.path.join(node.nodedir, mac_name) emanexml.create_mac_xml(self, config, mac_file, server) # create phy xml file - phy_file = os.path.join(self.session.session_dir, phy_name) + phy_file = os.path.join(node.nodedir, phy_name) emanexml.create_phy_xml(self, config, phy_file, server) def post_startup(self) -> None: diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index be95e6d0..dca85785 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -8,17 +8,10 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.distributed import DistributedServer -from core.emulator.enumerations import ( - EventTypes, - LinkTypes, - MessageFlags, - NodeTypes, - RegisterTlvs, - TransportType, -) +from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes, RegisterTlvs from core.errors import CoreError from core.nodes.base import CoreNetworkBase, CoreNode -from core.nodes.interface import CoreInterface, TunTap +from core.nodes.interface import CoreInterface if TYPE_CHECKING: from core.emane.emanemodel import EmaneModel @@ -139,45 +132,6 @@ class EmaneNet(CoreNetworkBase): return iface return None - def install_ifaces(self) -> None: - """ - Install TAP devices into their namespaces. This is done after - EMANE daemons have been started, because that is their only chance - to bind to the TAPs. - """ - if ( - self.session.emane.genlocationevents() - and self.session.emane.service is None - ): - warntxt = "unable to publish EMANE events because the eventservice " - warntxt += "Python bindings failed to load" - logging.error(warntxt) - for iface in self.get_ifaces(): - config = self.session.emane.get_iface_config( - self.id, iface, self.model.name - ) - external = config.get("external", "0") - if isinstance(iface, TunTap) and external == "0": - iface.set_ips() - if not self.session.emane.genlocationevents(): - iface.poshook = None - continue - # at this point we register location handlers for generating - # EMANE location events - iface.poshook = self.setnemposition - iface.setposition() - - def deinstall_ifaces(self) -> None: - """ - Uninstall TAP devices. This invokes their shutdown method for - any required cleanup; the device may be actually removed when - emanetransportd terminates. - """ - for iface in self.get_ifaces(): - if iface.transport_type == TransportType.VIRTUAL: - iface.shutdown() - iface.poshook = None - def _nem_position( self, iface: CoreInterface ) -> Optional[Tuple[int, float, float, float]]: @@ -275,20 +229,4 @@ class EmaneNet(CoreNetworkBase): iface.set_mac(iface_data.mac) for ip in iface_data.get_ips(): iface.add_ip(ip) - # TODO: if added during runtime start EMANE - if self.session.state == EventTypes.RUNTIME_STATE: - logging.info("startup emane for node: %s", node.name) - # create specific xml if needed - config = self.session.emane.get_iface_config( - self.model.id, iface, self.model.name - ) - if config: - self.model.build_xml_files(config, iface) - - # start emane daemon - - # install netif - - # add nem to nemfile - return iface diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index c2573578..9f5364b9 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -15,7 +15,7 @@ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVa from core import constants, utils from core.configservice.manager import ConfigServiceManager -from core.emane.emanemanager import EmaneManager +from core.emane.emanemanager import EmaneManager, EmaneState from core.emane.nodes import EmaneNet from core.emulator.data import ( ConfigData, @@ -1181,7 +1181,7 @@ class Session: self.distributed.start() # instantiate will be invoked again upon emane configure - if self.emane.startup() == self.emane.NOT_READY: + if self.emane.startup() == EmaneState.NOT_READY: return [] # boot node services and then start mobility diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index eece57c9..32ca0f67 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -10,6 +10,7 @@ from core.config import Configuration from core.emane.nodes import EmaneNet from core.emulator.distributed import DistributedServer from core.emulator.enumerations import TransportType +from core.errors import CoreError from core.nodes.interface import CoreInterface from core.nodes.network import CtrlNet from core.xml import corexml @@ -40,15 +41,11 @@ def _value_to_params(value: str) -> Optional[Tuple[str]]: """ try: values = utils.make_tuple_fromstr(value, str) - if not hasattr(values, "__iter__"): return None - if len(values) < 2: return None - return values - except SyntaxError: logging.exception("error in value string to param list") return None @@ -127,13 +124,13 @@ def add_configurations( add_param(xml_element, name, value) -def build_node_platform_xml( +def build_platform_xml( emane_manager: "EmaneManager", control_net: CtrlNet, - node: EmaneNet, + emane_net: EmaneNet, + iface: CoreInterface, nem_id: int, - platform_xmls: Dict[str, etree.Element], -) -> int: +) -> None: """ Create platform xml for a specific node. @@ -141,175 +138,121 @@ def build_node_platform_xml( configurations :param control_net: control net node for this emane network - :param node: node to write platform xml for - :param nem_id: nem id to use for interfaces for this node - :param platform_xmls: stores platform xml elements to append nem entries to + :param emane_net: emane network associated with interface + :param iface: interface running emane + :param nem_id: nem id to use for this interface :return: the next nem id that can be used for creating platform xml files """ - logging.debug( - "building emane platform xml for node(%s) nem_id(%s): %s", - node, - nem_id, - node.name, + # build nem xml + nem_definition = nem_file_name(iface) + nem_element = etree.Element( + "nem", id=str(nem_id), name=iface.localname, definition=nem_definition ) - nem_entries = {} - if node.model is None: - logging.warning("warning: EMANE network %s has no associated model", node.name) - return nem_id - - for iface in node.get_ifaces(): - logging.debug( - "building platform xml for interface(%s) nem_id(%s)", iface.name, nem_id - ) - # build nem xml - nem_definition = nem_file_name(node.model, iface) - nem_element = etree.Element( - "nem", id=str(nem_id), name=iface.localname, definition=nem_definition + # check if this is an external transport, get default config if an interface + # specific one does not exist + config = emane_manager.get_iface_config(emane_net, iface) + if is_external(config): + nem_element.set("transport", "external") + platform_endpoint = "platformendpoint" + add_param(nem_element, platform_endpoint, config[platform_endpoint]) + transport_endpoint = "transportendpoint" + add_param(nem_element, transport_endpoint, config[transport_endpoint]) + else: + # build transport xml + transport_type = iface.transport_type + if not transport_type: + logging.info("warning: %s interface type unsupported!", iface.name) + transport_type = TransportType.RAW + transport_file = transport_file_name(iface, transport_type) + transport_element = etree.SubElement( + nem_element, "transport", definition=transport_file ) + add_param(transport_element, "device", iface.name) - # check if this is an external transport, get default config if an interface - # specific one does not exist - config = emane_manager.get_iface_config(node.model.id, iface, node.model.name) - - if is_external(config): - nem_element.set("transport", "external") - platform_endpoint = "platformendpoint" - add_param(nem_element, platform_endpoint, config[platform_endpoint]) - transport_endpoint = "transportendpoint" - add_param(nem_element, transport_endpoint, config[transport_endpoint]) - else: - # build transport xml - transport_type = iface.transport_type - if not transport_type: - logging.info("warning: %s interface type unsupported!", iface.name) - transport_type = TransportType.RAW - transport_file = transport_file_name(node.id, transport_type) - transport_element = etree.SubElement( - nem_element, "transport", definition=transport_file - ) - - # add transport parameter - add_param(transport_element, "device", iface.name) - - # add nem entry - nem_entries[iface] = nem_element - - # merging code - key = iface.node.id - if iface.transport_type == TransportType.RAW: - key = "host" - otadev = control_net.brname - eventdev = control_net.brname - else: - otadev = None - eventdev = None - - platform_element = platform_xmls.get(key) - if platform_element is None: - platform_element = etree.Element("platform") - - if otadev: - emane_manager.set_config("otamanagerdevice", otadev) - - if eventdev: - emane_manager.set_config("eventservicedevice", eventdev) - - # append all platform options (except starting id) to doc - for configuration in emane_manager.emane_config.emulator_config: - name = configuration.id - if name == "platform_id_start": - continue - - value = emane_manager.get_config(name) - add_param(platform_element, name, value) - - # add platform xml - platform_xmls[key] = platform_element - - platform_element.append(nem_element) - - node.setnemid(iface, nem_id) - mac = _MAC_PREFIX + ":00:00:" - mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" - iface.set_mac(mac) - - # increment nem id - nem_id += 1 + # determine platform element to add xml to + key = iface.node.id + if iface.transport_type == TransportType.RAW: + key = "host" + otadev = control_net.brname + eventdev = control_net.brname + else: + otadev = None + eventdev = None + platform_element = etree.Element("platform") + if otadev: + emane_manager.set_config("otamanagerdevice", otadev) + if eventdev: + emane_manager.set_config("eventservicedevice", eventdev) + for configuration in emane_manager.emane_config.emulator_config: + name = configuration.id + value = emane_manager.get_config(name) + add_param(platform_element, name, value) + platform_element.append(nem_element) + emane_net.setnemid(iface, nem_id) + mac = _MAC_PREFIX + ":00:00:" + mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" + iface.set_mac(mac) doc_name = "platform" - for key in sorted(platform_xmls.keys()): - platform_element = platform_xmls[key] - if key == "host": - file_name = "platform.xml" - file_path = os.path.join(emane_manager.session.session_dir, file_name) - create_file(platform_element, doc_name, file_path) - else: - file_name = f"platform{key}.xml" - file_path = os.path.join(emane_manager.session.session_dir, file_name) - linked_node = emane_manager.session.nodes[key] - create_file(platform_element, doc_name, file_path, linked_node.server) - - return nem_id + server = None + if key == "host": + file_name = "platform.xml" + file_path = os.path.join(emane_manager.session.session_dir, file_name) + else: + node = iface.node + file_name = f"{iface.name}-platform.xml" + file_path = os.path.join(node.nodedir, file_name) + server = node.server + create_file(platform_element, doc_name, file_path, server) -def build_xml_files(emane_manager: "EmaneManager", node: EmaneNet) -> None: +def build_model_xmls( + manager: "EmaneManager", emane_net: EmaneNet, iface: CoreInterface +) -> None: """ Generate emane xml files required for node. - :param emane_manager: emane manager with emane + :param manager: emane manager with emane configurations - :param node: node to write platform xml for + :param emane_net: emane network associated with interface + :param iface: interface to create emane xml for :return: nothing """ - logging.debug("building all emane xml for node(%s): %s", node, node.name) - if node.model is None: - return - - # get model configurations - config = emane_manager.get_configs(node.model.id, node.model.name) - if not config: - return - - # build XML for overall network EMANE configs - node.model.build_xml_files(config) - # build XML for specific interface (NEM) configs + # check for interface specific emane configuration and write xml files + config = manager.get_iface_config(emane_net, iface) + emane_net.model.build_xml_files(config, iface) + + # check transport type needed for interface need_virtual = False need_raw = False vtype = TransportType.VIRTUAL rtype = TransportType.RAW - - for iface in node.get_ifaces(): - # check for interface specific emane configuration and write xml files - config = emane_manager.get_iface_config(node.model.id, iface, node.model.name) - if config: - node.model.build_xml_files(config, iface) - - # check transport type needed for interface - if iface.transport_type == TransportType.VIRTUAL: - need_virtual = True - vtype = iface.transport_type - else: - need_raw = True - rtype = iface.transport_type - + if iface.transport_type == TransportType.VIRTUAL: + need_virtual = True + vtype = iface.transport_type + else: + need_raw = True + rtype = iface.transport_type if need_virtual: - build_transport_xml(emane_manager, node, vtype) - + build_transport_xml(manager, emane_net, iface, vtype) if need_raw: - build_transport_xml(emane_manager, node, rtype) + build_transport_xml(manager, emane_net, iface, rtype) def build_transport_xml( - emane_manager: "EmaneManager", node: EmaneNet, transport_type: TransportType + manager: "EmaneManager", + emane_net: EmaneNet, + iface: CoreInterface, + transport_type: TransportType, ) -> None: """ Build transport xml file for node and transport type. - :param emane_manager: emane manager with emane - configurations - :param node: node to write platform xml for + :param manager: emane manager with emane configurations + :param emane_net: emane network associated with interface + :param iface: interface to build transport xml for :param transport_type: transport type to build xml for :return: nothing """ @@ -318,28 +261,24 @@ def build_transport_xml( name=f"{transport_type.value.capitalize()} Transport", library=f"trans{transport_type.value.lower()}", ) - - # add bitrate add_param(transport_element, "bitrate", "0") # get emane model cnfiguration - config = emane_manager.get_configs(node.id, node.model.name) + config = manager.get_iface_config(emane_net, iface) flowcontrol = config.get("flowcontrolenable", "0") == "1" - if transport_type == TransportType.VIRTUAL: device_path = "/dev/net/tun_flowctl" if not os.path.exists(device_path): device_path = "/dev/net/tun" add_param(transport_element, "devicepath", device_path) - if flowcontrol: add_param(transport_element, "flowcontrolenable", "on") - doc_name = "transport" - file_name = transport_file_name(node.id, transport_type) - file_path = os.path.join(emane_manager.session.session_dir, file_name) + node = iface.node + file_name = transport_file_name(iface, transport_type) + file_path = os.path.join(node.nodedir, file_name) create_file(transport_element, doc_name, file_path) - emane_manager.session.distributed.execute( + manager.session.distributed.execute( lambda x: create_file(transport_element, doc_name, file_path, x) ) @@ -348,7 +287,7 @@ def create_phy_xml( emane_model: "EmaneModel", config: Dict[str, str], file_path: str, - server: DistributedServer, + server: Optional[DistributedServer], ) -> None: """ Create the phy xml document. @@ -363,25 +302,17 @@ def create_phy_xml( phy_element = etree.Element("phy", name=f"{emane_model.name} PHY") if emane_model.phy_library: phy_element.set("library", emane_model.phy_library) - add_configurations( phy_element, emane_model.phy_config, config, emane_model.config_ignore ) - create_file(phy_element, "phy", file_path) - if server is not None: - create_file(phy_element, "phy", file_path, server) - else: - create_file(phy_element, "phy", file_path) - emane_model.session.distributed.execute( - lambda x: create_file(phy_element, "phy", file_path, x) - ) + create_file(phy_element, "phy", file_path, server) def create_mac_xml( emane_model: "EmaneModel", config: Dict[str, str], file_path: str, - server: DistributedServer, + server: Optional[DistributedServer], ) -> None: """ Create the mac xml document. @@ -394,22 +325,14 @@ def create_mac_xml( :return: nothing """ if not emane_model.mac_library: - raise ValueError("must define emane model library") - + raise CoreError("must define emane model library") mac_element = etree.Element( "mac", name=f"{emane_model.name} MAC", library=emane_model.mac_library ) add_configurations( mac_element, emane_model.mac_config, config, emane_model.config_ignore ) - create_file(mac_element, "mac", file_path) - if server is not None: - create_file(mac_element, "mac", file_path, server) - else: - create_file(mac_element, "mac", file_path) - emane_model.session.distributed.execute( - lambda x: create_file(mac_element, "mac", file_path, x) - ) + create_file(mac_element, "mac", file_path, server) def create_nem_xml( @@ -419,7 +342,7 @@ def create_nem_xml( transport_definition: str, mac_definition: str, phy_definition: str, - server: DistributedServer, + server: Optional[DistributedServer], ) -> None: """ Create the nem xml document. @@ -441,13 +364,7 @@ def create_nem_xml( etree.SubElement(nem_element, "transport", definition=transport_definition) etree.SubElement(nem_element, "mac", definition=mac_definition) etree.SubElement(nem_element, "phy", definition=phy_definition) - if server is not None: - create_file(nem_element, "nem", nem_file, server) - else: - create_file(nem_element, "nem", nem_file) - emane_model.session.distributed.execute( - lambda x: create_file(nem_element, "nem", nem_file, x) - ) + create_file(nem_element, "nem", nem_file, server) def create_event_service_xml( @@ -483,81 +400,55 @@ def create_event_service_xml( create_file(event_element, "emaneeventmsgsvc", file_path, server) -def transport_file_name(node_id: int, transport_type: TransportType) -> str: +def transport_file_name(iface: CoreInterface, transport_type: TransportType) -> str: """ Create name for a transport xml file. - :param node_id: node id to generate transport file name for + :param iface: interface running emane :param transport_type: transport type to generate transport file - :return: + :return: transport xml file name """ - return f"n{node_id}trans{transport_type.value}.xml" + return f"{iface.name}-trans-{transport_type.value}.xml" -def _basename(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: +def nem_file_name(iface: CoreInterface) -> str: """ - Create name that is leveraged for configuration file creation. + Return the string name for the NEM XML file, e.g. "eth0-nem.xml" - :param emane_model: emane model to create name for - :param iface: interface for this model - :return: basename used for file creation + :param iface: interface running emane + :return: nem xm file name """ - name = f"n{emane_model.id}" - - if iface: - node_id = iface.node.id - if emane_model.session.emane.get_iface_config(node_id, iface, emane_model.name): - name = iface.localname.replace(".", "_") - - return f"{name}{emane_model.name}" - - -def nem_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: - """ - Return the string name for the NEM XML file, e.g. "n3rfpipenem.xml" - - :param emane_model: emane model to create file - :param iface: interface for this model - :return: nem xml filename - """ - basename = _basename(emane_model, iface) append = "" if iface and iface.transport_type == TransportType.RAW: - append = "_raw" - return f"{basename}nem{append}.xml" + append = "-raw" + return f"{iface.name}-nem{append}.xml" -def shim_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: +def shim_file_name(iface: 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. "eth0-shim.xml" - :param emane_model: emane model to create file - :param iface: interface for this model - :return: shim xml filename + :param iface: interface running emane + :return: shim xml file name """ - name = _basename(emane_model, iface) - return f"{name}shim.xml" + return f"{iface.name}-shim.xml" -def mac_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: +def mac_file_name(iface: CoreInterface) -> 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. "eth0-mac.xml" - :param emane_model: emane model to create file - :param iface: interface for this model - :return: mac xml filename + :param iface: interface running emane + :return: mac xml file name """ - name = _basename(emane_model, iface) - return f"{name}mac.xml" + return f"{iface.name}-mac.xml" -def phy_file_name(emane_model: "EmaneModel", iface: CoreInterface = None) -> str: +def phy_file_name(iface: CoreInterface) -> 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. "eth0-phy.xml" - :param emane_model: emane model to create file - :param iface: interface for this model - :return: phy xml filename + :param iface: interface running emane + :return: phy xml file name """ - name = _basename(emane_model, iface) - return f"{name}phy.xml" + return f"{iface.name}-phy.xml" From ce4b61d3b21afe5a4ac4402698f0eb09af1c57ac Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 17:49:56 -0700 Subject: [PATCH 132/146] daemon: further heavy cleanup to how emane generates and runs xml files --- daemon/core/emane/commeffect.py | 26 ++--- daemon/core/emane/emanemanager.py | 46 ++++---- daemon/core/emane/emanemodel.py | 34 ++---- daemon/core/emane/nodes.py | 1 - daemon/core/xml/emanexml.py | 175 +++++++++++------------------- 5 files changed, 103 insertions(+), 179 deletions(-) diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 100af9a7..a812b66d 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -12,7 +12,6 @@ from core.config import ConfigGroup, Configuration from core.emane import emanemanifest, emanemodel from core.emane.nodes import EmaneNet from core.emulator.data import LinkOptions -from core.emulator.enumerations import TransportType from core.nodes.interface import CoreInterface from core.xml import emanexml @@ -73,26 +72,16 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): :param iface: interface for the emane node :return: nothing """ - # interface node - node = iface.node - - # retrieve xml names - nem_name = emanexml.nem_file_name(iface) - shim_name = emanexml.shim_file_name(iface) - # create and write nem document nem_element = etree.Element("nem", name=f"{self.name} NEM", type="unstructured") - transport_type = TransportType.VIRTUAL - if iface.transport_type == TransportType.RAW: - transport_type = TransportType.RAW - transport_file = emanexml.transport_file_name(iface, transport_type) - etree.SubElement(nem_element, "transport", definition=transport_file) + transport_name = emanexml.transport_file_name(iface) + etree.SubElement(nem_element, "transport", definition=transport_name) # set shim configuration + nem_name = emanexml.nem_file_name(iface) + shim_name = emanexml.shim_file_name(iface) etree.SubElement(nem_element, "shim", definition=shim_name) - - nem_file = os.path.join(node.nodedir, nem_name) - emanexml.create_file(nem_element, "nem", nem_file) + emanexml.create_iface_file(iface, nem_element, "nem", nem_name) # create and write shim document shim_element = etree.Element( @@ -111,9 +100,10 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): ff = config["filterfile"] if ff.strip() != "": emanexml.add_param(shim_element, "filterfile", ff) + emanexml.create_iface_file(iface, shim_element, "shim", shim_name) - shim_file = os.path.join(node.nodedir, shim_name) - emanexml.create_file(shim_element, "shim", shim_file) + # create transport xml + emanexml.create_transport_xml(iface, config) def linkconfig( self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 3317a5db..4e2984a0 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -281,8 +281,6 @@ class EmaneManager(ModelManager): instantiation """ logging.debug("emane setup") - - # TODO: drive this from the session object with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] @@ -291,7 +289,6 @@ class EmaneManager(ModelManager): "adding emane node: id(%s) name(%s)", node.id, node.name ) self.add_node(node) - if not self._emane_nets: logging.debug("no emane nodes in session") return EmaneState.NOT_NEEDED @@ -322,7 +319,7 @@ class EmaneManager(ModelManager): logging.debug("emane event service device index: %s", netidx) if netidx < 0: logging.error( - "EMANE cannot start, check core config. invalid event service device: %s", + "emane cannot start due to invalid event service device: %s", eventdev, ) return EmaneState.NOT_READY @@ -330,7 +327,6 @@ class EmaneManager(ModelManager): self.session.add_remove_control_net( net_index=netidx, remove=False, conf_required=False ) - self.check_node_models() return EmaneState.SUCCESS @@ -349,10 +345,6 @@ class EmaneManager(ModelManager): self.starteventmonitor() self.buildeventservicexml() with self._emane_node_lock: - # on master, control network bridge added earlier in startup() - control_net = self.session.add_remove_control_net( - 0, remove=False, conf_required=False - ) logging.info("emane building xmls...") for node_id in sorted(self._emane_nets): emane_net = self._emane_nets[node_id] @@ -360,25 +352,31 @@ class EmaneManager(ModelManager): logging.error("emane net(%s) has no model", emane_net.name) continue for iface in emane_net.get_ifaces(): - if not iface.node: - logging.error( - "emane net(%s) connected interface missing node", - emane_net.name, - ) - continue - nem_id = self.next_nem_id() - self.nems[nem_id] = iface - self.write_nem(iface, nem_id) - emanexml.build_platform_xml( - self, control_net, emane_net, iface, nem_id - ) - emanexml.build_model_xmls(self, emane_net, iface) - self.start_daemon(iface) - self.install_iface(emane_net, iface) + self.start_iface(emane_net, iface) if self.links_enabled(): self.link_monitor.start() return EmaneState.SUCCESS + def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: + if not iface.node: + logging.error( + "emane net(%s) connected interface(%s) missing node", + emane_net.name, + iface.name, + ) + return + control_net = self.session.add_remove_control_net( + 0, remove=False, conf_required=False + ) + nem_id = self.next_nem_id() + self.nems[nem_id] = iface + self.write_nem(iface, nem_id) + emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id) + config = self.get_iface_config(emane_net, iface) + emane_net.model.build_xml_files(config, iface) + self.start_daemon(iface) + self.install_iface(emane_net, iface) + def write_nem(self, iface: CoreInterface, nem_id: int) -> None: path = os.path.join(self.session.session_dir, "emane_nems") try: diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 0576d6c3..8672163d 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -9,7 +9,7 @@ from core.config import ConfigGroup, Configuration from core.emane import emanemanifest from core.emane.nodes import EmaneNet from core.emulator.data import LinkOptions -from core.emulator.enumerations import ConfigDataTypes, TransportType +from core.emulator.enumerations import ConfigDataTypes from core.errors import CoreError from core.location.mobility import WirelessModel from core.nodes.base import CoreNode @@ -102,34 +102,14 @@ class EmaneModel(WirelessModel): both mac.xml and phy.xml definitions. :param config: emane model configuration for the node and interface - :param iface: interface for the emane node + :param iface: interface to run emane for :return: nothing """ - nem_name = emanexml.nem_file_name(iface) - mac_name = emanexml.mac_file_name(iface) - phy_name = emanexml.phy_file_name(iface) - - # check if this is external - transport_type = TransportType.VIRTUAL - if iface.transport_type == TransportType.RAW: - transport_type = TransportType.RAW - transport_name = emanexml.transport_file_name(iface, transport_type) - - node = iface.node - server = node.server - # create nem xml file - nem_file = os.path.join(node.nodedir, nem_name) - emanexml.create_nem_xml( - self, config, nem_file, transport_name, mac_name, phy_name, server - ) - - # create mac xml file - mac_file = os.path.join(node.nodedir, mac_name) - emanexml.create_mac_xml(self, config, mac_file, server) - - # create phy xml file - phy_file = os.path.join(node.nodedir, phy_name) - emanexml.create_phy_xml(self, config, phy_file, server) + # create nem, mac, and phy xml files + emanexml.create_nem_xml(self, iface, config) + emanexml.create_mac_xml(self, iface, config) + emanexml.create_phy_xml(self, iface, config) + emanexml.create_transport_xml(iface, config) def post_startup(self) -> None: """ diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index dca85785..7e8a0a4f 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -96,7 +96,6 @@ class EmaneNet(CoreNetworkBase): """ set the EmaneModel associated with this node """ - logging.info("adding model: %s", model.name) if model.config_type == RegisterTlvs.WIRELESS: # EmaneModel really uses values from ConfigurableManager # when buildnemxml() is called, not during init() diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 32ca0f67..cb605b21 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -13,6 +13,7 @@ from core.emulator.enumerations import TransportType from core.errors import CoreError from core.nodes.interface import CoreInterface from core.nodes.network import CtrlNet +from core.nodes.physical import Rj45Node from core.xml import corexml if TYPE_CHECKING: @@ -63,16 +64,15 @@ def create_file( :param xml_element: root element to write to file :param doc_name: name to use in the emane doctype :param file_path: file path to write xml file to - :param server: remote server node - will run on, default is None for localhost + :param server: remote server to create file on :return: nothing """ doctype = ( f'' ) - if server is not None: + if server: temp = NamedTemporaryFile(delete=False) - create_file(xml_element, doc_name, temp.name) + corexml.write_xml_file(xml_element, temp.name, doctype=doctype) temp.close() server.remote_put(temp.name, file_path) os.unlink(temp.name) @@ -80,6 +80,26 @@ def create_file( corexml.write_xml_file(xml_element, file_path, doctype=doctype) +def create_iface_file( + iface: CoreInterface, xml_element: etree.Element, doc_name: str, file_name: str +) -> None: + """ + Create emane xml for an interface. + + :param iface: interface running emane + :param xml_element: root element to write to file + :param doc_name: name to use in the emane doctype + :param file_name: name of xml file + :return: + """ + node = iface.node + if isinstance(node, Rj45Node): + file_path = os.path.join(node.session.session_dir, file_name) + else: + file_path = os.path.join(node.nodedir, file_name) + create_file(xml_element, doc_name, file_path, node.server) + + def add_param(xml_element: etree.Element, name: str, value: str) -> None: """ Add emane configuration parameter to xml element. @@ -159,21 +179,14 @@ def build_platform_xml( transport_endpoint = "transportendpoint" add_param(nem_element, transport_endpoint, config[transport_endpoint]) else: - # build transport xml - transport_type = iface.transport_type - if not transport_type: - logging.info("warning: %s interface type unsupported!", iface.name) - transport_type = TransportType.RAW - transport_file = transport_file_name(iface, transport_type) + transport_name = transport_file_name(iface) transport_element = etree.SubElement( - nem_element, "transport", definition=transport_file + nem_element, "transport", definition=transport_name ) add_param(transport_element, "device", iface.name) # determine platform element to add xml to - key = iface.node.id if iface.transport_type == TransportType.RAW: - key = "host" otadev = control_net.brname eventdev = control_net.brname else: @@ -195,67 +208,19 @@ def build_platform_xml( iface.set_mac(mac) doc_name = "platform" - server = None - if key == "host": - file_name = "platform.xml" - file_path = os.path.join(emane_manager.session.session_dir, file_name) - else: - node = iface.node - file_name = f"{iface.name}-platform.xml" - file_path = os.path.join(node.nodedir, file_name) - server = node.server - create_file(platform_element, doc_name, file_path, server) + file_name = f"{iface.name}-platform.xml" + create_iface_file(iface, platform_element, doc_name, file_name) -def build_model_xmls( - manager: "EmaneManager", emane_net: EmaneNet, iface: CoreInterface -) -> None: - """ - Generate emane xml files required for node. - - :param manager: emane manager with emane - configurations - :param emane_net: emane network associated with interface - :param iface: interface to create emane xml for - :return: nothing - """ - # build XML for specific interface (NEM) configs - # check for interface specific emane configuration and write xml files - config = manager.get_iface_config(emane_net, iface) - emane_net.model.build_xml_files(config, iface) - - # check transport type needed for interface - need_virtual = False - need_raw = False - vtype = TransportType.VIRTUAL - rtype = TransportType.RAW - if iface.transport_type == TransportType.VIRTUAL: - need_virtual = True - vtype = iface.transport_type - else: - need_raw = True - rtype = iface.transport_type - if need_virtual: - build_transport_xml(manager, emane_net, iface, vtype) - if need_raw: - build_transport_xml(manager, emane_net, iface, rtype) - - -def build_transport_xml( - manager: "EmaneManager", - emane_net: EmaneNet, - iface: CoreInterface, - transport_type: TransportType, -) -> None: +def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None: """ Build transport xml file for node and transport type. - :param manager: emane manager with emane configurations - :param emane_net: emane network associated with interface :param iface: interface to build transport xml for - :param transport_type: transport type to build xml for + :param config: all current configuration values :return: nothing """ + transport_type = get_transport_type(iface) transport_element = etree.Element( "transport", name=f"{transport_type.value.capitalize()} Transport", @@ -264,7 +229,6 @@ def build_transport_xml( add_param(transport_element, "bitrate", "0") # get emane model cnfiguration - config = manager.get_iface_config(emane_net, iface) flowcontrol = config.get("flowcontrolenable", "0") == "1" if transport_type == TransportType.VIRTUAL: device_path = "/dev/net/tun_flowctl" @@ -274,29 +238,19 @@ def build_transport_xml( if flowcontrol: add_param(transport_element, "flowcontrolenable", "on") doc_name = "transport" - node = iface.node - file_name = transport_file_name(iface, transport_type) - file_path = os.path.join(node.nodedir, file_name) - create_file(transport_element, doc_name, file_path) - manager.session.distributed.execute( - lambda x: create_file(transport_element, doc_name, file_path, x) - ) + transport_name = transport_file_name(iface) + create_iface_file(iface, transport_element, doc_name, transport_name) def create_phy_xml( - emane_model: "EmaneModel", - config: Dict[str, str], - file_path: str, - server: Optional[DistributedServer], + emane_model: "EmaneModel", iface: CoreInterface, config: Dict[str, str] ) -> None: """ Create the phy xml document. :param emane_model: emane model to create xml + :param iface: interface to create xml for :param config: all current configuration values - :param file_path: path to write file to - :param server: remote server node - will run on, default is None for localhost :return: nothing """ phy_element = etree.Element("phy", name=f"{emane_model.name} PHY") @@ -305,23 +259,19 @@ def create_phy_xml( add_configurations( phy_element, emane_model.phy_config, config, emane_model.config_ignore ) - create_file(phy_element, "phy", file_path, server) + file_name = phy_file_name(iface) + create_iface_file(iface, phy_element, "phy", file_name) def create_mac_xml( - emane_model: "EmaneModel", - config: Dict[str, str], - file_path: str, - server: Optional[DistributedServer], + emane_model: "EmaneModel", iface: CoreInterface, config: Dict[str, str] ) -> None: """ Create the mac xml document. :param emane_model: emane model to create xml + :param iface: interface to create xml for :param config: all current configuration values - :param file_path: path to write file to - :param server: remote server node - will run on, default is None for localhost :return: nothing """ if not emane_model.mac_library: @@ -332,39 +282,33 @@ def create_mac_xml( add_configurations( mac_element, emane_model.mac_config, config, emane_model.config_ignore ) - create_file(mac_element, "mac", file_path, server) + file_name = mac_file_name(iface) + create_iface_file(iface, mac_element, "mac", file_name) def create_nem_xml( - emane_model: "EmaneModel", - config: Dict[str, str], - nem_file: str, - transport_definition: str, - mac_definition: str, - phy_definition: str, - server: Optional[DistributedServer], + emane_model: "EmaneModel", iface: CoreInterface, config: Dict[str, str] ) -> None: """ Create the nem xml document. :param emane_model: emane model to create xml + :param iface: interface to create xml for :param config: all current configuration values - :param nem_file: nem file path to write - :param transport_definition: transport file definition path - :param mac_definition: mac file definition path - :param phy_definition: phy file definition path - :param server: remote server node - will run on, default is None for localhost :return: nothing """ nem_element = etree.Element("nem", name=f"{emane_model.name} NEM") if is_external(config): nem_element.set("type", "unstructured") else: - etree.SubElement(nem_element, "transport", definition=transport_definition) - etree.SubElement(nem_element, "mac", definition=mac_definition) - etree.SubElement(nem_element, "phy", definition=phy_definition) - create_file(nem_element, "nem", nem_file, server) + transport_name = transport_file_name(iface) + etree.SubElement(nem_element, "transport", definition=transport_name) + mac_name = mac_file_name(iface) + etree.SubElement(nem_element, "mac", definition=mac_name) + phy_name = phy_file_name(iface) + etree.SubElement(nem_element, "phy", definition=phy_name) + nem_name = nem_file_name(iface) + create_iface_file(iface, nem_element, "nem", nem_name) def create_event_service_xml( @@ -400,14 +344,27 @@ def create_event_service_xml( create_file(event_element, "emaneeventmsgsvc", file_path, server) -def transport_file_name(iface: CoreInterface, transport_type: TransportType) -> str: +def get_transport_type(iface: CoreInterface) -> TransportType: + """ + Get transport type for a given interface. + + :param iface: interface to get transport type for + :return: transport type + """ + transport_type = TransportType.VIRTUAL + if iface.transport_type == TransportType.RAW: + transport_type = TransportType.RAW + return transport_type + + +def transport_file_name(iface: CoreInterface) -> str: """ Create name for a transport xml file. :param iface: interface running emane - :param transport_type: transport type to generate transport file :return: transport xml file name """ + transport_type = get_transport_type(iface) return f"{iface.name}-trans-{transport_type.value}.xml" From 5f676b27bacafcd4893564160fe87c5c37b0fbd9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 22:15:12 -0700 Subject: [PATCH 133/146] tests: removed invalid patch due to emane refactoring --- daemon/tests/conftest.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index be62fc03..665f2c1a 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -12,7 +12,6 @@ from mock.mock import MagicMock from core.api.grpc.client import InterfaceHelper from core.api.grpc.server import CoreGrpcServer from core.api.tlv.corehandlers import CoreHandler -from core.emane.emanemanager import EmaneManager from core.emulator.coreemu import CoreEmu from core.emulator.data import IpPrefixes from core.emulator.distributed import DistributedServer @@ -63,7 +62,6 @@ def patcher(request): patch_manager.patch_obj(CoreNode, "nodefile") patch_manager.patch_obj(Session, "write_state") patch_manager.patch_obj(Session, "write_nodes") - patch_manager.patch_obj(EmaneManager, "buildxml") yield patch_manager patch_manager.shutdown() From 2b3e26b7c2af9f724a9a060a06a35a94a7445d6c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 23:19:40 -0700 Subject: [PATCH 134/146] daemon: cleanup emane transport service in relation to refactoring, silenced stopdaemons for rj45 nodes --- daemon/core/emane/emanemanager.py | 56 +++++++++++---------------- daemon/core/services/emaneservices.py | 46 +++++++--------------- 2 files changed, 37 insertions(+), 65 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 4e2984a0..808e8020 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -134,31 +134,27 @@ class EmaneManager(ModelManager): :return: net, node, or interface model configuration """ model_name = emane_net.model.name - # use the network-wide config values or interface(NEM)-specific values? - if iface is None: - return self.get_configs(node_id=emane_net.id, config_type=model_name) - else: - # don"t use default values when interface config is the same as net - # note here that using iface.node.id as key allows for only one type - # of each model per node; - # TODO: use both node and interface as key - # Adamson change: first check for iface config keyed by "node:iface.name" - # (so that nodes w/ multiple interfaces of same conftype can have - # different configs for each separate interface) - key = 1000 * iface.node.id - if iface.node_id is not None: - key += iface.node_id - # try retrieve interface specific configuration, avoid getting defaults - config = self.get_configs(node_id=key, config_type=model_name) - # otherwise retrieve the interfaces node configuration, avoid using defaults - if not config: - config = self.get_configs(node_id=iface.node.id, config_type=model_name) - # get non interface config, when none found - if not config: - # with EMANE 0.9.2+, we need an extra NEM XML from - # model.buildnemxmlfiles(), so defaults are returned here - config = self.get_configs(node_id=emane_net.id, config_type=model_name) - return config + # don"t use default values when interface config is the same as net + # note here that using iface.node.id as key allows for only one type + # of each model per node; + # TODO: use both node and interface as key + # Adamson change: first check for iface config keyed by "node:iface.name" + # (so that nodes w/ multiple interfaces of same conftype can have + # different configs for each separate interface) + key = 1000 * iface.node.id + if iface.node_id is not None: + key += iface.node_id + # try retrieve interface specific configuration, avoid getting defaults + config = self.get_configs(node_id=key, config_type=model_name) + # otherwise retrieve the interfaces node configuration, avoid using defaults + if not config: + config = self.get_configs(node_id=iface.node.id, config_type=model_name) + # get non interface config, when none found + if not config: + # with EMANE 0.9.2+, we need an extra NEM XML from + # model.buildnemxmlfiles(), so defaults are returned here + config = self.get_configs(node_id=emane_net.id, config_type=model_name) + return config def config_reset(self, node_id: int = None) -> None: super().config_reset(node_id) @@ -587,26 +583,18 @@ class EmaneManager(ModelManager): """ Kill the appropriate EMANE daemons. """ - # TODO: we may want to improve this if we had the PIDs from the specific EMANE - # daemons that we"ve started kill_emaned = "killall -q emane" - kill_transortd = "killall -q emanetransportd" stop_emane_on_host = False for node in self.getnodes(): if isinstance(node, Rj45Node): stop_emane_on_host = True continue - if node.up: node.cmd(kill_emaned, wait=False) - # TODO: RJ45 node - if stop_emane_on_host: try: - utils.cmd(kill_emaned) - utils.cmd(kill_transortd) + utils.cmd(kill_emaned, wait=False) self.session.distributed.execute(lambda x: x.remote_cmd(kill_emaned)) - self.session.distributed.execute(lambda x: x.remote_cmd(kill_transortd)) except CoreCommandError: logging.exception("error shutting down emane daemons") diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index ef188fab..e734851d 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -1,7 +1,6 @@ from typing import Tuple from core.emane.nodes import EmaneNet -from core.errors import CoreError from core.nodes.base import CoreNode from core.services.coreservices import CoreService from core.xml import emanexml @@ -14,37 +13,22 @@ class EmaneTransportService(CoreService): dependencies: Tuple[str, ...] = () dirs: Tuple[str, ...] = () configs: Tuple[str, ...] = ("emanetransport.sh",) - startup: Tuple[str, ...] = ("sh %s" % configs[0],) - validate: Tuple[str, ...] = ("pidof %s" % executables[0],) + startup: Tuple[str, ...] = (f"sh {configs[0]}",) + validate: Tuple[str, ...] = (f"pidof {executables[0]}",) validation_timer: float = 0.5 - shutdown: Tuple[str, ...] = ("killall %s" % executables[0],) + shutdown: Tuple[str, ...] = (f"killall {executables[0]}",) @classmethod def generate_config(cls, node: CoreNode, filename: str) -> str: - if filename == cls.configs[0]: - transport_commands = [] - for iface in node.get_ifaces(): - try: - network_node = node.session.get_node(iface.net.id, EmaneNet) - config = node.session.emane.get_configs( - network_node.id, network_node.model.name - ) - if config and emanexml.is_external(config): - nem_id = network_node.getnemid(iface) - command = ( - "emanetransportd -r -l 0 -d ../transportdaemon%s.xml" - % nem_id - ) - transport_commands.append(command) - except CoreError: - pass - transport_commands = "\n".join(transport_commands) - return """ -emanegentransportxml -o ../ ../platform%s.xml -%s -""" % ( - node.id, - transport_commands, - ) - else: - raise ValueError + emane_manager = node.session.emane + cfg = "" + for iface in node.get_ifaces(): + if not isinstance(iface.net, EmaneNet): + continue + emane_net = iface.net + config = emane_manager.get_iface_config(emane_net, iface) + if emanexml.is_external(config): + nem_id = emane_net.getnemid(iface) + cfg += f"emanegentransportxml {iface.name}-platform.xml\n" + cfg += f"emanetransportd -r -l 0 -d transportdaemon{nem_id}.xml\n" + return cfg From ddcb0205f35a3b3a3ac064a4c79356f7ddf845ee Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 2 Jul 2020 23:32:59 -0700 Subject: [PATCH 135/146] daemon: cleaned up emane stopdaemons logic --- daemon/core/emane/emanemanager.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 808e8020..ca59ad04 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -567,36 +567,31 @@ class EmaneManager(ModelManager): log_file = os.path.join(node.nodedir, f"{iface.name}-emane.log") platform_xml = os.path.join(node.nodedir, f"{iface.name}-platform.xml") args = f"{emanecmd} -f {log_file} {platform_xml}" - output = node.cmd(args) + node.cmd(args) logging.info("node(%s) emane daemon running: %s", node.name, args) - logging.debug("node(%s) emane daemon output: %s", node.name, output) else: path = self.session.session_dir log_file = os.path.join(path, f"{iface.name}-emane.log") platform_xml = os.path.join(path, f"{iface.name}-platform.xml") emanecmd += f" -f {log_file} {platform_xml}" - utils.cmd(emanecmd, cwd=path) - self.session.distributed.execute(lambda x: x.remote_cmd(emanecmd, cwd=path)) - logging.info("host emane daemon running: %s", emanecmd) + node.host_cmd(emanecmd, cwd=path) + logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd) def stopdaemons(self) -> None: """ Kill the appropriate EMANE daemons. """ kill_emaned = "killall -q emane" - stop_emane_on_host = False - for node in self.getnodes(): - if isinstance(node, Rj45Node): - stop_emane_on_host = True - continue - if node.up: - node.cmd(kill_emaned, wait=False) - if stop_emane_on_host: - try: - utils.cmd(kill_emaned, wait=False) - self.session.distributed.execute(lambda x: x.remote_cmd(kill_emaned)) - except CoreCommandError: - logging.exception("error shutting down emane daemons") + for node_id in sorted(self._emane_nets): + emane_net = self._emane_nets[node_id] + for iface in emane_net.get_ifaces(): + node = iface.node + if not node.up: + continue + if isinstance(node, Rj45Node): + node.host_cmd(kill_emaned, wait=False) + else: + node.cmd(kill_emaned, wait=False) def install_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: config = self.get_iface_config(emane_net, iface) From ac1c27b1c8b74da40c79ed9e95765191c0e71605 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 3 Jul 2020 08:51:17 -0700 Subject: [PATCH 136/146] daemon: fixed issues when emane generated platform.xml for raw interfaces --- daemon/core/emane/emanemanager.py | 23 +++++++++++++---------- daemon/core/xml/emanexml.py | 22 ++++++++-------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index ca59ad04..476010cb 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -31,7 +31,6 @@ from core.emulator.enumerations import ( from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode, NodeBase from core.nodes.interface import CoreInterface, TunTap -from core.nodes.physical import Rj45Node from core.xml import emanexml if TYPE_CHECKING: @@ -531,19 +530,21 @@ class EmaneManager(ModelManager): cfgloglevel = self.session.options.get_config_int("emane_log_level") realtime = self.session.options.get_config_bool("emane_realtime", default=True) if cfgloglevel: - logging.info("setting user-defined EMANE log level: %d", cfgloglevel) + logging.info("setting user-defined emane log level: %d", cfgloglevel) loglevel = str(cfgloglevel) emanecmd = f"emane -d -l {loglevel}" if realtime: emanecmd += " -r" - otagroup, _otaport = self.get_config("otamanagergroup").split(":") - otadev = self.get_config("otamanagerdevice") - otanetidx = self.session.get_control_net_index(otadev) - eventgroup, _eventport = self.get_config("eventservicegroup").split(":") - eventdev = self.get_config("eventservicedevice") - eventservicenetidx = self.session.get_control_net_index(eventdev) node = iface.node - if not isinstance(node, Rj45Node): + transport_type = emanexml.get_transport_type(iface) + if not transport_type == TransportType.RAW: + otagroup, _otaport = self.get_config("otamanagergroup").split(":") + otadev = self.get_config("otamanagerdevice") + otanetidx = self.session.get_control_net_index(otadev) + eventgroup, _eventport = self.get_config("eventservicegroup").split(":") + eventdev = self.get_config("eventservicedevice") + eventservicenetidx = self.session.get_control_net_index(eventdev) + # control network not yet started here self.session.add_remove_control_iface( node, 0, remove=False, conf_required=False @@ -559,6 +560,7 @@ class EmaneManager(ModelManager): node, eventservicenetidx, remove=False, conf_required=False ) # multicast route is needed for OTA data + logging.info("OTA GROUP(%s) OTA DEV(%s)", otagroup, otadev) node.node_net_client.create_route(otagroup, otadev) # multicast route is also needed for event data if on control network if eventservicenetidx >= 0 and eventgroup != otagroup: @@ -588,7 +590,8 @@ class EmaneManager(ModelManager): node = iface.node if not node.up: continue - if isinstance(node, Rj45Node): + transport_type = emanexml.get_transport_type(iface) + if transport_type == TransportType.RAW: node.host_cmd(kill_emaned, wait=False) else: node.cmd(kill_emaned, wait=False) diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index cb605b21..cf973f34 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -13,7 +13,6 @@ from core.emulator.enumerations import TransportType from core.errors import CoreError from core.nodes.interface import CoreInterface from core.nodes.network import CtrlNet -from core.nodes.physical import Rj45Node from core.xml import corexml if TYPE_CHECKING: @@ -93,7 +92,8 @@ def create_iface_file( :return: """ node = iface.node - if isinstance(node, Rj45Node): + transport_type = get_transport_type(iface) + if transport_type == TransportType.RAW: file_path = os.path.join(node.session.session_dir, file_name) else: file_path = os.path.join(node.nodedir, file_name) @@ -185,21 +185,15 @@ def build_platform_xml( ) add_param(transport_element, "device", iface.name) - # determine platform element to add xml to - if iface.transport_type == TransportType.RAW: - otadev = control_net.brname - eventdev = control_net.brname - else: - otadev = None - eventdev = None + transport_type = get_transport_type(iface) + transport_configs = {"otamanagerdevice", "eventservicedevice"} platform_element = etree.Element("platform") - if otadev: - emane_manager.set_config("otamanagerdevice", otadev) - if eventdev: - emane_manager.set_config("eventservicedevice", eventdev) for configuration in emane_manager.emane_config.emulator_config: name = configuration.id - value = emane_manager.get_config(name) + if transport_type == TransportType.RAW and name in transport_configs: + value = control_net.brname + else: + value = emane_manager.get_config(name) add_param(platform_element, name, value) platform_element.append(nem_element) emane_net.setnemid(iface, nem_id) From fcda1f9f14432aab23c5bf937964b3054f5eca7b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 3 Jul 2020 09:08:36 -0700 Subject: [PATCH 137/146] daemon: CoreInterface now defaults to a virtual transport type, added utility methods to check if an interface is virtual/raw, cleaned up all emane code using these types of checks --- daemon/core/emane/emanemanager.py | 9 +++------ daemon/core/nodes/interface.py | 19 +++++++++++++++++-- daemon/core/xml/emanexml.py | 31 ++++++------------------------- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 476010cb..15faedcc 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -26,7 +26,6 @@ from core.emulator.enumerations import ( LinkTypes, MessageFlags, RegisterTlvs, - TransportType, ) from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode, NodeBase @@ -536,8 +535,7 @@ class EmaneManager(ModelManager): if realtime: emanecmd += " -r" node = iface.node - transport_type = emanexml.get_transport_type(iface) - if not transport_type == TransportType.RAW: + if iface.is_virtual(): otagroup, _otaport = self.get_config("otamanagergroup").split(":") otadev = self.get_config("otamanagerdevice") otanetidx = self.session.get_control_net_index(otadev) @@ -590,8 +588,7 @@ class EmaneManager(ModelManager): node = iface.node if not node.up: continue - transport_type = emanexml.get_transport_type(iface) - if transport_type == TransportType.RAW: + if iface.is_raw(): node.host_cmd(kill_emaned, wait=False) else: node.cmd(kill_emaned, wait=False) @@ -614,7 +611,7 @@ class EmaneManager(ModelManager): for key in sorted(self._emane_nets): emane_net = self._emane_nets[key] for iface in emane_net.get_ifaces(): - if iface.transport_type == TransportType.VIRTUAL: + if iface.is_virtual(): iface.shutdown() iface.poshook = None diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index e4d4d0ac..7f33973e 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -60,7 +60,7 @@ class CoreInterface: # placeholder position hook self.poshook: Callable[[CoreInterface], None] = lambda x: None # used with EMANE - self.transport_type: Optional[TransportType] = None + self.transport_type: TransportType = TransportType.VIRTUAL # id of interface for node self.node_id: Optional[int] = None # id of interface for network @@ -310,6 +310,22 @@ class CoreInterface: """ return id(self) < id(other) + def is_raw(self) -> bool: + """ + Used to determine if this interface is considered a raw interface. + + :return: True if raw interface, False otherwise + """ + return self.transport_type == TransportType.RAW + + def is_virtual(self) -> bool: + """ + Used to determine if this interface is considered a virtual interface. + + :return: True if virtual interface, False otherwise + """ + return self.transport_type == TransportType.VIRTUAL + class Veth(CoreInterface): """ @@ -404,7 +420,6 @@ class TunTap(CoreInterface): :param start: start flag """ super().__init__(session, node, name, localname, mtu, server) - self.transport_type = TransportType.VIRTUAL if start: self.startup() diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index cf973f34..0ef13a80 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -9,7 +9,6 @@ from core import utils from core.config import Configuration from core.emane.nodes import EmaneNet from core.emulator.distributed import DistributedServer -from core.emulator.enumerations import TransportType from core.errors import CoreError from core.nodes.interface import CoreInterface from core.nodes.network import CtrlNet @@ -92,8 +91,7 @@ def create_iface_file( :return: """ node = iface.node - transport_type = get_transport_type(iface) - if transport_type == TransportType.RAW: + if iface.is_raw(): file_path = os.path.join(node.session.session_dir, file_name) else: file_path = os.path.join(node.nodedir, file_name) @@ -185,12 +183,11 @@ def build_platform_xml( ) add_param(transport_element, "device", iface.name) - transport_type = get_transport_type(iface) transport_configs = {"otamanagerdevice", "eventservicedevice"} platform_element = etree.Element("platform") for configuration in emane_manager.emane_config.emulator_config: name = configuration.id - if transport_type == TransportType.RAW and name in transport_configs: + if iface.is_raw() and name in transport_configs: value = control_net.brname else: value = emane_manager.get_config(name) @@ -214,7 +211,7 @@ def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None: :param config: all current configuration values :return: nothing """ - transport_type = get_transport_type(iface) + transport_type = iface.transport_type transport_element = etree.Element( "transport", name=f"{transport_type.value.capitalize()} Transport", @@ -224,7 +221,7 @@ def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None: # get emane model cnfiguration flowcontrol = config.get("flowcontrolenable", "0") == "1" - if transport_type == TransportType.VIRTUAL: + if iface.is_virtual(): device_path = "/dev/net/tun_flowctl" if not os.path.exists(device_path): device_path = "/dev/net/tun" @@ -338,19 +335,6 @@ def create_event_service_xml( create_file(event_element, "emaneeventmsgsvc", file_path, server) -def get_transport_type(iface: CoreInterface) -> TransportType: - """ - Get transport type for a given interface. - - :param iface: interface to get transport type for - :return: transport type - """ - transport_type = TransportType.VIRTUAL - if iface.transport_type == TransportType.RAW: - transport_type = TransportType.RAW - return transport_type - - def transport_file_name(iface: CoreInterface) -> str: """ Create name for a transport xml file. @@ -358,8 +342,7 @@ def transport_file_name(iface: CoreInterface) -> str: :param iface: interface running emane :return: transport xml file name """ - transport_type = get_transport_type(iface) - return f"{iface.name}-trans-{transport_type.value}.xml" + return f"{iface.name}-trans-{iface.transport_type.value}.xml" def nem_file_name(iface: CoreInterface) -> str: @@ -369,9 +352,7 @@ def nem_file_name(iface: CoreInterface) -> str: :param iface: interface running emane :return: nem xm file name """ - append = "" - if iface and iface.transport_type == TransportType.RAW: - append = "-raw" + append = "-raw" if iface.is_raw() else "" return f"{iface.name}-nem{append}.xml" From 5cc4d92760137796e9b66f45f0705a67ebed8869 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 5 Jul 2020 21:29:03 -0700 Subject: [PATCH 138/146] daemon: removed nem map from individual emane networks, all nems are stored and generated from the emane manager --- daemon/core/api/grpc/grpcutils.py | 7 +++- daemon/core/api/grpc/server.py | 22 +++++----- daemon/core/emane/commeffect.py | 10 ++--- daemon/core/emane/emanemanager.py | 59 ++++++++++++++------------- daemon/core/emane/nodes.py | 41 ++++--------------- daemon/core/services/emaneservices.py | 2 +- daemon/core/xml/corexml.py | 4 +- daemon/core/xml/corexmldeployment.py | 7 ++-- daemon/core/xml/emanexml.py | 1 - 9 files changed, 64 insertions(+), 89 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index bd9e808d..bd3519f7 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -491,10 +491,13 @@ def iface_to_proto(node_id: int, iface: CoreInterface) -> core_pb2.Interface: ) -def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int: +def get_nem_id( + session: Session, node: CoreNode, iface_id: int, context: ServicerContext +) -> int: """ Get nem id for a given node and interface id. + :param session: session node belongs to :param node: node to get nem id for :param iface_id: id of interface on node to get nem id for :param context: request context @@ -508,7 +511,7 @@ def get_nem_id(node: CoreNode, iface_id: int, context: ServicerContext) -> int: if not isinstance(net, EmaneNet): message = f"{node.name} interface {iface_id} is not an EMANE network" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) - nem_id = net.getnemid(iface) + nem_id = session.emane.get_nem_id(iface) if nem_id is None: message = f"{node.name} interface {iface_id} nem id does not exist" context.abort(grpc.StatusCode.INVALID_ARGUMENT, message) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index aa5ec539..5bdebac6 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -1551,29 +1551,29 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("emane link: %s", request) session = self.get_session(request.session_id, context) nem1 = request.nem1 - emane1, iface = session.emane.nemlookup(nem1) - if not emane1 or not iface: + iface1 = session.emane.get_iface(nem1) + if not iface1: context.abort(grpc.StatusCode.NOT_FOUND, f"nem one {nem1} not found") - node1 = iface.node + node1 = iface1.node nem2 = request.nem2 - emane2, iface = session.emane.nemlookup(nem2) - if not emane2 or not iface: + iface2 = session.emane.get_iface(nem2) + if not iface2: context.abort(grpc.StatusCode.NOT_FOUND, f"nem two {nem2} not found") - node2 = iface.node + node2 = iface2.node - if emane1.id == emane2.id: + if iface1.net == iface2.net: if request.linked: flag = MessageFlags.ADD else: flag = MessageFlags.DELETE - color = session.get_link_color(emane1.id) + color = session.get_link_color(iface1.net.id) link = LinkData( message_type=flag, type=LinkTypes.WIRELESS, node1_id=node1.id, node2_id=node2.id, - network_id=emane1.id, + network_id=iface1.net.id, color=color, ) session.broadcast_link(link) @@ -1796,8 +1796,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): for request in request_iterator: session = self.get_session(request.session_id, context) node1 = self.get_node(session, request.node1_id, context, CoreNode) - nem1 = grpcutils.get_nem_id(node1, request.iface1_id, context) + nem1 = grpcutils.get_nem_id(session, node1, request.iface1_id, context) node2 = self.get_node(session, request.node2_id, context, CoreNode) - nem2 = grpcutils.get_nem_id(node2, request.iface2_id, context) + nem2 = grpcutils.get_nem_id(session, node2, request.iface2_id, context) session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2) return EmanePathlossesResponse() diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index a812b66d..0fa70a92 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -10,7 +10,6 @@ from lxml import etree from core.config import ConfigGroup, Configuration from core.emane import emanemanifest, emanemodel -from core.emane.nodes import EmaneNet from core.emulator.data import LinkOptions from core.nodes.interface import CoreInterface from core.xml import emanexml @@ -124,12 +123,11 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): # TODO: batch these into multiple events per transmission # TODO: may want to split out seconds portion of delay and jitter event = CommEffectEvent() - emane_node = self.session.get_node(self.id, EmaneNet) - nemid = emane_node.getnemid(iface) - nemid2 = emane_node.getnemid(iface2) + nem1 = self.session.emane.get_nem_id(iface) + nem2 = self.session.emane.get_nem_id(iface2) logging.info("sending comm effect event") event.append( - nemid, + nem1, latency=convert_none(options.delay), jitter=convert_none(options.jitter), loss=convert_none(options.loss), @@ -137,4 +135,4 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): unicast=int(convert_none(options.bandwidth)), broadcast=int(convert_none(options.bandwidth)), ) - service.publish(nemid2, event) + service.publish(nem2, event) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 15faedcc..2a7f9844 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -90,7 +90,8 @@ class EmaneManager(ModelManager): """ super().__init__() self.session: "Session" = session - self.nems: Dict[int, CoreInterface] = {} + self.nems_to_ifaces: Dict[int, CoreInterface] = {} + self.ifaces_to_nems: Dict[CoreInterface, int] = {} self._emane_nets: Dict[int, EmaneNet] = {} self._emane_node_lock: threading.Lock = threading.Lock() # port numbers are allocated from these counters @@ -117,7 +118,7 @@ class EmaneManager(ModelManager): def next_nem_id(self) -> int: nem_id = int(self.get_config("nem_id_start")) - while nem_id in self.nems: + while nem_id in self.nems_to_ifaces: nem_id += 1 return nem_id @@ -363,7 +364,7 @@ class EmaneManager(ModelManager): 0, remove=False, conf_required=False ) nem_id = self.next_nem_id() - self.nems[nem_id] = iface + self.set_nem(nem_id, iface) self.write_nem(iface, nem_id) emanexml.build_platform_xml(self, control_net, emane_net, iface, nem_id) config = self.get_iface_config(emane_net, iface) @@ -371,6 +372,18 @@ class EmaneManager(ModelManager): self.start_daemon(iface) self.install_iface(emane_net, iface) + def set_nem(self, nem_id: int, iface: CoreInterface) -> None: + if nem_id in self.nems_to_ifaces: + raise CoreError(f"adding duplicate nem: {nem_id}") + self.nems_to_ifaces[nem_id] = iface + self.ifaces_to_nems[iface] = nem_id + + def get_iface(self, nem_id: int) -> Optional[CoreInterface]: + return self.nems_to_ifaces.get(nem_id) + + def get_nem_id(self, iface: CoreInterface) -> Optional[int]: + return self.ifaces_to_nems.get(iface) + def write_nem(self, iface: CoreInterface, nem_id: int) -> None: path = os.path.join(self.session.session_dir, "emane_nems") try: @@ -405,7 +418,8 @@ class EmaneManager(ModelManager): """ with self._emane_node_lock: self._emane_nets.clear() - self.nems.clear() + self.nems_to_ifaces.clear() + self.ifaces_to_nems.clear() def shutdown(self) -> None: """ @@ -448,42 +462,29 @@ class EmaneManager(ModelManager): model_class = self.models[model_name] emane_net.setmodel(model_class, config) - def nemlookup(self, nemid) -> Tuple[Optional[EmaneNet], Optional[CoreInterface]]: - """ - Look for the given numerical NEM ID and return the first matching - EMANE network and NEM interface. - """ - emane_node = None - iface = None - for node_id in self._emane_nets: - emane_node = self._emane_nets[node_id] - iface = emane_node.get_nem_iface(nemid) - if iface is not None: - break - else: - emane_node = None - return emane_node, iface - def get_nem_link( self, nem1: int, nem2: int, flags: MessageFlags = MessageFlags.NONE ) -> Optional[LinkData]: - emane1, iface = self.nemlookup(nem1) - if not emane1 or not iface: + iface1 = self.get_iface(nem1) + if not iface1: logging.error("invalid nem: %s", nem1) return None - node1 = iface.node - emane2, iface = self.nemlookup(nem2) - if not emane2 or not iface: + node1 = iface1.node + iface2 = self.get_iface(nem2) + if not iface2: logging.error("invalid nem: %s", nem2) return None - node2 = iface.node - color = self.session.get_link_color(emane1.id) + node2 = iface2.node + if iface1.net != iface2.net: + return None + emane_net = iface1.net + color = self.session.get_link_color(emane_net.id) return LinkData( message_type=flags, type=LinkTypes.WIRELESS, node1_id=node1.id, node2_id=node2.id, - network_id=emane1.id, + network_id=emane_net.id, color=color, ) @@ -728,7 +729,7 @@ class EmaneManager(ModelManager): Returns True if successfully parsed and a Node Message was sent. """ # convert nemid to node number - _emanenode, iface = self.nemlookup(nemid) + iface = self.get_iface(nemid) if iface is None: logging.info("location event for unknown NEM %s", nemid) return False diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 7e8a0a4f..cfb3342e 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -52,7 +52,6 @@ class EmaneNet(CoreNetworkBase): ) -> None: super().__init__(session, _id, name, server) self.conf: str = "" - self.nemidmap: Dict[CoreInterface, int] = {} self.model: "OptionalEmaneModel" = None self.mobility: Optional[WayPointMobility] = None @@ -105,32 +104,6 @@ class EmaneNet(CoreNetworkBase): self.mobility = model(session=self.session, _id=self.id) self.mobility.update_config(config) - def setnemid(self, iface: CoreInterface, nemid: int) -> None: - """ - Record an interface to numerical ID mapping. The Emane controller - object manages and assigns these IDs for all NEMs. - """ - self.nemidmap[iface] = nemid - - def getnemid(self, iface: CoreInterface) -> Optional[int]: - """ - Given an interface, return its numerical ID. - """ - if iface not in self.nemidmap: - return None - else: - return self.nemidmap[iface] - - def get_nem_iface(self, nemid: int) -> Optional[CoreInterface]: - """ - Given a numerical NEM ID, return its interface. This returns the - first interface that matches the given NEM ID. - """ - for iface in self.nemidmap: - if self.nemidmap[iface] == nemid: - return iface - return None - def _nem_position( self, iface: CoreInterface ) -> Optional[Tuple[int, float, float, float]]: @@ -140,9 +113,9 @@ class EmaneNet(CoreNetworkBase): :param iface: interface to get nem emane position for :return: nem position tuple, None otherwise """ - nemid = self.getnemid(iface) + nem_id = self.session.emane.get_nem_id(iface) ifname = iface.localname - if nemid is None: + if nem_id is None: logging.info("nemid for %s is unknown", ifname) return node = iface.node @@ -153,7 +126,7 @@ class EmaneNet(CoreNetworkBase): node.position.set_geo(lon, lat, alt) # altitude must be an integer or warning is printed alt = int(round(alt)) - return nemid, lon, lat, alt + return nem_id, lon, lat, alt def setnemposition(self, iface: CoreInterface) -> None: """ @@ -164,7 +137,6 @@ class EmaneNet(CoreNetworkBase): if self.session.emane.service is None: logging.info("position service not available") return - position = self._nem_position(iface) if position: nemid, lon, lat, alt = position @@ -195,9 +167,12 @@ class EmaneNet(CoreNetworkBase): def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: links = super().links(flags) - # gather current emane links - nem_ids = set(self.nemidmap.values()) emane_manager = self.session.emane + # gather current emane links + nem_ids = set() + for iface in self.get_ifaces(): + nem_id = emane_manager.get_nem_id(iface) + nem_ids.add(nem_id) emane_links = emane_manager.link_monitor.links considered = set() for link_key in emane_links: diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index e734851d..d694317a 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -28,7 +28,7 @@ class EmaneTransportService(CoreService): emane_net = iface.net config = emane_manager.get_iface_config(emane_net, iface) if emanexml.is_external(config): - nem_id = emane_net.getnemid(iface) + nem_id = emane_manager.get_nem_id(iface) cfg += f"emanegentransportxml {iface.name}-platform.xml\n" cfg += f"emanetransportd -r -l 0 -d transportdaemon{nem_id}.xml\n" return cfg diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 340d81d0..ffd07ebd 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -501,8 +501,8 @@ class CoreXmlWriter: iface = node.get_iface(iface_data.id) # check if emane interface if isinstance(iface.net, EmaneNet): - nem = iface.net.getnemid(iface) - add_attribute(iface_element, "nem", nem) + nem_id = self.session.emane.get_nem_id(iface) + add_attribute(iface_element, "nem", nem_id) add_attribute(iface_element, "id", iface_data.id) add_attribute(iface_element, "name", iface_data.name) add_attribute(iface_element, "mac", iface_data.mac) diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index 51201787..c062a1d2 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -9,7 +9,6 @@ from core import utils from core.emane.nodes import EmaneNet from core.executables import IP from core.nodes.base import CoreNodeBase, NodeBase -from core.nodes.interface import CoreInterface if TYPE_CHECKING: from core.emulator.session import Session @@ -38,11 +37,10 @@ def add_mapping(parent_element: etree.Element, maptype: str, mapref: str) -> Non def add_emane_iface( host_element: etree.Element, - iface: CoreInterface, + nem_id: int, platform_name: str = "p1", transport_name: str = "t1", ) -> etree.Element: - nem_id = iface.net.nemidmap[iface] host_id = host_element.get("id") # platform data @@ -158,7 +156,8 @@ class CoreXmlDeployment: for iface in node.get_ifaces(): emane_element = None if isinstance(iface.net, EmaneNet): - emane_element = add_emane_iface(host_element, iface) + nem_id = self.session.emane.get_nem_id(iface) + emane_element = add_emane_iface(host_element, nem_id) parent_element = host_element if emane_element is not None: diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 0ef13a80..88aeaa97 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -193,7 +193,6 @@ def build_platform_xml( value = emane_manager.get_config(name) add_param(platform_element, name, value) platform_element.append(nem_element) - emane_net.setnemid(iface, nem_id) mac = _MAC_PREFIX + ":00:00:" mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" iface.set_mac(mac) From b3a4b1cb10a47f8a5b7de36e1135096468cf8d61 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 5 Jul 2020 21:56:22 -0700 Subject: [PATCH 139/146] daemon: updates to support running emane on the fly for a newly connected link --- daemon/core/emane/emanemanager.py | 4 ++-- daemon/core/emane/nodes.py | 10 +++++++++- daemon/core/emulator/session.py | 4 +++- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 2a7f9844..3765ba44 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -252,8 +252,8 @@ class EmaneManager(ModelManager): """ with self._emane_node_lock: if emane_net.id in self._emane_nets: - raise KeyError( - f"non-unique EMANE object id {emane_net.id} for {emane_net}" + raise CoreError( + f"duplicate emane network({emane_net.id}): {emane_net.name}" ) self._emane_nets[emane_net.id] = emane_net diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index cfb3342e..5791f46a 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -8,7 +8,13 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.distributed import DistributedServer -from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes, RegisterTlvs +from core.emulator.enumerations import ( + EventTypes, + LinkTypes, + MessageFlags, + NodeTypes, + RegisterTlvs, +) from core.errors import CoreError from core.nodes.base import CoreNetworkBase, CoreNode from core.nodes.interface import CoreInterface @@ -203,4 +209,6 @@ class EmaneNet(CoreNetworkBase): iface.set_mac(iface_data.mac) for ip in iface_data.get_ips(): iface.add_ip(ip) + if self.session.state == EventTypes.RUNTIME_STATE: + self.session.emane.start_iface(self, iface) return iface diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 9f5364b9..cad6ae3c 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -531,7 +531,7 @@ class Session: self.set_node_position(node, options) # add services to needed nodes - if isinstance(node, (CoreNode, PhysicalNode, DockerNode, LxcNode)): + if isinstance(node, (CoreNode, PhysicalNode)): node.type = options.model logging.debug("set node type: %s", node.type) self.services.add_services(node, node.type, options.services) @@ -545,6 +545,8 @@ class Session: # ensure default emane configuration if isinstance(node, EmaneNet) and options.emane: self.emane.set_model_config(_id, options.emane) + if self.state == EventTypes.RUNTIME_STATE: + self.emane.add_node(node) # set default wlan config if needed if isinstance(node, WlanNode): self.mobility.set_model_config(_id, BasicRangeModel.name) From 8dc570a98d7843337ace6d7ef22a37c7f27a309f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:13:54 -0700 Subject: [PATCH 140/146] daemon: removed commented out code --- daemon/core/nodes/base.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 039008ef..7f444480 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -815,17 +815,6 @@ class CoreNode(CoreNodeBase): with self.lock: if net.has_custom_iface: return net.custom_iface(self, iface_data) - # if net.is_emane is True: - # iface_id = self.newtuntap(iface_data.id, iface_data.name) - # # TUN/TAP is not ready for addressing yet; the device may - # # take some time to appear, and installing it into a - # # namespace after it has been bound removes addressing; - # # save addresses with the interface now - # self.attachnet(iface_id, net) - # iface = self.get_iface(iface_id) - # iface.set_mac(iface_data.mac) - # for ip in ips: - # iface.add_ip(ip) else: iface_id = self.newveth(iface_data.id, iface_data.name) self.attachnet(iface_id, net) From 6f7e42d310164bbe1bef9b98de3f48f8355df8b3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:32:43 -0700 Subject: [PATCH 141/146] daemon: avoid command error logging when checking for emane version as validation for checking if emane is installed --- daemon/core/emane/emanemanager.py | 33 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 3765ba44..ec39137d 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -165,23 +165,24 @@ class EmaneManager(ModelManager): :return: nothing """ - try: - # check for emane - args = "emane --version" - emane_version = utils.cmd(args) - logging.info("using EMANE: %s", emane_version) - self.session.distributed.execute(lambda x: x.remote_cmd(args)) - - # load default emane models - self.load_models(EMANE_MODELS) - - # load custom models - custom_models_path = self.session.options.get_config("emane_models_dir") - if custom_models_path: - emane_models = utils.load_classes(custom_models_path, EmaneModel) - self.load_models(emane_models) - except CoreCommandError: + # check for emane + path = utils.which("emane", required=False) + if not path: logging.info("emane is not installed") + return + + # get version + emane_version = utils.cmd("emane --version") + logging.info("using emane: %s", emane_version) + + # load default emane models + self.load_models(EMANE_MODELS) + + # load custom models + custom_models_path = self.session.options.get_config("emane_models_dir") + if custom_models_path: + emane_models = utils.load_classes(custom_models_path, EmaneModel) + self.load_models(emane_models) def deleteeventservice(self) -> None: if self.service: From 0045c8d79c53c89f5079f64fe8b536c7fa38f43c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:37:04 -0700 Subject: [PATCH 142/146] pygui: avoid trying to bring up a terminal for rj45 nodes --- daemon/core/gui/graph/node.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 6e8185b8..dfe724bd 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -193,7 +193,8 @@ class CanvasNode: def double_click(self, event: tk.Event) -> None: if self.app.core.is_runtime(): - self.canvas.core.launch_terminal(self.core_node.id) + if NodeUtils.is_container_node(self.core_node.type): + self.canvas.core.launch_terminal(self.core_node.id) else: self.show_config() From c761c55ebc5d7f89116cbfe81031953394657b85 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 6 Jul 2020 21:47:46 -0700 Subject: [PATCH 143/146] tests: patch utils.which --- daemon/tests/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 665f2c1a..0e25dee9 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -55,6 +55,7 @@ def patcher(request): if request.config.getoption("mock"): patch_manager.patch("os.mkdir") patch_manager.patch("core.utils.cmd") + patch_manager.patch("core.utils.which") patch_manager.patch("core.nodes.netclient.get_net_client") patch_manager.patch_obj( LinuxNetClient, "get_mac", return_value="00:00:00:00:00:00" From 6648dc7825279bf59ec857725545aca9ed9809e0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 7 Jul 2020 08:46:47 -0700 Subject: [PATCH 144/146] pygui: service and config service dialogs will now properly show services for default group selected --- daemon/core/gui/dialogs/nodeconfigservice.py | 2 +- daemon/core/gui/dialogs/nodeservice.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index b5250eba..b9a9a1f5 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -84,7 +84,7 @@ class NodeConfigServiceDialog(Dialog): button.grid(row=0, column=3, sticky="ew") # trigger group change - self.groups.listbox.event_generate("<>") + self.handle_group_change() def handle_group_change(self, event: tk.Event = None) -> None: selection = self.groups.listbox.curselection() diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index f6f5e5cf..6fcc2912 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -82,7 +82,7 @@ class NodeServiceDialog(Dialog): button.grid(row=0, column=3, sticky="ew") # trigger group change - self.groups.listbox.event_generate("<>") + self.handle_group_change() def handle_group_change(self, event: tk.Event = None) -> None: selection = self.groups.listbox.curselection() From f1ff1a65770dc446e6cd75f113ced9eacd93d3ea Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 7 Jul 2020 14:24:43 -0700 Subject: [PATCH 145/146] pygui: only attempt to run observer commands on container nodes --- daemon/core/gui/graph/node.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index dfe724bd..f765816d 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -179,7 +179,10 @@ class CanvasNode: self.app.core.edit_node(self.core_node) def on_enter(self, event: tk.Event) -> None: - if self.app.core.is_runtime() and self.app.core.observer: + is_runtime = self.app.core.is_runtime() + has_observer = self.app.core.observer is not None + is_container = NodeUtils.is_container_node(self.core_node.type) + if is_runtime and has_observer and is_container: self.tooltip.text.set("waiting...") self.tooltip.on_enter(event) try: From bb4514b93e315c9b5682ee893679b9bb1b764a4c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 7 Jul 2020 15:16:17 -0700 Subject: [PATCH 146/146] daemon: changes to saving and restoring server used for nodes in xml --- daemon/core/xml/corexml.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index ffd07ebd..d1c43d9b 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -128,6 +128,8 @@ class NodeElement: self.element: etree.Element = etree.Element(element_name) add_attribute(self.element, "id", node.id) add_attribute(self.element, "name", node.name) + server = self.node.server.name if self.node.server else None + add_attribute(self.element, "server", server) add_attribute(self.element, "icon", node.icon) add_attribute(self.element, "canvas", node.canvas) self.add_position() @@ -801,8 +803,10 @@ class CoreXmlReader: icon = device_element.get("icon") clazz = device_element.get("class") image = device_element.get("image") - options = NodeOptions(name=name, model=model, image=image, icon=icon) - + server = device_element.get("server") + options = NodeOptions( + name=name, model=model, image=image, icon=icon, server=server + ) node_type = NodeTypes.DEFAULT if clazz == "docker": node_type = NodeTypes.DOCKER @@ -842,7 +846,8 @@ class CoreXmlReader: node_type = NodeTypes[network_element.get("type")] _class = self.session.get_node_class(node_type) icon = network_element.get("icon") - options = NodeOptions(name=name, icon=icon) + server = network_element.get("server") + options = NodeOptions(name=name, icon=icon, server=server) position_element = network_element.find("position") if position_element is not None: