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] 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())