diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index e2e1e729..8e7b37c3 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -16,7 +16,7 @@ from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsRequest, GetNodeConfigServiceRequest, ) -from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest +from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest, LinkedRequest from core.api.grpc.emane_pb2 import ( EmaneLinkRequest, GetEmaneEventChannelRequest, @@ -1049,6 +1049,36 @@ class CoreGrpcClient: """ self.stub.EmanePathlosses(streamer.iter()) + def linked( + self, + session_id: int, + node1_id: int, + node2_id: int, + iface1_id: int, + iface2_id: int, + linked: bool, + ) -> None: + """ + Link or unlink an existing core wired link. + + :param session_id: session containing the link + :param node1_id: first node in link + :param node2_id: second node in link + :param iface1_id: node1 interface + :param iface2_id: node2 interface + :param linked: True to connect link, False to disconnect + :return: nothing + """ + request = LinkedRequest( + session_id=session_id, + node1_id=node1_id, + node2_id=node2_id, + iface1_id=iface1_id, + iface2_id=iface2_id, + linked=linked, + ) + self.stub.Linked(request) + def connect(self) -> None: """ Open connection to server, must be closed manually. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 4be0737d..ca0dd7f5 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -26,7 +26,7 @@ from core.api.grpc.configservices_pb2 import ( GetNodeConfigServiceRequest, GetNodeConfigServiceResponse, ) -from core.api.grpc.core_pb2 import ExecuteScriptResponse +from core.api.grpc.core_pb2 import ExecuteScriptResponse, LinkedRequest, LinkedResponse from core.api.grpc.emane_pb2 import ( EmaneLinkRequest, EmaneLinkResponse, @@ -1315,3 +1315,16 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): nem2 = grpcutils.get_nem_id(session, node2, request.iface2_id, context) session.emane.publish_pathloss(nem1, nem2, request.rx1, request.rx2) return EmanePathlossesResponse() + + def Linked( + self, request: LinkedRequest, context: ServicerContext + ) -> LinkedResponse: + session = self.get_session(request.session_id, context) + session.linked( + request.node1_id, + request.node2_id, + request.iface1_id, + request.iface2_id, + request.linked, + ) + return LinkedResponse() diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 4ca2485d..0d7930a9 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -192,6 +192,45 @@ class Session: def use_ovs(self) -> bool: return self.options.get_config("ovs") == "1" + def linked( + self, node1_id: int, node2_id: int, iface1_id: int, iface2_id: int, linked: bool + ) -> None: + """ + Links or unlinks wired core link interfaces from being connected to the same + bridge. + + :param node1_id: first node in link + :param node2_id: second node in link + :param iface1_id: node1 interface + :param iface2_id: node2 interface + :param linked: True if interfaces should be connected, False for disconnected + :return: nothing + """ + node1 = self.get_node(node1_id, NodeBase) + node2 = self.get_node(node2_id, NodeBase) + logger.info( + "link node(%s):interface(%s) node(%s):interface(%s) linked(%s)", + node1.name, + iface1_id, + node2.name, + iface2_id, + linked, + ) + iface1 = node1.get_iface(iface1_id) + iface2 = node2.get_iface(iface2_id) + core_link = self.link_manager.get_link(node1, iface1, node2, iface2) + if not core_link: + raise CoreError( + f"there is no link for node({node1.name}):interface({iface1_id}) " + f"node({node2.name}):interface({iface2_id})" + ) + if linked: + core_link.ptp.attach(iface1) + core_link.ptp.attach(iface2) + else: + core_link.ptp.detach(iface1) + core_link.ptp.detach(iface2) + def add_link( self, node1_id: int, @@ -223,7 +262,14 @@ class Session: node1 = self.get_node(node1_id, NodeBase) node2 = self.get_node(node2_id, NodeBase) # check for invalid linking - if isinstance(node1, WIRELESS_TYPE) and isinstance(node2, WIRELESS_TYPE): + if ( + isinstance(node1, WIRELESS_TYPE) + and isinstance(node2, WIRELESS_TYPE) + or isinstance(node1, WIRELESS_TYPE) + and not isinstance(node2, CoreNodeBase) + or not isinstance(node1, CoreNodeBase) + and isinstance(node2, WIRELESS_TYPE) + ): raise CoreError(f"cannot link node({type(node1)}) node({type(node2)})") # custom links iface1 = None diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index f0cb242d..e3b14341 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -61,6 +61,8 @@ service CoreApi { } rpc DeleteLink (DeleteLinkRequest) returns (DeleteLinkResponse) { } + rpc Linked (LinkedRequest) returns (LinkedResponse) { + } // mobility rpc rpc GetMobilityConfig (mobility.GetMobilityConfigRequest) returns (mobility.GetMobilityConfigResponse) { @@ -684,3 +686,15 @@ message Server { string name = 1; string host = 2; } + +message LinkedRequest { + int32 session_id = 1; + int32 node1_id = 2; + int32 node2_id = 3; + int32 iface1_id = 4; + int32 iface2_id = 5; + bool linked = 6; +} + +message LinkedResponse { +}