import time from pathlib import Path from queue import Queue from tempfile import TemporaryFile from typing import Optional import grpc import pytest from mock import patch from core.api.grpc import wrappers from core.api.grpc.client import CoreGrpcClient, InterfaceHelper, MoveNodesStreamer from core.api.grpc.server import CoreGrpcServer from core.api.grpc.wrappers import ( ConfigOption, ConfigOptionType, EmaneModelConfig, Event, Geo, Hook, Interface, Link, LinkOptions, MobilityAction, MoveNodesRequest, Node, NodeServiceData, NodeType, Position, ServiceAction, ServiceValidationMode, SessionLocation, SessionState, ) from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.data import EventData, IpPrefixes, NodeData from core.emulator.enumerations import EventTypes, ExceptionLevels, MessageFlags from core.errors import CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.nodes.base import CoreNode from core.nodes.network import SwitchNode, WlanNode from core.xml.corexml import CoreXmlWriter class TestGrpc: @pytest.mark.parametrize("definition", [False, True]) def test_start_session(self, grpc_server: CoreGrpcServer, definition): # given client = CoreGrpcClient() with client.context_connect(): session = client.create_session() position = Position(x=50, y=100) node1 = session.add_node(1, position=position) position = Position(x=100, y=100) node2 = session.add_node(2, position=position) position = Position(x=200, y=200) wlan_node = session.add_node(3, _type=NodeType.WIRELESS_LAN, position=position) 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 = Link(node1_id=node1.id, node2_id=node2.id, iface1=iface1, iface2=iface2) session.links = [link] hook = Hook(state=SessionState.RUNTIME, file="echo.sh", data="echo hello") session.hooks = {hook.file: hook} location_x = 5 location_y = 10 location_z = 15 location_lat = 20 location_lon = 30 location_alt = 40 location_scale = 5 session.location = SessionLocation( x=location_x, y=location_y, z=location_z, lat=location_lat, lon=location_lon, alt=location_alt, scale=location_scale, ) # setup wlan config wlan_config_key = "range" wlan_config_value = "333" wlan_node.set_wlan({wlan_config_key: wlan_config_value}) # setup mobility config mobility_config_key = "refresh_ms" mobility_config_value = "60" wlan_node.set_mobility({mobility_config_key: mobility_config_value}) # setup service config service_name = "DefaultRoute" service_validate = ["echo hello"] node1.service_configs[service_name] = NodeServiceData( executables=[], dependencies=[], dirs=[], configs=[], startup=[], validate=service_validate, validation_mode=ServiceValidationMode.NON_BLOCKING, validation_timer=0, shutdown=[], meta="", ) # setup service file config service_file = "defaultroute.sh" service_file_data = "echo hello" node1.service_file_configs[service_name] = {service_file: service_file_data} # setup session option option_key = "controlnet" option_value = "172.16.0.0/24" session.set_options({option_key: option_value}) # when with patch.object(CoreXmlWriter, "write"): with client.context_connect(): client.start_session(session, definition=definition) # then real_session = grpc_server.coreemu.sessions[session.id] if definition: state = EventTypes.DEFINITION_STATE else: state = EventTypes.RUNTIME_STATE assert real_session.state == state assert node1.id in real_session.nodes assert node2.id in real_session.nodes assert wlan_node.id in real_session.nodes assert iface1_id in real_session.nodes[node1.id].ifaces assert iface2_id in real_session.nodes[node2.id].ifaces hook_file, hook_data = real_session.hooks[EventTypes.RUNTIME_STATE][0] assert hook_file == hook.file assert hook_data == hook.data assert real_session.location.refxyz == (location_x, location_y, location_z) assert real_session.location.refgeo == ( location_lat, location_lon, location_alt, ) assert real_session.location.refscale == location_scale set_wlan_config = real_session.mobility.get_model_config( wlan_node.id, BasicRangeModel.name ) assert set_wlan_config[wlan_config_key] == wlan_config_value set_mobility_config = real_session.mobility.get_model_config( wlan_node.id, Ns2ScriptedMobility.name ) assert set_mobility_config[mobility_config_key] == mobility_config_value service = real_session.services.get_service( node1.id, service_name, default_service=True ) assert service.validate == tuple(service_validate) real_node1 = real_session.get_node(node1.id, CoreNode) service_file = real_session.services.get_service_file( real_node1, service_name, service_file ) assert service_file.data == service_file_data assert option_value == real_session.options.get(option_key) @pytest.mark.parametrize("session_id", [None, 6013]) def test_create_session( self, grpc_server: CoreGrpcServer, session_id: Optional[int] ): # given client = CoreGrpcClient() # when with client.context_connect(): created_session = client.create_session(session_id) # then assert isinstance(created_session, wrappers.Session) session = grpc_server.coreemu.sessions.get(created_session.id) assert session is not None if session_id is not None: assert created_session.id == session_id assert session.id == session_id @pytest.mark.parametrize("session_id, expected", [(None, True), (6013, False)]) def test_delete_session( self, grpc_server: CoreGrpcServer, session_id: Optional[int], expected: bool ): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() if session_id is None: session_id = session.id # then with client.context_connect(): result = client.delete_session(session_id) # then assert result is expected assert grpc_server.coreemu.sessions.get(session_id) is None def test_get_session(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.add_node(CoreNode) session.set_state(EventTypes.DEFINITION_STATE) # then with client.context_connect(): session = client.get_session(session.id) # then assert session.state == SessionState.DEFINITION assert len(session.nodes) == 1 assert len(session.links) == 0 def test_get_sessions(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() # then with client.context_connect(): sessions = client.get_sessions() # then found_session = None for current_session in sessions: if current_session.id == session.id: found_session = current_session break assert len(sessions) == 1 assert found_session is not None def test_add_node(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() # then with client.context_connect(): position = Position(x=0, y=0) node = Node(id=1, name="n1", type=NodeType.DEFAULT, position=position) node_id = client.add_node(session.id, node) # then assert node_id is not None assert session.get_node(node_id, CoreNode) is not None def test_get_node(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) # then with client.context_connect(): get_node, ifaces, links = client.get_node(session.id, node.id) # then assert node.id == get_node.id assert len(ifaces) == 0 assert len(links) == 0 def test_move_node_pos(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) position = Position(x=100.0, y=50.0) # then with client.context_connect(): result = client.move_node(session.id, node.id, position=position) # then assert result is True assert node.position.x == position.x assert node.position.y == position.y def test_move_node_geo(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) geo = Geo(lon=0.0, lat=0.0, alt=0.0) # then with client.context_connect(): result = client.move_node(session.id, node.id, geo=geo) # then assert result is True assert node.position.lon == geo.lon assert node.position.lat == geo.lat assert node.position.alt == geo.alt def test_move_node_exception(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) # then and when with pytest.raises(CoreError), client.context_connect(): client.move_node(session.id, node.id) def test_edit_node(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) icon = "test.png" # then with client.context_connect(): result = client.edit_node(session.id, node.id, icon) # then assert result is True assert node.icon == icon @pytest.mark.parametrize("node_id, expected", [(1, True), (2, False)]) def test_delete_node( self, grpc_server: CoreGrpcServer, node_id: int, expected: bool ): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) # then with client.context_connect(): result = client.delete_node(session.id, node_id) # then assert result is expected if expected is True: with pytest.raises(CoreError): assert session.get_node(node.id, CoreNode) def test_node_command(self, request, grpc_server: CoreGrpcServer): if request.config.getoption("mock"): pytest.skip("mocking calls") # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) node = session.add_node(CoreNode) session.instantiate() expected_output = "hello world" expected_status = 0 # then command = f"echo {expected_output}" with client.context_connect(): output = client.node_command(session.id, node.id, command) # then assert (expected_status, expected_output) == output def test_get_node_terminal(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) node = session.add_node(CoreNode) session.instantiate() # then with client.context_connect(): terminal = client.get_node_terminal(session.id, node.id) # then assert terminal is not None def test_save_xml(self, grpc_server: CoreGrpcServer, tmpdir: TemporaryFile): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() tmp = tmpdir.join("text.xml") # then with client.context_connect(): client.save_xml(session.id, str(tmp)) # then assert tmp.exists() def test_open_xml_hook(self, grpc_server: CoreGrpcServer, tmpdir: TemporaryFile): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() tmp = Path(tmpdir.join("text.xml")) session.save_xml(tmp) # then with client.context_connect(): result, session_id = client.open_xml(tmp) # then assert result is True assert session_id is not None def test_add_link(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) assert len(session.link_manager.links()) == 0 iface = InterfaceHelper("10.0.0.0/24").create_iface(node.id, 0) link = Link(node.id, switch.id, iface1=iface) # then with client.context_connect(): result, iface1, _ = client.add_link(session.id, link) # then assert result is True assert len(session.link_manager.links()) == 1 assert iface1.id == iface.id assert iface1.ip4 == iface.ip4 def test_add_link_exception(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) # then link = Link(node.id, 3) with pytest.raises(grpc.RpcError): with client.context_connect(): client.add_link(session.id, link) def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) iface_data = ip_prefixes.create_iface(node) iface, _ = session.add_link(node.id, switch.id, iface_data) session.instantiate() options = LinkOptions(bandwidth=30000) assert iface.options.bandwidth != options.bandwidth link = Link(node.id, switch.id, iface1=Interface(id=iface.id), options=options) # then with client.context_connect(): result = client.edit_link(session.id, link) # then assert result is True assert options.bandwidth == iface.options.bandwidth def test_delete_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node1 = session.add_node(CoreNode) iface1 = ip_prefixes.create_iface(node1) node2 = session.add_node(CoreNode) iface2 = ip_prefixes.create_iface(node2) session.add_link(node1.id, node2.id, iface1, iface2) assert len(session.link_manager.links()) == 1 link = Link( node1.id, node2.id, iface1=Interface(id=iface1.id), iface2=Interface(id=iface2.id), ) # then with client.context_connect(): result = client.delete_link(session.id, link) # then assert result is True assert len(session.link_manager.links()) == 0 def test_get_wlan_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() wlan = session.add_node(WlanNode) # then with client.context_connect(): config = client.get_wlan_config(session.id, wlan.id) # then assert len(config) > 0 def test_set_wlan_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) wlan = session.add_node(WlanNode) wlan.setmodel(BasicRangeModel, BasicRangeModel.default_values()) session.instantiate() range_key = "range" range_value = "50" # then with client.context_connect(): result = client.set_wlan_config( session.id, wlan.id, { range_key: range_value, "delay": "0", "loss": "0", "bandwidth": "50000", "error": "0", "jitter": "0", }, ) # then assert result is True config = session.mobility.get_model_config(wlan.id, BasicRangeModel.name) assert config[range_key] == range_value assert wlan.wireless_model.range == int(range_value) def test_set_emane_model_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_location(47.57917, -122.13232, 2.00000, 1.0) options = EmaneNet.create_options() options.emane_model = EmaneIeee80211abgModel.name emane_network = session.add_node(EmaneNet, options=options) session.emane.node_models[emane_network.id] = EmaneIeee80211abgModel.name config_key = "bandwidth" config_value = "900000" option = ConfigOption( label=config_key, name=config_key, value=config_value, type=ConfigOptionType.INT32, group="Default", ) config = EmaneModelConfig( emane_network.id, EmaneIeee80211abgModel.name, config={config_key: option} ) # then with client.context_connect(): result = client.set_emane_model_config(session.id, config) # then assert result is True config = session.emane.get_config(emane_network.id, EmaneIeee80211abgModel.name) assert config[config_key] == config_value def test_get_emane_model_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_location(47.57917, -122.13232, 2.00000, 1.0) options = EmaneNet.create_options() options.emane_model = EmaneIeee80211abgModel.name emane_network = session.add_node(EmaneNet, options=options) session.emane.node_models[emane_network.id] = EmaneIeee80211abgModel.name # then with client.context_connect(): config = client.get_emane_model_config( session.id, emane_network.id, EmaneIeee80211abgModel.name ) # then assert len(config) > 0 def test_get_mobility_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() wlan = session.add_node(WlanNode) session.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {}) # then with client.context_connect(): config = client.get_mobility_config(session.id, wlan.id) # then assert len(config) > 0 def test_set_mobility_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() wlan = session.add_node(WlanNode) config_key = "refresh_ms" config_value = "60" # then with client.context_connect(): result = client.set_mobility_config( session.id, wlan.id, {config_key: config_value} ) # then assert result is True config = session.mobility.get_model_config(wlan.id, Ns2ScriptedMobility.name) assert config[config_key] == config_value def test_mobility_action(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() wlan = session.add_node(WlanNode) session.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {}) session.instantiate() # then with client.context_connect(): result = client.mobility_action(session.id, wlan.id, MobilityAction.STOP) # then assert result is True def test_get_service_defaults(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() # then with client.context_connect(): defaults = client.get_service_defaults(session.id) # then assert len(defaults) > 0 def test_set_service_defaults(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() model = "test" services = ["SSH"] # then with client.context_connect(): result = client.set_service_defaults(session.id, {model: services}) # then assert result is True assert session.services.default_services[model] == services def test_get_node_service(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) # then with client.context_connect(): service = client.get_node_service(session.id, node.id, "DefaultRoute") # then assert len(service.configs) > 0 def test_get_node_service_file(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) # then with client.context_connect(): data = client.get_node_service_file( session.id, node.id, "DefaultRoute", "defaultroute.sh" ) # then assert data is not None def test_service_action(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() options = CoreNode.create_options() options.legacy = True node = session.add_node(CoreNode, options=options) service_name = "DefaultRoute" # then with client.context_connect(): result = client.service_action( session.id, node.id, service_name, ServiceAction.STOP ) # then assert result is True def test_config_service_action(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) service_name = "DefaultRoute" # then with client.context_connect(): result = client.config_service_action( session.id, node.id, service_name, ServiceAction.STOP ) # then assert result is True def test_node_events(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) node.position.lat = 10.0 node.position.lon = 20.0 node.position.alt = 5.0 queue = Queue() def handle_event(event: Event) -> None: assert event.session_id == session.id assert event.node_event is not None event_node = event.node_event.node assert event_node.geo.lat == node.position.lat assert event_node.geo.lon == node.position.lon assert event_node.geo.alt == node.position.alt queue.put(event) # then with client.context_connect(): client.events(session.id, handle_event) time.sleep(0.1) session.broadcast_node(node) # then queue.get(timeout=5) def test_link_events(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() wlan = session.add_node(WlanNode) node = session.add_node(CoreNode) iface_data = ip_prefixes.create_iface(node) session.add_link(node.id, wlan.id, iface_data) core_link = list(session.link_manager.links())[0] link_data = core_link.get_data(MessageFlags.ADD) queue = Queue() def handle_event(event: Event) -> None: assert event.session_id == session.id assert event.link_event is not None queue.put(event) # then with client.context_connect(): client.events(session.id, handle_event) time.sleep(0.1) session.broadcast_link(link_data) # then queue.get(timeout=5) def test_throughputs(self, request, grpc_server: CoreGrpcServer): if request.config.getoption("mock"): pytest.skip("mocking calls") # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() queue = Queue() def handle_event(event_data): assert event_data.session_id == session.id queue.put(event_data) # then with client.context_connect(): client.throughputs(session.id, handle_event) time.sleep(0.1) # then queue.get(timeout=5) def test_session_events(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() queue = Queue() def handle_event(event: Event) -> None: assert event.session_id == session.id assert event.session_event is not None queue.put(event) # then with client.context_connect(): client.events(session.id, handle_event) time.sleep(0.1) event_data = EventData( event_type=EventTypes.RUNTIME_STATE, time=str(time.monotonic()) ) session.broadcast_event(event_data) # then queue.get(timeout=5) def test_exception_events(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() queue = Queue() exception_level = ExceptionLevels.FATAL source = "test" node_id = None text = "exception message" def handle_event(event: Event) -> None: assert event.session_id == session.id assert event.exception_event is not None exception_event = event.exception_event assert exception_event.level.value == exception_level.value assert exception_event.node_id == 0 assert exception_event.source == source assert exception_event.text == text queue.put(event) # then with client.context_connect(): client.events(session.id, handle_event) time.sleep(0.1) session.exception(exception_level, source, text, node_id) # then queue.get(timeout=5) def test_file_events(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node(CoreNode) queue = Queue() def handle_event(event: Event) -> None: assert event.session_id == session.id assert event.file_event is not None queue.put(event) # then with client.context_connect(): client.events(session.id, handle_event) time.sleep(0.1) file_data = session.services.get_service_file( node, "DefaultRoute", "defaultroute.sh" ) session.broadcast_file(file_data) # 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 streamer = MoveNodesStreamer(session.id) streamer.send_position(node.id, x, y) streamer.stop() # then with client.context_connect(): client.move_nodes(streamer) # 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 streamer = MoveNodesStreamer(session.id) streamer.send_geo(node.id, lon, lat, alt) streamer.stop() queue = Queue() def node_handler(node_data: NodeData): 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) # then with client.context_connect(): client.move_nodes(streamer) # assert assert queue.get(timeout=5) assert node.position.lon == lon assert node.position.lat == lat assert node.position.alt == alt def test_move_nodes_exception(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() streamer = MoveNodesStreamer(session.id) request = MoveNodesRequest(session.id + 1, 1) streamer.send(request) streamer.stop() # then with pytest.raises(grpc.RpcError): with client.context_connect(): client.move_nodes(streamer) def test_wlan_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) wlan = session.add_node(WlanNode) node1 = session.add_node(CoreNode) node2 = session.add_node(CoreNode) iface1_data = ip_prefixes.create_iface(node1) iface2_data = ip_prefixes.create_iface(node2) session.add_link(node1.id, wlan.id, iface1_data) session.add_link(node2.id, wlan.id, iface2_data) session.instantiate() assert len(session.link_manager.links()) == 2 # when with client.context_connect(): result1 = client.wlan_link(session.id, wlan.id, node1.id, node2.id, True) result2 = client.wlan_link(session.id, wlan.id, node1.id, node2.id, False) # then assert result1 is True assert result2 is True