From 1c970bbe00b21acbb7f1d6ef7ef52a0ad1a84501 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Mar 2021 16:54:24 -0700 Subject: [PATCH 001/110] daemon: refactoring to remove usage of os.path where possible and pathlib.Path instead --- daemon/core/api/grpc/grpcutils.py | 4 +- daemon/core/api/grpc/server.py | 37 +++--- daemon/core/api/tlv/corehandlers.py | 84 +++++------- daemon/core/configservice/base.py | 19 +-- daemon/core/configservice/manager.py | 7 +- daemon/core/constants.py.in | 8 +- daemon/core/emane/commeffect.py | 6 +- daemon/core/emane/emanemanager.py | 30 ++--- daemon/core/emane/emanemanifest.py | 8 +- daemon/core/emane/emanemodel.py | 9 +- daemon/core/emane/ieee80211abg.py | 8 +- daemon/core/emane/rfpipe.py | 8 +- daemon/core/emane/tdma.py | 28 ++-- daemon/core/emulator/coreemu.py | 11 +- daemon/core/emulator/distributed.py | 20 ++- daemon/core/emulator/session.py | 99 +++++++------- daemon/core/gui/dialogs/serviceconfig.py | 13 +- daemon/core/gui/menubar.py | 37 +++--- daemon/core/location/mobility.py | 48 +++---- daemon/core/nodes/base.py | 161 +++++++++++------------ daemon/core/nodes/client.py | 5 +- daemon/core/nodes/docker.py | 72 +++++----- daemon/core/nodes/interface.py | 3 +- daemon/core/nodes/lxd.py | 78 +++++------ daemon/core/nodes/network.py | 10 +- daemon/core/nodes/physical.py | 84 +++++------- daemon/core/services/__init__.py | 4 +- daemon/core/services/coreservices.py | 29 ++-- daemon/core/utils.py | 71 ++++------ daemon/core/xml/corexml.py | 18 +-- daemon/core/xml/emanexml.py | 20 +-- daemon/scripts/core-daemon | 4 +- daemon/tests/emane/test_emane.py | 9 +- daemon/tests/test_config_services.py | 10 +- daemon/tests/test_core.py | 12 +- daemon/tests/test_gui.py | 23 ++-- daemon/tests/test_services.py | 12 +- daemon/tests/test_xml.py | 17 +-- 38 files changed, 520 insertions(+), 606 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 93927ec6..5d5eb456 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -271,11 +271,11 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node: node_dir = None config_services = [] if isinstance(node, CoreNodeBase): - node_dir = node.nodedir + node_dir = str(node.nodedir) config_services = [x for x in node.config_services] channel = None if isinstance(node, CoreNode): - channel = node.ctrlchnlname + channel = str(node.ctrlchnlname) emane_model = None if isinstance(node, EmaneNet): emane_model = node.model.name diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 73fa2fa6..3cf56fda 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -6,6 +6,7 @@ import tempfile import threading import time from concurrent import futures +from pathlib import Path from typing import Iterable, Optional, Pattern, Type import grpc @@ -221,8 +222,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): # clear previous state and setup for creation session.clear() - if not os.path.exists(session.session_dir): - os.mkdir(session.session_dir) + session.session_dir.mkdir(exist_ok=True) session.set_state(EventTypes.CONFIGURATION_STATE) # location @@ -366,12 +366,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): sessions = [] for session_id in self.coreemu.sessions: session = self.coreemu.sessions[session_id] + session_file = str(session.file_path) if session.file_path else None session_summary = core_pb2.SessionSummary( id=session_id, state=session.state.value, nodes=session.get_node_count(), - file=session.file_name, - dir=session.session_dir, + file=session_file, + dir=str(session.session_dir), ) sessions.append(session_summary) return core_pb2.GetSessionsResponse(sessions=sessions) @@ -423,14 +424,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("set session state: %s", request) session = self.get_session(request.session_id, context) - try: state = EventTypes(request.state) session.set_state(state) - if state == EventTypes.INSTANTIATION_STATE: - if not os.path.exists(session.session_dir): - os.mkdir(session.session_dir) + session.session_dir.mkdir(exist_ok=True) session.instantiate() elif state == EventTypes.SHUTDOWN_STATE: session.shutdown() @@ -438,11 +436,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.data_collect() elif state == EventTypes.DEFINITION_STATE: session.clear() - result = True except KeyError: result = False - return core_pb2.SetSessionStateResponse(result=result) def SetSessionUser( @@ -573,12 +569,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): mobility_configs = grpcutils.get_mobility_configs(session) service_configs = grpcutils.get_node_service_configs(session) config_service_configs = grpcutils.get_node_config_service_configs(session) + session_file = str(session.file_path) if session.file_path else None session_proto = core_pb2.Session( id=session.id, state=session.state.value, nodes=nodes, links=links, - dir=session.session_dir, + dir=str(session.session_dir), user=session.user, default_services=default_services, location=location, @@ -591,7 +588,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): config_service_configs=config_service_configs, mobility_configs=mobility_configs, metadata=session.metadata, - file=session.file_name, + file=session_file, ) return core_pb2.GetSessionResponse(session=session_proto) @@ -1508,15 +1505,15 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logging.debug("open xml: %s", request) session = self.coreemu.create_session() - temp = tempfile.NamedTemporaryFile(delete=False) temp.write(request.data.encode("utf-8")) temp.close() - + temp_path = Path(temp.name) + file_path = Path(request.file) try: - session.open_xml(temp.name, request.start) - session.name = os.path.basename(request.file) - session.file_name = request.file + session.open_xml(temp_path, request.start) + session.name = file_path.name + session.file_path = file_path return core_pb2.OpenXmlResponse(session_id=session.id, result=True) except IOError: logging.exception("error opening session file") @@ -1733,12 +1730,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def ExecuteScript(self, request, context): existing_sessions = set(self.coreemu.sessions.keys()) + file_path = Path(request.script) thread = threading.Thread( target=utils.execute_file, - args=( - request.script, - {"__file__": request.script, "coreemu": self.coreemu}, - ), + args=(file_path, {"coreemu": self.coreemu}), daemon=True, ) thread.start() diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 65abed8c..37a1cdf8 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -3,7 +3,6 @@ socket server request handlers leveraged by core servers. """ import logging -import os import shlex import shutil import socketserver @@ -11,6 +10,7 @@ import sys import threading import time from itertools import repeat +from pathlib import Path from queue import Empty, Queue from typing import Optional @@ -167,39 +167,27 @@ class CoreHandler(socketserver.BaseRequestHandler): date_list = [] thumb_list = [] num_sessions = 0 - with self._sessions_lock: for _id in self.coreemu.sessions: session = self.coreemu.sessions[_id] num_sessions += 1 id_list.append(str(_id)) - name = session.name if not name: name = "" name_list.append(name) - - file_name = session.file_name - if not file_name: - file_name = "" - file_list.append(file_name) - + file_name = str(session.file_path) if session.file_path else "" + file_list.append(str(file_name)) node_count_list.append(str(session.get_node_count())) - date_list.append(time.ctime(session.state_time)) - - thumb = session.thumbnail - if not thumb: - thumb = "" + thumb = str(session.thumbnail) if session.thumbnail else "" thumb_list.append(thumb) - session_ids = "|".join(id_list) names = "|".join(name_list) files = "|".join(file_list) node_counts = "|".join(node_count_list) dates = "|".join(date_list) thumbs = "|".join(thumb_list) - if num_sessions > 0: tlv_data = b"" if len(session_ids) > 0: @@ -221,7 +209,6 @@ class CoreHandler(socketserver.BaseRequestHandler): message = coreapi.CoreSessionMessage.pack(flags, tlv_data) else: message = None - return message def handle_broadcast_event(self, event_data): @@ -931,22 +918,18 @@ class CoreHandler(socketserver.BaseRequestHandler): if message.flags & MessageFlags.STRING.value: old_session_ids = set(self.coreemu.sessions.keys()) sys.argv = shlex.split(execute_server) - file_name = sys.argv[0] - - if os.path.splitext(file_name)[1].lower() == ".xml": + file_path = Path(sys.argv[0]) + if file_path.suffix == ".xml": session = self.coreemu.create_session() try: - session.open_xml(file_name) + session.open_xml(file_path) except Exception: self.coreemu.delete_session(session.id) raise else: thread = threading.Thread( target=utils.execute_file, - args=( - file_name, - {"__file__": file_name, "coreemu": self.coreemu}, - ), + args=(file_path, {"coreemu": self.coreemu}), daemon=True, ) thread.start() @@ -1465,10 +1448,12 @@ class CoreHandler(socketserver.BaseRequestHandler): :return: reply messages """ if message.flags & MessageFlags.ADD.value: - node_num = message.get_tlv(FileTlvs.NODE.value) + node_id = message.get_tlv(FileTlvs.NODE.value) file_name = message.get_tlv(FileTlvs.NAME.value) file_type = message.get_tlv(FileTlvs.TYPE.value) - source_name = message.get_tlv(FileTlvs.SOURCE_NAME.value) + src_path = message.get_tlv(FileTlvs.SOURCE_NAME.value) + if src_path: + src_path = Path(src_path) data = message.get_tlv(FileTlvs.DATA.value) compressed_data = message.get_tlv(FileTlvs.COMPRESSED_DATA.value) @@ -1478,7 +1463,7 @@ class CoreHandler(socketserver.BaseRequestHandler): ) return () - if source_name and data: + if src_path and data: logging.warning( "ignoring invalid File message: source and data TLVs are both present" ) @@ -1490,7 +1475,7 @@ class CoreHandler(socketserver.BaseRequestHandler): if file_type.startswith("service:"): _, service_name = file_type.split(":")[:2] self.session.services.set_service_file( - node_num, service_name, file_name, data + node_id, service_name, file_name, data ) return () elif file_type.startswith("hook:"): @@ -1500,19 +1485,20 @@ class CoreHandler(socketserver.BaseRequestHandler): return () state = int(state) state = EventTypes(state) - self.session.add_hook(state, file_name, data, source_name) + self.session.add_hook(state, file_name, data, src_path) return () # writing a file to the host - if node_num is None: - if source_name is not None: - shutil.copy2(source_name, file_name) + if node_id is None: + if src_path is not None: + shutil.copy2(src_path, file_name) else: - with open(file_name, "w") as open_file: - open_file.write(data) + with file_name.open("w") as f: + f.write(data) return () - self.session.add_node_file(node_num, source_name, file_name, data) + file_path = Path(file_name) + self.session.add_node_file(node_id, src_path, file_path, data) else: raise NotImplementedError @@ -1567,26 +1553,32 @@ class CoreHandler(socketserver.BaseRequestHandler): "dropping unhandled event message for node: %s", node.name ) return () - self.session.set_state(event_type) if event_type == EventTypes.DEFINITION_STATE: + self.session.set_state(event_type) # clear all session objects in order to receive new definitions self.session.clear() + elif event_type == EventTypes.CONFIGURATION_STATE: + self.session.set_state(event_type) elif event_type == EventTypes.INSTANTIATION_STATE: + self.session.set_state(event_type) if len(self.handler_threads) > 1: # TODO: sync handler threads here before continuing time.sleep(2.0) # XXX # done receiving node/link configuration, ready to instantiate self.session.instantiate() - # after booting nodes attempt to send emulation id for nodes waiting on status + # after booting nodes attempt to send emulation id for nodes + # waiting on status for _id in self.session.nodes: self.send_node_emulation_id(_id) elif event_type == EventTypes.RUNTIME_STATE: + self.session.set_state(event_type) logging.warning("Unexpected event message: RUNTIME state received") elif event_type == EventTypes.DATACOLLECT_STATE: self.session.data_collect() elif event_type == EventTypes.SHUTDOWN_STATE: + self.session.set_state(event_type) logging.warning("Unexpected event message: SHUTDOWN state received") elif event_type in { EventTypes.START, @@ -1613,13 +1605,13 @@ class CoreHandler(socketserver.BaseRequestHandler): name, ) elif event_type == EventTypes.FILE_OPEN: - filename = event_data.name - self.session.open_xml(filename, start=False) + file_path = Path(event_data.name) + self.session.open_xml(file_path, start=False) self.send_objects() return () elif event_type == EventTypes.FILE_SAVE: - filename = event_data.name - self.session.save_xml(filename) + file_path = Path(event_data.name) + self.session.save_xml(file_path) elif event_type == EventTypes.SCHEDULED: etime = event_data.time node_id = event_data.node @@ -1733,20 +1725,16 @@ class CoreHandler(socketserver.BaseRequestHandler): session = self.session else: session = self.coreemu.sessions.get(session_id) - if session is None: logging.warning("session %s not found", session_id) continue - if names is not None: session.name = names[index] - if files is not None: - session.file_name = files[index] - + session.file_path = Path(files[index]) if thumb: + thumb = Path(thumb) session.set_thumbnail(thumb) - if user: session.set_user(user) elif ( diff --git a/daemon/core/configservice/base.py b/daemon/core/configservice/base.py index bb97e321..92371d14 100644 --- a/daemon/core/configservice/base.py +++ b/daemon/core/configservice/base.py @@ -2,8 +2,8 @@ import abc import enum import inspect import logging -import pathlib import time +from pathlib import Path from typing import Any, Dict, List from mako import exceptions @@ -46,7 +46,7 @@ class ConfigService(abc.ABC): """ self.node: CoreNode = node class_file = inspect.getfile(self.__class__) - templates_path = pathlib.Path(class_file).parent.joinpath(TEMPLATES_DIR) + templates_path = Path(class_file).parent.joinpath(TEMPLATES_DIR) self.templates: TemplateLookup = TemplateLookup(directories=templates_path) self.config: Dict[str, Configuration] = {} self.custom_templates: Dict[str, str] = {} @@ -176,9 +176,10 @@ class ConfigService(abc.ABC): :raises CoreError: when there is a failure creating a directory """ for directory in self.directories: + dir_path = Path(directory) try: - self.node.privatedir(directory) - except (CoreCommandError, ValueError): + self.node.privatedir(dir_path) + except (CoreCommandError, CoreError): raise CoreError( f"node({self.node.name}) service({self.name}) " f"failure to create service directory: {directory}" @@ -220,7 +221,7 @@ class ConfigService(abc.ABC): """ templates = {} for name in self.files: - basename = pathlib.Path(name).name + basename = Path(name).name if name in self.custom_templates: template = self.custom_templates[name] template = self.clean_text(template) @@ -240,12 +241,12 @@ class ConfigService(abc.ABC): """ data = self.data() for name in self.files: - basename = pathlib.Path(name).name + file_path = Path(name) if name in self.custom_templates: text = self.custom_templates[name] rendered = self.render_text(text, data) - elif self.templates.has_template(basename): - rendered = self.render_template(basename, data) + elif self.templates.has_template(file_path.name): + rendered = self.render_template(file_path.name, data) else: text = self.get_text_template(name) rendered = self.render_text(text, data) @@ -256,7 +257,7 @@ class ConfigService(abc.ABC): name, rendered, ) - self.node.nodefile(name, rendered) + self.node.nodefile(file_path, rendered) def run_startup(self, wait: bool) -> None: """ diff --git a/daemon/core/configservice/manager.py b/daemon/core/configservice/manager.py index 83657655..2761b1b2 100644 --- a/daemon/core/configservice/manager.py +++ b/daemon/core/configservice/manager.py @@ -1,5 +1,6 @@ import logging import pathlib +from pathlib import Path from typing import Dict, List, Type from core import utils @@ -55,10 +56,10 @@ class ConfigServiceManager: except CoreError as e: raise CoreError(f"config service({service.name}): {e}") - # make service available + # make service available self.services[name] = service - def load(self, path: str) -> List[str]: + def load(self, path: Path) -> List[str]: """ Search path provided for configurable services and add them for being managed. @@ -71,7 +72,7 @@ class ConfigServiceManager: service_errors = [] for subdir in subdirs: logging.debug("loading config services from: %s", subdir) - services = utils.load_classes(str(subdir), ConfigService) + services = utils.load_classes(subdir, ConfigService) for service in services: try: self.add(service) diff --git a/daemon/core/constants.py.in b/daemon/core/constants.py.in index cb566e40..1ade8287 100644 --- a/daemon/core/constants.py.in +++ b/daemon/core/constants.py.in @@ -1,3 +1,5 @@ -COREDPY_VERSION = "@PACKAGE_VERSION@" -CORE_CONF_DIR = "@CORE_CONF_DIR@" -CORE_DATA_DIR = "@CORE_DATA_DIR@" +from pathlib import Path + +COREDPY_VERSION: str = "@PACKAGE_VERSION@" +CORE_CONF_DIR: Path = Path("@CORE_CONF_DIR@") +CORE_DATA_DIR: Path = Path("@CORE_DATA_DIR@") diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 13ec53f7..727d2faa 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -3,7 +3,7 @@ commeffect.py: EMANE CommEffect model for CORE """ import logging -import os +from pathlib import Path from typing import Dict, List from lxml import etree @@ -48,8 +48,8 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): external_config: List[Configuration] = [] @classmethod - def load(cls, emane_prefix: str) -> None: - shim_xml_path = os.path.join(emane_prefix, "share/emane/manifest", cls.shim_xml) + def load(cls, emane_prefix: Path) -> None: + shim_xml_path = emane_prefix / "share/emane/manifest" / cls.shim_xml cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults) @classmethod diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 6ae66b93..bd6089e9 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -8,6 +8,7 @@ import threading from collections import OrderedDict from dataclasses import dataclass, field from enum import Enum +from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type from core import utils @@ -175,17 +176,15 @@ class EmaneManager(ModelManager): 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: + if custom_models_path is not None: + custom_models_path = Path(custom_models_path) emane_models = utils.load_classes(custom_models_path, EmaneModel) self.load_models(emane_models) @@ -246,6 +245,7 @@ class EmaneManager(ModelManager): emane_prefix = self.session.options.get_config( "emane_prefix", default=DEFAULT_EMANE_PREFIX ) + emane_prefix = Path(emane_prefix) emane_model.load(emane_prefix) self.models[emane_model.name] = emane_model @@ -398,9 +398,9 @@ class EmaneManager(ModelManager): 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") + path = self.session.session_dir / "emane_nems" try: - with open(path, "a") as f: + with path.open("a") as f: f.write(f"{iface.node.name} {iface.name} {nem_id}\n") except IOError: logging.exception("error writing to emane nem file") @@ -590,18 +590,17 @@ class EmaneManager(ModelManager): if eventservicenetidx >= 0 and eventgroup != otagroup: node.node_net_client.create_route(eventgroup, eventdev) # start emane - log_file = os.path.join(node.nodedir, f"{node.name}-emane.log") - platform_xml = os.path.join(node.nodedir, f"{node.name}-platform.xml") + log_file = node.nodedir / f"{node.name}-emane.log" + platform_xml = node.nodedir / f"{node.name}-platform.xml" args = f"{emanecmd} -f {log_file} {platform_xml}" node.cmd(args) logging.info("node(%s) emane daemon running: %s", node.name, args) else: - path = self.session.session_dir - log_file = os.path.join(path, f"{node.name}-emane.log") - platform_xml = os.path.join(path, f"{node.name}-platform.xml") - emanecmd += f" -f {log_file} {platform_xml}" - node.host_cmd(emanecmd, cwd=path) - logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd) + log_file = self.session.session_dir / f"{node.name}-emane.log" + platform_xml = self.session.session_dir / f"{node.name}-platform.xml" + args = f"{emanecmd} -f {log_file} {platform_xml}" + node.host_cmd(args, cwd=self.session.session_dir) + logging.info("node(%s) host emane daemon running: %s", node.name, args) def install_iface(self, iface: CoreInterface) -> None: emane_net = iface.net @@ -869,7 +868,8 @@ class EmaneGlobalModel: emane_prefix = self.session.options.get_config( "emane_prefix", default=DEFAULT_EMANE_PREFIX ) - emulator_xml = os.path.join(emane_prefix, "share/emane/manifest/nemmanager.xml") + emane_prefix = Path(emane_prefix) + emulator_xml = emane_prefix / "share/emane/manifest/nemmanager.xml" emulator_defaults = { "eventservicedevice": DEFAULT_DEV, "eventservicegroup": "224.1.2.8:45703", diff --git a/daemon/core/emane/emanemanifest.py b/daemon/core/emane/emanemanifest.py index 41dc7beb..8e09d040 100644 --- a/daemon/core/emane/emanemanifest.py +++ b/daemon/core/emane/emanemanifest.py @@ -1,4 +1,5 @@ import logging +from pathlib import Path from typing import Dict, List from core.config import Configuration @@ -71,9 +72,10 @@ def _get_default(config_type_name: str, config_value: List[str]) -> str: return config_default -def parse(manifest_path: str, defaults: Dict[str, str]) -> List[Configuration]: +def parse(manifest_path: Path, defaults: Dict[str, str]) -> List[Configuration]: """ - Parses a valid emane manifest file and converts the provided configuration values into ones used by core. + Parses a valid emane manifest file and converts the provided configuration values + into ones used by core. :param manifest_path: absolute manifest file path :param defaults: used to override default values for configurations @@ -85,7 +87,7 @@ def parse(manifest_path: str, defaults: Dict[str, str]) -> List[Configuration]: return [] # load configuration file - manifest_file = manifest.Manifest(manifest_path) + manifest_file = manifest.Manifest(str(manifest_path)) manifest_configurations = manifest_file.getAllConfiguration() configurations = [] diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 755f07aa..565096bb 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -2,7 +2,7 @@ Defines Emane Models used within CORE. """ import logging -import os +from pathlib import Path from typing import Dict, List, Optional, Set from core.config import ConfigGroup, Configuration @@ -53,7 +53,7 @@ class EmaneModel(WirelessModel): config_ignore: Set[str] = set() @classmethod - def load(cls, emane_prefix: str) -> None: + def load(cls, emane_prefix: Path) -> None: """ Called after being loaded within the EmaneManager. Provides configured emane_prefix for parsing xml files. @@ -63,11 +63,10 @@ class EmaneModel(WirelessModel): """ manifest_path = "share/emane/manifest" # load mac configuration - mac_xml_path = os.path.join(emane_prefix, manifest_path, cls.mac_xml) + mac_xml_path = emane_prefix / manifest_path / cls.mac_xml cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults) - # load phy configuration - phy_xml_path = os.path.join(emane_prefix, manifest_path, cls.phy_xml) + phy_xml_path = emane_prefix / manifest_path / cls.phy_xml cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults) @classmethod diff --git a/daemon/core/emane/ieee80211abg.py b/daemon/core/emane/ieee80211abg.py index 0d58ec9e..f6b32264 100644 --- a/daemon/core/emane/ieee80211abg.py +++ b/daemon/core/emane/ieee80211abg.py @@ -1,7 +1,7 @@ """ ieee80211abg.py: EMANE IEEE 802.11abg model for CORE """ -import os +from pathlib import Path from core.emane import emanemodel @@ -15,8 +15,8 @@ class EmaneIeee80211abgModel(emanemodel.EmaneModel): mac_xml: str = "ieee80211abgmaclayer.xml" @classmethod - def load(cls, emane_prefix: str) -> None: - cls.mac_defaults["pcrcurveuri"] = os.path.join( - emane_prefix, "share/emane/xml/models/mac/ieee80211abg/ieee80211pcr.xml" + def load(cls, emane_prefix: Path) -> None: + cls.mac_defaults["pcrcurveuri"] = str( + emane_prefix / "share/emane/xml/models/mac/ieee80211abg/ieee80211pcr.xml" ) super().load(emane_prefix) diff --git a/daemon/core/emane/rfpipe.py b/daemon/core/emane/rfpipe.py index 068ef800..7dace8c7 100644 --- a/daemon/core/emane/rfpipe.py +++ b/daemon/core/emane/rfpipe.py @@ -1,7 +1,7 @@ """ rfpipe.py: EMANE RF-PIPE model for CORE """ -import os +from pathlib import Path from core.emane import emanemodel @@ -15,8 +15,8 @@ class EmaneRfPipeModel(emanemodel.EmaneModel): mac_xml: str = "rfpipemaclayer.xml" @classmethod - def load(cls, emane_prefix: str) -> None: - cls.mac_defaults["pcrcurveuri"] = os.path.join( - emane_prefix, "share/emane/xml/models/mac/rfpipe/rfpipepcr.xml" + def load(cls, emane_prefix: Path) -> None: + cls.mac_defaults["pcrcurveuri"] = str( + emane_prefix / "share/emane/xml/models/mac/rfpipe/rfpipepcr.xml" ) super().load(emane_prefix) diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py index ee80f3d7..1ddb14ad 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/tdma.py @@ -3,7 +3,7 @@ tdma.py: EMANE TDMA model bindings for CORE """ import logging -import os +from pathlib import Path from typing import Set from core import constants, utils @@ -22,27 +22,25 @@ class EmaneTdmaModel(emanemodel.EmaneModel): # add custom schedule options and ignore it when writing emane xml schedule_name: str = "schedule" - default_schedule: str = os.path.join( - constants.CORE_DATA_DIR, "examples", "tdma", "schedule.xml" + default_schedule: Path = ( + constants.CORE_DATA_DIR / "examples" / "tdma" / "schedule.xml" ) config_ignore: Set[str] = {schedule_name} @classmethod - def load(cls, emane_prefix: str) -> None: - cls.mac_defaults["pcrcurveuri"] = os.path.join( - emane_prefix, - "share/emane/xml/models/mac/tdmaeventscheduler/tdmabasemodelpcr.xml", + def load(cls, emane_prefix: Path) -> None: + cls.mac_defaults["pcrcurveuri"] = str( + emane_prefix + / "share/emane/xml/models/mac/tdmaeventscheduler/tdmabasemodelpcr.xml" ) super().load(emane_prefix) - cls.mac_config.insert( - 0, - Configuration( - _id=cls.schedule_name, - _type=ConfigDataTypes.STRING, - default=cls.default_schedule, - label="TDMA schedule file (core)", - ), + config_item = Configuration( + _id=cls.schedule_name, + _type=ConfigDataTypes.STRING, + default=str(cls.default_schedule), + label="TDMA schedule file (core)", ) + cls.mac_config.insert(0, config_item) def post_startup(self) -> None: """ diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 885fb431..83e6a940 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -3,6 +3,7 @@ import logging import os import signal import sys +from pathlib import Path from typing import Dict, List, Type import core.services @@ -60,10 +61,11 @@ class CoreEmu: # config services self.service_manager: ConfigServiceManager = ConfigServiceManager() - config_services_path = os.path.abspath(os.path.dirname(configservices.__file__)) + config_services_path = Path(configservices.__file__).resolve().parent self.service_manager.load(config_services_path) custom_dir = self.config.get("custom_config_services_dir") - if custom_dir: + if custom_dir is not None: + custom_dir = Path(custom_dir) self.service_manager.load(custom_dir) # check executables exist on path @@ -91,13 +93,12 @@ class CoreEmu: """ # load default services self.service_errors = core.services.load() - # load custom services service_paths = self.config.get("custom_services_dir") logging.debug("custom service paths: %s", service_paths) - if service_paths: + if service_paths is not None: for service_path in service_paths.split(","): - service_path = service_path.strip() + service_path = Path(service_path.strip()) custom_service_errors = ServiceManager.add_services(service_path) self.service_errors.extend(custom_service_errors) diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index a5e1009f..0731b9af 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -6,6 +6,7 @@ import logging import os import threading from collections import OrderedDict +from pathlib import Path from tempfile import NamedTemporaryFile from typing import TYPE_CHECKING, Callable, Dict, Tuple @@ -79,23 +80,23 @@ class DistributedServer: stdout, stderr = e.streams_for_display() raise CoreCommandError(e.result.exited, cmd, stdout, stderr) - def remote_put(self, source: str, destination: str) -> None: + def remote_put(self, src_path: Path, dst_path: Path) -> None: """ Push file to remote server. - :param source: source file to push - :param destination: destination file location + :param src_path: source file to push + :param dst_path: destination file location :return: nothing """ with self.lock: - self.conn.put(source, destination) + self.conn.put(str(src_path), str(dst_path)) - def remote_put_temp(self, destination: str, data: str) -> None: + def remote_put_temp(self, dst_path: Path, data: str) -> None: """ Remote push file contents to a remote server, using a temp file as an intermediate step. - :param destination: file destination for data + :param dst_path: file destination for data :param data: data to store in remote file :return: nothing """ @@ -103,7 +104,7 @@ class DistributedServer: temp = NamedTemporaryFile(delete=False) temp.write(data.encode("utf-8")) temp.close() - self.conn.put(temp.name, destination) + self.conn.put(temp.name, str(dst_path)) os.unlink(temp.name) @@ -170,13 +171,11 @@ class DistributedController: tunnels = self.tunnels[key] for tunnel in tunnels: tunnel.shutdown() - # remove all remote session directories for name in self.servers: server = self.servers[name] cmd = f"rm -rf {self.session.session_dir}" server.remote_cmd(cmd) - # clear tunnels self.tunnels.clear() @@ -212,14 +211,12 @@ class DistributedController: tunnel = self.tunnels.get(key) if tunnel is not None: return tunnel - # local to server logging.info( "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_iface_master(node.brname, local_tap.localname) - # server to local logging.info( "remote tunnel node(%s) to local(%s) key(%s)", node.name, self.address, key @@ -228,7 +225,6 @@ class DistributedController: session=self.session, remoteip=self.address, key=key, server=server ) remote_tap.net_client.set_iface_master(node.brname, remote_tap.localname) - # save tunnels for shutdown tunnel = (local_tap, remote_tap) self.tunnels[key] = tunnel diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index cb5f3722..fbc91907 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -103,13 +103,13 @@ class Session: self.id: int = _id # define and create session directory when desired - self.session_dir: str = os.path.join(tempfile.gettempdir(), f"pycore.{self.id}") + self.session_dir: Path = Path(tempfile.gettempdir()) / f"pycore.{self.id}" if mkdir: - os.mkdir(self.session_dir) + self.session_dir.mkdir() self.name: Optional[str] = None - self.file_name: Optional[str] = None - self.thumbnail: Optional[str] = None + self.file_path: Optional[Path] = None + self.thumbnail: Optional[Path] = None self.user: Optional[str] = None self.event_loop: EventLoop = EventLoop() self.link_colors: Dict[int, str] = {} @@ -641,42 +641,39 @@ class Session: logging.info("session(%s) checking if active: %s", self.id, result) return result - def open_xml(self, file_name: str, start: bool = False) -> None: + def open_xml(self, file_path: Path, start: bool = False) -> None: """ Import a session from the EmulationScript XML format. - :param file_name: xml file to load session from + :param file_path: xml file to load session from :param start: instantiate session if true, false otherwise :return: nothing """ - logging.info("opening xml: %s", file_name) - + logging.info("opening xml: %s", file_path) # clear out existing session self.clear() - # 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 - CoreXmlReader(self).read(file_name) - + self.name = file_path.name + self.file_path = file_path + CoreXmlReader(self).read(file_path) # start session if needed if start: self.set_state(EventTypes.INSTANTIATION_STATE) self.instantiate() - def save_xml(self, file_name: str) -> None: + def save_xml(self, file_path: Path) -> None: """ Export a session to the EmulationScript XML format. - :param file_name: file name to write session xml to + :param file_path: file name to write session xml to :return: nothing """ - CoreXmlWriter(self).write(file_name) + CoreXmlWriter(self).write(file_path) def add_hook( - self, state: EventTypes, file_name: str, data: str, source_name: str = None + self, state: EventTypes, file_name: str, data: str, src_name: str = None ) -> None: """ Store a hook from a received file message. @@ -684,11 +681,11 @@ class Session: :param state: when to run hook :param file_name: file name for hook :param data: hook data - :param source_name: source name + :param src_name: source name :return: nothing """ logging.info( - "setting state hook: %s - %s source(%s)", state, file_name, source_name + "setting state hook: %s - %s source(%s)", state, file_name, src_name ) hook = file_name, data state_hooks = self.hooks.setdefault(state, []) @@ -700,22 +697,22 @@ class Session: self.run_hook(hook) def add_node_file( - self, node_id: int, source_name: str, file_name: str, data: str + self, node_id: int, src_path: Path, file_path: Path, data: str ) -> None: """ Add a file to a node. :param node_id: node to add file to - :param source_name: source file name - :param file_name: file name to add + :param src_path: source file path + :param file_path: file path to add :param data: file data :return: nothing """ node = self.get_node(node_id, CoreNodeBase) - if source_name is not None: - node.addfile(source_name, file_name) + if src_path is not None: + node.addfile(src_path, file_path) elif data is not None: - node.nodefile(file_name, data) + node.nodefile(file_path, data) def clear(self) -> None: """ @@ -879,9 +876,9 @@ class Session: :param state: state to write to file :return: nothing """ - state_file = os.path.join(self.session_dir, "state") + state_file = self.session_dir / "state" try: - with open(state_file, "w") as f: + with state_file.open("w") as f: f.write(f"{state.value} {state.name}\n") except IOError: logging.exception("error writing state file: %s", state.name) @@ -907,12 +904,12 @@ class Session: """ file_name, data = hook logging.info("running hook %s", file_name) - file_path = os.path.join(self.session_dir, file_name) - log_path = os.path.join(self.session_dir, f"{file_name}.log") + file_path = self.session_dir / file_name + log_path = self.session_dir / f"{file_name}.log" try: - with open(file_path, "w") as f: + with file_path.open("w") as f: f.write(data) - with open(log_path, "w") as f: + with log_path.open("w") as f: args = ["/bin/sh", file_name] subprocess.check_call( args, @@ -983,10 +980,10 @@ class Session: """ 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) + xml_file_path = self.session_dir / "session-deployed.xml" + xml_writer.write(xml_file_path) def get_environment(self, state: bool = True) -> Dict[str, str]: """ @@ -1001,9 +998,9 @@ class Session: env["CORE_PYTHON"] = sys.executable env["SESSION"] = str(self.id) env["SESSION_SHORT"] = self.short_session_id() - env["SESSION_DIR"] = self.session_dir + env["SESSION_DIR"] = str(self.session_dir) env["SESSION_NAME"] = str(self.name) - env["SESSION_FILENAME"] = str(self.file_name) + env["SESSION_FILENAME"] = str(self.file_path) env["SESSION_USER"] = str(self.user) if state: env["SESSION_STATE"] = str(self.state) @@ -1011,8 +1008,8 @@ class Session: # /etc/core/environment # /home/user/.core/environment # /tmp/pycore./environment - core_env_path = Path(constants.CORE_CONF_DIR) / "environment" - session_env_path = Path(self.session_dir) / "environment" + core_env_path = constants.CORE_CONF_DIR / "environment" + session_env_path = self.session_dir / "environment" if self.user: user_home_path = Path(f"~{self.user}").expanduser() user_env1 = user_home_path / ".core" / "environment" @@ -1028,20 +1025,20 @@ class Session: logging.exception("error reading environment file: %s", path) return env - def set_thumbnail(self, thumb_file: str) -> None: + def set_thumbnail(self, thumb_file: Path) -> None: """ Set the thumbnail filename. Move files from /tmp to session dir. :param thumb_file: tumbnail file to set for session :return: nothing """ - if not os.path.exists(thumb_file): + if not thumb_file.is_file(): 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 + dst_path = self.session_dir / thumb_file.name + shutil.copy(thumb_file, dst_path) + self.thumbnail = dst_path def set_user(self, user: str) -> None: """ @@ -1054,7 +1051,7 @@ class Session: if user: try: uid = pwd.getpwnam(user).pw_uid - gid = os.stat(self.session_dir).st_gid + gid = self.session_dir.stat().st_gid os.chown(self.session_dir, uid, gid) except IOError: logging.exception("failed to set permission on %s", self.session_dir) @@ -1140,10 +1137,10 @@ 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") + file_path = self.session_dir / "nodes" try: with self.nodes_lock: - with open(file_path, "w") as f: + with file_path.open("w") as f: for _id, node in self.nodes.items(): f.write(f"{_id} {node.name} {node.apitype} {type(node)}\n") except IOError: @@ -1268,15 +1265,13 @@ class Session: # stop event loop self.event_loop.stop() - # stop node services + # stop mobility and node services with self.nodes_lock: funcs = [] - for node_id in self.nodes: - node = self.nodes[node_id] - if not isinstance(node, CoreNodeBase) or not node.up: - continue - args = (node,) - funcs.append((self.services.stop_services, args, {})) + for node in self.nodes.values(): + if isinstance(node, CoreNodeBase) and node.up: + args = (node,) + funcs.append((self.services.stop_services, args, {})) utils.threadpool(funcs) # shutdown emane diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 2f788dce..65a9e353 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -1,6 +1,6 @@ import logging -import os import tkinter as tk +from pathlib import Path from tkinter import filedialog, ttk from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple @@ -579,11 +579,12 @@ class ServiceConfigDialog(Dialog): self.directory_entry.insert("end", d) 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) + directory = self.directory_entry.get() + directory = Path(directory) + if directory.is_dir(): + if str(directory) not in self.temp_directories: + self.dir_list.listbox.insert("end", directory) + self.temp_directories.append(directory) def remove_directory(self) -> None: d = self.directory_entry.get() diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index c7764f1a..60df3a8e 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -1,8 +1,8 @@ import logging -import os import tkinter as tk import webbrowser from functools import partial +from pathlib import Path from tkinter import filedialog, messagebox from typing import TYPE_CHECKING, Optional @@ -272,12 +272,12 @@ class Menubar(tk.Menu): menu.add_command(label="About", command=self.click_about) self.add_cascade(label="Help", menu=menu) - def open_recent_files(self, filename: str) -> None: - if os.path.isfile(filename): - logging.debug("Open recent file %s", filename) - self.open_xml_task(filename) + def open_recent_files(self, file_path: Path) -> None: + if file_path.is_file(): + logging.debug("Open recent file %s", file_path) + self.open_xml_task(file_path) else: - logging.warning("File does not exist %s", filename) + logging.warning("File does not exist %s", file_path) def update_recent_files(self) -> None: self.recent_menu.delete(0, tk.END) @@ -286,7 +286,7 @@ class Menubar(tk.Menu): label=i, command=partial(self.open_recent_files, i) ) - def click_save(self, _event=None) -> None: + def click_save(self, _event: tk.Event = None) -> None: if self.core.session.file: self.core.save_xml() else: @@ -314,7 +314,7 @@ class Menubar(tk.Menu): if file_path: self.open_xml_task(file_path) - def open_xml_task(self, file_path: str) -> None: + def open_xml_task(self, file_path: Path) -> None: self.add_recent_file_to_gui_config(file_path) self.prompt_save_running_session() task = ProgressTask(self.app, "Open XML", self.core.open_xml, args=(file_path,)) @@ -324,21 +324,14 @@ class Menubar(tk.Menu): dialog = ExecutePythonDialog(self.app) dialog.show() - def add_recent_file_to_gui_config(self, file_path) -> None: + def add_recent_file_to_gui_config(self, file_path: Path) -> None: recent_files = self.app.guiconfig.recentfiles - num_files = len(recent_files) - if num_files == 0: - recent_files.insert(0, file_path) - elif 0 < num_files <= MAX_FILES: - if file_path in recent_files: - recent_files.remove(file_path) - recent_files.insert(0, file_path) - else: - if num_files == MAX_FILES: - recent_files.pop() - recent_files.insert(0, file_path) - else: - logging.error("unexpected number of recent files") + file_path = str(file_path) + if file_path in recent_files: + recent_files.remove(file_path) + recent_files.insert(0, file_path) + if len(recent_files) > MAX_FILES: + recent_files.pop() self.app.save_config() self.app.menubar.update_recent_files() diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 688910c5..d4e7ee38 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -419,7 +419,7 @@ class BasicRangeModel(WirelessModel): self.wlan.link(a, b) self.sendlinkmsg(a, b) except KeyError: - logging.exception("error getting interfaces during calclinkS") + logging.exception("error getting interfaces during calclink") @staticmethod def calcdistance( @@ -920,7 +920,7 @@ class Ns2ScriptedMobility(WayPointMobility): :param _id: object id """ super().__init__(session, _id) - self.file: Optional[str] = None + self.file: Optional[Path] = None self.autostart: Optional[str] = None self.nodemap: Dict[int, int] = {} self.script_start: Optional[str] = None @@ -928,7 +928,7 @@ class Ns2ScriptedMobility(WayPointMobility): self.script_stop: Optional[str] = None def update_config(self, config: Dict[str, str]) -> None: - self.file = config["file"] + self.file = Path(config["file"]) logging.info( "ns-2 scripted mobility configured for WLAN %d using file: %s", self.id, @@ -953,15 +953,15 @@ class Ns2ScriptedMobility(WayPointMobility): :return: nothing """ - filename = self.findfile(self.file) + file_path = self.findfile(self.file) try: - f = open(filename, "r") + f = file_path.open("r") except IOError: logging.exception( "ns-2 scripted mobility failed to load file: %s", self.file ) return - logging.info("reading ns-2 script file: %s", filename) + logging.info("reading ns-2 script file: %s", file_path) ln = 0 ix = iy = iz = None inodenum = None @@ -977,13 +977,13 @@ class Ns2ScriptedMobility(WayPointMobility): # waypoints: # $ns_ at 1.00 "$node_(6) setdest 500.0 178.0 25.0" parts = line.split() - time = float(parts[2]) + line_time = float(parts[2]) nodenum = parts[3][1 + parts[3].index("(") : parts[3].index(")")] x = float(parts[5]) y = float(parts[6]) z = None speed = float(parts[7].strip('"')) - self.addwaypoint(time, self.map(nodenum), x, y, z, speed) + self.addwaypoint(line_time, self.map(nodenum), x, y, z, speed) elif line[:7] == "$node_(": # initial position (time=0, speed=0): # $node_(6) set X_ 780.0 @@ -1011,31 +1011,31 @@ class Ns2ScriptedMobility(WayPointMobility): if ix is not None and iy is not None: self.addinitial(self.map(inodenum), ix, iy, iz) - def findfile(self, file_name: str) -> str: + def findfile(self, file_path: Path) -> Path: """ Locate a script file. If the specified file doesn't exist, look in the same directory as the scenario file, or in gui directories. - :param file_name: file name to find + :param file_path: file name to find :return: absolute path to the file :raises CoreError: when file is not found """ - file_path = Path(file_name).expanduser() + file_path = file_path.expanduser() if file_path.exists(): - return str(file_path) - if self.session.file_name: - file_path = Path(self.session.file_name).parent / file_name - if file_path.exists(): - return str(file_path) + return file_path + if self.session.file_path: + session_file_path = self.session.file_path.parent / file_path + if session_file_path.exists(): + return session_file_path if self.session.user: user_path = Path(f"~{self.session.user}").expanduser() - file_path = user_path / ".core" / "configs" / file_name - if file_path.exists(): - return str(file_path) - file_path = user_path / ".coregui" / "mobility" / file_name - if file_path.exists(): - return str(file_path) - raise CoreError(f"invalid file: {file_name}") + configs_path = user_path / ".core" / "configs" / file_path + if configs_path.exists(): + return configs_path + mobility_path = user_path / ".coregui" / "mobility" / file_path + if mobility_path.exists(): + return mobility_path + raise CoreError(f"invalid file: {file_path}") def parsemap(self, mapstr: str) -> None: """ @@ -1047,7 +1047,6 @@ class Ns2ScriptedMobility(WayPointMobility): self.nodemap = {} if mapstr.strip() == "": return - for pair in mapstr.split(","): parts = pair.split(":") try: @@ -1152,6 +1151,7 @@ class Ns2ScriptedMobility(WayPointMobility): filename = self.script_stop if filename is None or filename == "": return + filename = Path(filename) filename = self.findfile(filename) args = f"{BASH} {filename} {typestr}" utils.cmd( diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 0a952e04..0af4d5ae 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -3,9 +3,9 @@ Defines the base logic for nodes used within core. """ import abc import logging -import os import shutil import threading +from pathlib import Path from threading import RLock from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union @@ -30,6 +30,8 @@ if TYPE_CHECKING: CoreServices = List[Union[CoreService, Type[CoreService]]] ConfigServiceType = Type[ConfigService] +PRIVATE_DIRS: List[Path] = [Path("/var/run"), Path("/var/log")] + class NodeBase(abc.ABC): """ @@ -97,7 +99,7 @@ class NodeBase(abc.ABC): self, args: str, env: Dict[str, str] = None, - cwd: str = None, + cwd: Path = None, wait: bool = True, shell: bool = False, ) -> str: @@ -221,7 +223,7 @@ class CoreNodeBase(NodeBase): """ super().__init__(session, _id, name, server) self.config_services: Dict[str, "ConfigService"] = {} - self.nodedir: Optional[str] = None + self.nodedir: Optional[Path] = None self.tmpnodedir: bool = False @abc.abstractmethod @@ -233,11 +235,11 @@ class CoreNodeBase(NodeBase): raise NotImplementedError @abc.abstractmethod - def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: + def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. - :param filename: name of file to create + :param file_path: name of file to create :param contents: contents of file :param mode: mode for file :return: nothing @@ -245,12 +247,12 @@ class CoreNodeBase(NodeBase): raise NotImplementedError @abc.abstractmethod - def addfile(self, srcname: str, filename: str) -> None: + def addfile(self, src_path: Path, file_path: Path) -> None: """ Add a file. - :param srcname: source file name - :param filename: file name to add + :param src_path: source file path + :param file_path: file name to add :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ @@ -302,6 +304,21 @@ class CoreNodeBase(NodeBase): """ raise NotImplementedError + def host_path(self, path: Path, is_dir: bool = False) -> Path: + """ + Return the name of a node"s file on the host filesystem. + + :param path: path to translate to host path + :param is_dir: True if path is a directory path, False otherwise + :return: path to file + """ + if is_dir: + directory = str(path).strip("/").replace("/", ".") + return self.nodedir / directory + else: + directory = str(path.parent).strip("/").replace("/", ".") + return self.nodedir / directory / path.name + def add_config_service(self, service_class: "ConfigServiceType") -> None: """ Adds a configuration service to the node. @@ -346,7 +363,7 @@ class CoreNodeBase(NodeBase): :return: nothing """ if self.nodedir is None: - self.nodedir = os.path.join(self.session.session_dir, self.name + ".conf") + self.nodedir = self.session.session_dir / f"{self.name}.conf" self.host_cmd(f"mkdir -p {self.nodedir}") self.tmpnodedir = True else: @@ -458,7 +475,7 @@ class CoreNode(CoreNodeBase): session: "Session", _id: int = None, name: str = None, - nodedir: str = None, + nodedir: Path = None, server: "DistributedServer" = None, ) -> None: """ @@ -472,14 +489,12 @@ class CoreNode(CoreNodeBase): will run on, default is None for localhost """ 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) - ) + self.nodedir: Optional[Path] = nodedir + self.ctrlchnlname: Path = self.session.session_dir / self.name self.client: Optional[VnodeClient] = None self.pid: Optional[int] = None self.lock: RLock = RLock() - self._mounts: List[Tuple[str, str]] = [] + self._mounts: List[Tuple[Path, Path]] = [] self.node_net_client: LinuxNetClient = self.create_node_net_client( self.session.use_ovs() ) @@ -549,8 +564,8 @@ class CoreNode(CoreNodeBase): self.up = True # create private directories - self.privatedir("/var/run") - self.privatedir("/var/log") + for dir_path in PRIVATE_DIRS: + self.privatedir(dir_path) def shutdown(self) -> None: """ @@ -561,29 +576,24 @@ class CoreNode(CoreNodeBase): # nothing to do if node is not up if not self.up: return - with self.lock: try: # unmount all targets (NOTE: non-persistent mount namespaces are # removed by the kernel when last referencing process is killed) self._mounts = [] - # shutdown all interfaces for iface in self.get_ifaces(): iface.shutdown() - # kill node process if present try: self.host_cmd(f"kill -9 {self.pid}") except CoreCommandError: logging.exception("error killing process") - # remove node directory if present try: self.host_cmd(f"rm -rf {self.ctrlchnlname}") except CoreCommandError: logging.exception("error removing node directory") - # clear interface data, close client, and mark self and not up self.ifaces.clear() self.client.close() @@ -636,35 +646,32 @@ class CoreNode(CoreNodeBase): else: return f"ssh -X -f {self.server.host} xterm -e {terminal}" - def privatedir(self, path: str) -> None: + def privatedir(self, dir_path: Path) -> None: """ Create a private directory. - :param path: path to create + :param dir_path: path to create :return: nothing """ - if path[0] != "/": - raise ValueError(f"path not fully qualified: {path}") - hostpath = os.path.join( - self.nodedir, os.path.normpath(path).strip("/").replace("/", ".") - ) - self.host_cmd(f"mkdir -p {hostpath}") - self.mount(hostpath, path) + if not str(dir_path).startswith("/"): + raise CoreError(f"private directory path not fully qualified: {dir_path}") + host_path = self.host_path(dir_path, is_dir=True) + self.host_cmd(f"mkdir -p {host_path}") + self.mount(host_path, dir_path) - def mount(self, source: str, target: str) -> None: + def mount(self, src_path: Path, target_path: Path) -> None: """ Create and mount a directory. - :param source: source directory to mount - :param target: target directory to create + :param src_path: source directory to mount + :param target_path: target directory to create :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - 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} -n --bind {source} {target}") - self._mounts.append((source, target)) + logging.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path) + self.cmd(f"mkdir -p {target_path}") + self.cmd(f"{MOUNT} -n --bind {src_path} {target_path}") + self._mounts.append((src_path, target_path)) def next_iface_id(self) -> int: """ @@ -851,86 +858,66 @@ class CoreNode(CoreNodeBase): self.ifup(iface_id) return self.get_iface(iface_id) - def addfile(self, srcname: str, filename: str) -> None: + def addfile(self, src_path: Path, file_path: Path) -> None: """ Add a file. - :param srcname: source file name - :param filename: file name to add + :param src_path: source file path + :param file_path: file name to add :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - logging.info("adding file from %s to %s", srcname, filename) - directory = os.path.dirname(filename) + logging.info("adding file from %s to %s", src_path, file_path) + directory = file_path.parent if self.server is None: self.client.check_cmd(f"mkdir -p {directory}") - self.client.check_cmd(f"mv {srcname} {filename}") + self.client.check_cmd(f"mv {src_path} {file_path}") self.client.check_cmd("sync") else: self.host_cmd(f"mkdir -p {directory}") - self.server.remote_put(srcname, filename) + self.server.remote_put(src_path, file_path) - def hostfilename(self, filename: str) -> str: - """ - Return the name of a node"s file on the host filesystem. - - :param filename: host file name - :return: path to file - """ - dirname, basename = os.path.split(filename) - if not basename: - raise ValueError(f"no basename for filename: {filename}") - if dirname and dirname[0] == "/": - dirname = dirname[1:] - dirname = dirname.replace("/", ".") - dirname = os.path.join(self.nodedir, dirname) - return os.path.join(dirname, basename) - - def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: + def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. - :param filename: name of file to create + :param file_path: name of file to create :param contents: contents of file :param mode: mode for file :return: nothing """ - hostfilename = self.hostfilename(filename) - dirname, _basename = os.path.split(hostfilename) + host_path = self.host_path(file_path) + directory = host_path.parent if self.server is None: - if not os.path.isdir(dirname): - os.makedirs(dirname, mode=0o755) - with open(hostfilename, "w") as open_file: - open_file.write(contents) - os.chmod(open_file.name, mode) + if not directory.exists(): + directory.mkdir(parents=True, mode=0o755) + with host_path.open("w") as f: + f.write(contents) + host_path.chmod(mode) else: - self.host_cmd(f"mkdir -m {0o755:o} -p {dirname}") - self.server.remote_put_temp(hostfilename, contents) - self.host_cmd(f"chmod {mode:o} {hostfilename}") - logging.debug( - "node(%s) added file: %s; mode: 0%o", self.name, hostfilename, mode - ) + self.host_cmd(f"mkdir -m {0o755:o} -p {directory}") + self.server.remote_put_temp(host_path, contents) + self.host_cmd(f"chmod {mode:o} {host_path}") + logging.debug("node(%s) added file: %s; mode: 0%o", self.name, host_path, mode) - def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None: + def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None: """ Copy a file to a node, following symlinks and preserving metadata. Change file mode if specified. - :param filename: file name to copy file to - :param srcfilename: file to copy + :param file_path: file name to copy file to + :param src_path: file to copy :param mode: mode to copy to :return: nothing """ - hostfilename = self.hostfilename(filename) + host_path = self.host_path(file_path) if self.server is None: - shutil.copy2(srcfilename, hostfilename) + shutil.copy2(src_path, host_path) else: - self.server.remote_put(srcfilename, hostfilename) + self.server.remote_put(src_path, host_path) if mode is not None: - self.host_cmd(f"chmod {mode:o} {hostfilename}") - logging.info( - "node(%s) copied file: %s; mode: %s", self.name, hostfilename, mode - ) + self.host_cmd(f"chmod {mode:o} {host_path}") + logging.info("node(%s) copied file: %s; mode: %s", self.name, host_path, mode) class CoreNetworkBase(NodeBase): diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index 710724b1..c3afb907 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -3,6 +3,7 @@ client.py: implementation of the VnodeClient class for issuing commands over a control channel to the vnoded process running in a network namespace. The control channel can be accessed via calls using the vcmd shell. """ +from pathlib import Path from core import utils from core.executables import BASH, VCMD @@ -13,7 +14,7 @@ class VnodeClient: Provides client functionality for interacting with a virtual node. """ - def __init__(self, name: str, ctrlchnlname: str) -> None: + def __init__(self, name: str, ctrlchnlname: Path) -> None: """ Create a VnodeClient instance. @@ -21,7 +22,7 @@ class VnodeClient: :param ctrlchnlname: control channel name """ self.name: str = name - self.ctrlchnlname: str = ctrlchnlname + self.ctrlchnlname: Path = ctrlchnlname def _verify_connection(self) -> None: """ diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index ce34bd98..aa925a7d 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -1,6 +1,6 @@ import json import logging -import os +from pathlib import Path from tempfile import NamedTemporaryFile from typing import TYPE_CHECKING, Callable, Dict, Optional @@ -63,8 +63,8 @@ class DockerClient: logging.debug("node(%s) pid: %s", self.name, self.pid) return output - def copy_file(self, source: str, destination: str) -> str: - args = f"docker cp {source} {self.name}:{destination}" + def copy_file(self, src_path: Path, dst_path: Path) -> str: + args = f"docker cp {src_path} {self.name}:{dst_path}" return self.run(args) @@ -162,77 +162,73 @@ class DockerNode(CoreNode): """ return f"docker exec -it {self.name} bash" - def privatedir(self, path: str) -> None: + def privatedir(self, dir_path: str) -> None: """ Create a private directory. - :param path: path to create + :param dir_path: path to create :return: nothing """ - logging.debug("creating node dir: %s", path) - args = f"mkdir -p {path}" + logging.debug("creating node dir: %s", dir_path) + args = f"mkdir -p {dir_path}" self.cmd(args) - def mount(self, source: str, target: str) -> None: + def mount(self, src_path: str, target_path: str) -> None: """ Create and mount a directory. - :param source: source directory to mount - :param target: target directory to create + :param src_path: source directory to mount + :param target_path: target directory to create :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - logging.debug("mounting source(%s) target(%s)", source, target) + logging.debug("mounting source(%s) target(%s)", src_path, target_path) raise Exception("not supported") - def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: + def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. - :param filename: name of file to create + :param file_path: name of file to create :param contents: contents of file :param mode: mode for file :return: nothing """ - logging.debug("nodefile filename(%s) mode(%s)", filename, mode) - directory = os.path.dirname(filename) + logging.debug("nodefile filename(%s) mode(%s)", file_path, mode) temp = NamedTemporaryFile(delete=False) temp.write(contents.encode("utf-8")) temp.close() - - if directory: + temp_path = Path(temp.name) + directory = file_path.name + if str(directory) != ".": self.cmd(f"mkdir -m {0o755:o} -p {directory}") if self.server is not None: - self.server.remote_put(temp.name, temp.name) - self.client.copy_file(temp.name, filename) - self.cmd(f"chmod {mode:o} {filename}") + self.server.remote_put(temp_path, temp_path) + self.client.copy_file(temp_path, file_path) + self.cmd(f"chmod {mode:o} {file_path}") if self.server is not None: - self.host_cmd(f"rm -f {temp.name}") - os.unlink(temp.name) - logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode) + self.host_cmd(f"rm -f {temp_path}") + temp_path.unlink() + logging.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode) - def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None: + def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None: """ Copy a file to a node, following symlinks and preserving metadata. Change file mode if specified. - :param filename: file name to copy file to - :param srcfilename: file to copy + :param file_path: file name to copy file to + :param src_path: file to copy :param mode: mode to copy to :return: nothing """ logging.info( - "node file copy file(%s) source(%s) mode(%s)", filename, srcfilename, mode + "node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode ) - directory = os.path.dirname(filename) - self.cmd(f"mkdir -p {directory}") - - if self.server is None: - source = srcfilename - else: + self.cmd(f"mkdir -p {file_path.parent}") + if self.server: temp = NamedTemporaryFile(delete=False) - source = temp.name - self.server.remote_put(source, temp.name) - - self.client.copy_file(source, filename) - self.cmd(f"chmod {mode:o} {filename}") + temp_path = Path(temp.name) + src_path = temp_path + self.server.remote_put(src_path, temp_path) + self.client.copy_file(src_path, file_path) + self.cmd(f"chmod {mode:o} {file_path}") diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 32d47af2..604f2941 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -4,6 +4,7 @@ virtual ethernet classes that implement the interfaces available under Linux. import logging import time +from pathlib import Path from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple import netaddr @@ -79,7 +80,7 @@ class CoreInterface: self, args: str, env: Dict[str, str] = None, - cwd: str = None, + cwd: Path = None, wait: bool = True, shell: bool = False, ) -> str: diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index 9773cb95..d6352dc0 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -1,7 +1,7 @@ import json import logging -import os import time +from pathlib import Path from tempfile import NamedTemporaryFile from typing import TYPE_CHECKING, Callable, Dict, Optional @@ -57,11 +57,10 @@ class LxdClient: args = self.create_cmd(cmd) return utils.cmd(args, wait=wait, shell=shell) - def copy_file(self, source: str, destination: str) -> None: - if destination[0] != "/": - destination = os.path.join("/root/", destination) - - args = f"lxc file push {source} {self.name}/{destination}" + def copy_file(self, src_path: Path, dst_path: Path) -> None: + if not str(dst_path).startswith("/"): + dst_path = Path("/root/") / dst_path + args = f"lxc file push {src_path} {self.name}/{dst_path}" self.run(args) @@ -139,81 +138,76 @@ class LxcNode(CoreNode): """ return f"lxc exec {self.name} -- {sh}" - def privatedir(self, path: str) -> None: + def privatedir(self, dir_path: Path) -> None: """ Create a private directory. - :param path: path to create + :param dir_path: path to create :return: nothing """ - logging.info("creating node dir: %s", path) - args = f"mkdir -p {path}" + logging.info("creating node dir: %s", dir_path) + args = f"mkdir -p {dir_path}" self.cmd(args) - def mount(self, source: str, target: str) -> None: + def mount(self, src_path: Path, target_path: Path) -> None: """ Create and mount a directory. - :param source: source directory to mount - :param target: target directory to create + :param src_path: source directory to mount + :param target_path: target directory to create :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - logging.debug("mounting source(%s) target(%s)", source, target) + logging.debug("mounting source(%s) target(%s)", src_path, target_path) raise Exception("not supported") - def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: + def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. - :param filename: name of file to create + :param file_path: name of file to create :param contents: contents of file :param mode: mode for file :return: nothing """ - logging.debug("nodefile filename(%s) mode(%s)", filename, mode) - - directory = os.path.dirname(filename) + logging.debug("nodefile filename(%s) mode(%s)", file_path, mode) temp = NamedTemporaryFile(delete=False) temp.write(contents.encode("utf-8")) temp.close() - - if directory: + temp_path = Path(temp.name) + directory = file_path.parent + if str(directory) != ".": self.cmd(f"mkdir -m {0o755:o} -p {directory}") if self.server is not None: - self.server.remote_put(temp.name, temp.name) - self.client.copy_file(temp.name, filename) - self.cmd(f"chmod {mode:o} {filename}") + self.server.remote_put(temp_path, temp_path) + self.client.copy_file(temp_path, file_path) + self.cmd(f"chmod {mode:o} {file_path}") if self.server is not None: - self.host_cmd(f"rm -f {temp.name}") - os.unlink(temp.name) - logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode) + self.host_cmd(f"rm -f {temp_path}") + temp_path.unlink() + logging.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode) - def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None: + def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None: """ Copy a file to a node, following symlinks and preserving metadata. Change file mode if specified. - :param filename: file name to copy file to - :param srcfilename: file to copy + :param file_path: file name to copy file to + :param src_path: file to copy :param mode: mode to copy to :return: nothing """ logging.info( - "node file copy file(%s) source(%s) mode(%s)", filename, srcfilename, mode + "node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode ) - directory = os.path.dirname(filename) - self.cmd(f"mkdir -p {directory}") - - if self.server is None: - source = srcfilename - else: + self.cmd(f"mkdir -p {file_path.parent}") + if self.server: temp = NamedTemporaryFile(delete=False) - source = temp.name - self.server.remote_put(source, temp.name) - - self.client.copy_file(source, filename) - self.cmd(f"chmod {mode:o} {filename}") + temp_path = Path(temp.name) + src_path = temp_path + self.server.remote_put(src_path, temp_path) + self.client.copy_file(src_path, file_path) + self.cmd(f"chmod {mode:o} {file_path}") def add_iface(self, iface: CoreInterface, iface_id: int) -> None: super().add_iface(iface, iface_id) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index cb3aca79..5d103805 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -6,6 +6,7 @@ import logging import math import threading import time +from pathlib import Path from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type import netaddr @@ -292,7 +293,7 @@ class CoreNetwork(CoreNetworkBase): self, args: str, env: Dict[str, str] = None, - cwd: str = None, + cwd: Path = None, wait: bool = True, shell: bool = False, ) -> str: @@ -333,9 +334,7 @@ class CoreNetwork(CoreNetworkBase): """ if not self.up: return - ebq.stopupdateloop(self) - try: self.net_client.delete_bridge(self.brname) if self.has_ebtables_chain: @@ -346,11 +345,9 @@ class CoreNetwork(CoreNetworkBase): ebtablescmds(self.host_cmd, cmds) except CoreCommandError: logging.exception("error during shutdown") - # removes veth pairs used for bridge-to-bridge connections for iface in self.get_ifaces(): iface.shutdown() - self.ifaces.clear() self._linked.clear() del self.session @@ -389,10 +386,8 @@ class CoreNetwork(CoreNetworkBase): # check if the network interfaces are attached to this network if self.ifaces[iface1.net_id] != iface1: raise ValueError(f"inconsistency for interface {iface1.name}") - if self.ifaces[iface2.net_id] != iface2: raise ValueError(f"inconsistency for interface {iface2.name}") - try: linked = self._linked[iface1][iface2] except KeyError: @@ -403,7 +398,6 @@ class CoreNetwork(CoreNetworkBase): else: raise Exception(f"unknown policy: {self.policy.value}") self._linked[iface1][iface2] = linked - return linked def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None: diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 4e8c9464..c326c4f0 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -3,9 +3,9 @@ PhysicalNode class for including real systems in the emulated network. """ import logging -import os import threading -from typing import IO, TYPE_CHECKING, List, Optional, Tuple +from pathlib import Path +from typing import TYPE_CHECKING, List, Optional, Tuple from core.emulator.data import InterfaceData, LinkOptions from core.emulator.distributed import DistributedServer @@ -26,15 +26,15 @@ class PhysicalNode(CoreNodeBase): session: "Session", _id: int = None, name: str = None, - nodedir: str = None, + nodedir: Path = None, server: DistributedServer = None, ) -> None: 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.nodedir: Optional[Path] = nodedir self.lock: threading.RLock = threading.RLock() - self._mounts: List[Tuple[str, str]] = [] + self._mounts: List[Tuple[Path, Path]] = [] def startup(self) -> None: with self.lock: @@ -44,15 +44,12 @@ class PhysicalNode(CoreNodeBase): def shutdown(self) -> None: if not self.up: return - with self.lock: while self._mounts: - _source, target = self._mounts.pop(-1) - self.umount(target) - + _, target_path = self._mounts.pop(-1) + self.umount(target_path) for iface in self.get_ifaces(): iface.shutdown() - self.rmnodedir() def path_exists(self, path: str) -> bool: @@ -186,55 +183,40 @@ class PhysicalNode(CoreNodeBase): self.adopt_iface(iface, iface_id, iface_data.mac, ips) return iface - def privatedir(self, path: str) -> None: - if path[0] != "/": - raise ValueError(f"path not fully qualified: {path}") - hostpath = os.path.join( - self.nodedir, os.path.normpath(path).strip("/").replace("/", ".") - ) - os.mkdir(hostpath) - self.mount(hostpath, path) + def privatedir(self, dir_path: Path) -> None: + if not str(dir_path).startswith("/"): + raise CoreError(f"private directory path not fully qualified: {dir_path}") + host_path = self.host_path(dir_path, is_dir=True) + self.host_cmd(f"mkdir -p {host_path}") + self.mount(host_path, dir_path) - def mount(self, source: str, target: str) -> None: - source = os.path.abspath(source) - logging.info("mounting %s at %s", source, target) - os.makedirs(target) - self.host_cmd(f"{MOUNT} --bind {source} {target}", cwd=self.nodedir) - self._mounts.append((source, target)) + def mount(self, src_path: Path, target_path: Path) -> None: + logging.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path) + self.cmd(f"mkdir -p {target_path}") + self.host_cmd(f"{MOUNT} --bind {src_path} {target_path}", cwd=self.nodedir) + self._mounts.append((src_path, target_path)) - def umount(self, target: str) -> None: - logging.info("unmounting '%s'", target) + def umount(self, target_path: Path) -> None: + logging.info("unmounting '%s'", target_path) try: - self.host_cmd(f"{UMOUNT} -l {target}", cwd=self.nodedir) + self.host_cmd(f"{UMOUNT} -l {target_path}", cwd=self.nodedir) except CoreCommandError: - logging.exception("unmounting failed for %s", target) + logging.exception("unmounting failed for %s", target_path) - def opennodefile(self, filename: str, mode: str = "w") -> IO: - dirname, basename = os.path.split(filename) - if not basename: - raise ValueError("no basename for filename: " + filename) - - if dirname and dirname[0] == "/": - dirname = dirname[1:] - - dirname = dirname.replace("/", ".") - dirname = os.path.join(self.nodedir, dirname) - if not os.path.isdir(dirname): - os.makedirs(dirname, mode=0o755) - - hostfilename = os.path.join(dirname, basename) - return open(hostfilename, mode) - - def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: - with self.opennodefile(filename, "w") as f: + def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: + host_path = self.host_path(file_path) + directory = host_path.parent + if not directory.is_dir(): + directory.mkdir(parents=True, mode=0o755) + with host_path.open("w") as f: f.write(contents) - os.chmod(f.name, mode) - logging.info("created nodefile: '%s'; mode: 0%o", f.name, mode) + host_path.chmod(mode) + logging.info("created nodefile: '%s'; mode: 0%o", host_path, 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: + def addfile(self, src_path: str, file_path: str) -> None: raise CoreError("physical node does not support addfile") @@ -464,10 +446,10 @@ class Rj45Node(CoreNodeBase): def termcmdstring(self, sh: str) -> str: raise CoreError("rj45 does not support terminal commands") - def addfile(self, srcname: str, filename: str) -> None: + def addfile(self, src_path: str, file_path: str) -> None: raise CoreError("rj45 does not support addfile") - def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: + def nodefile(self, file_path: str, contents: str, mode: int = 0o644) -> None: raise CoreError("rj45 does not support nodefile") def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: diff --git a/daemon/core/services/__init__.py b/daemon/core/services/__init__.py index 94e1e9d1..4d9b90a2 100644 --- a/daemon/core/services/__init__.py +++ b/daemon/core/services/__init__.py @@ -4,11 +4,11 @@ Services Services available to nodes can be put in this directory. Everything listed in __all__ is automatically loaded by the main core module. """ -import os +from pathlib import Path from core.services.coreservices import ServiceManager -_PATH = os.path.abspath(os.path.dirname(__file__)) +_PATH: Path = Path(__file__).resolve().parent def load(): diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index b4c33990..4da858fa 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -10,6 +10,7 @@ services. import enum import logging import time +from pathlib import Path from typing import ( TYPE_CHECKING, Dict, @@ -264,7 +265,7 @@ class ServiceManager: return service @classmethod - def add_services(cls, path: str) -> List[str]: + def add_services(cls, path: Path) -> List[str]: """ Method for retrieving all CoreServices from a given path. @@ -276,7 +277,6 @@ class ServiceManager: for service in services: if not service.name: continue - try: cls.add(service) except (CoreError, ValueError) as e: @@ -488,9 +488,10 @@ class CoreServices: # create service directories for directory in service.dirs: + dir_path = Path(directory) try: - node.privatedir(directory) - except (CoreCommandError, ValueError) as e: + node.privatedir(dir_path) + except (CoreCommandError, CoreError) as e: logging.warning( "error mounting private dir '%s' for service '%s': %s", directory, @@ -534,14 +535,14 @@ class CoreServices: "node(%s) service(%s) failed validation" % (node.name, service.name) ) - def copy_service_file(self, node: CoreNode, filename: str, cfg: str) -> bool: + def copy_service_file(self, node: CoreNode, file_path: Path, cfg: str) -> bool: """ Given a configured service filename and config, determine if the config references an existing file that should be copied. Returns True for local files, False for generated. :param node: node to copy service for - :param filename: file name for a configured service + :param file_path: file name for a configured service :param cfg: configuration string :return: True if successful, False otherwise """ @@ -550,7 +551,7 @@ class CoreServices: src = src.split("\n")[0] src = utils.expand_corepath(src, node.session, node) # TODO: glob here - node.nodefilecopy(filename, src, mode=0o644) + node.nodefilecopy(file_path, src, mode=0o644) return True return False @@ -729,8 +730,8 @@ class CoreServices: config_files = service.configs if not service.custom: config_files = service.get_configs(node) - for file_name in config_files: + file_path = Path(file_name) logging.debug( "generating service config custom(%s): %s", service.custom, file_name ) @@ -738,18 +739,16 @@ class CoreServices: cfg = service.config_data.get(file_name) if cfg is None: cfg = service.generate_config(node, file_name) - # cfg may have a file:/// url for copying from a file try: - if self.copy_service_file(node, file_name, cfg): + if self.copy_service_file(node, file_path, cfg): continue except IOError: logging.exception("error copying service file: %s", file_name) continue else: cfg = service.generate_config(node, file_name) - - node.nodefile(file_name, cfg) + node.nodefile(file_path, cfg) def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None: """ @@ -762,17 +761,15 @@ class CoreServices: config_files = service.configs if not service.custom: config_files = service.get_configs(node) - for file_name in config_files: + file_path = Path(file_name) if file_name[:7] == "file:///": # TODO: implement this raise NotImplementedError - cfg = service.config_data.get(file_name) if cfg is None: cfg = service.generate_config(node, file_name) - - node.nodefile(file_name, cfg) + node.nodefile(file_path, cfg) class CoreService: diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 4a9d6ca6..d434b169 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -46,11 +46,10 @@ IFACE_CONFIG_FACTOR: int = 1000 def execute_file( - path: str, exec_globals: Dict[str, str] = None, exec_locals: Dict[str, str] = None + path: Path, exec_globals: Dict[str, str] = None, exec_locals: Dict[str, str] = None ) -> None: """ - Provides an alternative way to run execfile to be compatible for - both python2/3. + Provides a way to execute a file. :param path: path of file to execute :param exec_globals: globals values to pass to execution @@ -59,10 +58,10 @@ def execute_file( """ if exec_globals is None: exec_globals = {} - exec_globals.update({"__file__": path, "__name__": "__main__"}) - with open(path, "rb") as f: + exec_globals.update({"__file__": str(path), "__name__": "__main__"}) + with path.open("rb") as f: data = compile(f.read(), path, "exec") - exec(data, exec_globals, exec_locals) + exec(data, exec_globals, exec_locals) def hashkey(value: Union[str, int]) -> int: @@ -92,24 +91,19 @@ def _detach_init() -> None: os.setsid() -def _valid_module(path: str, file_name: str) -> bool: +def _valid_module(path: Path) -> bool: """ Check if file is a valid python module. :param path: path to file - :param file_name: file name to check :return: True if a valid python module file, False otherwise """ - file_path = os.path.join(path, file_name) - if not os.path.isfile(file_path): + if not path.is_file(): return False - - if file_name.startswith("_"): + if path.name.startswith("_"): return False - - if not file_name.endswith(".py"): + if not path.suffix == ".py": return False - return True @@ -124,13 +118,10 @@ def _is_class(module: Any, member: Type, clazz: Type) -> bool: """ if not inspect.isclass(member): return False - if not issubclass(member, clazz): return False - if member.__module__ != module.__name__: return False - return True @@ -196,7 +187,7 @@ def mute_detach(args: str, **kwargs: Dict[str, Any]) -> int: def cmd( args: str, env: Dict[str, str] = None, - cwd: str = None, + cwd: Path = None, wait: bool = True, shell: bool = False, ) -> str: @@ -282,7 +273,7 @@ def file_demunge(pathname: str, header: str) -> None: def expand_corepath( pathname: str, session: "Session" = None, node: "CoreNode" = None -) -> str: +) -> Path: """ Expand a file path given session information. @@ -294,14 +285,12 @@ def expand_corepath( if session is not None: pathname = pathname.replace("~", f"/home/{session.user}") pathname = pathname.replace("%SESSION%", str(session.id)) - pathname = pathname.replace("%SESSION_DIR%", session.session_dir) + pathname = pathname.replace("%SESSION_DIR%", str(session.session_dir)) pathname = pathname.replace("%SESSION_USER%", session.user) - if node is not None: pathname = pathname.replace("%NODE%", str(node.id)) pathname = pathname.replace("%NODENAME%", node.name) - - return pathname + return Path(pathname) def sysctl_devname(devname: str) -> Optional[str]: @@ -337,7 +326,7 @@ def load_config(file_path: Path, d: Dict[str, str]) -> None: logging.exception("error reading file to dict: %s", file_path) -def load_classes(path: str, clazz: Generic[T]) -> T: +def load_classes(path: Path, clazz: Generic[T]) -> T: """ Dynamically load classes for use within CORE. @@ -347,24 +336,19 @@ def load_classes(path: str, clazz: Generic[T]) -> T: """ # validate path exists logging.debug("attempting to load modules from path: %s", path) - if not os.path.isdir(path): + if not path.is_dir(): logging.warning("invalid custom module directory specified" ": %s", path) # check if path is in sys.path - parent_path = os.path.dirname(path) - if parent_path not in sys.path: - logging.debug("adding parent path to allow imports: %s", parent_path) - sys.path.append(parent_path) - - # retrieve potential service modules, and filter out invalid modules - base_module = os.path.basename(path) - module_names = os.listdir(path) - module_names = filter(lambda x: _valid_module(path, x), module_names) - module_names = map(lambda x: x[:-3], module_names) - + parent = str(path.parent) + if parent not in sys.path: + logging.debug("adding parent path to allow imports: %s", parent) + sys.path.append(parent) # import and add all service modules in the path classes = [] - for module_name in module_names: - import_statement = f"{base_module}.{module_name}" + for p in path.iterdir(): + if not _valid_module(p): + continue + import_statement = f"{path.name}.{p.stem}" logging.debug("importing custom module: %s", import_statement) try: module = importlib.import_module(import_statement) @@ -376,20 +360,19 @@ def load_classes(path: str, clazz: Generic[T]) -> T: logging.exception( "unexpected error during import, skipping: %s", import_statement ) - return classes -def load_logging_config(config_path: str) -> None: +def load_logging_config(config_path: Path) -> None: """ Load CORE logging configuration file. :param config_path: path to logging config file :return: nothing """ - with open(config_path, "r") as log_config_file: - log_config = json.load(log_config_file) - logging.config.dictConfig(log_config) + with config_path.open("r") as f: + log_config = json.load(f) + logging.config.dictConfig(log_config) def threadpool( diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 8d7b5ea1..d1717495 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -1,4 +1,5 @@ import logging +from pathlib import Path from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, Type, TypeVar from lxml import etree @@ -25,7 +26,7 @@ T = TypeVar("T") def write_xml_file( - xml_element: etree.Element, file_path: str, doctype: str = None + xml_element: etree.Element, file_path: Path, doctype: str = None ) -> None: xml_data = etree.tostring( xml_element, @@ -34,8 +35,8 @@ def write_xml_file( encoding="UTF-8", doctype=doctype, ) - with open(file_path, "wb") as xml_file: - xml_file.write(xml_data) + with file_path.open("wb") as f: + f.write(xml_data) def get_type(element: etree.Element, name: str, _type: Generic[T]) -> Optional[T]: @@ -293,13 +294,12 @@ class CoreXmlWriter: self.write_session_metadata() self.write_default_services() - def write(self, file_name: str) -> None: - self.scenario.set("name", file_name) - + def write(self, path: Path) -> None: + self.scenario.set("name", str(path)) # write out generated xml xml_tree = etree.ElementTree(self.scenario) xml_tree.write( - file_name, xml_declaration=True, pretty_print=True, encoding="UTF-8" + str(path), xml_declaration=True, pretty_print=True, encoding="UTF-8" ) def write_session_origin(self) -> None: @@ -580,8 +580,8 @@ class CoreXmlReader: self.session: "Session" = session self.scenario: Optional[etree.ElementTree] = None - def read(self, file_name: str) -> None: - xml_tree = etree.parse(file_name) + def read(self, file_path: Path) -> None: + xml_tree = etree.parse(str(file_path)) self.scenario = xml_tree.getroot() # read xml session content diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index c0d5462b..ee736da4 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -1,5 +1,5 @@ import logging -import os +from pathlib import Path from tempfile import NamedTemporaryFile from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple @@ -54,7 +54,7 @@ def _value_to_params(value: str) -> Optional[Tuple[str]]: def create_file( xml_element: etree.Element, doc_name: str, - file_path: str, + file_path: Path, server: DistributedServer = None, ) -> None: """ @@ -71,10 +71,11 @@ def create_file( ) if server: temp = NamedTemporaryFile(delete=False) - corexml.write_xml_file(xml_element, temp.name, doctype=doctype) + temp_path = Path(temp.name) + corexml.write_xml_file(xml_element, temp_path, doctype=doctype) temp.close() - server.remote_put(temp.name, file_path) - os.unlink(temp.name) + server.remote_put(temp_path, file_path) + temp_path.unlink() else: corexml.write_xml_file(xml_element, file_path, doctype=doctype) @@ -92,9 +93,9 @@ def create_node_file( :return: """ if isinstance(node, CoreNode): - file_path = os.path.join(node.nodedir, file_name) + file_path = node.nodedir / file_name else: - file_path = os.path.join(node.session.session_dir, file_name) + file_path = node.session.session_dir / file_name create_file(xml_element, doc_name, file_path, node.server) @@ -316,7 +317,7 @@ def create_event_service_xml( group: str, port: str, device: str, - file_directory: str, + file_directory: Path, server: DistributedServer = None, ) -> None: """ @@ -340,8 +341,7 @@ def create_event_service_xml( ): sub_element = etree.SubElement(event_element, name) sub_element.text = value - file_name = "libemaneeventservice.xml" - file_path = os.path.join(file_directory, file_name) + file_path = file_directory / "libemaneeventservice.xml" create_file(event_element, "emaneeventmsgsvc", file_path, server) diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index 16b0ac59..29f29a33 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -12,6 +12,7 @@ import sys import threading import time from configparser import ConfigParser +from pathlib import Path from core import constants from core.api.grpc.server import CoreGrpcServer @@ -148,7 +149,8 @@ def main(): :return: nothing """ cfg = get_merged_config(f"{CORE_CONF_DIR}/core.conf") - load_logging_config(cfg["logfile"]) + log_config_path = Path(cfg["logfile"]) + load_logging_config(log_config_path) banner() try: cored(cfg) diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index ccbfb446..29963401 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -1,7 +1,7 @@ """ Unit tests for testing CORE EMANE networks. """ -import os +from pathlib import Path from tempfile import TemporaryFile from typing import Type from xml.etree import ElementTree @@ -28,7 +28,8 @@ _EMANE_MODELS = [ EmaneCommEffectModel, EmaneTdmaModel, ] -_DIR = os.path.dirname(os.path.abspath(__file__)) +_DIR: Path = Path(__file__).resolve().parent +_SCHEDULE: Path = _DIR / "../../examples/tdma/schedule.xml" def ping( @@ -107,9 +108,7 @@ class TestEmane: # configure tdma if model == EmaneTdmaModel: session.emane.set_model_config( - emane_network.id, - EmaneTdmaModel.name, - {"schedule": os.path.join(_DIR, "../../examples/tdma/schedule.xml")}, + emane_network.id, EmaneTdmaModel.name, {"schedule": str(_SCHEDULE)} ) # create nodes diff --git a/daemon/tests/test_config_services.py b/daemon/tests/test_config_services.py index eaba4d47..7efde087 100644 --- a/daemon/tests/test_config_services.py +++ b/daemon/tests/test_config_services.py @@ -1,3 +1,4 @@ +from pathlib import Path from unittest import mock import pytest @@ -68,7 +69,8 @@ class TestConfigServices: service.create_dirs() # then - node.privatedir.assert_called_with(MyService.directories[0]) + directory = Path(MyService.directories[0]) + node.privatedir.assert_called_with(directory) def test_create_files_custom(self): # given @@ -81,7 +83,8 @@ class TestConfigServices: service.create_files() # then - node.nodefile.assert_called_with(MyService.files[0], text) + file_path = Path(MyService.files[0]) + node.nodefile.assert_called_with(file_path, text) def test_create_files_text(self): # given @@ -92,7 +95,8 @@ class TestConfigServices: service.create_files() # then - node.nodefile.assert_called_with(MyService.files[0], TEMPLATE_TEXT) + file_path = Path(MyService.files[0]) + node.nodefile.assert_called_with(file_path, TEMPLATE_TEXT) def test_run_startup(self): # given diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index c4465863..1342861b 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -2,9 +2,9 @@ Unit tests for testing basic CORE networks. """ -import os import threading -from typing import Type +from pathlib import Path +from typing import List, Type import pytest @@ -16,9 +16,9 @@ from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.nodes.base import CoreNode, NodeBase from core.nodes.network import HubNode, PtpNet, SwitchNode, WlanNode -_PATH = os.path.abspath(os.path.dirname(__file__)) -_MOBILITY_FILE = os.path.join(_PATH, "mobility.scen") -_WIRED = [PtpNet, HubNode, SwitchNode] +_PATH: Path = Path(__file__).resolve().parent +_MOBILITY_FILE: Path = _PATH / "mobility.scen" +_WIRED: List = [PtpNet, HubNode, SwitchNode] def ping(from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes): @@ -195,7 +195,7 @@ class TestCore: # configure mobility script for session config = { - "file": _MOBILITY_FILE, + "file": str(_MOBILITY_FILE), "refresh_ms": "50", "loop": "1", "autostart": "0.0", diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index a0b3bd8a..732b57d1 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -1,8 +1,8 @@ """ Tests for testing tlv message handling. """ -import os import time +from pathlib import Path from typing import Optional import mock @@ -425,7 +425,7 @@ class TestGui: assert file_data == service_file.data def test_file_node_file_copy(self, request, coretlv: CoreHandler): - file_name = "/var/log/test/node.log" + file_path = Path("/var/log/test/node.log") node = coretlv.session.add_node(CoreNode) node.makenodedir() file_data = "echo hello" @@ -433,7 +433,7 @@ class TestGui: MessageFlags.ADD.value, [ (FileTlvs.NODE, node.id), - (FileTlvs.NAME, file_name), + (FileTlvs.NAME, str(file_path)), (FileTlvs.DATA, file_data), ], ) @@ -441,10 +441,10 @@ class TestGui: coretlv.handle_message(message) if not request.config.getoption("mock"): - directory, basename = os.path.split(file_name) + directory = str(file_path.parent) created_directory = directory[1:].replace("/", ".") - create_path = os.path.join(node.nodedir, created_directory, basename) - assert os.path.exists(create_path) + create_path = node.nodedir / created_directory / file_path.name + assert create_path.exists() def test_exec_node_tty(self, coretlv: CoreHandler): coretlv.dispatch_replies = mock.MagicMock() @@ -547,20 +547,21 @@ class TestGui: 0, [(EventTlvs.TYPE, EventTypes.FILE_SAVE.value), (EventTlvs.NAME, file_path)], ) - coretlv.handle_message(message) - - assert os.path.exists(file_path) + assert Path(file_path).exists() def test_event_open_xml(self, coretlv: CoreHandler, tmpdir): xml_file = tmpdir.join("coretlv.session.xml") - file_path = xml_file.strpath + file_path = Path(xml_file.strpath) node = coretlv.session.add_node(CoreNode) coretlv.session.save_xml(file_path) coretlv.session.delete_node(node.id) message = coreapi.CoreEventMessage.create( 0, - [(EventTlvs.TYPE, EventTypes.FILE_OPEN.value), (EventTlvs.NAME, file_path)], + [ + (EventTlvs.TYPE, EventTypes.FILE_OPEN.value), + (EventTlvs.NAME, str(file_path)), + ], ) coretlv.handle_message(message) diff --git a/daemon/tests/test_services.py b/daemon/tests/test_services.py index 44776ea2..bbccaaac 100644 --- a/daemon/tests/test_services.py +++ b/daemon/tests/test_services.py @@ -1,5 +1,5 @@ import itertools -import os +from pathlib import Path import pytest from mock import MagicMock @@ -9,8 +9,8 @@ from core.errors import CoreCommandError from core.nodes.base import CoreNode from core.services.coreservices import CoreService, ServiceDependencies, ServiceManager -_PATH = os.path.abspath(os.path.dirname(__file__)) -_SERVICES_PATH = os.path.join(_PATH, "myservices") +_PATH: Path = Path(__file__).resolve().parent +_SERVICES_PATH = _PATH / "myservices" SERVICE_ONE = "MyService" SERVICE_TWO = "MyService2" @@ -64,15 +64,15 @@ class TestServices: ServiceManager.add_services(_SERVICES_PATH) my_service = ServiceManager.get(SERVICE_ONE) node = session.add_node(CoreNode) - file_name = my_service.configs[0] - file_path = node.hostfilename(file_name) + file_path = Path(my_service.configs[0]) + file_path = node.host_path(file_path) # when session.services.create_service_files(node, my_service) # then if not request.config.getoption("mock"): - assert os.path.exists(file_path) + assert file_path.exists() def test_service_validate(self, session: Session): # given diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 8a6e465d..653e77f6 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -1,3 +1,4 @@ +from pathlib import Path from tempfile import TemporaryFile from xml.etree import ElementTree @@ -34,7 +35,7 @@ class TestXml: # save xml xml_file = tmpdir.join("session.xml") - file_path = xml_file.strpath + file_path = Path(xml_file.strpath) session.save_xml(file_path) # verify xml file was created and can be parsed @@ -85,7 +86,7 @@ class TestXml: # save xml xml_file = tmpdir.join("session.xml") - file_path = xml_file.strpath + file_path = Path(xml_file.strpath) session.save_xml(file_path) # verify xml file was created and can be parsed @@ -148,7 +149,7 @@ class TestXml: # save xml xml_file = tmpdir.join("session.xml") - file_path = xml_file.strpath + file_path = Path(xml_file.strpath) session.save_xml(file_path) # verify xml file was created and can be parsed @@ -210,7 +211,7 @@ class TestXml: # save xml xml_file = tmpdir.join("session.xml") - file_path = xml_file.strpath + file_path = Path(xml_file.strpath) session.save_xml(file_path) # verify xml file was created and can be parsed @@ -261,7 +262,7 @@ class TestXml: # save xml xml_file = tmpdir.join("session.xml") - file_path = xml_file.strpath + file_path = Path(xml_file.strpath) session.save_xml(file_path) # verify xml file was created and can be parsed @@ -321,7 +322,7 @@ class TestXml: # save xml xml_file = tmpdir.join("session.xml") - file_path = xml_file.strpath + file_path = Path(xml_file.strpath) session.save_xml(file_path) # verify xml file was created and can be parsed @@ -390,7 +391,7 @@ class TestXml: # save xml xml_file = tmpdir.join("session.xml") - file_path = xml_file.strpath + file_path = Path(xml_file.strpath) session.save_xml(file_path) # verify xml file was created and can be parsed @@ -471,7 +472,7 @@ class TestXml: # save xml xml_file = tmpdir.join("session.xml") - file_path = xml_file.strpath + file_path = Path(xml_file.strpath) session.save_xml(file_path) # verify xml file was created and can be parsed From a2148c692302bdf24fe95d78049d6d2c67611b9e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Mar 2021 16:56:54 -0700 Subject: [PATCH 002/110] daemon: refactored session.session_dir to session.directory --- daemon/core/api/grpc/server.py | 8 ++++---- daemon/core/emane/emanemanager.py | 12 ++++++------ daemon/core/emulator/distributed.py | 4 ++-- daemon/core/emulator/session.py | 30 ++++++++++++++--------------- daemon/core/location/mobility.py | 4 +--- daemon/core/nodes/base.py | 4 ++-- daemon/core/utils.py | 2 +- daemon/core/xml/emanexml.py | 2 +- 8 files changed, 32 insertions(+), 34 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 3cf56fda..b8b2a445 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -222,7 +222,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): # clear previous state and setup for creation session.clear() - session.session_dir.mkdir(exist_ok=True) + session.directory.mkdir(exist_ok=True) session.set_state(EventTypes.CONFIGURATION_STATE) # location @@ -372,7 +372,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): state=session.state.value, nodes=session.get_node_count(), file=session_file, - dir=str(session.session_dir), + dir=str(session.directory), ) sessions.append(session_summary) return core_pb2.GetSessionsResponse(sessions=sessions) @@ -428,7 +428,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): state = EventTypes(request.state) session.set_state(state) if state == EventTypes.INSTANTIATION_STATE: - session.session_dir.mkdir(exist_ok=True) + session.directory.mkdir(exist_ok=True) session.instantiate() elif state == EventTypes.SHUTDOWN_STATE: session.shutdown() @@ -575,7 +575,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): state=session.state.value, nodes=nodes, links=links, - dir=str(session.session_dir), + dir=str(session.directory), user=session.user, default_services=default_services, location=location, diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index bd6089e9..b3841357 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -398,7 +398,7 @@ class EmaneManager(ModelManager): return self.ifaces_to_nems.get(iface) def write_nem(self, iface: CoreInterface, nem_id: int) -> None: - path = self.session.session_dir / "emane_nems" + path = self.session.directory / "emane_nems" try: with path.open("a") as f: f.write(f"{iface.node.name} {iface.name} {nem_id}\n") @@ -539,10 +539,10 @@ class EmaneManager(ModelManager): return dev = self.get_config("eventservicedevice") - emanexml.create_event_service_xml(group, port, dev, self.session.session_dir) + emanexml.create_event_service_xml(group, port, dev, self.session.directory) self.session.distributed.execute( lambda x: emanexml.create_event_service_xml( - group, port, dev, self.session.session_dir, x + group, port, dev, self.session.directory, x ) ) @@ -596,10 +596,10 @@ class EmaneManager(ModelManager): node.cmd(args) logging.info("node(%s) emane daemon running: %s", node.name, args) else: - log_file = self.session.session_dir / f"{node.name}-emane.log" - platform_xml = self.session.session_dir / f"{node.name}-platform.xml" + log_file = self.session.directory / f"{node.name}-emane.log" + platform_xml = self.session.directory / f"{node.name}-platform.xml" args = f"{emanecmd} -f {log_file} {platform_xml}" - node.host_cmd(args, cwd=self.session.session_dir) + node.host_cmd(args, cwd=self.session.directory) logging.info("node(%s) host emane daemon running: %s", node.name, args) def install_iface(self, iface: CoreInterface) -> None: diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index 0731b9af..d062e25b 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -145,7 +145,7 @@ class DistributedController: f"command({requirement})" ) self.servers[name] = server - cmd = f"mkdir -p {self.session.session_dir}" + cmd = f"mkdir -p {self.session.directory}" server.remote_cmd(cmd) def execute(self, func: Callable[[DistributedServer], None]) -> None: @@ -174,7 +174,7 @@ class DistributedController: # remove all remote session directories for name in self.servers: server = self.servers[name] - cmd = f"rm -rf {self.session.session_dir}" + cmd = f"rm -rf {self.session.directory}" server.remote_cmd(cmd) # clear tunnels self.tunnels.clear() diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index fbc91907..59c89f89 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -103,9 +103,9 @@ class Session: self.id: int = _id # define and create session directory when desired - self.session_dir: Path = Path(tempfile.gettempdir()) / f"pycore.{self.id}" + self.directory: Path = Path(tempfile.gettempdir()) / f"pycore.{self.id}" if mkdir: - self.session_dir.mkdir() + self.directory.mkdir() self.name: Optional[str] = None self.file_path: Optional[Path] = None @@ -777,7 +777,7 @@ class Session: # remove this sessions working directory preserve = self.options.get_config("preservedir") == "1" if not preserve: - shutil.rmtree(self.session_dir, ignore_errors=True) + shutil.rmtree(self.directory, ignore_errors=True) def broadcast_event(self, event_data: EventData) -> None: """ @@ -876,7 +876,7 @@ class Session: :param state: state to write to file :return: nothing """ - state_file = self.session_dir / "state" + state_file = self.directory / "state" try: with state_file.open("w") as f: f.write(f"{state.value} {state.name}\n") @@ -904,8 +904,8 @@ class Session: """ file_name, data = hook logging.info("running hook %s", file_name) - file_path = self.session_dir / file_name - log_path = self.session_dir / f"{file_name}.log" + file_path = self.directory / file_name + log_path = self.directory / f"{file_name}.log" try: with file_path.open("w") as f: f.write(data) @@ -916,7 +916,7 @@ class Session: stdout=f, stderr=subprocess.STDOUT, close_fds=True, - cwd=self.session_dir, + cwd=self.directory, env=self.get_environment(), ) except (IOError, subprocess.CalledProcessError): @@ -982,7 +982,7 @@ class Session: # create session deployed xml xml_writer = corexml.CoreXmlWriter(self) corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario) - xml_file_path = self.session_dir / "session-deployed.xml" + xml_file_path = self.directory / "session-deployed.xml" xml_writer.write(xml_file_path) def get_environment(self, state: bool = True) -> Dict[str, str]: @@ -998,7 +998,7 @@ class Session: env["CORE_PYTHON"] = sys.executable env["SESSION"] = str(self.id) env["SESSION_SHORT"] = self.short_session_id() - env["SESSION_DIR"] = str(self.session_dir) + env["SESSION_DIR"] = str(self.directory) env["SESSION_NAME"] = str(self.name) env["SESSION_FILENAME"] = str(self.file_path) env["SESSION_USER"] = str(self.user) @@ -1009,7 +1009,7 @@ class Session: # /home/user/.core/environment # /tmp/pycore./environment core_env_path = constants.CORE_CONF_DIR / "environment" - session_env_path = self.session_dir / "environment" + session_env_path = self.directory / "environment" if self.user: user_home_path = Path(f"~{self.user}").expanduser() user_env1 = user_home_path / ".core" / "environment" @@ -1036,7 +1036,7 @@ class Session: logging.error("thumbnail file to set does not exist: %s", thumb_file) self.thumbnail = None return - dst_path = self.session_dir / thumb_file.name + dst_path = self.directory / thumb_file.name shutil.copy(thumb_file, dst_path) self.thumbnail = dst_path @@ -1051,10 +1051,10 @@ class Session: if user: try: uid = pwd.getpwnam(user).pw_uid - gid = self.session_dir.stat().st_gid - os.chown(self.session_dir, uid, gid) + gid = self.directory.stat().st_gid + os.chown(self.directory, uid, gid) except IOError: - logging.exception("failed to set permission on %s", self.session_dir) + logging.exception("failed to set permission on %s", self.directory) self.user = user def create_node( @@ -1137,7 +1137,7 @@ class Session: Write nodes to a 'nodes' file in the session dir. The 'nodes' file lists: number, name, api-type, class-type """ - file_path = self.session_dir / "nodes" + file_path = self.directory / "nodes" try: with self.nodes_lock: with file_path.open("w") as f: diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index d4e7ee38..6acd6be7 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -1154,6 +1154,4 @@ class Ns2ScriptedMobility(WayPointMobility): filename = Path(filename) filename = self.findfile(filename) args = f"{BASH} {filename} {typestr}" - utils.cmd( - args, cwd=self.session.session_dir, env=self.session.get_environment() - ) + utils.cmd(args, cwd=self.session.directory, env=self.session.get_environment()) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 0af4d5ae..52caa670 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -363,7 +363,7 @@ class CoreNodeBase(NodeBase): :return: nothing """ if self.nodedir is None: - self.nodedir = self.session.session_dir / f"{self.name}.conf" + self.nodedir = self.session.directory / f"{self.name}.conf" self.host_cmd(f"mkdir -p {self.nodedir}") self.tmpnodedir = True else: @@ -490,7 +490,7 @@ class CoreNode(CoreNodeBase): """ super().__init__(session, _id, name, server) self.nodedir: Optional[Path] = nodedir - self.ctrlchnlname: Path = self.session.session_dir / self.name + self.ctrlchnlname: Path = self.session.directory / self.name self.client: Optional[VnodeClient] = None self.pid: Optional[int] = None self.lock: RLock = RLock() diff --git a/daemon/core/utils.py b/daemon/core/utils.py index d434b169..0ea6f66a 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -285,7 +285,7 @@ def expand_corepath( if session is not None: pathname = pathname.replace("~", f"/home/{session.user}") pathname = pathname.replace("%SESSION%", str(session.id)) - pathname = pathname.replace("%SESSION_DIR%", str(session.session_dir)) + pathname = pathname.replace("%SESSION_DIR%", str(session.directory)) pathname = pathname.replace("%SESSION_USER%", session.user) if node is not None: pathname = pathname.replace("%NODE%", str(node.id)) diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index ee736da4..06983dc2 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -95,7 +95,7 @@ def create_node_file( if isinstance(node, CoreNode): file_path = node.nodedir / file_name else: - file_path = node.session.session_dir / file_name + file_path = node.session.directory / file_name create_file(xml_element, doc_name, file_path, node.server) From be0e0175a2b89a2de9a2fb9ac8199429e10bf011 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 19 Mar 2021 17:01:22 -0700 Subject: [PATCH 003/110] daemon: refactored node.nodedir to node.directory --- daemon/core/api/grpc/grpcutils.py | 2 +- daemon/core/emane/emanemanager.py | 4 ++-- daemon/core/nodes/base.py | 24 ++++++++++++------------ daemon/core/nodes/docker.py | 6 +++--- daemon/core/nodes/lxd.py | 6 +++--- daemon/core/nodes/physical.py | 8 ++++---- daemon/core/xml/emanexml.py | 2 +- daemon/tests/test_gui.py | 2 +- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 5d5eb456..34dc6c03 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -271,7 +271,7 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node: node_dir = None config_services = [] if isinstance(node, CoreNodeBase): - node_dir = str(node.nodedir) + node_dir = str(node.directory) config_services = [x for x in node.config_services] channel = None if isinstance(node, CoreNode): diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index b3841357..5d8e0c07 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -590,8 +590,8 @@ class EmaneManager(ModelManager): if eventservicenetidx >= 0 and eventgroup != otagroup: node.node_net_client.create_route(eventgroup, eventdev) # start emane - log_file = node.nodedir / f"{node.name}-emane.log" - platform_xml = node.nodedir / f"{node.name}-platform.xml" + log_file = node.directory / f"{node.name}-emane.log" + platform_xml = node.directory / f"{node.name}-platform.xml" args = f"{emanecmd} -f {log_file} {platform_xml}" node.cmd(args) logging.info("node(%s) emane daemon running: %s", node.name, args) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 52caa670..15c001d5 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -223,7 +223,7 @@ class CoreNodeBase(NodeBase): """ super().__init__(session, _id, name, server) self.config_services: Dict[str, "ConfigService"] = {} - self.nodedir: Optional[Path] = None + self.directory: Optional[Path] = None self.tmpnodedir: bool = False @abc.abstractmethod @@ -314,10 +314,10 @@ class CoreNodeBase(NodeBase): """ if is_dir: directory = str(path).strip("/").replace("/", ".") - return self.nodedir / directory + return self.directory / directory else: directory = str(path.parent).strip("/").replace("/", ".") - return self.nodedir / directory / path.name + return self.directory / directory / path.name def add_config_service(self, service_class: "ConfigServiceType") -> None: """ @@ -362,9 +362,9 @@ class CoreNodeBase(NodeBase): :return: nothing """ - if self.nodedir is None: - self.nodedir = self.session.directory / f"{self.name}.conf" - self.host_cmd(f"mkdir -p {self.nodedir}") + if self.directory is None: + self.directory = self.session.directory / f"{self.name}.conf" + self.host_cmd(f"mkdir -p {self.directory}") self.tmpnodedir = True else: self.tmpnodedir = False @@ -379,7 +379,7 @@ class CoreNodeBase(NodeBase): if preserve: return if self.tmpnodedir: - self.host_cmd(f"rm -rf {self.nodedir}") + self.host_cmd(f"rm -rf {self.directory}") def add_iface(self, iface: CoreInterface, iface_id: int) -> None: """ @@ -475,7 +475,7 @@ class CoreNode(CoreNodeBase): session: "Session", _id: int = None, name: str = None, - nodedir: Path = None, + directory: Path = None, server: "DistributedServer" = None, ) -> None: """ @@ -484,12 +484,12 @@ class CoreNode(CoreNodeBase): :param session: core session instance :param _id: object id :param name: object name - :param nodedir: node directory + :param directory: node directory :param server: remote server node will run on, default is None for localhost """ super().__init__(session, _id, name, server) - self.nodedir: Optional[Path] = nodedir + self.directory: Optional[Path] = directory self.ctrlchnlname: Path = self.session.directory / self.name self.client: Optional[VnodeClient] = None self.pid: Optional[int] = None @@ -539,8 +539,8 @@ class CoreNode(CoreNodeBase): f"{VNODED} -v -c {self.ctrlchnlname} -l {self.ctrlchnlname}.log " f"-p {self.ctrlchnlname}.pid" ) - if self.nodedir: - vnoded += f" -C {self.nodedir}" + if self.directory: + vnoded += f" -C {self.directory}" env = self.session.get_environment(state=False) env["NODE_NUMBER"] = str(self.id) env["NODE_NAME"] = str(self.name) diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index aa925a7d..baf204cc 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -76,7 +76,7 @@ class DockerNode(CoreNode): session: "Session", _id: int = None, name: str = None, - nodedir: str = None, + directory: str = None, server: DistributedServer = None, image: str = None, ) -> None: @@ -86,7 +86,7 @@ class DockerNode(CoreNode): :param session: core session instance :param _id: object id :param name: object name - :param nodedir: node directory + :param directory: node directory :param server: remote server node will run on, default is None for localhost :param image: image to start container with @@ -94,7 +94,7 @@ class DockerNode(CoreNode): if image is None: image = "ubuntu" self.image: str = image - super().__init__(session, _id, name, nodedir, server) + super().__init__(session, _id, name, directory, 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 d6352dc0..858baf9c 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -72,7 +72,7 @@ class LxcNode(CoreNode): session: "Session", _id: int = None, name: str = None, - nodedir: str = None, + directory: str = None, server: DistributedServer = None, image: str = None, ) -> None: @@ -82,7 +82,7 @@ class LxcNode(CoreNode): :param session: core session instance :param _id: object id :param name: object name - :param nodedir: node directory + :param directory: node directory :param server: remote server node will run on, default is None for localhost :param image: image to start container with @@ -90,7 +90,7 @@ class LxcNode(CoreNode): if image is None: image = "ubuntu" self.image: str = image - super().__init__(session, _id, name, nodedir, server) + super().__init__(session, _id, name, directory, server) def alive(self) -> bool: """ diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index c326c4f0..03847752 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -26,13 +26,13 @@ class PhysicalNode(CoreNodeBase): session: "Session", _id: int = None, name: str = None, - nodedir: Path = None, + directory: Path = None, server: DistributedServer = None, ) -> None: super().__init__(session, _id, name, server) if not self.server: raise CoreError("physical nodes must be assigned to a remote server") - self.nodedir: Optional[Path] = nodedir + self.directory: Optional[Path] = directory self.lock: threading.RLock = threading.RLock() self._mounts: List[Tuple[Path, Path]] = [] @@ -193,13 +193,13 @@ class PhysicalNode(CoreNodeBase): def mount(self, src_path: Path, target_path: Path) -> None: logging.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path) self.cmd(f"mkdir -p {target_path}") - self.host_cmd(f"{MOUNT} --bind {src_path} {target_path}", cwd=self.nodedir) + self.host_cmd(f"{MOUNT} --bind {src_path} {target_path}", cwd=self.directory) self._mounts.append((src_path, target_path)) def umount(self, target_path: Path) -> None: logging.info("unmounting '%s'", target_path) try: - self.host_cmd(f"{UMOUNT} -l {target_path}", cwd=self.nodedir) + self.host_cmd(f"{UMOUNT} -l {target_path}", cwd=self.directory) except CoreCommandError: logging.exception("unmounting failed for %s", target_path) diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 06983dc2..b2bff91f 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -93,7 +93,7 @@ def create_node_file( :return: """ if isinstance(node, CoreNode): - file_path = node.nodedir / file_name + file_path = node.directory / file_name else: file_path = node.session.directory / file_name create_file(xml_element, doc_name, file_path, node.server) diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 732b57d1..8e9c943b 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -443,7 +443,7 @@ class TestGui: if not request.config.getoption("mock"): directory = str(file_path.parent) created_directory = directory[1:].replace("/", ".") - create_path = node.nodedir / created_directory / file_path.name + create_path = node.directory / created_directory / file_path.name assert create_path.exists() def test_exec_node_tty(self, coretlv: CoreHandler): From f7f54d9aa6591439c7aa83b8ac4c2d0c732ff7ca Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Mar 2021 10:43:45 -0700 Subject: [PATCH 004/110] pygui: updates to leveraged wrapped grpc client for proper type hinting without manual conversion --- daemon/core/api/grpc/clientw.py | 18 +- daemon/core/api/grpc/wrappers.py | 8 +- daemon/core/gui/coreclient.py | 348 ++++++------------ .../core/gui/dialogs/configserviceconfig.py | 10 +- daemon/core/gui/dialogs/mobilityplayer.py | 6 +- daemon/core/gui/dialogs/nodeconfig.py | 16 +- daemon/core/gui/dialogs/runtool.py | 6 +- daemon/core/gui/dialogs/serviceconfig.py | 24 +- daemon/core/gui/dialogs/sessionoptions.py | 7 +- daemon/core/gui/dialogs/sessions.py | 7 +- daemon/core/gui/graph/node.py | 4 +- 11 files changed, 160 insertions(+), 294 deletions(-) diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index 36ec69ad..1371218c 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -66,6 +66,7 @@ from core.api.grpc.wlan_pb2 import ( WlanConfig, WlanLinkRequest, ) +from core.api.grpc.wrappers import Hook from core.emulator.data import IpPrefixes @@ -859,25 +860,16 @@ class CoreGrpcClient: hooks.append(hook) return hooks - def add_hook( - self, - session_id: int, - state: wrappers.SessionState, - file_name: str, - file_data: str, - ) -> bool: + def add_hook(self, session_id: int, hook: Hook) -> bool: """ Add hook scripts. :param session_id: session id - :param state: state to trigger hook - :param file_name: name of file for hook script - :param file_data: hook script contents + :param hook: hook to add :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ - hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data) - request = core_pb2.AddHookRequest(session_id=session_id, hook=hook) + request = core_pb2.AddHookRequest(session_id=session_id, hook=hook.to_proto()) response = self.stub.AddHook(request) return response.result @@ -1277,7 +1269,7 @@ class CoreGrpcClient: :param file_path: path of scenario XML file :param start: tuple of result and session id when successful - :return: response with opened session id + :return: tuple of result and session id """ with open(file_path, "r") as xml_file: data = xml_file.read() diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index 1ef43be2..d3231509 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -171,15 +171,16 @@ class ConfigServiceData: class ConfigServiceDefaults: templates: Dict[str, str] config: Dict[str, "ConfigOption"] - modes: List[str] + modes: Dict[str, Dict[str, str]] @classmethod def from_proto( cls, proto: configservices_pb2.GetConfigServicesResponse ) -> "ConfigServiceDefaults": config = ConfigOption.from_dict(proto.config) + modes = {x.name: dict(x.config) for x in proto.modes} return ConfigServiceDefaults( - templates=dict(proto.templates), config=config, modes=list(proto.modes) + templates=dict(proto.templates), config=config, modes=modes ) @@ -598,11 +599,12 @@ class EmaneModelConfig: ) def to_proto(self) -> emane_pb2.EmaneModelConfig: + config = ConfigOption.to_dict(self.config) return emane_pb2.EmaneModelConfig( node_id=self.node_id, model=self.model, iface_id=self.iface_id, - config=self.config, + config=config, ) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index d2f0cb87..214cc3b4 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -11,18 +11,12 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple import grpc -from core.api.grpc import ( - client, - configservices_pb2, - core_pb2, - emane_pb2, - mobility_pb2, - services_pb2, - wlan_pb2, -) +from core.api.grpc import clientw, configservices_pb2, core_pb2 from core.api.grpc.wrappers import ( ConfigOption, ConfigService, + EmaneModelConfig, + Event, ExceptionEvent, Link, LinkEvent, @@ -33,6 +27,7 @@ from core.api.grpc.wrappers import ( NodeServiceData, NodeType, Position, + ServiceConfig, Session, SessionLocation, SessionState, @@ -67,7 +62,7 @@ class CoreClient: """ self.app: "Application" = app self.master: tk.Tk = app.master - self._client: client.CoreGrpcClient = client.CoreGrpcClient(proxy=proxy) + self._client: clientw.CoreGrpcClient = clientw.CoreGrpcClient(proxy=proxy) self.session: Optional[Session] = None self.user = getpass.getuser() @@ -96,10 +91,9 @@ class CoreClient: self.handling_events: Optional[grpc.Future] = None @property - def client(self) -> client.CoreGrpcClient: + def client(self) -> clientw.CoreGrpcClient: if self.session: - response = self._client.check_session(self.session.id) - if not response.result: + if not self._client.check_session(self.session.id): throughputs_enabled = self.handling_throughputs is not None self.cancel_throughputs() self.cancel_events() @@ -150,7 +144,7 @@ class CoreClient: for observer in self.app.guiconfig.observers: self.custom_observers[observer.name] = observer - def handle_events(self, event: core_pb2.Event) -> None: + def handle_events(self, event: Event) -> None: if not self.session or event.source == GUI_SOURCE: return if event.session_id != self.session.id: @@ -160,11 +154,9 @@ class CoreClient: self.session.id, ) return - - if event.HasField("link_event"): - link_event = LinkEvent.from_proto(event.link_event) - self.app.after(0, self.handle_link_event, link_event) - elif event.HasField("session_event"): + if event.link_event: + self.app.after(0, self.handle_link_event, event.link_event) + elif event.session_event: logging.info("session event: %s", event) session_event = event.session_event if session_event.event <= SessionState.SHUTDOWN.value: @@ -181,14 +173,12 @@ class CoreClient: dialog.set_pause() else: logging.warning("unknown session event: %s", session_event) - elif event.HasField("node_event"): - node_event = NodeEvent.from_proto(event.node_event) - self.app.after(0, self.handle_node_event, node_event) - elif event.HasField("config_event"): + elif event.node_event: + self.app.after(0, self.handle_node_event, event.node_event) + elif event.config_event: logging.info("config event: %s", event) - elif event.HasField("exception_event"): - event = ExceptionEvent.from_proto(event.session_id, event.exception_event) - self.handle_exception_event(event) + elif event.exception_event: + self.handle_exception_event(event.exception_event) else: logging.info("unhandled event: %s", event) @@ -278,8 +268,7 @@ class CoreClient: CPU_USAGE_DELAY, self.handle_cpu_event ) - def handle_throughputs(self, event: core_pb2.ThroughputsEvent) -> None: - event = ThroughputsEvent.from_proto(event) + def handle_throughputs(self, event: ThroughputsEvent) -> None: if event.session_id != self.session.id: logging.warning( "ignoring throughput event session(%s) current(%s)", @@ -301,8 +290,7 @@ class CoreClient: logging.info("joining session(%s)", session_id) self.reset() try: - response = self.client.get_session(session_id) - self.session = Session.from_proto(response.session) + self.session = self.client.get_session(session_id) self.client.set_session_user(self.session.id, self.user) title_file = self.session.file.name if self.session.file else "" self.master.title(f"CORE Session({self.session.id}) {title_file}") @@ -364,9 +352,9 @@ class CoreClient: Create a new session """ try: - response = self.client.create_session() - logging.info("created session: %s", response) - self.join_session(response.session_id) + session_id = self.client.create_session() + logging.info("created session: %s", session_id) + self.join_session(session_id) location_config = self.app.guiconfig.location self.session.location = SessionLocation( x=location_config.x, @@ -398,22 +386,19 @@ class CoreClient: try: self.client.connect() # get all available services - response = self.client.get_services() - for service in response.services: + for service in self.client.get_services(): group_services = self.services.setdefault(service.group, set()) group_services.add(service.name) # get config service informations - response = self.client.get_config_services() - for service in response.services: - self.config_services[service.name] = ConfigService.from_proto(service) + for service in self.client.get_config_services(): + self.config_services[service.name] = service group_services = self.config_services_groups.setdefault( service.group, set() ) group_services.add(service.name) # join provided session, create new session, or show dialog to select an # existing session - response = self.client.get_sessions() - sessions = response.sessions + sessions = self.client.get_sessions() if session_id: session_ids = set(x.id for x in sessions) if session_id not in session_ids: @@ -438,9 +423,8 @@ class CoreClient: def edit_node(self, core_node: Node) -> None: try: - position = core_node.position.to_proto() self.client.edit_node( - self.session.id, core_node.id, position, source=GUI_SOURCE + self.session.id, core_node.id, core_node.position, source=GUI_SOURCE ) except grpc.RpcError as e: self.app.show_grpc_exception("Edit Node Error", e) @@ -451,7 +435,6 @@ class CoreClient: def start_session(self) -> Tuple[bool, List[str]]: self.ifaces_manager.set_macs([x.link for x in self.links.values()]) - nodes = [x.to_proto() for x in self.session.nodes.values()] links = [] asymmetric_links = [] for edge in self.links.values(): @@ -460,43 +443,20 @@ class CoreClient: link.iface1.mac = self.ifaces_manager.next_mac() if link.iface2 and not link.iface2.mac: link.iface2.mac = self.ifaces_manager.next_mac() - links.append(link.to_proto()) + links.append(link) if edge.asymmetric_link: - asymmetric_links.append(edge.asymmetric_link.to_proto()) - wlan_configs = self.get_wlan_configs_proto() - mobility_configs = self.get_mobility_configs_proto() - emane_model_configs = self.get_emane_model_configs_proto() - hooks = [x.to_proto() for x in self.session.hooks.values()] - service_configs = self.get_service_configs_proto() - file_configs = self.get_service_file_configs_proto() - config_service_configs = self.get_config_service_configs_proto() - emane_config = to_dict(self.session.emane_config) + asymmetric_links.append(edge.asymmetric_link) + self.session.links = links result = False exceptions = [] try: self.send_servers() - response = self.client.start_session( - self.session.id, - nodes, - links, - self.session.location.to_proto(), - hooks, - emane_config, - emane_model_configs, - wlan_configs, - mobility_configs, - service_configs, - file_configs, - asymmetric_links, - config_service_configs, + result, exceptions = self.client.start_session( + self.session, asymmetric_links ) - logging.info( - "start session(%s), result: %s", self.session.id, response.result - ) - if response.result: + logging.info("start session(%s), result: %s", self.session.id, result) + if result: self.set_metadata() - result = response.result - exceptions = response.exceptions except grpc.RpcError as e: self.app.show_grpc_exception("Start Session Error", e) return result, exceptions @@ -506,9 +466,8 @@ class CoreClient: session_id = self.session.id result = False try: - response = self.client.stop_session(session_id) - logging.info("stopped session(%s), result: %s", session_id, response) - result = response.result + result = self.client.stop_session(session_id) + logging.info("stopped session(%s), result: %s", session_id, result) except grpc.RpcError as e: self.app.show_grpc_exception("Stop Session Error", e) return result @@ -564,8 +523,8 @@ class CoreClient: parent=self.app, ) return - response = self.client.get_node_terminal(self.session.id, node_id) - cmd = f"{terminal} {response.terminal} &" + node_term = self.client.get_node_terminal(self.session.id, node_id) + cmd = f"{terminal} {node_term} &" logging.info("launching terminal %s", cmd) os.system(cmd) except grpc.RpcError as e: @@ -587,8 +546,8 @@ class CoreClient: if not self.is_runtime(): logging.debug("Send session data to the daemon") self.send_data() - response = self.client.save_xml(self.session.id, file_path) - logging.info("saved xml file %s, result: %s", file_path, response) + self.client.save_xml(self.session.id, file_path) + logging.info("saved xml file %s", file_path) except grpc.RpcError as e: self.app.show_grpc_exception("Save XML Error", e) @@ -597,72 +556,50 @@ class CoreClient: Open core xml """ try: - response = self._client.open_xml(file_path) - logging.info("open xml file %s, response: %s", file_path, response) - self.join_session(response.session_id) + result, session_id = self._client.open_xml(file_path) + logging.info( + "open xml file %s, result(%s) session(%s)", + file_path, + result, + session_id, + ) + self.join_session(session_id) except grpc.RpcError as e: self.app.show_grpc_exception("Open XML Error", e) def get_node_service(self, node_id: int, service_name: str) -> NodeServiceData: - response = self.client.get_node_service(self.session.id, node_id, service_name) + node_service = self.client.get_node_service( + self.session.id, node_id, service_name + ) logging.debug( - "get node(%s) %s service, response: %s", node_id, service_name, response + "get node(%s) service(%s): %s", node_id, service_name, node_service ) - return NodeServiceData.from_proto(response.service) + return node_service - def set_node_service( - self, - node_id: int, - service_name: str, - dirs: List[str], - files: List[str], - startups: List[str], - validations: List[str], - shutdowns: List[str], - ) -> NodeServiceData: - response = self.client.set_node_service( - self.session.id, - node_id, - service_name, - directories=dirs, - files=files, - startup=startups, - validate=validations, - shutdown=shutdowns, - ) - logging.info( - "Set %s service for node(%s), files: %s, Startup: %s, " - "Validation: %s, Shutdown: %s, Result: %s", - service_name, - node_id, - files, - startups, - validations, - shutdowns, - response, - ) - response = self.client.get_node_service(self.session.id, node_id, service_name) - return NodeServiceData.from_proto(response.service) + def set_node_service(self, node_id: int, config: ServiceConfig) -> NodeServiceData: + result = self.client.set_node_service(self.session.id, config) + logging.info("set node service result(%s): %s", result, config) + return self.client.get_node_service(self.session.id, node_id, config.service) def get_node_service_file( self, node_id: int, service_name: str, file_name: str ) -> str: - response = self.client.get_node_service_file( + data = self.client.get_node_service_file( self.session.id, node_id, service_name, file_name ) logging.debug( - "get service file for node(%s), service: %s, file: %s, result: %s", + "get service file for node(%s), service: %s, file: %s, data: %s", node_id, service_name, file_name, - response, + data, ) - return response.data + return data 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( + result = self.client.set_node_service_file( self.session.id, node_id, service_name, file_name, data ) logging.info( @@ -671,19 +608,17 @@ class CoreClient: service_name, file_name, data, - response, + result, ) def create_nodes_and_links(self) -> None: """ create nodes and links that have not been created yet """ - self.client.set_session_state(self.session.id, SessionState.DEFINITION.value) + self.client.set_session_state(self.session.id, SessionState.DEFINITION) for node in self.session.nodes.values(): - response = self.client.add_node( - self.session.id, node.to_proto(), source=GUI_SOURCE - ) - logging.debug("created node: %s", response) + node_id = self.client.add_node(self.session.id, node, source=GUI_SOURCE) + logging.debug("created node: %s", node_id) asymmetric_links = [] for edge in self.links.values(): self.add_link(edge.link) @@ -698,58 +633,23 @@ class CoreClient: """ self.send_servers() self.create_nodes_and_links() - for config_proto in self.get_wlan_configs_proto(): - self.client.set_wlan_config( - self.session.id, config_proto.node_id, config_proto.config - ) - for config_proto in self.get_mobility_configs_proto(): - self.client.set_mobility_config( - self.session.id, config_proto.node_id, config_proto.config - ) - for config_proto in self.get_service_configs_proto(): - self.client.set_node_service( - self.session.id, - config_proto.node_id, - config_proto.service, - config_proto.files, - config_proto.directories, - config_proto.startup, - config_proto.validate, - config_proto.shutdown, - ) - for config_proto in self.get_service_file_configs_proto(): + for node_id, config in self.get_wlan_configs(): + self.client.set_wlan_config(self.session.id, node_id, config) + for node_id, config in self.get_mobility_configs(): + self.client.set_mobility_config(self.session.id, node_id, config) + for config in self.get_service_configs(): + self.client.set_node_service(self.session.id, config) + for node_id, service, file, data in self.get_service_file_configs(): self.client.set_node_service_file( - self.session.id, - config_proto.node_id, - config_proto.service, - config_proto.file, - config_proto.data, + self.session.id, node_id, service, file, data ) for hook in self.session.hooks.values(): - self.client.add_hook( - self.session.id, hook.state.value, hook.file, hook.data - ) - for config_proto in self.get_emane_model_configs_proto(): - self.client.set_emane_model_config( - self.session.id, - config_proto.node_id, - config_proto.model, - config_proto.config, - config_proto.iface_id, - ) + self.client.add_hook(self.session.id, hook) + for config in self.get_emane_model_configs(): + self.client.set_emane_model_config(self.session.id, config) config = to_dict(self.session.emane_config) self.client.set_emane_config(self.session.id, config) - location = self.session.location - self.client.set_session_location( - self.session.id, - location.x, - location.y, - location.z, - location.lat, - location.lon, - location.alt, - location.scale, - ) + self.client.set_session_location(self.session.id, self.session.location) self.set_metadata() def close(self) -> None: @@ -850,7 +750,7 @@ class CoreClient: dst_iface_id = edge.link.iface2.id self.iface_to_edge[(dst_node.id, dst_iface_id)] = edge - def get_wlan_configs_proto(self) -> List[wlan_pb2.WlanConfig]: + def get_wlan_configs(self) -> List[Tuple[int, Dict[str, str]]]: configs = [] for node in self.session.nodes.values(): if node.type != NodeType.WIRELESS_LAN: @@ -858,11 +758,10 @@ class CoreClient: if not node.wlan_config: continue config = ConfigOption.to_dict(node.wlan_config) - wlan_config = wlan_pb2.WlanConfig(node_id=node.id, config=config) - configs.append(wlan_config) + configs.append((node.id, config)) return configs - def get_mobility_configs_proto(self) -> List[mobility_pb2.MobilityConfig]: + def get_mobility_configs(self) -> List[Tuple[int, Dict[str, str]]]: configs = [] for node in self.session.nodes.values(): if not nutils.is_mobility(node): @@ -870,27 +769,24 @@ class CoreClient: if not node.mobility_config: continue config = ConfigOption.to_dict(node.mobility_config) - mobility_config = mobility_pb2.MobilityConfig( - node_id=node.id, config=config - ) - configs.append(mobility_config) + configs.append((node.id, config)) return configs - def get_emane_model_configs_proto(self) -> List[emane_pb2.EmaneModelConfig]: + def get_emane_model_configs(self) -> List[EmaneModelConfig]: configs = [] for node in self.session.nodes.values(): for key, config in node.emane_model_configs.items(): model, iface_id = key - config = ConfigOption.to_dict(config) + # config = ConfigOption.to_dict(config) if iface_id is None: iface_id = -1 - config_proto = emane_pb2.EmaneModelConfig( - node_id=node.id, iface_id=iface_id, model=model, config=config + config = EmaneModelConfig( + node_id=node.id, model=model, iface_id=iface_id, config=config ) - configs.append(config_proto) + configs.append(config) return configs - def get_service_configs_proto(self) -> List[services_pb2.ServiceConfig]: + def get_service_configs(self) -> List[ServiceConfig]: configs = [] for node in self.session.nodes.values(): if not nutils.is_container(node): @@ -898,19 +794,19 @@ class CoreClient: if not node.service_configs: continue for name, config in node.service_configs.items(): - config_proto = services_pb2.ServiceConfig( + config = ServiceConfig( node_id=node.id, service=name, - directories=config.dirs, files=config.configs, + directories=config.dirs, startup=config.startup, validate=config.validate, shutdown=config.shutdown, ) - configs.append(config_proto) + configs.append(config) return configs - def get_service_file_configs_proto(self) -> List[services_pb2.ServiceFileConfig]: + def get_service_file_configs(self) -> List[Tuple[int, str, str, str]]: configs = [] for node in self.session.nodes.values(): if not nutils.is_container(node): @@ -919,10 +815,7 @@ class CoreClient: continue for service, file_configs in node.service_file_configs.items(): for file, data in file_configs.items(): - config_proto = services_pb2.ServiceFileConfig( - node_id=node.id, service=service, file=file, data=data - ) - configs.append(config_proto) + configs.append((node.id, service, file, data)) return configs def get_config_service_configs_proto( @@ -946,37 +839,35 @@ class CoreClient: def run(self, node_id: int) -> str: logging.info("running node(%s) cmd: %s", node_id, self.observer) - return self.client.node_command(self.session.id, node_id, self.observer).output + _, output = self.client.node_command(self.session.id, node_id, self.observer) + return output 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 + config = self.client.get_wlan_config(self.session.id, node_id) logging.debug( "get wlan configuration from node %s, result configuration: %s", node_id, config, ) - return ConfigOption.from_dict(config) + return config 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 + config = self.client.get_mobility_config(self.session.id, node_id) logging.debug( "get mobility config from node %s, result configuration: %s", node_id, config, ) - return ConfigOption.from_dict(config) + return config def get_emane_model_config( self, node_id: int, model: str, iface_id: int = None ) -> Dict[str, ConfigOption]: if iface_id is None: iface_id = -1 - response = self.client.get_emane_model_config( + config = self.client.get_emane_model_config( 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", @@ -985,42 +876,21 @@ class CoreClient: iface_id, config, ) - return ConfigOption.from_dict(config) + return config def execute_script(self, script) -> None: - response = self.client.execute_script(script) - logging.info("execute python script %s", response) - if response.session_id != -1: - self.join_session(response.session_id) + session_id = self.client.execute_script(script) + logging.info("execute python script %s", session_id) + if session_id != -1: + self.join_session(session_id) def add_link(self, link: Link) -> None: - iface1 = link.iface1.to_proto() if link.iface1 else None - iface2 = link.iface2.to_proto() if link.iface2 else None - options = link.options.to_proto() if link.options else None - response = self.client.add_link( - self.session.id, - link.node1_id, - link.node2_id, - iface1, - iface2, - options, - source=GUI_SOURCE, - ) - logging.debug("added link: %s", response) - if not response.result: + result, _, _ = self.client.add_link(self.session.id, link, source=GUI_SOURCE) + logging.debug("added link: %s", result) + if not result: logging.error("error adding link: %s", link) def edit_link(self, link: Link) -> None: - iface1_id = link.iface1.id if link.iface1 else None - iface2_id = link.iface2.id if link.iface2 else None - response = self.client.edit_link( - self.session.id, - link.node1_id, - link.node2_id, - link.options.to_proto(), - iface1_id, - iface2_id, - source=GUI_SOURCE, - ) - if not response.result: + result = self.client.edit_link(self.session.id, link, source=GUI_SOURCE) + if not result: logging.error("error editing link: %s", link) diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 14388f5a..25c3613c 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -86,12 +86,12 @@ class ConfigServiceConfigDialog(Dialog): self.validation_time = service.validation_timer self.validation_period.set(service.validation_period) - response = self.core.client.get_config_service_defaults(self.service_name) - self.original_service_files = response.templates + defaults = self.core.client.get_config_service_defaults(self.service_name) + self.original_service_files = defaults.templates self.temp_service_files = dict(self.original_service_files) - self.modes = sorted(x.name for x in response.modes) - self.mode_configs = {x.name: x.config for x in response.modes} - self.config = ConfigOption.from_dict(response.config) + self.modes = sorted(defaults.modes) + self.mode_configs = defaults.modes + self.config = ConfigOption.from_dict(defaults.config) self.default_config = {x.name: x.value for x in self.config.values()} service_config = self.node.config_service_configs.get(self.service_name) if service_config: diff --git a/daemon/core/gui/dialogs/mobilityplayer.py b/daemon/core/gui/dialogs/mobilityplayer.py index c27cd2a5..7b6c4d9f 100644 --- a/daemon/core/gui/dialogs/mobilityplayer.py +++ b/daemon/core/gui/dialogs/mobilityplayer.py @@ -134,7 +134,7 @@ class MobilityPlayerDialog(Dialog): session_id = self.app.core.session.id try: self.app.core.client.mobility_action( - session_id, self.node.id, MobilityAction.START.value + session_id, self.node.id, MobilityAction.START ) except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) @@ -144,7 +144,7 @@ class MobilityPlayerDialog(Dialog): session_id = self.app.core.session.id try: self.app.core.client.mobility_action( - session_id, self.node.id, MobilityAction.PAUSE.value + session_id, self.node.id, MobilityAction.PAUSE ) except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) @@ -154,7 +154,7 @@ class MobilityPlayerDialog(Dialog): session_id = self.app.core.session.id try: self.app.core.client.mobility_action( - session_id, self.node.id, MobilityAction.STOP.value + session_id, self.node.id, MobilityAction.STOP ) except grpc.RpcError as e: self.app.show_grpc_exception("Mobility Error", e) diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index a78df050..a263c7be 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -260,17 +260,17 @@ class NodeConfigDialog(Dialog): row += 1 if nutils.is_rj45(self.node): - response = self.app.core.client.get_ifaces() - logging.debug("host machine available interfaces: %s", response) - ifaces = ListboxScroll(frame) - ifaces.listbox.config(state=state) - ifaces.grid( + ifaces = self.app.core.client.get_ifaces() + logging.debug("host machine available interfaces: %s", ifaces) + ifaces_scroll = ListboxScroll(frame) + ifaces_scroll.listbox.config(state=state) + ifaces_scroll.grid( row=row, column=0, columnspan=2, sticky=tk.EW, padx=PADX, pady=PADY ) - for inf in sorted(response.ifaces[:]): - ifaces.listbox.insert(tk.END, inf) + for inf in sorted(ifaces): + ifaces_scroll.listbox.insert(tk.END, inf) row += 1 - ifaces.listbox.bind("<>", self.iface_select) + ifaces_scroll.listbox.bind("<>", self.iface_select) # interfaces if self.canvas_node.ifaces: diff --git a/daemon/core/gui/dialogs/runtool.py b/daemon/core/gui/dialogs/runtool.py index 707ac2b8..494020e3 100644 --- a/daemon/core/gui/dialogs/runtool.py +++ b/daemon/core/gui/dialogs/runtool.py @@ -106,10 +106,8 @@ class RunToolDialog(Dialog): for selection in self.node_list.listbox.curselection(): node_name = self.node_list.listbox.get(selection) node_id = self.executable_nodes[node_name] - response = self.app.core.client.node_command( + _, output = self.app.core.client.node_command( self.app.core.session.id, node_id, command ) - self.result.text.insert( - tk.END, f"> {node_name} > {command}:\n{response.output}\n" - ) + self.result.text.insert(tk.END, f"> {node_name} > {command}:\n{output}\n") self.result.text.config(state=tk.DISABLED) diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 65a9e353..a5e67f02 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -7,7 +7,12 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple import grpc from PIL.ImageTk import PhotoImage -from core.api.grpc.wrappers import Node, NodeServiceData, ServiceValidationMode +from core.api.grpc.wrappers import ( + Node, + NodeServiceData, + ServiceConfig, + ServiceValidationMode, +) from core.gui import images from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog from core.gui.dialogs.dialog import Dialog @@ -455,16 +460,17 @@ class ServiceConfigDialog(Dialog): or self.is_custom_directory() ): startup, validate, shutdown = self.get_commands() - config = self.core.set_node_service( - self.node.id, - self.service_name, - dirs=self.temp_directories, + config = ServiceConfig( + node_id=self.node.id, + service=self.service_name, files=list(self.filename_combobox["values"]), - startups=startup, - validations=validate, - shutdowns=shutdown, + directories=self.temp_directories, + startup=startup, + validate=validate, + shutdown=shutdown, ) - self.node.service_configs[self.service_name] = config + service_data = self.core.set_node_service(self.node.id, config) + self.node.service_configs[self.service_name] = service_data for file in self.modified_files: file_configs = self.node.service_file_configs.setdefault( self.service_name, {} diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index 4b086d67..c642b084 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -27,8 +27,7 @@ class SessionOptionsDialog(Dialog): 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) - return ConfigOption.from_dict(response.config) + return self.app.core.client.get_session_options(session_id) except grpc.RpcError as e: self.app.show_grpc_exception("Get Session Options Error", e) self.has_error = True @@ -55,8 +54,8 @@ class SessionOptionsDialog(Dialog): config = self.config_frame.parse_config() try: session_id = self.app.core.session.id - response = self.app.core.client.set_session_options(session_id, config) - logging.info("saved session config: %s", response) + result = self.app.core.client.set_session_options(session_id, config) + logging.info("saved session config: %s", result) except grpc.RpcError as e: self.app.show_grpc_exception("Set Session Options Error", e) self.destroy() diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index efcbbeea..ebf2218d 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -30,10 +30,9 @@ class SessionsDialog(Dialog): def get_sessions(self) -> List[SessionSummary]: try: - response = self.app.core.client.get_sessions() - logging.info("sessions: %s", response) - sessions = sorted(response.sessions, key=lambda x: x.id) - return [SessionSummary.from_proto(x) for x in sessions] + sessions = self.app.core.client.get_sessions() + logging.info("sessions: %s", sessions) + return sorted(sessions, key=lambda x: x.id) except grpc.RpcError as e: self.app.show_grpc_exception("Get Sessions Error", e) self.destroy() diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index c58741ba..e81a1022 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -459,10 +459,10 @@ class CanvasNode: def _service_action(self, service: str, action: ServiceAction) -> None: session_id = self.app.core.session.id try: - response = self.app.core.client.service_action( + result = self.app.core.client.service_action( session_id, self.core_node.id, service, action ) - if not response.result: + if not result: self.app.show_error("Service Action Error", "Action Failed!") except grpc.RpcError as e: self.app.show_grpc_exception("Service Error", e) From bb3590fbde8da6adc986976aeedbadf78821038f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 26 Mar 2021 10:56:33 -0700 Subject: [PATCH 005/110] daemon: fix to correct rj45 link modifications from core-gui failing to include an iface id --- daemon/core/api/tlv/corehandlers.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 37a1cdf8..aec29b99 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -788,13 +788,19 @@ class CoreHandler(socketserver.BaseRequestHandler): if dup is not None: options.dup = int(dup) + # fix for rj45 nodes missing iface id + node1 = self.session.get_node(node1_id, NodeBase) + node2 = self.session.get_node(node2_id, NodeBase) + if isinstance(node1, Rj45Node) and iface1_data.id is None: + iface1_data.id = 0 + if isinstance(node2, Rj45Node) and iface2_data.id is None: + iface2_data.id = 0 + if message.flags & MessageFlags.ADD.value: self.session.add_link( node1_id, node2_id, iface1_data, iface2_data, options, link_type ) elif message.flags & MessageFlags.DELETE.value: - node1 = self.session.get_node(node1_id, NodeBase) - node2 = self.session.get_node(node2_id, NodeBase) if isinstance(node1, Rj45Node): iface1_data.id = node1.iface_id if isinstance(node2, Rj45Node): From 6086d1229bbced66f31dcbcfd1a2a14673c5d1c8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 31 Mar 2021 11:13:40 -0700 Subject: [PATCH 006/110] daemon: updated config.py to use dataclasses for config classes, updated naming and referencing. updated configurable options to self validate default values align with the config type. updated the example emane model to better align with the current state of things --- daemon/core/config.py | 82 +++++++++---------- .../securityservices/services.py | 24 +++--- daemon/core/configservices/simpleservice.py | 8 +- daemon/core/emane/bypass.py | 4 +- daemon/core/emane/emanemanager.py | 24 +++--- daemon/core/emane/emanemanifest.py | 4 +- daemon/core/emane/tdma.py | 4 +- daemon/core/emulator/sessionconfig.py | 32 ++++---- daemon/core/errors.py | 8 ++ daemon/core/location/mobility.py | 52 ++++++------ daemon/examples/myemane/examplemodel.py | 26 +++++- daemon/tests/test_conf.py | 4 +- daemon/tests/test_config_services.py | 8 +- docs/emane.md | 24 +++++- 14 files changed, 171 insertions(+), 133 deletions(-) diff --git a/daemon/core/config.py b/daemon/core/config.py index 222abf01..1fe32adc 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -4,10 +4,12 @@ Common support for configurable CORE objects. import logging from collections import OrderedDict -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Type, Union +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple, Type, Union from core.emane.nodes import EmaneNet from core.emulator.enumerations import ConfigDataTypes +from core.errors import CoreConfigError from core.nodes.network import WlanNode if TYPE_CHECKING: @@ -15,62 +17,56 @@ if TYPE_CHECKING: WirelessModelType = Type[WirelessModel] +_BOOL_OPTIONS: Set[str] = {"0", "1"} + +@dataclass class ConfigGroup: """ Defines configuration group tabs used for display by ConfigurationOptions. """ - def __init__(self, name: str, start: int, stop: int) -> None: - """ - Creates a ConfigGroup object. - - :param name: configuration group display name - :param start: configurations start index for this group - :param stop: configurations stop index for this group - """ - self.name: str = name - self.start: int = start - self.stop: int = stop + name: str + start: int + stop: int +@dataclass class Configuration: """ Represents a configuration options. """ - def __init__( - self, - _id: str, - _type: ConfigDataTypes, - label: str = None, - default: str = "", - options: List[str] = None, - ) -> None: - """ - Creates a Configuration object. + id: str + type: ConfigDataTypes + label: str = None + default: str = "" + options: List[str] = field(default_factory=list) - :param _id: unique name for configuration - :param _type: configuration data type - :param label: configuration label for display - :param default: default value for configuration - :param options: list options if this is a configuration with a combobox - """ - self.id: str = _id - self.type: ConfigDataTypes = _type - self.default: str = default - if not options: - options = [] - self.options: List[str] = options - if not label: - label = _id - self.label: str = label - - def __str__(self): - return ( - f"{self.__class__.__name__}(id={self.id}, type={self.type}, " - f"default={self.default}, options={self.options})" - ) + def __post_init__(self) -> None: + self.label = self.label if self.label else self.id + if self.type == ConfigDataTypes.BOOL: + if self.default and self.default not in _BOOL_OPTIONS: + raise CoreConfigError( + f"{self.id} bool value must be one of: {_BOOL_OPTIONS}: " + f"{self.default}" + ) + elif self.type == ConfigDataTypes.FLOAT: + if self.default: + try: + float(self.default) + except ValueError: + raise CoreConfigError( + f"{self.id} is not a valid float: {self.default}" + ) + elif self.type != ConfigDataTypes.STRING: + if self.default: + try: + int(self.default) + except ValueError: + raise CoreConfigError( + f"{self.id} is not a valid int: {self.default}" + ) class ConfigurableOptions: diff --git a/daemon/core/configservices/securityservices/services.py b/daemon/core/configservices/securityservices/services.py index c656f5ca..9da27010 100644 --- a/daemon/core/configservices/securityservices/services.py +++ b/daemon/core/configservices/securityservices/services.py @@ -20,20 +20,20 @@ class VpnClient(ConfigService): validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING default_configs: List[Configuration] = [ Configuration( - _id="keydir", - _type=ConfigDataTypes.STRING, + id="keydir", + type=ConfigDataTypes.STRING, label="Key Dir", default="/etc/core/keys", ), Configuration( - _id="keyname", - _type=ConfigDataTypes.STRING, + id="keyname", + type=ConfigDataTypes.STRING, label="Key Name", default="client1", ), Configuration( - _id="server", - _type=ConfigDataTypes.STRING, + id="server", + type=ConfigDataTypes.STRING, label="Server", default="10.0.2.10", ), @@ -54,20 +54,20 @@ class VpnServer(ConfigService): validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING default_configs: List[Configuration] = [ Configuration( - _id="keydir", - _type=ConfigDataTypes.STRING, + id="keydir", + type=ConfigDataTypes.STRING, label="Key Dir", default="/etc/core/keys", ), Configuration( - _id="keyname", - _type=ConfigDataTypes.STRING, + id="keyname", + type=ConfigDataTypes.STRING, label="Key Name", default="server", ), Configuration( - _id="subnet", - _type=ConfigDataTypes.STRING, + id="subnet", + type=ConfigDataTypes.STRING, label="Subnet", default="10.0.200.0", ), diff --git a/daemon/core/configservices/simpleservice.py b/daemon/core/configservices/simpleservice.py index c2e7242f..4370977d 100644 --- a/daemon/core/configservices/simpleservice.py +++ b/daemon/core/configservices/simpleservice.py @@ -17,11 +17,11 @@ class SimpleService(ConfigService): 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(id="value1", type=ConfigDataTypes.STRING, label="Text"), + Configuration(id="value2", type=ConfigDataTypes.BOOL, label="Boolean"), Configuration( - _id="value3", - _type=ConfigDataTypes.STRING, + id="value3", + type=ConfigDataTypes.STRING, label="Multiple Choice", options=["value1", "value2", "value3"], ), diff --git a/daemon/core/emane/bypass.py b/daemon/core/emane/bypass.py index 8aabc3f9..aebdde21 100644 --- a/daemon/core/emane/bypass.py +++ b/daemon/core/emane/bypass.py @@ -18,8 +18,8 @@ class EmaneBypassModel(emanemodel.EmaneModel): mac_library: str = "bypassmaclayer" mac_config: List[Configuration] = [ Configuration( - _id="none", - _type=ConfigDataTypes.BOOL, + id="none", + type=ConfigDataTypes.BOOL, default="0", label="There are no parameters for the bypass model.", ) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 5d8e0c07..2eb28c28 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -825,38 +825,38 @@ class EmaneGlobalModel: self.session: "Session" = session self.core_config: List[Configuration] = [ Configuration( - _id="platform_id_start", - _type=ConfigDataTypes.INT32, + id="platform_id_start", + type=ConfigDataTypes.INT32, default="1", label="Starting Platform ID", ), Configuration( - _id="nem_id_start", - _type=ConfigDataTypes.INT32, + id="nem_id_start", + type=ConfigDataTypes.INT32, default="1", label="Starting NEM ID", ), Configuration( - _id="link_enabled", - _type=ConfigDataTypes.BOOL, + id="link_enabled", + type=ConfigDataTypes.BOOL, default="1", label="Enable Links?", ), Configuration( - _id="loss_threshold", - _type=ConfigDataTypes.INT32, + id="loss_threshold", + type=ConfigDataTypes.INT32, default="30", label="Link Loss Threshold (%)", ), Configuration( - _id="link_interval", - _type=ConfigDataTypes.INT32, + id="link_interval", + type=ConfigDataTypes.INT32, default="1", label="Link Check Interval (sec)", ), Configuration( - _id="link_timeout", - _type=ConfigDataTypes.INT32, + id="link_timeout", + type=ConfigDataTypes.INT32, default="4", label="Link Timeout (sec)", ), diff --git a/daemon/core/emane/emanemanifest.py b/daemon/core/emane/emanemanifest.py index 8e09d040..6ada2da7 100644 --- a/daemon/core/emane/emanemanifest.py +++ b/daemon/core/emane/emanemanifest.py @@ -118,8 +118,8 @@ def parse(manifest_path: Path, defaults: Dict[str, str]) -> List[Configuration]: config_descriptions = f"{config_descriptions} file" configuration = Configuration( - _id=config_name, - _type=config_type_value, + id=config_name, + type=config_type_value, default=config_default, options=possible, label=config_descriptions, diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py index 1ddb14ad..35ea55b0 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/tdma.py @@ -35,8 +35,8 @@ class EmaneTdmaModel(emanemodel.EmaneModel): ) super().load(emane_prefix) config_item = Configuration( - _id=cls.schedule_name, - _type=ConfigDataTypes.STRING, + id=cls.schedule_name, + type=ConfigDataTypes.STRING, default=str(cls.default_schedule), label="TDMA schedule file (core)", ) diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py index 9b22bcc7..c4099fcd 100644 --- a/daemon/core/emulator/sessionconfig.py +++ b/daemon/core/emulator/sessionconfig.py @@ -13,51 +13,51 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions): name: str = "session" options: List[Configuration] = [ Configuration( - _id="controlnet", _type=ConfigDataTypes.STRING, label="Control Network" + id="controlnet", type=ConfigDataTypes.STRING, label="Control Network" ), Configuration( - _id="controlnet0", _type=ConfigDataTypes.STRING, label="Control Network 0" + id="controlnet0", type=ConfigDataTypes.STRING, label="Control Network 0" ), Configuration( - _id="controlnet1", _type=ConfigDataTypes.STRING, label="Control Network 1" + id="controlnet1", type=ConfigDataTypes.STRING, label="Control Network 1" ), Configuration( - _id="controlnet2", _type=ConfigDataTypes.STRING, label="Control Network 2" + id="controlnet2", type=ConfigDataTypes.STRING, label="Control Network 2" ), Configuration( - _id="controlnet3", _type=ConfigDataTypes.STRING, label="Control Network 3" + id="controlnet3", type=ConfigDataTypes.STRING, label="Control Network 3" ), Configuration( - _id="controlnet_updown_script", - _type=ConfigDataTypes.STRING, + id="controlnet_updown_script", + type=ConfigDataTypes.STRING, label="Control Network Script", ), Configuration( - _id="enablerj45", - _type=ConfigDataTypes.BOOL, + id="enablerj45", + type=ConfigDataTypes.BOOL, default="1", label="Enable RJ45s", ), Configuration( - _id="preservedir", - _type=ConfigDataTypes.BOOL, + id="preservedir", + type=ConfigDataTypes.BOOL, default="0", label="Preserve session dir", ), Configuration( - _id="enablesdt", - _type=ConfigDataTypes.BOOL, + id="enablesdt", + type=ConfigDataTypes.BOOL, default="0", label="Enable SDT3D output", ), Configuration( - _id="sdturl", - _type=ConfigDataTypes.STRING, + id="sdturl", + type=ConfigDataTypes.STRING, default=Sdt.DEFAULT_SDT_URL, label="SDT3D URL", ), Configuration( - _id="ovs", _type=ConfigDataTypes.BOOL, default="0", label="Enable OVS" + id="ovs", type=ConfigDataTypes.BOOL, default="0", label="Enable OVS" ), ] config_type: RegisterTlvs = RegisterTlvs.UTILITY diff --git a/daemon/core/errors.py b/daemon/core/errors.py index a75bd536..20ffc3a9 100644 --- a/daemon/core/errors.py +++ b/daemon/core/errors.py @@ -46,3 +46,11 @@ class CoreServiceBootError(Exception): """ pass + + +class CoreConfigError(Exception): + """ + Used when there is an error defining a configurable option. + """ + + pass diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 6acd6be7..8861d7f6 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -236,35 +236,35 @@ class BasicRangeModel(WirelessModel): name: str = "basic_range" options: List[Configuration] = [ Configuration( - _id="range", - _type=ConfigDataTypes.UINT32, + id="range", + type=ConfigDataTypes.UINT32, default="275", label="wireless range (pixels)", ), Configuration( - _id="bandwidth", - _type=ConfigDataTypes.UINT64, + id="bandwidth", + type=ConfigDataTypes.UINT64, default="54000000", label="bandwidth (bps)", ), Configuration( - _id="jitter", - _type=ConfigDataTypes.UINT64, + id="jitter", + type=ConfigDataTypes.UINT64, default="0", label="transmission jitter (usec)", ), Configuration( - _id="delay", - _type=ConfigDataTypes.UINT64, + id="delay", + type=ConfigDataTypes.UINT64, default="5000", label="transmission delay (usec)", ), Configuration( - _id="error", _type=ConfigDataTypes.STRING, default="0", label="loss (%)" + id="error", type=ConfigDataTypes.STRING, default="0", label="loss (%)" ), Configuration( - _id="promiscuous", - _type=ConfigDataTypes.BOOL, + id="promiscuous", + type=ConfigDataTypes.BOOL, default="0", label="promiscuous mode", ), @@ -868,40 +868,38 @@ class Ns2ScriptedMobility(WayPointMobility): name: str = "ns2script" options: List[Configuration] = [ Configuration( - _id="file", _type=ConfigDataTypes.STRING, label="mobility script file" + id="file", type=ConfigDataTypes.STRING, label="mobility script file" ), Configuration( - _id="refresh_ms", - _type=ConfigDataTypes.UINT32, + id="refresh_ms", + type=ConfigDataTypes.UINT32, default="50", label="refresh time (ms)", ), + Configuration(id="loop", type=ConfigDataTypes.BOOL, default="1", label="loop"), Configuration( - _id="loop", _type=ConfigDataTypes.BOOL, default="1", label="loop" - ), - Configuration( - _id="autostart", - _type=ConfigDataTypes.STRING, + id="autostart", + type=ConfigDataTypes.STRING, label="auto-start seconds (0.0 for runtime)", ), Configuration( - _id="map", - _type=ConfigDataTypes.STRING, + id="map", + type=ConfigDataTypes.STRING, label="node mapping (optional, e.g. 0:1,1:2,2:3)", ), Configuration( - _id="script_start", - _type=ConfigDataTypes.STRING, + id="script_start", + type=ConfigDataTypes.STRING, label="script file to run upon start", ), Configuration( - _id="script_pause", - _type=ConfigDataTypes.STRING, + id="script_pause", + type=ConfigDataTypes.STRING, label="script file to run upon pause", ), Configuration( - _id="script_stop", - _type=ConfigDataTypes.STRING, + id="script_stop", + type=ConfigDataTypes.STRING, label="script file to run upon stop", ), ] diff --git a/daemon/examples/myemane/examplemodel.py b/daemon/examples/myemane/examplemodel.py index b9e6e148..c33ac166 100644 --- a/daemon/examples/myemane/examplemodel.py +++ b/daemon/examples/myemane/examplemodel.py @@ -1,6 +1,7 @@ """ Example custom emane model. """ +from pathlib import Path from typing import Dict, List, Optional, Set from core.config import Configuration @@ -39,17 +40,34 @@ class ExampleModel(emanemodel.EmaneModel): name: str = "emane_example" mac_library: str = "rfpipemaclayer" - mac_xml: str = "/usr/share/emane/manifest/rfpipemaclayer.xml" + mac_xml: str = "rfpipemaclayer.xml" mac_defaults: Dict[str, str] = { "pcrcurveuri": "/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml" } - mac_config: List[Configuration] = emanemanifest.parse(mac_xml, mac_defaults) + mac_config: List[Configuration] = [] phy_library: Optional[str] = None - phy_xml: str = "/usr/share/emane/manifest/emanephy.xml" + phy_xml: str = "emanephy.xml" phy_defaults: Dict[str, str] = { "subid": "1", "propagationmodel": "2ray", "noisemode": "none", } - phy_config: List[Configuration] = emanemanifest.parse(phy_xml, phy_defaults) + phy_config: List[Configuration] = [] config_ignore: Set[str] = set() + + @classmethod + def load(cls, emane_prefix: Path) -> None: + """ + Called after being loaded within the EmaneManager. Provides configured + emane_prefix for parsing xml files. + + :param emane_prefix: configured emane prefix path + :return: nothing + """ + manifest_path = "share/emane/manifest" + # load mac configuration + mac_xml_path = emane_prefix / manifest_path / cls.mac_xml + cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults) + # load phy configuration + phy_xml_path = emane_prefix / manifest_path / cls.phy_xml + cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults) diff --git a/daemon/tests/test_conf.py b/daemon/tests/test_conf.py index e90acfbd..994a09f3 100644 --- a/daemon/tests/test_conf.py +++ b/daemon/tests/test_conf.py @@ -17,8 +17,8 @@ class TestConfigurableOptions(ConfigurableOptions): name1 = "value1" name2 = "value2" options = [ - Configuration(_id=name1, _type=ConfigDataTypes.STRING, label=name1), - Configuration(_id=name2, _type=ConfigDataTypes.STRING, label=name2), + Configuration(id=name1, type=ConfigDataTypes.STRING, label=name1), + Configuration(id=name2, type=ConfigDataTypes.STRING, label=name2), ] diff --git a/daemon/tests/test_config_services.py b/daemon/tests/test_config_services.py index 7efde087..432f2089 100644 --- a/daemon/tests/test_config_services.py +++ b/daemon/tests/test_config_services.py @@ -27,11 +27,11 @@ class MyService(ConfigService): shutdown = [f"pkill {files[0]}"] validation_mode = ConfigServiceMode.BLOCKING default_configs = [ - Configuration(_id="value1", _type=ConfigDataTypes.STRING, label="Text"), - Configuration(_id="value2", _type=ConfigDataTypes.BOOL, label="Boolean"), + Configuration(id="value1", type=ConfigDataTypes.STRING, label="Text"), + Configuration(id="value2", type=ConfigDataTypes.BOOL, label="Boolean"), Configuration( - _id="value3", - _type=ConfigDataTypes.STRING, + id="value3", + type=ConfigDataTypes.STRING, label="Multiple Choice", options=["value1", "value2", "value3"], ), diff --git a/docs/emane.md b/docs/emane.md index f589f834..9e2e5b0b 100644 --- a/docs/emane.md +++ b/docs/emane.md @@ -120,7 +120,8 @@ Here is an example model with documentation describing functionality: """ Example custom emane model. """ -from typing import Dict, List, Optional, Set +from pathlib import Path +from typing import Dict, Optional, Set, List from core.config import Configuration from core.emane import emanemanifest, emanemodel @@ -162,14 +163,31 @@ class ExampleModel(emanemodel.EmaneModel): mac_defaults: Dict[str, str] = { "pcrcurveuri": "/usr/share/emane/xml/models/mac/rfpipe/rfpipepcr.xml" } - mac_config: List[Configuration] = emanemanifest.parse(mac_xml, mac_defaults) + mac_config: List[Configuration] = [] phy_library: Optional[str] = None phy_xml: str = "/usr/share/emane/manifest/emanephy.xml" phy_defaults: Dict[str, str] = { "subid": "1", "propagationmodel": "2ray", "noisemode": "none" } - phy_config: List[Configuration] = emanemanifest.parse(phy_xml, phy_defaults) + phy_config: List[Configuration] = [] config_ignore: Set[str] = set() + + @classmethod + def load(cls, emane_prefix: Path) -> None: + """ + Called after being loaded within the EmaneManager. Provides configured + emane_prefix for parsing xml files. + + :param emane_prefix: configured emane prefix path + :return: nothing + """ + manifest_path = "share/emane/manifest" + # load mac configuration + mac_xml_path = emane_prefix / manifest_path / cls.mac_xml + cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults) + # load phy configuration + phy_xml_path = emane_prefix / manifest_path / cls.phy_xml + cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults) ``` ## Single PC with EMANE From 44f81391c4ece5141838f7bdaa125d140305e10f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 1 Apr 2021 13:37:19 -0700 Subject: [PATCH 007/110] daemon: added grpc wrapped client tests, added new wrapped class ServiceFileConfig to consolidate associated data for its purpose --- daemon/core/api/grpc/clientw.py | 36 +- daemon/core/api/grpc/wrappers.py | 33 + daemon/core/gui/coreclient.py | 29 +- daemon/core/gui/menubar.py | 1 + daemon/tests/test_grpcw.py | 1272 ++++++++++++++++++++++++++++++ 5 files changed, 1335 insertions(+), 36 deletions(-) create mode 100644 daemon/tests/test_grpcw.py diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index 1371218c..b7785662 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -5,6 +5,7 @@ gRpc client for interfacing with CORE. import logging import threading from contextlib import contextmanager +from pathlib import Path from queue import Queue from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple @@ -54,7 +55,6 @@ from core.api.grpc.services_pb2 import ( GetServicesRequest, ServiceActionRequest, ServiceDefaults, - ServiceFileConfig, SetNodeServiceFileRequest, SetNodeServiceRequest, SetServiceDefaultsRequest, @@ -68,6 +68,7 @@ from core.api.grpc.wlan_pb2 import ( ) from core.api.grpc.wrappers import Hook from core.emulator.data import IpPrefixes +from core.errors import CoreError class MoveNodesStreamer: @@ -675,13 +676,17 @@ class CoreGrpcClient: :return: True for success, False otherwise :raises grpc.RpcError: when session or node doesn't exist """ + if position and geo: + raise CoreError("cannot edit position and geo at same time") + position_proto = position.to_proto() if position else None + geo_proto = geo.to_proto() if geo else None request = core_pb2.EditNodeRequest( session_id=session_id, node_id=node_id, - position=position.to_proto(), + position=position_proto, icon=icon, source=source, - geo=geo.to_proto(), + geo=geo_proto, ) response = self.stub.EditNode(request) return response.result @@ -994,7 +999,7 @@ class CoreGrpcClient: def get_node_service_configs( self, session_id: int - ) -> List[wrappers.NodeServiceData]: + ) -> List[wrappers.NodeServiceConfig]: """ Get service data for a node. @@ -1005,8 +1010,8 @@ class CoreGrpcClient: request = GetNodeServiceConfigsRequest(session_id=session_id) response = self.stub.GetNodeServiceConfigs(request) node_services = [] - for service_proto in response.configs: - node_service = wrappers.NodeServiceData.from_proto(service_proto) + for config in response.configs: + node_service = wrappers.NodeServiceConfig.from_proto(config) node_services.append(node_service) return node_services @@ -1065,22 +1070,17 @@ class CoreGrpcClient: return response.result def set_node_service_file( - self, session_id: int, node_id: int, service: str, file_name: str, data: str + self, session_id: int, service_file_config: wrappers.ServiceFileConfig ) -> bool: """ Set a service file for a node. :param session_id: session id - :param node_id: node id - :param service: service name - :param file_name: file name to save - :param data: data to save for file + :param service_file_config: configuration to set :return: True for success, False otherwise :raises grpc.RpcError: when session or node doesn't exist """ - config = ServiceFileConfig( - node_id=node_id, service=service, file=file_name, data=data - ) + config = service_file_config.to_proto() request = SetNodeServiceFileRequest(session_id=session_id, config=config) response = self.stub.SetNodeServiceFile(request) return response.result @@ -1263,7 +1263,7 @@ class CoreGrpcClient: with open(file_path, "w") as xml_file: xml_file.write(response.data) - def open_xml(self, file_path: str, start: bool = False) -> Tuple[bool, int]: + def open_xml(self, file_path: Path, start: bool = False) -> Tuple[bool, int]: """ Load a local scenario XML file to open as a new session. @@ -1271,9 +1271,9 @@ class CoreGrpcClient: :param start: tuple of result and session id when successful :return: tuple of result and session id """ - with open(file_path, "r") as xml_file: - data = xml_file.read() - request = core_pb2.OpenXmlRequest(data=data, start=start, file=file_path) + with file_path.open("r") as f: + data = f.read() + request = core_pb2.OpenXmlRequest(data=data, start=start, file=str(file_path)) response = self.stub.OpenXml(request) return response.result, response.session_id diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index d3231509..d68a075b 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -233,6 +233,23 @@ class NodeServiceData: ) +@dataclass +class NodeServiceConfig: + node_id: int + service: str + data: NodeServiceData + files: Dict[str, str] = field(default_factory=dict) + + @classmethod + def from_proto(cls, proto: services_pb2.NodeServiceConfig) -> "NodeServiceConfig": + return NodeServiceConfig( + node_id=proto.node_id, + service=proto.service, + data=NodeServiceData.from_proto(proto.data), + files=dict(proto.files), + ) + + @dataclass class ServiceConfig: node_id: int @@ -255,6 +272,19 @@ class ServiceConfig: ) +@dataclass +class ServiceFileConfig: + node_id: int + service: str + file: str + data: str = field(repr=False) + + def to_proto(self) -> services_pb2.ServiceFileConfig: + return services_pb2.ServiceFileConfig( + node_id=self.node_id, service=self.service, file=self.file, data=self.data + ) + + @dataclass class BridgeThroughput: node_id: int @@ -724,6 +754,9 @@ class Session: metadata: Dict[str, str] file: Path + def set_node(self, node: Node) -> None: + self.nodes[node.id] = node + @classmethod def from_proto(cls, proto: core_pb2.Session) -> "Session": nodes: Dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes} diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 214cc3b4..744a5be8 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -6,6 +6,7 @@ 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, Set, Tuple @@ -28,6 +29,7 @@ from core.api.grpc.wrappers import ( NodeType, Position, ServiceConfig, + ServiceFileConfig, Session, SessionLocation, SessionState, @@ -551,7 +553,7 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("Save XML Error", e) - def open_xml(self, file_path: str) -> None: + def open_xml(self, file_path: Path) -> None: """ Open core xml """ @@ -599,17 +601,9 @@ class CoreClient: def set_node_service_file( self, node_id: int, service_name: str, file_name: str, data: str ) -> None: - result = self.client.set_node_service_file( - self.session.id, node_id, service_name, file_name, data - ) - logging.info( - "set node(%s) service file, service: %s, file: %s, data: %s, result: %s", - node_id, - service_name, - file_name, - data, - result, - ) + config = ServiceFileConfig(node_id, service_name, file_name, data) + result = self.client.set_node_service_file(self.session.id, config) + logging.info("set service file config %s: %s", config, result) def create_nodes_and_links(self) -> None: """ @@ -639,10 +633,8 @@ class CoreClient: self.client.set_mobility_config(self.session.id, node_id, config) for config in self.get_service_configs(): self.client.set_node_service(self.session.id, config) - for node_id, service, file, data in self.get_service_file_configs(): - self.client.set_node_service_file( - self.session.id, node_id, service, file, data - ) + for config in self.get_service_file_configs(): + self.client.set_node_service_file(self.session.id, config) for hook in self.session.hooks.values(): self.client.add_hook(self.session.id, hook) for config in self.get_emane_model_configs(): @@ -806,7 +798,7 @@ class CoreClient: configs.append(config) return configs - def get_service_file_configs(self) -> List[Tuple[int, str, str, str]]: + def get_service_file_configs(self) -> List[ServiceFileConfig]: configs = [] for node in self.session.nodes.values(): if not nutils.is_container(node): @@ -815,7 +807,8 @@ class CoreClient: continue for service, file_configs in node.service_file_configs.items(): for file, data in file_configs.items(): - configs.append((node.id, service, file, data)) + config = ServiceFileConfig(node.id, service, file, data) + configs.append(config) return configs def get_config_service_configs_proto( diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 60df3a8e..10bf0926 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -312,6 +312,7 @@ class Menubar(tk.Menu): filetypes=(("XML Files", "*.xml"), ("All Files", "*")), ) if file_path: + file_path = Path(file_path) self.open_xml_task(file_path) def open_xml_task(self, file_path: Path) -> None: diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py new file mode 100644 index 00000000..69915150 --- /dev/null +++ b/daemon/tests/test_grpcw.py @@ -0,0 +1,1272 @@ +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 core_pb2 +from core.api.grpc.clientw 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, + Node, + NodeServiceData, + NodeType, + Position, + ServiceAction, + ServiceConfig, + ServiceFileConfig, + ServiceValidationMode, + SessionLocation, + SessionState, +) +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, IpPrefixes, NodeData, NodeOptions +from core.emulator.enumerations import EventTypes, ExceptionLevels +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 TestGrpcw: + def test_start_session(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + with client.context_connect(): + session_id = client.create_session() + session = client.get_session(session_id) + position = Position(x=50, y=100) + node1 = Node( + id=1, name="n1", position=position, type=NodeType.DEFAULT, model="PC" + ) + position = Position(x=100, y=100) + node2 = Node( + id=2, name="n2", position=position, type=NodeType.DEFAULT, model="PC" + ) + position = Position(x=200, y=200) + wlan_node = Node(id=3, name="n3", type=NodeType.WIRELESS_LAN, position=position) + session.set_node(node1) + session.set_node(node2) + session.set_node(wlan_node) + 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 global emane config + emane_config_key = "platform_id_start" + emane_config_value = "2" + option = ConfigOption( + label=emane_config_key, + name=emane_config_key, + value=emane_config_value, + type=ConfigOptionType.INT64, + group="Default", + ) + session.emane_config[emane_config_key] = option + + # setup wlan config + wlan_config_key = "range" + wlan_config_value = "333" + option = ConfigOption( + label=wlan_config_key, + name=wlan_config_key, + value=wlan_config_value, + type=ConfigOptionType.INT64, + group="Default", + ) + wlan_node.wlan_config[wlan_config_key] = option + + # setup mobility config + mobility_config_key = "refresh_ms" + mobility_config_value = "60" + option = ConfigOption( + label=mobility_config_key, + name=mobility_config_key, + value=mobility_config_value, + type=ConfigOptionType.INT64, + group="Default", + ) + wlan_node.mobility_config[mobility_config_key] = option + + # 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} + + # when + with patch.object(CoreXmlWriter, "write"): + with client.context_connect(): + client.start_session(session) + + # then + real_session = grpc_server.coreemu.sessions[session.id] + 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 + assert real_session.emane.get_config(emane_config_key) == emane_config_value + 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 + + @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_id = client.create_session(session_id) + + # then + assert isinstance(created_session_id, int) + 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_get_session_options(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + + # then + with client.context_connect(): + config = client.get_session_options(session.id) + + # then + assert len(config) > 0 + + def test_get_session_location(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + + # then + with client.context_connect(): + location = client.get_session_location(session.id) + + # then + assert location.scale == 1.0 + assert location.x == 0 + assert location.y == 0 + assert location.z == 0 + assert location.lat == 0 + assert location.lon == 0 + assert location.alt == 0 + + def test_set_session_location(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + scale = 2 + xyz = (1, 1, 1) + lat_lon_alt = (1, 1, 1) + location = SessionLocation( + xyz[0], + xyz[1], + xyz[2], + lat_lon_alt[0], + lat_lon_alt[1], + lat_lon_alt[2], + scale, + ) + + # then + with client.context_connect(): + result = client.set_session_location(session.id, location) + + # then + assert result is True + assert session.location.refxyz == xyz + assert session.location.refscale == scale + assert session.location.refgeo == lat_lon_alt + + def test_set_session_options(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + + # then + option = "enablerj45" + value = "1" + with client.context_connect(): + result = client.set_session_options(session.id, {option: value}) + + # then + assert result is True + assert session.options.get_config(option) == value + config = session.options.get_configs() + assert len(config) > 0 + + def test_set_session_metadata(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + + # then + key = "meta1" + value = "value1" + with client.context_connect(): + result = client.set_session_metadata(session.id, {key: value}) + + # then + assert result is True + assert session.metadata[key] == value + + def test_get_session_metadata(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + key = "meta1" + value = "value1" + session.metadata[key] = value + + # then + with client.context_connect(): + config = client.get_session_metadata(session.id) + + # then + assert config[key] == value + + def test_set_session_state(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + + # then + with client.context_connect(): + result = client.set_session_state(session.id, SessionState.DEFINITION) + + # then + assert result is True + assert session.state == EventTypes.DEFINITION_STATE + + 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 = client.get_node(session.id, node.id) + + # then + assert node.id == get_node.id + + def test_edit_node(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + node = session.add_node(CoreNode) + + # then + x, y = 10, 10 + with client.context_connect(): + position = Position(x=x, y=y) + result = client.edit_node(session.id, node.id, position) + + # then + assert result is True + assert node.position.x == x + assert node.position.y == y + + def test_edit_node_exception(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + node = session.add_node(CoreNode) + + # then + x, y = 10, 10 + with client.context_connect(): + position = Position(x=x, y=y) + geo = Geo(lat=0, lon=0, alt=0) + with pytest.raises(CoreError): + client.edit_node(session.id, node.id, position, geo=geo) + + @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) + options = NodeOptions(model="Host") + node = session.add_node(CoreNode, options=options) + session.instantiate() + expected_output = "hello world" + + # then + command = f"echo {expected_output}" + with client.context_connect(): + output = client.node_command(session.id, node.id, command) + + # then + assert 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) + options = NodeOptions(model="Host") + node = session.add_node(CoreNode, options=options) + session.instantiate() + + # then + with client.context_connect(): + terminal = client.get_node_terminal(session.id, node.id) + + # then + assert terminal is not None + + def test_get_hooks(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + file_name = "test" + file_data = "echo hello" + session.add_hook(EventTypes.RUNTIME_STATE, file_name, file_data) + + # then + with client.context_connect(): + hooks = client.get_hooks(session.id) + + # then + assert len(hooks) == 1 + hook = hooks[0] + assert hook.state == SessionState.RUNTIME + assert hook.file == file_name + assert hook.data == file_data + + def test_add_hook(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + hook = Hook(SessionState.RUNTIME, "test", "echo hello") + + # then + with client.context_connect(): + result = client.add_hook(session.id, hook) + + # then + assert result is True + + 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_get_node_links(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + switch = session.add_node(SwitchNode) + node = session.add_node(CoreNode) + iface_data = ip_prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface_data) + + # then + with client.context_connect(): + links = client.get_node_links(session.id, switch.id) + + # then + assert len(links) == 1 + + def test_get_node_links_exception( + self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes + ): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + switch = session.add_node(SwitchNode) + node = session.add_node(CoreNode) + 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): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + switch = session.add_node(SwitchNode) + node = session.add_node(CoreNode) + assert len(switch.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(switch.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() + switch = session.add_node(SwitchNode) + node = session.add_node(CoreNode) + iface = ip_prefixes.create_iface(node) + session.add_link(node.id, switch.id, iface) + options = LinkOptions(bandwidth=30000) + link = switch.links()[0] + assert options.bandwidth != link.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 + link = switch.links()[0] + assert options.bandwidth == link.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) + link_node = None + for node_id in session.nodes: + node = session.nodes[node_id] + if node.id not in {node1.id, node2.id}: + link_node = node + break + assert len(link_node.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(link_node.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.model.range == int(range_value) + + def test_get_emane_config(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + + # then + with client.context_connect(): + config = client.get_emane_config(session.id) + + # then + assert len(config) > 0 + + def test_set_emane_config(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + config_key = "platform_id_start" + config_value = "2" + + # then + with client.context_connect(): + result = client.set_emane_config(session.id, {config_key: config_value}) + + # then + assert result is True + config = session.emane.get_configs() + assert len(config) > 1 + assert config[config_key] == config_value + + def test_get_emane_model_configs(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 = NodeOptions(emane=EmaneIeee80211abgModel.name) + emane_network = session.add_node(EmaneNet, options=options) + session.emane.set_model(emane_network, EmaneIeee80211abgModel) + config_key = "platform_id_start" + config_value = "2" + session.emane.set_model_config( + emane_network.id, EmaneIeee80211abgModel.name, {config_key: config_value} + ) + + # then + with client.context_connect(): + configs = client.get_emane_model_configs(session.id) + + # then + assert len(configs) == 1 + model_config = configs[0] + assert emane_network.id == model_config.node_id + assert model_config.model == EmaneIeee80211abgModel.name + assert len(model_config.config) > 0 + assert model_config.iface_id is None + + 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 = NodeOptions(emane=EmaneIeee80211abgModel.name) + emane_network = session.add_node(EmaneNet, options=options) + session.emane.set_model(emane_network, EmaneIeee80211abgModel) + 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_model_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 = NodeOptions(emane=EmaneIeee80211abgModel.name) + emane_network = session.add_node(EmaneNet, options=options) + session.emane.set_model(emane_network, EmaneIeee80211abgModel) + + # 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_emane_models(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + + # then + with client.context_connect(): + models = client.get_emane_models(session.id) + + # then + assert len(models) > 0 + + def test_get_mobility_configs(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(): + configs = client.get_mobility_configs(session.id) + + # then + assert len(configs) > 0 + assert wlan.id in configs + config = configs[wlan.id] + 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_services(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + + # then + with client.context_connect(): + services = client.get_services() + + # then + assert len(services) > 0 + + 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() + node_type = "test" + services = ["SSH"] + + # then + with client.context_connect(): + result = client.set_service_defaults(session.id, {node_type: services}) + + # then + assert result is True + assert session.services.default_services[node_type] == services + + def test_get_node_service_configs(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + node = session.add_node(CoreNode) + service_name = "DefaultRoute" + session.services.set_service(node.id, service_name) + + # then + with client.context_connect(): + services = client.get_node_service_configs(session.id) + + # then + assert len(services) == 1 + service_config = services[0] + assert service_config.node_id == node.id + assert service_config.service == service_name + + 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_set_node_service(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + node = session.add_node(CoreNode) + config = ServiceConfig(node.id, "DefaultRoute", validate=["echo hello"]) + + # then + with client.context_connect(): + result = client.set_node_service(session.id, config) + + # then + assert result is True + service = session.services.get_service( + node.id, config.service, default_service=True + ) + assert service.validate == tuple(config.validate) + + def test_set_node_service_file(self, grpc_server: CoreGrpcServer): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + node = session.add_node(CoreNode) + config = ServiceFileConfig( + node.id, "DefaultRoute", "defaultroute.sh", "echo hello" + ) + + # then + with client.context_connect(): + result = client.set_node_service_file(session.id, config) + + # then + assert result is True + service_file = session.services.get_service_file( + node, config.service, config.file + ) + assert service_file.data == config.data + + def test_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.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 = ip_prefixes.create_iface(node) + session.add_link(node.id, wlan.id, iface) + link_data = wlan.links()[0] + 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_config_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.config_event is not None + queue.put(event) + + # then + with client.context_connect(): + client.events(session.id, handle_event) + time.sleep(0.1) + session_config = session.options.get_configs() + config_data = ConfigShim.config_data( + 0, None, ConfigFlags.UPDATE.value, session.options, session_config + ) + session.broadcast_config(config_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 = core_pb2.MoveNodesRequest() + streamer.send(request) + streamer.stop() + + # then + with pytest.raises(grpc.RpcError): + with client.context_connect(): + client.move_nodes(streamer) From b12aa981d5d2a6e0f30de91577820f2fec1eecb5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 16 Apr 2021 23:43:25 -0700 Subject: [PATCH 008/110] install: removed unwanted tasks, added option to avoid installing ospf mdr --- tasks.py | 51 +++++++++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/tasks.py b/tasks.py index bdebffc1..84fb5b72 100644 --- a/tasks.py +++ b/tasks.py @@ -247,12 +247,6 @@ def install_ospf_mdr(c: Context, os_info: OsInfo, hide: bool) -> None: c.run("sudo make install", hide=hide) -@task( - help={ - "verbose": "enable verbose", - "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}" - }, -) def install_service(c, verbose=False, prefix=DEFAULT_PREFIX): """ install systemd core service @@ -283,13 +277,6 @@ def install_service(c, verbose=False, prefix=DEFAULT_PREFIX): print(f"ERROR: systemd service path not found: {systemd_dir}") -@task( - help={ - "verbose": "enable verbose", - "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}", - "local": "determines if core will install to local system, default is False", - }, -) def install_scripts(c, local=False, verbose=False, prefix=DEFAULT_PREFIX): """ install core script files, modified to leverage virtual environment @@ -344,10 +331,17 @@ def install_scripts(c, local=False, verbose=False, prefix=DEFAULT_PREFIX): "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}", "install-type": "used to force an install type, " "can be one of the following (redhat, debian)", + "ospf": "disable ospf installation", }, ) def install( - c, dev=False, verbose=False, local=False, prefix=DEFAULT_PREFIX, install_type=None + c, + dev=False, + verbose=False, + local=False, + prefix=DEFAULT_PREFIX, + install_type=None, + ospf=True, ): """ install core, poetry, scripts, service, and ospf mdr @@ -376,8 +370,9 @@ def install( install_scripts(c, local, hide, prefix) with p.start("installing systemd service"): install_service(c, hide, prefix) - with p.start("installing ospf mdr"): - install_ospf_mdr(c, os_info, hide) + if ospf: + with p.start("installing ospf mdr"): + install_ospf_mdr(c, os_info, hide) print("\ninstall complete!") @@ -444,10 +439,16 @@ def install_emane(c, verbose=False, local=False, install_type=None): "dev": "uninstall development mode", "verbose": "enable verbose", "local": "determines if core was installed local to system, default is False", - "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}" + "prefix": f"prefix where scripts are installed, default is {DEFAULT_PREFIX}", }, ) -def uninstall(c, dev=False, verbose=False, local=False, prefix=DEFAULT_PREFIX): +def uninstall( + c, + dev=False, + verbose=False, + local=False, + prefix=DEFAULT_PREFIX, +): """ uninstall core, scripts, service, virtual environment, and clean build directory """ @@ -535,20 +536,6 @@ def reinstall( install(c, dev, verbose, local, prefix, install_type) -@task -def daemon(c): - """ - start core-daemon - """ - python = get_python(c) - with c.cd(DAEMON_DIR): - c.run( - f"sudo {python} scripts/core-daemon " - "-f data/core.conf -l data/logging.conf", - pty=True - ) - - @task def test(c): """ From 55d5bb3859f05cf8d1ad82fa83c48192c7d4c9ce Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 20 Apr 2021 09:48:44 -0700 Subject: [PATCH 009/110] daemon: added non promiscuous as default when configuring wlan --- daemon/core/location/mobility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 8861d7f6..85875457 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -454,7 +454,7 @@ class BasicRangeModel(WirelessModel): self.delay = self._get_config(self.delay, config, "delay") self.loss = self._get_config(self.loss, config, "error") self.jitter = self._get_config(self.jitter, config, "jitter") - promiscuous = config["promiscuous"] == "1" + promiscuous = config.get("promiscuous", "0") == "1" if self.promiscuous and not promiscuous: self.wlan.net_client.set_mac_learning(self.wlan.brname, LEARNING_ENABLED) elif not self.promiscuous and promiscuous: From 69652ac5776732640d4eaac0f009089f5e9cc0f3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 21 Apr 2021 21:09:35 -0700 Subject: [PATCH 010/110] updates to python based logging to use module named loggers, updated logging config file to align with these changes --- daemon/core/api/grpc/client.py | 6 +- daemon/core/api/grpc/clientw.py | 14 +- daemon/core/api/grpc/events.py | 4 +- daemon/core/api/grpc/grpcutils.py | 7 +- daemon/core/api/grpc/server.py | 113 +++++----- daemon/core/api/tlv/corehandlers.py | 212 +++++++++--------- daemon/core/api/tlv/dataconversion.py | 8 +- daemon/core/api/tlv/structutils.py | 6 +- daemon/core/config.py | 8 +- daemon/core/configservice/base.py | 9 +- daemon/core/configservice/dependencies.py | 10 +- daemon/core/configservice/manager.py | 8 +- .../configservices/quaggaservices/services.py | 3 +- daemon/core/emane/commeffect.py | 10 +- daemon/core/emane/emanemanager.py | 92 ++++---- daemon/core/emane/emanemanifest.py | 4 +- daemon/core/emane/emanemodel.py | 8 +- daemon/core/emane/linkmonitor.py | 20 +- daemon/core/emane/nodes.py | 14 +- daemon/core/emane/tdma.py | 4 +- daemon/core/emulator/coreemu.py | 16 +- daemon/core/emulator/distributed.py | 12 +- daemon/core/emulator/session.py | 94 ++++---- daemon/core/gui/app.py | 5 +- daemon/core/gui/coreclient.py | 94 ++++---- daemon/core/gui/dialogs/canvaswallpaper.py | 4 +- .../core/gui/dialogs/configserviceconfig.py | 12 +- daemon/core/gui/dialogs/customnodes.py | 8 +- daemon/core/gui/dialogs/executepython.py | 4 +- daemon/core/gui/dialogs/find.py | 6 +- daemon/core/gui/dialogs/nodeconfig.py | 4 +- daemon/core/gui/dialogs/nodeconfigservice.py | 4 +- daemon/core/gui/dialogs/preferences.py | 4 +- daemon/core/gui/dialogs/serviceconfig.py | 6 +- daemon/core/gui/dialogs/sessionoptions.py | 4 +- daemon/core/gui/dialogs/sessions.py | 8 +- daemon/core/gui/graph/edges.py | 8 +- daemon/core/gui/graph/graph.py | 46 ++-- daemon/core/gui/graph/manager.py | 12 +- daemon/core/gui/graph/node.py | 10 +- daemon/core/gui/graph/shape.py | 8 +- daemon/core/gui/interface.py | 12 +- daemon/core/gui/menubar.py | 16 +- daemon/core/gui/nodeutils.py | 6 +- daemon/core/gui/task.py | 4 +- daemon/core/gui/toolbar.py | 10 +- daemon/core/gui/widgets.py | 4 +- daemon/core/location/geo.py | 9 +- daemon/core/location/mobility.py | 46 ++-- daemon/core/nodes/base.py | 28 +-- daemon/core/nodes/docker.py | 16 +- daemon/core/nodes/interface.py | 16 +- daemon/core/nodes/lxd.py | 12 +- daemon/core/nodes/network.py | 24 +- daemon/core/nodes/physical.py | 16 +- daemon/core/plugins/sdt.py | 30 +-- daemon/core/services/coreservices.py | 52 ++--- daemon/core/services/security.py | 10 +- daemon/core/utils.py | 20 +- daemon/core/xml/corexml.py | 38 ++-- daemon/core/xml/emanexml.py | 4 +- daemon/data/logging.conf | 11 +- daemon/scripts/core-daemon | 10 +- 63 files changed, 717 insertions(+), 606 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index e28233fc..771d6926 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -94,6 +94,8 @@ from core.api.grpc.wlan_pb2 import ( ) from core.emulator.data import IpPrefixes +logger = logging.getLogger(__name__) + class InterfaceHelper: """ @@ -147,9 +149,9 @@ def stream_listener(stream: Any, handler: Callable[[core_pb2.Event], None]) -> N handler(event) except grpc.RpcError as e: if e.code() == grpc.StatusCode.CANCELLED: - logging.debug("stream closed") + logger.debug("stream closed") else: - logging.exception("stream error") + logger.exception("stream error") def start_streamer(stream: Any, handler: Callable[[core_pb2.Event], None]) -> None: diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index b7785662..5ba768be 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -70,6 +70,8 @@ from core.api.grpc.wrappers import Hook from core.emulator.data import IpPrefixes from core.errors import CoreError +logger = logging.getLogger(__name__) + class MoveNodesStreamer: def __init__(self, session_id: int = None, source: str = None) -> None: @@ -184,9 +186,9 @@ def throughput_listener( handler(event) except grpc.RpcError as e: if e.code() == grpc.StatusCode.CANCELLED: - logging.debug("throughput stream closed") + logger.debug("throughput stream closed") else: - logging.exception("throughput stream error") + logger.exception("throughput stream error") def cpu_listener( @@ -205,9 +207,9 @@ def cpu_listener( handler(event) except grpc.RpcError as e: if e.code() == grpc.StatusCode.CANCELLED: - logging.debug("cpu stream closed") + logger.debug("cpu stream closed") else: - logging.exception("cpu stream error") + logger.exception("cpu stream error") def event_listener(stream: Any, handler: Callable[[wrappers.Event], None]) -> None: @@ -224,9 +226,9 @@ def event_listener(stream: Any, handler: Callable[[wrappers.Event], None]) -> No handler(event) except grpc.RpcError as e: if e.code() == grpc.StatusCode.CANCELLED: - logging.debug("session stream closed") + logger.debug("session stream closed") else: - logging.exception("session stream error") + logger.exception("session stream error") class CoreGrpcClient: diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index aff3c5e5..b319a978 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -14,6 +14,8 @@ from core.emulator.data import ( ) from core.emulator.session import Session +logger = logging.getLogger(__name__) + def handle_node_event(node_data: NodeData) -> core_pb2.Event: """ @@ -199,7 +201,7 @@ class EventStreamer: elif isinstance(data, FileData): event = handle_file_event(data) else: - logging.error("unknown event: %s", data) + logger.error("unknown event: %s", data) except Empty: pass if event: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 34dc6c03..7f9099aa 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -31,6 +31,7 @@ from core.nodes.lxd import LxcNode from core.nodes.network import WlanNode from core.services.coreservices import CoreService +logger = logging.getLogger(__name__) WORKERS = 10 @@ -156,7 +157,7 @@ def create_nodes( start = time.monotonic() results, exceptions = utils.threadpool(funcs) total = time.monotonic() - start - logging.debug("grpc created nodes time: %s", total) + logger.debug("grpc created nodes time: %s", total) return results, exceptions @@ -180,7 +181,7 @@ def create_links( start = time.monotonic() results, exceptions = utils.threadpool(funcs) total = time.monotonic() - start - logging.debug("grpc created links time: %s", total) + logger.debug("grpc created links time: %s", total) return results, exceptions @@ -204,7 +205,7 @@ def edit_links( start = time.monotonic() results, exceptions = utils.threadpool(funcs) total = time.monotonic() - start - logging.debug("grpc edit links time: %s", total) + logger.debug("grpc edit links time: %s", total) return results, exceptions diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index b8b2a445..94202890 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -116,6 +116,7 @@ from core.nodes.base import CoreNode, NodeBase from core.nodes.network import CtrlNet, PtpNet, WlanNode from core.services.coreservices import ServiceManager +logger = logging.getLogger(__name__) _ONE_DAY_IN_SECONDS: int = 60 * 60 * 24 _INTERFACE_REGEX: Pattern = re.compile(r"veth(?P[0-9a-fA-F]+)") _MAX_WORKERS = 1000 @@ -136,7 +137,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): atexit.register(self._exit_handler) def _exit_handler(self) -> None: - logging.debug("catching exit, stop running") + logger.debug("catching exit, stop running") self.running = False def _is_running(self, context) -> bool: @@ -146,7 +147,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): context.abort(grpc.StatusCode.CANCELLED, "server stopping") def listen(self, address: str) -> None: - logging.info("CORE gRPC API listening on: %s", address) + logger.info("CORE gRPC API listening on: %s", address) self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=_MAX_WORKERS)) core_pb2_grpc.add_CoreApiServicer_to_server(self, self.server) self.server.add_insecure_port(address) @@ -217,7 +218,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: grpc context :return: start session response """ - logging.debug("start session: %s", request) + logger.debug("start session: %s", request) session = self.get_session(request.session_id, context) # clear previous state and setup for creation @@ -313,7 +314,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: grpc context :return: stop session response """ - logging.debug("stop session: %s", request) + logger.debug("stop session: %s", request) session = self.get_session(request.session_id, context) session.data_collect() session.shutdown() @@ -329,7 +330,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: :return: a create-session response """ - logging.debug("create session: %s", request) + logger.debug("create session: %s", request) session = self.coreemu.create_session(request.session_id) session.set_state(EventTypes.DEFINITION_STATE) session.location.setrefgeo(47.57917, -122.13232, 2.0) @@ -348,7 +349,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: a delete-session response """ - logging.debug("delete session: %s", request) + logger.debug("delete session: %s", request) result = self.coreemu.delete_session(request.session_id) return core_pb2.DeleteSessionResponse(result=result) @@ -362,7 +363,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: a delete-session response """ - logging.debug("get sessions: %s", request) + logger.debug("get sessions: %s", request) sessions = [] for session_id in self.coreemu.sessions: session = self.coreemu.sessions[session_id] @@ -387,7 +388,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: a get-session-location response """ - logging.debug("get session location: %s", request) + logger.debug("get session location: %s", request) session = self.get_session(request.session_id, context) x, y, z = session.location.refxyz lat, lon, alt = session.location.refgeo @@ -407,7 +408,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: a set-session-location-response """ - logging.debug("set session location: %s", request) + logger.debug("set session location: %s", request) session = self.get_session(request.session_id, context) grpcutils.session_location(session, request.location) return core_pb2.SetSessionLocationResponse(result=True) @@ -422,7 +423,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context:context object :return: set-session-state response """ - logging.debug("set session state: %s", request) + logger.debug("set session state: %s", request) session = self.get_session(request.session_id, context) try: state = EventTypes(request.state) @@ -451,7 +452,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set session user response """ - logging.debug("set session user: %s", request) + logger.debug("set session user: %s", request) session = self.get_session(request.session_id, context) session.user = request.user return core_pb2.SetSessionUserResponse(result=True) @@ -467,7 +468,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-session-options response about all session's options """ - logging.debug("get session options: %s", request) + logger.debug("get session options: %s", request) session = self.get_session(request.session_id, context) current_config = session.options.get_configs() default_config = session.options.default_values() @@ -485,7 +486,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set-session-options response """ - logging.debug("set session options: %s", request) + logger.debug("set session options: %s", request) session = self.get_session(request.session_id, context) config = session.options.get_configs() config.update(request.config) @@ -502,7 +503,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get session metadata response """ - logging.debug("get session metadata: %s", request) + logger.debug("get session metadata: %s", request) session = self.get_session(request.session_id, context) return core_pb2.GetSessionMetadataResponse(config=session.metadata) @@ -516,7 +517,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set metadata response """ - logging.debug("set session metadata: %s", request) + logger.debug("set session metadata: %s", request) session = self.get_session(request.session_id, context) session.metadata = dict(request.config) return core_pb2.SetSessionMetadataResponse(result=True) @@ -544,7 +545,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-session response """ - logging.debug("get session: %s", request) + logger.debug("get session: %s", request) session = self.get_session(request.session_id, context) links = [] nodes = [] @@ -718,7 +719,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: add-node response """ - logging.debug("add node: %s", request) + logger.debug("add node: %s", request) session = self.get_session(request.session_id, context) _type, _id, options = grpcutils.add_node_data(request.node) _class = session.get_node_class(_type) @@ -737,7 +738,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-node response """ - logging.debug("get node: %s", request) + logger.debug("get node: %s", request) session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, NodeBase) ifaces = [] @@ -768,7 +769,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): options = NodeOptions() has_geo = request.HasField("geo") if has_geo: - logging.info("has geo") + logger.info("has geo") lat = request.geo.lat lon = request.geo.lon alt = request.geo.alt @@ -776,7 +777,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): else: x = request.position.x y = request.position.y - logging.info("has pos: %s,%s", x, y) + logger.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 @@ -794,7 +795,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: edit-node response """ - logging.debug("edit node: %s", request) + logger.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(icon=request.icon) @@ -830,7 +831,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: core.api.grpc.core_pb2.DeleteNodeResponse """ - logging.debug("delete node: %s", request) + logger.debug("delete node: %s", request) session = self.get_session(request.session_id, context) result = False if request.node_id in session.nodes: @@ -850,7 +851,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: core.api.grpc.core_pb2.NodeCommandResponse """ - logging.debug("sending node command: %s", request) + logger.debug("sending node command: %s", request) session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, CoreNode) try: @@ -871,7 +872,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-node-terminal response """ - logging.debug("getting node terminal: %s", request) + logger.debug("getting node terminal: %s", request) session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, CoreNode) terminal = node.termcmdstring("/bin/bash") @@ -887,7 +888,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-node-links response """ - logging.debug("get node links: %s", request) + logger.debug("get node links: %s", request) session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, NodeBase) links = get_links(node) @@ -903,7 +904,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: add-link response """ - logging.debug("add link: %s", request) + logger.debug("add link: %s", request) session = self.get_session(request.session_id, context) node1_id = request.link.node1_id node2_id = request.link.node2_id @@ -952,7 +953,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: edit-link response """ - logging.debug("edit link: %s", request) + logger.debug("edit link: %s", request) session = self.get_session(request.session_id, context) node1_id = request.node1_id node2_id = request.node2_id @@ -998,7 +999,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: delete-link response """ - logging.debug("delete link: %s", request) + logger.debug("delete link: %s", request) session = self.get_session(request.session_id, context) node1_id = request.node1_id node2_id = request.node2_id @@ -1029,7 +1030,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-hooks response about all the hooks in all session states """ - logging.debug("get hooks: %s", request) + logger.debug("get hooks: %s", request) session = self.get_session(request.session_id, context) hooks = grpcutils.get_hooks(session) return core_pb2.GetHooksResponse(hooks=hooks) @@ -1044,7 +1045,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: add-hook response """ - logging.debug("add hook: %s", request) + logger.debug("add hook: %s", request) session = self.get_session(request.session_id, context) hook = request.hook state = EventTypes(hook.state) @@ -1062,7 +1063,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-mobility-configurations response that has a list of configurations """ - logging.debug("get mobility configs: %s", request) + logger.debug("get mobility configs: %s", request) session = self.get_session(request.session_id, context) configs = grpcutils.get_mobility_configs(session) return GetMobilityConfigsResponse(configs=configs) @@ -1078,7 +1079,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-mobility-configuration response """ - logging.debug("get mobility config: %s", request) + logger.debug("get mobility config: %s", request) session = self.get_session(request.session_id, context) current_config = session.mobility.get_model_config( request.node_id, Ns2ScriptedMobility.name @@ -1097,7 +1098,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set-mobility-configuration response """ - logging.debug("set mobility config: %s", request) + logger.debug("set mobility config: %s", request) session = self.get_session(request.session_id, context) mobility_config = request.mobility_config session.mobility.set_model_config( @@ -1116,7 +1117,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: mobility-action response """ - logging.debug("mobility action: %s", request) + logger.debug("mobility action: %s", request) session = self.get_session(request.session_id, context) node = grpcutils.get_mobility_node(session, request.node_id, context) if not node.mobility: @@ -1144,7 +1145,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-services response """ - logging.debug("get services: %s", request) + logger.debug("get services: %s", request) services = [] for name in ServiceManager.services: service = ServiceManager.services[name] @@ -1162,7 +1163,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-service-defaults response about all the available default services """ - logging.debug("get service defaults: %s", request) + logger.debug("get service defaults: %s", request) session = self.get_session(request.session_id, context) defaults = grpcutils.get_default_services(session) return GetServiceDefaultsResponse(defaults=defaults) @@ -1177,7 +1178,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set-service-defaults response """ - logging.debug("set service defaults: %s", request) + logger.debug("set service defaults: %s", request) session = self.get_session(request.session_id, context) session.services.default_services.clear() for service_defaults in request.defaults: @@ -1197,7 +1198,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: all node service configs response """ - logging.debug("get node service configs: %s", request) + logger.debug("get node service configs: %s", request) session = self.get_session(request.session_id, context) configs = grpcutils.get_node_service_configs(session) return GetNodeServiceConfigsResponse(configs=configs) @@ -1213,7 +1214,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-node-service response about the requested service """ - logging.debug("get node service: %s", request) + logger.debug("get node service: %s", request) session = self.get_session(request.session_id, context) service = session.services.get_service( request.node_id, request.service, default_service=True @@ -1232,7 +1233,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-node-service response about the requested service """ - logging.debug("get node service file: %s", request) + logger.debug("get node service file: %s", request) session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, CoreNode) file_data = session.services.get_service_file( @@ -1251,7 +1252,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set-node-service response """ - logging.debug("set node service: %s", request) + logger.debug("set node service: %s", request) session = self.get_session(request.session_id, context) config = request.config grpcutils.service_configuration(session, config) @@ -1268,7 +1269,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set-node-service-file response """ - logging.debug("set node service file: %s", request) + logger.debug("set node service file: %s", request) session = self.get_session(request.session_id, context) config = request.config session.services.set_service_file( @@ -1287,7 +1288,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: service-action response about status of action """ - logging.debug("service action: %s", request) + logger.debug("service action: %s", request) session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context, CoreNode) service = None @@ -1327,7 +1328,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: core.api.grpc.core_pb2.GetWlanConfigResponse :return: all wlan configurations """ - logging.debug("get wlan configs: %s", request) + logger.debug("get wlan configs: %s", request) session = self.get_session(request.session_id, context) configs = grpcutils.get_wlan_configs(session) return GetWlanConfigsResponse(configs=configs) @@ -1342,7 +1343,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: core.api.grpc.core_pb2.GetWlanConfigResponse :return: get-wlan-configuration response about the wlan configuration of a node """ - logging.debug("get wlan config: %s", request) + logger.debug("get wlan config: %s", request) session = self.get_session(request.session_id, context) current_config = session.mobility.get_model_config( request.node_id, BasicRangeModel.name @@ -1360,7 +1361,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set-wlan-configuration response """ - logging.debug("set wlan config: %s", request) + logger.debug("set wlan config: %s", request) session = self.get_session(request.session_id, context) node_id = request.wlan_config.node_id config = request.wlan_config.config @@ -1380,7 +1381,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-EMANE-configuration response """ - logging.debug("get emane config: %s", request) + logger.debug("get emane config: %s", request) session = self.get_session(request.session_id, context) config = grpcutils.get_emane_config(session) return GetEmaneConfigResponse(config=config) @@ -1395,7 +1396,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set-EMANE-configuration response """ - logging.debug("set emane config: %s", request) + logger.debug("set emane config: %s", request) session = self.get_session(request.session_id, context) config = session.emane.get_configs() config.update(request.config) @@ -1411,7 +1412,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-EMANE-models response that has all the models """ - logging.debug("get emane models: %s", request) + logger.debug("get emane models: %s", request) session = self.get_session(request.session_id, context) models = grpcutils.get_emane_models(session) return GetEmaneModelsResponse(models=models) @@ -1427,7 +1428,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: get-EMANE-model-configuration response """ - logging.debug("get emane model config: %s", request) + logger.debug("get emane model config: %s", request) session = self.get_session(request.session_id, context) model = session.emane.models.get(request.model) if not model: @@ -1448,7 +1449,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: set-EMANE-model-configuration response """ - logging.debug("set emane model config: %s", request) + logger.debug("set emane model config: %s", request) session = self.get_session(request.session_id, context) model_config = request.emane_model_config _id = utils.iface_config_id(model_config.node_id, model_config.iface_id) @@ -1467,7 +1468,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :return: get-EMANE-model-configurations response that has all the EMANE configurations """ - logging.debug("get emane model configs: %s", request) + logger.debug("get emane model configs: %s", request) session = self.get_session(request.session_id, context) configs = grpcutils.get_emane_model_configs(session) return GetEmaneModelConfigsResponse(configs=configs) @@ -1482,7 +1483,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: save-xml response """ - logging.debug("save xml: %s", request) + logger.debug("save xml: %s", request) session = self.get_session(request.session_id, context) _, temp_path = tempfile.mkstemp() @@ -1503,7 +1504,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: Open-XML response or raise an exception if invalid XML file """ - logging.debug("open xml: %s", request) + logger.debug("open xml: %s", request) session = self.coreemu.create_session() temp = tempfile.NamedTemporaryFile(delete=False) temp.write(request.data.encode("utf-8")) @@ -1516,7 +1517,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.file_path = file_path return core_pb2.OpenXmlResponse(session_id=session.id, result=True) except IOError: - logging.exception("error opening session file") + logger.exception("error opening session file") self.coreemu.delete_session(session.id) context.abort(grpc.StatusCode.INVALID_ARGUMENT, "invalid xml file") finally: @@ -1549,7 +1550,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :param context: context object :return: emane link response with success status """ - logging.debug("emane link: %s", request) + logger.debug("emane link: %s", request) session = self.get_session(request.session_id, context) nem1 = request.nem1 iface1 = session.emane.get_iface(nem1) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index aec29b99..fc8f9891 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -55,6 +55,8 @@ from core.nodes.network import WlanNode from core.nodes.physical import Rj45Node from core.services.coreservices import ServiceManager, ServiceShim +logger = logging.getLogger(__name__) + class CoreHandler(socketserver.BaseRequestHandler): """ @@ -104,7 +106,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :return: nothing """ - logging.debug("new TCP connection: %s", self.client_address) + logger.debug("new TCP connection: %s", self.client_address) def finish(self): """ @@ -113,40 +115,40 @@ class CoreHandler(socketserver.BaseRequestHandler): :return: nothing """ - logging.debug("finishing request handler") - logging.debug("remaining message queue size: %s", self.message_queue.qsize()) + logger.debug("finishing request handler") + logger.debug("remaining message queue size: %s", self.message_queue.qsize()) # give some time for message queue to deplete timeout = 10 wait = 0 while not self.message_queue.empty(): - logging.debug("waiting for message queue to empty: %s seconds", wait) + logger.debug("waiting for message queue to empty: %s seconds", wait) time.sleep(1) wait += 1 if wait == timeout: - logging.warning("queue failed to be empty, finishing request handler") + logger.warning("queue failed to be empty, finishing request handler") break - logging.info("client disconnected: notifying threads") + logger.info("client disconnected: notifying threads") self.done = True for thread in self.handler_threads: - logging.info("waiting for thread: %s", thread.getName()) + logger.info("waiting for thread: %s", thread.getName()) thread.join(timeout) if thread.is_alive(): - logging.warning( + logger.warning( "joining %s failed: still alive after %s sec", thread.getName(), timeout, ) - logging.info("connection closed: %s", self.client_address) + logger.info("connection closed: %s", self.client_address) if self.session: # remove client from session broker and shutdown if there are no clients self.remove_session_handlers() clients = self.session_clients[self.session.id] clients.remove(self) if not clients and not self.session.is_active(): - logging.info( + logger.info( "no session clients left and not active, initiating shutdown" ) self.coreemu.delete_session(self.session.id) @@ -218,7 +220,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :param core.emulator.data.EventData event_data: event data to handle :return: nothing """ - logging.debug("handling broadcast event: %s", event_data) + logger.debug("handling broadcast event: %s", event_data) tlv_data = structutils.pack_values( coreapi.CoreEventTlv, @@ -236,7 +238,7 @@ class CoreHandler(socketserver.BaseRequestHandler): try: self.sendall(message) except IOError: - logging.exception("error sending event message") + logger.exception("error sending event message") def handle_broadcast_file(self, file_data): """ @@ -245,7 +247,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :param core.emulator.data.FileData file_data: file data to handle :return: nothing """ - logging.debug("handling broadcast file: %s", file_data) + logger.debug("handling broadcast file: %s", file_data) tlv_data = structutils.pack_values( coreapi.CoreFileTlv, @@ -266,7 +268,7 @@ class CoreHandler(socketserver.BaseRequestHandler): try: self.sendall(message) except IOError: - logging.exception("error sending file message") + logger.exception("error sending file message") def handle_broadcast_config(self, config_data): """ @@ -275,12 +277,12 @@ class CoreHandler(socketserver.BaseRequestHandler): :param core.emulator.data.ConfigData config_data: config data to handle :return: nothing """ - logging.debug("handling broadcast config: %s", config_data) + logger.debug("handling broadcast config: %s", config_data) message = dataconversion.convert_config(config_data) try: self.sendall(message) except IOError: - logging.exception("error sending config message") + logger.exception("error sending config message") def handle_broadcast_exception(self, exception_data): """ @@ -289,7 +291,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :param core.emulator.data.ExceptionData exception_data: exception data to handle :return: nothing """ - logging.debug("handling broadcast exception: %s", exception_data) + logger.debug("handling broadcast exception: %s", exception_data) tlv_data = structutils.pack_values( coreapi.CoreExceptionTlv, [ @@ -306,7 +308,7 @@ class CoreHandler(socketserver.BaseRequestHandler): try: self.sendall(message) except IOError: - logging.exception("error sending exception message") + logger.exception("error sending exception message") def handle_broadcast_node(self, node_data): """ @@ -315,12 +317,12 @@ class CoreHandler(socketserver.BaseRequestHandler): :param core.emulator.data.NodeData node_data: node data to handle :return: nothing """ - logging.debug("handling broadcast node: %s", node_data) + logger.debug("handling broadcast node: %s", node_data) message = dataconversion.convert_node(node_data) try: self.sendall(message) except IOError: - logging.exception("error sending node message") + logger.exception("error sending node message") def handle_broadcast_link(self, link_data): """ @@ -329,7 +331,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :param core.emulator.data.LinkData link_data: link data to handle :return: nothing """ - logging.debug("handling broadcast link: %s", link_data) + logger.debug("handling broadcast link: %s", link_data) options_data = link_data.options loss = "" if options_data.loss is not None: @@ -381,7 +383,7 @@ class CoreHandler(socketserver.BaseRequestHandler): try: self.sendall(message) except IOError: - logging.exception("error sending Event Message") + logger.exception("error sending Event Message") def register(self): """ @@ -389,7 +391,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :return: register message data """ - logging.info( + logger.info( "GUI has connected to session %d at %s", self.session.id, time.ctime() ) tlv_data = b"" @@ -461,14 +463,14 @@ class CoreHandler(socketserver.BaseRequestHandler): header ) if message_len == 0: - logging.warning("received message with no data") + logger.warning("received message with no data") data = b"" while len(data) < message_len: data += self.request.recv(message_len - len(data)) if len(data) > message_len: error_message = f"received message length does not match received data ({len(data)} != {message_len})" - logging.error(error_message) + logger.error(error_message) raise IOError(error_message) try: @@ -477,7 +479,7 @@ class CoreHandler(socketserver.BaseRequestHandler): except KeyError: message = coreapi.CoreMessage(message_flags, header, data) message.message_type = message_type - logging.exception("unimplemented core message type: %s", message.type_str()) + logger.exception("unimplemented core message type: %s", message.type_str()) return message @@ -488,7 +490,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :param message: message to queue :return: nothing """ - logging.debug( + logger.debug( "queueing msg (queuedtimes = %s): type %s", message.queuedtimes, MessageTypes(message.message_type), @@ -518,11 +520,11 @@ class CoreHandler(socketserver.BaseRequestHandler): :param message: message to handle :return: nothing """ - logging.debug( + logger.debug( "%s handling message:\n%s", threading.currentThread().getName(), message ) if message.message_type not in self.message_handlers: - logging.error("no handler for message type: %s", message.type_str()) + logger.error("no handler for message type: %s", message.type_str()) return message_handler = self.message_handlers[message.message_type] @@ -532,7 +534,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.dispatch_replies(replies, message) except Exception as e: self.send_exception(ExceptionLevels.ERROR, "corehandler", str(e)) - logging.exception( + logger.exception( "%s: exception while handling message: %s", threading.currentThread().getName(), message, @@ -560,12 +562,12 @@ class CoreHandler(socketserver.BaseRequestHandler): # multiple TLVs of same type cause KeyError exception reply_message = f"CoreMessage (type {message_type} flags {message_flags} length {message_length})" - logging.debug("sending reply:\n%s", reply_message) + logger.debug("sending reply:\n%s", reply_message) try: self.sendall(reply) except IOError: - logging.exception("error dispatching reply") + logger.exception("error dispatching reply") def handle(self): """ @@ -580,7 +582,7 @@ class CoreHandler(socketserver.BaseRequestHandler): # TODO: add shutdown handler for session self.session = self.coreemu.create_session(port) - logging.debug("created new session for client: %s", self.session.id) + logger.debug("created new session for client: %s", self.session.id) clients = self.session_clients.setdefault(self.session.id, []) clients.append(self) @@ -594,10 +596,10 @@ class CoreHandler(socketserver.BaseRequestHandler): try: message = self.receive_message() except EOFError: - logging.info("client disconnected") + logger.info("client disconnected") break except IOError: - logging.exception("error receiving message") + logger.exception("error receiving message") break message.queuedtimes = 0 @@ -619,7 +621,7 @@ class CoreHandler(socketserver.BaseRequestHandler): if client == self: continue - logging.debug("BROADCAST TO OTHER CLIENT: %s", client) + logger.debug("BROADCAST TO OTHER CLIENT: %s", client) client.sendall(message.raw_message) def send_exception(self, level, source, text, node=None): @@ -643,7 +645,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.handle_broadcast_exception(exception_data) def add_session_handlers(self): - logging.debug("adding session broadcast handlers") + logger.debug("adding session broadcast handlers") self.session.event_handlers.append(self.handle_broadcast_event) self.session.exception_handlers.append(self.handle_broadcast_exception) self.session.node_handlers.append(self.handle_broadcast_node) @@ -652,7 +654,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.config_handlers.append(self.handle_broadcast_config) def remove_session_handlers(self): - logging.debug("removing session broadcast handlers") + logger.debug("removing session broadcast handlers") self.session.event_handlers.remove(self.handle_broadcast_event) self.session.exception_handlers.remove(self.handle_broadcast_exception) self.session.node_handlers.remove(self.handle_broadcast_node) @@ -672,7 +674,7 @@ class CoreHandler(socketserver.BaseRequestHandler): message.flags & MessageFlags.ADD.value and message.flags & MessageFlags.DELETE.value ): - logging.warning("ignoring invalid message: add and delete flag both set") + logger.warning("ignoring invalid message: add and delete flag both set") return () _class = CoreNode @@ -898,7 +900,7 @@ class CoreHandler(socketserver.BaseRequestHandler): else: node.cmd(command, wait=False) except CoreError: - logging.exception("error getting object: %s", node_id) + logger.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: @@ -920,7 +922,7 @@ class CoreHandler(socketserver.BaseRequestHandler): execute_server = message.get_tlv(RegisterTlvs.EXECUTE_SERVER.value) if execute_server: try: - logging.info("executing: %s", execute_server) + logger.info("executing: %s", execute_server) if message.flags & MessageFlags.STRING.value: old_session_ids = set(self.coreemu.sessions.keys()) sys.argv = shlex.split(execute_server) @@ -946,26 +948,26 @@ class CoreHandler(socketserver.BaseRequestHandler): new_sid = new_session_ids.difference(old_session_ids) try: sid = new_sid.pop() - logging.info("executed: %s as session %d", execute_server, sid) + logger.info("executed: %s as session %d", execute_server, sid) except KeyError: - logging.info( + logger.info( "executed %s with unknown session ID", execute_server ) return replies - logging.debug("checking session %d for RUNTIME state", sid) + logger.debug("checking session %d for RUNTIME state", sid) session = self.coreemu.sessions.get(sid) retries = 10 # wait for session to enter RUNTIME state, to prevent GUI from # connecting while nodes are still being instantiated while session.state != EventTypes.RUNTIME_STATE: - logging.debug( + logger.debug( "waiting for session %d to enter RUNTIME state", sid ) time.sleep(1) retries -= 1 if retries <= 0: - logging.debug("session %d did not enter RUNTIME state", sid) + logger.debug("session %d did not enter RUNTIME state", sid) return replies tlv_data = coreapi.CoreRegisterTlv.pack( @@ -977,7 +979,7 @@ class CoreHandler(socketserver.BaseRequestHandler): message = coreapi.CoreRegMessage.pack(0, tlv_data) replies.append(message) except Exception as e: - logging.exception("error executing: %s", execute_server) + logger.exception("error executing: %s", execute_server) tlv_data = coreapi.CoreExceptionTlv.pack(ExceptionTlvs.LEVEL.value, 2) tlv_data += coreapi.CoreExceptionTlv.pack( ExceptionTlvs.TEXT.value, str(e) @@ -989,7 +991,7 @@ class CoreHandler(socketserver.BaseRequestHandler): gui = message.get_tlv(RegisterTlvs.GUI.value) if gui is None: - logging.debug("ignoring Register message") + logger.debug("ignoring Register message") else: # register capabilities with the GUI replies.append(self.register()) @@ -1020,7 +1022,7 @@ class CoreHandler(socketserver.BaseRequestHandler): network_id=message.get_tlv(ConfigTlvs.NETWORK_ID.value), opaque=message.get_tlv(ConfigTlvs.OPAQUE.value), ) - logging.debug( + logger.debug( "configuration message for %s node %s", config_data.object, config_data.node ) message_type = ConfigFlags(config_data.type) @@ -1095,7 +1097,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.location.reset() else: if not config_data.data_values: - logging.warning("location data missing") + logger.warning("location data missing") else: values = [float(x) for x in config_data.data_values.split("|")] @@ -1108,7 +1110,7 @@ class CoreHandler(socketserver.BaseRequestHandler): # geographic reference point self.session.location.setrefgeo(lat, lon, alt) self.session.location.refscale = values[5] - logging.info( + logger.info( "location configured: %s = %s scale=%s", self.session.location.refxyz, self.session.location.refgeo, @@ -1145,7 +1147,7 @@ class CoreHandler(socketserver.BaseRequestHandler): def handle_config_broker(self, message_type, config_data): if message_type not in [ConfigFlags.REQUEST, ConfigFlags.RESET]: if not config_data.data_values: - logging.info("emulation server data missing") + logger.info("emulation server data missing") else: values = config_data.data_values.split("|") @@ -1169,7 +1171,7 @@ class CoreHandler(socketserver.BaseRequestHandler): session_id = config_data.session opaque = config_data.opaque - logging.debug( + logger.debug( "configuration request: node(%s) session(%s) opaque(%s)", node_id, session_id, @@ -1199,10 +1201,10 @@ class CoreHandler(socketserver.BaseRequestHandler): values = [] group_strings = [] start_index = 1 - logging.debug("sorted groups: %s", groups) + logger.debug("sorted groups: %s", groups) for group in groups: services = sorted(group_map[group], key=lambda x: x.name.lower()) - logging.debug("sorted services for group(%s): %s", group, services) + logger.debug("sorted services for group(%s): %s", group, services) end_index = start_index + len(services) - 1 group_strings.append(f"{group}:{start_index}-{end_index}") start_index += len(services) @@ -1226,7 +1228,7 @@ class CoreHandler(socketserver.BaseRequestHandler): node = self.session.get_node(node_id, CoreNodeBase) if node is None: - logging.warning( + logger.warning( "request to configure service for unknown node %s", node_id ) return replies @@ -1285,7 +1287,7 @@ class CoreHandler(socketserver.BaseRequestHandler): error_message = "services config message that I don't know how to handle" if values is None: - logging.error(error_message) + logger.error(error_message) else: if opaque is None: values = values.split("|") @@ -1294,11 +1296,11 @@ class CoreHandler(socketserver.BaseRequestHandler): data_types is None or data_types[0] != ConfigDataTypes.STRING.value ): - logging.info(error_message) + logger.info(error_message) return None key = values.pop(0) self.session.services.default_services[key] = values - logging.debug("default services for type %s set to %s", key, values) + logger.debug("default services for type %s set to %s", key, values) elif node_id: services = ServiceShim.servicesfromopaque(opaque) if services: @@ -1337,16 +1339,16 @@ class CoreHandler(socketserver.BaseRequestHandler): values_str = config_data.data_values node_id = utils.iface_config_id(node_id, iface_id) - logging.debug( + logger.debug( "received configure message for %s nodenum: %s", object_name, node_id ) if message_type == ConfigFlags.REQUEST: - logging.info("replying to configure request for model: %s", object_name) + logger.info("replying to configure request for model: %s", object_name) typeflags = ConfigFlags.NONE.value model_class = self.session.mobility.models.get(object_name) if not model_class: - logging.warning("model class does not exist: %s", object_name) + logger.warning("model class does not exist: %s", object_name) return [] config = self.session.mobility.get_model_config(node_id, object_name) @@ -1357,7 +1359,7 @@ class CoreHandler(socketserver.BaseRequestHandler): elif message_type != ConfigFlags.RESET: # store the configuration values for later use, when the node if not object_name: - logging.warning("no configuration object for node: %s", node_id) + logger.warning("no configuration object for node: %s", node_id) return [] parsed_config = {} @@ -1371,7 +1373,7 @@ class CoreHandler(socketserver.BaseRequestHandler): if object_name == BasicRangeModel.name: node.updatemodel(parsed_config) except CoreError: - logging.error( + logger.error( "skipping mobility configuration for unknown node: %s", node_id ) @@ -1385,11 +1387,11 @@ class CoreHandler(socketserver.BaseRequestHandler): values_str = config_data.data_values node_id = utils.iface_config_id(node_id, iface_id) - logging.debug( + logger.debug( "received configure message for %s nodenum: %s", object_name, node_id ) if message_type == ConfigFlags.REQUEST: - logging.info("replying to configure request for %s model", object_name) + logger.info("replying to configure request for %s model", object_name) typeflags = ConfigFlags.NONE.value config = self.session.emane.get_configs() config_response = ConfigShim.config_data( @@ -1398,7 +1400,7 @@ class CoreHandler(socketserver.BaseRequestHandler): replies.append(config_response) elif message_type != ConfigFlags.RESET: if not object_name: - logging.info("no configuration object for node %s", node_id) + logger.info("no configuration object for node %s", node_id) return [] if values_str: @@ -1415,16 +1417,16 @@ class CoreHandler(socketserver.BaseRequestHandler): values_str = config_data.data_values node_id = utils.iface_config_id(node_id, iface_id) - logging.debug( + logger.debug( "received configure message for %s nodenum: %s", object_name, node_id ) if message_type == ConfigFlags.REQUEST: - logging.info("replying to configure request for model: %s", object_name) + logger.info("replying to configure request for model: %s", object_name) typeflags = ConfigFlags.NONE.value model_class = self.session.emane.models.get(object_name) if not model_class: - logging.warning("model class does not exist: %s", object_name) + logger.warning("model class does not exist: %s", object_name) return [] config = self.session.emane.get_model_config(node_id, object_name) @@ -1435,7 +1437,7 @@ class CoreHandler(socketserver.BaseRequestHandler): elif message_type != ConfigFlags.RESET: # store the configuration values for later use, when the node if not object_name: - logging.warning("no configuration object for node: %s", node_id) + logger.warning("no configuration object for node: %s", node_id) return [] parsed_config = {} @@ -1464,13 +1466,11 @@ class CoreHandler(socketserver.BaseRequestHandler): compressed_data = message.get_tlv(FileTlvs.COMPRESSED_DATA.value) if compressed_data: - logging.warning( - "Compressed file data not implemented for File message." - ) + logger.warning("Compressed file data not implemented for File message.") return () if src_path and data: - logging.warning( + logger.warning( "ignoring invalid File message: source and data TLVs are both present" ) return () @@ -1487,7 +1487,7 @@ class CoreHandler(socketserver.BaseRequestHandler): elif file_type.startswith("hook:"): _, state = file_type.split(":")[:2] if not state.isdigit(): - logging.error("error setting hook having state '%s'", state) + logger.error("error setting hook having state '%s'", state) return () state = int(state) state = EventTypes(state) @@ -1517,7 +1517,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :param message: interface message to handle :return: reply messages """ - logging.info("ignoring Interface message") + logger.info("ignoring Interface message") return () def handle_event_message(self, message): @@ -1543,7 +1543,7 @@ class CoreHandler(socketserver.BaseRequestHandler): raise NotImplementedError("Event message missing event type") node_id = event_data.node - logging.debug("handling event %s at %s", event_type.name, time.ctime()) + logger.debug("handling event %s at %s", event_type.name, time.ctime()) if event_type.value <= EventTypes.SHUTDOWN_STATE.value: if node_id is not None: node = self.session.get_node(node_id, NodeBase) @@ -1555,7 +1555,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.start_mobility(node_ids=[node.id]) return () - logging.warning( + logger.warning( "dropping unhandled event message for node: %s", node.name ) return () @@ -1580,12 +1580,12 @@ class CoreHandler(socketserver.BaseRequestHandler): self.send_node_emulation_id(_id) elif event_type == EventTypes.RUNTIME_STATE: self.session.set_state(event_type) - logging.warning("Unexpected event message: RUNTIME state received") + logger.warning("Unexpected event message: RUNTIME state received") elif event_type == EventTypes.DATACOLLECT_STATE: self.session.data_collect() elif event_type == EventTypes.SHUTDOWN_STATE: self.session.set_state(event_type) - logging.warning("Unexpected event message: SHUTDOWN state received") + logger.warning("Unexpected event message: SHUTDOWN state received") elif event_type in { EventTypes.START, EventTypes.STOP, @@ -1605,7 +1605,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.mobility_event(event_data) handled = True if not handled: - logging.warning( + logger.warning( "unhandled event message: event type %s, name %s ", event_type.name, name, @@ -1624,7 +1624,7 @@ class CoreHandler(socketserver.BaseRequestHandler): name = event_data.name data = event_data.data if etime is None: - logging.warning("Event message scheduled event missing start time") + logger.warning("Event message scheduled event missing start time") return () if message.flags & MessageFlags.ADD.value: self.session.add_event( @@ -1650,7 +1650,7 @@ class CoreHandler(socketserver.BaseRequestHandler): try: node = self.session.get_node(node_id, CoreNodeBase) except CoreError: - logging.warning( + logger.warning( "ignoring event for service '%s', unknown node '%s'", name, node_id ) return @@ -1692,7 +1692,7 @@ class CoreHandler(socketserver.BaseRequestHandler): if num > 1: unknown_data += ", " num -= 1 - logging.warning("Event requested for unknown service(s): %s", unknown_data) + logger.warning("Event requested for unknown service(s): %s", unknown_data) unknown_data = f"Unknown:{unknown_data}" event_data = EventData( @@ -1720,7 +1720,7 @@ class CoreHandler(socketserver.BaseRequestHandler): files = coreapi.str_to_list(file_str) thumb = message.get_tlv(SessionTlvs.THUMB.value) user = message.get_tlv(SessionTlvs.USER.value) - logging.debug( + logger.debug( "SESSION message flags=0x%x sessions=%s", message.flags, session_id_str ) @@ -1732,7 +1732,7 @@ class CoreHandler(socketserver.BaseRequestHandler): else: session = self.coreemu.sessions.get(session_id) if session is None: - logging.warning("session %s not found", session_id) + logger.warning("session %s not found", session_id) continue if names is not None: session.name = names[index] @@ -1756,14 +1756,14 @@ class CoreHandler(socketserver.BaseRequestHandler): session = self.coreemu.sessions.get(session_id) if session is None: - logging.info( + logger.info( "session %s not found (flags=0x%x)", session_id, message.flags ) continue if message.flags & MessageFlags.ADD.value: # connect to the first session that exists - logging.info("request to connect to session %s", session_id) + logger.info("request to connect to session %s", session_id) # remove client from session broker and shutdown if needed self.remove_session_handlers() @@ -1780,7 +1780,7 @@ class CoreHandler(socketserver.BaseRequestHandler): clients.append(self) # add broadcast handlers - logging.info("adding session broadcast handlers") + logger.info("adding session broadcast handlers") self.add_session_handlers() if user: @@ -1790,12 +1790,10 @@ class CoreHandler(socketserver.BaseRequestHandler): self.send_objects() elif message.flags & MessageFlags.DELETE.value: # shut down the specified session(s) - logging.info("request to terminate session %s", session_id) + logger.info("request to terminate session %s", session_id) self.coreemu.delete_session(session_id) else: - logging.warning( - "unhandled session flags for session %s", session_id - ) + logger.warning("unhandled session flags for session %s", session_id) return () @@ -1817,9 +1815,7 @@ class CoreHandler(socketserver.BaseRequestHandler): try: self.sendall(reply) except IOError: - logging.exception( - "error sending node emulation id message: %s", node_id - ) + logger.exception("error sending node emulation id message: %s", node_id) del self.node_status_request[node_id] @@ -1845,7 +1841,7 @@ class CoreHandler(socketserver.BaseRequestHandler): for model_name in mobility_configs: config = mobility_configs[model_name] model_class = self.session.mobility.models[model_name] - logging.debug( + logger.debug( "mobility config: node(%s) class(%s) values(%s)", node_id, model_class, @@ -1858,7 +1854,7 @@ class CoreHandler(socketserver.BaseRequestHandler): # send global emane config config = self.session.emane.get_configs() - logging.debug("global emane config: values(%s)", config) + logger.debug("global emane config: values(%s)", config) config_data = ConfigShim.config_data( 0, None, ConfigFlags.UPDATE.value, self.session.emane.emane_config, config ) @@ -1870,7 +1866,7 @@ class CoreHandler(socketserver.BaseRequestHandler): for model_name in emane_configs: config = emane_configs[model_name] model_class = self.session.emane.models[model_name] - logging.debug( + logger.debug( "emane config: node(%s) class(%s) values(%s)", node_id, model_class, @@ -1951,7 +1947,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.broadcast_config(config_data) node_count = self.session.get_node_count() - logging.info( + logger.info( "informed GUI about %d nodes and %d links", node_count, len(all_links) ) @@ -1991,11 +1987,11 @@ class CoreUdpHandler(CoreHandler): header ) if message_len == 0: - logging.warning("received message with no data") + logger.warning("received message with no data") return if len(data) != coreapi.CoreMessage.header_len + message_len: - logging.error( + logger.error( "received message length does not match received data (%s != %s)", len(data), coreapi.CoreMessage.header_len + message_len, @@ -2013,7 +2009,7 @@ class CoreUdpHandler(CoreHandler): message_flags, header, data[coreapi.CoreMessage.header_len :] ) message.msgtype = message_type - logging.exception("unimplemented core message type: %s", message.type_str()) + logger.exception("unimplemented core message type: %s", message.type_str()) def handle(self): message = self.receive_message() @@ -2023,12 +2019,12 @@ class CoreUdpHandler(CoreHandler): for session_id in sessions: session = self.server.mainserver.coreemu.sessions.get(session_id) if session: - logging.debug("session handling message: %s", session.id) + logger.debug("session handling message: %s", session.id) self.session = session self.handle_message(message) self.broadcast(message) else: - logging.error( + logger.error( "session %d in %s message not found.", session_id, message.type_str(), @@ -2052,7 +2048,7 @@ class CoreUdpHandler(CoreHandler): self.handle_message(message) self.broadcast(message) else: - logging.error( + logger.error( "no active session, dropping %s message.", message.type_str() ) @@ -2065,7 +2061,7 @@ class CoreUdpHandler(CoreHandler): try: client.sendall(message.raw_message) except IOError: - logging.error("error broadcasting") + logger.error("error broadcasting") def finish(self): return socketserver.BaseRequestHandler.finish(self) diff --git a/daemon/core/api/tlv/dataconversion.py b/daemon/core/api/tlv/dataconversion.py index 8a26300a..d625a615 100644 --- a/daemon/core/api/tlv/dataconversion.py +++ b/daemon/core/api/tlv/dataconversion.py @@ -10,6 +10,8 @@ from core.api.tlv.enumerations import ConfigTlvs, NodeTlvs from core.config import ConfigGroup, ConfigurableOptions from core.emulator.data import ConfigData, NodeData +logger = logging.getLogger(__name__) + def convert_node(node_data: NodeData): """ @@ -139,9 +141,9 @@ class ConfigShim: captions = None data_types = [] possible_values = [] - logging.debug("configurable: %s", configurable_options) - logging.debug("configuration options: %s", configurable_options.configurations) - logging.debug("configuration data: %s", config) + logger.debug("configurable: %s", configurable_options) + logger.debug("configuration options: %s", configurable_options.configurations) + logger.debug("configuration data: %s", config) for configuration in configurable_options.configurations(): if not captions: captions = configuration.label diff --git a/daemon/core/api/tlv/structutils.py b/daemon/core/api/tlv/structutils.py index 41358848..d67f388e 100644 --- a/daemon/core/api/tlv/structutils.py +++ b/daemon/core/api/tlv/structutils.py @@ -4,6 +4,8 @@ Utilities for working with python struct data. import logging +logger = logging.getLogger(__name__) + def pack_values(clazz, packers): """ @@ -15,7 +17,7 @@ def pack_values(clazz, packers): """ # iterate through tuples of values to pack - logging.debug("packing: %s", packers) + logger.debug("packing: %s", packers) data = b"" for packer in packers: # check if a transformer was provided for valid values @@ -37,7 +39,7 @@ def pack_values(clazz, packers): value = transformer(value) # pack and add to existing data - logging.debug("packing: %s - %s type(%s)", tlv_type, value, type(value)) + logger.debug("packing: %s - %s type(%s)", tlv_type, value, type(value)) data += clazz.pack(tlv_type.value, value) return data diff --git a/daemon/core/config.py b/daemon/core/config.py index 1fe32adc..7d04947e 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -12,6 +12,8 @@ from core.emulator.enumerations import ConfigDataTypes from core.errors import CoreConfigError from core.nodes.network import WlanNode +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.location.mobility import WirelessModel @@ -178,7 +180,7 @@ class ConfigurableManager: :param config_type: configuration type to store configuration for :return: nothing """ - logging.debug( + logger.debug( "setting config for node(%s) type(%s): %s", node_id, config_type, config ) node_configs = self.node_configurations.setdefault(node_id, OrderedDict()) @@ -310,7 +312,7 @@ class ModelManager(ConfigurableManager): :param config: model configuration, None for default configuration :return: nothing """ - logging.debug( + logger.debug( "setting model(%s) for node(%s): %s", model_class.name, node.id, config ) self.set_model_config(node.id, model_class.name, config) @@ -339,5 +341,5 @@ class ModelManager(ConfigurableManager): model_class = self.models[model_name] models.append((model_class, config)) - logging.debug("models for node(%s): %s", node.id, models) + logger.debug("models for node(%s): %s", node.id, models) return models diff --git a/daemon/core/configservice/base.py b/daemon/core/configservice/base.py index 92371d14..64a5dd03 100644 --- a/daemon/core/configservice/base.py +++ b/daemon/core/configservice/base.py @@ -14,6 +14,7 @@ from core.config import Configuration from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNode +logger = logging.getLogger(__name__) TEMPLATES_DIR: str = "templates" @@ -133,7 +134,7 @@ class ConfigService(abc.ABC): :return: nothing :raises ConfigServiceBootError: when there is an error starting service """ - logging.info("node(%s) service(%s) starting...", self.node.name, self.name) + logger.info("node(%s) service(%s) starting...", self.node.name, self.name) self.create_dirs() self.create_files() wait = self.validation_mode == ConfigServiceMode.BLOCKING @@ -154,7 +155,7 @@ class ConfigService(abc.ABC): try: self.node.cmd(cmd) except CoreCommandError: - logging.exception( + logger.exception( f"node({self.node.name}) service({self.name}) " f"failed shutdown: {cmd}" ) @@ -250,7 +251,7 @@ class ConfigService(abc.ABC): else: text = self.get_text_template(name) rendered = self.render_text(text, data) - logging.debug( + logger.debug( "node(%s) service(%s) template(%s): \n%s", self.node.name, self.name, @@ -301,7 +302,7 @@ class ConfigService(abc.ABC): del cmds[index] index += 1 except CoreCommandError: - logging.debug( + logger.debug( f"node({self.node.name}) service({self.name}) " f"validate command failed: {cmd}" ) diff --git a/daemon/core/configservice/dependencies.py b/daemon/core/configservice/dependencies.py index be1c45e7..b24c83c6 100644 --- a/daemon/core/configservice/dependencies.py +++ b/daemon/core/configservice/dependencies.py @@ -1,6 +1,8 @@ import logging from typing import TYPE_CHECKING, Dict, List, Set +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.configservice.base import ConfigService @@ -41,7 +43,7 @@ class ConfigServiceDependencies: for name in self.node_services: service = self.node_services[name] if service.name in self.started: - logging.debug( + logger.debug( "skipping service that will already be started: %s", service.name ) continue @@ -75,7 +77,7 @@ class ConfigServiceDependencies: :param service: service to check dependencies for :return: list of config services to start in order """ - logging.debug("starting service dependency check: %s", service.name) + logger.debug("starting service dependency check: %s", service.name) self._reset() return self._visit(service) @@ -86,7 +88,7 @@ class ConfigServiceDependencies: :param current_service: service being visited :return: list of dependent services for a visited service """ - logging.debug("visiting service(%s): %s", current_service.name, self.path) + logger.debug("visiting service(%s): %s", current_service.name, self.path) self.visited.add(current_service.name) self.visiting.add(current_service.name) @@ -109,7 +111,7 @@ class ConfigServiceDependencies: self._visit(service) # add service when bottom is found - logging.debug("adding service to startup path: %s", current_service.name) + logger.debug("adding service to startup path: %s", current_service.name) self.started.add(current_service.name) self.path.append(current_service) self.visiting.remove(current_service.name) diff --git a/daemon/core/configservice/manager.py b/daemon/core/configservice/manager.py index 2761b1b2..254ce719 100644 --- a/daemon/core/configservice/manager.py +++ b/daemon/core/configservice/manager.py @@ -7,6 +7,8 @@ from core import utils from core.configservice.base import ConfigService from core.errors import CoreError +logger = logging.getLogger(__name__) + class ConfigServiceManager: """ @@ -41,7 +43,7 @@ class ConfigServiceManager: :raises CoreError: when service is a duplicate or has unmet executables """ name = service.name - logging.debug( + logger.debug( "loading service: class(%s) name(%s)", service.__class__.__name__, name ) @@ -71,12 +73,12 @@ class ConfigServiceManager: subdirs.append(path) service_errors = [] for subdir in subdirs: - logging.debug("loading config services from: %s", subdir) + logger.debug("loading config services from: %s", subdir) services = utils.load_classes(subdir, ConfigService) for service in services: try: self.add(service) except CoreError as e: service_errors.append(service.name) - logging.debug("not loading service(%s): %s", service.name, e) + logger.debug("not loading service(%s): %s", service.name, e) return service_errors diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 8ea9fcf4..07ce4644 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -9,6 +9,7 @@ from core.nodes.base import CoreNodeBase from core.nodes.interface import DEFAULT_MTU, CoreInterface from core.nodes.network import WlanNode +logger = logging.getLogger(__name__) GROUP: str = "Quagga" QUAGGA_STATE_DIR: str = "/var/run/quagga" @@ -229,7 +230,7 @@ class Ospfv3mdr(Ospfv3): def data(self) -> Dict[str, Any]: for iface in self.node.get_ifaces(): is_wireless = isinstance(iface.net, (WlanNode, EmaneNet)) - logging.info("MDR wireless: %s", is_wireless) + logger.info("MDR wireless: %s", is_wireless) return dict() def quagga_iface_config(self, iface: CoreInterface) -> str: diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 727d2faa..2ce1715f 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -14,6 +14,8 @@ from core.emulator.data import LinkOptions from core.nodes.interface import CoreInterface from core.xml import emanexml +logger = logging.getLogger(__name__) + try: from emane.events.commeffectevent import CommEffectEvent except ImportError: @@ -21,7 +23,7 @@ except ImportError: from emanesh.events.commeffectevent import CommEffectEvent except ImportError: CommEffectEvent = None - logging.debug("compatible emane python bindings not installed") + logger.debug("compatible emane python bindings not installed") def convert_none(x: float) -> int: @@ -113,11 +115,11 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): """ service = self.session.emane.service if service is None: - logging.warning("%s: EMANE event service unavailable", self.name) + logger.warning("%s: EMANE event service unavailable", self.name) return if iface is None or iface2 is None: - logging.warning("%s: missing NEM information", self.name) + logger.warning("%s: missing NEM information", self.name) return # TODO: batch these into multiple events per transmission @@ -125,7 +127,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): event = CommEffectEvent() nem1 = self.session.emane.get_nem_id(iface) nem2 = self.session.emane.get_nem_id(iface2) - logging.info("sending comm effect event") + logger.info("sending comm effect event") event.append( nem1, latency=convert_none(options.delay), diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 2eb28c28..68c88b50 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -34,6 +34,8 @@ from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase from core.nodes.interface import CoreInterface, TunTap from core.xml import emanexml +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.session import Session @@ -51,7 +53,7 @@ except ImportError: LocationEvent = None PathlossEvent = None EventServiceException = None - logging.debug("compatible emane python bindings not installed") + logger.debug("compatible emane python bindings not installed") EMANE_MODELS = [ EmaneRfPipeModel, @@ -174,11 +176,11 @@ class EmaneManager(ModelManager): # check for emane path = utils.which("emane", required=False) if not path: - logging.info("emane is not installed") + logger.info("emane is not installed") return # get version emane_version = utils.cmd("emane --version") - logging.info("using emane: %s", emane_version) + logger.info("using emane: %s", emane_version) # load default emane models self.load_models(EMANE_MODELS) # load custom models @@ -214,7 +216,7 @@ class EmaneManager(ModelManager): self.event_device = self.get_config("eventservicedevice") eventnetidx = self.session.get_control_net_index(self.event_device) if eventnetidx < 0: - logging.error( + logger.error( "invalid emane event service device provided: %s", self.event_device ) return @@ -230,18 +232,18 @@ class EmaneManager(ModelManager): # disabled otachannel for event service # only needed for e.g. antennaprofile events xmit by models - logging.info("using %s for event service traffic", self.event_device) + logger.info("using %s for event service traffic", self.event_device) try: self.service = EventService(eventchannel=self.eventchannel, otachannel=None) except EventServiceException: - logging.exception("error instantiating emane EventService") + logger.exception("error instantiating emane EventService") def load_models(self, emane_models: List[Type[EmaneModel]]) -> None: """ Load EMANE models and make them available. """ for emane_model in emane_models: - logging.debug("loading emane model: %s", emane_model.__name__) + logger.debug("loading emane model: %s", emane_model.__name__) emane_prefix = self.session.options.get_config( "emane_prefix", default=DEFAULT_EMANE_PREFIX ) @@ -281,17 +283,17 @@ class EmaneManager(ModelManager): :return: SUCCESS, NOT_NEEDED, NOT_READY in order to delay session instantiation """ - logging.debug("emane setup") + logger.debug("emane setup") with self.session.nodes_lock: for node_id in self.session.nodes: node = self.session.nodes[node_id] if isinstance(node, EmaneNet): - logging.debug( + logger.debug( "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") + logger.debug("no emane nodes in session") return EmaneState.NOT_NEEDED # check if bindings were installed @@ -302,9 +304,9 @@ class EmaneManager(ModelManager): # - needs to exist when eventservice binds to it (initeventservice) otadev = self.get_config("otamanagerdevice") netidx = self.session.get_control_net_index(otadev) - logging.debug("emane ota manager device: index(%s) otadev(%s)", netidx, otadev) + logger.debug("emane ota manager device: index(%s) otadev(%s)", netidx, otadev) if netidx < 0: - logging.error( + logger.error( "EMANE cannot start, check core config. invalid OTA device provided: %s", otadev, ) @@ -314,12 +316,12 @@ class EmaneManager(ModelManager): net_index=netidx, remove=False, conf_required=False ) eventdev = self.get_config("eventservicedevice") - logging.debug("emane event service device: eventdev(%s)", eventdev) + logger.debug("emane event service device: eventdev(%s)", eventdev) if eventdev != otadev: netidx = self.session.get_control_net_index(eventdev) - logging.debug("emane event service device index: %s", netidx) + logger.debug("emane event service device index: %s", netidx) if netidx < 0: - logging.error( + logger.error( "emane cannot start due to invalid event service device: %s", eventdev, ) @@ -346,7 +348,7 @@ class EmaneManager(ModelManager): self.starteventmonitor() self.buildeventservicexml() with self._emane_node_lock: - logging.info("emane building xmls...") + logger.info("emane building xmls...") start_data = self.get_start_data() for data in start_data: self.start_node(data) @@ -359,11 +361,11 @@ class EmaneManager(ModelManager): 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) + logger.error("emane net(%s) has no model", emane_net.name) continue for iface in emane_net.get_ifaces(): if not iface.node: - logging.error( + logger.error( "emane net(%s) connected interface(%s) missing node", emane_net.name, iface.name, @@ -403,7 +405,7 @@ class EmaneManager(ModelManager): with path.open("a") as f: f.write(f"{iface.node.name} {iface.name} {nem_id}\n") except IOError: - logging.exception("error writing to emane nem file") + logger.exception("error writing to emane nem file") def links_enabled(self) -> bool: return self.get_config("link_enabled") == "1" @@ -417,7 +419,7 @@ class EmaneManager(ModelManager): with self._emane_node_lock: for node_id in sorted(self._emane_nets): emane_net = self._emane_nets[node_id] - logging.debug( + logger.debug( "post startup for emane node: %s - %s", emane_net.id, emane_net.name ) emane_net.model.post_startup() @@ -441,7 +443,7 @@ class EmaneManager(ModelManager): with self._emane_node_lock: if not self._emane_nets: return - logging.info("stopping EMANE daemons") + logger.info("stopping EMANE daemons") if self.links_enabled(): self.link_monitor.stop() # shutdown interfaces and stop daemons @@ -467,11 +469,11 @@ class EmaneManager(ModelManager): """ for node_id in self._emane_nets: emane_net = self._emane_nets[node_id] - logging.debug("checking emane model for node: %s", node_id) + logger.debug("checking emane model for node: %s", node_id) # skip nodes that already have a model set if emane_net.model: - logging.debug( + logger.debug( "node(%s) already has model(%s)", emane_net.id, emane_net.model.name ) continue @@ -480,11 +482,11 @@ class EmaneManager(ModelManager): # 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) + logger.error("emane node(%s) has no node model", node_id) raise ValueError("emane node has no model set") config = self.get_model_config(node_id=node_id, model_name=model_name) - logging.debug("setting emane model(%s) config(%s)", model_name, config) + logger.debug("setting emane model(%s) config(%s)", model_name, config) model_class = self.models[model_name] emane_net.setmodel(model_class, config) @@ -493,12 +495,12 @@ class EmaneManager(ModelManager): ) -> Optional[LinkData]: iface1 = self.get_iface(nem1) if not iface1: - logging.error("invalid nem: %s", nem1) + logger.error("invalid nem: %s", nem1) return None node1 = iface1.node iface2 = self.get_iface(nem2) if not iface2: - logging.error("invalid nem: %s", nem2) + logger.error("invalid nem: %s", nem2) return None node2 = iface2.node if iface1.net != iface2.net: @@ -535,7 +537,7 @@ class EmaneManager(ModelManager): try: group, port = self.get_config("eventservicegroup").split(":") except ValueError: - logging.exception("invalid eventservicegroup in EMANE config") + logger.exception("invalid eventservicegroup in EMANE config") return dev = self.get_config("eventservicedevice") @@ -551,12 +553,12 @@ class EmaneManager(ModelManager): Start one EMANE daemon per node having a radio. Add a control network even if the user has not configured one. """ - logging.info("starting emane daemons...") + logger.info("starting emane daemons...") loglevel = str(EmaneManager.DEFAULT_LOG_LEVEL) 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) + logger.info("setting user-defined emane log level: %d", cfgloglevel) loglevel = str(cfgloglevel) emanecmd = f"emane -d -l {loglevel}" if realtime: @@ -574,17 +576,17 @@ class EmaneManager(ModelManager): node, 0, remove=False, conf_required=False ) if otanetidx > 0: - logging.info("adding ota device ctrl%d", otanetidx) + logger.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) + logger.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 - logging.info("OTA GROUP(%s) OTA DEV(%s)", otagroup, otadev) + logger.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: @@ -594,13 +596,13 @@ class EmaneManager(ModelManager): platform_xml = node.directory / f"{node.name}-platform.xml" args = f"{emanecmd} -f {log_file} {platform_xml}" node.cmd(args) - logging.info("node(%s) emane daemon running: %s", node.name, args) + logger.info("node(%s) emane daemon running: %s", node.name, args) else: log_file = self.session.directory / f"{node.name}-emane.log" platform_xml = self.session.directory / f"{node.name}-platform.xml" args = f"{emanecmd} -f {log_file} {platform_xml}" node.host_cmd(args, cwd=self.session.directory) - logging.info("node(%s) host emane daemon running: %s", node.name, args) + logger.info("node(%s) host emane daemon running: %s", node.name, args) def install_iface(self, iface: CoreInterface) -> None: emane_net = iface.net @@ -641,11 +643,11 @@ class EmaneManager(ModelManager): """ Start monitoring EMANE location events if configured to do so. """ - logging.info("emane start event monitor") + logger.info("emane start event monitor") if not self.doeventmonitor(): return if self.service is None: - logging.error( + logger.error( "Warning: EMANE events will not be generated " "because the emaneeventservice\n binding was " "unable to load " @@ -678,7 +680,7 @@ class EmaneManager(ModelManager): """ if self.service is None: return - logging.info( + logger.info( "subscribing to EMANE location events. (%s)", threading.currentThread().getName(), ) @@ -694,7 +696,7 @@ class EmaneManager(ModelManager): if eid == LocationEvent.IDENTIFIER: self.handlelocationevent(nem, eid, data) - logging.info( + logger.info( "unsubscribing from EMANE location events. (%s)", threading.currentThread().getName(), ) @@ -712,14 +714,14 @@ class EmaneManager(ModelManager): or "longitude" not in attrs or "altitude" not in attrs ): - logging.warning("dropped invalid location event") + logger.warning("dropped invalid location event") continue # yaw,pitch,roll,azimuth,elevation,velocity are unhandled lat = attrs["latitude"] lon = attrs["longitude"] alt = attrs["altitude"] - logging.debug("emane location event: %s,%s,%s", lat, lon, alt) + logger.debug("emane location event: %s,%s,%s", lat, lon, alt) self.handlelocationeventtoxyz(txnemid, lat, lon, alt) def handlelocationeventtoxyz( @@ -733,7 +735,7 @@ class EmaneManager(ModelManager): # convert nemid to node number iface = self.get_iface(nemid) if iface is None: - logging.info("location event for unknown NEM %s", nemid) + logger.info("location event for unknown NEM %s", nemid) return False n = iface.node.id @@ -742,7 +744,7 @@ class EmaneManager(ModelManager): x = int(x) y = int(y) z = int(z) - logging.debug( + logger.debug( "location event NEM %s (%s, %s, %s) -> (%s, %s, %s)", nemid, lat, @@ -756,7 +758,7 @@ class EmaneManager(ModelManager): ybit_check = y.bit_length() > 16 or y < 0 zbit_check = z.bit_length() > 16 or z < 0 if any([xbit_check, ybit_check, zbit_check]): - logging.error( + logger.error( "Unable to build node location message, received lat/long/alt " "exceeds coordinate space: NEM %s (%d, %d, %d)", nemid, @@ -770,7 +772,7 @@ class EmaneManager(ModelManager): try: node = self.session.get_node(n, NodeBase) except CoreError: - logging.exception( + logger.exception( "location event NEM %s has no corresponding node %s", nemid, n ) return False diff --git a/daemon/core/emane/emanemanifest.py b/daemon/core/emane/emanemanifest.py index 6ada2da7..0fb5bc17 100644 --- a/daemon/core/emane/emanemanifest.py +++ b/daemon/core/emane/emanemanifest.py @@ -5,6 +5,8 @@ from typing import Dict, List from core.config import Configuration from core.emulator.enumerations import ConfigDataTypes +logger = logging.getLogger(__name__) + manifest = None try: from emane.shell import manifest @@ -13,7 +15,7 @@ except ImportError: from emanesh import manifest except ImportError: manifest = None - logging.debug("compatible emane python bindings not installed") + logger.debug("compatible emane python bindings not installed") def _type_value(config_type: str) -> ConfigDataTypes: diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 565096bb..b6c037e0 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -15,6 +15,8 @@ from core.location.mobility import WirelessModel from core.nodes.interface import CoreInterface from core.xml import emanexml +logger = logging.getLogger(__name__) + class EmaneModel(WirelessModel): """ @@ -115,7 +117,7 @@ class EmaneModel(WirelessModel): :return: nothing """ - logging.debug("emane model(%s) has no post setup tasks", self.name) + logger.debug("emane model(%s) has no post setup tasks", self.name) def update(self, moved_ifaces: List[CoreInterface]) -> None: """ @@ -130,7 +132,7 @@ class EmaneModel(WirelessModel): emane_net = self.session.get_node(self.id, EmaneNet) emane_net.setnempositions(moved_ifaces) except CoreError: - logging.exception("error during update") + logger.exception("error during update") def linkconfig( self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None @@ -143,4 +145,4 @@ class EmaneModel(WirelessModel): :param iface2: interface two :return: nothing """ - logging.warning("emane model(%s) does not support link config", self.name) + logger.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 56473f62..a4bbae34 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -10,6 +10,8 @@ from core.emulator.data import LinkData from core.emulator.enumerations import LinkTypes, MessageFlags from core.nodes.network import CtrlNet +logger = logging.getLogger(__name__) + try: from emane import shell except ImportError: @@ -17,7 +19,7 @@ except ImportError: from emanesh import shell except ImportError: shell = None - logging.debug("compatible emane python bindings not installed") + logger.debug("compatible emane python bindings not installed") if TYPE_CHECKING: from core.emane.emanemanager import EmaneManager @@ -91,7 +93,7 @@ class EmaneClient: # get mac config mac_id, _, emane_model = components[MAC_COMPONENT_INDEX] mac_config = self.client.getConfiguration(mac_id) - logging.debug( + logger.debug( "address(%s) nem(%s) emane(%s)", self.address, nem_id, emane_model ) @@ -101,9 +103,9 @@ class EmaneClient: elif emane_model == EMANE_RFPIPE: loss_table = self.handle_rfpipe(mac_config) else: - logging.warning("unknown emane link model: %s", emane_model) + logger.warning("unknown emane link model: %s", emane_model) continue - logging.info("monitoring links nem(%s) model(%s)", nem_id, emane_model) + logger.info("monitoring links nem(%s) model(%s)", nem_id, emane_model) loss_table.mac_id = mac_id self.nems[nem_id] = loss_table @@ -138,12 +140,12 @@ class EmaneClient: def handle_tdma(self, config: Dict[str, Tuple]): pcr = config["pcrcurveuri"][0][0] - logging.debug("tdma pcr: %s", pcr) + logger.debug("tdma pcr: %s", pcr) def handle_80211(self, config: Dict[str, Tuple]) -> LossTable: unicastrate = config["unicastrate"][0][0] pcr = config["pcrcurveuri"][0][0] - logging.debug("80211 pcr: %s", pcr) + logger.debug("80211 pcr: %s", pcr) tree = etree.parse(pcr) root = tree.getroot() table = root.find("table") @@ -159,7 +161,7 @@ class EmaneClient: def handle_rfpipe(self, config: Dict[str, Tuple]) -> LossTable: pcr = config["pcrcurveuri"][0][0] - logging.debug("rfpipe pcr: %s", pcr) + logger.debug("rfpipe pcr: %s", pcr) tree = etree.parse(pcr) root = tree.getroot() table = root.find("table") @@ -192,7 +194,7 @@ class EmaneLinkMonitor: self.link_timeout = int(self.emane_manager.get_config("link_timeout")) self.initialize() if not self.clients: - logging.info("no valid emane models to monitor links") + logger.info("no valid emane models to monitor links") return self.scheduler = sched.scheduler() self.scheduler.enter(0, 0, self.check_links) @@ -228,7 +230,7 @@ class EmaneLinkMonitor: client.check_links(self.links, self.loss_threshold) except shell.ControlPortException: if self.running: - logging.exception("link monitor error") + logger.exception("link monitor error") # find new links current_links = set(self.links.keys()) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 5791f46a..1e43723b 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -19,6 +19,8 @@ from core.errors import CoreError from core.nodes.base import CoreNetworkBase, CoreNode from core.nodes.interface import CoreInterface +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emane.emanemodel import EmaneModel from core.emulator.session import Session @@ -34,7 +36,7 @@ except ImportError: from emanesh.events import LocationEvent except ImportError: LocationEvent = None - logging.debug("compatible emane python bindings not installed") + logger.debug("compatible emane python bindings not installed") class EmaneNet(CoreNetworkBase): @@ -92,9 +94,7 @@ class EmaneNet(CoreNetworkBase): def updatemodel(self, config: Dict[str, str]) -> None: if not self.model: 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 - ) + logger.info("node(%s) updating model(%s): %s", self.id, self.model.name, config) self.model.update_config(config) def setmodel(self, model: "WirelessModelType", config: Dict[str, str]) -> None: @@ -122,7 +122,7 @@ class EmaneNet(CoreNetworkBase): nem_id = self.session.emane.get_nem_id(iface) ifname = iface.localname if nem_id is None: - logging.info("nemid for %s is unknown", ifname) + logger.info("nemid for %s is unknown", ifname) return node = iface.node x, y, z = node.getposition() @@ -141,7 +141,7 @@ class EmaneNet(CoreNetworkBase): :param iface: interface to set nem position for """ if self.session.emane.service is None: - logging.info("position service not available") + logger.info("position service not available") return position = self._nem_position(iface) if position: @@ -160,7 +160,7 @@ class EmaneNet(CoreNetworkBase): return if self.session.emane.service is None: - logging.info("position service not available") + logger.info("position service not available") return event = LocationEvent() diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py index 35ea55b0..060a06bc 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/tdma.py @@ -11,6 +11,8 @@ from core.config import Configuration from core.emane import emanemodel from core.emulator.enumerations import ConfigDataTypes +logger = logging.getLogger(__name__) + class EmaneTdmaModel(emanemodel.EmaneModel): # model name @@ -58,7 +60,7 @@ class EmaneTdmaModel(emanemodel.EmaneModel): event_device = self.session.emane.event_device # initiate tdma schedule - logging.info( + logger.info( "setting up tdma schedule: schedule(%s) device(%s)", schedule, event_device ) args = f"emaneevent-tdmaschedule -i {event_device} {schedule}" diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 83e6a940..c2864157 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -13,6 +13,8 @@ from core.emulator.session import Session from core.executables import get_requirements from core.services.coreservices import ServiceManager +logger = logging.getLogger(__name__) + def signal_handler(signal_number: int, _) -> None: """ @@ -22,7 +24,7 @@ def signal_handler(signal_number: int, _) -> None: :param _: ignored :return: nothing """ - logging.info("caught signal: %s", signal_number) + logger.info("caught signal: %s", signal_number) sys.exit(signal_number) @@ -95,7 +97,7 @@ class CoreEmu: self.service_errors = core.services.load() # load custom services service_paths = self.config.get("custom_services_dir") - logging.debug("custom service paths: %s", service_paths) + logger.debug("custom service paths: %s", service_paths) if service_paths is not None: for service_path in service_paths.split(","): service_path = Path(service_path.strip()) @@ -108,7 +110,7 @@ class CoreEmu: :return: nothing """ - logging.info("shutting down all sessions") + logger.info("shutting down all sessions") sessions = self.sessions.copy() self.sessions.clear() for _id in sessions: @@ -129,7 +131,7 @@ class CoreEmu: _id += 1 session = _cls(_id, config=self.config) session.service_manager = self.service_manager - logging.info("created session: %s", _id) + logger.info("created session: %s", _id) self.sessions[_id] = session return session @@ -140,14 +142,14 @@ class CoreEmu: :param _id: session id to delete :return: True if deleted, False otherwise """ - logging.info("deleting session: %s", _id) + logger.info("deleting session: %s", _id) session = self.sessions.pop(_id, None) result = False if session: - logging.info("shutting session down: %s", _id) + logger.info("shutting session down: %s", _id) session.data_collect() session.shutdown() result = True else: - logging.error("session to delete did not exist: %s", _id) + logger.error("session to delete did not exist: %s", _id) return result diff --git a/daemon/core/emulator/distributed.py b/daemon/core/emulator/distributed.py index d062e25b..147f524f 100644 --- a/daemon/core/emulator/distributed.py +++ b/daemon/core/emulator/distributed.py @@ -20,6 +20,8 @@ from core.executables import get_requirements from core.nodes.interface import GreTap from core.nodes.network import CoreNetwork, CtrlNet +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.session import Session @@ -62,7 +64,7 @@ class DistributedServer: replace_env = env is not None if not wait: cmd += " &" - logging.debug( + logger.debug( "remote cmd server(%s) cwd(%s) wait(%s): %s", self.host, cwd, wait, cmd ) try: @@ -212,13 +214,11 @@ class DistributedController: if tunnel is not None: return tunnel # local to server - logging.info( - "local tunnel node(%s) to remote(%s) key(%s)", node.name, host, key - ) + logger.info("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_iface_master(node.brname, local_tap.localname) # server to local - logging.info( + logger.info( "remote tunnel node(%s) to local(%s) key(%s)", node.name, self.address, key ) remote_tap = GreTap( @@ -240,7 +240,7 @@ class DistributedController: :param node2_id: node two id :return: tunnel key for the node pair """ - logging.debug("creating tunnel key for: %s, %s", node1_id, node2_id) + logger.debug("creating tunnel key for: %s, %s", node1_id, node2_id) key = ( (self.session.id << 16) ^ utils.hashkey(node1_id) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 59c89f89..bc036343 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -62,6 +62,8 @@ from core.services.coreservices import CoreServices from core.xml import corexml, corexmldeployment from core.xml.corexml import CoreXmlReader, CoreXmlWriter +logger = logging.getLogger(__name__) + # maps for converting from API call node type values to classes and vice versa NODES: Dict[NodeTypes, Type[NodeBase]] = { NodeTypes.DEFAULT: CoreNode, @@ -197,7 +199,7 @@ class Session: :raises core.CoreError: when objects to link is less than 2, or no common networks are found """ - logging.info( + logger.info( "handling wireless linking node1(%s) node2(%s): %s", node1.name, node2.name, @@ -208,7 +210,7 @@ class Session: raise CoreError("no common network found for wireless link/unlink") for common_network, iface1, iface2 in common_networks: if not isinstance(common_network, (WlanNode, EmaneNet)): - logging.info( + logger.info( "skipping common network that is not wireless/emane: %s", common_network, ) @@ -263,7 +265,7 @@ class Session: else: # peer to peer link if isinstance(node1, CoreNodeBase) and isinstance(node2, CoreNodeBase): - logging.info("linking ptp: %s - %s", node1.name, node2.name) + logger.info("linking ptp: %s - %s", node1.name, node2.name) start = self.state.should_start() ptp = self.create_node(PtpNet, start) iface1 = node1.new_iface(ptp, iface1_data) @@ -286,7 +288,7 @@ class Session: elif isinstance(node1, CoreNetworkBase) and isinstance( node2, CoreNetworkBase ): - logging.info( + logger.info( "linking network to network: %s - %s", node1.name, node2.name ) iface1 = node1.linknet(node2) @@ -303,10 +305,10 @@ class Session: # configure tunnel nodes key = options.key if isinstance(node1, TunnelNode): - logging.info("setting tunnel key for: %s", node1.name) + logger.info("setting tunnel key for: %s", node1.name) node1.setkey(key, iface1_data) if isinstance(node2, TunnelNode): - logging.info("setting tunnel key for: %s", node2.name) + logger.info("setting tunnel key for: %s", node2.name) node2.setkey(key, iface2_data) self.sdt.add_link(node1_id, node2_id) return iface1, iface2 @@ -332,7 +334,7 @@ class Session: """ node1 = self.get_node(node1_id, NodeBase) node2 = self.get_node(node2_id, NodeBase) - logging.info( + logger.info( "deleting link(%s) node(%s):interface(%s) node(%s):interface(%s)", link_type.name, node1.name, @@ -409,7 +411,7 @@ class Session: options = LinkOptions() node1 = self.get_node(node1_id, NodeBase) node2 = self.get_node(node2_id, NodeBase) - logging.info( + logger.info( "update link(%s) node(%s):interface(%s) node(%s):interface(%s)", link_type.name, node1.name, @@ -525,7 +527,7 @@ class Session: raise CoreError(f"invalid distributed server: {options.server}") # create node - logging.info( + logger.info( "creating node(%s) id(%s) name(%s) start(%s)", _class.__name__, _id, @@ -547,11 +549,11 @@ class Session: # add services to needed nodes if isinstance(node, (CoreNode, PhysicalNode)): node.type = options.model - logging.debug("set node type: %s", node.type) + logger.debug("set node type: %s", node.type) self.services.add_services(node, node.type, options.services) # add config services - logging.info("setting node config services: %s", options.config_services) + logger.info("setting node config services: %s", options.config_services) for name in options.config_services: service_class = self.service_manager.get_service(name) node.add_config_service(service_class) @@ -638,7 +640,7 @@ class Session: :return: True if active, False otherwise """ result = self.state in {EventTypes.RUNTIME_STATE, EventTypes.DATACOLLECT_STATE} - logging.info("session(%s) checking if active: %s", self.id, result) + logger.info("session(%s) checking if active: %s", self.id, result) return result def open_xml(self, file_path: Path, start: bool = False) -> None: @@ -649,7 +651,7 @@ class Session: :param start: instantiate session if true, false otherwise :return: nothing """ - logging.info("opening xml: %s", file_path) + logger.info("opening xml: %s", file_path) # clear out existing session self.clear() # set state and read xml @@ -684,7 +686,7 @@ class Session: :param src_name: source name :return: nothing """ - logging.info( + logger.info( "setting state hook: %s - %s source(%s)", state, file_name, src_name ) hook = file_name, data @@ -693,7 +695,7 @@ class Session: # immediately run a hook if it is in the current state if self.state == state: - logging.info("immediately running new state hook") + logger.info("immediately running new state hook") self.run_hook(hook) def add_node_file( @@ -766,9 +768,9 @@ class Session: Shutdown all session nodes and remove the session directory. """ if self.state == EventTypes.SHUTDOWN_STATE: - logging.info("session(%s) state(%s) already shutdown", self.id, self.state) + logger.info("session(%s) state(%s) already shutdown", self.id, self.state) else: - logging.info("session(%s) state(%s) shutting down", self.id, self.state) + logger.info("session(%s) state(%s) shutting down", self.id, self.state) self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True) # clear out current core session self.clear() @@ -861,7 +863,7 @@ class Session: return self.state = state self.state_time = time.monotonic() - logging.info("changing session(%s) to state %s", self.id, state.name) + logger.info("changing session(%s) to state %s", self.id, state.name) self.write_state(state) self.run_hooks(state) self.run_state_hooks(state) @@ -881,7 +883,7 @@ class Session: with state_file.open("w") as f: f.write(f"{state.value} {state.name}\n") except IOError: - logging.exception("error writing state file: %s", state.name) + logger.exception("error writing state file: %s", state.name) def run_hooks(self, state: EventTypes) -> None: """ @@ -903,7 +905,7 @@ class Session: :return: nothing """ file_name, data = hook - logging.info("running hook %s", file_name) + logger.info("running hook %s", file_name) file_path = self.directory / file_name log_path = self.directory / f"{file_name}.log" try: @@ -920,7 +922,7 @@ class Session: env=self.get_environment(), ) except (IOError, subprocess.CalledProcessError): - logging.exception("error running hook: %s", file_path) + logger.exception("error running hook: %s", file_path) def run_state_hooks(self, state: EventTypes) -> None: """ @@ -937,7 +939,7 @@ class Session: hook(state) except Exception: message = f"exception occurred when running {state.name} state hook: {hook}" - logging.exception(message) + logger.exception(message) self.exception(ExceptionLevels.ERROR, "Session.run_state_hooks", message) def add_state_hook( @@ -1022,7 +1024,7 @@ class Session: try: utils.load_config(path, env) except IOError: - logging.exception("error reading environment file: %s", path) + logger.exception("error reading environment file: %s", path) return env def set_thumbnail(self, thumb_file: Path) -> None: @@ -1033,7 +1035,7 @@ class Session: :return: nothing """ if not thumb_file.is_file(): - logging.error("thumbnail file to set does not exist: %s", thumb_file) + logger.error("thumbnail file to set does not exist: %s", thumb_file) self.thumbnail = None return dst_path = self.directory / thumb_file.name @@ -1054,7 +1056,7 @@ class Session: gid = self.directory.stat().st_gid os.chown(self.directory, uid, gid) except IOError: - logging.exception("failed to set permission on %s", self.directory) + logger.exception("failed to set permission on %s", self.directory) self.user = user def create_node( @@ -1111,7 +1113,7 @@ class Session: with self.nodes_lock: if _id in self.nodes: node = self.nodes.pop(_id) - logging.info("deleted node(%s)", node.name) + logger.info("deleted node(%s)", node.name) if node: node.shutdown() self.sdt.delete_node(_id) @@ -1144,7 +1146,7 @@ class Session: 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") + logger.exception("error writing nodes file") def exception( self, level: ExceptionLevels, source: str, text: str, node_id: int = None @@ -1235,13 +1237,13 @@ class Session: """ # this is called from instantiate() after receiving an event message # for the instantiation state - logging.debug( + logger.debug( "session(%s) checking if not in runtime state, current state: %s", self.id, self.state.name, ) if self.state == EventTypes.RUNTIME_STATE: - logging.info("valid runtime state found, returning") + logger.info("valid runtime state found, returning") return # start event loop and set to runtime self.event_loop.run() @@ -1255,11 +1257,11 @@ class Session: :return: nothing """ if self.state.already_collected(): - logging.info( + logger.info( "session(%s) state(%s) already data collected", self.id, self.state ) return - logging.info("session(%s) state(%s) data collection", self.id, self.state) + logger.info("session(%s) state(%s) data collection", self.id, self.state) self.set_state(EventTypes.DATACOLLECT_STATE, send_event=True) # stop event loop @@ -1302,7 +1304,7 @@ class Session: :param node: node to boot :return: nothing """ - logging.info("booting node(%s): %s", node.name, [x.name for x in node.services]) + logger.info("booting node(%s): %s", node.name, [x.name for x in node.services]) self.services.boot_services(node) node.start_config_services() @@ -1323,7 +1325,7 @@ class Session: funcs.append((self.boot_node, (node,), {})) results, exceptions = utils.threadpool(funcs) total = time.monotonic() - start - logging.debug("boot run time: %s", total) + logger.debug("boot run time: %s", total) if not exceptions: self.update_control_iface_hosts() return exceptions @@ -1351,7 +1353,7 @@ class Session: """ d0 = self.options.get_config("controlnetif0") if d0: - logging.error("controlnet0 cannot be assigned with a host interface") + logger.error("controlnet0 cannot be assigned with a host interface") d1 = self.options.get_config("controlnetif1") d2 = self.options.get_config("controlnetif2") d3 = self.options.get_config("controlnetif3") @@ -1396,7 +1398,7 @@ class Session: :param conf_required: flag to check if conf is required :return: control net node """ - logging.debug( + logger.debug( "add/remove control net: index(%s) remove(%s) conf_required(%s)", net_index, remove, @@ -1410,7 +1412,7 @@ class Session: return None else: prefix_spec = CtrlNet.DEFAULT_PREFIX_LIST[net_index] - logging.debug("prefix spec: %s", prefix_spec) + logger.debug("prefix spec: %s", prefix_spec) server_iface = self.get_control_net_server_ifaces()[net_index] # return any existing controlnet bridge @@ -1433,7 +1435,7 @@ class Session: if net_index == 0: updown_script = self.options.get_config("controlnet_updown_script") if not updown_script: - logging.debug("controlnet updown script not configured") + logger.debug("controlnet updown script not configured") prefixes = prefix_spec.split() if len(prefixes) > 1: @@ -1447,7 +1449,7 @@ class Session: else: prefix = prefixes[0] - logging.info( + logger.info( "controlnet(%s) prefix(%s) updown(%s) serverintf(%s)", _id, prefix, @@ -1510,7 +1512,7 @@ class Session: 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) + logger.exception(msg) def update_control_iface_hosts( self, net_index: int = 0, remove: bool = False @@ -1528,12 +1530,12 @@ class Session: try: control_net = self.get_control_net(net_index) except CoreError: - logging.exception("error retrieving control net node") + logger.exception("error retrieving control net node") return header = f"CORE session {self.id} host entries" if remove: - logging.info("Removing /etc/hosts file entries.") + logger.info("Removing /etc/hosts file entries.") utils.file_demunge("/etc/hosts", header) return @@ -1543,7 +1545,7 @@ class Session: for ip in iface.ips(): entries.append(f"{ip.ip} {name}") - logging.info("Adding %d /etc/hosts file entries.", len(entries)) + logger.info("Adding %d /etc/hosts file entries.", len(entries)) utils.file_munge("/etc/hosts", header, "\n".join(entries) + "\n") def runtime(self) -> float: @@ -1572,7 +1574,7 @@ class Session: current_time = self.runtime() if current_time > 0: if event_time <= current_time: - logging.warning( + logger.warning( "could not schedule past event for time %s (run time is now %s)", event_time, current_time, @@ -1584,7 +1586,7 @@ class Session: ) if not name: name = "" - logging.info( + logger.info( "scheduled event %s at time %s data=%s", name, event_time + current_time, @@ -1603,12 +1605,12 @@ class Session: :return: nothing """ if data is None: - logging.warning("no data for event node(%s) name(%s)", node_id, name) + logger.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) + logger.info("running event %s at time %s cmd=%s", name, now, data) if not node_id: utils.mute_detach(data) else: diff --git a/daemon/core/gui/app.py b/daemon/core/gui/app.py index eddb60d3..d905bff3 100644 --- a/daemon/core/gui/app.py +++ b/daemon/core/gui/app.py @@ -22,6 +22,7 @@ from core.gui.statusbar import StatusBar from core.gui.themes import PADY from core.gui.toolbar import Toolbar +logger = logging.getLogger(__name__) WIDTH: int = 1000 HEIGHT: int = 800 @@ -171,7 +172,7 @@ class Application(ttk.Frame): def show_grpc_exception( self, message: str, e: grpc.RpcError, blocking: bool = False ) -> None: - logging.exception("app grpc exception", exc_info=e) + logger.exception("app grpc exception", exc_info=e) dialog = ErrorDialog(self, "GRPC Exception", message, e.details()) if blocking: dialog.show() @@ -179,7 +180,7 @@ class Application(ttk.Frame): self.after(0, lambda: dialog.show()) def show_exception(self, message: str, e: Exception) -> None: - logging.exception("app exception", exc_info=e) + logger.exception("app exception", exc_info=e) self.after( 0, lambda: ErrorDialog(self, "App Exception", message, str(e)).show() ) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 744a5be8..3559f669 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -46,6 +46,8 @@ from core.gui.graph.shape import Shape from core.gui.interface import InterfaceManager from core.gui.nodeutils import NodeDraw +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -150,7 +152,7 @@ class CoreClient: if not self.session or event.source == GUI_SOURCE: return if event.session_id != self.session.id: - logging.warning( + logger.warning( "ignoring event session(%s) current(%s)", event.session_id, self.session.id, @@ -159,7 +161,7 @@ class CoreClient: if event.link_event: self.app.after(0, self.handle_link_event, event.link_event) elif event.session_event: - logging.info("session event: %s", event) + logger.info("session event: %s", event) session_event = event.session_event if session_event.event <= SessionState.SHUTDOWN.value: self.session.state = SessionState(session_event.event) @@ -174,22 +176,22 @@ class CoreClient: else: dialog.set_pause() else: - logging.warning("unknown session event: %s", session_event) + logger.warning("unknown session event: %s", session_event) elif event.node_event: self.app.after(0, self.handle_node_event, event.node_event) elif event.config_event: - logging.info("config event: %s", event) + logger.info("config event: %s", event) elif event.exception_event: self.handle_exception_event(event.exception_event) else: - logging.info("unhandled event: %s", event) + logger.info("unhandled event: %s", event) def handle_link_event(self, event: LinkEvent) -> None: - logging.debug("Link event: %s", event) + logger.debug("Link event: %s", event) 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) + logger.warning("ignoring links with loops: %s", event) return canvas_node1 = self.canvas_nodes[node1_id] canvas_node2 = self.canvas_nodes[node2_id] @@ -207,7 +209,7 @@ class CoreClient: canvas_node1, canvas_node2, event.link ) else: - logging.warning("unknown link event: %s", event) + logger.warning("unknown link event: %s", event) else: if event.message_type == MessageType.ADD: self.app.manager.add_wired_edge(canvas_node1, canvas_node2, event.link) @@ -216,10 +218,10 @@ class CoreClient: elif event.message_type == MessageType.NONE: self.app.manager.update_wired_edge(event.link) else: - logging.warning("unknown link event: %s", event) + logger.warning("unknown link event: %s", event) def handle_node_event(self, event: NodeEvent) -> None: - logging.debug("node event: %s", event) + logger.debug("node event: %s", event) node = event.node if event.message_type == MessageType.NONE: canvas_node = self.canvas_nodes[node.id] @@ -235,10 +237,10 @@ class CoreClient: canvas_node.canvas.delete_selected_objects() elif event.message_type == MessageType.ADD: if node.id in self.session.nodes: - logging.error("core node already exists: %s", node) + logger.error("core node already exists: %s", node) self.app.manager.add_core_node(node) else: - logging.warning("unknown node event: %s", event) + logger.warning("unknown node event: %s", event) def enable_throughputs(self) -> None: self.handling_throughputs = self.client.throughputs( @@ -272,24 +274,24 @@ class CoreClient: def handle_throughputs(self, event: ThroughputsEvent) -> None: if event.session_id != self.session.id: - logging.warning( + logger.warning( "ignoring throughput event session(%s) current(%s)", event.session_id, self.session.id, ) return - logging.debug("handling throughputs event: %s", event) + logger.debug("handling throughputs event: %s", event) self.app.after(0, self.app.manager.set_throughputs, event) def handle_cpu_event(self, event: core_pb2.CpuUsageEvent) -> None: self.app.after(0, self.app.statusbar.set_cpu, event.usage) def handle_exception_event(self, event: ExceptionEvent) -> None: - logging.info("exception event: %s", event) + logger.info("exception event: %s", event) self.app.statusbar.add_alert(event) def join_session(self, session_id: int) -> None: - logging.info("joining session(%s)", session_id) + logger.info("joining session(%s)", session_id) self.reset() try: self.session = self.client.get_session(session_id) @@ -314,7 +316,7 @@ class CoreClient: # canvas setting config = self.session.metadata canvas_config = config.get("canvas") - logging.debug("canvas metadata: %s", canvas_config) + logger.debug("canvas metadata: %s", canvas_config) if canvas_config: canvas_config = json.loads(canvas_config) self.app.manager.parse_metadata(canvas_config) @@ -324,14 +326,14 @@ class CoreClient: if shapes_config: shapes_config = json.loads(shapes_config) for shape_config in shapes_config: - logging.debug("loading shape: %s", shape_config) + logger.debug("loading shape: %s", shape_config) Shape.from_metadata(self.app, shape_config) # load edges config edges_config = config.get("edges") if edges_config: edges_config = json.loads(edges_config) - logging.info("edges config: %s", edges_config) + logger.info("edges config: %s", edges_config) for edge_config in edges_config: edge = self.links[edge_config["token"]] edge.width = edge_config["width"] @@ -347,7 +349,7 @@ class CoreClient: if canvas_node: canvas_node.hide() else: - logging.warning("invalid node to hide: %s", _id) + logger.warning("invalid node to hide: %s", _id) def create_new_session(self) -> None: """ @@ -355,7 +357,7 @@ class CoreClient: """ try: session_id = self.client.create_session() - logging.info("created session: %s", session_id) + logger.info("created session: %s", session_id) self.join_session(session_id) location_config = self.app.guiconfig.location self.session.location = SessionLocation( @@ -377,7 +379,7 @@ class CoreClient: session_id = self.session.id try: response = self.client.delete_session(session_id) - logging.info("deleted session(%s), Result: %s", session_id, response) + logger.info("deleted session(%s), Result: %s", session_id, response) except grpc.RpcError as e: self.app.show_grpc_exception("Delete Session Error", e) @@ -419,7 +421,7 @@ class CoreClient: dialog = SessionsDialog(self.app, True) dialog.show() except grpc.RpcError as e: - logging.exception("core setup error") + logger.exception("core setup error") self.app.show_grpc_exception("Setup Error", e, blocking=True) self.app.close() @@ -456,7 +458,7 @@ class CoreClient: result, exceptions = self.client.start_session( self.session, asymmetric_links ) - logging.info("start session(%s), result: %s", self.session.id, result) + logger.info("start session(%s), result: %s", self.session.id, result) if result: self.set_metadata() except grpc.RpcError as e: @@ -469,7 +471,7 @@ class CoreClient: result = False try: result = self.client.stop_session(session_id) - logging.info("stopped session(%s), result: %s", session_id, result) + logger.info("stopped session(%s), result: %s", session_id, result) except grpc.RpcError as e: self.app.show_grpc_exception("Stop Session Error", e) return result @@ -513,7 +515,7 @@ class CoreClient: canvas=canvas_config, shapes=shapes, edges=edges_config, hidden=hidden ) response = self.client.set_session_metadata(self.session.id, metadata) - logging.debug("set session metadata %s, result: %s", metadata, response) + logger.debug("set session metadata %s, result: %s", metadata, response) def launch_terminal(self, node_id: int) -> None: try: @@ -527,7 +529,7 @@ class CoreClient: return node_term = self.client.get_node_terminal(self.session.id, node_id) cmd = f"{terminal} {node_term} &" - logging.info("launching terminal %s", cmd) + logger.info("launching terminal %s", cmd) os.system(cmd) except grpc.RpcError as e: self.app.show_grpc_exception("Node Terminal Error", e) @@ -540,16 +542,16 @@ class CoreClient: Save core session as to an xml file """ if not file_path and not self.session.file: - logging.error("trying to save xml for session with no file") + logger.error("trying to save xml for session with no file") return if not file_path: file_path = str(self.session.file) try: if not self.is_runtime(): - logging.debug("Send session data to the daemon") + logger.debug("Send session data to the daemon") self.send_data() self.client.save_xml(self.session.id, file_path) - logging.info("saved xml file %s", file_path) + logger.info("saved xml file %s", file_path) except grpc.RpcError as e: self.app.show_grpc_exception("Save XML Error", e) @@ -559,7 +561,7 @@ class CoreClient: """ try: result, session_id = self._client.open_xml(file_path) - logging.info( + logger.info( "open xml file %s, result(%s) session(%s)", file_path, result, @@ -573,14 +575,14 @@ class CoreClient: node_service = self.client.get_node_service( self.session.id, node_id, service_name ) - logging.debug( + logger.debug( "get node(%s) service(%s): %s", node_id, service_name, node_service ) return node_service def set_node_service(self, node_id: int, config: ServiceConfig) -> NodeServiceData: result = self.client.set_node_service(self.session.id, config) - logging.info("set node service result(%s): %s", result, config) + logger.info("set node service result(%s): %s", result, config) return self.client.get_node_service(self.session.id, node_id, config.service) def get_node_service_file( @@ -589,7 +591,7 @@ class CoreClient: data = self.client.get_node_service_file( self.session.id, node_id, service_name, file_name ) - logging.debug( + logger.debug( "get service file for node(%s), service: %s, file: %s, data: %s", node_id, service_name, @@ -603,7 +605,7 @@ class CoreClient: ) -> None: config = ServiceFileConfig(node_id, service_name, file_name, data) result = self.client.set_node_service_file(self.session.id, config) - logging.info("set service file config %s: %s", config, result) + logger.info("set service file config %s: %s", config, result) def create_nodes_and_links(self) -> None: """ @@ -612,7 +614,7 @@ class CoreClient: self.client.set_session_state(self.session.id, SessionState.DEFINITION) for node in self.session.nodes.values(): node_id = self.client.add_node(self.session.id, node, source=GUI_SOURCE) - logging.debug("created node: %s", node_id) + logger.debug("created node: %s", node_id) asymmetric_links = [] for edge in self.links.values(): self.add_link(edge.link) @@ -648,7 +650,7 @@ class CoreClient: """ Clean ups when done using grpc """ - logging.debug("close grpc") + logger.debug("close grpc") self.client.close() def next_node_id(self) -> int: @@ -704,7 +706,7 @@ class CoreClient: services = self.session.default_services.get(model) if services: node.services = services.copy() - logging.info( + logger.info( "add node(%s) to session(%s), coordinates(%s, %s)", node.name, self.session.id, @@ -831,13 +833,13 @@ class CoreClient: return config_service_protos def run(self, node_id: int) -> str: - logging.info("running node(%s) cmd: %s", node_id, self.observer) + logger.info("running node(%s) cmd: %s", node_id, self.observer) _, output = self.client.node_command(self.session.id, node_id, self.observer) return output def get_wlan_config(self, node_id: int) -> Dict[str, ConfigOption]: config = self.client.get_wlan_config(self.session.id, node_id) - logging.debug( + logger.debug( "get wlan configuration from node %s, result configuration: %s", node_id, config, @@ -846,7 +848,7 @@ class CoreClient: def get_mobility_config(self, node_id: int) -> Dict[str, ConfigOption]: config = self.client.get_mobility_config(self.session.id, node_id) - logging.debug( + logger.debug( "get mobility config from node %s, result configuration: %s", node_id, config, @@ -861,7 +863,7 @@ class CoreClient: config = self.client.get_emane_model_config( self.session.id, node_id, model, iface_id ) - logging.debug( + logger.debug( "get emane model config: node id: %s, EMANE model: %s, " "interface: %s, config: %s", node_id, @@ -873,17 +875,17 @@ class CoreClient: def execute_script(self, script) -> None: session_id = self.client.execute_script(script) - logging.info("execute python script %s", session_id) + logger.info("execute python script %s", session_id) if session_id != -1: self.join_session(session_id) def add_link(self, link: Link) -> None: result, _, _ = self.client.add_link(self.session.id, link, source=GUI_SOURCE) - logging.debug("added link: %s", result) + logger.debug("added link: %s", result) if not result: - logging.error("error adding link: %s", link) + logger.error("error adding link: %s", link) def edit_link(self, link: Link) -> None: result = self.client.edit_link(self.session.id, link, source=GUI_SOURCE) if not result: - logging.error("error editing link: %s", link) + logger.error("error editing link: %s", link) diff --git a/daemon/core/gui/dialogs/canvaswallpaper.py b/daemon/core/gui/dialogs/canvaswallpaper.py index 871de2f7..0ef294c7 100644 --- a/daemon/core/gui/dialogs/canvaswallpaper.py +++ b/daemon/core/gui/dialogs/canvaswallpaper.py @@ -13,6 +13,8 @@ from core.gui.graph.graph import CanvasGraph from core.gui.themes import PADX, PADY from core.gui.widgets import image_chooser +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -167,5 +169,5 @@ class CanvasWallpaperDialog(Dialog): try: self.canvas.set_wallpaper(filename) except FileNotFoundError: - logging.error("invalid background: %s", filename) + logger.error("invalid background: %s", filename) self.destroy() diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 25c3613c..2a60b7c4 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -18,6 +18,8 @@ from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CodeText, ConfigFrame, ListboxScroll +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application from core.gui.coreclient import CoreClient @@ -97,7 +99,7 @@ class ConfigServiceConfigDialog(Dialog): if service_config: for key, value in service_config.config.items(): self.config[key].value = value - logging.info("default config: %s", self.default_config) + logger.info("default config: %s", self.default_config) for file, data in service_config.templates.items(): self.modified_files.add(file) self.temp_service_files[file] = data @@ -181,7 +183,7 @@ class ConfigServiceConfigDialog(Dialog): self.modes_combobox.bind("<>", self.handle_mode_changed) self.modes_combobox.grid(row=0, column=1, sticky=tk.EW, pady=PADY) - logging.info("config service config: %s", self.config) + logger.info("config service config: %s", self.config) self.config_frame = ConfigFrame(tab, self.app, self.config) self.config_frame.draw_config() self.config_frame.grid(sticky=tk.NSEW, pady=PADY) @@ -328,7 +330,7 @@ class ConfigServiceConfigDialog(Dialog): 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) + logger.info("mode config: %s", config) self.config_frame.set_values(config) def update_template_file_data(self, event: tk.Event) -> None: @@ -350,7 +352,7 @@ class ConfigServiceConfigDialog(Dialog): def click_defaults(self) -> None: self.node.config_service_configs.pop(self.service_name, None) - logging.info( + logger.info( "cleared config service config: %s", self.node.config_service_configs ) self.temp_service_files = dict(self.original_service_files) @@ -358,7 +360,7 @@ class ConfigServiceConfigDialog(Dialog): self.template_text.text.delete(1.0, "end") self.template_text.text.insert("end", self.temp_service_files[filename]) if self.config_frame: - logging.info("resetting defaults: %s", self.default_config) + logger.info("resetting defaults: %s", self.default_config) self.config_frame.set_values(self.default_config) def click_copy(self) -> None: diff --git a/daemon/core/gui/dialogs/customnodes.py b/daemon/core/gui/dialogs/customnodes.py index d10bf30c..065cc43e 100644 --- a/daemon/core/gui/dialogs/customnodes.py +++ b/daemon/core/gui/dialogs/customnodes.py @@ -13,6 +13,8 @@ from core.gui.nodeutils import NodeDraw from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CheckboxList, ListboxScroll, image_chooser +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -209,7 +211,7 @@ class CustomNodesDialog(Dialog): 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) + logger.info("saving custom nodes: %s", self.app.guiconfig.nodes) self.app.save_config() self.destroy() @@ -219,7 +221,7 @@ class CustomNodesDialog(Dialog): image_file = str(Path(self.image_file).absolute()) custom_node = CustomNode(name, image_file, list(self.services)) node_draw = NodeDraw.from_custom(custom_node) - logging.info( + logger.info( "created new custom node (%s), image file (%s), services: (%s)", name, image_file, @@ -239,7 +241,7 @@ class CustomNodesDialog(Dialog): node_draw.image_file = str(Path(self.image_file).absolute()) node_draw.image = self.image node_draw.services = set(self.services) - logging.debug( + logger.debug( "edit custom node (%s), image: (%s), services (%s)", node_draw.model, node_draw.image_file, diff --git a/daemon/core/gui/dialogs/executepython.py b/daemon/core/gui/dialogs/executepython.py index 0bef9dc1..8fe44616 100644 --- a/daemon/core/gui/dialogs/executepython.py +++ b/daemon/core/gui/dialogs/executepython.py @@ -7,6 +7,8 @@ from core.gui.appconfig import SCRIPT_PATH from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -83,6 +85,6 @@ class ExecutePythonDialog(Dialog): def script_execute(self) -> None: file = self.file_entry.get() options = self.option_entry.get() - logging.info("Execute %s with options %s", file, options) + logger.info("Execute %s with options %s", file, options) self.app.core.execute_script(file) self.destroy() diff --git a/daemon/core/gui/dialogs/find.py b/daemon/core/gui/dialogs/find.py index 3b899ef8..54be81b0 100644 --- a/daemon/core/gui/dialogs/find.py +++ b/daemon/core/gui/dialogs/find.py @@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, Optional from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -139,8 +141,8 @@ class FindDialog(Dialog): _x, _y, _, _ = canvas_node.canvas.bbox(canvas_node.id) oid = canvas_node.canvas.find_withtag("rectangle") x0, y0, x1, y1 = canvas_node.canvas.bbox(oid[0]) - logging.debug("Dist to most left: %s", abs(x0 - _x)) - logging.debug("White canvas width: %s", abs(x0 - x1)) + logger.debug("Dist to most left: %s", abs(x0 - _x)) + logger.debug("White canvas width: %s", abs(x0 - x1)) # calculate the node's location # (as fractions of white canvas's width and height) diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index a263c7be..686cd5ae 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -17,6 +17,8 @@ from core.gui.dialogs.emaneconfig import EmaneModelDialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import ListboxScroll, image_chooser +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.node import CanvasNode @@ -261,7 +263,7 @@ class NodeConfigDialog(Dialog): if nutils.is_rj45(self.node): ifaces = self.app.core.client.get_ifaces() - logging.debug("host machine available interfaces: %s", ifaces) + logger.debug("host machine available interfaces: %s", ifaces) ifaces_scroll = ListboxScroll(frame) ifaces_scroll.listbox.config(state=state) ifaces_scroll.grid( diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index 2141b3dc..de97ea15 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -12,6 +12,8 @@ from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CheckboxList, ListboxScroll +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -131,7 +133,7 @@ class NodeConfigServiceDialog(Dialog): def click_save(self) -> None: self.node.config_services = self.current_services.copy() - logging.info("saved node config services: %s", self.node.config_services) + logger.info("saved node config services: %s", self.node.config_services) self.destroy() def click_cancel(self) -> None: diff --git a/daemon/core/gui/dialogs/preferences.py b/daemon/core/gui/dialogs/preferences.py index f78e5c48..4a6a1c08 100644 --- a/daemon/core/gui/dialogs/preferences.py +++ b/daemon/core/gui/dialogs/preferences.py @@ -9,6 +9,8 @@ from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY, scale_fonts from core.gui.validation import LARGEST_SCALE, SMALLEST_SCALE +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -102,7 +104,7 @@ class PreferencesDialog(Dialog): def theme_change(self, event: tk.Event) -> None: theme = self.theme.get() - logging.info("changing theme: %s", theme) + logger.info("changing theme: %s", theme) self.app.style.theme_use(theme) def click_save(self) -> None: diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index a5e67f02..1d6881df 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -20,6 +20,8 @@ from core.gui.images import ImageEnum from core.gui.themes import FRAME_PAD, PADX, PADY from core.gui.widgets import CodeText, ListboxScroll +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application from core.gui.coreclient import CoreClient @@ -393,7 +395,7 @@ class ServiceConfigDialog(Dialog): 1.0, "end" ) else: - logging.debug("file already existed") + logger.debug("file already existed") def delete_filename(self) -> None: cbb = self.filename_combobox @@ -601,7 +603,7 @@ class ServiceConfigDialog(Dialog): i = dirs.index(d) self.dir_list.listbox.delete(i) except ValueError: - logging.debug("directory is not in the list") + logger.debug("directory is not in the list") self.directory_entry.delete(0, "end") def directory_select(self, event) -> None: diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index c642b084..06b91313 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -10,6 +10,8 @@ from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -55,7 +57,7 @@ class SessionOptionsDialog(Dialog): try: session_id = self.app.core.session.id result = self.app.core.client.set_session_options(session_id, config) - logging.info("saved session config: %s", result) + logger.info("saved session config: %s", result) except grpc.RpcError as e: self.app.show_grpc_exception("Set Session Options Error", e) self.destroy() diff --git a/daemon/core/gui/dialogs/sessions.py b/daemon/core/gui/dialogs/sessions.py index ebf2218d..deca7404 100644 --- a/daemon/core/gui/dialogs/sessions.py +++ b/daemon/core/gui/dialogs/sessions.py @@ -12,6 +12,8 @@ from core.gui.images import ImageEnum from core.gui.task import ProgressTask from core.gui.themes import PADX, PADY +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -31,7 +33,7 @@ class SessionsDialog(Dialog): def get_sessions(self) -> List[SessionSummary]: try: sessions = self.app.core.client.get_sessions() - logging.info("sessions: %s", sessions) + logger.info("sessions: %s", sessions) return sorted(sessions, key=lambda x: x.id) except grpc.RpcError as e: self.app.show_grpc_exception("Get Sessions Error", e) @@ -175,7 +177,7 @@ class SessionsDialog(Dialog): self.selected_id = None self.delete_button.config(state=tk.DISABLED) self.connect_button.config(state=tk.DISABLED) - logging.debug("selected session: %s", self.selected_session) + logger.debug("selected session: %s", self.selected_session) def click_connect(self) -> None: if not self.selected_session: @@ -199,7 +201,7 @@ class SessionsDialog(Dialog): def click_delete(self) -> None: if not self.selected_session: return - logging.info("click delete session: %s", self.selected_session) + logger.info("click delete session: %s", self.selected_session) self.tree.delete(self.selected_id) self.app.core.delete_session(self.selected_session) session_id = None diff --git a/daemon/core/gui/graph/edges.py b/daemon/core/gui/graph/edges.py index 509b67ec..405ef658 100644 --- a/daemon/core/gui/graph/edges.py +++ b/daemon/core/gui/graph/edges.py @@ -11,6 +11,8 @@ from core.gui.frames.link import EdgeInfoFrame, WirelessEdgeInfoFrame from core.gui.graph import tags from core.gui.utils import bandwidth_text, delay_jitter_text +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.graph import CanvasGraph @@ -393,7 +395,7 @@ class Edge: self.dst.canvas.coords(self.dst_label2, *dst_pos) def delete(self) -> None: - logging.debug("deleting canvas edge, id: %s", self.id) + logger.debug("deleting canvas edge, id: %s", self.id) self.src.canvas.delete(self.id) self.src.canvas.delete(self.src_label) self.src.canvas.delete(self.dst_label) @@ -488,7 +490,7 @@ class CanvasWirelessEdge(Edge): token: str, link: Link, ) -> None: - logging.debug("drawing wireless link from node %s to node %s", src, dst) + logger.debug("drawing wireless link from node %s to node %s", src, dst) super().__init__(app, src, dst) self.src.wireless_edges.add(self) self.dst.wireless_edges.add(self) @@ -622,7 +624,7 @@ class CanvasEdge(Edge): self.draw_link_options() def complete(self, dst: "CanvasNode", link: Link = None) -> None: - logging.debug( + logger.debug( "completing wired link from node(%s) to node(%s)", self.src.core_node.name, dst.core_node.name, diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 7dd68215..93401f95 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -18,6 +18,8 @@ from core.gui.graph.node import CanvasNode, ShadowNode from core.gui.graph.shape import Shape from core.gui.graph.shapeutils import ShapeType, is_draw_shape, is_marker +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.manager import CanvasManager @@ -184,7 +186,7 @@ class CanvasGraph(tk.Canvas): """ Draw a node or finish drawing an edge according to the current graph mode """ - logging.debug("click release") + logger.debug("click release") x, y = self.canvas_xy(event) if not self.inside_canvas(x, y): return @@ -210,7 +212,7 @@ class CanvasGraph(tk.Canvas): else: self.focus_set() self.selected = self.get_selected(event) - logging.debug( + logger.debug( "click release selected(%s) mode(%s)", self.selected, self.manager.mode ) if self.manager.mode == GraphMode.EDGE: @@ -228,7 +230,7 @@ class CanvasGraph(tk.Canvas): edge = self.drawing_edge self.drawing_edge = None # edge dst must be a node - logging.debug("current selected: %s", self.selected) + logger.debug("current selected: %s", self.selected) dst_node = self.nodes.get(self.selected) if not dst_node: edge.delete() @@ -331,8 +333,8 @@ class CanvasGraph(tk.Canvas): self.offset[0] * factor + event.x * (1 - factor), self.offset[1] * factor + event.y * (1 - factor), ) - logging.debug("ratio: %s", self.ratio) - logging.debug("offset: %s", self.offset) + logger.debug("ratio: %s", self.ratio) + logger.debug("offset: %s", self.offset) self.app.statusbar.set_zoom(self.ratio) if self.wallpaper: self.redraw_wallpaper() @@ -347,10 +349,10 @@ class CanvasGraph(tk.Canvas): self.cursor = x, y selected = self.get_selected(event) - logging.debug("click press(%s): %s", self.cursor, selected) + logger.debug("click press(%s): %s", self.cursor, selected) x_check = self.cursor[0] - self.offset[0] y_check = self.cursor[1] - self.offset[1] - logging.debug("click press offset(%s, %s)", x_check, y_check) + logger.debug("click press offset(%s, %s)", x_check, y_check) is_node = selected in self.nodes if self.manager.mode == GraphMode.EDGE and is_node: node = self.nodes[selected] @@ -387,7 +389,7 @@ class CanvasGraph(tk.Canvas): node = self.nodes[selected] self.select_object(node.id) self.selected = selected - logging.debug( + logger.debug( "selected node(%s), coords: (%s, %s)", node.core_node.name, node.core_node.position.x, @@ -397,7 +399,7 @@ class CanvasGraph(tk.Canvas): shadow_node = self.shadow_nodes[selected] self.select_object(shadow_node.id) self.selected = selected - logging.debug( + logger.debug( "selected shadow node(%s), coords: (%s, %s)", shadow_node.node.core_node.name, shadow_node.node.core_node.position.x, @@ -418,7 +420,7 @@ class CanvasGraph(tk.Canvas): self.cursor = x, y # handle multiple selections - logging.debug("control left click: %s", event) + logger.debug("control left click: %s", event) selected = self.get_selected(event) if ( selected not in self.selection @@ -489,12 +491,12 @@ class CanvasGraph(tk.Canvas): """ delete selected nodes and any data that relates to it """ - logging.debug("press delete key") + logger.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") + logger.debug("node deletion is disabled during runtime state") def double_click(self, event: tk.Event) -> None: selected = self.get_selected(event) @@ -606,10 +608,10 @@ class CanvasGraph(tk.Canvas): self.draw_wallpaper(image) def redraw_canvas(self, dimensions: Tuple[int, int] = None) -> None: - logging.debug("redrawing canvas to dimensions: %s", dimensions) + logger.debug("redrawing canvas to dimensions: %s", dimensions) # reset scale and move back to original position - logging.debug("resetting scaling: %s %s", self.ratio, self.offset) + logger.debug("resetting scaling: %s %s", self.ratio, self.offset) factor = 1 / self.ratio self.scale(tk.ALL, self.offset[0], self.offset[1], factor, factor) self.move(tk.ALL, -self.offset[0], -self.offset[1]) @@ -628,11 +630,11 @@ class CanvasGraph(tk.Canvas): def redraw_wallpaper(self) -> None: if self.adjust_to_dim.get(): - logging.debug("drawing wallpaper to canvas dimensions") + logger.debug("drawing wallpaper to canvas dimensions") self.resize_to_wallpaper() else: option = ScaleOption(self.scale_option.get()) - logging.debug("drawing canvas using scaling option: %s", option) + logger.debug("drawing canvas using scaling option: %s", option) if option == ScaleOption.UPPER_LEFT: self.wallpaper_upper_left() elif option == ScaleOption.CENTERED: @@ -640,7 +642,7 @@ class CanvasGraph(tk.Canvas): elif option == ScaleOption.SCALED: self.wallpaper_scaled() elif option == ScaleOption.TILED: - logging.warning("tiled background not implemented yet") + logger.warning("tiled background not implemented yet") self.organize() def organize(self) -> None: @@ -648,7 +650,7 @@ class CanvasGraph(tk.Canvas): self.tag_raise(tag) def set_wallpaper(self, filename: Optional[str]) -> None: - logging.info("setting canvas(%s) background: %s", self.id, filename) + logger.info("setting canvas(%s) background: %s", self.id, filename) if filename: img = Image.open(filename) self.wallpaper = img @@ -673,10 +675,10 @@ class CanvasGraph(tk.Canvas): def copy(self) -> None: if self.core.is_runtime(): - logging.debug("copy is disabled during runtime state") + logger.debug("copy is disabled during runtime state") return if self.selection: - logging.debug("to copy nodes: %s", self.selection) + logger.debug("to copy nodes: %s", self.selection) self.to_copy.clear() for node_id in self.selection.keys(): canvas_node = self.nodes[node_id] @@ -684,7 +686,7 @@ class CanvasGraph(tk.Canvas): def paste(self) -> None: if self.core.is_runtime(): - logging.debug("paste is disabled during runtime state") + logger.debug("paste is disabled during runtime state") return # maps original node canvas id to copy node canvas id copy_map = {} @@ -825,7 +827,7 @@ class CanvasGraph(tk.Canvas): wallpaper = Path(wallpaper) if not wallpaper.is_file(): wallpaper = appconfig.BACKGROUNDS_PATH.joinpath(wallpaper) - logging.info("canvas(%s), wallpaper: %s", self.id, wallpaper) + logger.info("canvas(%s), wallpaper: %s", self.id, wallpaper) if wallpaper.is_file(): self.set_wallpaper(str(wallpaper)) else: diff --git a/daemon/core/gui/graph/manager.py b/daemon/core/gui/graph/manager.py index 5f4be2e2..7a2c0ed8 100644 --- a/daemon/core/gui/graph/manager.py +++ b/daemon/core/gui/graph/manager.py @@ -19,6 +19,8 @@ from core.gui.graph.node import CanvasNode from core.gui.graph.shapeutils import ShapeType from core.gui.nodeutils import NodeDraw +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application from core.gui.coreclient import CoreClient @@ -166,7 +168,7 @@ class CanvasManager: canvas_id = self._next_id() self.notebook.add(tab, text=f"Canvas {canvas_id}") unique_id = self.notebook.tabs()[-1] - logging.info("creating canvas(%s)", canvas_id) + logger.info("creating canvas(%s)", canvas_id) self.canvas_ids[unique_id] = canvas_id self.unique_ids[canvas_id] = unique_id @@ -213,7 +215,7 @@ class CanvasManager: self.unique_ids.clear() self.edges.clear() self.wireless_edges.clear() - logging.info("cleared canvases") + logger.info("cleared canvases") # reset settings self.show_node_labels.set(True) @@ -289,7 +291,7 @@ class CanvasManager: for canvas_config in config.get("canvases", []): canvas_id = canvas_config.get("id") if canvas_id is None: - logging.error("canvas config id not provided") + logger.error("canvas config id not provided") continue canvas = self.get(canvas_id) canvas.parse_metadata(canvas_config) @@ -297,7 +299,7 @@ class CanvasManager: def add_core_node(self, core_node: Node) -> None: # get canvas tab for node canvas_id = core_node.canvas if core_node.canvas > 0 else 1 - logging.info("adding core node canvas(%s): %s", core_node.name, canvas_id) + logger.info("adding core node canvas(%s): %s", core_node.name, canvas_id) canvas = self.get(canvas_id) image = nutils.get_icon(core_node, self.app) x = core_node.position.x @@ -354,7 +356,7 @@ class CanvasManager: network_id = link.network_id if link.network_id else None token = create_wireless_token(src.id, dst.id, network_id) if token in self.wireless_edges: - logging.warning("ignoring link that already exists: %s", link) + logger.warning("ignoring link that already exists: %s", link) return edge = CanvasWirelessEdge(self.app, src, dst, network_id, token, link) self.wireless_edges[token] = edge diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index e81a1022..1b79e530 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -24,6 +24,8 @@ from core.gui.graph.edges import CanvasEdge, CanvasWirelessEdge from core.gui.graph.tooltip import CanvasTooltip from core.gui.images import ImageEnum +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.graph import CanvasGraph @@ -87,7 +89,7 @@ class CanvasNode: self.canvas.tag_bind(self.id, "", self.show_info) def delete(self) -> None: - logging.debug("Delete canvas node for %s", self.core_node) + logger.debug("Delete canvas node for %s", self.core_node) self.canvas.delete(self.id) self.canvas.delete(self.text_id) self.delete_antennas() @@ -110,7 +112,7 @@ class CanvasNode: """ delete one antenna """ - logging.debug("Delete an antenna on %s", self.core_node.name) + logger.debug("Delete an antenna on %s", self.core_node.name) if self.antennas: antenna_id = self.antennas.pop() self.canvas.delete(antenna_id) @@ -120,7 +122,7 @@ class CanvasNode: """ delete all antennas """ - logging.debug("Remove all antennas for %s", self.core_node.name) + logger.debug("Remove all antennas for %s", self.core_node.name) for antenna_id in self.antennas: self.canvas.delete(antenna_id) self.antennas.clear() @@ -400,7 +402,7 @@ class CanvasNode: def update_icon(self, icon_path: str) -> None: if not Path(icon_path).exists(): - logging.error(f"node icon does not exist: {icon_path}") + logger.error(f"node icon does not exist: {icon_path}") return self.core_node.icon = icon_path self.image = images.from_file(icon_path, width=images.NODE_SIZE) diff --git a/daemon/core/gui/graph/shape.py b/daemon/core/gui/graph/shape.py index 24786b04..7db18b5b 100644 --- a/daemon/core/gui/graph/shape.py +++ b/daemon/core/gui/graph/shape.py @@ -5,6 +5,8 @@ from core.gui.dialogs.shapemod import ShapeDialog from core.gui.graph import tags from core.gui.graph.shapeutils import ShapeType +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application from core.gui.graph.graph import CanvasGraph @@ -92,7 +94,7 @@ class Shape: shape = Shape(app, canvas, shape_type, *coords, data=data) canvas.shapes[shape.id] = shape except ValueError: - logging.exception("unknown shape: %s", shape_type) + logger.exception("unknown shape: %s", shape_type) def draw(self) -> None: if self.created: @@ -139,7 +141,7 @@ class Shape: state=self.app.manager.show_annotations.state(), ) else: - logging.error("unknown shape type: %s", self.shape_type) + logger.error("unknown shape type: %s", self.shape_type) self.created = True def get_font(self) -> List[Union[int, str]]: @@ -192,7 +194,7 @@ class Shape: self.canvas.move(self.text_id, x_offset, y_offset) def delete(self) -> None: - logging.debug("Delete shape, id(%s)", self.id) + logger.debug("Delete shape, id(%s)", self.id) self.canvas.delete(self.id) self.canvas.delete(self.text_id) diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 5fa70326..8076e202 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -9,6 +9,8 @@ from core.gui import nodeutils as nutils from core.gui.graph.edges import CanvasEdge from core.gui.graph.node import CanvasNode +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -196,12 +198,12 @@ class InterfaceManager: else: self.current_subnets = self.next_subnets() else: - logging.info("ignoring subnet change for link between network nodes") + logger.info("ignoring subnet change for link between network nodes") def find_subnets( self, canvas_node: CanvasNode, visited: Set[int] = None ) -> Optional[IPNetwork]: - logging.info("finding subnet for node: %s", canvas_node.core_node.name) + logger.info("finding subnet for node: %s", canvas_node.core_node.name) subnets = None if not visited: visited = set() @@ -220,7 +222,7 @@ class InterfaceManager: else: subnets = self.find_subnets(check_node, visited) if subnets: - logging.info("found subnets: %s", subnets) + logger.info("found subnets: %s", subnets) break return subnets @@ -244,7 +246,7 @@ class InterfaceManager: iface1=src_iface, iface2=dst_iface, ) - logging.info("added link between %s and %s", src_node.name, dst_node.name) + logger.info("added link between %s and %s", src_node.name, dst_node.name) return link def create_iface(self, canvas_node: CanvasNode, wireless_link: bool) -> Interface: @@ -266,5 +268,5 @@ class InterfaceManager: ip6=ip6, ip6_mask=ip6_mask, ) - logging.info("create node(%s) interface(%s)", node.name, iface) + logger.info("create node(%s) interface(%s)", node.name, iface) return iface diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 10bf0926..d612aaa8 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -27,6 +27,8 @@ from core.gui.graph.manager import CanvasManager from core.gui.observers import ObserversMenu from core.gui.task import ProgressTask +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -274,10 +276,10 @@ class Menubar(tk.Menu): def open_recent_files(self, file_path: Path) -> None: if file_path.is_file(): - logging.debug("Open recent file %s", file_path) + logger.debug("Open recent file %s", file_path) self.open_xml_task(file_path) else: - logging.warning("File does not exist %s", file_path) + logger.warning("File does not exist %s", file_path) def update_recent_files(self) -> None: self.recent_menu.delete(0, tk.END) @@ -429,23 +431,23 @@ class Menubar(tk.Menu): canvas.show_hidden() def click_session_options(self) -> None: - logging.debug("Click options") + logger.debug("Click options") dialog = SessionOptionsDialog(self.app) if not dialog.has_error: dialog.show() def click_sessions(self) -> None: - logging.debug("Click change sessions") + logger.debug("Click change sessions") dialog = SessionsDialog(self.app) dialog.show() def click_hooks(self) -> None: - logging.debug("Click hooks") + logger.debug("Click hooks") dialog = HooksDialog(self.app) dialog.show() def click_servers(self) -> None: - logging.debug("Click emulation servers") + logger.debug("Click emulation servers") dialog = ServersDialog(self.app) dialog.show() @@ -458,7 +460,7 @@ class Menubar(tk.Menu): padding = (images.NODE_SIZE / 2) + 10 layout_size = padding + images.NODE_SIZE col_count = width // layout_size - logging.info( + logger.info( "auto grid layout: dimension(%s, %s) col(%s)", width, height, col_count ) canvas = self.manager.current() diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index 251ed275..f829f48b 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -8,6 +8,8 @@ from core.gui import images from core.gui.appconfig import CustomNode, GuiConfig from core.gui.images import ImageEnum +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -118,11 +120,11 @@ def get_icon(node: Node, app: "Application") -> PhotoImage: try: image = images.from_file(node.icon, width=images.NODE_SIZE, scale=scale) except OSError: - logging.error("invalid icon: %s", node.icon) + logger.error("invalid icon: %s", node.icon) # custom node elif is_custom(node): image_file = _get_custom_file(app.guiconfig, node.model) - logging.info("custom node file: %s", image_file) + logger.info("custom node file: %s", image_file) if image_file: image = images.from_file(image_file, width=images.NODE_SIZE, scale=scale) # built in node diff --git a/daemon/core/gui/task.py b/daemon/core/gui/task.py index 02148f5a..2623136d 100644 --- a/daemon/core/gui/task.py +++ b/daemon/core/gui/task.py @@ -4,6 +4,8 @@ import time import tkinter as tk from typing import TYPE_CHECKING, Any, Callable, Optional, Tuple +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -43,7 +45,7 @@ class ProgressTask: if self.callback: self.app.after(0, self.callback, *values) except Exception as e: - logging.exception("progress task exception") + logger.exception("progress task exception") self.app.show_exception("Task Error", e) finally: self.app.after(0, self.complete) diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 844a68f2..994d9026 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -20,6 +20,8 @@ from core.gui.task import ProgressTask from core.gui.themes import Styles from core.gui.tooltip import Tooltip +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -338,7 +340,7 @@ class Toolbar(ttk.Frame): type_enum: NodeTypeEnum, image: PhotoImage, ) -> None: - logging.debug("update button(%s): %s", button, node_draw) + logger.debug("update button(%s): %s", button, node_draw) button.configure(image=image) button.image = image self.app.manager.node_draw = node_draw @@ -399,7 +401,7 @@ class Toolbar(ttk.Frame): """ redraw buttons on the toolbar, send node and link messages to grpc server """ - logging.info("clicked stop button") + logger.info("clicked stop button") self.app.menubar.set_state(is_runtime=False) self.app.core.close_mobility_players() enable_buttons(self.runtime_frame, enabled=False) @@ -415,7 +417,7 @@ class Toolbar(ttk.Frame): def update_annotation( self, shape_type: ShapeType, image_enum: ImageEnum, image: PhotoImage ) -> None: - logging.debug("clicked annotation") + logger.debug("clicked annotation") self.annotation_button.configure(image=image) self.annotation_button.image = image self.app.manager.annotation_type = shape_type @@ -433,7 +435,7 @@ class Toolbar(ttk.Frame): self.marker_frame.grid() def click_run_button(self) -> None: - logging.debug("Click on RUN button") + logger.debug("Click on RUN button") dialog = RunToolDialog(self.app) dialog.show() diff --git a/daemon/core/gui/widgets.py b/daemon/core/gui/widgets.py index 004aa7b7..1f6cd637 100644 --- a/daemon/core/gui/widgets.py +++ b/daemon/core/gui/widgets.py @@ -10,6 +10,8 @@ from core.gui import appconfig, themes, validation from core.gui.dialogs.dialog import Dialog from core.gui.themes import FRAME_PAD, PADX, PADY +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.gui.app import Application @@ -161,7 +163,7 @@ class ConfigFrame(ttk.Notebook): ) entry.grid(row=index, column=1, sticky=tk.EW) else: - logging.error("unhandled config option type: %s", option.type) + logger.error("unhandled config option type: %s", option.type) self.values[option.name] = value def parse_config(self) -> Dict[str, str]: diff --git a/daemon/core/location/geo.py b/daemon/core/location/geo.py index af463595..5896e074 100644 --- a/daemon/core/location/geo.py +++ b/daemon/core/location/geo.py @@ -10,6 +10,7 @@ from pyproj import Transformer from core.emulator.enumerations import RegisterTlvs +logger = logging.getLogger(__name__) SCALE_FACTOR: float = 100.0 CRS_WGS84: int = 4326 CRS_PROJ: int = 3857 @@ -92,7 +93,7 @@ class GeoLocation: :param alt: altitude value :return: x,y,z representation of provided values """ - logging.debug("input lon,lat,alt(%s, %s, %s)", lon, lat, alt) + logger.debug("input lon,lat,alt(%s, %s, %s)", lon, lat, alt) px, py = self.to_pixels.transform(lon, lat) px -= self.refproj[0] py -= self.refproj[1] @@ -100,7 +101,7 @@ class GeoLocation: x = self.meters2pixels(px) + self.refxyz[0] y = -(self.meters2pixels(py) + self.refxyz[1]) z = self.meters2pixels(pz) + self.refxyz[2] - logging.debug("result x,y,z(%s, %s, %s)", x, y, z) + logger.debug("result x,y,z(%s, %s, %s)", x, y, z) return x, y, z def getgeo(self, x: float, y: float, z: float) -> Tuple[float, float, float]: @@ -112,7 +113,7 @@ class GeoLocation: :param z: z value :return: lat,lon,alt representation of provided values """ - logging.debug("input x,y(%s, %s)", x, y) + logger.debug("input x,y(%s, %s)", x, y) x -= self.refxyz[0] y = -(y - self.refxyz[1]) if z is None: @@ -123,5 +124,5 @@ class GeoLocation: py = self.refproj[1] + self.pixels2meters(y) lon, lat = self.to_geo.transform(px, py) alt = self.refgeo[2] + self.pixels2meters(z) - logging.debug("result lon,lat,alt(%s, %s, %s)", lon, lat, alt) + logger.debug("result lon,lat,alt(%s, %s, %s)", lon, lat, alt) return lat, lon, alt diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 85875457..428bc06b 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -28,6 +28,8 @@ from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface from core.nodes.network import WlanNode +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.session import Session @@ -81,7 +83,7 @@ class MobilityManager(ModelManager): if node_ids is None: node_ids = self.nodes() for node_id in node_ids: - logging.debug( + logger.debug( "node(%s) mobility startup: %s", node_id, self.get_all_configs(node_id) ) try: @@ -95,8 +97,8 @@ class MobilityManager(ModelManager): if node.mobility: self.session.event_loop.add_event(0.0, node.mobility.startup) except CoreError: - logging.exception("mobility startup error") - logging.warning( + logger.exception("mobility startup error") + logger.warning( "skipping mobility configuration for unknown node: %s", node_id ) @@ -114,7 +116,7 @@ class MobilityManager(ModelManager): try: node = get_mobility_node(self.session, node_id) except CoreError: - logging.exception( + logger.exception( "ignoring event for model(%s), unknown node(%s)", name, node_id ) return @@ -124,17 +126,17 @@ class MobilityManager(ModelManager): for model in models: cls = self.models.get(model) if not cls: - logging.warning("ignoring event for unknown model '%s'", model) + logger.warning("ignoring event for unknown model '%s'", model) continue if cls.config_type in [RegisterTlvs.WIRELESS, RegisterTlvs.MOBILITY]: model = node.mobility else: continue if model is None: - logging.warning("ignoring event, %s has no model", node.name) + logger.warning("ignoring event, %s has no model", node.name) continue if cls.name != model.name: - logging.warning( + logger.warning( "ignoring event for %s wrong model %s,%s", node.name, cls.name, @@ -410,16 +412,16 @@ class BasicRangeModel(WirelessModel): linked = self.wlan.linked(a, b) if d > self.range: if linked: - logging.debug("was linked, unlinking") + logger.debug("was linked, unlinking") self.wlan.unlink(a, b) self.sendlinkmsg(a, b, unlink=True) else: if not linked: - logging.debug("was not linked, linking") + logger.debug("was not linked, linking") self.wlan.link(a, b) self.sendlinkmsg(a, b) except KeyError: - logging.exception("error getting interfaces during calclink") + logger.exception("error getting interfaces during calclink") @staticmethod def calcdistance( @@ -449,7 +451,7 @@ class BasicRangeModel(WirelessModel): self.range = self._get_config(self.range, config, "range") if self.range is None: self.range = 0 - logging.debug("wlan %s set range to %s", self.wlan.name, self.range) + logger.debug("wlan %s set range to %s", self.wlan.name, self.range) self.bw = self._get_config(self.bw, config, "bandwidth") self.delay = self._get_config(self.delay, config, "delay") self.loss = self._get_config(self.loss, config, "error") @@ -927,7 +929,7 @@ class Ns2ScriptedMobility(WayPointMobility): def update_config(self, config: Dict[str, str]) -> None: self.file = Path(config["file"]) - logging.info( + logger.info( "ns-2 scripted mobility configured for WLAN %d using file: %s", self.id, self.file, @@ -955,11 +957,11 @@ class Ns2ScriptedMobility(WayPointMobility): try: f = file_path.open("r") except IOError: - logging.exception( + logger.exception( "ns-2 scripted mobility failed to load file: %s", self.file ) return - logging.info("reading ns-2 script file: %s", file_path) + logger.info("reading ns-2 script file: %s", file_path) ln = 0 ix = iy = iz = None inodenum = None @@ -1002,7 +1004,7 @@ class Ns2ScriptedMobility(WayPointMobility): else: raise ValueError except ValueError: - logging.exception( + logger.exception( "skipping line %d of file %s '%s'", ln, self.file, line ) continue @@ -1052,7 +1054,7 @@ class Ns2ScriptedMobility(WayPointMobility): raise ValueError self.nodemap[int(parts[0])] = int(parts[1]) except ValueError: - logging.exception("ns-2 mobility node map error") + logger.exception("ns-2 mobility node map error") def map(self, nodenum: str) -> int: """ @@ -1074,19 +1076,19 @@ class Ns2ScriptedMobility(WayPointMobility): :return: nothing """ if self.autostart == "": - logging.info("not auto-starting ns-2 script for %s", self.net.name) + logger.info("not auto-starting ns-2 script for %s", self.net.name) return try: t = float(self.autostart) except ValueError: - logging.exception( + logger.exception( "Invalid auto-start seconds specified '%s' for %s", self.autostart, self.net.name, ) return self.movenodesinitial() - logging.info("scheduling ns-2 script for %s autostart at %s", self.net.name, t) + logger.info("scheduling ns-2 script for %s autostart at %s", self.net.name, t) self.state = self.STATE_RUNNING self.session.event_loop.add_event(t, self.run) @@ -1096,7 +1098,7 @@ class Ns2ScriptedMobility(WayPointMobility): :return: nothing """ - logging.info("starting script: %s", self.file) + logger.info("starting script: %s", self.file) laststate = self.state super().start() if laststate == self.STATE_PAUSED: @@ -1117,7 +1119,7 @@ class Ns2ScriptedMobility(WayPointMobility): :return: nothing """ - logging.info("pausing script: %s", self.file) + logger.info("pausing script: %s", self.file) super().pause() self.statescript("pause") @@ -1129,7 +1131,7 @@ class Ns2ScriptedMobility(WayPointMobility): position :return: nothing """ - logging.info("stopping script: %s", self.file) + logger.info("stopping script: %s", self.file) super().stop(move_initial=move_initial) self.statescript("stop") diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 15c001d5..9779829e 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -21,6 +21,8 @@ from core.nodes.client import VnodeClient from core.nodes.interface import CoreInterface, TunTap, Veth from core.nodes.netclient import LinuxNetClient, get_net_client +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.distributed import DistributedServer from core.emulator.session import Session @@ -404,7 +406,7 @@ class CoreNodeBase(NodeBase): 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) + logger.info("node(%s) removing interface(%s)", self.name, iface.name) iface.detachnet() iface.shutdown() @@ -547,17 +549,17 @@ class CoreNode(CoreNodeBase): output = self.host_cmd(vnoded, env=env) self.pid = int(output) - logging.debug("node(%s) pid: %s", self.name, self.pid) + logger.debug("node(%s) pid: %s", self.name, self.pid) # create vnode client self.client = VnodeClient(self.name, self.ctrlchnlname) # bring up the loopback interface - logging.debug("bringing up loopback interface") + logger.debug("bringing up loopback interface") self.node_net_client.device_up("lo") # set hostname for node - logging.debug("setting hostname: %s", self.name) + logger.debug("setting hostname: %s", self.name) self.node_net_client.set_hostname(self.name) # mark node as up @@ -588,18 +590,18 @@ class CoreNode(CoreNodeBase): try: self.host_cmd(f"kill -9 {self.pid}") except CoreCommandError: - logging.exception("error killing process") + logger.exception("error killing process") # remove node directory if present try: self.host_cmd(f"rm -rf {self.ctrlchnlname}") except CoreCommandError: - logging.exception("error removing node directory") + logger.exception("error removing node directory") # clear interface data, close client, and mark self and not up self.ifaces.clear() self.client.close() self.up = False except OSError: - logging.exception("error during shutdown") + logger.exception("error during shutdown") finally: self.rmnodedir() @@ -668,7 +670,7 @@ class CoreNode(CoreNodeBase): :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - logging.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path) + logger.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path) self.cmd(f"mkdir -p {target_path}") self.cmd(f"{MOUNT} -n --bind {src_path} {target_path}") self._mounts.append((src_path, target_path)) @@ -726,9 +728,9 @@ class CoreNode(CoreNodeBase): if self.up: 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) + logger.debug("interface flow index: %s - %s", veth.name, veth.flow_id) mac = self.node_net_client.get_mac(veth.name) - logging.debug("interface mac: %s - %s", veth.name, mac) + logger.debug("interface mac: %s - %s", veth.name, mac) veth.set_mac(mac) try: @@ -867,7 +869,7 @@ class CoreNode(CoreNodeBase): :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - logging.info("adding file from %s to %s", src_path, file_path) + logger.info("adding file from %s to %s", src_path, file_path) directory = file_path.parent if self.server is None: self.client.check_cmd(f"mkdir -p {directory}") @@ -898,7 +900,7 @@ class CoreNode(CoreNodeBase): self.host_cmd(f"mkdir -m {0o755:o} -p {directory}") self.server.remote_put_temp(host_path, contents) self.host_cmd(f"chmod {mode:o} {host_path}") - logging.debug("node(%s) added file: %s; mode: 0%o", self.name, host_path, mode) + logger.debug("node(%s) added file: %s; mode: 0%o", self.name, host_path, mode) def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None: """ @@ -917,7 +919,7 @@ class CoreNode(CoreNodeBase): self.server.remote_put(src_path, host_path) if mode is not None: self.host_cmd(f"chmod {mode:o} {host_path}") - logging.info("node(%s) copied file: %s; mode: %s", self.name, host_path, mode) + logger.info("node(%s) copied file: %s; mode: %s", self.name, host_path, mode) class CoreNetworkBase(NodeBase): diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index baf204cc..8118807f 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -11,6 +11,8 @@ from core.errors import CoreCommandError from core.nodes.base import CoreNode from core.nodes.netclient import LinuxNetClient, get_net_client +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.session import Session @@ -50,7 +52,7 @@ class DockerClient: self.run(f"docker rm -f {self.name}") def check_cmd(self, cmd: str, wait: bool = True, shell: bool = False) -> str: - logging.info("docker cmd output: %s", cmd) + logger.info("docker cmd output: %s", cmd) return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell) def create_ns_cmd(self, cmd: str) -> str: @@ -60,7 +62,7 @@ class DockerClient: args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}" output = self.run(args) self.pid = output - logging.debug("node(%s) pid: %s", self.name, self.pid) + logger.debug("node(%s) pid: %s", self.name, self.pid) return output def copy_file(self, src_path: Path, dst_path: Path) -> str: @@ -169,7 +171,7 @@ class DockerNode(CoreNode): :param dir_path: path to create :return: nothing """ - logging.debug("creating node dir: %s", dir_path) + logger.debug("creating node dir: %s", dir_path) args = f"mkdir -p {dir_path}" self.cmd(args) @@ -182,7 +184,7 @@ class DockerNode(CoreNode): :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - logging.debug("mounting source(%s) target(%s)", src_path, target_path) + logger.debug("mounting source(%s) target(%s)", src_path, target_path) raise Exception("not supported") def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: @@ -194,7 +196,7 @@ class DockerNode(CoreNode): :param mode: mode for file :return: nothing """ - logging.debug("nodefile filename(%s) mode(%s)", file_path, mode) + logger.debug("nodefile filename(%s) mode(%s)", file_path, mode) temp = NamedTemporaryFile(delete=False) temp.write(contents.encode("utf-8")) temp.close() @@ -209,7 +211,7 @@ class DockerNode(CoreNode): if self.server is not None: self.host_cmd(f"rm -f {temp_path}") temp_path.unlink() - logging.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode) + logger.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode) def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None: """ @@ -221,7 +223,7 @@ class DockerNode(CoreNode): :param mode: mode to copy to :return: nothing """ - logging.info( + logger.info( "node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode ) self.cmd(f"mkdir -p {file_path.parent}") diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 604f2941..2ad1d9be 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -15,6 +15,8 @@ from core.emulator.enumerations import TransportType from core.errors import CoreCommandError, CoreError from core.nodes.netclient import LinuxNetClient, get_net_client +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.distributed import DistributedServer from core.emulator.session import Session @@ -274,7 +276,7 @@ class CoreInterface: :return: True if parameter changed, False otherwise """ # treat None and 0 as unchanged values - logging.debug("setting param: %s - %s", key, value) + logger.debug("setting param: %s - %s", key, value) if value is None or value < 0: return False @@ -457,7 +459,7 @@ class TunTap(CoreInterface): try: self.node.node_net_client.device_flush(self.name) except CoreCommandError: - logging.exception("error shutting down tunnel tap") + logger.exception("error shutting down tunnel tap") self.up = False @@ -482,14 +484,14 @@ class TunTap(CoreInterface): msg = f"attempt {i} failed with nonzero exit status {r}" if i < attempts + 1: msg += ", retrying..." - logging.info(msg) + logger.info(msg) time.sleep(delay) delay += delay if delay > maxretrydelay: delay = maxretrydelay else: msg += ", giving up" - logging.info(msg) + logger.info(msg) return result @@ -500,7 +502,7 @@ class TunTap(CoreInterface): :return: wait for device local response """ - logging.debug("waiting for device local: %s", self.localname) + logger.debug("waiting for device local: %s", self.localname) def localdevexists(): try: @@ -517,7 +519,7 @@ class TunTap(CoreInterface): :return: nothing """ - logging.debug("waiting for device node: %s", self.name) + logger.debug("waiting for device node: %s", self.name) def nodedevexists(): try: @@ -634,5 +636,5 @@ class GreTap(CoreInterface): self.net_client.device_down(self.localname) self.net_client.delete_device(self.localname) except CoreCommandError: - logging.exception("error during shutdown") + logger.exception("error during shutdown") self.localname = None diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index 858baf9c..0f1a2799 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -12,6 +12,8 @@ from core.errors import CoreCommandError from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.session import Session @@ -145,7 +147,7 @@ class LxcNode(CoreNode): :param dir_path: path to create :return: nothing """ - logging.info("creating node dir: %s", dir_path) + logger.info("creating node dir: %s", dir_path) args = f"mkdir -p {dir_path}" self.cmd(args) @@ -158,7 +160,7 @@ class LxcNode(CoreNode): :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ - logging.debug("mounting source(%s) target(%s)", src_path, target_path) + logger.debug("mounting source(%s) target(%s)", src_path, target_path) raise Exception("not supported") def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: @@ -170,7 +172,7 @@ class LxcNode(CoreNode): :param mode: mode for file :return: nothing """ - logging.debug("nodefile filename(%s) mode(%s)", file_path, mode) + logger.debug("nodefile filename(%s) mode(%s)", file_path, mode) temp = NamedTemporaryFile(delete=False) temp.write(contents.encode("utf-8")) temp.close() @@ -185,7 +187,7 @@ class LxcNode(CoreNode): if self.server is not None: self.host_cmd(f"rm -f {temp_path}") temp_path.unlink() - logging.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode) + logger.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode) def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None: """ @@ -197,7 +199,7 @@ class LxcNode(CoreNode): :param mode: mode to copy to :return: nothing """ - logging.info( + logger.info( "node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode ) self.cmd(f"mkdir -p {file_path.parent}") diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 5d103805..1389cb4f 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -26,6 +26,8 @@ from core.nodes.base import CoreNetworkBase from core.nodes.interface import CoreInterface, GreTap, Veth from core.nodes.netclient import get_net_client +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.distributed import DistributedServer from core.emulator.session import Session @@ -90,7 +92,7 @@ class EbtablesQueue: try: del self.last_update_time[wlan] except KeyError: - logging.exception( + logger.exception( "error deleting last update time for wlan, ignored before: %s", wlan ) if len(self.last_update_time) > 0: @@ -186,7 +188,7 @@ class EbtablesQueue: try: wlan.host_cmd(f"rm -f {self.atomic_file}") except CoreCommandError: - logging.exception("error removing atomic file: %s", self.atomic_file) + logger.exception("error removing atomic file: %s", self.atomic_file) def ebchange(self, wlan: "CoreNetwork") -> None: """ @@ -309,7 +311,7 @@ class CoreNetwork(CoreNetworkBase): :return: combined stdout and stderr :raises CoreCommandError: when a non-zero exit status occurs """ - logging.debug("network node(%s) cmd", self.name) + logger.debug("network node(%s) cmd", self.name) output = utils.cmd(args, env, cwd, wait, shell) self.session.distributed.execute(lambda x: x.remote_cmd(args, env, cwd, wait)) return output @@ -344,7 +346,7 @@ class CoreNetwork(CoreNetworkBase): ] ebtablescmds(self.host_cmd, cmds) except CoreCommandError: - logging.exception("error during shutdown") + logger.exception("error during shutdown") # removes veth pairs used for bridge-to-bridge connections for iface in self.get_ifaces(): iface.shutdown() @@ -763,7 +765,7 @@ class CtrlNet(CoreNetwork): raise CoreError(f"old bridges exist for node: {self.id}") super().startup() - logging.info("added control network bridge: %s %s", self.brname, self.prefix) + logger.info("added control network bridge: %s %s", self.brname, self.prefix) if self.hostid and self.assign_address: self.add_addresses(self.hostid) @@ -771,7 +773,7 @@ class CtrlNet(CoreNetwork): self.add_addresses(-2) if self.updown_script: - logging.info( + logger.info( "interface %s updown script (%s startup) called", self.brname, self.updown_script, @@ -791,7 +793,7 @@ class CtrlNet(CoreNetwork): try: self.net_client.delete_iface(self.brname, self.serverintf) except CoreCommandError: - logging.exception( + logger.exception( "error deleting server interface %s from bridge %s", self.serverintf, self.brname, @@ -799,14 +801,14 @@ class CtrlNet(CoreNetwork): if self.updown_script is not None: try: - logging.info( + logger.info( "interface %s updown script (%s shutdown) called", self.brname, self.updown_script, ) self.host_cmd(f"{self.updown_script} {self.brname} shutdown") except CoreCommandError: - logging.exception("error issuing shutdown script shutdown") + logger.exception("error issuing shutdown script shutdown") super().shutdown() @@ -1006,7 +1008,7 @@ class WlanNode(CoreNetwork): :param config: configuration for model being set :return: nothing """ - logging.debug("node(%s) setting model: %s", self.name, model.name) + logger.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 iface in self.get_ifaces(): @@ -1025,7 +1027,7 @@ class WlanNode(CoreNetwork): def updatemodel(self, config: Dict[str, str]) -> None: if not self.model: raise CoreError(f"no model set to update for node({self.name})") - logging.debug( + logger.debug( "node(%s) updating model(%s): %s", self.id, self.model.name, config ) self.model.update_config(config) diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 03847752..18fa9d06 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -16,6 +16,8 @@ from core.nodes.base import CoreNetworkBase, CoreNodeBase from core.nodes.interface import DEFAULT_MTU, CoreInterface from core.nodes.network import CoreNetwork, GreTap +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.session import Session @@ -163,7 +165,7 @@ class PhysicalNode(CoreNodeBase): def new_iface( self, net: CoreNetworkBase, iface_data: InterfaceData ) -> CoreInterface: - logging.info("creating interface") + logger.info("creating interface") ips = iface_data.get_ips() iface_id = iface_data.id if iface_id is None: @@ -191,17 +193,17 @@ class PhysicalNode(CoreNodeBase): self.mount(host_path, dir_path) def mount(self, src_path: Path, target_path: Path) -> None: - logging.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path) + logger.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path) self.cmd(f"mkdir -p {target_path}") self.host_cmd(f"{MOUNT} --bind {src_path} {target_path}", cwd=self.directory) self._mounts.append((src_path, target_path)) def umount(self, target_path: Path) -> None: - logging.info("unmounting '%s'", target_path) + logger.info("unmounting '%s'", target_path) try: self.host_cmd(f"{UMOUNT} -l {target_path}", cwd=self.directory) except CoreCommandError: - logging.exception("unmounting failed for %s", target_path) + logger.exception("unmounting failed for %s", target_path) def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: host_path = self.host_path(file_path) @@ -211,7 +213,7 @@ class PhysicalNode(CoreNodeBase): with host_path.open("w") as f: f.write(contents) host_path.chmod(mode) - logging.info("created nodefile: '%s'; mode: 0%o", host_path, mode) + logger.info("created nodefile: '%s'; mode: 0%o", host_path, mode) def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: return self.host_cmd(args, wait=wait) @@ -415,7 +417,7 @@ class Rj45Node(CoreNodeBase): if items[1][:4] == "fe80": continue self.old_addrs.append((items[1], None)) - logging.info("saved rj45 state: addrs(%s) up(%s)", self.old_addrs, self.old_up) + logger.info("saved rj45 state: addrs(%s) up(%s)", self.old_addrs, self.old_up) def restorestate(self) -> None: """ @@ -425,7 +427,7 @@ class Rj45Node(CoreNodeBase): :raises CoreCommandError: when there is a command exception """ localname = self.iface.localname - logging.info("restoring rj45 state: %s", localname) + logger.info("restoring rj45 state: %s", localname) for addr in self.old_addrs: self.net_client.create_address(localname, addr[0], addr[1]) if self.old_up: diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 45e09255..575f9257 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -15,6 +15,8 @@ from core.errors import CoreError from core.nodes.base import CoreNetworkBase, NodeBase from core.nodes.network import WlanNode +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.session import Session @@ -109,7 +111,7 @@ class Sdt: return False self.seturl() - logging.info("connecting to SDT at %s://%s", self.protocol, self.address) + logger.info("connecting to SDT at %s://%s", self.protocol, self.address) if self.sock is None: try: if self.protocol.lower() == "udp": @@ -119,7 +121,7 @@ class Sdt: # Default to tcp self.sock = socket.create_connection(self.address, 5) except IOError: - logging.exception("SDT socket connect error") + logger.exception("SDT socket connect error") return False if not self.initialize(): @@ -157,7 +159,7 @@ class Sdt: try: self.sock.close() except IOError: - logging.error("error closing socket") + logger.error("error closing socket") finally: self.sock = None @@ -191,11 +193,11 @@ class Sdt: try: cmd = f"{cmdstr}\n".encode() - logging.debug("sdt cmd: %s", cmd) + logger.debug("sdt cmd: %s", cmd) self.sock.sendall(cmd) return True except IOError: - logging.exception("SDT connection error") + logger.exception("SDT connection error") self.sock = None self.connected = False return False @@ -250,7 +252,7 @@ class Sdt: :param node: node to add :return: nothing """ - logging.debug("sdt add node: %s - %s", node.id, node.name) + logger.debug("sdt add node: %s - %s", node.id, node.name) if not self.connect(): return pos = self.get_node_position(node) @@ -262,8 +264,8 @@ class Sdt: icon = node.icon if icon: node_type = node.name - icon = icon.replace("$CORE_DATA_DIR", CORE_DATA_DIR) - icon = icon.replace("$CORE_CONF_DIR", CORE_CONF_DIR) + icon = icon.replace("$CORE_DATA_DIR", str(CORE_DATA_DIR)) + icon = icon.replace("$CORE_CONF_DIR", str(CORE_CONF_DIR)) self.cmd(f"sprite {node_type} image {icon}") self.cmd( f'node {node.id} nodeLayer "{NODE_LAYER}" ' @@ -280,7 +282,7 @@ class Sdt: :param alt: node altitude :return: nothing """ - logging.debug("sdt update node: %s - %s", node.id, node.name) + logger.debug("sdt update node: %s - %s", node.id, node.name) if not self.connect(): return @@ -300,7 +302,7 @@ class Sdt: :param node_id: node id to delete :return: nothing """ - logging.debug("sdt delete node: %s", node_id) + logger.debug("sdt delete node: %s", node_id) if not self.connect(): return self.cmd(f"delete node,{node_id}") @@ -315,7 +317,7 @@ class Sdt: if not self.connect(): return node = node_data.node - logging.debug("sdt handle node update: %s - %s", node.id, node.name) + logger.debug("sdt handle node update: %s - %s", node.id, node.name) if node_data.message_type == MessageFlags.DELETE: self.cmd(f"delete node,{node.id}") else: @@ -356,7 +358,7 @@ class Sdt: :param label: label for link :return: nothing """ - logging.debug("sdt add link: %s, %s, %s", node1_id, node2_id, network_id) + logger.debug("sdt add link: %s, %s, %s", node1_id, node2_id, network_id) if not self.connect(): return if self.wireless_net_check(node1_id) or self.wireless_net_check(node2_id): @@ -396,7 +398,7 @@ class Sdt: :param network_id: network link is associated with, None otherwise :return: nothing """ - logging.debug("sdt delete link: %s, %s, %s", node1_id, node2_id, network_id) + logger.debug("sdt delete link: %s, %s, %s", node1_id, node2_id, network_id) if not self.connect(): return if self.wireless_net_check(node1_id) or self.wireless_net_check(node2_id): @@ -416,7 +418,7 @@ class Sdt: :param label: label to update :return: nothing """ - logging.debug("sdt edit link: %s, %s, %s", node1_id, node2_id, network_id) + logger.debug("sdt edit link: %s, %s, %s", node1_id, node2_id, network_id) if not self.connect(): return if self.wireless_net_check(node1_id) or self.wireless_net_check(node2_id): diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 4da858fa..25cfbf12 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -34,6 +34,8 @@ from core.errors import ( ) from core.nodes.base import CoreNode +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.session import Session @@ -161,7 +163,7 @@ class ServiceShim: cls.setvalue(service, key, values[cls.keys.index(key)]) except IndexError: # old config does not need to have new keys - logging.exception("error indexing into key") + logger.exception("error indexing into key") @classmethod def setvalue(cls, service: "CoreService", key: str, value: str) -> None: @@ -228,7 +230,7 @@ class ServiceManager: :raises ValueError: when service cannot be loaded """ name = service.name - logging.debug("loading service: class(%s) name(%s)", service.__name__, name) + logger.debug("loading service: class(%s) name(%s)", service.__name__, name) # avoid duplicate services if name in cls.services: @@ -245,7 +247,7 @@ class ServiceManager: try: service.on_load() except Exception as e: - logging.exception("error during service(%s) on load", service.name) + logger.exception("error during service(%s) on load", service.name) raise ValueError(e) # make service available @@ -281,7 +283,7 @@ class ServiceManager: cls.add(service) except (CoreError, ValueError) as e: service_errors.append(service.name) - logging.debug("not loading service(%s): %s", service.name, e) + logger.debug("not loading service(%s): %s", service.name, e) return service_errors @@ -329,14 +331,14 @@ class CoreServices: :param node_type: node type to get default services for :return: default services """ - logging.debug("getting default services for type: %s", node_type) + logger.debug("getting default services for type: %s", node_type) results = [] defaults = self.default_services.get(node_type, []) for name in defaults: - logging.debug("checking for service with service manager: %s", name) + logger.debug("checking for service with service manager: %s", name) service = ServiceManager.get(name) if not service: - logging.warning("default service %s is unknown", name) + logger.warning("default service %s is unknown", name) else: results.append(service) return results @@ -369,7 +371,7 @@ class CoreServices: :param service_name: name of service to set :return: nothing """ - logging.debug("setting custom service(%s) for node: %s", service_name, node_id) + logger.debug("setting custom service(%s) for node: %s", service_name, node_id) service = self.get_service(node_id, service_name) if not service: service_class = ServiceManager.get(service_name) @@ -391,15 +393,15 @@ class CoreServices: :return: nothing """ if not services: - logging.info( + logger.info( "using default services for node(%s) type(%s)", node.name, node_type ) services = self.default_services.get(node_type, []) - logging.info("setting services for node(%s): %s", node.name, services) + logger.info("setting services for node(%s): %s", node.name, services) for service_name in services: service = self.get_service(node.id, service_name, default_service=True) if not service: - logging.warning( + logger.warning( "unknown service(%s) for node(%s)", service_name, node.name ) continue @@ -457,7 +459,7 @@ class CoreServices: raise CoreServiceBootError(*exceptions) def _boot_service_path(self, node: CoreNode, boot_path: List["CoreServiceType"]): - logging.info( + logger.info( "booting node(%s) services: %s", node.name, " -> ".join([x.name for x in boot_path]), @@ -467,7 +469,7 @@ class CoreServices: try: self.boot_service(node, service) except Exception as e: - logging.exception("exception booting service: %s", service.name) + logger.exception("exception booting service: %s", service.name) raise CoreServiceBootError(e) def boot_service(self, node: CoreNode, service: "CoreServiceType") -> None: @@ -479,7 +481,7 @@ class CoreServices: :param service: service to start :return: nothing """ - logging.info( + logger.info( "starting node(%s) service(%s) validation(%s)", node.name, service.name, @@ -492,7 +494,7 @@ class CoreServices: try: node.privatedir(dir_path) except (CoreCommandError, CoreError) as e: - logging.warning( + logger.warning( "error mounting private dir '%s' for service '%s': %s", directory, service.name, @@ -563,21 +565,21 @@ class CoreServices: :param service: service to validate :return: service validation status """ - logging.debug("validating node(%s) service(%s)", node.name, service.name) + logger.debug("validating node(%s) service(%s)", node.name, service.name) cmds = service.validate if not service.custom: cmds = service.get_validate(node) status = 0 for cmd in cmds: - logging.debug("validating service(%s) using: %s", service.name, cmd) + logger.debug("validating service(%s) using: %s", service.name, cmd) try: node.cmd(cmd) except CoreCommandError as e: - logging.debug( + logger.debug( "node(%s) service(%s) validate failed", node.name, service.name ) - logging.debug("cmd(%s): %s", e.cmd, e.output) + logger.debug("cmd(%s): %s", e.cmd, e.output) status = -1 break @@ -612,7 +614,7 @@ class CoreServices: f"error stopping service {service.name}: {e.stderr}", node.id, ) - logging.exception("error running stop command %s", args) + logger.exception("error running stop command %s", args) status = -1 return status @@ -680,13 +682,13 @@ class CoreServices: # retrieve custom service service = self.get_service(node_id, service_name) if service is None: - logging.warning("received file name for unknown service: %s", service_name) + logger.warning("received file name for unknown service: %s", service_name) return # validate file being set is valid config_files = service.configs if file_name not in config_files: - logging.warning( + logger.warning( "received unknown file(%s) for service(%s)", file_name, service_name ) return @@ -714,7 +716,7 @@ class CoreServices: try: node.cmd(cmd, wait) except CoreCommandError: - logging.exception("error starting command") + logger.exception("error starting command") status = -1 return status @@ -732,7 +734,7 @@ class CoreServices: config_files = service.get_configs(node) for file_name in config_files: file_path = Path(file_name) - logging.debug( + logger.debug( "generating service config custom(%s): %s", service.custom, file_name ) if service.custom: @@ -744,7 +746,7 @@ class CoreServices: if self.copy_service_file(node, file_path, cfg): continue except IOError: - logging.exception("error copying service file: %s", file_name) + logger.exception("error copying service file: %s", file_name) continue else: cfg = service.generate_config(node, file_name) diff --git a/daemon/core/services/security.py b/daemon/core/services/security.py index 788988c9..f53e8533 100644 --- a/daemon/core/services/security.py +++ b/daemon/core/services/security.py @@ -11,6 +11,8 @@ from core.nodes.base import CoreNode from core.nodes.interface import CoreInterface from core.services.coreservices import CoreService +logger = logging.getLogger(__name__) + class VPNClient(CoreService): name: str = "VPNClient" @@ -33,7 +35,7 @@ class VPNClient(CoreService): with open(fname, "r") as f: cfg += f.read() except IOError: - logging.exception( + logger.exception( "error opening VPN client configuration template (%s)", fname ) return cfg @@ -61,7 +63,7 @@ class VPNServer(CoreService): with open(fname, "r") as f: cfg += f.read() except IOError: - logging.exception( + logger.exception( "Error opening VPN server configuration template (%s)", fname ) return cfg @@ -89,7 +91,7 @@ class IPsec(CoreService): with open(fname, "r") as f: cfg += f.read() except IOError: - logging.exception("Error opening IPsec configuration template (%s)", fname) + logger.exception("Error opening IPsec configuration template (%s)", fname) return cfg @@ -112,7 +114,7 @@ class Firewall(CoreService): with open(fname, "r") as f: cfg += f.read() except IOError: - logging.exception( + logger.exception( "Error opening Firewall configuration template (%s)", fname ) return cfg diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 0ea6f66a..201797e1 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -36,6 +36,8 @@ import netaddr from core.errors import CoreCommandError, CoreError +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emulator.session import Session from core.nodes.base import CoreNode @@ -204,7 +206,7 @@ def cmd( :raises CoreCommandError: when there is a non-zero exit status or the file to execute is not found """ - logging.debug("command cwd(%s) wait(%s): %s", cwd, wait, args) + logger.debug("command cwd(%s) wait(%s): %s", cwd, wait, args) if shell is False: args = shlex.split(args) try: @@ -221,7 +223,7 @@ def cmd( else: return "" except OSError as e: - logging.error("cmd error: %s", e.strerror) + logger.error("cmd error: %s", e.strerror) raise CoreCommandError(1, args, "", e.strerror) @@ -323,7 +325,7 @@ def load_config(file_path: Path, d: Dict[str, str]) -> None: key, value = line.split("=", 1) d[key] = value.strip() except ValueError: - logging.exception("error reading file to dict: %s", file_path) + logger.exception("error reading file to dict: %s", file_path) def load_classes(path: Path, clazz: Generic[T]) -> T: @@ -335,13 +337,13 @@ def load_classes(path: Path, clazz: Generic[T]) -> T: :return: list of classes loaded """ # validate path exists - logging.debug("attempting to load modules from path: %s", path) + logger.debug("attempting to load modules from path: %s", path) if not path.is_dir(): - logging.warning("invalid custom module directory specified" ": %s", path) + logger.warning("invalid custom module directory specified" ": %s", path) # check if path is in sys.path parent = str(path.parent) if parent not in sys.path: - logging.debug("adding parent path to allow imports: %s", parent) + logger.debug("adding parent path to allow imports: %s", parent) sys.path.append(parent) # import and add all service modules in the path classes = [] @@ -349,7 +351,7 @@ def load_classes(path: Path, clazz: Generic[T]) -> T: if not _valid_module(p): continue import_statement = f"{path.name}.{p.stem}" - logging.debug("importing custom module: %s", import_statement) + logger.debug("importing custom module: %s", import_statement) try: module = importlib.import_module(import_statement) members = inspect.getmembers(module, lambda x: _is_class(module, x, clazz)) @@ -357,7 +359,7 @@ def load_classes(path: Path, clazz: Generic[T]) -> T: valid_class = member[1] classes.append(valid_class) except Exception: - logging.exception( + logger.exception( "unexpected error during import, skipping: %s", import_statement ) return classes @@ -398,7 +400,7 @@ def threadpool( result = future.result() results.append(result) except Exception as e: - logging.exception("thread pool exception") + logger.exception("thread pool exception") exceptions.append(e) return results, exceptions diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index d1717495..745e14e0 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -17,6 +17,8 @@ from core.nodes.lxd import LxcNode from core.nodes.network import CtrlNet, GreTapBridge, WlanNode from core.services.coreservices import CoreService +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emane.emanemodel import EmaneModel from core.emulator.session import Session @@ -384,7 +386,7 @@ class CoreXmlWriter: node_id, iface_id = utils.parse_iface_config_id(node_id) for model_name in all_configs: config = all_configs[model_name] - logging.debug( + logger.debug( "writing emane config node(%s) model(%s)", node_id, model_name ) model = self.session.emane.models[model_name] @@ -404,7 +406,7 @@ class CoreXmlWriter: for model_name in all_configs: config = all_configs[model_name] - logging.debug( + logger.debug( "writing mobility config node(%s) model(%s)", node_id, model_name ) mobility_configuration = etree.SubElement( @@ -609,7 +611,7 @@ class CoreXmlReader: services = [] for service in node.iterchildren(): services.append(service.get("name")) - logging.info( + logger.info( "reading default services for nodes(%s): %s", node_type, services ) self.session.services.default_services[node_type] = services @@ -624,7 +626,7 @@ class CoreXmlReader: name = data.get("name") value = data.get("value") configs[name] = value - logging.info("reading session metadata: %s", configs) + logger.info("reading session metadata: %s", configs) self.session.metadata = configs def read_session_options(self) -> None: @@ -636,7 +638,7 @@ class CoreXmlReader: name = configuration.get("name") value = configuration.get("value") xml_config[name] = value - logging.info("reading session options: %s", xml_config) + logger.info("reading session options: %s", xml_config) config = self.session.options.get_configs() config.update(xml_config) @@ -650,7 +652,7 @@ class CoreXmlReader: state = get_int(hook, "state") state = EventTypes(state) data = hook.text - logging.info("reading hook: state(%s) name(%s)", state, name) + logger.info("reading hook: state(%s) name(%s)", state, name) self.session.add_hook(state, name, data) def read_servers(self) -> None: @@ -660,7 +662,7 @@ class CoreXmlReader: for server in servers.iterchildren(): name = server.get("name") address = server.get("address") - logging.info("reading server: name(%s) address(%s)", name, address) + logger.info("reading server: name(%s) address(%s)", name, address) self.session.distributed.add_server(name, address) def read_session_origin(self) -> None: @@ -672,19 +674,19 @@ class CoreXmlReader: lon = get_float(session_origin, "lon") alt = get_float(session_origin, "alt") if all([lat, lon, alt]): - logging.info("reading session reference geo: %s, %s, %s", lat, lon, alt) + logger.info("reading session reference geo: %s, %s, %s", lat, lon, alt) self.session.location.setrefgeo(lat, lon, alt) scale = get_float(session_origin, "scale") if scale: - logging.info("reading session reference scale: %s", scale) + logger.info("reading session reference scale: %s", scale) self.session.location.refscale = scale x = get_float(session_origin, "x") y = get_float(session_origin, "y") z = get_float(session_origin, "z") if all([x, y]): - logging.info("reading session reference xyz: %s, %s, %s", x, y, z) + logger.info("reading session reference xyz: %s, %s, %s", x, y, z) self.session.location.refxyz = (x, y, z) def read_service_configs(self) -> None: @@ -695,7 +697,7 @@ class CoreXmlReader: for service_configuration in service_configurations.iterchildren(): node_id = get_int(service_configuration, "node") service_name = service_configuration.get("name") - logging.info( + logger.info( "reading custom service(%s) for node(%s)", service_name, node_id ) self.session.services.set_service(node_id, service_name) @@ -790,7 +792,7 @@ class CoreXmlReader: value = config.get("value") configs[name] = value - logging.info( + logger.info( "reading emane configuration node(%s) model(%s)", node_id, model_name ) node_id = utils.iface_config_id(node_id, iface_id) @@ -811,7 +813,7 @@ class CoreXmlReader: value = config.get("value") configs[name] = value - logging.info( + logger.info( "reading mobility configuration node(%s) model(%s)", node_id, model_name ) self.session.mobility.set_model_config(node_id, model_name, configs) @@ -868,7 +870,7 @@ class CoreXmlReader: if all([lat, lon, alt]): options.set_location(lat, lon, alt) - logging.info("reading node id(%s) model(%s) name(%s)", node_id, model, name) + logger.info("reading node id(%s) model(%s) name(%s)", node_id, model, name) self.session.add_node(_class, node_id, options) def read_network(self, network_element: etree.Element) -> None: @@ -896,7 +898,7 @@ class CoreXmlReader: if all([lat, lon, alt]): options.set_location(lat, lon, alt) - logging.info( + logger.info( "reading node id(%s) node_type(%s) name(%s)", node_id, node_type, name ) self.session.add_node(_class, node_id, options) @@ -926,7 +928,7 @@ class CoreXmlReader: for template_element in templates_element.iterchildren(): name = template_element.get("name") template = template_element.text - logging.info( + logger.info( "loading xml template(%s): %s", type(template), template ) service.set_template(name, template) @@ -978,12 +980,12 @@ class CoreXmlReader: options.buffer = get_int(options_element, "buffer") if options.unidirectional == 1 and node_set in node_sets: - logging.info("updating link node1(%s) node2(%s)", node1_id, node2_id) + logger.info("updating link node1(%s) node2(%s)", node1_id, node2_id) self.session.update_link( node1_id, node2_id, iface1_data.id, iface2_data.id, options ) else: - logging.info("adding link node1(%s) node2(%s)", node1_id, node2_id) + logger.info("adding link node1(%s) node2(%s)", node1_id, node2_id) self.session.add_link( node1_id, node2_id, iface1_data, iface2_data, options ) diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index b2bff91f..6c85bc94 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -15,6 +15,8 @@ from core.nodes.interface import CoreInterface from core.nodes.network import CtrlNet from core.xml import corexml +logger = logging.getLogger(__name__) + if TYPE_CHECKING: from core.emane.emanemanager import EmaneManager, StartData from core.emane.emanemodel import EmaneModel @@ -47,7 +49,7 @@ def _value_to_params(value: str) -> Optional[Tuple[str]]: return None return values except SyntaxError: - logging.exception("error in value string to param list") + logger.exception("error in value string to param list") return None diff --git a/daemon/data/logging.conf b/daemon/data/logging.conf index 7f3d496f..a300b0db 100644 --- a/daemon/data/logging.conf +++ b/daemon/data/logging.conf @@ -13,8 +13,11 @@ "format": "%(asctime)s - %(levelname)s - %(module)s:%(funcName)s - %(message)s" } }, - "root": { - "level": "INFO", - "handlers": ["console"] - } + "loggers": { + "core": { + "level": "INFO", + "handlers": ["console"], + "propagate": false + } + } } diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index 29f29a33..0ff4ca77 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -22,6 +22,8 @@ from core.api.tlv.enumerations import CORE_API_PORT from core.constants import CORE_CONF_DIR, COREDPY_VERSION from core.utils import close_onexec, load_logging_config +logger = logging.getLogger(__name__) + def banner(): """ @@ -29,7 +31,7 @@ def banner(): :return: nothing """ - logging.info("CORE daemon v.%s started %s", constants.COREDPY_VERSION, time.ctime()) + logger.info("CORE daemon v.%s started %s", constants.COREDPY_VERSION, time.ctime()) def start_udp(mainserver, server_address): @@ -62,7 +64,7 @@ def cored(cfg): address = (host, port) server = CoreServer(address, CoreHandler, cfg) except: - logging.exception("error starting main server on: %s:%s", host, port) + logger.exception("error starting main server on: %s:%s", host, port) sys.exit(1) # initialize grpc api @@ -79,7 +81,7 @@ def cored(cfg): # close handlers close_onexec(server.fileno()) - logging.info("CORE TLV API TCP/UDP listening on: %s:%s", host, port) + logger.info("CORE TLV API TCP/UDP listening on: %s:%s", host, port) server.serve_forever() @@ -155,7 +157,7 @@ def main(): try: cored(cfg) except KeyboardInterrupt: - logging.info("keyboard interrupt, stopping core daemon") + logger.info("keyboard interrupt, stopping core daemon") if __name__ == "__main__": From 188914ccb1de1053a5f8f294ff2210aed95d54f8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 21 Apr 2021 21:14:32 -0700 Subject: [PATCH 011/110] updated default logging to be at warning level and removed need to adjust the paramiko logger --- daemon/core/__init__.py | 3 --- daemon/data/logging.conf | 5 +++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/daemon/core/__init__.py b/daemon/core/__init__.py index 40ca3604..c847c8dc 100644 --- a/daemon/core/__init__.py +++ b/daemon/core/__init__.py @@ -2,6 +2,3 @@ import logging.config # setup default null handler logging.getLogger(__name__).addHandler(logging.NullHandler()) - -# disable paramiko logging -logging.getLogger("paramiko").setLevel(logging.WARNING) diff --git a/daemon/data/logging.conf b/daemon/data/logging.conf index a300b0db..f002e53b 100644 --- a/daemon/data/logging.conf +++ b/daemon/data/logging.conf @@ -14,6 +14,11 @@ } }, "loggers": { + "": { + "level": "WARNING", + "handlers": ["console"], + "propagate": false + }, "core": { "level": "INFO", "handlers": ["console"], From 597834a9932db49b34db4023273577fa43b8b3d1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 21 Apr 2021 23:22:54 -0700 Subject: [PATCH 012/110] added default logging configuration for __main__ scripts --- daemon/data/logging.conf | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/daemon/data/logging.conf b/daemon/data/logging.conf index f002e53b..a76dc0da 100644 --- a/daemon/data/logging.conf +++ b/daemon/data/logging.conf @@ -23,6 +23,11 @@ "level": "INFO", "handlers": ["console"], "propagate": false + }, + "__main__": { + "level": "INFO", + "handlers": ["console"], + "propagate": false } } } From 7938379e6d068135955dd5e6177ba6bbdf693cfb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 22 Apr 2021 16:12:33 -0700 Subject: [PATCH 013/110] grpc: added session options to session wrapped object, updated get_session and start_session to utilize this change, updated pygui to leverage as well --- daemon/core/api/grpc/clientw.py | 2 ++ daemon/core/api/grpc/server.py | 7 ++++++ daemon/core/api/grpc/wrappers.py | 3 +++ daemon/core/emulator/sessionconfig.py | 10 +++++++++ daemon/core/gui/dialogs/sessionoptions.py | 26 +++++------------------ daemon/proto/core/api/grpc/core.proto | 2 ++ daemon/tests/test_grpcw.py | 13 ++++++++++++ 7 files changed, 42 insertions(+), 21 deletions(-) diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index 5ba768be..7ab632c0 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -314,6 +314,7 @@ class CoreGrpcClient: config=service_config.config, ) config_service_configs.append(config_service_config) + options = {k: v.value for k, v in session.options.items()} request = core_pb2.StartSessionRequest( session_id=session.id, nodes=nodes, @@ -328,6 +329,7 @@ class CoreGrpcClient: service_file_configs=service_file_configs, asymmetric_links=asymmetric_links, config_service_configs=config_service_configs, + options=options, ) response = self.stub.StartSession(request) return response.result, list(response.exceptions) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 94202890..592cb871 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -226,6 +226,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.directory.mkdir(exist_ok=True) session.set_state(EventTypes.CONFIGURATION_STATE) + # session options + session.options.config_reset() + for key, value in request.options.items(): + session.options.set_config(key, value) + # location if request.HasField("location"): grpcutils.session_location(session, request.location) @@ -571,6 +576,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): service_configs = grpcutils.get_node_service_configs(session) config_service_configs = grpcutils.get_node_config_service_configs(session) session_file = str(session.file_path) if session.file_path else None + options = get_config_options(session.options.get_configs(), session.options) session_proto = core_pb2.Session( id=session.id, state=session.state.value, @@ -590,6 +596,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): mobility_configs=mobility_configs, metadata=session.metadata, file=session_file, + options=options, ) return core_pb2.GetSessionResponse(session=session_proto) diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index d68a075b..b32e7b8f 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -753,6 +753,7 @@ class Session: emane_config: Dict[str, ConfigOption] metadata: Dict[str, str] file: Path + options: Dict[str, ConfigOption] def set_node(self, node: Node) -> None: self.nodes[node.id] = node @@ -792,6 +793,7 @@ class Session: node = nodes[node_id] node.mobility_config = ConfigOption.from_dict(mapped_config.config) file_path = Path(proto.file) if proto.file else None + options = ConfigOption.from_dict(proto.options) return Session( id=proto.id, state=SessionState(proto.state), @@ -806,6 +808,7 @@ class Session: emane_config=ConfigOption.from_dict(proto.emane_config), metadata=dict(proto.metadata), file=file_path, + options=options, ) diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py index c4099fcd..f76f4638 100644 --- a/daemon/core/emulator/sessionconfig.py +++ b/daemon/core/emulator/sessionconfig.py @@ -112,3 +112,13 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions): if value is not None: value = int(value) return value + + def config_reset(self, node_id: int = None) -> None: + """ + Clear prior configuration files and reset to default values. + + :param node_id: node id to store configuration for + :return: nothing + """ + super().config_reset(node_id) + self.set_configs(self.default_values()) diff --git a/daemon/core/gui/dialogs/sessionoptions.py b/daemon/core/gui/dialogs/sessionoptions.py index 06b91313..28d780dc 100644 --- a/daemon/core/gui/dialogs/sessionoptions.py +++ b/daemon/core/gui/dialogs/sessionoptions.py @@ -1,11 +1,8 @@ import logging import tkinter as tk from tkinter import ttk -from typing import TYPE_CHECKING, Dict, Optional +from typing import TYPE_CHECKING, Optional -import grpc - -from core.api.grpc.wrappers import ConfigOption from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import ConfigFrame @@ -21,24 +18,15 @@ class SessionOptionsDialog(Dialog): super().__init__(app, "Session Options") 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() - def get_config(self) -> Dict[str, ConfigOption]: - try: - session_id = self.app.core.session.id - return self.app.core.client.get_session_options(session_id) - except grpc.RpcError as e: - self.app.show_grpc_exception("Get Session Options Error", e) - self.has_error = True - self.destroy() - 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.enabled) + options = self.app.core.session.options + self.config_frame = ConfigFrame(self.top, self.app, options, self.enabled) self.config_frame.draw_config() self.config_frame.grid(sticky=tk.NSEW, pady=PADY) @@ -54,10 +42,6 @@ class SessionOptionsDialog(Dialog): def save(self) -> None: config = self.config_frame.parse_config() - try: - session_id = self.app.core.session.id - result = self.app.core.client.set_session_options(session_id, config) - logger.info("saved session config: %s", result) - except grpc.RpcError as e: - self.app.show_grpc_exception("Set Session Options Error", e) + for key, value in config.items(): + self.app.core.session.options[key].value = value self.destroy() diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index cdc89202..50aeffab 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -188,6 +188,7 @@ message StartSessionRequest { repeated services.ServiceFileConfig service_file_configs = 11; repeated Link asymmetric_links = 12; repeated configservices.ConfigServiceConfig config_service_configs = 13; + map options = 14; } message StartSessionResponse { @@ -727,6 +728,7 @@ message Session { map mobility_configs = 16; map metadata = 17; string file = 18; + map options = 19; } message SessionSummary { diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index 69915150..671fa8a8 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -149,6 +149,18 @@ class TestGrpcw: 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" + option = ConfigOption( + label=option_key, + name=option_key, + value=option_value, + type=ConfigOptionType.STRING, + group="Default", + ) + session.options[option_key] = option + # when with patch.object(CoreXmlWriter, "write"): with client.context_connect(): @@ -189,6 +201,7 @@ class TestGrpcw: real_node1, service_name, service_file ) assert service_file.data == service_file_data + assert option_value == real_session.options.get_config(option_key) @pytest.mark.parametrize("session_id", [None, 6013]) def test_create_session( From dcf402ae048e811be0bf3d485ba76b54c1dba7e5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 22 Apr 2021 23:02:42 -0700 Subject: [PATCH 014/110] daemon: fixed issue with dhcp service and dealing with addresses with a prefix of 32 --- daemon/core/configservices/utilservices/services.py | 2 ++ daemon/core/services/utility.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index 633da333..af1d3a84 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -149,6 +149,8 @@ class DhcpService(ConfigService): subnets = [] for iface in self.node.get_ifaces(control=False): for ip4 in iface.ip4s: + if ip4.size == 1: + continue # divide the address space in half index = (ip4.size - 2) / 2 rangelow = ip4[index] diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 8ff93c28..36649f3f 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -236,7 +236,7 @@ max-lease-time 7200; ddns-update-style none; """ for iface in node.get_ifaces(control=False): - cfg += "\n".join(map(cls.subnetentry, iface.ips())) + cfg += "\n".join(map(cls.subnetentry, iface.ip4s)) cfg += "\n" return cfg @@ -246,6 +246,8 @@ ddns-update-style none; Generate a subnet declaration block given an IPv4 prefix string for inclusion in the dhcpd3 config file. """ + if ip.size == 1: + return "" address = str(ip.ip) if netaddr.valid_ipv6(address): return "" From 483053805355570aee7fe99864c2a70b3b7ce216 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 23 Apr 2021 14:00:05 -0700 Subject: [PATCH 015/110] daemon: fixed issue in dhcp server subnet line using wrong address --- .../configservices/utilservices/services.py | 2 +- daemon/core/services/utility.py | 26 ++++++++----------- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/daemon/core/configservices/utilservices/services.py b/daemon/core/configservices/utilservices/services.py index af1d3a84..3a4addfe 100644 --- a/daemon/core/configservices/utilservices/services.py +++ b/daemon/core/configservices/utilservices/services.py @@ -155,7 +155,7 @@ class DhcpService(ConfigService): index = (ip4.size - 2) / 2 rangelow = ip4[index] rangehigh = ip4[-2] - subnets.append((ip4.ip, ip4.netmask, rangelow, rangehigh, str(ip4.ip))) + subnets.append((ip4.cidr.ip, ip4.netmask, rangelow, rangehigh, ip4.ip)) return dict(subnets=subnets) diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 36649f3f..54a58b2a 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -248,15 +248,11 @@ ddns-update-style none; """ if ip.size == 1: return "" - address = str(ip.ip) - if netaddr.valid_ipv6(address): - return "" - else: - # divide the address space in half - index = (ip.size - 2) / 2 - rangelow = ip[index] - rangehigh = ip[-2] - return """ + # divide the address space in half + index = (ip.size - 2) / 2 + rangelow = ip[index] + rangehigh = ip[-2] + return """ subnet %s netmask %s { pool { range %s %s; @@ -265,12 +261,12 @@ subnet %s netmask %s { } } """ % ( - ip.ip, - ip.netmask, - rangelow, - rangehigh, - address, - ) + ip.cidr.ip, + ip.netmask, + rangelow, + rangehigh, + ip.ip, + ) class DhcpClientService(UtilService): From 3d356272f1d0997f38c5f112a38c26869d6e02f7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 23 Apr 2021 22:51:35 -0700 Subject: [PATCH 016/110] pygui: adjust service configuration to not use grpc calls, data is saved and provided in start session call --- daemon/core/api/grpc/server.py | 12 ++--- daemon/core/api/grpc/wrappers.py | 20 ++++---- daemon/core/gui/coreclient.py | 12 ----- daemon/core/gui/dialogs/serviceconfig.py | 65 ++++++++++-------------- 4 files changed, 43 insertions(+), 66 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 592cb871..3c4966ac 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -269,6 +269,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): for config in request.service_configs: grpcutils.service_configuration(session, config) + # service file configs + for config in request.service_file_configs: + session.services.set_service_file( + config.node_id, config.service, config.file, config.data + ) + # config service configs for config in request.config_service_configs: node = self.get_node(session, config.node_id, context, CoreNode) @@ -278,12 +284,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): for name, template in config.templates.items(): service.set_template(name, template) - # service file configs - for config in request.service_file_configs: - session.services.set_service_file( - config.node_id, config.service, config.file, config.data - ) - # create links _, exceptions = grpcutils.create_links(session, request.links) if exceptions: diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index b32e7b8f..cda54ef2 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -206,16 +206,16 @@ class ServiceDefault: @dataclass class NodeServiceData: - executables: List[str] - dependencies: List[str] - dirs: List[str] - configs: List[str] - startup: List[str] - validate: List[str] - validation_mode: ServiceValidationMode - validation_timer: int - shutdown: List[str] - meta: str + executables: List[str] = field(default_factory=list) + dependencies: List[str] = field(default_factory=list) + dirs: List[str] = field(default_factory=list) + configs: List[str] = field(default_factory=list) + startup: List[str] = field(default_factory=list) + validate: List[str] = field(default_factory=list) + validation_mode: ServiceValidationMode = ServiceValidationMode.NON_BLOCKING + validation_timer: int = 5 + shutdown: List[str] = field(default_factory=list) + meta: str = None @classmethod def from_proto(cls, proto: services_pb2.NodeServiceData) -> "NodeServiceData": diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 3559f669..c612cb8d 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -580,11 +580,6 @@ class CoreClient: ) return node_service - def set_node_service(self, node_id: int, config: ServiceConfig) -> NodeServiceData: - result = self.client.set_node_service(self.session.id, config) - logger.info("set node service result(%s): %s", result, config) - return self.client.get_node_service(self.session.id, node_id, config.service) - def get_node_service_file( self, node_id: int, service_name: str, file_name: str ) -> str: @@ -600,13 +595,6 @@ class CoreClient: ) return data - def set_node_service_file( - self, node_id: int, service_name: str, file_name: str, data: str - ) -> None: - config = ServiceFileConfig(node_id, service_name, file_name, data) - result = self.client.set_node_service_file(self.session.id, config) - logger.info("set service file config %s: %s", config, result) - def create_nodes_and_links(self) -> None: """ create nodes and links that have not been created yet diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 1d6881df..56514479 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -7,12 +7,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple import grpc from PIL.ImageTk import PhotoImage -from core.api.grpc.wrappers import ( - Node, - NodeServiceData, - ServiceConfig, - ServiceValidationMode, -) +from core.api.grpc.wrappers import Node, NodeServiceData, ServiceValidationMode from core.gui import images from core.gui.dialogs.copyserviceconfig import CopyServiceConfigDialog from core.gui.dialogs.dialog import Dialog @@ -454,37 +449,31 @@ class ServiceConfigDialog(Dialog): self.current_service_color("") self.destroy() return - - try: - if ( - self.is_custom_command() - or self.has_new_files() - or self.is_custom_directory() - ): - startup, validate, shutdown = self.get_commands() - config = ServiceConfig( - node_id=self.node.id, - service=self.service_name, - files=list(self.filename_combobox["values"]), - directories=self.temp_directories, - startup=startup, - validate=validate, - shutdown=shutdown, - ) - service_data = self.core.set_node_service(self.node.id, config) - self.node.service_configs[self.service_name] = service_data - for file in self.modified_files: - file_configs = self.node.service_file_configs.setdefault( - self.service_name, {} - ) - file_configs[file] = self.temp_service_files[file] - # TODO: check if this is really needed - self.app.core.set_node_service_file( - self.node.id, self.service_name, file, self.temp_service_files[file] - ) - self.current_service_color("green") - except grpc.RpcError as e: - self.app.show_grpc_exception("Save Service Config Error", e) + files = set(self.filenames) + if ( + self.is_custom_command() + or self.has_new_files() + or self.is_custom_directory() + ): + startup, validate, shutdown = self.get_commands() + files = set(self.filename_combobox["values"]) + service_data = NodeServiceData( + configs=list(files), + dirs=self.temp_directories, + startup=startup, + validate=validate, + shutdown=shutdown, + ) + logger.info("setting service data: %s", service_data) + self.node.service_configs[self.service_name] = service_data + for file in self.modified_files: + if file not in files: + continue + file_configs = self.node.service_file_configs.setdefault( + self.service_name, {} + ) + file_configs[file] = self.temp_service_files[file] + self.current_service_color("green") self.destroy() def display_service_file_data(self, event: tk.Event) -> None: @@ -592,7 +581,7 @@ class ServiceConfigDialog(Dialog): if directory.is_dir(): if str(directory) not in self.temp_directories: self.dir_list.listbox.insert("end", directory) - self.temp_directories.append(directory) + self.temp_directories.append(str(directory)) def remove_directory(self) -> None: d = self.directory_entry.get() From d4c008e564ef80991d479b8af04bccf67271c02f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 24 Apr 2021 21:37:00 -0700 Subject: [PATCH 017/110] grpc: removed get/set session options, now accomplished through get_session and start_session --- daemon/core/api/grpc/clientw.py | 27 ------------------------- daemon/core/api/grpc/wrappers.py | 8 ++++---- daemon/proto/core/api/grpc/core.proto | 21 ------------------- daemon/tests/test_grpcw.py | 29 --------------------------- 4 files changed, 4 insertions(+), 81 deletions(-) diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index 7ab632c0..0c1145f4 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -407,33 +407,6 @@ class CoreGrpcClient: response = self.stub.GetSession(request) return wrappers.Session.from_proto(response.session) - def get_session_options(self, session_id: int) -> Dict[str, wrappers.ConfigOption]: - """ - Retrieve session options as a dict with id mapping. - - :param session_id: id of session - :return: session configuration options - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.GetSessionOptionsRequest(session_id=session_id) - response = self.stub.GetSessionOptions(request) - return wrappers.ConfigOption.from_dict(response.config) - - def set_session_options(self, session_id: int, config: Dict[str, str]) -> bool: - """ - Set options for a session. - - :param session_id: id of session - :param config: configuration values to set - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.SetSessionOptionsRequest( - session_id=session_id, config=config - ) - response = self.stub.SetSessionOptions(request) - return response.result - def get_session_metadata(self, session_id: int) -> Dict[str, str]: """ Retrieve session metadata as a dict with id mapping. diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index cda54ef2..a3bd8895 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -667,11 +667,11 @@ class Geo: @dataclass class Node: - id: int - name: str - type: NodeType + id: int = None + name: str = None + type: NodeType = NodeType.DEFAULT model: str = None - position: Position = None + position: Position = Position(x=0, y=0) services: Set[str] = field(default_factory=set) config_services: Set[str] = field(default_factory=set) emane: str = None diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 50aeffab..bea791df 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -25,10 +25,6 @@ service CoreApi { } rpc CheckSession (CheckSessionRequest) returns (CheckSessionResponse) { } - rpc GetSessionOptions (GetSessionOptionsRequest) returns (GetSessionOptionsResponse) { - } - rpc SetSessionOptions (SetSessionOptionsRequest) returns (SetSessionOptionsResponse) { - } rpc SetSessionMetadata (SetSessionMetadataRequest) returns (SetSessionMetadataResponse) { } rpc GetSessionMetadata (GetSessionMetadataRequest) returns (GetSessionMetadataResponse) { @@ -244,23 +240,6 @@ message GetSessionResponse { Session session = 1; } -message GetSessionOptionsRequest { - int32 session_id = 1; -} - -message GetSessionOptionsResponse { - map config = 2; -} - -message SetSessionOptionsRequest { - int32 session_id = 1; - map config = 2; -} - -message SetSessionOptionsResponse { - bool result = 1; -} - message SetSessionMetadataRequest { int32 session_id = 1; map config = 2; diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index 671fa8a8..d911cbec 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -274,18 +274,6 @@ class TestGrpcw: assert len(sessions) == 1 assert found_session is not None - def test_get_session_options(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - config = client.get_session_options(session.id) - - # then - assert len(config) > 0 - def test_get_session_location(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() @@ -331,23 +319,6 @@ class TestGrpcw: assert session.location.refscale == scale assert session.location.refgeo == lat_lon_alt - def test_set_session_options(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - option = "enablerj45" - value = "1" - with client.context_connect(): - result = client.set_session_options(session.id, {option: value}) - - # then - assert result is True - assert session.options.get_config(option) == value - config = session.options.get_configs() - assert len(config) > 0 - def test_set_session_metadata(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From d8a3f9e78cce4c41d2ae3fb82d4e7814a63fa860 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 24 Apr 2021 22:10:28 -0700 Subject: [PATCH 018/110] grpc: removed set/get sessopm options, removed get session metadata/location, can be done with get/start session --- daemon/core/api/grpc/client.py | 55 --------------------- daemon/core/api/grpc/clientw.py | 24 --------- daemon/core/api/grpc/server.py | 70 --------------------------- daemon/proto/core/api/grpc/core.proto | 20 -------- daemon/tests/test_grpc.py | 65 +------------------------ daemon/tests/test_grpcw.py | 33 ------------- 6 files changed, 2 insertions(+), 265 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 771d6926..0f1cf107 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -298,48 +298,6 @@ class CoreGrpcClient: request = core_pb2.GetSessionRequest(session_id=session_id) return self.stub.GetSession(request) - def get_session_options( - self, session_id: int - ) -> core_pb2.GetSessionOptionsResponse: - """ - Retrieve session options as a dict with id mapping. - - :param session_id: id of session - :return: response with a list of configuration groups - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.GetSessionOptionsRequest(session_id=session_id) - return self.stub.GetSessionOptions(request) - - def set_session_options( - self, session_id: int, config: Dict[str, str] - ) -> core_pb2.SetSessionOptionsResponse: - """ - Set options for a session. - - :param session_id: id of session - :param config: configuration values to set - :return: response with result of success or failure - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.SetSessionOptionsRequest( - session_id=session_id, config=config - ) - return self.stub.SetSessionOptions(request) - - def get_session_metadata( - self, session_id: int - ) -> core_pb2.GetSessionMetadataResponse: - """ - Retrieve session metadata as a dict with id mapping. - - :param session_id: id of session - :return: response with metadata dict - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.GetSessionMetadataRequest(session_id=session_id) - return self.stub.GetSessionMetadata(request) - def set_session_metadata( self, session_id: int, config: Dict[str, str] ) -> core_pb2.SetSessionMetadataResponse: @@ -356,19 +314,6 @@ class CoreGrpcClient: ) return self.stub.SetSessionMetadata(request) - def get_session_location( - self, session_id: int - ) -> core_pb2.GetSessionLocationResponse: - """ - Get session location. - - :param session_id: id of session - :return: response with session position reference and scale - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.GetSessionLocationRequest(session_id=session_id) - return self.stub.GetSessionLocation(request) - def set_session_location( self, session_id: int, diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index 0c1145f4..20dccc88 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -407,18 +407,6 @@ class CoreGrpcClient: response = self.stub.GetSession(request) return wrappers.Session.from_proto(response.session) - def get_session_metadata(self, session_id: int) -> Dict[str, str]: - """ - Retrieve session metadata as a dict with id mapping. - - :param session_id: id of session - :return: response with metadata dict - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.GetSessionMetadataRequest(session_id=session_id) - response = self.stub.GetSessionMetadata(request) - return dict(response.config) - def set_session_metadata(self, session_id: int, config: Dict[str, str]) -> bool: """ Set metadata for a session. @@ -434,18 +422,6 @@ class CoreGrpcClient: response = self.stub.SetSessionMetadata(request) return response.result - def get_session_location(self, session_id: int) -> wrappers.SessionLocation: - """ - Get session location. - - :param session_id: id of session - :return: response with session position reference and scale - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.GetSessionLocationRequest(session_id=session_id) - response = self.stub.GetSessionLocation(request) - return wrappers.SessionLocation.from_proto(response.location) - def set_session_location( self, session_id: int, location: wrappers.SessionLocation ) -> bool: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 3c4966ac..084d40e1 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -383,26 +383,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): sessions.append(session_summary) return core_pb2.GetSessionsResponse(sessions=sessions) - def GetSessionLocation( - self, request: core_pb2.GetSessionLocationRequest, context: ServicerContext - ) -> core_pb2.GetSessionLocationResponse: - """ - Retrieve a requested session location - - :param request: get-session-location request - :param context: context object - :return: a get-session-location response - """ - logger.debug("get session location: %s", request) - session = self.get_session(request.session_id, context) - x, y, z = session.location.refxyz - lat, lon, alt = session.location.refgeo - scale = session.location.refscale - location = core_pb2.SessionLocation( - x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=scale - ) - return core_pb2.GetSessionLocationResponse(location=location) - def SetSessionLocation( self, request: core_pb2.SetSessionLocationRequest, context: ServicerContext ) -> core_pb2.SetSessionLocationResponse: @@ -462,56 +442,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.user = request.user return core_pb2.SetSessionUserResponse(result=True) - def GetSessionOptions( - self, request: core_pb2.GetSessionOptionsRequest, context: ServicerContext - ) -> core_pb2.GetSessionOptionsResponse: - """ - Retrieve session options. - - :param request: - get-session-options request - :param context: context object - :return: get-session-options response about all session's options - """ - logger.debug("get session options: %s", request) - session = self.get_session(request.session_id, context) - current_config = session.options.get_configs() - default_config = session.options.default_values() - default_config.update(current_config) - config = get_config_options(default_config, session.options) - return core_pb2.GetSessionOptionsResponse(config=config) - - def SetSessionOptions( - self, request: core_pb2.SetSessionOptionsRequest, context: ServicerContext - ) -> core_pb2.SetSessionOptionsResponse: - """ - Update a session's configuration - - :param request: set-session-options request - :param context: context object - :return: set-session-options response - """ - logger.debug("set session options: %s", request) - session = self.get_session(request.session_id, context) - config = session.options.get_configs() - config.update(request.config) - return core_pb2.SetSessionOptionsResponse(result=True) - - def GetSessionMetadata( - self, request: core_pb2.GetSessionMetadataRequest, context: ServicerContext - ) -> core_pb2.GetSessionMetadataResponse: - """ - Retrieve session metadata. - - :param request: get session metadata - request - :param context: context object - :return: get session metadata response - """ - logger.debug("get session metadata: %s", request) - session = self.get_session(request.session_id, context) - return core_pb2.GetSessionMetadataResponse(config=session.metadata) - def SetSessionMetadata( self, request: core_pb2.SetSessionMetadataRequest, context: ServicerContext ) -> core_pb2.SetSessionMetadataResponse: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index bea791df..24d5e018 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -27,10 +27,6 @@ service CoreApi { } rpc SetSessionMetadata (SetSessionMetadataRequest) returns (SetSessionMetadataResponse) { } - rpc GetSessionMetadata (GetSessionMetadataRequest) returns (GetSessionMetadataResponse) { - } - rpc GetSessionLocation (GetSessionLocationRequest) returns (GetSessionLocationResponse) { - } rpc SetSessionLocation (SetSessionLocationRequest) returns (SetSessionLocationResponse) { } rpc SetSessionState (SetSessionStateRequest) returns (SetSessionStateResponse) { @@ -249,22 +245,6 @@ message SetSessionMetadataResponse { bool result = 1; } -message GetSessionMetadataRequest { - int32 session_id = 1; -} - -message GetSessionMetadataResponse { - map config = 1; -} - -message GetSessionLocationRequest { - int32 session_id = 1; -} - -message GetSessionLocationResponse { - SessionLocation location = 1; -} - message SetSessionLocationRequest { int32 session_id = 1; SessionLocation location = 2; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index a4efd6d9..e1f0279c 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -1,4 +1,5 @@ import time +from pathlib import Path from queue import Queue from tempfile import TemporaryFile from typing import Optional @@ -235,36 +236,6 @@ class TestGrpc: assert len(response.sessions) == 1 assert found_session is not None - def test_get_session_options(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - response = client.get_session_options(session.id) - - # then - assert len(response.config) > 0 - - def test_get_session_location(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - response = client.get_session_location(session.id) - - # then - assert response.location.scale == 1.0 - assert response.location.x == 0 - assert response.location.y == 0 - assert response.location.z == 0 - assert response.location.lat == 0 - assert response.location.lon == 0 - assert response.location.alt == 0 - def test_set_session_location(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() @@ -292,23 +263,6 @@ class TestGrpc: assert session.location.refscale == scale assert session.location.refgeo == lat_lon_alt - def test_set_session_options(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - option = "enablerj45" - value = "1" - with client.context_connect(): - response = client.set_session_options(session.id, {option: value}) - - # then - assert response.result is True - assert session.options.get_config(option) == value - config = session.options.get_configs() - assert len(config) > 0 - def test_set_session_metadata(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() @@ -324,21 +278,6 @@ class TestGrpc: assert response.result is True assert session.metadata[key] == value - def test_get_session_metadata(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - key = "meta1" - value = "value1" - session.metadata[key] = value - - # then - with client.context_connect(): - response = client.get_session_metadata(session.id) - - # then - assert response.config[key] == value - def test_set_session_state(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() @@ -507,7 +446,7 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() tmp = tmpdir.join("text.xml") - session.save_xml(str(tmp)) + session.save_xml(Path(str(tmp))) # then with client.context_connect(): diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index d911cbec..74e285c1 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -274,24 +274,6 @@ class TestGrpcw: assert len(sessions) == 1 assert found_session is not None - def test_get_session_location(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - location = client.get_session_location(session.id) - - # then - assert location.scale == 1.0 - assert location.x == 0 - assert location.y == 0 - assert location.z == 0 - assert location.lat == 0 - assert location.lon == 0 - assert location.alt == 0 - def test_set_session_location(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() @@ -334,21 +316,6 @@ class TestGrpcw: assert result is True assert session.metadata[key] == value - def test_get_session_metadata(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - key = "meta1" - value = "value1" - session.metadata[key] = value - - # then - with client.context_connect(): - config = client.get_session_metadata(session.id) - - # then - assert config[key] == value - def test_set_session_state(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From f891974e3a34bb9f27edb87b62b2bc279156c45b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sat, 24 Apr 2021 22:35:45 -0700 Subject: [PATCH 019/110] grpc: removed set session user, added to start session --- daemon/core/api/grpc/client.py | 14 -------------- daemon/core/api/grpc/clientw.py | 14 +------------- daemon/core/api/grpc/server.py | 16 +--------------- daemon/core/gui/coreclient.py | 2 +- daemon/proto/core/api/grpc/core.proto | 12 +----------- 5 files changed, 4 insertions(+), 54 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 0f1cf107..b91505c1 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -361,20 +361,6 @@ class CoreGrpcClient: request = core_pb2.SetSessionStateRequest(session_id=session_id, state=state) return self.stub.SetSessionState(request) - def set_session_user( - self, session_id: int, user: str - ) -> core_pb2.SetSessionUserResponse: - """ - Set session user, used for helping to find files without full paths. - - :param session_id: id of session - :param user: user to set for session - :return: response with result of success or failure - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.SetSessionUserRequest(session_id=session_id, user=user) - return self.stub.SetSessionUser(request) - def add_session_server( self, session_id: int, name: str, host: str ) -> core_pb2.AddSessionServerResponse: diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index 20dccc88..297a9f18 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -330,6 +330,7 @@ class CoreGrpcClient: asymmetric_links=asymmetric_links, config_service_configs=config_service_configs, options=options, + user=session.user, ) response = self.stub.StartSession(request) return response.result, list(response.exceptions) @@ -454,19 +455,6 @@ class CoreGrpcClient: response = self.stub.SetSessionState(request) return response.result - def set_session_user(self, session_id: int, user: str) -> bool: - """ - Set session user, used for helping to find files without full paths. - - :param session_id: id of session - :param user: user to set for session - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.SetSessionUserRequest(session_id=session_id, user=user) - response = self.stub.SetSessionUser(request) - return response.result - def add_session_server(self, session_id: int, name: str, host: str) -> bool: """ Add distributed session server. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 084d40e1..a66e7a24 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -225,6 +225,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.clear() session.directory.mkdir(exist_ok=True) session.set_state(EventTypes.CONFIGURATION_STATE) + session.user = request.user # session options session.options.config_reset() @@ -427,21 +428,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): result = False return core_pb2.SetSessionStateResponse(result=result) - def SetSessionUser( - self, request: core_pb2.SetSessionUserRequest, context: ServicerContext - ) -> core_pb2.SetSessionUserResponse: - """ - Sets the user for a session. - - :param request: set session user request - :param context: context object - :return: set session user response - """ - logger.debug("set session user: %s", request) - session = self.get_session(request.session_id, context) - session.user = request.user - return core_pb2.SetSessionUserResponse(result=True) - def SetSessionMetadata( self, request: core_pb2.SetSessionMetadataRequest, context: ServicerContext ) -> core_pb2.SetSessionMetadataResponse: diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index c612cb8d..3a555c81 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -295,7 +295,7 @@ class CoreClient: self.reset() try: self.session = self.client.get_session(session_id) - self.client.set_session_user(self.session.id, self.user) + self.session.user = self.user title_file = self.session.file.name if self.session.file else "" self.master.title(f"CORE Session({self.session.id}) {title_file}") self.handling_events = self.client.events( diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 24d5e018..a3e02e00 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -31,8 +31,6 @@ service CoreApi { } rpc SetSessionState (SetSessionStateRequest) returns (SetSessionStateResponse) { } - rpc SetSessionUser (SetSessionUserRequest) returns (SetSessionUserResponse) { - } rpc AddSessionServer (AddSessionServerRequest) returns (AddSessionServerResponse) { } rpc SessionAlert (SessionAlertRequest) returns (SessionAlertResponse) { @@ -181,6 +179,7 @@ message StartSessionRequest { repeated Link asymmetric_links = 12; repeated configservices.ConfigServiceConfig config_service_configs = 13; map options = 14; + string user = 15; } message StartSessionResponse { @@ -263,15 +262,6 @@ message SetSessionStateResponse { bool result = 1; } -message SetSessionUserRequest { - int32 session_id = 1; - string user = 2; -} - -message SetSessionUserResponse { - bool result = 1; -} - message AddSessionServerRequest { int32 session_id = 1; string name = 2; From e0fe86bcb254151b6299e5e84be3c8b6c6fcbcce Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 27 Apr 2021 10:49:52 -0700 Subject: [PATCH 020/110] grpc: updated start_session to have a definition option, to allow saving xml files and getting service configurations, without fully starting a session --- daemon/core/api/grpc/clientw.py | 6 +- daemon/core/api/grpc/server.py | 30 ++++--- daemon/core/gui/coreclient.py | 82 +++++++------------ .../core/gui/dialogs/configserviceconfig.py | 2 +- daemon/core/gui/dialogs/serviceconfig.py | 2 +- daemon/proto/core/api/grpc/core.proto | 1 + daemon/tests/test_grpcw.py | 10 ++- 7 files changed, 64 insertions(+), 69 deletions(-) diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index 297a9f18..d61e0e7c 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -248,7 +248,10 @@ class CoreGrpcClient: self.proxy: bool = proxy def start_session( - self, session: wrappers.Session, asymmetric_links: List[wrappers.Link] = None + self, + session: wrappers.Session, + asymmetric_links: List[wrappers.Link] = None, + definition: bool = False, ) -> Tuple[bool, List[str]]: """ Start a session. @@ -331,6 +334,7 @@ class CoreGrpcClient: config_service_configs=config_service_configs, options=options, user=session.user, + definition=definition, ) response = self.stub.StartSession(request) return response.result, list(response.exceptions) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index a66e7a24..c044f329 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -223,8 +223,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): # clear previous state and setup for creation session.clear() - session.directory.mkdir(exist_ok=True) - session.set_state(EventTypes.CONFIGURATION_STATE) + if request.definition: + state = EventTypes.DEFINITION_STATE + else: + state = EventTypes.CONFIGURATION_STATE + session.directory.mkdir(exist_ok=True) + session.set_state(state) session.user = request.user # session options @@ -298,16 +302,18 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) # set to instantiation and start - session.set_state(EventTypes.INSTANTIATION_STATE) - - # boot services - boot_exceptions = session.instantiate() - if boot_exceptions: - exceptions = [] - for boot_exception in boot_exceptions: - for service_exception in boot_exception.args: - exceptions.append(str(service_exception)) - return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) + if not request.definition: + session.set_state(EventTypes.INSTANTIATION_STATE) + # boot services + boot_exceptions = session.instantiate() + if boot_exceptions: + exceptions = [] + for boot_exception in boot_exceptions: + for service_exception in boot_exception.args: + exceptions.append(str(service_exception)) + return core_pb2.StartSessionResponse( + result=False, exceptions=exceptions + ) return core_pb2.StartSessionResponse(result=True) def StopSession( diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 3a555c81..887f7397 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -437,28 +437,38 @@ class CoreClient: for server in self.servers.values(): self.client.add_session_server(self.session.id, server.name, server.address) - def start_session(self) -> Tuple[bool, List[str]]: - self.ifaces_manager.set_macs([x.link for x in self.links.values()]) - links = [] - asymmetric_links = [] + def get_links(self, definition: bool = False) -> Tuple[List[Link], List[Link]]: + if not definition: + self.ifaces_manager.set_macs([x.link for x in self.links.values()]) + links, asym_links = [], [] for edge in self.links.values(): link = edge.link - if link.iface1 and not link.iface1.mac: - link.iface1.mac = self.ifaces_manager.next_mac() - if link.iface2 and not link.iface2.mac: - link.iface2.mac = self.ifaces_manager.next_mac() + if not definition: + if link.iface1 and not link.iface1.mac: + link.iface1.mac = self.ifaces_manager.next_mac() + if link.iface2 and not link.iface2.mac: + link.iface2.mac = self.ifaces_manager.next_mac() links.append(link) if edge.asymmetric_link: - asymmetric_links.append(edge.asymmetric_link) + asym_links.append(edge.asymmetric_link) + return links, asym_links + + def start_session(self, definition: bool = False) -> Tuple[bool, List[str]]: + links, asym_links = self.get_links(definition) self.session.links = links result = False exceptions = [] try: self.send_servers() result, exceptions = self.client.start_session( - self.session, asymmetric_links + self.session, asym_links, definition + ) + logger.info( + "start session(%s) definition(%s), result: %s", + self.session.id, + definition, + result, ) - logger.info("start session(%s), result: %s", self.session.id, result) if result: self.set_metadata() except grpc.RpcError as e: @@ -548,8 +558,15 @@ class CoreClient: file_path = str(self.session.file) try: if not self.is_runtime(): - logger.debug("Send session data to the daemon") - self.send_data() + logger.debug("sending session data to the daemon") + result, exceptions = self.start_session(definition=True) + if not result: + message = "\n".join(exceptions) + self.app.show_exception_data( + "Session Definition Exception", + "Failed to define session", + message, + ) self.client.save_xml(self.session.id, file_path) logger.info("saved xml file %s", file_path) except grpc.RpcError as e: @@ -595,45 +612,6 @@ class CoreClient: ) return data - def create_nodes_and_links(self) -> None: - """ - create nodes and links that have not been created yet - """ - self.client.set_session_state(self.session.id, SessionState.DEFINITION) - for node in self.session.nodes.values(): - node_id = self.client.add_node(self.session.id, node, source=GUI_SOURCE) - logger.debug("created node: %s", node_id) - asymmetric_links = [] - for edge in self.links.values(): - self.add_link(edge.link) - if edge.asymmetric_link: - asymmetric_links.append(edge.asymmetric_link) - for link in asymmetric_links: - self.add_link(link) - - def send_data(self) -> None: - """ - Send to daemon all session info, but don't start the session - """ - self.send_servers() - self.create_nodes_and_links() - for node_id, config in self.get_wlan_configs(): - self.client.set_wlan_config(self.session.id, node_id, config) - for node_id, config in self.get_mobility_configs(): - self.client.set_mobility_config(self.session.id, node_id, config) - for config in self.get_service_configs(): - self.client.set_node_service(self.session.id, config) - for config in self.get_service_file_configs(): - self.client.set_node_service_file(self.session.id, config) - for hook in self.session.hooks.values(): - self.client.add_hook(self.session.id, hook) - for config in self.get_emane_model_configs(): - self.client.set_emane_model_config(self.session.id, config) - config = to_dict(self.session.emane_config) - self.client.set_emane_config(self.session.id, config) - self.client.set_session_location(self.session.id, self.session.location) - self.set_metadata() - def close(self) -> None: """ Clean ups when done using grpc diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 2a60b7c4..4935ddda 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -75,7 +75,7 @@ class ConfigServiceConfigDialog(Dialog): def load(self) -> None: try: - self.core.create_nodes_and_links() + self.core.start_session(definition=True) service = self.core.config_services[self.service_name] self.dependencies = service.dependencies[:] self.executables = service.executables[:] diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 56514479..16d3a951 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -78,7 +78,7 @@ class ServiceConfigDialog(Dialog): def load(self) -> None: try: - self.app.core.create_nodes_and_links() + self.core.start_session(definition=True) default_config = self.app.core.get_node_service( self.node.id, self.service_name ) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index a3e02e00..428d180c 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -180,6 +180,7 @@ message StartSessionRequest { repeated configservices.ConfigServiceConfig config_service_configs = 13; map options = 14; string user = 15; + bool definition = 16; } message StartSessionResponse { diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index 74e285c1..11775d12 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -47,7 +47,8 @@ from core.xml.corexml import CoreXmlWriter class TestGrpcw: - def test_start_session(self, grpc_server: CoreGrpcServer): + @pytest.mark.parametrize("definition", [False, True]) + def test_start_session(self, grpc_server: CoreGrpcServer, definition): # given client = CoreGrpcClient() with client.context_connect(): @@ -164,10 +165,15 @@ class TestGrpcw: # when with patch.object(CoreXmlWriter, "write"): with client.context_connect(): - client.start_session(session) + 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 From a217c2445c9d15fcda97dd6c15dc87ab13408c33 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 27 Apr 2021 10:59:10 -0700 Subject: [PATCH 021/110] grpc: removed set session location, achieved with start session --- daemon/core/api/grpc/client.py | 33 --------------------------- daemon/core/api/grpc/clientw.py | 17 -------------- daemon/core/api/grpc/server.py | 15 ------------ daemon/proto/core/api/grpc/core.proto | 11 --------- daemon/tests/test_grpc.py | 27 ---------------------- daemon/tests/test_grpcw.py | 27 ---------------------- 6 files changed, 130 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index b91505c1..eced68d7 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -314,39 +314,6 @@ class CoreGrpcClient: ) return self.stub.SetSessionMetadata(request) - def set_session_location( - self, - session_id: int, - x: float = None, - y: float = None, - z: float = None, - lat: float = None, - lon: float = None, - alt: float = None, - scale: float = None, - ) -> core_pb2.SetSessionLocationResponse: - """ - Set session location. - - :param session_id: id of session - :param x: x position - :param y: y position - :param z: z position - :param lat: latitude position - :param lon: longitude position - :param alt: altitude position - :param scale: geo scale - :return: response with result of success or failure - :raises grpc.RpcError: when session doesn't exist - """ - location = core_pb2.SessionLocation( - x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=scale - ) - request = core_pb2.SetSessionLocationRequest( - session_id=session_id, location=location - ) - return self.stub.SetSessionLocation(request) - def set_session_state( self, session_id: int, state: core_pb2.SessionState ) -> core_pb2.SetSessionStateResponse: diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index d61e0e7c..10235ea6 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -427,23 +427,6 @@ class CoreGrpcClient: response = self.stub.SetSessionMetadata(request) return response.result - def set_session_location( - self, session_id: int, location: wrappers.SessionLocation - ) -> bool: - """ - Set session location. - - :param session_id: id of session - :param location: session location - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.SetSessionLocationRequest( - session_id=session_id, location=location.to_proto() - ) - response = self.stub.SetSessionLocation(request) - return response.result - def set_session_state(self, session_id: int, state: wrappers.SessionState) -> bool: """ Set session state. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index c044f329..f198882b 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -390,21 +390,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): sessions.append(session_summary) return core_pb2.GetSessionsResponse(sessions=sessions) - def SetSessionLocation( - self, request: core_pb2.SetSessionLocationRequest, context: ServicerContext - ) -> core_pb2.SetSessionLocationResponse: - """ - Set session location - - :param request: set-session-location request - :param context: context object - :return: a set-session-location-response - """ - logger.debug("set session location: %s", request) - session = self.get_session(request.session_id, context) - grpcutils.session_location(session, request.location) - return core_pb2.SetSessionLocationResponse(result=True) - def SetSessionState( self, request: core_pb2.SetSessionStateRequest, context: ServicerContext ) -> core_pb2.SetSessionStateResponse: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 428d180c..d8964a52 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -27,8 +27,6 @@ service CoreApi { } rpc SetSessionMetadata (SetSessionMetadataRequest) returns (SetSessionMetadataResponse) { } - rpc SetSessionLocation (SetSessionLocationRequest) returns (SetSessionLocationResponse) { - } rpc SetSessionState (SetSessionStateRequest) returns (SetSessionStateResponse) { } rpc AddSessionServer (AddSessionServerRequest) returns (AddSessionServerResponse) { @@ -245,15 +243,6 @@ message SetSessionMetadataResponse { bool result = 1; } -message SetSessionLocationRequest { - int32 session_id = 1; - SessionLocation location = 2; -} - -message SetSessionLocationResponse { - bool result = 1; -} - message SetSessionStateRequest { int32 session_id = 1; SessionState.Enum state = 2; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index e1f0279c..208835df 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -236,33 +236,6 @@ class TestGrpc: assert len(response.sessions) == 1 assert found_session is not None - def test_set_session_location(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - scale = 2 - xyz = (1, 1, 1) - lat_lon_alt = (1, 1, 1) - with client.context_connect(): - response = client.set_session_location( - session.id, - x=xyz[0], - y=xyz[1], - z=xyz[2], - lat=lat_lon_alt[0], - lon=lat_lon_alt[1], - alt=lat_lon_alt[2], - scale=scale, - ) - - # then - assert response.result is True - assert session.location.refxyz == xyz - assert session.location.refscale == scale - assert session.location.refgeo == lat_lon_alt - def test_set_session_metadata(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index 11775d12..b6d4bded 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -280,33 +280,6 @@ class TestGrpcw: assert len(sessions) == 1 assert found_session is not None - def test_set_session_location(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - scale = 2 - xyz = (1, 1, 1) - lat_lon_alt = (1, 1, 1) - location = SessionLocation( - xyz[0], - xyz[1], - xyz[2], - lat_lon_alt[0], - lat_lon_alt[1], - lat_lon_alt[2], - scale, - ) - - # then - with client.context_connect(): - result = client.set_session_location(session.id, location) - - # then - assert result is True - assert session.location.refxyz == xyz - assert session.location.refscale == scale - assert session.location.refgeo == lat_lon_alt - def test_set_session_metadata(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From 634341dd032f53bf53ea0d7a0ef8054cdd0ab4de Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 27 Apr 2021 21:08:58 -0700 Subject: [PATCH 022/110] grpc: removed set session metadata, now accomplished with start session --- daemon/core/api/grpc/client.py | 16 ---------------- daemon/core/api/grpc/clientw.py | 17 ++--------------- daemon/core/api/grpc/server.py | 16 +--------------- daemon/core/gui/coreclient.py | 9 +++------ daemon/core/gui/toolbar.py | 1 - daemon/proto/core/api/grpc/core.proto | 12 +----------- daemon/tests/test_grpc.py | 15 --------------- daemon/tests/test_grpcw.py | 15 --------------- 8 files changed, 7 insertions(+), 94 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index eced68d7..8a5a38a2 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -298,22 +298,6 @@ class CoreGrpcClient: request = core_pb2.GetSessionRequest(session_id=session_id) return self.stub.GetSession(request) - def set_session_metadata( - self, session_id: int, config: Dict[str, str] - ) -> core_pb2.SetSessionMetadataResponse: - """ - Set metadata for a session. - - :param session_id: id of session - :param config: configuration values to set - :return: response with result of success or failure - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.SetSessionMetadataRequest( - session_id=session_id, config=config - ) - return self.stub.SetSessionMetadata(request) - def set_session_state( self, session_id: int, state: core_pb2.SessionState ) -> core_pb2.SetSessionStateResponse: diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index 10235ea6..231a5051 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -258,6 +258,7 @@ class CoreGrpcClient: :param session: session to start :param asymmetric_links: link configuration for asymmetric links + :param definition: True to only define session data, False to start session :return: tuple of result and exception strings """ nodes = [x.to_proto() for x in session.nodes.values()] @@ -335,6 +336,7 @@ class CoreGrpcClient: options=options, user=session.user, definition=definition, + metadata=session.metadata, ) response = self.stub.StartSession(request) return response.result, list(response.exceptions) @@ -412,21 +414,6 @@ class CoreGrpcClient: response = self.stub.GetSession(request) return wrappers.Session.from_proto(response.session) - def set_session_metadata(self, session_id: int, config: Dict[str, str]) -> bool: - """ - Set metadata for a session. - - :param session_id: id of session - :param config: configuration values to set - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.SetSessionMetadataRequest( - session_id=session_id, config=config - ) - response = self.stub.SetSessionMetadata(request) - return response.result - def set_session_state(self, session_id: int, state: wrappers.SessionState) -> bool: """ Set session state. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index f198882b..0333a6ab 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -235,6 +235,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.options.config_reset() for key, value in request.options.items(): session.options.set_config(key, value) + session.metadata = dict(request.metadata) # location if request.HasField("location"): @@ -419,21 +420,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): result = False return core_pb2.SetSessionStateResponse(result=result) - def SetSessionMetadata( - self, request: core_pb2.SetSessionMetadataRequest, context: ServicerContext - ) -> core_pb2.SetSessionMetadataResponse: - """ - Update a session's metadata. - - :param request: set metadata request - :param context: context object - :return: set metadata response - """ - logger.debug("set session metadata: %s", request) - session = self.get_session(request.session_id, context) - session.metadata = dict(request.config) - return core_pb2.SetSessionMetadataResponse(result=True) - def CheckSession( self, request: core_pb2.GetSessionRequest, context: ServicerContext ) -> core_pb2.CheckSessionResponse: diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 887f7397..a9396517 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -456,6 +456,7 @@ class CoreClient: def start_session(self, definition: bool = False) -> Tuple[bool, List[str]]: links, asym_links = self.get_links(definition) self.session.links = links + self.session.metadata = self.get_metadata() result = False exceptions = [] try: @@ -469,8 +470,6 @@ class CoreClient: definition, result, ) - if result: - self.set_metadata() except grpc.RpcError as e: self.app.show_grpc_exception("Start Session Error", e) return result, exceptions @@ -495,7 +494,7 @@ class CoreClient: self.mobility_players[node.id] = mobility_player mobility_player.show() - def set_metadata(self) -> None: + def get_metadata(self) -> Dict[str, str]: # create canvas data canvas_config = self.app.manager.get_metadata() canvas_config = json.dumps(canvas_config) @@ -521,11 +520,9 @@ class CoreClient: hidden = json.dumps(hidden) # save metadata - metadata = dict( + return dict( canvas=canvas_config, shapes=shapes, edges=edges_config, hidden=hidden ) - response = self.client.set_session_metadata(self.session.id, metadata) - logger.debug("set session metadata %s, result: %s", metadata, response) def launch_terminal(self, node_id: int) -> None: try: diff --git a/daemon/core/gui/toolbar.py b/daemon/core/gui/toolbar.py index 994d9026..faf8ab36 100644 --- a/daemon/core/gui/toolbar.py +++ b/daemon/core/gui/toolbar.py @@ -306,7 +306,6 @@ class Toolbar(ttk.Frame): def start_callback(self, result: bool, exceptions: List[str]) -> None: if result: self.set_runtime() - self.app.core.set_metadata() self.app.core.show_mobility_players() else: enable_buttons(self.design_frame, enabled=True) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index d8964a52..c219b39a 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -25,8 +25,6 @@ service CoreApi { } rpc CheckSession (CheckSessionRequest) returns (CheckSessionResponse) { } - rpc SetSessionMetadata (SetSessionMetadataRequest) returns (SetSessionMetadataResponse) { - } rpc SetSessionState (SetSessionStateRequest) returns (SetSessionStateResponse) { } rpc AddSessionServer (AddSessionServerRequest) returns (AddSessionServerResponse) { @@ -179,6 +177,7 @@ message StartSessionRequest { map options = 14; string user = 15; bool definition = 16; + map metadata = 17; } message StartSessionResponse { @@ -234,15 +233,6 @@ message GetSessionResponse { Session session = 1; } -message SetSessionMetadataRequest { - int32 session_id = 1; - map config = 2; -} - -message SetSessionMetadataResponse { - bool result = 1; -} - message SetSessionStateRequest { int32 session_id = 1; SessionState.Enum state = 2; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 208835df..3c931697 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -236,21 +236,6 @@ class TestGrpc: assert len(response.sessions) == 1 assert found_session is not None - def test_set_session_metadata(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - key = "meta1" - value = "value1" - with client.context_connect(): - response = client.set_session_metadata(session.id, {key: value}) - - # then - assert response.result is True - assert session.metadata[key] == value - def test_set_session_state(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index b6d4bded..f8776ae9 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -280,21 +280,6 @@ class TestGrpcw: assert len(sessions) == 1 assert found_session is not None - def test_set_session_metadata(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - key = "meta1" - value = "value1" - with client.context_connect(): - result = client.set_session_metadata(session.id, {key: value}) - - # then - assert result is True - assert session.metadata[key] == value - def test_set_session_state(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From f271b0289ef37fe5c7de25b4bc6174bab814f96f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 27 Apr 2021 21:48:20 -0700 Subject: [PATCH 023/110] grpc: removed add hook and get gooks, can be achieved with get session and start session --- daemon/core/api/grpc/client.py | 32 ------------------------ daemon/core/api/grpc/clientw.py | 30 ----------------------- daemon/core/api/grpc/server.py | 32 ------------------------ daemon/proto/core/api/grpc/core.proto | 23 ------------------ daemon/tests/test_grpc.py | 35 --------------------------- daemon/tests/test_grpcw.py | 32 ------------------------ 6 files changed, 184 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 8a5a38a2..23c7cc0c 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -646,38 +646,6 @@ class CoreGrpcClient: ) return self.stub.DeleteLink(request) - def get_hooks(self, session_id: int) -> core_pb2.GetHooksResponse: - """ - Get all hook scripts. - - :param session_id: session id - :return: response with a list of hooks - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.GetHooksRequest(session_id=session_id) - return self.stub.GetHooks(request) - - def add_hook( - self, - session_id: int, - state: core_pb2.SessionState, - file_name: str, - file_data: str, - ) -> core_pb2.AddHookResponse: - """ - Add hook scripts. - - :param session_id: session id - :param state: state to trigger hook - :param file_name: name of file for hook script - :param file_data: hook script contents - :return: response with result of success or failure - :raises grpc.RpcError: when session doesn't exist - """ - hook = core_pb2.Hook(state=state, file=file_name, data=file_data) - request = core_pb2.AddHookRequest(session_id=session_id, hook=hook) - return self.stub.AddHook(request) - def get_mobility_configs(self, session_id: int) -> GetMobilityConfigsResponse: """ Get all mobility configurations. diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index 231a5051..b2396662 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -66,7 +66,6 @@ from core.api.grpc.wlan_pb2 import ( WlanConfig, WlanLinkRequest, ) -from core.api.grpc.wrappers import Hook from core.emulator.data import IpPrefixes from core.errors import CoreError @@ -764,35 +763,6 @@ class CoreGrpcClient: response = self.stub.DeleteLink(request) return response.result - def get_hooks(self, session_id: int) -> List[wrappers.Hook]: - """ - Get all hook scripts. - - :param session_id: session id - :return: list of hooks - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.GetHooksRequest(session_id=session_id) - response = self.stub.GetHooks(request) - hooks = [] - for hook_proto in response.hooks: - hook = wrappers.Hook.from_proto(hook_proto) - hooks.append(hook) - return hooks - - def add_hook(self, session_id: int, hook: Hook) -> bool: - """ - Add hook scripts. - - :param session_id: session id - :param hook: hook to add - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.AddHookRequest(session_id=session_id, hook=hook.to_proto()) - response = self.stub.AddHook(request) - return response.result - def get_mobility_configs( self, session_id: int ) -> Dict[int, Dict[str, wrappers.ConfigOption]]: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 0333a6ab..9aeefb8a 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -920,38 +920,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.broadcast_link(link_data) return core_pb2.DeleteLinkResponse(result=True) - def GetHooks( - self, request: core_pb2.GetHooksRequest, context: ServicerContext - ) -> core_pb2.GetHooksResponse: - """ - Retrieve all hooks from a session - - :param request: get-hook request - :param context: context object - :return: get-hooks response about all the hooks in all session states - """ - logger.debug("get hooks: %s", request) - session = self.get_session(request.session_id, context) - hooks = grpcutils.get_hooks(session) - return core_pb2.GetHooksResponse(hooks=hooks) - - def AddHook( - self, request: core_pb2.AddHookRequest, context: ServicerContext - ) -> core_pb2.AddHookResponse: - """ - Add hook to a session - - :param request: add-hook request - :param context: context object - :return: add-hook response - """ - logger.debug("add hook: %s", request) - session = self.get_session(request.session_id, context) - hook = request.hook - state = EventTypes(hook.state) - session.add_hook(state, hook.file, hook.data) - return core_pb2.AddHookResponse(result=True) - def GetMobilityConfigs( self, request: GetMobilityConfigsRequest, context: ServicerContext ) -> GetMobilityConfigsResponse: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index c219b39a..640b3a4b 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -66,12 +66,6 @@ service CoreApi { rpc DeleteLink (DeleteLinkRequest) returns (DeleteLinkResponse) { } - // hook rpc - rpc GetHooks (GetHooksRequest) returns (GetHooksResponse) { - } - rpc AddHook (AddHookRequest) returns (AddHookResponse) { - } - // mobility rpc rpc GetMobilityConfigs (mobility.GetMobilityConfigsRequest) returns (mobility.GetMobilityConfigsResponse) { } @@ -492,23 +486,6 @@ message DeleteLinkResponse { bool result = 1; } -message GetHooksRequest { - int32 session_id = 1; -} - -message GetHooksResponse { - repeated Hook hooks = 1; -} - -message AddHookRequest { - int32 session_id = 1; - Hook hook = 2; -} - -message AddHookResponse { - bool result = 1; -} - message SaveXmlRequest { int32 session_id = 1; } diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 3c931697..59012956 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -351,41 +351,6 @@ class TestGrpc: # then assert response.terminal is not None - def test_get_hooks(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - file_name = "test" - file_data = "echo hello" - session.add_hook(EventTypes.RUNTIME_STATE, file_name, file_data) - - # then - with client.context_connect(): - response = client.get_hooks(session.id) - - # then - assert len(response.hooks) == 1 - hook = response.hooks[0] - assert hook.state == core_pb2.SessionState.RUNTIME - assert hook.file == file_name - assert hook.data == file_data - - def test_add_hook(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - file_name = "test" - file_data = "echo hello" - with client.context_connect(): - response = client.add_hook( - session.id, core_pb2.SessionState.RUNTIME, file_name, file_data - ) - - # then - assert response.result is True - def test_save_xml(self, grpc_server: CoreGrpcServer, tmpdir: TemporaryFile): # given client = CoreGrpcClient() diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index f8776ae9..d85e6198 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -408,38 +408,6 @@ class TestGrpcw: # then assert terminal is not None - def test_get_hooks(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - file_name = "test" - file_data = "echo hello" - session.add_hook(EventTypes.RUNTIME_STATE, file_name, file_data) - - # then - with client.context_connect(): - hooks = client.get_hooks(session.id) - - # then - assert len(hooks) == 1 - hook = hooks[0] - assert hook.state == SessionState.RUNTIME - assert hook.file == file_name - assert hook.data == file_data - - def test_add_hook(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - hook = Hook(SessionState.RUNTIME, "test", "echo hello") - - # then - with client.context_connect(): - result = client.add_hook(session.id, hook) - - # then - assert result is True - def test_save_xml(self, grpc_server: CoreGrpcServer, tmpdir: TemporaryFile): # given client = CoreGrpcClient() From 38e162aec5a1efc68ffb240cc3c9af454b54a894 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Sun, 2 May 2021 20:58:24 -0700 Subject: [PATCH 024/110] grpc: removed get_node_links, added links to get_node instead --- daemon/core/api/grpc/client.py | 14 --------- daemon/core/api/grpc/clientw.py | 8 +++-- daemon/core/api/grpc/server.py | 19 ++--------- daemon/core/api/grpc/wrappers.py | 30 +++++++++--------- daemon/proto/core/api/grpc/core.proto | 12 +------ daemon/tests/test_grpc.py | 45 --------------------------- daemon/tests/test_grpcw.py | 36 ++------------------- 7 files changed, 28 insertions(+), 136 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 23c7cc0c..5e1fe6a1 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -531,20 +531,6 @@ class CoreGrpcClient: ) return self.stub.GetNodeTerminal(request) - def get_node_links( - self, session_id: int, node_id: int - ) -> core_pb2.GetNodeLinksResponse: - """ - Get current links for a node. - - :param session_id: session id - :param node_id: node id - :return: response with a list of links - :raises grpc.RpcError: when session or node doesn't exist - """ - request = core_pb2.GetNodeLinksRequest(session_id=session_id, node_id=node_id) - return self.stub.GetNodeLinks(request) - def add_link( self, session_id: int, diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index b2396662..fdcc16d6 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -550,7 +550,7 @@ class CoreGrpcClient: def get_node( self, session_id: int, node_id: int - ) -> Tuple[wrappers.Node, List[wrappers.Interface]]: + ) -> Tuple[wrappers.Node, List[wrappers.Interface], List[wrappers.Link]]: """ Get node details. @@ -566,7 +566,11 @@ class CoreGrpcClient: for iface_proto in response.ifaces: iface = wrappers.Interface.from_proto(iface_proto) ifaces.append(iface) - return node, ifaces + links = [] + for link_proto in response.links: + link = wrappers.Link.from_proto(link_proto) + links.append(link) + return node, ifaces, links def edit_node( self, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 9aeefb8a..f18f2eec 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -647,7 +647,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): 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) + links = get_links(node) + return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links) def MoveNodes( self, @@ -778,22 +779,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): terminal = node.termcmdstring("/bin/bash") return core_pb2.GetNodeTerminalResponse(terminal=terminal) - def GetNodeLinks( - self, request: core_pb2.GetNodeLinksRequest, context: ServicerContext - ) -> core_pb2.GetNodeLinksResponse: - """ - Retrieve all links form a requested node - - :param request: get-node-links request - :param context: context object - :return: get-node-links response - """ - logger.debug("get node links: %s", request) - session = self.get_session(request.session_id, context) - node = self.get_node(session, request.node_id, context, NodeBase) - links = get_links(node) - return core_pb2.GetNodeLinksResponse(links=links) - def AddLink( self, request: core_pb2.AddLinkRequest, context: ServicerContext ) -> core_pb2.AddLinkResponse: diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index a3bd8895..6b7ace26 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -740,20 +740,22 @@ class Node: @dataclass class Session: - id: int - state: SessionState - nodes: Dict[int, Node] - links: List[Link] - dir: str - user: str - default_services: Dict[str, Set[str]] - location: SessionLocation - hooks: Dict[str, Hook] - emane_models: List[str] - emane_config: Dict[str, ConfigOption] - metadata: Dict[str, str] - file: Path - options: Dict[str, ConfigOption] + id: int = None + state: SessionState = SessionState.DEFINITION + nodes: Dict[int, Node] = field(default_factory=dict) + links: List[Link] = field(default_factory=list) + dir: str = None + user: str = None + default_services: Dict[str, Set[str]] = field(default_factory=dict) + location: SessionLocation = SessionLocation( + x=0.0, y=0.0, z=0.0, lat=47.57917, lon=-122.13232, alt=2.0, scale=150.0 + ) + hooks: Dict[str, Hook] = field(default_factory=dict) + emane_models: List[str] = field(default_factory=list) + emane_config: Dict[str, ConfigOption] = field(default_factory=dict) + metadata: Dict[str, str] = field(default_factory=dict) + file: Path = None + options: Dict[str, ConfigOption] = field(default_factory=dict) def set_node(self, node: Node) -> None: self.nodes[node.id] = node diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 640b3a4b..28208e7d 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -57,8 +57,6 @@ service CoreApi { } // link rpc - rpc GetNodeLinks (GetNodeLinksRequest) returns (GetNodeLinksResponse) { - } rpc AddLink (AddLinkRequest) returns (AddLinkResponse) { } rpc EditLink (EditLinkRequest) returns (EditLinkResponse) { @@ -378,6 +376,7 @@ message GetNodeRequest { message GetNodeResponse { Node node = 1; repeated Interface ifaces = 2; + repeated Link links = 3; } message EditNodeRequest { @@ -438,15 +437,6 @@ message NodeCommandResponse { int32 return_code = 2; } -message GetNodeLinksRequest { - int32 session_id = 1; - int32 node_id = 2; -} - -message GetNodeLinksResponse { - repeated Link links = 1; -} - message AddLinkRequest { int32 session_id = 1; Link link = 2; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 59012956..14e971d3 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -265,19 +265,6 @@ class TestGrpc: assert response.node_id is not None assert session.get_node(response.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(): - response = client.get_node(session.id, node.id) - - # then - assert response.node.id == node.id - def test_edit_node(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() @@ -379,38 +366,6 @@ class TestGrpc: assert response.result is True assert response.session_id is not None - def test_get_node_links(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - switch = session.add_node(SwitchNode) - node = session.add_node(CoreNode) - iface_data = ip_prefixes.create_iface(node) - session.add_link(node.id, switch.id, iface_data) - - # then - with client.context_connect(): - response = client.get_node_links(session.id, switch.id) - - # then - assert len(response.links) == 1 - - def test_get_node_links_exception( - self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes - ): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - switch = session.add_node(SwitchNode) - node = session.add_node(CoreNode) - 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, iface_helper: InterfaceHelper): # given client = CoreGrpcClient() diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index d85e6198..f8f0764e 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -316,10 +316,12 @@ class TestGrpcw: # then with client.context_connect(): - get_node, ifaces = client.get_node(session.id, node.id) + 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_edit_node(self, grpc_server: CoreGrpcServer): # given @@ -436,38 +438,6 @@ class TestGrpcw: assert result is True assert session_id is not None - def test_get_node_links(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - switch = session.add_node(SwitchNode) - node = session.add_node(CoreNode) - iface_data = ip_prefixes.create_iface(node) - session.add_link(node.id, switch.id, iface_data) - - # then - with client.context_connect(): - links = client.get_node_links(session.id, switch.id) - - # then - assert len(links) == 1 - - def test_get_node_links_exception( - self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes - ): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - switch = session.add_node(SwitchNode) - node = session.add_node(CoreNode) - 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): # given client = CoreGrpcClient() From 917c45e70bd571ba017e8f09e3ab3c28e2ad72bd Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 3 May 2021 15:25:18 -0700 Subject: [PATCH 025/110] grpc: updates to wrapper classes to help write client code in a more simple way using the consolidated api, updated examples to used the wrapped client --- daemon/core/api/grpc/clientw.py | 4 ++ daemon/core/api/grpc/wrappers.py | 84 ++++++++++++++++++++-- daemon/examples/grpc/distributed_switch.py | 81 ++++++++------------- daemon/examples/grpc/emane80211.py | 65 +++++++---------- daemon/examples/grpc/peertopeer.py | 40 ++++------- daemon/examples/grpc/switch.py | 49 +++++-------- daemon/examples/grpc/wlan.py | 59 ++++++--------- daemon/tests/test_grpcw.py | 49 ++----------- 8 files changed, 196 insertions(+), 235 deletions(-) diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py index fdcc16d6..23a66f1f 100644 --- a/daemon/core/api/grpc/clientw.py +++ b/daemon/core/api/grpc/clientw.py @@ -352,6 +352,10 @@ class CoreGrpcClient: response = self.stub.StopSession(request) return response.result + def add_session(self, session_id: int = None) -> wrappers.Session: + session_id = self.create_session(session_id) + return self.get_session(session_id) + def create_session(self, session_id: int = None) -> int: """ Create a session. diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index 6b7ace26..41f7d7b8 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -395,11 +395,11 @@ class ExceptionEvent: @dataclass class ConfigOption: - label: str name: str value: str - type: ConfigOptionType - group: str + label: str = None + type: ConfigOptionType = None + group: str = None select: List[str] = None @classmethod @@ -737,6 +737,25 @@ class Node: canvas=self.canvas, ) + def set_wlan(self, config: Dict[str, str]) -> None: + for key, value in config.items(): + option = ConfigOption(name=key, value=value) + self.wlan_config[key] = option + + def set_mobility(self, config: Dict[str, str]) -> None: + for key, value in config.items(): + option = ConfigOption(name=key, value=value) + self.mobility_config[key] = option + + def set_emane_model( + self, model: str, config: Dict[str, str], iface_id: int = None + ) -> None: + key = (model, iface_id) + config_options = self.emane_model_configs.setdefault(key, {}) + for key, value in config.items(): + option = ConfigOption(name=key, value=value) + config_options[key] = option + @dataclass class Session: @@ -757,9 +776,6 @@ class Session: file: Path = None options: Dict[str, ConfigOption] = field(default_factory=dict) - def set_node(self, node: Node) -> None: - self.nodes[node.id] = node - @classmethod def from_proto(cls, proto: core_pb2.Session) -> "Session": nodes: Dict[int, Node] = {x.id: Node.from_proto(x) for x in proto.nodes} @@ -813,6 +829,62 @@ class Session: options=options, ) + def add_node( + self, + _id: int, + *, + name: str = None, + _type: NodeType = NodeType.DEFAULT, + model: str = "PC", + position: Position = None, + geo: Geo = None, + emane: str = None, + image: str = None, + server: str = None, + ) -> Node: + node = Node( + id=_id, + name=name, + type=_type, + model=model, + position=position, + geo=geo, + emane=emane, + image=image, + server=server, + ) + self.nodes[node.id] = node + return node + + def add_link( + self, + *, + node1: Node, + node2: Node, + iface1: Interface = None, + iface2: Interface = None, + options: LinkOptions = None, + ) -> Link: + link = Link( + node1_id=node1.id, + node2_id=node2.id, + iface1=iface1, + iface2=iface2, + options=options, + ) + self.links.append(link) + return link + + def set_emane(self, config: Dict[str, str]) -> None: + for key, value in config.items(): + option = ConfigOption(name=key, value=value) + self.emane_config[key] = option + + def set_options(self, config: Dict[str, str]) -> None: + for key, value in config.items(): + option = ConfigOption(name=key, value=value) + self.options[key] = option + @dataclass class LinkEvent: diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index e8ddfb4c..f9534b41 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -1,8 +1,8 @@ import argparse import logging -from core.api.grpc import client -from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.api.grpc import clientw +from core.api.grpc.wrappers import NodeType, Position def log_event(event): @@ -10,62 +10,41 @@ def log_event(event): def main(args): - core = client.CoreGrpcClient() + # helper to create interfaces + interface_helper = clientw.InterfaceHelper(ip4_prefix="10.83.0.0/16") - with core.context_connect(): - # create session - response = core.create_session() - session_id = response.session_id - logging.info("created session: %s", response) + # create grpc client and connect + core = clientw.CoreGrpcClient() + core.connect() - # add distributed server - server_name = "core2" - response = core.add_session_server(session_id, server_name, args.server) - logging.info("added session server: %s", response) + # create session + session = core.add_session() + logging.info("created session: %s", session.id) - # handle events session may broadcast - core.events(session_id, log_event) + # add distributed server + server_name = "core2" + result = core.add_session_server(session.id, server_name, args.server) + logging.info("added session server: %s", result) - # change session state - response = core.set_session_state(session_id, SessionState.CONFIGURATION) - logging.info("set session state: %s", response) + # handle events session may broadcast + core.events(session.id, log_event) - # create switch node - switch = Node(type=NodeType.SWITCH) - response = core.add_node(session_id, switch) - logging.info("created switch: %s", response) - switch_id = response.node_id + # create switch node + position = Position(x=150, y=100) + switch = session.add_node(1, _type=NodeType.SWITCH, position=position) + position = Position(x=100, y=50) + node1 = session.add_node(2, position=position) + position = Position(x=200, y=50) + node2 = session.add_node(3, position=position, server=server_name) - # helper to create interfaces - interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") + # create links + iface1 = interface_helper.create_iface(node1.id, 0) + session.add_link(node1=node1, node2=switch, iface1=iface1) + iface1 = interface_helper.create_iface(node2.id, 0) + session.add_link(node1=node2, node2=switch, iface1=iface1) - # create node one - position = Position(x=100, y=50) - node = Node(position=position) - response = core.add_node(session_id, node) - logging.info("created node one: %s", response) - node1_id = response.node_id - - # create link - 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) - - # create node two - 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) - node2_id = response.node_id - - # create link - 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) - - # change session state - response = core.set_session_state(session_id, SessionState.INSTANTIATION) - logging.info("set session state: %s", response) + # start session + core.start_session(session) if __name__ == "__main__": diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py index c9ced47e..c6fba6c8 100644 --- a/daemon/examples/grpc/emane80211.py +++ b/daemon/examples/grpc/emane80211.py @@ -1,54 +1,37 @@ # required imports -from core.api.grpc import client -from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.api.grpc import clientw +from core.api.grpc.wrappers import NodeType, Position from core.emane.ieee80211abg import EmaneIeee80211abgModel # interface helper -iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") +iface_helper = clientw.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") # create grpc client and connect -core = client.CoreGrpcClient() +core = clientw.CoreGrpcClient() core.connect() -# create session and get id -response = core.create_session() -session_id = response.session_id +# add session +session = core.add_session() -# change session state to configuration so that nodes get started when added -core.set_session_state(session_id, SessionState.CONFIGURATION) - -# create emane node +# create nodes position = Position(x=200, y=200) -emane = Node(type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name) -response = core.add_node(session_id, emane) -emane_id = response.node_id - -# create node one -position = Position(x=100, y=100) -n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr") -response = core.add_node(session_id, n1) -n1_id = response.node_id - -# create node two -position = Position(x=300, y=100) -n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr") -response = core.add_node(session_id, n2) -n2_id = response.node_id - -# configure general emane settings -core.set_emane_config(session_id, {"eventservicettl": "2"}) - -# configure emane model settings -# using a dict mapping currently support values as strings -core.set_emane_model_config( - session_id, emane_id, EmaneIeee80211abgModel.name, {"unicastrate": "3"} +emane = session.add_node( + 1, _type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name ) +position = Position(x=100, y=100) +node1 = session.add_node(2, model="mdr", position=position) +position = Position(x=300, y=100) +node2 = session.add_node(3, model="mdr", position=position) -# links nodes to emane -iface1 = iface_helper.create_iface(n1_id, 0) -core.add_link(session_id, n1_id, emane_id, iface1) -iface1 = iface_helper.create_iface(n2_id, 0) -core.add_link(session_id, n2_id, emane_id, iface1) +# create links +iface1 = iface_helper.create_iface(node1.id, 0) +session.add_link(node1=node1, node2=emane, iface1=iface1) +iface1 = iface_helper.create_iface(node2.id, 0) +session.add_link(node1=node2, node2=emane, iface1=iface1) -# change session state -core.set_session_state(session_id, SessionState.INSTANTIATION) +# setup emane configurations using a dict mapping currently support values as strings +session.set_emane({"eventservicettl": "2"}) +emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"}) + +# start session +core.start_session(session) diff --git a/daemon/examples/grpc/peertopeer.py b/daemon/examples/grpc/peertopeer.py index a5695b4b..8c1b47a1 100644 --- a/daemon/examples/grpc/peertopeer.py +++ b/daemon/examples/grpc/peertopeer.py @@ -1,36 +1,26 @@ -from core.api.grpc import client -from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.api.grpc import clientw +from core.api.grpc.wrappers import Position # interface helper -iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") +iface_helper = clientw.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") # create grpc client and connect -core = client.CoreGrpcClient() +core = clientw.CoreGrpcClient() core.connect() -# create session and get id -response = core.create_session() -session_id = response.session_id +# add session +session = core.add_session() -# change session state to configuration so that nodes get started when added -core.set_session_state(session_id, SessionState.CONFIGURATION) - -# create node one +# create nodes position = Position(x=100, y=100) -n1 = Node(type=NodeType.DEFAULT, position=position, model="PC") -response = core.add_node(session_id, n1) -n1_id = response.node_id - -# create node two +node1 = session.add_node(1, position=position) position = Position(x=300, y=100) -n2 = Node(type=NodeType.DEFAULT, position=position, model="PC") -response = core.add_node(session_id, n2) -n2_id = response.node_id +node2 = session.add_node(2, position=position) -# links nodes together -iface1 = iface_helper.create_iface(n1_id, 0) -iface2 = iface_helper.create_iface(n2_id, 0) -core.add_link(session_id, n1_id, n2_id, iface1, iface2) +# create link +iface1 = iface_helper.create_iface(node1.id, 0) +iface2 = iface_helper.create_iface(node2.id, 0) +session.add_link(node1=node1, node2=node2, iface1=iface1, iface2=iface2) -# change session state -core.set_session_state(session_id, SessionState.INSTANTIATION) +# start session +core.start_session(session) diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index f79f8544..0a5be8a1 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -1,44 +1,29 @@ -# required imports -from core.api.grpc import client -from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.api.grpc import clientw +from core.api.grpc.wrappers import NodeType, Position # interface helper -iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") +iface_helper = clientw.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") # create grpc client and connect -core = client.CoreGrpcClient() +core = clientw.CoreGrpcClient() core.connect() -# create session and get id -response = core.create_session() -session_id = response.session_id +# add session +session = core.add_session() -# change session state to configuration so that nodes get started when added -core.set_session_state(session_id, SessionState.CONFIGURATION) - -# create switch node +# create nodes position = Position(x=200, y=200) -switch = Node(type=NodeType.SWITCH, position=position) -response = core.add_node(session_id, switch) -switch_id = response.node_id - -# create node one +switch = session.add_node(1, _type=NodeType.SWITCH, position=position) position = Position(x=100, y=100) -n1 = Node(type=NodeType.DEFAULT, position=position, model="PC") -response = core.add_node(session_id, n1) -n1_id = response.node_id - -# create node two +node1 = session.add_node(2, position=position) position = Position(x=300, y=100) -n2 = Node(type=NodeType.DEFAULT, position=position, model="PC") -response = core.add_node(session_id, n2) -n2_id = response.node_id +node2 = session.add_node(3, position=position) -# links nodes to switch -iface1 = iface_helper.create_iface(n1_id, 0) -core.add_link(session_id, n1_id, switch_id, iface1) -iface1 = iface_helper.create_iface(n2_id, 0) -core.add_link(session_id, n2_id, switch_id, iface1) +# create links +iface1 = iface_helper.create_iface(node1.id, 0) +session.add_link(node1=node1, node2=switch, iface1=iface1) +iface1 = iface_helper.create_iface(node2.id, 0) +session.add_link(node1=node2, node2=switch, iface1=iface1) -# change session state -core.set_session_state(session_id, SessionState.INSTANTIATION) +# start session +core.start_session(session) diff --git a/daemon/examples/grpc/wlan.py b/daemon/examples/grpc/wlan.py index fa8ef9f6..86a3856b 100644 --- a/daemon/examples/grpc/wlan.py +++ b/daemon/examples/grpc/wlan.py @@ -1,58 +1,41 @@ -# required imports -from core.api.grpc import client -from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.api.grpc import clientw +from core.api.grpc.wrappers import NodeType, Position # interface helper -iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") +iface_helper = clientw.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") # create grpc client and connect -core = client.CoreGrpcClient() +core = clientw.CoreGrpcClient() core.connect() -# create session and get id -response = core.create_session() -session_id = response.session_id +# add session +session = core.add_session() -# change session state to configuration so that nodes get started when added -core.set_session_state(session_id, SessionState.CONFIGURATION) - -# create wlan node +# create nodes position = Position(x=200, y=200) -wlan = Node(type=NodeType.WIRELESS_LAN, position=position) -response = core.add_node(session_id, wlan) -wlan_id = response.node_id - -# create node one +wlan = session.add_node(1, _type=NodeType.WIRELESS_LAN, position=position) position = Position(x=100, y=100) -n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr") -response = core.add_node(session_id, n1) -n1_id = response.node_id - -# create node two +node1 = session.add_node(2, model="mdr", position=position) position = Position(x=300, y=100) -n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr") -response = core.add_node(session_id, n2) -n2_id = response.node_id +node2 = session.add_node(3, model="mdr", position=position) -# configure wlan using a dict mapping currently +# create links +iface1 = iface_helper.create_iface(node1.id, 0) +session.add_link(node1=node1, node2=wlan, iface1=iface1) +iface1 = iface_helper.create_iface(node2.id, 0) +session.add_link(node1=node2, node2=wlan, iface1=iface1) + +# set wlan config using a dict mapping currently # support values as strings -core.set_wlan_config( - session_id, - wlan_id, +wlan.set_wlan( { "range": "280", "bandwidth": "55000000", "delay": "6000", "jitter": "5", "error": "5", - }, + } ) -# links nodes to wlan -iface1 = iface_helper.create_iface(n1_id, 0) -core.add_link(session_id, n1_id, wlan_id, iface1) -iface1 = iface_helper.create_iface(n2_id, 0) -core.add_link(session_id, n2_id, wlan_id, iface1) - -# change session state -core.set_session_state(session_id, SessionState.INSTANTIATION) +# start session +core.start_session(session) diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index f8f0764e..f512dba1 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -55,18 +55,11 @@ class TestGrpcw: session_id = client.create_session() session = client.get_session(session_id) position = Position(x=50, y=100) - node1 = Node( - id=1, name="n1", position=position, type=NodeType.DEFAULT, model="PC" - ) + node1 = session.add_node(1, position=position) position = Position(x=100, y=100) - node2 = Node( - id=2, name="n2", position=position, type=NodeType.DEFAULT, model="PC" - ) + node2 = session.add_node(2, position=position) position = Position(x=200, y=200) - wlan_node = Node(id=3, name="n3", type=NodeType.WIRELESS_LAN, position=position) - session.set_node(node1) - session.set_node(node2) - session.set_node(wlan_node) + 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) @@ -96,38 +89,17 @@ class TestGrpcw: # setup global emane config emane_config_key = "platform_id_start" emane_config_value = "2" - option = ConfigOption( - label=emane_config_key, - name=emane_config_key, - value=emane_config_value, - type=ConfigOptionType.INT64, - group="Default", - ) - session.emane_config[emane_config_key] = option + session.set_emane({emane_config_key: emane_config_value}) # setup wlan config wlan_config_key = "range" wlan_config_value = "333" - option = ConfigOption( - label=wlan_config_key, - name=wlan_config_key, - value=wlan_config_value, - type=ConfigOptionType.INT64, - group="Default", - ) - wlan_node.wlan_config[wlan_config_key] = option + wlan_node.set_wlan({wlan_config_key: wlan_config_value}) # setup mobility config mobility_config_key = "refresh_ms" mobility_config_value = "60" - option = ConfigOption( - label=mobility_config_key, - name=mobility_config_key, - value=mobility_config_value, - type=ConfigOptionType.INT64, - group="Default", - ) - wlan_node.mobility_config[mobility_config_key] = option + wlan_node.set_mobility({mobility_config_key: mobility_config_value}) # setup service config service_name = "DefaultRoute" @@ -153,14 +125,7 @@ class TestGrpcw: # setup session option option_key = "controlnet" option_value = "172.16.0.0/24" - option = ConfigOption( - label=option_key, - name=option_key, - value=option_value, - type=ConfigOptionType.STRING, - group="Default", - ) - session.options[option_key] = option + session.set_options({option_key: option_value}) # when with patch.object(CoreXmlWriter, "write"): From 1cbe891dab3786dd5c46584728e828bcafa36f07 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 3 May 2021 16:55:53 -0700 Subject: [PATCH 026/110] grpc: updated core-cli to use the wrapped client, removed json output for now --- daemon/scripts/core-cli | 219 +++++++++++++++------------------------- 1 file changed, 83 insertions(+), 136 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 92fe8a3c..41083f94 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -9,25 +9,24 @@ from argparse import ( ) from functools import wraps from pathlib import Path -from typing import Any, Optional, Tuple +from typing import Optional, Tuple import grpc 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 ( +from core.api.grpc.clientw import CoreGrpcClient +from core.api.grpc.wrappers import ( Geo, Interface, + Link, LinkOptions, Node, NodeType, Position, - SessionState, ) -NODE_TYPES = [k for k, v in NodeType.Enum.items() if v != NodeType.PEER_TO_PEER] +NODE_TYPES = [x for x in NodeType if x != NodeType.PEER_TO_PEER] def coreclient(func): @@ -105,11 +104,11 @@ def file_type(value: str) -> str: def get_current_session(core: CoreGrpcClient, session_id: Optional[int]) -> int: if session_id: return session_id - response = core.get_sessions() - if not response.sessions: + sessions = core.get_sessions() + if not sessions: print("no current session to interact with") sys.exit(1) - return response.sessions[0].id + return sessions[0].id def create_iface(iface_id: int, mac: str, ip4_net: IPNetwork, ip6_net: IPNetwork) -> Interface: @@ -137,24 +136,16 @@ 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) - - @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}") + config = core.get_wlan_config(session_id, args.node) + size = 0 + for option in config.values(): + size = max(size, len(option.name)) + print(f"{'Name':<{size}.{size}} | Value") + for option in config.values(): + print(f"{option.name:<{size}.{size}} | {option.value}") @coreclient @@ -171,110 +162,82 @@ def set_wlan_config(core: CoreGrpcClient, args: Namespace) -> None: config["jitter"] = str(args.jitter) if args.range: config["range"] = str(args.range) - response = core.set_wlan_config(session_id, args.node, config) - if args.json: - print_json(response) - else: - print(f"set wlan config: {response.result}") + result = core.set_wlan_config(session_id, args.node, config) + print(f"set wlan config: {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}") + result, session_id = core.open_xml(args.file, args.start) + print(f"opened xml: {result},{session_id}") @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}") + sessions = core.get_sessions() + print("Session ID | Session State | Nodes") + for session in sessions: + print(f"{session.id:<10} | {session.state.name:<13} | {session.nodes}") @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 = {} - 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() + session = core.get_session(args.id) + print("Nodes") + print("Node ID | Node Name | Node Type") + for node in session.nodes.values(): + print(f"{node.id:<7} | {node.name:<9} | {node.type.name}") + print("\nLinks") + for link in session.links: + n1 = session.nodes[link.node1_id].name + n2 = session.nodes[link.node2_id].name + print(f"Node | ", end="") + print_iface_header() + print(f"{n1:<6} | ", end="") + if link.iface1: + print_iface(link.iface1) + else: print() + print(f"{n2:<6} | ", end="") + if link.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.net2_id] + session = core.get_session(args.id) + node, ifaces, _ = core.get_node(args.id, args.node) + print("ID | Name | Type") + print(f"{node.id:<4} | {node.name:<7} | {node.type.name}") + print("Interfaces") + print("Connected To | ", end="") + print_iface_header() + for iface in ifaces: + if iface.net_id == node.id: + if iface.node_id: + name = session.nodes[iface.node_id].name else: - name = names.get(iface.net_id, "") - print(f"{name:<12} | ", end="") - print_iface(iface) + name = session.nodes[iface.net2_id].name + else: + net_node = session.nodes.get(iface.net_id) + name = net_node.name if net_node else "" + print(f"{name:<12} | ", end="") + print_iface(iface) @coreclient def delete_session(core: CoreGrpcClient, args: Namespace) -> None: - response = core.delete_session(args.id) - if args.json: - print_json(response) - else: - print(f"delete session({args.id}): {response.result}") + result = core.delete_session(args.id) + print(f"delete session({args.id}): {result}") @coreclient def add_node(core: CoreGrpcClient, args: Namespace) -> None: session_id = get_current_session(core, args.session) - node_type = NodeType.Enum.Value(args.type) + node_type = NodeType[args.type] pos = None if args.pos: x, y = args.pos @@ -294,11 +257,8 @@ def add_node(core: CoreGrpcClient, args: Namespace) -> None: 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_id = core.add_node(session_id, node) + print(f"created node: {node_id}") @coreclient @@ -312,21 +272,15 @@ def edit_node(core: CoreGrpcClient, args: Namespace) -> None: if args.geo: lon, lat, alt = args.geo geo = Geo(lon=lon, lat=lat, alt=alt) - 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}") + result = core.edit_node(session_id, args.id, pos, args.icon, geo) + print(f"edit node: {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}") + result = core.delete_node(session_id, args.id) + print(f"deleted node: {result}") @coreclient @@ -346,11 +300,9 @@ def add_link(core: CoreGrpcClient, args: Namespace) -> None: dup=args.duplicate, unidirectional=args.uni, ) - 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}") + link = Link(args.node1, args.node2, iface1=iface1, iface2=iface2, options=options) + result, _, _ = core.add_link(session_id, link) + print(f"add link: {result}") @coreclient @@ -364,23 +316,21 @@ def edit_link(core: CoreGrpcClient, args: Namespace) -> None: dup=args.duplicate, unidirectional=args.uni, ) - 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}") + iface1 = Interface(args.iface1) + iface2 = Interface(args.iface2) + link = Link(args.node1, args.node2, iface1=iface1, iface2=iface2, options=options) + result = core.edit_link(session_id, link) + print(f"edit link: {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}") + iface1 = Interface(args.iface1) + iface2 = Interface(args.iface2) + link = Link(args.node1, args.node2, iface1=iface1, iface2=iface2) + result = core.delete_link(session_id, link) + print(f"delete link: {result}") def setup_sessions_parser(parent: _SubParsersAction) -> None: @@ -544,9 +494,6 @@ def setup_wlan_parser(parent: _SubParsersAction) -> None: def main() -> None: parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) - parser.add_argument( - "-js", "--json", action="store_true", help="print responses to terminal as json" - ) subparsers = parser.add_subparsers(help="supported commands") subparsers.required = True subparsers.dest = "command" From 0ed30a4febec939accf644c0c785a75f338024d4 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 09:56:58 -0700 Subject: [PATCH 027/110] scripts: updated route monitor to use grpc wrapped client --- daemon/scripts/core-route-monitor | 34 +++++++++++++++---------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/daemon/scripts/core-route-monitor b/daemon/scripts/core-route-monitor index d644ae1b..2a18ea9f 100755 --- a/daemon/scripts/core-route-monitor +++ b/daemon/scripts/core-route-monitor @@ -15,8 +15,8 @@ from typing import Dict, Tuple import grpc from core import utils -from core.api.grpc.client import CoreGrpcClient -from core.api.grpc.core_pb2 import NodeType +from core.api.grpc.clientw import CoreGrpcClient +from core.api.grpc.wrappers import NodeType SDT_HOST = "127.0.0.1" SDT_PORT = 50000 @@ -60,15 +60,15 @@ class SdtClient: class RouterMonitor: def __init__( - self, - session: int, - src: str, - dst: str, - pkt: str, - rate: int, - dead: int, - sdt_host: str, - sdt_port: int, + self, + session: int, + src: str, + dst: str, + pkt: str, + rate: int, + dead: int, + sdt_host: str, + sdt_port: int, ) -> None: self.queue = Queue() self.core = CoreGrpcClient() @@ -92,16 +92,15 @@ class RouterMonitor: self.session = self.get_session() print("session: ", self.session) try: - response = self.core.get_session(self.session) - nodes = response.session.nodes + session = self.core.get_session(self.session) node_map = {} - for node in nodes: + for node in session.nodes.values(): if node.type != NodeType.DEFAULT: continue node_map[node.id] = node.channel if self.src_id is None: - response = self.core.get_node(self.session, node.id) - for iface in response.ifaces: + _, ifaces, _ = self.core.get_node(self.session, node.id) + for iface in ifaces: if self.src == iface.ip4: self.src_id = node.id break @@ -117,8 +116,7 @@ class RouterMonitor: return node_map def get_session(self) -> int: - response = self.core.get_sessions() - sessions = response.sessions + sessions = self.core.get_sessions() session = None if sessions: session = sessions[0] From 924e86da2b4dd27f527e992fd31da3f910af9cbb Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 10:02:15 -0700 Subject: [PATCH 028/110] scripts: updated imn to xml to use grpc wrapped client --- daemon/scripts/core-imn-to-xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/scripts/core-imn-to-xml b/daemon/scripts/core-imn-to-xml index 495093ed..9575fd75 100755 --- a/daemon/scripts/core-imn-to-xml +++ b/daemon/scripts/core-imn-to-xml @@ -5,7 +5,7 @@ import sys from pathlib import Path from core import utils -from core.api.grpc.client import CoreGrpcClient +from core.api.grpc.clientw import CoreGrpcClient from core.errors import CoreCommandError if __name__ == "__main__": @@ -61,7 +61,7 @@ if __name__ == "__main__": client = CoreGrpcClient() with client.context_connect(): print(f"saving xml {xml_file.resolve()}") - client.save_xml(session_id, xml_file) + client.save_xml(session_id, str(xml_file)) print(f"deleting session {session_id}") client.delete_session(session_id) From 15acdaa40f5fdffb7a01f77e8f37180af1df0111 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 13:29:22 -0700 Subject: [PATCH 029/110] grpc: removed old client, refactored wrapped client to be the new standard client, removed old client tests, updated documentation --- daemon/core/api/grpc/client.py | 942 +++++++------ daemon/core/api/grpc/clientw.py | 1385 -------------------- daemon/core/gui/coreclient.py | 6 +- daemon/examples/grpc/distributed_switch.py | 6 +- daemon/examples/grpc/emane80211.py | 6 +- daemon/examples/grpc/peertopeer.py | 6 +- daemon/examples/grpc/switch.py | 6 +- daemon/examples/grpc/wlan.py | 6 +- daemon/scripts/core-cli | 2 +- daemon/scripts/core-imn-to-xml | 2 +- daemon/scripts/core-route-monitor | 2 +- daemon/tests/test_grpc.py | 1054 --------------- daemon/tests/test_grpcw.py | 2 +- docs/grpc.md | 281 ++-- 14 files changed, 698 insertions(+), 3008 deletions(-) delete mode 100644 daemon/core/api/grpc/clientw.py delete mode 100644 daemon/tests/test_grpc.py diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 5e1fe6a1..23a66f1f 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -5,98 +5,131 @@ gRpc client for interfacing with CORE. import logging import threading from contextlib import contextmanager -from typing import Any, Callable, Dict, Generator, Iterable, List, Optional +from pathlib import Path +from queue import Queue +from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple import grpc -from core.api.grpc import configservices_pb2, core_pb2, core_pb2_grpc +from core.api.grpc import ( + configservices_pb2, + core_pb2, + core_pb2_grpc, + emane_pb2, + mobility_pb2, + services_pb2, + wlan_pb2, + wrappers, +) from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsRequest, - GetConfigServiceDefaultsResponse, GetConfigServicesRequest, - GetConfigServicesResponse, GetNodeConfigServiceConfigsRequest, - GetNodeConfigServiceConfigsResponse, GetNodeConfigServiceRequest, - GetNodeConfigServiceResponse, GetNodeConfigServicesRequest, - GetNodeConfigServicesResponse, SetNodeConfigServiceRequest, - SetNodeConfigServiceResponse, ) -from core.api.grpc.core_pb2 import ExecuteScriptRequest, ExecuteScriptResponse +from core.api.grpc.core_pb2 import ExecuteScriptRequest from core.api.grpc.emane_pb2 import ( EmaneLinkRequest, - EmaneLinkResponse, - EmaneModelConfig, - EmanePathlossesRequest, - EmanePathlossesResponse, GetEmaneConfigRequest, - GetEmaneConfigResponse, GetEmaneEventChannelRequest, - GetEmaneEventChannelResponse, GetEmaneModelConfigRequest, - GetEmaneModelConfigResponse, GetEmaneModelConfigsRequest, - GetEmaneModelConfigsResponse, GetEmaneModelsRequest, - GetEmaneModelsResponse, SetEmaneConfigRequest, - SetEmaneConfigResponse, SetEmaneModelConfigRequest, - SetEmaneModelConfigResponse, ) from core.api.grpc.mobility_pb2 import ( GetMobilityConfigRequest, - GetMobilityConfigResponse, GetMobilityConfigsRequest, - GetMobilityConfigsResponse, MobilityActionRequest, - MobilityActionResponse, MobilityConfig, SetMobilityConfigRequest, - SetMobilityConfigResponse, ) from core.api.grpc.services_pb2 import ( GetNodeServiceConfigsRequest, - GetNodeServiceConfigsResponse, GetNodeServiceFileRequest, - GetNodeServiceFileResponse, GetNodeServiceRequest, - GetNodeServiceResponse, GetServiceDefaultsRequest, - GetServiceDefaultsResponse, GetServicesRequest, - GetServicesResponse, - ServiceAction, ServiceActionRequest, - ServiceActionResponse, - ServiceConfig, ServiceDefaults, - ServiceFileConfig, SetNodeServiceFileRequest, - SetNodeServiceFileResponse, SetNodeServiceRequest, - SetNodeServiceResponse, SetServiceDefaultsRequest, - SetServiceDefaultsResponse, ) from core.api.grpc.wlan_pb2 import ( GetWlanConfigRequest, - GetWlanConfigResponse, GetWlanConfigsRequest, - GetWlanConfigsResponse, SetWlanConfigRequest, - SetWlanConfigResponse, WlanConfig, WlanLinkRequest, - WlanLinkResponse, ) from core.emulator.data import IpPrefixes +from core.errors import CoreError logger = logging.getLogger(__name__) +class MoveNodesStreamer: + def __init__(self, session_id: int = None, source: str = None) -> None: + self.session_id = session_id + self.source = source + self.queue: Queue = Queue() + + def send_position(self, node_id: int, x: float, y: float) -> None: + position = wrappers.Position(x=x, y=y) + request = wrappers.MoveNodesRequest( + session_id=self.session_id, + node_id=node_id, + source=self.source, + position=position, + ) + self.send(request) + + def send_geo(self, node_id: int, lon: float, lat: float, alt: float) -> None: + geo = wrappers.Geo(lon=lon, lat=lat, alt=alt) + request = wrappers.MoveNodesRequest( + session_id=self.session_id, node_id=node_id, source=self.source, geo=geo + ) + self.send(request) + + def send(self, request: wrappers.MoveNodesRequest) -> None: + self.queue.put(request) + + def stop(self) -> None: + self.queue.put(None) + + def next(self) -> Optional[core_pb2.MoveNodesRequest]: + request: Optional[wrappers.MoveNodesRequest] = self.queue.get() + if request: + return request.to_proto() + else: + return request + + def iter(self) -> Iterable: + return iter(self.next, None) + + +class EmanePathlossesStreamer: + def __init__(self) -> None: + self.queue: Queue = Queue() + + def send(self, request: Optional[wrappers.EmanePathlossesRequest]) -> None: + self.queue.put(request) + + def next(self) -> Optional[emane_pb2.EmanePathlossesRequest]: + request: Optional[wrappers.EmanePathlossesRequest] = self.queue.get() + if request: + return request.to_proto() + else: + return request + + def iter(self): + return iter(self.next, None) + + class InterfaceHelper: """ Convenience class to help generate IP4 and IP6 addresses for gRPC clients. @@ -114,7 +147,7 @@ class InterfaceHelper: def create_iface( self, node_id: int, iface_id: int, name: str = None, mac: str = None - ) -> core_pb2.Interface: + ) -> wrappers.Interface: """ Create an interface protobuf object. @@ -125,7 +158,7 @@ class InterfaceHelper: :return: interface protobuf """ iface_data = self.prefixes.gen_iface(node_id, name, mac) - return core_pb2.Interface( + return wrappers.Interface( id=iface_id, name=iface_data.name, ip4=iface_data.ip4, @@ -136,36 +169,65 @@ class InterfaceHelper: ) -def stream_listener(stream: Any, handler: Callable[[core_pb2.Event], None]) -> None: +def throughput_listener( + stream: Any, handler: Callable[[wrappers.ThroughputsEvent], None] +) -> None: """ - Listen for stream events and provide them to the handler. + Listen for throughput events and provide them to the handler. + + :param stream: grpc stream that will provide events + :param handler: function that handles an event + :return: nothing + """ + try: + for event_proto in stream: + event = wrappers.ThroughputsEvent.from_proto(event_proto) + handler(event) + except grpc.RpcError as e: + if e.code() == grpc.StatusCode.CANCELLED: + logger.debug("throughput stream closed") + else: + logger.exception("throughput stream error") + + +def cpu_listener( + stream: Any, handler: Callable[[wrappers.CpuUsageEvent], None] +) -> None: + """ + Listen for cpu events and provide them to the handler. :param stream: grpc stream that will provide events :param handler: function that handles an event :return: nothing """ try: - for event in stream: + for event_proto in stream: + event = wrappers.CpuUsageEvent.from_proto(event_proto) handler(event) except grpc.RpcError as e: if e.code() == grpc.StatusCode.CANCELLED: - logger.debug("stream closed") + logger.debug("cpu stream closed") else: - logger.exception("stream error") + logger.exception("cpu stream error") -def start_streamer(stream: Any, handler: Callable[[core_pb2.Event], None]) -> None: +def event_listener(stream: Any, handler: Callable[[wrappers.Event], None]) -> None: """ - Convenience method for starting a grpc stream thread for handling streamed events. + Listen for session events and provide them to the handler. :param stream: grpc stream that will provide events :param handler: function that handles an event :return: nothing """ - thread = threading.Thread( - target=stream_listener, args=(stream, handler), daemon=True - ) - thread.start() + try: + for event_proto in stream: + event = wrappers.Event.from_proto(event_proto) + handler(event) + except grpc.RpcError as e: + if e.code() == grpc.StatusCode.CANCELLED: + logger.debug("session stream closed") + else: + logger.exception("session stream error") class CoreGrpcClient: @@ -186,43 +248,81 @@ class CoreGrpcClient: def start_session( self, - session_id: int, - nodes: List[core_pb2.Node], - links: List[core_pb2.Link], - location: core_pb2.SessionLocation = None, - hooks: List[core_pb2.Hook] = None, - emane_config: Dict[str, str] = None, - emane_model_configs: List[EmaneModelConfig] = None, - wlan_configs: List[WlanConfig] = None, - mobility_configs: List[MobilityConfig] = None, - service_configs: List[ServiceConfig] = None, - service_file_configs: List[ServiceFileConfig] = None, - asymmetric_links: List[core_pb2.Link] = None, - config_service_configs: List[configservices_pb2.ConfigServiceConfig] = None, - ) -> core_pb2.StartSessionResponse: + session: wrappers.Session, + asymmetric_links: List[wrappers.Link] = None, + definition: bool = False, + ) -> Tuple[bool, List[str]]: """ Start a session. - :param session_id: id of session - :param nodes: list of nodes to create - :param links: list of links to create - :param location: location to set - :param hooks: session hooks to set - :param emane_config: emane configuration to set - :param emane_model_configs: node emane model configurations - :param wlan_configs: node wlan configurations - :param mobility_configs: node mobility configurations - :param service_configs: node service configurations - :param service_file_configs: node service file configurations - :param asymmetric_links: asymmetric links to edit - :param config_service_configs: config service configurations - :return: start session response + :param session: session to start + :param asymmetric_links: link configuration for asymmetric links + :param definition: True to only define session data, False to start session + :return: tuple of result and exception strings """ + nodes = [x.to_proto() for x in session.nodes.values()] + links = [x.to_proto() for x in session.links] + if asymmetric_links: + asymmetric_links = [x.to_proto() for x in asymmetric_links] + hooks = [x.to_proto() for x in session.hooks.values()] + emane_config = {k: v.value for k, v in session.emane_config.items()} + emane_model_configs = [] + mobility_configs = [] + wlan_configs = [] + service_configs = [] + service_file_configs = [] + config_service_configs = [] + for node in session.nodes.values(): + for key, config in node.emane_model_configs.items(): + model, iface_id = key + config = wrappers.ConfigOption.to_dict(config) + if iface_id is None: + iface_id = -1 + emane_model_config = emane_pb2.EmaneModelConfig( + node_id=node.id, iface_id=iface_id, model=model, config=config + ) + emane_model_configs.append(emane_model_config) + if node.wlan_config: + config = wrappers.ConfigOption.to_dict(node.wlan_config) + wlan_config = wlan_pb2.WlanConfig(node_id=node.id, config=config) + wlan_configs.append(wlan_config) + if node.mobility_config: + config = wrappers.ConfigOption.to_dict(node.mobility_config) + mobility_config = mobility_pb2.MobilityConfig( + node_id=node.id, config=config + ) + mobility_configs.append(mobility_config) + for name, config in node.service_configs.items(): + service_config = services_pb2.ServiceConfig( + node_id=node.id, + service=name, + directories=config.dirs, + files=config.configs, + startup=config.startup, + validate=config.validate, + shutdown=config.shutdown, + ) + service_configs.append(service_config) + for service, file_configs in node.service_file_configs.items(): + for file, data in file_configs.items(): + service_file_config = services_pb2.ServiceFileConfig( + node_id=node.id, service=service, file=file, data=data + ) + service_file_configs.append(service_file_config) + for name, service_config in node.config_service_configs.items(): + config_service_config = configservices_pb2.ConfigServiceConfig( + node_id=node.id, + name=name, + templates=service_config.templates, + config=service_config.config, + ) + config_service_configs.append(config_service_config) + options = {k: v.value for k, v in session.options.items()} request = core_pb2.StartSessionRequest( - session_id=session_id, + session_id=session.id, nodes=nodes, links=links, - location=location, + location=session.location.to_proto(), hooks=hooks, emane_config=emane_config, emane_model_configs=emane_model_configs, @@ -232,125 +332,155 @@ class CoreGrpcClient: service_file_configs=service_file_configs, asymmetric_links=asymmetric_links, config_service_configs=config_service_configs, + options=options, + user=session.user, + definition=definition, + metadata=session.metadata, ) - return self.stub.StartSession(request) + response = self.stub.StartSession(request) + return response.result, list(response.exceptions) - def stop_session(self, session_id: int) -> core_pb2.StopSessionResponse: + def stop_session(self, session_id: int) -> bool: """ Stop a running session. :param session_id: id of session - :return: stop session response + :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.StopSessionRequest(session_id=session_id) - return self.stub.StopSession(request) + response = self.stub.StopSession(request) + return response.result - def create_session(self, session_id: int = None) -> core_pb2.CreateSessionResponse: + def add_session(self, session_id: int = None) -> wrappers.Session: + session_id = self.create_session(session_id) + return self.get_session(session_id) + + def create_session(self, session_id: int = None) -> int: """ Create a session. :param session_id: id for session, default is None and one will be created for you - :return: response with created session id + :return: session id """ request = core_pb2.CreateSessionRequest(session_id=session_id) - return self.stub.CreateSession(request) + response = self.stub.CreateSession(request) + return response.session_id - def delete_session(self, session_id: int) -> core_pb2.DeleteSessionResponse: + def delete_session(self, session_id: int) -> bool: """ Delete a session. :param session_id: id of session - :return: response with result of deletion success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.DeleteSessionRequest(session_id=session_id) - return self.stub.DeleteSession(request) + response = self.stub.DeleteSession(request) + return response.result - def get_sessions(self) -> core_pb2.GetSessionsResponse: + def get_sessions(self) -> List[wrappers.SessionSummary]: """ Retrieves all currently known sessions. :return: response with a list of currently known session, their state and number of nodes """ - return self.stub.GetSessions(core_pb2.GetSessionsRequest()) + response = self.stub.GetSessions(core_pb2.GetSessionsRequest()) + sessions = [] + for session_proto in response.sessions: + session = wrappers.SessionSummary.from_proto(session_proto) + sessions.append(session) + return sessions - def check_session(self, session_id: int) -> core_pb2.CheckSessionResponse: + def check_session(self, session_id: int) -> bool: """ Check if a session exists. :param session_id: id of session to check for - :return: response with result if session was found + :return: True if exists, False otherwise """ request = core_pb2.CheckSessionRequest(session_id=session_id) - return self.stub.CheckSession(request) + response = self.stub.CheckSession(request) + return response.result - def get_session(self, session_id: int) -> core_pb2.GetSessionResponse: + def get_session(self, session_id: int) -> wrappers.Session: """ Retrieve a session. :param session_id: id of session - :return: response with sessions state, nodes, and links + :return: session :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.GetSessionRequest(session_id=session_id) - return self.stub.GetSession(request) + response = self.stub.GetSession(request) + return wrappers.Session.from_proto(response.session) - def set_session_state( - self, session_id: int, state: core_pb2.SessionState - ) -> core_pb2.SetSessionStateResponse: + def set_session_state(self, session_id: int, state: wrappers.SessionState) -> bool: """ Set session state. :param session_id: id of session :param state: session state to transition to - :return: response with result of success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.SetSessionStateRequest(session_id=session_id, state=state) - return self.stub.SetSessionState(request) + request = core_pb2.SetSessionStateRequest( + session_id=session_id, state=state.value + ) + response = self.stub.SetSessionState(request) + return response.result - def add_session_server( - self, session_id: int, name: str, host: str - ) -> core_pb2.AddSessionServerResponse: + def add_session_server(self, session_id: int, name: str, host: str) -> bool: """ Add distributed session server. :param session_id: id of session :param name: name of server to add :param host: host address to connect to - :return: response with result of success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.AddSessionServerRequest( session_id=session_id, name=name, host=host ) - return self.stub.AddSessionServer(request) + response = self.stub.AddSessionServer(request) + return response.result def alert( self, session_id: int, - level: core_pb2.ExceptionLevel, + level: wrappers.ExceptionLevel, source: str, text: str, node_id: int = None, - ) -> core_pb2.SessionAlertResponse: + ) -> bool: + """ + Initiate an alert to be broadcast out to all listeners. + + :param session_id: id of session + :param level: alert level + :param source: source of alert + :param text: alert text + :param node_id: node associated with alert + :return: True for success, False otherwise + """ request = core_pb2.SessionAlertRequest( session_id=session_id, - level=level, + level=level.value, source=source, text=text, node_id=node_id, ) - return self.stub.SessionAlert(request) + response = self.stub.SessionAlert(request) + return response.result def events( self, session_id: int, - handler: Callable[[core_pb2.Event], None], - events: List[core_pb2.Event] = None, + handler: Callable[[wrappers.Event], None], + events: List[wrappers.EventType] = None, ) -> grpc.Future: """ Listen for session events. @@ -363,11 +493,14 @@ class CoreGrpcClient: """ request = core_pb2.EventsRequest(session_id=session_id, events=events) stream = self.stub.Events(request) - start_streamer(stream, handler) + thread = threading.Thread( + target=event_listener, args=(stream, handler), daemon=True + ) + thread.start() return stream def throughputs( - self, session_id: int, handler: Callable[[core_pb2.ThroughputsEvent], None] + self, session_id: int, handler: Callable[[wrappers.ThroughputsEvent], None] ) -> grpc.Future: """ Listen for throughput events with information for interfaces and bridges. @@ -379,11 +512,14 @@ class CoreGrpcClient: """ request = core_pb2.ThroughputsRequest(session_id=session_id) stream = self.stub.Throughputs(request) - start_streamer(stream, handler) + thread = threading.Thread( + target=throughput_listener, args=(stream, handler), daemon=True + ) + thread.start() return stream def cpu_usage( - self, delay: int, handler: Callable[[core_pb2.CpuUsageEvent], None] + self, delay: int, handler: Callable[[wrappers.CpuUsageEvent], None] ) -> grpc.Future: """ Listen for cpu usage events with the given repeat delay. @@ -394,47 +530,61 @@ class CoreGrpcClient: """ request = core_pb2.CpuUsageRequest(delay=delay) stream = self.stub.CpuUsage(request) - start_streamer(stream, handler) + thread = threading.Thread( + target=cpu_listener, args=(stream, handler), daemon=True + ) + thread.start() return stream - def add_node( - self, session_id: int, node: core_pb2.Node, source: str = None - ) -> core_pb2.AddNodeResponse: + def add_node(self, session_id: int, node: wrappers.Node, source: str = None) -> int: """ Add node to session. :param session_id: session id :param node: node to add :param source: source application - :return: response with node id + :return: id of added node :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.AddNodeRequest( - session_id=session_id, node=node, source=source + session_id=session_id, node=node.to_proto(), source=source ) - return self.stub.AddNode(request) + response = self.stub.AddNode(request) + return response.node_id - def get_node(self, session_id: int, node_id: int) -> core_pb2.GetNodeResponse: + def get_node( + self, session_id: int, node_id: int + ) -> Tuple[wrappers.Node, List[wrappers.Interface], List[wrappers.Link]]: """ Get node details. :param session_id: session id :param node_id: node id - :return: response with node details + :return: tuple of node and its interfaces :raises grpc.RpcError: when session or node doesn't exist """ request = core_pb2.GetNodeRequest(session_id=session_id, node_id=node_id) - return self.stub.GetNode(request) + response = self.stub.GetNode(request) + node = wrappers.Node.from_proto(response.node) + ifaces = [] + for iface_proto in response.ifaces: + iface = wrappers.Interface.from_proto(iface_proto) + ifaces.append(iface) + links = [] + for link_proto in response.links: + link = wrappers.Link.from_proto(link_proto) + links.append(link) + return node, ifaces, links def edit_node( self, session_id: int, node_id: int, - position: core_pb2.Position = None, + position: wrappers.Position = None, icon: str = None, - geo: core_pb2.Geo = None, + geo: wrappers.Geo = None, source: str = None, - ) -> core_pb2.EditNodeResponse: + ) -> bool: """ Edit a node's icon and/or location, can only use position(x,y) or geo(lon, lat, alt), not both. @@ -445,47 +595,49 @@ class CoreGrpcClient: :param icon: path to icon for gui to use for node :param geo: lon,lat,alt location for node :param source: application source - :return: response with result of success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session or node doesn't exist """ + if position and geo: + raise CoreError("cannot edit position and geo at same time") + position_proto = position.to_proto() if position else None + geo_proto = geo.to_proto() if geo else None request = core_pb2.EditNodeRequest( session_id=session_id, node_id=node_id, - position=position, + position=position_proto, icon=icon, source=source, - geo=geo, + geo=geo_proto, ) - return self.stub.EditNode(request) + response = self.stub.EditNode(request) + return response.result - def move_nodes( - self, move_iterator: Iterable[core_pb2.MoveNodesRequest] - ) -> core_pb2.MoveNodesResponse: + def move_nodes(self, streamer: MoveNodesStreamer) -> None: """ Stream node movements using the provided iterator. - :param move_iterator: iterator for generating node movements - :return: move nodes response + :param streamer: move nodes streamer + :return: nothing :raises grpc.RpcError: when session or nodes do not exist """ - return self.stub.MoveNodes(move_iterator) + self.stub.MoveNodes(streamer.iter()) - def delete_node( - self, session_id: int, node_id: int, source: str = None - ) -> core_pb2.DeleteNodeResponse: + def delete_node(self, session_id: int, node_id: int, source: str = None) -> bool: """ 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 + :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.DeleteNodeRequest( session_id=session_id, node_id=node_id, source=source ) - return self.stub.DeleteNode(request) + response = self.stub.DeleteNode(request) + return response.result def node_command( self, @@ -494,7 +646,7 @@ class CoreGrpcClient: command: str, wait: bool = True, shell: bool = False, - ) -> core_pb2.NodeCommandResponse: + ) -> Tuple[int, str]: """ Send command to a node and get the output. @@ -503,7 +655,7 @@ class CoreGrpcClient: :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 + :return: returns tuple of return code and output :raises grpc.RpcError: when session or node doesn't exist """ request = core_pb2.NodeCommandRequest( @@ -513,214 +665,220 @@ class CoreGrpcClient: wait=wait, shell=shell, ) - return self.stub.NodeCommand(request) + response = self.stub.NodeCommand(request) + return response.return_code, response.output - def get_node_terminal( - self, session_id: int, node_id: int - ) -> core_pb2.GetNodeTerminalResponse: + def get_node_terminal(self, session_id: int, node_id: int) -> str: """ Retrieve terminal command string for launching a local terminal. :param session_id: session id :param node_id: node id - :return: response with a node terminal command + :return: node terminal :raises grpc.RpcError: when session or node doesn't exist """ request = core_pb2.GetNodeTerminalRequest( session_id=session_id, node_id=node_id ) - return self.stub.GetNodeTerminal(request) + response = self.stub.GetNodeTerminal(request) + return response.terminal + + def get_node_links(self, session_id: int, node_id: int) -> List[wrappers.Link]: + """ + Get current links for a node. + + :param session_id: session id + :param node_id: node id + :return: list of links + :raises grpc.RpcError: when session or node doesn't exist + """ + request = core_pb2.GetNodeLinksRequest(session_id=session_id, node_id=node_id) + response = self.stub.GetNodeLinks(request) + links = [] + for link_proto in response.links: + link = wrappers.Link.from_proto(link_proto) + links.append(link) + return links def add_link( - self, - session_id: int, - node1_id: int, - node2_id: int, - iface1: core_pb2.Interface = None, - iface2: core_pb2.Interface = None, - options: core_pb2.LinkOptions = None, - source: str = None, - ) -> core_pb2.AddLinkResponse: + self, session_id: int, link: wrappers.Link, source: str = None + ) -> Tuple[bool, wrappers.Interface, wrappers.Interface]: """ Add a link between nodes. :param session_id: session id - :param node1_id: node one id - :param node2_id: node two id - :param iface1: node one interface data - :param iface2: node two interface data - :param options: options for link (jitter, bandwidth, etc) + :param link: link to add :param source: application source - :return: response with result of success or failure + :return: tuple of result and finalized interface values :raises grpc.RpcError: when session or one of the nodes don't exist """ - link = core_pb2.Link( - node1_id=node1_id, - node2_id=node2_id, - type=core_pb2.LinkType.WIRED, - iface1=iface1, - iface2=iface2, - options=options, - ) request = core_pb2.AddLinkRequest( - session_id=session_id, link=link, source=source + session_id=session_id, link=link.to_proto(), source=source ) - return self.stub.AddLink(request) + response = self.stub.AddLink(request) + iface1 = wrappers.Interface.from_proto(response.iface1) + iface2 = wrappers.Interface.from_proto(response.iface2) + return response.result, iface1, iface2 def edit_link( - self, - session_id: int, - node1_id: int, - node2_id: int, - options: core_pb2.LinkOptions, - iface1_id: int = None, - iface2_id: int = None, - source: str = None, - ) -> core_pb2.EditLinkResponse: + self, session_id: int, link: wrappers.Link, source: str = None + ) -> bool: """ Edit a link between nodes. :param session_id: session id - :param node1_id: node one id - :param node2_id: node two id - :param options: options for link (jitter, bandwidth, etc) - :param iface1_id: node one interface id - :param iface2_id: node two interface id + :param link: link to edit :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 """ + iface1_id = link.iface1.id if link.iface1 else None + iface2_id = link.iface2.id if link.iface2 else None request = core_pb2.EditLinkRequest( session_id=session_id, - node1_id=node1_id, - node2_id=node2_id, - options=options, + node1_id=link.node1_id, + node2_id=link.node2_id, + options=link.options.to_proto(), iface1_id=iface1_id, iface2_id=iface2_id, source=source, ) - return self.stub.EditLink(request) + response = self.stub.EditLink(request) + return response.result def delete_link( - self, - session_id: int, - node1_id: int, - node2_id: int, - iface1_id: int = None, - iface2_id: int = None, - source: str = None, - ) -> core_pb2.DeleteLinkResponse: + self, session_id: int, link: wrappers.Link, source: str = None + ) -> bool: """ Delete a link between nodes. :param session_id: session id - :param node1_id: node one id - :param node2_id: node two id - :param iface1_id: node one interface id - :param iface2_id: node two interface id + :param link: link to delete :param source: application source :return: response with result of success or failure :raises grpc.RpcError: when session doesn't exist """ + iface1_id = link.iface1.id if link.iface1 else None + iface2_id = link.iface2.id if link.iface2 else None request = core_pb2.DeleteLinkRequest( session_id=session_id, - node1_id=node1_id, - node2_id=node2_id, + node1_id=link.node1_id, + node2_id=link.node2_id, iface1_id=iface1_id, iface2_id=iface2_id, source=source, ) - return self.stub.DeleteLink(request) + response = self.stub.DeleteLink(request) + return response.result - def get_mobility_configs(self, session_id: int) -> GetMobilityConfigsResponse: + def get_mobility_configs( + self, session_id: int + ) -> Dict[int, Dict[str, wrappers.ConfigOption]]: """ Get all mobility configurations. :param session_id: session id - :return: response with a dict of node ids to mobility configurations + :return: dict of node id to mobility configuration dict :raises grpc.RpcError: when session doesn't exist """ request = GetMobilityConfigsRequest(session_id=session_id) - return self.stub.GetMobilityConfigs(request) + response = self.stub.GetMobilityConfigs(request) + configs = {} + for node_id, mapped_config in response.configs.items(): + configs[node_id] = wrappers.ConfigOption.from_dict(mapped_config.config) + return configs def get_mobility_config( self, session_id: int, node_id: int - ) -> GetMobilityConfigResponse: + ) -> Dict[str, wrappers.ConfigOption]: """ Get mobility configuration for a node. :param session_id: session id :param node_id: node id - :return: response with a list of configuration groups + :return: dict of config name to options :raises grpc.RpcError: when session or node doesn't exist """ request = GetMobilityConfigRequest(session_id=session_id, node_id=node_id) - return self.stub.GetMobilityConfig(request) + response = self.stub.GetMobilityConfig(request) + return wrappers.ConfigOption.from_dict(response.config) def set_mobility_config( self, session_id: int, node_id: int, config: Dict[str, str] - ) -> SetMobilityConfigResponse: + ) -> bool: """ Set mobility configuration for a node. :param session_id: session id :param node_id: node id :param config: mobility configuration - :return: response with result of success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session or node doesn't exist """ mobility_config = MobilityConfig(node_id=node_id, config=config) request = SetMobilityConfigRequest( session_id=session_id, mobility_config=mobility_config ) - return self.stub.SetMobilityConfig(request) + response = self.stub.SetMobilityConfig(request) + return response.result def mobility_action( - self, session_id: int, node_id: int, action: ServiceAction - ) -> MobilityActionResponse: + self, session_id: int, node_id: int, action: wrappers.MobilityAction + ) -> bool: """ Send a mobility action for a node. :param session_id: session id :param node_id: node id :param action: action to take - :return: response with result of success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session or node doesn't exist """ request = MobilityActionRequest( - session_id=session_id, node_id=node_id, action=action + session_id=session_id, node_id=node_id, action=action.value ) - return self.stub.MobilityAction(request) + response = self.stub.MobilityAction(request) + return response.result - def get_services(self) -> GetServicesResponse: + def get_services(self) -> List[wrappers.Service]: """ Get all currently loaded services. - :return: response with a list of services + :return: list of services, name and groups only """ request = GetServicesRequest() - return self.stub.GetServices(request) + response = self.stub.GetServices(request) + services = [] + for service_proto in response.services: + service = wrappers.Service.from_proto(service_proto) + services.append(service) + return services - def get_service_defaults(self, session_id: int) -> GetServiceDefaultsResponse: + def get_service_defaults(self, session_id: int) -> List[wrappers.ServiceDefault]: """ Get default services for different default node models. :param session_id: session id - :return: response with a dict of node model to a list of services + :return: list of service defaults :raises grpc.RpcError: when session doesn't exist """ request = GetServiceDefaultsRequest(session_id=session_id) - return self.stub.GetServiceDefaults(request) + response = self.stub.GetServiceDefaults(request) + defaults = [] + for default_proto in response.defaults: + default = wrappers.ServiceDefault.from_proto(default_proto) + defaults.append(default) + return defaults def set_service_defaults( self, session_id: int, service_defaults: Dict[str, List[str]] - ) -> SetServiceDefaultsResponse: + ) -> bool: """ Set default services for node models. :param session_id: session id :param service_defaults: node models to lists of services - :return: response with result of success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ defaults = [] @@ -729,41 +887,48 @@ class CoreGrpcClient: default = ServiceDefaults(node_type=node_type, services=services) defaults.append(default) request = SetServiceDefaultsRequest(session_id=session_id, defaults=defaults) - return self.stub.SetServiceDefaults(request) + response = self.stub.SetServiceDefaults(request) + return response.result def get_node_service_configs( self, session_id: int - ) -> GetNodeServiceConfigsResponse: + ) -> List[wrappers.NodeServiceConfig]: """ Get service data for a node. :param session_id: session id - :return: response with all node service configs + :return: list of node service data :raises grpc.RpcError: when session doesn't exist """ request = GetNodeServiceConfigsRequest(session_id=session_id) - return self.stub.GetNodeServiceConfigs(request) + response = self.stub.GetNodeServiceConfigs(request) + node_services = [] + for config in response.configs: + node_service = wrappers.NodeServiceConfig.from_proto(config) + node_services.append(node_service) + return node_services def get_node_service( self, session_id: int, node_id: int, service: str - ) -> GetNodeServiceResponse: + ) -> wrappers.NodeServiceData: """ Get service data for a node. :param session_id: session id :param node_id: node id :param service: service name - :return: response with node service data + :return: node service data :raises grpc.RpcError: when session or node doesn't exist """ request = GetNodeServiceRequest( session_id=session_id, node_id=node_id, service=service ) - return self.stub.GetNodeService(request) + response = self.stub.GetNodeService(request) + return wrappers.NodeServiceData.from_proto(response.service) def get_node_service_file( self, session_id: int, node_id: int, service: str, file_name: str - ) -> GetNodeServiceFileResponse: + ) -> str: """ Get a service file for a node. @@ -771,74 +936,55 @@ class CoreGrpcClient: :param node_id: node id :param service: service name :param file_name: file name to get data for - :return: response with file data + :return: file data :raises grpc.RpcError: when session or node doesn't exist """ request = GetNodeServiceFileRequest( session_id=session_id, node_id=node_id, service=service, file=file_name ) - return self.stub.GetNodeServiceFile(request) + response = self.stub.GetNodeServiceFile(request) + return response.data def set_node_service( - self, - session_id: int, - node_id: int, - service: str, - files: List[str] = None, - directories: List[str] = None, - startup: List[str] = None, - validate: List[str] = None, - shutdown: List[str] = None, - ) -> SetNodeServiceResponse: + self, session_id: int, service_config: wrappers.ServiceConfig + ) -> bool: """ Set service data for a node. :param session_id: session id - :param node_id: node id - :param service: service name - :param files: service files - :param directories: service directories - :param startup: startup commands - :param validate: validation commands - :param shutdown: shutdown commands - :return: response with result of success or failure + :param service_config: service configuration for a node + :return: True for success, False otherwise :raises grpc.RpcError: when session or node doesn't exist """ - config = ServiceConfig( - node_id=node_id, - service=service, - files=files, - directories=directories, - startup=startup, - validate=validate, - shutdown=shutdown, + request = SetNodeServiceRequest( + session_id=session_id, config=service_config.to_proto() ) - request = SetNodeServiceRequest(session_id=session_id, config=config) - return self.stub.SetNodeService(request) + response = self.stub.SetNodeService(request) + return response.result def set_node_service_file( - self, session_id: int, node_id: int, service: str, file_name: str, data: str - ) -> SetNodeServiceFileResponse: + self, session_id: int, service_file_config: wrappers.ServiceFileConfig + ) -> bool: """ Set a service file for a node. :param session_id: session id - :param node_id: node id - :param service: service name - :param file_name: file name to save - :param data: data to save for file - :return: response with result of success or failure + :param service_file_config: configuration to set + :return: True for success, False otherwise :raises grpc.RpcError: when session or node doesn't exist """ - config = ServiceFileConfig( - node_id=node_id, service=service, file=file_name, data=data - ) + config = service_file_config.to_proto() request = SetNodeServiceFileRequest(session_id=session_id, config=config) - return self.stub.SetNodeServiceFile(request) + response = self.stub.SetNodeServiceFile(request) + return response.result def service_action( - self, session_id: int, node_id: int, service: str, action: ServiceAction - ) -> ServiceActionResponse: + self, + session_id: int, + node_id: int, + service: str, + action: wrappers.ServiceAction, + ) -> bool: """ Send an action to a service for a node. @@ -847,54 +993,65 @@ class CoreGrpcClient: :param service: service name :param action: action for service (start, stop, restart, validate) - :return: response with result of success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session or node doesn't exist """ request = ServiceActionRequest( - session_id=session_id, node_id=node_id, service=service, action=action + session_id=session_id, node_id=node_id, service=service, action=action.value ) - return self.stub.ServiceAction(request) + response = self.stub.ServiceAction(request) + return response.result - def get_wlan_configs(self, session_id: int) -> GetWlanConfigsResponse: + def get_wlan_configs( + self, session_id: int + ) -> Dict[int, Dict[str, wrappers.ConfigOption]]: """ Get all wlan configurations. :param session_id: session id - :return: response with a dict of node ids to wlan configurations + :return: dict of node ids to dict of names to options :raises grpc.RpcError: when session doesn't exist """ request = GetWlanConfigsRequest(session_id=session_id) - return self.stub.GetWlanConfigs(request) + response = self.stub.GetWlanConfigs(request) + configs = {} + for node_id, mapped_config in response.configs.items(): + configs[node_id] = wrappers.ConfigOption.from_dict(mapped_config.config) + return configs - def get_wlan_config(self, session_id: int, node_id: int) -> GetWlanConfigResponse: + def get_wlan_config( + self, session_id: int, node_id: int + ) -> Dict[str, wrappers.ConfigOption]: """ Get wlan configuration for a node. :param session_id: session id :param node_id: node id - :return: response with a list of configuration groups + :return: dict of names to options :raises grpc.RpcError: when session doesn't exist """ request = GetWlanConfigRequest(session_id=session_id, node_id=node_id) - return self.stub.GetWlanConfig(request) + response = self.stub.GetWlanConfig(request) + return wrappers.ConfigOption.from_dict(response.config) def set_wlan_config( self, session_id: int, node_id: int, config: Dict[str, str] - ) -> SetWlanConfigResponse: + ) -> bool: """ Set wlan configuration for a node. :param session_id: session id :param node_id: node id :param config: wlan configuration - :return: response with result of success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ wlan_config = WlanConfig(node_id=node_id, config=config) request = SetWlanConfigRequest(session_id=session_id, wlan_config=wlan_config) - return self.stub.SetWlanConfig(request) + response = self.stub.SetWlanConfig(request) + return response.result - def get_emane_config(self, session_id: int) -> GetEmaneConfigResponse: + def get_emane_config(self, session_id: int) -> Dict[str, wrappers.ConfigOption]: """ Get session emane configuration. @@ -903,36 +1060,37 @@ class CoreGrpcClient: :raises grpc.RpcError: when session doesn't exist """ request = GetEmaneConfigRequest(session_id=session_id) - return self.stub.GetEmaneConfig(request) + response = self.stub.GetEmaneConfig(request) + return wrappers.ConfigOption.from_dict(response.config) - def set_emane_config( - self, session_id: int, config: Dict[str, str] - ) -> SetEmaneConfigResponse: + def set_emane_config(self, session_id: int, config: Dict[str, str]) -> bool: """ Set session emane configuration. :param session_id: session id :param config: emane configuration - :return: response with result of success or failure + :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ request = SetEmaneConfigRequest(session_id=session_id, config=config) - return self.stub.SetEmaneConfig(request) + response = self.stub.SetEmaneConfig(request) + return response.result - def get_emane_models(self, session_id: int) -> GetEmaneModelsResponse: + def get_emane_models(self, session_id: int) -> List[str]: """ Get session emane models. :param session_id: session id - :return: response with a list of emane models + :return: list of emane models :raises grpc.RpcError: when session doesn't exist """ request = GetEmaneModelsRequest(session_id=session_id) - return self.stub.GetEmaneModels(request) + response = self.stub.GetEmaneModels(request) + return list(response.models) def get_emane_model_config( self, session_id: int, node_id: int, model: str, iface_id: int = -1 - ) -> GetEmaneModelConfigResponse: + ) -> Dict[str, wrappers.ConfigOption]: """ Get emane model configuration for a node or a node's interface. @@ -940,53 +1098,51 @@ class CoreGrpcClient: :param node_id: node id :param model: emane model name :param iface_id: node interface id - :return: response with a list of configuration groups + :return: dict of names to options :raises grpc.RpcError: when session doesn't exist """ request = GetEmaneModelConfigRequest( session_id=session_id, node_id=node_id, model=model, iface_id=iface_id ) - return self.stub.GetEmaneModelConfig(request) + response = self.stub.GetEmaneModelConfig(request) + return wrappers.ConfigOption.from_dict(response.config) def set_emane_model_config( - self, - session_id: int, - node_id: int, - model: str, - config: Dict[str, str] = None, - iface_id: int = -1, - ) -> SetEmaneModelConfigResponse: + self, session_id: int, emane_model_config: wrappers.EmaneModelConfig + ) -> bool: """ Set emane model configuration for a node or a node's interface. :param session_id: session id - :param node_id: node id - :param model: emane model name - :param config: emane model configuration - :param iface_id: node interface id - :return: response with result of success or failure + :param emane_model_config: emane model config to set + :return: True for success, False otherwise :raises grpc.RpcError: when session doesn't exist """ - model_config = EmaneModelConfig( - node_id=node_id, model=model, config=config, iface_id=iface_id - ) request = SetEmaneModelConfigRequest( - session_id=session_id, emane_model_config=model_config + session_id=session_id, emane_model_config=emane_model_config.to_proto() ) - return self.stub.SetEmaneModelConfig(request) + response = self.stub.SetEmaneModelConfig(request) + return response.result - def get_emane_model_configs(self, session_id: int) -> GetEmaneModelConfigsResponse: + def get_emane_model_configs( + self, session_id: int + ) -> List[wrappers.EmaneModelConfig]: """ Get all EMANE model configurations for a session. :param session_id: session to get emane model configs - :return: response with a dictionary of node/interface ids to configurations + :return: list of emane model configs :raises grpc.RpcError: when session doesn't exist """ request = GetEmaneModelConfigsRequest(session_id=session_id) - return self.stub.GetEmaneModelConfigs(request) + response = self.stub.GetEmaneModelConfigs(request) + configs = [] + for config_proto in response.configs: + config = wrappers.EmaneModelConfig.from_proto(config_proto) + configs.append(config) + return configs - def save_xml(self, session_id: int, file_path: str) -> core_pb2.SaveXmlResponse: + def save_xml(self, session_id: int, file_path: str) -> None: """ Save the current scenario to an XML file. @@ -1000,22 +1156,21 @@ class CoreGrpcClient: with open(file_path, "w") as xml_file: xml_file.write(response.data) - def open_xml(self, file_path: str, start: bool = False) -> core_pb2.OpenXmlResponse: + def open_xml(self, file_path: Path, start: bool = False) -> Tuple[bool, int]: """ Load a local scenario XML file to open as a new session. :param file_path: path of scenario XML file - :param start: True to start session, False otherwise - :return: response with opened session id + :param start: tuple of result and session id when successful + :return: tuple of result and session id """ - with open(file_path, "r") as xml_file: - data = xml_file.read() - request = core_pb2.OpenXmlRequest(data=data, start=start, file=file_path) - return self.stub.OpenXml(request) + with file_path.open("r") as f: + data = f.read() + request = core_pb2.OpenXmlRequest(data=data, start=start, file=str(file_path)) + response = self.stub.OpenXml(request) + return response.result, response.session_id - def emane_link( - self, session_id: int, nem1: int, nem2: int, linked: bool - ) -> EmaneLinkResponse: + def emane_link(self, session_id: int, nem1: int, nem2: int, linked: bool) -> bool: """ Helps broadcast wireless link/unlink between EMANE nodes. @@ -1023,92 +1178,103 @@ class CoreGrpcClient: :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 + :return: True for success, False otherwise :raises grpc.RpcError: when session or nodes related to nems do not exist """ request = EmaneLinkRequest( session_id=session_id, nem1=nem1, nem2=nem2, linked=linked ) - return self.stub.EmaneLink(request) + response = self.stub.EmaneLink(request) + return response.result - def get_ifaces(self) -> core_pb2.GetInterfacesResponse: + def get_ifaces(self) -> List[str]: """ Retrieves a list of interfaces available on the host machine that are not a part of a CORE session. - :return: get interfaces response + :return: list of interfaces """ request = core_pb2.GetInterfacesRequest() - return self.stub.GetInterfaces(request) + response = self.stub.GetInterfaces(request) + return list(response.ifaces) - def get_config_services(self) -> GetConfigServicesResponse: + def get_config_services(self) -> List[wrappers.ConfigService]: """ Retrieve all known config services. - :return: get config services response + :return: list of config services """ request = GetConfigServicesRequest() - return self.stub.GetConfigServices(request) + response = self.stub.GetConfigServices(request) + services = [] + for service_proto in response.services: + service = wrappers.ConfigService.from_proto(service_proto) + services.append(service) + return services - def get_config_service_defaults( - self, name: str - ) -> GetConfigServiceDefaultsResponse: + def get_config_service_defaults(self, name: str) -> wrappers.ConfigServiceDefaults: """ Retrieves config service default values. :param name: name of service to get defaults for - :return: get config service defaults + :return: config service defaults """ request = GetConfigServiceDefaultsRequest(name=name) - return self.stub.GetConfigServiceDefaults(request) + response = self.stub.GetConfigServiceDefaults(request) + return wrappers.ConfigServiceDefaults.from_proto(response) def get_node_config_service_configs( self, session_id: int - ) -> GetNodeConfigServiceConfigsResponse: + ) -> List[wrappers.ConfigServiceConfig]: """ 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 + :return: list of node config service configs :raises grpc.RpcError: when session doesn't exist """ request = GetNodeConfigServiceConfigsRequest(session_id=session_id) - return self.stub.GetNodeConfigServiceConfigs(request) + response = self.stub.GetNodeConfigServiceConfigs(request) + configs = [] + for config_proto in response.configs: + config = wrappers.ConfigServiceConfig.from_proto(config_proto) + configs.append(config) + return configs def get_node_config_service( self, session_id: int, node_id: int, name: str - ) -> GetNodeConfigServiceResponse: + ) -> Dict[str, str]: """ 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 + :return: config dict of names to values :raises grpc.RpcError: when session or node doesn't exist """ request = GetNodeConfigServiceRequest( session_id=session_id, node_id=node_id, name=name ) - return self.stub.GetNodeConfigService(request) + response = self.stub.GetNodeConfigService(request) + return dict(response.config) - def get_node_config_services( - self, session_id: int, node_id: int - ) -> GetNodeConfigServicesResponse: + def get_node_config_services(self, session_id: int, node_id: int) -> List[str]: """ 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 + :return: list of config services :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) + response = self.stub.GetNodeConfigServices(request) + return list(response.services) def set_node_config_service( self, session_id: int, node_id: int, name: str, config: Dict[str, str] - ) -> SetNodeConfigServiceResponse: + ) -> bool: """ Assigns a config service to a node with the provided configuration. @@ -1116,38 +1282,41 @@ class CoreGrpcClient: :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 + :return: True for success, False otherwise :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) + response = self.stub.SetNodeConfigService(request) + return response.result - def get_emane_event_channel(self, session_id: int) -> GetEmaneEventChannelResponse: + def get_emane_event_channel(self, session_id: int) -> wrappers.EmaneEventChannel: """ 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 + :return: emane event channel :raises grpc.RpcError: when session doesn't exist """ request = GetEmaneEventChannelRequest(session_id=session_id) - return self.stub.GetEmaneEventChannel(request) + response = self.stub.GetEmaneEventChannel(request) + return wrappers.EmaneEventChannel.from_proto(response) - def execute_script(self, script: str) -> ExecuteScriptResponse: + def execute_script(self, script: str) -> Optional[int]: """ Executes a python script given context of the current CoreEmu object. :param script: script to execute - :return: execute script response + :return: create session id for script executed """ request = ExecuteScriptRequest(script=script) - return self.stub.ExecuteScript(request) + response = self.stub.ExecuteScript(request) + return response.session_id if response.session_id else None def wlan_link( self, session_id: int, wlan_id: int, node1_id: int, node2_id: int, linked: bool - ) -> WlanLinkResponse: + ) -> bool: """ Links/unlinks nodes on the same WLAN. @@ -1156,7 +1325,7 @@ class CoreGrpcClient: :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 + :return: True for success, False otherwise :raises grpc.RpcError: when session or one of the nodes do not exist """ request = WlanLinkRequest( @@ -1166,20 +1335,19 @@ class CoreGrpcClient: node2_id=node2_id, linked=linked, ) - return self.stub.WlanLink(request) + response = self.stub.WlanLink(request) + return response.result - def emane_pathlosses( - self, pathloss_iterator: Iterable[EmanePathlossesRequest] - ) -> EmanePathlossesResponse: + def emane_pathlosses(self, streamer: EmanePathlossesStreamer) -> None: """ Stream EMANE pathloss events. - :param pathloss_iterator: iterator for sending emane pathloss events - :return: emane pathloss response + :param streamer: emane pathlosses streamer + :return: nothing :raises grpc.RpcError: when a pathloss event session or one of the nodes do not exist """ - return self.stub.EmanePathlosses(pathloss_iterator) + self.stub.EmanePathlosses(streamer.iter()) def connect(self) -> None: """ diff --git a/daemon/core/api/grpc/clientw.py b/daemon/core/api/grpc/clientw.py deleted file mode 100644 index 23a66f1f..00000000 --- a/daemon/core/api/grpc/clientw.py +++ /dev/null @@ -1,1385 +0,0 @@ -""" -gRpc client for interfacing with CORE. -""" - -import logging -import threading -from contextlib import contextmanager -from pathlib import Path -from queue import Queue -from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple - -import grpc - -from core.api.grpc import ( - configservices_pb2, - core_pb2, - core_pb2_grpc, - emane_pb2, - mobility_pb2, - services_pb2, - wlan_pb2, - wrappers, -) -from core.api.grpc.configservices_pb2 import ( - GetConfigServiceDefaultsRequest, - GetConfigServicesRequest, - GetNodeConfigServiceConfigsRequest, - GetNodeConfigServiceRequest, - GetNodeConfigServicesRequest, - SetNodeConfigServiceRequest, -) -from core.api.grpc.core_pb2 import ExecuteScriptRequest -from core.api.grpc.emane_pb2 import ( - EmaneLinkRequest, - GetEmaneConfigRequest, - GetEmaneEventChannelRequest, - GetEmaneModelConfigRequest, - GetEmaneModelConfigsRequest, - GetEmaneModelsRequest, - SetEmaneConfigRequest, - SetEmaneModelConfigRequest, -) -from core.api.grpc.mobility_pb2 import ( - GetMobilityConfigRequest, - GetMobilityConfigsRequest, - MobilityActionRequest, - MobilityConfig, - SetMobilityConfigRequest, -) -from core.api.grpc.services_pb2 import ( - GetNodeServiceConfigsRequest, - GetNodeServiceFileRequest, - GetNodeServiceRequest, - GetServiceDefaultsRequest, - GetServicesRequest, - ServiceActionRequest, - ServiceDefaults, - SetNodeServiceFileRequest, - SetNodeServiceRequest, - SetServiceDefaultsRequest, -) -from core.api.grpc.wlan_pb2 import ( - GetWlanConfigRequest, - GetWlanConfigsRequest, - SetWlanConfigRequest, - WlanConfig, - WlanLinkRequest, -) -from core.emulator.data import IpPrefixes -from core.errors import CoreError - -logger = logging.getLogger(__name__) - - -class MoveNodesStreamer: - def __init__(self, session_id: int = None, source: str = None) -> None: - self.session_id = session_id - self.source = source - self.queue: Queue = Queue() - - def send_position(self, node_id: int, x: float, y: float) -> None: - position = wrappers.Position(x=x, y=y) - request = wrappers.MoveNodesRequest( - session_id=self.session_id, - node_id=node_id, - source=self.source, - position=position, - ) - self.send(request) - - def send_geo(self, node_id: int, lon: float, lat: float, alt: float) -> None: - geo = wrappers.Geo(lon=lon, lat=lat, alt=alt) - request = wrappers.MoveNodesRequest( - session_id=self.session_id, node_id=node_id, source=self.source, geo=geo - ) - self.send(request) - - def send(self, request: wrappers.MoveNodesRequest) -> None: - self.queue.put(request) - - def stop(self) -> None: - self.queue.put(None) - - def next(self) -> Optional[core_pb2.MoveNodesRequest]: - request: Optional[wrappers.MoveNodesRequest] = self.queue.get() - if request: - return request.to_proto() - else: - return request - - def iter(self) -> Iterable: - return iter(self.next, None) - - -class EmanePathlossesStreamer: - def __init__(self) -> None: - self.queue: Queue = Queue() - - def send(self, request: Optional[wrappers.EmanePathlossesRequest]) -> None: - self.queue.put(request) - - def next(self) -> Optional[emane_pb2.EmanePathlossesRequest]: - request: Optional[wrappers.EmanePathlossesRequest] = self.queue.get() - if request: - return request.to_proto() - else: - return request - - def iter(self): - return iter(self.next, None) - - -class InterfaceHelper: - """ - Convenience class to help generate IP4 and IP6 addresses for gRPC clients. - """ - - def __init__(self, ip4_prefix: str = None, ip6_prefix: str = None) -> None: - """ - Creates an InterfaceHelper 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 - """ - self.prefixes: IpPrefixes = IpPrefixes(ip4_prefix, ip6_prefix) - - def create_iface( - self, node_id: int, iface_id: int, name: str = None, mac: str = None - ) -> wrappers.Interface: - """ - Create an interface protobuf object. - - :param node_id: node id to create interface for - :param iface_id: interface id - :param name: name of interface - :param mac: mac address for interface - :return: interface protobuf - """ - iface_data = self.prefixes.gen_iface(node_id, name, mac) - return wrappers.Interface( - id=iface_id, - name=iface_data.name, - ip4=iface_data.ip4, - ip4_mask=iface_data.ip4_mask, - ip6=iface_data.ip6, - ip6_mask=iface_data.ip6_mask, - mac=iface_data.mac, - ) - - -def throughput_listener( - stream: Any, handler: Callable[[wrappers.ThroughputsEvent], None] -) -> None: - """ - Listen for throughput events and provide them to the handler. - - :param stream: grpc stream that will provide events - :param handler: function that handles an event - :return: nothing - """ - try: - for event_proto in stream: - event = wrappers.ThroughputsEvent.from_proto(event_proto) - handler(event) - except grpc.RpcError as e: - if e.code() == grpc.StatusCode.CANCELLED: - logger.debug("throughput stream closed") - else: - logger.exception("throughput stream error") - - -def cpu_listener( - stream: Any, handler: Callable[[wrappers.CpuUsageEvent], None] -) -> None: - """ - Listen for cpu events and provide them to the handler. - - :param stream: grpc stream that will provide events - :param handler: function that handles an event - :return: nothing - """ - try: - for event_proto in stream: - event = wrappers.CpuUsageEvent.from_proto(event_proto) - handler(event) - except grpc.RpcError as e: - if e.code() == grpc.StatusCode.CANCELLED: - logger.debug("cpu stream closed") - else: - logger.exception("cpu stream error") - - -def event_listener(stream: Any, handler: Callable[[wrappers.Event], None]) -> None: - """ - Listen for session events and provide them to the handler. - - :param stream: grpc stream that will provide events - :param handler: function that handles an event - :return: nothing - """ - try: - for event_proto in stream: - event = wrappers.Event.from_proto(event_proto) - handler(event) - except grpc.RpcError as e: - if e.code() == grpc.StatusCode.CANCELLED: - logger.debug("session stream closed") - else: - logger.exception("session stream error") - - -class CoreGrpcClient: - """ - Provides convenience methods for interfacing with the CORE grpc server. - """ - - def __init__(self, address: str = "localhost:50051", proxy: bool = False) -> None: - """ - Creates a CoreGrpcClient instance. - - :param address: grpc server address to connect to - """ - 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, - session: wrappers.Session, - asymmetric_links: List[wrappers.Link] = None, - definition: bool = False, - ) -> Tuple[bool, List[str]]: - """ - Start a session. - - :param session: session to start - :param asymmetric_links: link configuration for asymmetric links - :param definition: True to only define session data, False to start session - :return: tuple of result and exception strings - """ - nodes = [x.to_proto() for x in session.nodes.values()] - links = [x.to_proto() for x in session.links] - if asymmetric_links: - asymmetric_links = [x.to_proto() for x in asymmetric_links] - hooks = [x.to_proto() for x in session.hooks.values()] - emane_config = {k: v.value for k, v in session.emane_config.items()} - emane_model_configs = [] - mobility_configs = [] - wlan_configs = [] - service_configs = [] - service_file_configs = [] - config_service_configs = [] - for node in session.nodes.values(): - for key, config in node.emane_model_configs.items(): - model, iface_id = key - config = wrappers.ConfigOption.to_dict(config) - if iface_id is None: - iface_id = -1 - emane_model_config = emane_pb2.EmaneModelConfig( - node_id=node.id, iface_id=iface_id, model=model, config=config - ) - emane_model_configs.append(emane_model_config) - if node.wlan_config: - config = wrappers.ConfigOption.to_dict(node.wlan_config) - wlan_config = wlan_pb2.WlanConfig(node_id=node.id, config=config) - wlan_configs.append(wlan_config) - if node.mobility_config: - config = wrappers.ConfigOption.to_dict(node.mobility_config) - mobility_config = mobility_pb2.MobilityConfig( - node_id=node.id, config=config - ) - mobility_configs.append(mobility_config) - for name, config in node.service_configs.items(): - service_config = services_pb2.ServiceConfig( - node_id=node.id, - service=name, - directories=config.dirs, - files=config.configs, - startup=config.startup, - validate=config.validate, - shutdown=config.shutdown, - ) - service_configs.append(service_config) - for service, file_configs in node.service_file_configs.items(): - for file, data in file_configs.items(): - service_file_config = services_pb2.ServiceFileConfig( - node_id=node.id, service=service, file=file, data=data - ) - service_file_configs.append(service_file_config) - for name, service_config in node.config_service_configs.items(): - config_service_config = configservices_pb2.ConfigServiceConfig( - node_id=node.id, - name=name, - templates=service_config.templates, - config=service_config.config, - ) - config_service_configs.append(config_service_config) - options = {k: v.value for k, v in session.options.items()} - request = core_pb2.StartSessionRequest( - session_id=session.id, - nodes=nodes, - links=links, - location=session.location.to_proto(), - hooks=hooks, - emane_config=emane_config, - emane_model_configs=emane_model_configs, - wlan_configs=wlan_configs, - mobility_configs=mobility_configs, - service_configs=service_configs, - service_file_configs=service_file_configs, - asymmetric_links=asymmetric_links, - config_service_configs=config_service_configs, - options=options, - user=session.user, - definition=definition, - metadata=session.metadata, - ) - response = self.stub.StartSession(request) - return response.result, list(response.exceptions) - - def stop_session(self, session_id: int) -> bool: - """ - Stop a running session. - - :param session_id: id of session - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.StopSessionRequest(session_id=session_id) - response = self.stub.StopSession(request) - return response.result - - def add_session(self, session_id: int = None) -> wrappers.Session: - session_id = self.create_session(session_id) - return self.get_session(session_id) - - def create_session(self, session_id: int = None) -> int: - """ - Create a session. - - :param session_id: id for session, default is None and one will be created - for you - :return: session id - """ - request = core_pb2.CreateSessionRequest(session_id=session_id) - response = self.stub.CreateSession(request) - return response.session_id - - def delete_session(self, session_id: int) -> bool: - """ - Delete a session. - - :param session_id: id of session - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.DeleteSessionRequest(session_id=session_id) - response = self.stub.DeleteSession(request) - return response.result - - def get_sessions(self) -> List[wrappers.SessionSummary]: - """ - Retrieves all currently known sessions. - - :return: response with a list of currently known session, their state and - number of nodes - """ - response = self.stub.GetSessions(core_pb2.GetSessionsRequest()) - sessions = [] - for session_proto in response.sessions: - session = wrappers.SessionSummary.from_proto(session_proto) - sessions.append(session) - return sessions - - def check_session(self, session_id: int) -> bool: - """ - Check if a session exists. - - :param session_id: id of session to check for - :return: True if exists, False otherwise - """ - request = core_pb2.CheckSessionRequest(session_id=session_id) - response = self.stub.CheckSession(request) - return response.result - - def get_session(self, session_id: int) -> wrappers.Session: - """ - Retrieve a session. - - :param session_id: id of session - :return: session - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.GetSessionRequest(session_id=session_id) - response = self.stub.GetSession(request) - return wrappers.Session.from_proto(response.session) - - def set_session_state(self, session_id: int, state: wrappers.SessionState) -> bool: - """ - Set session state. - - :param session_id: id of session - :param state: session state to transition to - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.SetSessionStateRequest( - session_id=session_id, state=state.value - ) - response = self.stub.SetSessionState(request) - return response.result - - def add_session_server(self, session_id: int, name: str, host: str) -> bool: - """ - Add distributed session server. - - :param session_id: id of session - :param name: name of server to add - :param host: host address to connect to - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.AddSessionServerRequest( - session_id=session_id, name=name, host=host - ) - response = self.stub.AddSessionServer(request) - return response.result - - def alert( - self, - session_id: int, - level: wrappers.ExceptionLevel, - source: str, - text: str, - node_id: int = None, - ) -> bool: - """ - Initiate an alert to be broadcast out to all listeners. - - :param session_id: id of session - :param level: alert level - :param source: source of alert - :param text: alert text - :param node_id: node associated with alert - :return: True for success, False otherwise - """ - request = core_pb2.SessionAlertRequest( - session_id=session_id, - level=level.value, - source=source, - text=text, - node_id=node_id, - ) - response = self.stub.SessionAlert(request) - return response.result - - def events( - self, - session_id: int, - handler: Callable[[wrappers.Event], None], - events: List[wrappers.EventType] = None, - ) -> grpc.Future: - """ - Listen for session events. - - :param session_id: id of session - :param handler: handler for received events - :param events: events to listen to, defaults to all - :return: stream processing events, can be used to cancel stream - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.EventsRequest(session_id=session_id, events=events) - stream = self.stub.Events(request) - thread = threading.Thread( - target=event_listener, args=(stream, handler), daemon=True - ) - thread.start() - return stream - - def throughputs( - self, session_id: int, handler: Callable[[wrappers.ThroughputsEvent], None] - ) -> grpc.Future: - """ - Listen for throughput events with information for interfaces and bridges. - - :param session_id: session id - :param handler: handler for every event - :return: stream processing events, can be used to cancel stream - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.ThroughputsRequest(session_id=session_id) - stream = self.stub.Throughputs(request) - thread = threading.Thread( - target=throughput_listener, args=(stream, handler), daemon=True - ) - thread.start() - return stream - - def cpu_usage( - self, delay: int, handler: Callable[[wrappers.CpuUsageEvent], None] - ) -> grpc.Future: - """ - Listen for cpu usage events with the given repeat delay. - - :param delay: delay between receiving events - :param handler: handler for every event - :return: stream processing events, can be used to cancel stream - """ - request = core_pb2.CpuUsageRequest(delay=delay) - stream = self.stub.CpuUsage(request) - thread = threading.Thread( - target=cpu_listener, args=(stream, handler), daemon=True - ) - thread.start() - return stream - - def add_node(self, session_id: int, node: wrappers.Node, source: str = None) -> int: - """ - Add node to session. - - :param session_id: session id - :param node: node to add - :param source: source application - :return: id of added node - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.AddNodeRequest( - session_id=session_id, node=node.to_proto(), source=source - ) - response = self.stub.AddNode(request) - return response.node_id - - def get_node( - self, session_id: int, node_id: int - ) -> Tuple[wrappers.Node, List[wrappers.Interface], List[wrappers.Link]]: - """ - Get node details. - - :param session_id: session id - :param node_id: node id - :return: tuple of node and its interfaces - :raises grpc.RpcError: when session or node doesn't exist - """ - request = core_pb2.GetNodeRequest(session_id=session_id, node_id=node_id) - response = self.stub.GetNode(request) - node = wrappers.Node.from_proto(response.node) - ifaces = [] - for iface_proto in response.ifaces: - iface = wrappers.Interface.from_proto(iface_proto) - ifaces.append(iface) - links = [] - for link_proto in response.links: - link = wrappers.Link.from_proto(link_proto) - links.append(link) - return node, ifaces, links - - def edit_node( - self, - session_id: int, - node_id: int, - position: wrappers.Position = None, - icon: str = None, - geo: wrappers.Geo = None, - source: str = None, - ) -> bool: - """ - Edit a node's icon and/or location, can only use position(x,y) or - geo(lon, lat, alt), not both. - - :param session_id: session id - :param node_id: node id - :param position: x,y location for node - :param icon: path to icon for gui to use for node - :param geo: lon,lat,alt location for node - :param source: application source - :return: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - if position and geo: - raise CoreError("cannot edit position and geo at same time") - position_proto = position.to_proto() if position else None - geo_proto = geo.to_proto() if geo else None - request = core_pb2.EditNodeRequest( - session_id=session_id, - node_id=node_id, - position=position_proto, - icon=icon, - source=source, - geo=geo_proto, - ) - response = self.stub.EditNode(request) - return response.result - - def move_nodes(self, streamer: MoveNodesStreamer) -> None: - """ - Stream node movements using the provided iterator. - - :param streamer: move nodes streamer - :return: nothing - :raises grpc.RpcError: when session or nodes do not exist - """ - self.stub.MoveNodes(streamer.iter()) - - def delete_node(self, session_id: int, node_id: int, source: str = None) -> bool: - """ - Delete node from session. - - :param session_id: session id - :param node_id: node id - :param source: application source - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.DeleteNodeRequest( - session_id=session_id, node_id=node_id, source=source - ) - response = self.stub.DeleteNode(request) - return response.result - - def node_command( - self, - session_id: int, - node_id: int, - command: str, - wait: bool = True, - shell: bool = False, - ) -> Tuple[int, str]: - """ - Send command to a node and get the output. - - :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: returns tuple of return code and output - :raises grpc.RpcError: when session or node doesn't exist - """ - request = core_pb2.NodeCommandRequest( - session_id=session_id, - node_id=node_id, - command=command, - wait=wait, - shell=shell, - ) - response = self.stub.NodeCommand(request) - return response.return_code, response.output - - def get_node_terminal(self, session_id: int, node_id: int) -> str: - """ - Retrieve terminal command string for launching a local terminal. - - :param session_id: session id - :param node_id: node id - :return: node terminal - :raises grpc.RpcError: when session or node doesn't exist - """ - request = core_pb2.GetNodeTerminalRequest( - session_id=session_id, node_id=node_id - ) - response = self.stub.GetNodeTerminal(request) - return response.terminal - - def get_node_links(self, session_id: int, node_id: int) -> List[wrappers.Link]: - """ - Get current links for a node. - - :param session_id: session id - :param node_id: node id - :return: list of links - :raises grpc.RpcError: when session or node doesn't exist - """ - request = core_pb2.GetNodeLinksRequest(session_id=session_id, node_id=node_id) - response = self.stub.GetNodeLinks(request) - links = [] - for link_proto in response.links: - link = wrappers.Link.from_proto(link_proto) - links.append(link) - return links - - def add_link( - self, session_id: int, link: wrappers.Link, source: str = None - ) -> Tuple[bool, wrappers.Interface, wrappers.Interface]: - """ - Add a link between nodes. - - :param session_id: session id - :param link: link to add - :param source: application source - :return: tuple of result and finalized interface values - :raises grpc.RpcError: when session or one of the nodes don't exist - """ - request = core_pb2.AddLinkRequest( - session_id=session_id, link=link.to_proto(), source=source - ) - response = self.stub.AddLink(request) - iface1 = wrappers.Interface.from_proto(response.iface1) - iface2 = wrappers.Interface.from_proto(response.iface2) - return response.result, iface1, iface2 - - def edit_link( - self, session_id: int, link: wrappers.Link, source: str = None - ) -> bool: - """ - Edit a link between nodes. - - :param session_id: session id - :param link: link to edit - :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 - """ - iface1_id = link.iface1.id if link.iface1 else None - iface2_id = link.iface2.id if link.iface2 else None - request = core_pb2.EditLinkRequest( - session_id=session_id, - node1_id=link.node1_id, - node2_id=link.node2_id, - options=link.options.to_proto(), - iface1_id=iface1_id, - iface2_id=iface2_id, - source=source, - ) - response = self.stub.EditLink(request) - return response.result - - def delete_link( - self, session_id: int, link: wrappers.Link, source: str = None - ) -> bool: - """ - Delete a link between nodes. - - :param session_id: session id - :param link: link to delete - :param source: application source - :return: response with result of success or failure - :raises grpc.RpcError: when session doesn't exist - """ - iface1_id = link.iface1.id if link.iface1 else None - iface2_id = link.iface2.id if link.iface2 else None - request = core_pb2.DeleteLinkRequest( - session_id=session_id, - node1_id=link.node1_id, - node2_id=link.node2_id, - iface1_id=iface1_id, - iface2_id=iface2_id, - source=source, - ) - response = self.stub.DeleteLink(request) - return response.result - - def get_mobility_configs( - self, session_id: int - ) -> Dict[int, Dict[str, wrappers.ConfigOption]]: - """ - Get all mobility configurations. - - :param session_id: session id - :return: dict of node id to mobility configuration dict - :raises grpc.RpcError: when session doesn't exist - """ - request = GetMobilityConfigsRequest(session_id=session_id) - response = self.stub.GetMobilityConfigs(request) - configs = {} - for node_id, mapped_config in response.configs.items(): - configs[node_id] = wrappers.ConfigOption.from_dict(mapped_config.config) - return configs - - def get_mobility_config( - self, session_id: int, node_id: int - ) -> Dict[str, wrappers.ConfigOption]: - """ - Get mobility configuration for a node. - - :param session_id: session id - :param node_id: node id - :return: dict of config name to options - :raises grpc.RpcError: when session or node doesn't exist - """ - request = GetMobilityConfigRequest(session_id=session_id, node_id=node_id) - response = self.stub.GetMobilityConfig(request) - return wrappers.ConfigOption.from_dict(response.config) - - def set_mobility_config( - self, session_id: int, node_id: int, config: Dict[str, str] - ) -> bool: - """ - Set mobility configuration for a node. - - :param session_id: session id - :param node_id: node id - :param config: mobility configuration - :return: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - mobility_config = MobilityConfig(node_id=node_id, config=config) - request = SetMobilityConfigRequest( - session_id=session_id, mobility_config=mobility_config - ) - response = self.stub.SetMobilityConfig(request) - return response.result - - def mobility_action( - self, session_id: int, node_id: int, action: wrappers.MobilityAction - ) -> bool: - """ - Send a mobility action for a node. - - :param session_id: session id - :param node_id: node id - :param action: action to take - :return: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - request = MobilityActionRequest( - session_id=session_id, node_id=node_id, action=action.value - ) - response = self.stub.MobilityAction(request) - return response.result - - def get_services(self) -> List[wrappers.Service]: - """ - Get all currently loaded services. - - :return: list of services, name and groups only - """ - request = GetServicesRequest() - response = self.stub.GetServices(request) - services = [] - for service_proto in response.services: - service = wrappers.Service.from_proto(service_proto) - services.append(service) - return services - - def get_service_defaults(self, session_id: int) -> List[wrappers.ServiceDefault]: - """ - Get default services for different default node models. - - :param session_id: session id - :return: list of service defaults - :raises grpc.RpcError: when session doesn't exist - """ - request = GetServiceDefaultsRequest(session_id=session_id) - response = self.stub.GetServiceDefaults(request) - defaults = [] - for default_proto in response.defaults: - default = wrappers.ServiceDefault.from_proto(default_proto) - defaults.append(default) - return defaults - - def set_service_defaults( - self, session_id: int, service_defaults: Dict[str, List[str]] - ) -> bool: - """ - Set default services for node models. - - :param session_id: session id - :param service_defaults: node models to lists of services - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - defaults = [] - for node_type in service_defaults: - services = service_defaults[node_type] - default = ServiceDefaults(node_type=node_type, services=services) - defaults.append(default) - request = SetServiceDefaultsRequest(session_id=session_id, defaults=defaults) - response = self.stub.SetServiceDefaults(request) - return response.result - - def get_node_service_configs( - self, session_id: int - ) -> List[wrappers.NodeServiceConfig]: - """ - Get service data for a node. - - :param session_id: session id - :return: list of node service data - :raises grpc.RpcError: when session doesn't exist - """ - request = GetNodeServiceConfigsRequest(session_id=session_id) - response = self.stub.GetNodeServiceConfigs(request) - node_services = [] - for config in response.configs: - node_service = wrappers.NodeServiceConfig.from_proto(config) - node_services.append(node_service) - return node_services - - def get_node_service( - self, session_id: int, node_id: int, service: str - ) -> wrappers.NodeServiceData: - """ - Get service data for a node. - - :param session_id: session id - :param node_id: node id - :param service: service name - :return: node service data - :raises grpc.RpcError: when session or node doesn't exist - """ - request = GetNodeServiceRequest( - session_id=session_id, node_id=node_id, service=service - ) - response = self.stub.GetNodeService(request) - return wrappers.NodeServiceData.from_proto(response.service) - - def get_node_service_file( - self, session_id: int, node_id: int, service: str, file_name: str - ) -> str: - """ - Get a service file for a node. - - :param session_id: session id - :param node_id: node id - :param service: service name - :param file_name: file name to get data for - :return: file data - :raises grpc.RpcError: when session or node doesn't exist - """ - request = GetNodeServiceFileRequest( - session_id=session_id, node_id=node_id, service=service, file=file_name - ) - response = self.stub.GetNodeServiceFile(request) - return response.data - - def set_node_service( - self, session_id: int, service_config: wrappers.ServiceConfig - ) -> bool: - """ - Set service data for a node. - - :param session_id: session id - :param service_config: service configuration for a node - :return: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - request = SetNodeServiceRequest( - session_id=session_id, config=service_config.to_proto() - ) - response = self.stub.SetNodeService(request) - return response.result - - def set_node_service_file( - self, session_id: int, service_file_config: wrappers.ServiceFileConfig - ) -> bool: - """ - Set a service file for a node. - - :param session_id: session id - :param service_file_config: configuration to set - :return: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - config = service_file_config.to_proto() - request = SetNodeServiceFileRequest(session_id=session_id, config=config) - response = self.stub.SetNodeServiceFile(request) - return response.result - - def service_action( - self, - session_id: int, - node_id: int, - service: str, - action: wrappers.ServiceAction, - ) -> bool: - """ - Send an action to a service for a node. - - :param session_id: session id - :param node_id: node id - :param service: service name - :param action: action for service (start, stop, restart, - validate) - :return: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - request = ServiceActionRequest( - session_id=session_id, node_id=node_id, service=service, action=action.value - ) - response = self.stub.ServiceAction(request) - return response.result - - def get_wlan_configs( - self, session_id: int - ) -> Dict[int, Dict[str, wrappers.ConfigOption]]: - """ - Get all wlan configurations. - - :param session_id: session id - :return: dict of node ids to dict of names to options - :raises grpc.RpcError: when session doesn't exist - """ - request = GetWlanConfigsRequest(session_id=session_id) - response = self.stub.GetWlanConfigs(request) - configs = {} - for node_id, mapped_config in response.configs.items(): - configs[node_id] = wrappers.ConfigOption.from_dict(mapped_config.config) - return configs - - def get_wlan_config( - self, session_id: int, node_id: int - ) -> Dict[str, wrappers.ConfigOption]: - """ - Get wlan configuration for a node. - - :param session_id: session id - :param node_id: node id - :return: dict of names to options - :raises grpc.RpcError: when session doesn't exist - """ - request = GetWlanConfigRequest(session_id=session_id, node_id=node_id) - response = self.stub.GetWlanConfig(request) - return wrappers.ConfigOption.from_dict(response.config) - - def set_wlan_config( - self, session_id: int, node_id: int, config: Dict[str, str] - ) -> bool: - """ - Set wlan configuration for a node. - - :param session_id: session id - :param node_id: node id - :param config: wlan configuration - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - wlan_config = WlanConfig(node_id=node_id, config=config) - request = SetWlanConfigRequest(session_id=session_id, wlan_config=wlan_config) - response = self.stub.SetWlanConfig(request) - return response.result - - def get_emane_config(self, session_id: int) -> Dict[str, wrappers.ConfigOption]: - """ - Get session emane configuration. - - :param session_id: session id - :return: response with a list of configuration groups - :raises grpc.RpcError: when session doesn't exist - """ - request = GetEmaneConfigRequest(session_id=session_id) - response = self.stub.GetEmaneConfig(request) - return wrappers.ConfigOption.from_dict(response.config) - - def set_emane_config(self, session_id: int, config: Dict[str, str]) -> bool: - """ - Set session emane configuration. - - :param session_id: session id - :param config: emane configuration - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = SetEmaneConfigRequest(session_id=session_id, config=config) - response = self.stub.SetEmaneConfig(request) - return response.result - - def get_emane_models(self, session_id: int) -> List[str]: - """ - Get session emane models. - - :param session_id: session id - :return: list of emane models - :raises grpc.RpcError: when session doesn't exist - """ - request = GetEmaneModelsRequest(session_id=session_id) - response = self.stub.GetEmaneModels(request) - return list(response.models) - - def get_emane_model_config( - self, session_id: int, node_id: int, model: str, iface_id: int = -1 - ) -> Dict[str, wrappers.ConfigOption]: - """ - Get emane model configuration for a node or a node's interface. - - :param session_id: session id - :param node_id: node id - :param model: emane model name - :param iface_id: node interface id - :return: dict of names to options - :raises grpc.RpcError: when session doesn't exist - """ - request = GetEmaneModelConfigRequest( - session_id=session_id, node_id=node_id, model=model, iface_id=iface_id - ) - response = self.stub.GetEmaneModelConfig(request) - return wrappers.ConfigOption.from_dict(response.config) - - def set_emane_model_config( - self, session_id: int, emane_model_config: wrappers.EmaneModelConfig - ) -> bool: - """ - Set emane model configuration for a node or a node's interface. - - :param session_id: session id - :param emane_model_config: emane model config to set - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = SetEmaneModelConfigRequest( - session_id=session_id, emane_model_config=emane_model_config.to_proto() - ) - response = self.stub.SetEmaneModelConfig(request) - return response.result - - def get_emane_model_configs( - self, session_id: int - ) -> List[wrappers.EmaneModelConfig]: - """ - Get all EMANE model configurations for a session. - - :param session_id: session to get emane model configs - :return: list of emane model configs - :raises grpc.RpcError: when session doesn't exist - """ - request = GetEmaneModelConfigsRequest(session_id=session_id) - response = self.stub.GetEmaneModelConfigs(request) - configs = [] - for config_proto in response.configs: - config = wrappers.EmaneModelConfig.from_proto(config_proto) - configs.append(config) - return configs - - def save_xml(self, session_id: int, file_path: str) -> None: - """ - Save the current scenario to an XML file. - - :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) - with open(file_path, "w") as xml_file: - xml_file.write(response.data) - - def open_xml(self, file_path: Path, start: bool = False) -> Tuple[bool, int]: - """ - Load a local scenario XML file to open as a new session. - - :param file_path: path of scenario XML file - :param start: tuple of result and session id when successful - :return: tuple of result and session id - """ - with file_path.open("r") as f: - data = f.read() - request = core_pb2.OpenXmlRequest(data=data, start=start, file=str(file_path)) - response = self.stub.OpenXml(request) - return response.result, response.session_id - - def emane_link(self, session_id: int, nem1: int, nem2: int, linked: bool) -> bool: - """ - Helps broadcast wireless link/unlink between EMANE nodes. - - :param session_id: session to 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: True for success, False otherwise - :raises grpc.RpcError: when session or nodes related to nems do not exist - """ - request = EmaneLinkRequest( - session_id=session_id, nem1=nem1, nem2=nem2, linked=linked - ) - response = self.stub.EmaneLink(request) - return response.result - - def get_ifaces(self) -> List[str]: - """ - Retrieves a list of interfaces available on the host machine that are not - a part of a CORE session. - - :return: list of interfaces - """ - request = core_pb2.GetInterfacesRequest() - response = self.stub.GetInterfaces(request) - return list(response.ifaces) - - def get_config_services(self) -> List[wrappers.ConfigService]: - """ - Retrieve all known config services. - - :return: list of config services - """ - request = GetConfigServicesRequest() - response = self.stub.GetConfigServices(request) - services = [] - for service_proto in response.services: - service = wrappers.ConfigService.from_proto(service_proto) - services.append(service) - return services - - def get_config_service_defaults(self, name: str) -> wrappers.ConfigServiceDefaults: - """ - Retrieves config service default values. - - :param name: name of service to get defaults for - :return: config service defaults - """ - request = GetConfigServiceDefaultsRequest(name=name) - response = self.stub.GetConfigServiceDefaults(request) - return wrappers.ConfigServiceDefaults.from_proto(response) - - def get_node_config_service_configs( - self, session_id: int - ) -> List[wrappers.ConfigServiceConfig]: - """ - Retrieves all node config service configurations for a session. - - :param session_id: session to get config service configurations for - :return: list of node config service configs - :raises grpc.RpcError: when session doesn't exist - """ - request = GetNodeConfigServiceConfigsRequest(session_id=session_id) - response = self.stub.GetNodeConfigServiceConfigs(request) - configs = [] - for config_proto in response.configs: - config = wrappers.ConfigServiceConfig.from_proto(config_proto) - configs.append(config) - return configs - - def get_node_config_service( - self, session_id: int, node_id: int, name: str - ) -> Dict[str, str]: - """ - 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: config dict of names to values - :raises grpc.RpcError: when session or node doesn't exist - """ - request = GetNodeConfigServiceRequest( - session_id=session_id, node_id=node_id, name=name - ) - response = self.stub.GetNodeConfigService(request) - return dict(response.config) - - def get_node_config_services(self, session_id: int, node_id: int) -> List[str]: - """ - 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: list of config services - :raises grpc.RpcError: when session or node doesn't exist - """ - request = GetNodeConfigServicesRequest(session_id=session_id, node_id=node_id) - response = self.stub.GetNodeConfigServices(request) - return list(response.services) - - def set_node_config_service( - self, session_id: int, node_id: int, name: str, config: Dict[str, str] - ) -> bool: - """ - 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: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - request = SetNodeConfigServiceRequest( - session_id=session_id, node_id=node_id, name=name, config=config - ) - response = self.stub.SetNodeConfigService(request) - return response.result - - def get_emane_event_channel(self, session_id: int) -> wrappers.EmaneEventChannel: - """ - 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 - :raises grpc.RpcError: when session doesn't exist - """ - request = GetEmaneEventChannelRequest(session_id=session_id) - response = self.stub.GetEmaneEventChannel(request) - return wrappers.EmaneEventChannel.from_proto(response) - - def execute_script(self, script: str) -> Optional[int]: - """ - Executes a python script given context of the current CoreEmu object. - - :param script: script to execute - :return: create session id for script executed - """ - request = ExecuteScriptRequest(script=script) - response = self.stub.ExecuteScript(request) - return response.session_id if response.session_id else None - - def wlan_link( - self, session_id: int, wlan_id: int, node1_id: int, node2_id: int, linked: bool - ) -> bool: - """ - Links/unlinks nodes on the same WLAN. - - :param session_id: session id containing wlan and nodes - :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: True for success, False otherwise - :raises grpc.RpcError: when session or one of the nodes do not exist - """ - request = WlanLinkRequest( - session_id=session_id, - wlan=wlan_id, - node1_id=node1_id, - node2_id=node2_id, - linked=linked, - ) - response = self.stub.WlanLink(request) - return response.result - - def emane_pathlosses(self, streamer: EmanePathlossesStreamer) -> None: - """ - Stream EMANE pathloss events. - - :param streamer: emane pathlosses streamer - :return: nothing - :raises grpc.RpcError: when a pathloss event session or one of the nodes do not - exist - """ - self.stub.EmanePathlosses(streamer.iter()) - - def connect(self) -> None: - """ - Open connection to server, must be closed manually. - - :return: nothing - """ - self.channel = grpc.insecure_channel( - self.address, options=[("grpc.enable_http_proxy", self.proxy)] - ) - self.stub = core_pb2_grpc.CoreApiStub(self.channel) - - def close(self) -> None: - """ - Close currently opened server channel connection. - - :return: nothing - """ - if self.channel: - self.channel.close() - self.channel = None - - @contextmanager - def context_connect(self) -> Generator: - """ - Makes a context manager based connection to the server, will close after - context ends. - - :return: nothing - """ - try: - self.connect() - yield - finally: - self.close() diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index a9396517..2cdf31d0 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -12,7 +12,7 @@ from typing import TYPE_CHECKING, Dict, Iterable, List, Optional, Set, Tuple import grpc -from core.api.grpc import clientw, configservices_pb2, core_pb2 +from core.api.grpc import client, configservices_pb2, core_pb2 from core.api.grpc.wrappers import ( ConfigOption, ConfigService, @@ -66,7 +66,7 @@ class CoreClient: """ self.app: "Application" = app self.master: tk.Tk = app.master - self._client: clientw.CoreGrpcClient = clientw.CoreGrpcClient(proxy=proxy) + self._client: client.CoreGrpcClient = client.CoreGrpcClient(proxy=proxy) self.session: Optional[Session] = None self.user = getpass.getuser() @@ -95,7 +95,7 @@ class CoreClient: self.handling_events: Optional[grpc.Future] = None @property - def client(self) -> clientw.CoreGrpcClient: + def client(self) -> client.CoreGrpcClient: if self.session: if not self._client.check_session(self.session.id): throughputs_enabled = self.handling_throughputs is not None diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index f9534b41..6503abbf 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -1,7 +1,7 @@ import argparse import logging -from core.api.grpc import clientw +from core.api.grpc import client from core.api.grpc.wrappers import NodeType, Position @@ -11,10 +11,10 @@ def log_event(event): def main(args): # helper to create interfaces - interface_helper = clientw.InterfaceHelper(ip4_prefix="10.83.0.0/16") + interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") # create grpc client and connect - core = clientw.CoreGrpcClient() + core = client.CoreGrpcClient() core.connect() # create session diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py index c6fba6c8..edb1f529 100644 --- a/daemon/examples/grpc/emane80211.py +++ b/daemon/examples/grpc/emane80211.py @@ -1,13 +1,13 @@ # required imports -from core.api.grpc import clientw +from core.api.grpc import client from core.api.grpc.wrappers import NodeType, Position from core.emane.ieee80211abg import EmaneIeee80211abgModel # interface helper -iface_helper = clientw.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") +iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") # create grpc client and connect -core = clientw.CoreGrpcClient() +core = client.CoreGrpcClient() core.connect() # add session diff --git a/daemon/examples/grpc/peertopeer.py b/daemon/examples/grpc/peertopeer.py index 8c1b47a1..730128eb 100644 --- a/daemon/examples/grpc/peertopeer.py +++ b/daemon/examples/grpc/peertopeer.py @@ -1,11 +1,11 @@ -from core.api.grpc import clientw +from core.api.grpc import client from core.api.grpc.wrappers import Position # interface helper -iface_helper = clientw.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") +iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") # create grpc client and connect -core = clientw.CoreGrpcClient() +core = client.CoreGrpcClient() core.connect() # add session diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 0a5be8a1..ce8e2622 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -1,11 +1,11 @@ -from core.api.grpc import clientw +from core.api.grpc import client from core.api.grpc.wrappers import NodeType, Position # interface helper -iface_helper = clientw.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") +iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") # create grpc client and connect -core = clientw.CoreGrpcClient() +core = client.CoreGrpcClient() core.connect() # add session diff --git a/daemon/examples/grpc/wlan.py b/daemon/examples/grpc/wlan.py index 86a3856b..561a772b 100644 --- a/daemon/examples/grpc/wlan.py +++ b/daemon/examples/grpc/wlan.py @@ -1,11 +1,11 @@ -from core.api.grpc import clientw +from core.api.grpc import client from core.api.grpc.wrappers import NodeType, Position # interface helper -iface_helper = clientw.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") +iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") # create grpc client and connect -core = clientw.CoreGrpcClient() +core = client.CoreGrpcClient() core.connect() # add session diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 41083f94..30041188 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -15,7 +15,7 @@ import grpc import netaddr from netaddr import EUI, AddrFormatError, IPNetwork -from core.api.grpc.clientw import CoreGrpcClient +from core.api.grpc.client import CoreGrpcClient from core.api.grpc.wrappers import ( Geo, Interface, diff --git a/daemon/scripts/core-imn-to-xml b/daemon/scripts/core-imn-to-xml index 9575fd75..c11533a4 100755 --- a/daemon/scripts/core-imn-to-xml +++ b/daemon/scripts/core-imn-to-xml @@ -5,7 +5,7 @@ import sys from pathlib import Path from core import utils -from core.api.grpc.clientw import CoreGrpcClient +from core.api.grpc.client import CoreGrpcClient from core.errors import CoreCommandError if __name__ == "__main__": diff --git a/daemon/scripts/core-route-monitor b/daemon/scripts/core-route-monitor index 2a18ea9f..bc61f6fa 100755 --- a/daemon/scripts/core-route-monitor +++ b/daemon/scripts/core-route-monitor @@ -15,7 +15,7 @@ from typing import Dict, Tuple import grpc from core import utils -from core.api.grpc.clientw import CoreGrpcClient +from core.api.grpc.client import CoreGrpcClient from core.api.grpc.wrappers import NodeType SDT_HOST = "127.0.0.1" diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py deleted file mode 100644 index 14e971d3..00000000 --- a/daemon/tests/test_grpc.py +++ /dev/null @@ -1,1054 +0,0 @@ -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 core_pb2 -from core.api.grpc.client import CoreGrpcClient, InterfaceHelper -from core.api.grpc.emane_pb2 import EmaneModelConfig -from core.api.grpc.mobility_pb2 import MobilityAction, MobilityConfig -from core.api.grpc.server import CoreGrpcServer -from core.api.grpc.services_pb2 import ServiceAction, ServiceConfig, ServiceFileConfig -from core.api.grpc.wlan_pb2 import WlanConfig -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, IpPrefixes, NodeData, NodeOptions -from core.emulator.enumerations import EventTypes, ExceptionLevels, NodeTypes -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: - def test_start_session(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - position = core_pb2.Position(x=50, y=100) - node1 = core_pb2.Node(id=1, position=position, model="PC") - position = core_pb2.Position(x=100, y=100) - 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 = [node1, node2, wlan_node] - 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, - iface1=iface1, - iface2=iface2, - ) - links = [link] - hook = core_pb2.Hook( - state=core_pb2.SessionState.RUNTIME, file="echo.sh", data="echo hello" - ) - hooks = [hook] - location_x = 5 - location_y = 10 - location_z = 15 - location_lat = 20 - location_lon = 30 - location_alt = 40 - location_scale = 5 - location = core_pb2.SessionLocation( - x=location_x, - y=location_y, - z=location_z, - lat=location_lat, - lon=location_lon, - alt=location_alt, - scale=location_scale, - ) - emane_config_key = "platform_id_start" - emane_config_value = "2" - emane_config = {emane_config_key: emane_config_value} - model_node_id = 20 - model_config_key = "bandwidth" - model_config_value = "500000" - model_config = EmaneModelConfig( - node_id=model_node_id, - iface_id=-1, - model=EmaneIeee80211abgModel.name, - config={model_config_key: model_config_value}, - ) - model_configs = [model_config] - wlan_config_key = "range" - wlan_config_value = "333" - wlan_config = WlanConfig( - node_id=wlan_node.id, config={wlan_config_key: wlan_config_value} - ) - wlan_configs = [wlan_config] - mobility_config_key = "refresh_ms" - mobility_config_value = "60" - mobility_config = MobilityConfig( - node_id=wlan_node.id, config={mobility_config_key: mobility_config_value} - ) - mobility_configs = [mobility_config] - service_config = ServiceConfig( - node_id=node1.id, service="DefaultRoute", validate=["echo hello"] - ) - service_configs = [service_config] - service_file_config = ServiceFileConfig( - node_id=node1.id, - service="DefaultRoute", - file="defaultroute.sh", - data="echo hello", - ) - service_file_configs = [service_file_config] - - # when - with patch.object(CoreXmlWriter, "write"): - with client.context_connect(): - client.start_session( - session.id, - nodes, - links, - location, - hooks, - emane_config, - model_configs, - wlan_configs, - mobility_configs, - service_configs, - service_file_configs, - ) - - # then - assert node1.id in session.nodes - assert node2.id in session.nodes - assert wlan_node.id in session.nodes - 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 - assert session.location.refxyz == (location_x, location_y, location_z) - assert session.location.refgeo == (location_lat, location_lon, location_alt) - assert session.location.refscale == location_scale - assert session.emane.get_config(emane_config_key) == emane_config_value - set_wlan_config = session.mobility.get_model_config( - wlan_node.id, BasicRangeModel.name - ) - assert set_wlan_config[wlan_config_key] == wlan_config_value - set_mobility_config = session.mobility.get_model_config( - wlan_node.id, Ns2ScriptedMobility.name - ) - assert set_mobility_config[mobility_config_key] == mobility_config_value - set_model_config = session.emane.get_model_config( - model_node_id, EmaneIeee80211abgModel.name - ) - assert set_model_config[model_config_key] == model_config_value - service = session.services.get_service( - node1.id, service_config.service, default_service=True - ) - assert service.validate == tuple(service_config.validate) - service_file = session.services.get_service_file( - node1, service_file_config.service, service_file_config.file - ) - assert service_file.data == service_file_config.data - - @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(): - response = client.create_session(session_id) - - # then - assert isinstance(response.session_id, int) - assert isinstance(response.state, int) - session = grpc_server.coreemu.sessions.get(response.session_id) - assert session is not None - assert session.state == EventTypes(response.state) - if session_id is not None: - assert response.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(): - response = client.delete_session(session_id) - - # then - assert response.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(): - response = client.get_session(session.id) - - # then - assert response.session.state == core_pb2.SessionState.DEFINITION - assert len(response.session.nodes) == 1 - assert len(response.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(): - response = client.get_sessions() - - # then - found_session = None - for current_session in response.sessions: - if current_session.id == session.id: - found_session = current_session - break - assert len(response.sessions) == 1 - assert found_session is not None - - def test_set_session_state(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - response = client.set_session_state( - session.id, core_pb2.SessionState.DEFINITION - ) - - # then - assert response.result is True - assert session.state == EventTypes.DEFINITION_STATE - - def test_add_node(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - node = core_pb2.Node() - response = client.add_node(session.id, node) - - # then - assert response.node_id is not None - assert session.get_node(response.node_id, CoreNode) is not None - - def test_edit_node(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) - - # then - x, y = 10, 10 - with client.context_connect(): - position = core_pb2.Position(x=x, y=y) - response = client.edit_node(session.id, node.id, position) - - # then - assert response.result is True - assert node.position.x == x - assert node.position.y == y - - @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(): - response = client.delete_node(session.id, node_id) - - # then - assert response.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) - options = NodeOptions(model="Host") - node = session.add_node(CoreNode, options=options) - session.instantiate() - output = "hello world" - - # then - command = f"echo {output}" - with client.context_connect(): - response = client.node_command(session.id, node.id, command) - - # then - assert response.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) - options = NodeOptions(model="Host") - node = session.add_node(CoreNode, options=options) - session.instantiate() - - # then - with client.context_connect(): - response = client.get_node_terminal(session.id, node.id) - - # then - assert response.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 = tmpdir.join("text.xml") - session.save_xml(Path(str(tmp))) - - # then - with client.context_connect(): - response = client.open_xml(str(tmp)) - - # then - assert response.result is True - assert response.session_id is not None - - def test_add_link(self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelper): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - switch = session.add_node(SwitchNode) - node = session.add_node(CoreNode) - assert len(switch.links()) == 0 - - # then - iface = iface_helper.create_iface(node.id, 0) - with client.context_connect(): - response = client.add_link(session.id, node.id, switch.id, iface) - - # then - assert response.result is True - assert len(switch.links()) == 1 - - def test_add_link_exception( - self, grpc_server: CoreGrpcServer, iface_helper: InterfaceHelper - ): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) - - # then - iface = iface_helper.create_iface(node.id, 0) - with pytest.raises(grpc.RpcError): - with client.context_connect(): - client.add_link(session.id, 1, 3, iface) - - def test_edit_link(self, grpc_server: CoreGrpcServer, ip_prefixes: IpPrefixes): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - switch = session.add_node(SwitchNode) - node = session.add_node(CoreNode) - iface = ip_prefixes.create_iface(node) - session.add_link(node.id, switch.id, iface) - options = core_pb2.LinkOptions(bandwidth=30000) - link = switch.links()[0] - assert options.bandwidth != link.options.bandwidth - - # then - with client.context_connect(): - response = client.edit_link( - session.id, node.id, switch.id, options, iface1_id=iface.id - ) - - # then - assert response.result is True - link = switch.links()[0] - assert options.bandwidth == link.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) - link_node = None - for node_id in session.nodes: - node = session.nodes[node_id] - if node.id not in {node1.id, node2.id}: - link_node = node - break - assert len(link_node.links()) == 1 - - # then - with client.context_connect(): - response = client.delete_link( - session.id, node1.id, node2.id, iface1.id, iface2.id - ) - - # then - assert response.result is True - assert len(link_node.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(): - response = client.get_wlan_config(session.id, wlan.id) - - # then - assert len(response.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(): - response = client.set_wlan_config( - session.id, - wlan.id, - { - range_key: range_value, - "delay": "0", - "loss": "0", - "bandwidth": "50000", - "error": "0", - "jitter": "0", - }, - ) - - # then - assert response.result is True - config = session.mobility.get_model_config(wlan.id, BasicRangeModel.name) - assert config[range_key] == range_value - assert wlan.model.range == int(range_value) - - def test_get_emane_config(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - response = client.get_emane_config(session.id) - - # then - assert len(response.config) > 0 - - def test_set_emane_config(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - config_key = "platform_id_start" - config_value = "2" - - # then - with client.context_connect(): - response = client.set_emane_config(session.id, {config_key: config_value}) - - # then - assert response.result is True - config = session.emane.get_configs() - assert len(config) > 1 - assert config[config_key] == config_value - - def test_get_emane_model_configs(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 = NodeOptions(emane=EmaneIeee80211abgModel.name) - emane_network = session.add_node(EmaneNet, options=options) - session.emane.set_model(emane_network, EmaneIeee80211abgModel) - config_key = "platform_id_start" - config_value = "2" - session.emane.set_model_config( - emane_network.id, EmaneIeee80211abgModel.name, {config_key: config_value} - ) - - # then - with client.context_connect(): - response = client.get_emane_model_configs(session.id) - - # then - assert len(response.configs) == 1 - model_config = response.configs[0] - assert emane_network.id == model_config.node_id - assert model_config.model == EmaneIeee80211abgModel.name - assert len(model_config.config) > 0 - assert model_config.iface_id == -1 - - 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 = NodeOptions(emane=EmaneIeee80211abgModel.name) - emane_network = session.add_node(EmaneNet, options=options) - session.emane.set_model(emane_network, EmaneIeee80211abgModel) - config_key = "bandwidth" - config_value = "900000" - - # then - with client.context_connect(): - response = client.set_emane_model_config( - session.id, - emane_network.id, - EmaneIeee80211abgModel.name, - {config_key: config_value}, - ) - - # then - assert response.result is True - config = session.emane.get_model_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 = NodeOptions(emane=EmaneIeee80211abgModel.name) - emane_network = session.add_node(EmaneNet, options=options) - session.emane.set_model(emane_network, EmaneIeee80211abgModel) - - # then - with client.context_connect(): - response = client.get_emane_model_config( - session.id, emane_network.id, EmaneIeee80211abgModel.name - ) - - # then - assert len(response.config) > 0 - - def test_get_emane_models(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - response = client.get_emane_models(session.id) - - # then - assert len(response.models) > 0 - - def test_get_mobility_configs(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(): - response = client.get_mobility_configs(session.id) - - # then - assert len(response.configs) > 0 - assert wlan.id in response.configs - mapped_config = response.configs[wlan.id] - assert len(mapped_config.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(): - response = client.get_mobility_config(session.id, wlan.id) - - # then - assert len(response.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(): - response = client.set_mobility_config( - session.id, wlan.id, {config_key: config_value} - ) - - # then - assert response.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(): - response = client.mobility_action(session.id, wlan.id, MobilityAction.STOP) - - # then - assert response.result is True - - def test_get_services(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - - # then - with client.context_connect(): - response = client.get_services() - - # then - assert len(response.services) > 0 - - def test_get_service_defaults(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - response = client.get_service_defaults(session.id) - - # then - assert len(response.defaults) > 0 - - def test_set_service_defaults(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node_type = "test" - services = ["SSH"] - - # then - with client.context_connect(): - response = client.set_service_defaults(session.id, {node_type: services}) - - # then - assert response.result is True - assert session.services.default_services[node_type] == services - - def test_get_node_service_configs(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) - service_name = "DefaultRoute" - session.services.set_service(node.id, service_name) - - # then - with client.context_connect(): - response = client.get_node_service_configs(session.id) - - # then - assert len(response.configs) == 1 - service_config = response.configs[0] - assert service_config.node_id == node.id - assert service_config.service == service_name - - 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(): - response = client.get_node_service(session.id, node.id, "DefaultRoute") - - # then - assert len(response.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(): - response = client.get_node_service_file( - session.id, node.id, "DefaultRoute", "defaultroute.sh" - ) - - # then - assert response.data is not None - - def test_set_node_service(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) - service_name = "DefaultRoute" - validate = ["echo hello"] - - # then - with client.context_connect(): - response = client.set_node_service( - session.id, node.id, service_name, validate=validate - ) - - # then - assert response.result is True - service = session.services.get_service( - node.id, service_name, default_service=True - ) - assert service.validate == tuple(validate) - - def test_set_node_service_file(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) - service_name = "DefaultRoute" - file_name = "defaultroute.sh" - file_data = "echo hello" - - # then - with client.context_connect(): - response = client.set_node_service_file( - session.id, node.id, service_name, file_name, file_data - ) - - # then - assert response.result is True - service_file = session.services.get_service_file(node, service_name, file_name) - assert service_file.data == file_data - - def test_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(): - response = client.service_action( - session.id, node.id, service_name, ServiceAction.STOP - ) - - # then - assert response.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_data): - assert event_data.session_id == session.id - assert event_data.HasField("node_event") - event_node = event_data.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_data) - - # 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 = ip_prefixes.create_iface(node) - session.add_link(node.id, wlan.id, iface) - link_data = wlan.links()[0] - queue = Queue() - - def handle_event(event_data): - assert event_data.session_id == session.id - assert event_data.HasField("link_event") - queue.put(event_data) - - # 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_data): - assert event_data.session_id == session.id - assert event_data.HasField("session_event") - queue.put(event_data) - - # then - with client.context_connect(): - client.events(session.id, handle_event) - time.sleep(0.1) - event = EventData( - event_type=EventTypes.RUNTIME_STATE, time=str(time.monotonic()) - ) - session.broadcast_event(event) - - # then - queue.get(timeout=5) - - def test_config_events(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - queue = Queue() - - def handle_event(event_data): - assert event_data.session_id == session.id - assert event_data.HasField("config_event") - queue.put(event_data) - - # then - with client.context_connect(): - client.events(session.id, handle_event) - time.sleep(0.1) - session_config = session.options.get_configs() - config_data = ConfigShim.config_data( - 0, None, ConfigFlags.UPDATE.value, session.options, session_config - ) - session.broadcast_config(config_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_data): - assert event_data.session_id == session.id - assert event_data.HasField("exception_event") - exception_event = event_data.exception_event - assert exception_event.level == exception_level.value - assert exception_event.node_id == 0 - assert exception_event.source == source - assert exception_event.text == text - queue.put(event_data) - - # 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_data): - assert event_data.session_id == session.id - assert event_data.HasField("file_event") - queue.put(event_data) - - # 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 - - 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): - 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) - - 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()) diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index f512dba1..5160128c 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -9,7 +9,7 @@ import pytest from mock import patch from core.api.grpc import core_pb2 -from core.api.grpc.clientw import CoreGrpcClient, InterfaceHelper, MoveNodesStreamer +from core.api.grpc.client import CoreGrpcClient, InterfaceHelper, MoveNodesStreamer from core.api.grpc.server import CoreGrpcServer from core.api.grpc.wrappers import ( ConfigOption, diff --git a/docs/grpc.md b/docs/grpc.md index 998970c5..ca3ebe00 100644 --- a/docs/grpc.md +++ b/docs/grpc.md @@ -53,15 +53,15 @@ and the services they map to. There is an interface helper class that can be leveraged for convenience when creating interface data for nodes. Alternatively one can manually create -a `core.api.grpc.core_pb2.Interface` class instead with appropriate information. +a `core.api.grpc.wrappers.Interface` class instead with appropriate information. -Manually creating gRPC interface data: +Manually creating gRPC client interface: ```python -from core.api.grpc import core_pb2 +from core.api.grpc.wrappers import Interface # id is optional and will set to the next available id # name is optional and will default to eth # mac is optional and will result in a randomly generated mac -iface_data = core_pb2.Interface( +iface = Interface( id=0, name="eth0", ip4="10.0.0.1", @@ -98,16 +98,24 @@ Event types: * file - file events when the legacy gui joins a session ```python -from core.api.grpc import core_pb2 +from core.api.grpc import client +from core.api.grpc.wrappers import EventType def event_listener(event): print(event) +# create grpc client and connect +core = client.CoreGrpcClient() +core.connect() + +# add session +session = core.add_session() + # provide no events to listen to all events -core.events(session_id, event_listener) +core.events(session.id, event_listener) # provide events to listen to specific events -core.events(session_id, event_listener, [core_pb2.EventType.NODE]) +core.events(session.id, event_listener, [EventType.NODE]) ``` ### Configuring Links @@ -122,27 +130,47 @@ Currently supported configuration options: * loss (%) ```python -from core.api.grpc import core_pb2 +from core.api.grpc import client +from core.api.grpc.wrappers import LinkOptions, Position + +# interface helper +iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") + +# create grpc client and connect +core = client.CoreGrpcClient() +core.connect() + +# add session +session = core.add_session() + +# create nodes +position = Position(x=100, y=100) +node1 = session.add_node(1, position=position) +position = Position(x=300, y=100) +node2 = session.add_node(2, position=position) # configuring when creating a link -options = core_pb2.LinkOptions( +options = LinkOptions( bandwidth=54_000_000, delay=5000, dup=5, loss=5.5, jitter=0, ) -core.add_link(session_id, n1_id, n2_id, iface1_data, iface2_data, options) +iface1 = iface_helper.create_iface(node1.id, 0) +iface2 = iface_helper.create_iface(node2.id, 0) +link = session.add_link(node1=node1, node2=node2, iface1=iface1, iface2=iface2) # configuring during runtime -core.edit_link(session_id, n1_id, n2_id, iface1_id, iface2_id, options) +link.options.loss = 10.0 +core.edit_link(session.id, link) ``` ### Peer to Peer Example ```python # required imports from core.api.grpc import client -from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.api.grpc.core_pb2 import Position # interface helper iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") @@ -151,39 +179,29 @@ iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001 core = client.CoreGrpcClient() core.connect() -# create session and get id -response = core.create_session() -session_id = response.session_id +# add session +session = core.add_session() -# change session state to configuration so that nodes get started when added -core.set_session_state(session_id, SessionState.CONFIGURATION) - -# create node one +# create nodes position = Position(x=100, y=100) -n1 = Node(type=NodeType.DEFAULT, position=position, model="PC") -response = core.add_node(session_id, n1) -n1_id = response.node_id - -# create node two +node1 = session.add_node(1, position=position) position = Position(x=300, y=100) -n2 = Node(type=NodeType.DEFAULT, position=position, model="PC") -response = core.add_node(session_id, n2) -n2_id = response.node_id +node2 = session.add_node(2, position=position) -# links nodes together -iface1 = iface_helper.create_iface(n1_id, 0) -iface2 = iface_helper.create_iface(n2_id, 0) -core.add_link(session_id, n1_id, n2_id, iface1, iface2) +# create link +iface1 = iface_helper.create_iface(node1.id, 0) +iface2 = iface_helper.create_iface(node2.id, 0) +session.add_link(node1=node1, node2=node2, iface1=iface1, iface2=iface2) -# change session state -core.set_session_state(session_id, SessionState.INSTANTIATION) +# start session +core.start_session(session) ``` ### Switch/Hub Example ```python # required imports from core.api.grpc import client -from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.api.grpc.core_pb2 import NodeType, Position # interface helper iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") @@ -192,46 +210,32 @@ iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001 core = client.CoreGrpcClient() core.connect() -# create session and get id -response = core.create_session() -session_id = response.session_id +# add session +session = core.add_session() -# change session state to configuration so that nodes get started when added -core.set_session_state(session_id, SessionState.CONFIGURATION) - -# create switch node +# create nodes position = Position(x=200, y=200) -switch = Node(type=NodeType.SWITCH, position=position) -response = core.add_node(session_id, switch) -switch_id = response.node_id - -# create node one +switch = session.add_node(1, _type=NodeType.SWITCH, position=position) position = Position(x=100, y=100) -n1 = Node(type=NodeType.DEFAULT, position=position, model="PC") -response = core.add_node(session_id, n1) -n1_id = response.node_id - -# create node two +node1 = session.add_node(2, position=position) position = Position(x=300, y=100) -n2 = Node(type=NodeType.DEFAULT, position=position, model="PC") -response = core.add_node(session_id, n2) -n2_id = response.node_id +node2 = session.add_node(3, position=position) -# links nodes to switch -iface1 = iface_helper.create_iface(n1_id, 0) -core.add_link(session_id, n1_id, switch_id, iface1) -iface1 = iface_helper.create_iface(n2_id, 0) -core.add_link(session_id, n2_id, switch_id, iface1) +# create links +iface1 = iface_helper.create_iface(node1.id, 0) +session.add_link(node1=node1, node2=switch, iface1=iface1) +iface1 = iface_helper.create_iface(node2.id, 0) +session.add_link(node1=node2, node2=switch, iface1=iface1) -# change session state -core.set_session_state(session_id, SessionState.INSTANTIATION) +# start session +core.start_session(session) ``` ### WLAN Example ```python # required imports from core.api.grpc import client -from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.api.grpc.core_pb2 import NodeType, Position # interface helper iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") @@ -240,49 +244,37 @@ iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001 core = client.CoreGrpcClient() core.connect() -# create session and get id -response = core.create_session() -session_id = response.session_id +# add session +session = core.add_session() -# change session state to configuration so that nodes get started when added -core.set_session_state(session_id, SessionState.CONFIGURATION) - -# create wlan node +# create nodes position = Position(x=200, y=200) -wlan = Node(type=NodeType.WIRELESS_LAN, position=position) -response = core.add_node(session_id, wlan) -wlan_id = response.node_id - -# create node one +wlan = session.add_node(1, _type=NodeType.WIRELESS_LAN, position=position) position = Position(x=100, y=100) -n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr") -response = core.add_node(session_id, n1) -n1_id = response.node_id - -# create node two +node1 = session.add_node(2, model="mdr", position=position) position = Position(x=300, y=100) -n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr") -response = core.add_node(session_id, n2) -n2_id = response.node_id +node2 = session.add_node(3, model="mdr", position=position) -# configure wlan using a dict mapping currently +# create links +iface1 = iface_helper.create_iface(node1.id, 0) +session.add_link(node1=node1, node2=wlan, iface1=iface1) +iface1 = iface_helper.create_iface(node2.id, 0) +session.add_link(node1=node2, node2=wlan, iface1=iface1) + +# set wlan config using a dict mapping currently # support values as strings -core.set_wlan_config(session_id, wlan_id, { - "range": "280", - "bandwidth": "55000000", - "delay": "6000", - "jitter": "5", - "error": "5", -}) +wlan.set_wlan( + { + "range": "280", + "bandwidth": "55000000", + "delay": "6000", + "jitter": "5", + "error": "5", + } +) -# links nodes to wlan -iface1 = iface_helper.create_iface(n1_id, 0) -core.add_link(session_id, n1_id, wlan_id, iface1) -iface1 = iface_helper.create_iface(n2_id, 0) -core.add_link(session_id, n2_id, wlan_id, iface1) - -# change session state -core.set_session_state(session_id, SessionState.INSTANTIATION) +# start session +core.start_session(session) ``` ### EMANE Example @@ -307,7 +299,7 @@ will use the defaults. When no configuration is used, the defaults are used. ```python # required imports from core.api.grpc import client -from core.api.grpc.core_pb2 import Node, NodeType, Position, SessionState +from core.api.grpc.core_pb2 import NodeType, Position from core.emane.ieee80211abg import EmaneIeee80211abgModel # interface helper @@ -317,68 +309,45 @@ iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001 core = client.CoreGrpcClient() core.connect() -# create session and get id -response = core.create_session() -session_id = response.session_id +# add session +session = core.add_session() -# change session state to configuration so that nodes get started when added -core.set_session_state(session_id, SessionState.CONFIGURATION) - -# create emane node +# create nodes position = Position(x=200, y=200) -emane = Node(type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name) -response = core.add_node(session_id, emane) -emane_id = response.node_id - -# create node one +emane = session.add_node( + 1, _type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name +) position = Position(x=100, y=100) -n1 = Node(type=NodeType.DEFAULT, position=position, model="mdr") -response = core.add_node(session_id, n1) -n1_id = response.node_id - -# create node two +node1 = session.add_node(2, model="mdr", position=position) position = Position(x=300, y=100) -n2 = Node(type=NodeType.DEFAULT, position=position, model="mdr") -response = core.add_node(session_id, n2) -n2_id = response.node_id +node2 = session.add_node(3, model="mdr", position=position) -# configure general emane settings -core.set_emane_config(session_id, { - "eventservicettl": "2" -}) +# create links +iface1 = iface_helper.create_iface(node1.id, 0) +session.add_link(node1=node1, node2=emane, iface1=iface1) +iface1 = iface_helper.create_iface(node2.id, 0) +session.add_link(node1=node2, node2=emane, iface1=iface1) -# configure emane model settings -# using a dict mapping currently support values as strings -core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name, { - "unicastrate": "3", -}) +# setting global emane configuration +session.set_emane({"eventservicettl": "2"}) +# setting emane specific emane model configuration +emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"}) -# links nodes to emane -iface1 = iface_helper.create_iface(n1_id, 0) -core.add_link(session_id, n1_id, emane_id, iface1) -iface1 = iface_helper.create_iface(n2_id, 0) -core.add_link(session_id, n2_id, emane_id, iface1) - -# change session state -core.set_session_state(session_id, SessionState.INSTANTIATION) +# start session +core.start_session(session) ``` EMANE Model Configuration: ```python -# emane network specific config -core.set_emane_model_config(session_id, emane_id, EmaneIeee80211abgModel.name, { - "unicastrate": "3", -}) +# emane network specific config, set on an emane node +# this setting applies to all nodes connected +emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"}) -# node specific config -core.set_emane_model_config(session_id, node_id, EmaneIeee80211abgModel.name, { - "unicastrate": "3", -}) +# node specific config for an individual node connected to an emane network +node.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"}) -# node interface specific config -core.set_emane_model_config(session_id, node_id, EmaneIeee80211abgModel.name, { - "unicastrate": "3", -}, iface_id) +# node interface specific config for an individual node connected to an emane network +node.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"}, iface_id=0) ``` ## Configuring a Service @@ -398,11 +367,8 @@ The following features can be configured for a service: Editing service properties: ```python # configure a service, for a node, for a given session -core.set_node_service( - session_id, - node_id, - service_name, - files=["file1.sh", "file2.sh"], +node.service_configs[service_name] = NodeServiceData( + configs=["file1.sh", "file2.sh"], directories=["/etc/node"], startup=["bash file1.sh"], validate=[], @@ -417,13 +383,8 @@ Editing a service file: ```python # to edit the contents of a generated file you can specify # the service, the file name, and its contents -core.set_node_service_file( - session_id, - node_id, - service_name, - file_name, - "echo hello", -) +file_configs = node.service_file_configs.setdefault(service_name, {}) +file_configs[file_name] = "echo hello world" ``` ## File Examples From 639b29a1346ef30b1ebdab5b09079b513a9fc87d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 13:36:10 -0700 Subject: [PATCH 030/110] grpc: removed get wlan configs, achieved with get session --- daemon/core/api/grpc/client.py | 18 ------------------ daemon/core/api/grpc/server.py | 17 ----------------- daemon/proto/core/api/grpc/core.proto | 2 -- daemon/proto/core/api/grpc/wlan.proto | 8 -------- 4 files changed, 45 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 23a66f1f..e2d6b503 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -61,7 +61,6 @@ from core.api.grpc.services_pb2 import ( ) from core.api.grpc.wlan_pb2 import ( GetWlanConfigRequest, - GetWlanConfigsRequest, SetWlanConfigRequest, WlanConfig, WlanLinkRequest, @@ -1002,23 +1001,6 @@ class CoreGrpcClient: response = self.stub.ServiceAction(request) return response.result - def get_wlan_configs( - self, session_id: int - ) -> Dict[int, Dict[str, wrappers.ConfigOption]]: - """ - Get all wlan configurations. - - :param session_id: session id - :return: dict of node ids to dict of names to options - :raises grpc.RpcError: when session doesn't exist - """ - request = GetWlanConfigsRequest(session_id=session_id) - response = self.stub.GetWlanConfigs(request) - configs = {} - for node_id, mapped_config in response.configs.items(): - configs[node_id] = wrappers.ConfigOption.from_dict(mapped_config.config) - return configs - def get_wlan_config( self, session_id: int, node_id: int ) -> Dict[str, wrappers.ConfigOption]: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index f18f2eec..8a3e570e 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -94,8 +94,6 @@ from core.api.grpc.services_pb2 import ( from core.api.grpc.wlan_pb2 import ( GetWlanConfigRequest, GetWlanConfigResponse, - GetWlanConfigsRequest, - GetWlanConfigsResponse, SetWlanConfigRequest, SetWlanConfigResponse, WlanLinkRequest, @@ -1171,21 +1169,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): return ServiceActionResponse(result=result) - def GetWlanConfigs( - self, request: GetWlanConfigsRequest, context: ServicerContext - ) -> GetWlanConfigsResponse: - """ - Retrieve all wireless-lan configurations. - - :param request: request - :param context: core.api.grpc.core_pb2.GetWlanConfigResponse - :return: all wlan configurations - """ - logger.debug("get wlan configs: %s", request) - session = self.get_session(request.session_id, context) - configs = grpcutils.get_wlan_configs(session) - return GetWlanConfigsResponse(configs=configs) - def GetWlanConfig( self, request: GetWlanConfigRequest, context: ServicerContext ) -> GetWlanConfigResponse: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 28208e7d..2081cc90 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -109,8 +109,6 @@ service CoreApi { } // wlan rpc - rpc GetWlanConfigs (wlan.GetWlanConfigsRequest) returns (wlan.GetWlanConfigsResponse) { - } rpc GetWlanConfig (wlan.GetWlanConfigRequest) returns (wlan.GetWlanConfigResponse) { } rpc SetWlanConfig (wlan.SetWlanConfigRequest) returns (wlan.SetWlanConfigResponse) { diff --git a/daemon/proto/core/api/grpc/wlan.proto b/daemon/proto/core/api/grpc/wlan.proto index 9605d633..2d161a04 100644 --- a/daemon/proto/core/api/grpc/wlan.proto +++ b/daemon/proto/core/api/grpc/wlan.proto @@ -9,14 +9,6 @@ message WlanConfig { map config = 2; } -message GetWlanConfigsRequest { - int32 session_id = 1; -} - -message GetWlanConfigsResponse { - map configs = 1; -} - message GetWlanConfigRequest { int32 session_id = 1; int32 node_id = 2; From 618d89b8dbc570172e8eb487a6e2e2fc0f64bfd8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 13:51:17 -0700 Subject: [PATCH 031/110] grpc: removed get model configs, can be achieved with get session --- daemon/core/api/grpc/client.py | 19 ------------------- daemon/core/api/grpc/server.py | 19 ------------------- daemon/proto/core/api/grpc/core.proto | 2 -- daemon/proto/core/api/grpc/emane.proto | 8 -------- daemon/tests/test_grpcw.py | 26 -------------------------- 5 files changed, 74 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index e2d6b503..e42d7c43 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -35,7 +35,6 @@ from core.api.grpc.emane_pb2 import ( GetEmaneConfigRequest, GetEmaneEventChannelRequest, GetEmaneModelConfigRequest, - GetEmaneModelConfigsRequest, GetEmaneModelsRequest, SetEmaneConfigRequest, SetEmaneModelConfigRequest, @@ -1106,24 +1105,6 @@ class CoreGrpcClient: response = self.stub.SetEmaneModelConfig(request) return response.result - def get_emane_model_configs( - self, session_id: int - ) -> List[wrappers.EmaneModelConfig]: - """ - Get all EMANE model configurations for a session. - - :param session_id: session to get emane model configs - :return: list of emane model configs - :raises grpc.RpcError: when session doesn't exist - """ - request = GetEmaneModelConfigsRequest(session_id=session_id) - response = self.stub.GetEmaneModelConfigs(request) - configs = [] - for config_proto in response.configs: - config = wrappers.EmaneModelConfig.from_proto(config_proto) - configs.append(config) - return configs - def save_xml(self, session_id: int, file_path: str) -> None: """ Save the current scenario to an XML file. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 8a3e570e..066eace0 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -47,8 +47,6 @@ from core.api.grpc.emane_pb2 import ( GetEmaneEventChannelResponse, GetEmaneModelConfigRequest, GetEmaneModelConfigResponse, - GetEmaneModelConfigsRequest, - GetEmaneModelConfigsResponse, GetEmaneModelsRequest, GetEmaneModelsResponse, SetEmaneConfigRequest, @@ -1292,23 +1290,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.emane.set_model_config(_id, model_config.model, model_config.config) return SetEmaneModelConfigResponse(result=True) - def GetEmaneModelConfigs( - self, request: GetEmaneModelConfigsRequest, context: ServicerContext - ) -> GetEmaneModelConfigsResponse: - """ - Retrieve all EMANE model configurations of a session - - :param request: - get-EMANE-model-configurations request - :param context: context object - :return: get-EMANE-model-configurations response that has all the EMANE - configurations - """ - logger.debug("get emane model configs: %s", request) - session = self.get_session(request.session_id, context) - configs = grpcutils.get_emane_model_configs(session) - return GetEmaneModelConfigsResponse(configs=configs) - def SaveXml( self, request: core_pb2.SaveXmlRequest, context: ServicerContext ) -> core_pb2.SaveXmlResponse: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 2081cc90..0a5c7dad 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -127,8 +127,6 @@ service CoreApi { } rpc SetEmaneModelConfig (emane.SetEmaneModelConfigRequest) returns (emane.SetEmaneModelConfigResponse) { } - rpc GetEmaneModelConfigs (emane.GetEmaneModelConfigsRequest) returns (emane.GetEmaneModelConfigsResponse) { - } rpc GetEmaneEventChannel (emane.GetEmaneEventChannelRequest) returns (emane.GetEmaneEventChannelResponse) { } rpc EmanePathlosses (stream emane.EmanePathlossesRequest) returns (emane.EmanePathlossesResponse) { diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index ad6a22ca..65ee1026 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -49,10 +49,6 @@ message SetEmaneModelConfigResponse { bool result = 1; } -message GetEmaneModelConfigsRequest { - int32 session_id = 1; -} - message GetEmaneModelConfig { int32 node_id = 1; string model = 2; @@ -60,10 +56,6 @@ message GetEmaneModelConfig { map config = 4; } -message GetEmaneModelConfigsResponse { - repeated GetEmaneModelConfig configs = 1; -} - message GetEmaneEventChannelRequest { int32 session_id = 1; } diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index 5160128c..10b772a6 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -562,32 +562,6 @@ class TestGrpcw: assert len(config) > 1 assert config[config_key] == config_value - def test_get_emane_model_configs(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 = NodeOptions(emane=EmaneIeee80211abgModel.name) - emane_network = session.add_node(EmaneNet, options=options) - session.emane.set_model(emane_network, EmaneIeee80211abgModel) - config_key = "platform_id_start" - config_value = "2" - session.emane.set_model_config( - emane_network.id, EmaneIeee80211abgModel.name, {config_key: config_value} - ) - - # then - with client.context_connect(): - configs = client.get_emane_model_configs(session.id) - - # then - assert len(configs) == 1 - model_config = configs[0] - assert emane_network.id == model_config.node_id - assert model_config.model == EmaneIeee80211abgModel.name - assert len(model_config.config) > 0 - assert model_config.iface_id is None - def test_set_emane_model_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From 9205fe1764a17ddf1400542dd588f991cc589f7a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 14:27:46 -0700 Subject: [PATCH 032/110] grpc: removed get mobility configs, achieved with get session --- daemon/core/api/grpc/client.py | 18 --------------- daemon/core/api/grpc/server.py | 18 --------------- daemon/proto/core/api/grpc/core.proto | 28 +++++++++++------------ daemon/proto/core/api/grpc/mobility.proto | 8 ------- daemon/tests/test_grpcw.py | 17 -------------- 5 files changed, 14 insertions(+), 75 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index e42d7c43..278e8d4a 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -41,7 +41,6 @@ from core.api.grpc.emane_pb2 import ( ) from core.api.grpc.mobility_pb2 import ( GetMobilityConfigRequest, - GetMobilityConfigsRequest, MobilityActionRequest, MobilityConfig, SetMobilityConfigRequest, @@ -769,23 +768,6 @@ class CoreGrpcClient: response = self.stub.DeleteLink(request) return response.result - def get_mobility_configs( - self, session_id: int - ) -> Dict[int, Dict[str, wrappers.ConfigOption]]: - """ - Get all mobility configurations. - - :param session_id: session id - :return: dict of node id to mobility configuration dict - :raises grpc.RpcError: when session doesn't exist - """ - request = GetMobilityConfigsRequest(session_id=session_id) - response = self.stub.GetMobilityConfigs(request) - configs = {} - for node_id, mapped_config in response.configs.items(): - configs[node_id] = wrappers.ConfigOption.from_dict(mapped_config.config) - return configs - def get_mobility_config( self, session_id: int, node_id: int ) -> Dict[str, wrappers.ConfigOption]: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 066eace0..661d9e59 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -59,8 +59,6 @@ from core.api.grpc.grpcutils import get_config_options, get_links, get_net_stats from core.api.grpc.mobility_pb2 import ( GetMobilityConfigRequest, GetMobilityConfigResponse, - GetMobilityConfigsRequest, - GetMobilityConfigsResponse, MobilityAction, MobilityActionRequest, MobilityActionResponse, @@ -901,22 +899,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.broadcast_link(link_data) return core_pb2.DeleteLinkResponse(result=True) - def GetMobilityConfigs( - self, request: GetMobilityConfigsRequest, context: ServicerContext - ) -> GetMobilityConfigsResponse: - """ - Retrieve all mobility configurations from a session - - :param request: - get-mobility-configurations request - :param context: context object - :return: get-mobility-configurations response that has a list of configurations - """ - logger.debug("get mobility configs: %s", request) - session = self.get_session(request.session_id, context) - configs = grpcutils.get_mobility_configs(session) - return GetMobilityConfigsResponse(configs=configs) - def GetMobilityConfig( self, request: GetMobilityConfigRequest, context: ServicerContext ) -> GetMobilityConfigResponse: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 0a5c7dad..c7b2ba6e 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -65,8 +65,6 @@ service CoreApi { } // mobility rpc - rpc GetMobilityConfigs (mobility.GetMobilityConfigsRequest) returns (mobility.GetMobilityConfigsResponse) { - } rpc GetMobilityConfig (mobility.GetMobilityConfigRequest) returns (mobility.GetMobilityConfigResponse) { } rpc SetMobilityConfig (mobility.SetMobilityConfigRequest) returns (mobility.SetMobilityConfigResponse) { @@ -75,10 +73,6 @@ service CoreApi { } // service rpc - rpc GetServices (services.GetServicesRequest) returns (services.GetServicesResponse) { - } - rpc GetServiceDefaults (services.GetServiceDefaultsRequest) returns (services.GetServiceDefaultsResponse) { - } rpc SetServiceDefaults (services.SetServiceDefaultsRequest) returns (services.SetServiceDefaultsResponse) { } rpc GetNodeServiceConfigs (services.GetNodeServiceConfigsRequest) returns (services.GetNodeServiceConfigsResponse) { @@ -95,10 +89,6 @@ service CoreApi { } // config services - rpc GetConfigServices (configservices.GetConfigServicesRequest) returns (configservices.GetConfigServicesResponse) { - } - rpc GetConfigServiceDefaults (configservices.GetConfigServiceDefaultsRequest) returns (configservices.GetConfigServiceDefaultsResponse) { - } rpc GetNodeConfigServiceConfigs (configservices.GetNodeConfigServiceConfigsRequest) returns (configservices.GetNodeConfigServiceConfigsResponse) { } rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) { @@ -121,8 +111,6 @@ service CoreApi { } rpc SetEmaneConfig (emane.SetEmaneConfigRequest) returns (emane.SetEmaneConfigResponse) { } - rpc GetEmaneModels (emane.GetEmaneModelsRequest) returns (emane.GetEmaneModelsResponse) { - } rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) { } rpc SetEmaneModelConfig (emane.SetEmaneModelConfigRequest) returns (emane.SetEmaneModelConfigResponse) { @@ -131,6 +119,8 @@ service CoreApi { } rpc EmanePathlosses (stream emane.EmanePathlossesRequest) returns (emane.EmanePathlossesResponse) { } + rpc EmaneLink (emane.EmaneLinkRequest) returns (emane.EmaneLinkResponse) { + } // xml rpc rpc SaveXml (SaveXmlRequest) returns (SaveXmlResponse) { @@ -141,10 +131,20 @@ service CoreApi { // utilities rpc GetInterfaces (GetInterfacesRequest) returns (GetInterfacesResponse) { } - rpc EmaneLink (emane.EmaneLinkRequest) returns (emane.EmaneLinkResponse) { - } rpc ExecuteScript (ExecuteScriptRequest) returns (ExecuteScriptResponse) { } + + // globals + rpc GetEmaneModels (emane.GetEmaneModelsRequest) returns (emane.GetEmaneModelsResponse) { + } + rpc GetConfigServices (configservices.GetConfigServicesRequest) returns (configservices.GetConfigServicesResponse) { + } + rpc GetConfigServiceDefaults (configservices.GetConfigServiceDefaultsRequest) returns (configservices.GetConfigServiceDefaultsResponse) { + } + rpc GetServices (services.GetServicesRequest) returns (services.GetServicesResponse) { + } + rpc GetServiceDefaults (services.GetServiceDefaultsRequest) returns (services.GetServiceDefaultsResponse) { + } } // rpc request/response messages diff --git a/daemon/proto/core/api/grpc/mobility.proto b/daemon/proto/core/api/grpc/mobility.proto index abfad8ef..6eaf8fc3 100644 --- a/daemon/proto/core/api/grpc/mobility.proto +++ b/daemon/proto/core/api/grpc/mobility.proto @@ -17,14 +17,6 @@ message MobilityConfig { map config = 2; } -message GetMobilityConfigsRequest { - int32 session_id = 1; -} - -message GetMobilityConfigsResponse { - map configs = 1; -} - message GetMobilityConfigRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index 10b772a6..d3da8742 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -624,23 +624,6 @@ class TestGrpcw: # then assert len(models) > 0 - def test_get_mobility_configs(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(): - configs = client.get_mobility_configs(session.id) - - # then - assert len(configs) > 0 - assert wlan.id in configs - config = configs[wlan.id] - assert len(config) > 0 - def test_get_mobility_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From c574ace9a001d0996c9fd7994a51e81e49353f91 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 14:40:17 -0700 Subject: [PATCH 033/110] grpc: removed get node service configs, achieved with get session --- daemon/core/api/grpc/client.py | 19 ------------------- daemon/core/api/grpc/server.py | 18 ------------------ daemon/proto/core/api/grpc/core.proto | 2 -- daemon/proto/core/api/grpc/services.proto | 8 -------- daemon/tests/test_grpcw.py | 18 ------------------ 5 files changed, 65 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 278e8d4a..80f3fdd4 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -46,7 +46,6 @@ from core.api.grpc.mobility_pb2 import ( SetMobilityConfigRequest, ) from core.api.grpc.services_pb2 import ( - GetNodeServiceConfigsRequest, GetNodeServiceFileRequest, GetNodeServiceRequest, GetServiceDefaultsRequest, @@ -870,24 +869,6 @@ class CoreGrpcClient: response = self.stub.SetServiceDefaults(request) return response.result - def get_node_service_configs( - self, session_id: int - ) -> List[wrappers.NodeServiceConfig]: - """ - Get service data for a node. - - :param session_id: session id - :return: list of node service data - :raises grpc.RpcError: when session doesn't exist - """ - request = GetNodeServiceConfigsRequest(session_id=session_id) - response = self.stub.GetNodeServiceConfigs(request) - node_services = [] - for config in response.configs: - node_service = wrappers.NodeServiceConfig.from_proto(config) - node_services.append(node_service) - return node_services - def get_node_service( self, session_id: int, node_id: int, service: str ) -> wrappers.NodeServiceData: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 661d9e59..4f06c4dc 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -66,8 +66,6 @@ from core.api.grpc.mobility_pb2 import ( SetMobilityConfigResponse, ) from core.api.grpc.services_pb2 import ( - GetNodeServiceConfigsRequest, - GetNodeServiceConfigsResponse, GetNodeServiceFileRequest, GetNodeServiceFileResponse, GetNodeServiceRequest, @@ -1018,22 +1016,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): ] = service_defaults.services return SetServiceDefaultsResponse(result=True) - def GetNodeServiceConfigs( - self, request: GetNodeServiceConfigsRequest, context: ServicerContext - ) -> GetNodeServiceConfigsResponse: - """ - Retrieve all node service configurations. - - :param request: - get-node-service request - :param context: context object - :return: all node service configs response - """ - logger.debug("get node service configs: %s", request) - session = self.get_session(request.session_id, context) - configs = grpcutils.get_node_service_configs(session) - return GetNodeServiceConfigsResponse(configs=configs) - def GetNodeService( self, request: GetNodeServiceRequest, context: ServicerContext ) -> GetNodeServiceResponse: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index c7b2ba6e..2c429e80 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -75,8 +75,6 @@ service CoreApi { // service rpc rpc SetServiceDefaults (services.SetServiceDefaultsRequest) returns (services.SetServiceDefaultsResponse) { } - rpc GetNodeServiceConfigs (services.GetNodeServiceConfigsRequest) returns (services.GetNodeServiceConfigsResponse) { - } rpc GetNodeService (services.GetNodeServiceRequest) returns (services.GetNodeServiceResponse) { } rpc GetNodeServiceFile (services.GetNodeServiceFileRequest) returns (services.GetNodeServiceFileResponse) { diff --git a/daemon/proto/core/api/grpc/services.proto b/daemon/proto/core/api/grpc/services.proto index cf6d9cbf..f7eb9a3a 100644 --- a/daemon/proto/core/api/grpc/services.proto +++ b/daemon/proto/core/api/grpc/services.proto @@ -91,14 +91,6 @@ message SetServiceDefaultsResponse { bool result = 1; } -message GetNodeServiceConfigsRequest { - int32 session_id = 1; -} - -message GetNodeServiceConfigsResponse { - repeated NodeServiceConfig configs = 1; -} - message GetNodeServiceRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index d3da8742..cfe6dfbd 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -710,24 +710,6 @@ class TestGrpcw: assert result is True assert session.services.default_services[node_type] == services - def test_get_node_service_configs(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) - service_name = "DefaultRoute" - session.services.set_service(node.id, service_name) - - # then - with client.context_connect(): - services = client.get_node_service_configs(session.id) - - # then - assert len(services) == 1 - service_config = services[0] - assert service_config.node_id == node.id - assert service_config.service == service_name - def test_get_node_service(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From 3e2cb86b6b927c315454196c0daa2f309e00d069 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 14:55:12 -0700 Subject: [PATCH 034/110] grpc: removed set node service, achieved with start session --- daemon/core/api/grpc/client.py | 18 ------------------ daemon/core/api/grpc/server.py | 19 ------------------- daemon/proto/core/api/grpc/core.proto | 2 -- daemon/proto/core/api/grpc/services.proto | 9 --------- daemon/tests/test_grpcw.py | 19 ------------------- 5 files changed, 67 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 80f3fdd4..a260e7e4 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -53,7 +53,6 @@ from core.api.grpc.services_pb2 import ( ServiceActionRequest, ServiceDefaults, SetNodeServiceFileRequest, - SetNodeServiceRequest, SetServiceDefaultsRequest, ) from core.api.grpc.wlan_pb2 import ( @@ -906,23 +905,6 @@ class CoreGrpcClient: response = self.stub.GetNodeServiceFile(request) return response.data - def set_node_service( - self, session_id: int, service_config: wrappers.ServiceConfig - ) -> bool: - """ - Set service data for a node. - - :param session_id: session id - :param service_config: service configuration for a node - :return: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - request = SetNodeServiceRequest( - session_id=session_id, config=service_config.to_proto() - ) - response = self.stub.SetNodeService(request) - return response.result - def set_node_service_file( self, session_id: int, service_file_config: wrappers.ServiceFileConfig ) -> bool: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 4f06c4dc..2dd7e9a8 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -80,8 +80,6 @@ from core.api.grpc.services_pb2 import ( ServiceActionResponse, SetNodeServiceFileRequest, SetNodeServiceFileResponse, - SetNodeServiceRequest, - SetNodeServiceResponse, SetServiceDefaultsRequest, SetServiceDefaultsResponse, ) @@ -1054,23 +1052,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): ) return GetNodeServiceFileResponse(data=file_data.data) - def SetNodeService( - self, request: SetNodeServiceRequest, context: ServicerContext - ) -> SetNodeServiceResponse: - """ - Set a node service for a node - - :param request: set-node-service - request that has info to set a node service - :param context: context object - :return: set-node-service response - """ - logger.debug("set node service: %s", request) - session = self.get_session(request.session_id, context) - config = request.config - grpcutils.service_configuration(session, config) - return SetNodeServiceResponse(result=True) - def SetNodeServiceFile( self, request: SetNodeServiceFileRequest, context: ServicerContext ) -> SetNodeServiceFileResponse: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 2c429e80..3dabbb74 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -79,8 +79,6 @@ service CoreApi { } rpc GetNodeServiceFile (services.GetNodeServiceFileRequest) returns (services.GetNodeServiceFileResponse) { } - rpc SetNodeService (services.SetNodeServiceRequest) returns (services.SetNodeServiceResponse) { - } rpc SetNodeServiceFile (services.SetNodeServiceFileRequest) returns (services.SetNodeServiceFileResponse) { } rpc ServiceAction (services.ServiceActionRequest) returns (services.ServiceActionResponse) { diff --git a/daemon/proto/core/api/grpc/services.proto b/daemon/proto/core/api/grpc/services.proto index f7eb9a3a..f6766a75 100644 --- a/daemon/proto/core/api/grpc/services.proto +++ b/daemon/proto/core/api/grpc/services.proto @@ -112,15 +112,6 @@ message GetNodeServiceFileResponse { string data = 1; } -message SetNodeServiceRequest { - int32 session_id = 1; - ServiceConfig config = 2; -} - -message SetNodeServiceResponse { - bool result = 1; -} - message SetNodeServiceFileRequest { int32 session_id = 1; ServiceFileConfig config = 2; diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index cfe6dfbd..7719f021 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -27,7 +27,6 @@ from core.api.grpc.wrappers import ( NodeType, Position, ServiceAction, - ServiceConfig, ServiceFileConfig, ServiceValidationMode, SessionLocation, @@ -738,24 +737,6 @@ class TestGrpcw: # then assert data is not None - def test_set_node_service(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) - config = ServiceConfig(node.id, "DefaultRoute", validate=["echo hello"]) - - # then - with client.context_connect(): - result = client.set_node_service(session.id, config) - - # then - assert result is True - service = session.services.get_service( - node.id, config.service, default_service=True - ) - assert service.validate == tuple(config.validate) - def test_set_node_service_file(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From e299d3dd160a6cdedb656c6345d863c4b01f619c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 15:02:23 -0700 Subject: [PATCH 035/110] grpc: removed set node service file, achieved with start session, removed get node config service configs, achieved with get session --- daemon/core/api/grpc/client.py | 36 ------------------ daemon/core/api/grpc/server.py | 38 ------------------- .../proto/core/api/grpc/configservices.proto | 8 ---- daemon/proto/core/api/grpc/core.proto | 6 +-- daemon/proto/core/api/grpc/services.proto | 9 ----- daemon/tests/test_grpcw.py | 21 ---------- 6 files changed, 1 insertion(+), 117 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index a260e7e4..b0a3ec11 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -24,7 +24,6 @@ from core.api.grpc import ( from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsRequest, GetConfigServicesRequest, - GetNodeConfigServiceConfigsRequest, GetNodeConfigServiceRequest, GetNodeConfigServicesRequest, SetNodeConfigServiceRequest, @@ -52,7 +51,6 @@ from core.api.grpc.services_pb2 import ( GetServicesRequest, ServiceActionRequest, ServiceDefaults, - SetNodeServiceFileRequest, SetServiceDefaultsRequest, ) from core.api.grpc.wlan_pb2 import ( @@ -905,22 +903,6 @@ class CoreGrpcClient: response = self.stub.GetNodeServiceFile(request) return response.data - def set_node_service_file( - self, session_id: int, service_file_config: wrappers.ServiceFileConfig - ) -> bool: - """ - Set a service file for a node. - - :param session_id: session id - :param service_file_config: configuration to set - :return: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - config = service_file_config.to_proto() - request = SetNodeServiceFileRequest(session_id=session_id, config=config) - response = self.stub.SetNodeServiceFile(request) - return response.result - def service_action( self, session_id: int, @@ -1131,24 +1113,6 @@ class CoreGrpcClient: response = self.stub.GetConfigServiceDefaults(request) return wrappers.ConfigServiceDefaults.from_proto(response) - def get_node_config_service_configs( - self, session_id: int - ) -> List[wrappers.ConfigServiceConfig]: - """ - Retrieves all node config service configurations for a session. - - :param session_id: session to get config service configurations for - :return: list of node config service configs - :raises grpc.RpcError: when session doesn't exist - """ - request = GetNodeConfigServiceConfigsRequest(session_id=session_id) - response = self.stub.GetNodeConfigServiceConfigs(request) - configs = [] - for config_proto in response.configs: - config = wrappers.ConfigServiceConfig.from_proto(config_proto) - configs.append(config) - return configs - def get_node_config_service( self, session_id: int, node_id: int, name: str ) -> Dict[str, str]: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 2dd7e9a8..8fee64ef 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -26,8 +26,6 @@ from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsResponse, GetConfigServicesRequest, GetConfigServicesResponse, - GetNodeConfigServiceConfigsRequest, - GetNodeConfigServiceConfigsResponse, GetNodeConfigServiceRequest, GetNodeConfigServiceResponse, GetNodeConfigServicesRequest, @@ -78,8 +76,6 @@ from core.api.grpc.services_pb2 import ( ServiceAction, ServiceActionRequest, ServiceActionResponse, - SetNodeServiceFileRequest, - SetNodeServiceFileResponse, SetServiceDefaultsRequest, SetServiceDefaultsResponse, ) @@ -1052,25 +1048,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): ) return GetNodeServiceFileResponse(data=file_data.data) - def SetNodeServiceFile( - self, request: SetNodeServiceFileRequest, context: ServicerContext - ) -> SetNodeServiceFileResponse: - """ - Store the customized service file in the service config - - :param request: - set-node-service-file request - :param context: context object - :return: set-node-service-file response - """ - logger.debug("set node service file: %s", request) - session = self.get_session(request.session_id, context) - config = request.config - session.services.set_service_file( - config.node_id, config.service, config.file, config.data - ) - return SetNodeServiceFileResponse(result=True) - def ServiceAction( self, request: ServiceActionRequest, context: ServicerContext ) -> ServiceActionResponse: @@ -1427,21 +1404,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): templates=templates, config=config, modes=modes ) - def GetNodeConfigServiceConfigs( - self, request: GetNodeConfigServiceConfigsRequest, context: ServicerContext - ) -> GetNodeConfigServiceConfigsResponse: - """ - Get current custom templates and config for configuration services for a given - node. - - :param request: get node config service configs request - :param context: grpc context - :return: get node config service configs response - """ - session = self.get_session(request.session_id, context) - configs = grpcutils.get_node_config_service_configs(session) - return GetNodeConfigServiceConfigsResponse(configs=configs) - def GetNodeConfigServices( self, request: GetNodeConfigServicesRequest, context: ServicerContext ) -> GetNodeConfigServicesResponse: diff --git a/daemon/proto/core/api/grpc/configservices.proto b/daemon/proto/core/api/grpc/configservices.proto index f1272df8..e8d93fb0 100644 --- a/daemon/proto/core/api/grpc/configservices.proto +++ b/daemon/proto/core/api/grpc/configservices.proto @@ -57,14 +57,6 @@ message GetConfigServiceDefaultsResponse { repeated ConfigMode modes = 3; } -message GetNodeConfigServiceConfigsRequest { - int32 session_id = 1; -} - -message GetNodeConfigServiceConfigsResponse { - repeated ConfigServiceConfig configs = 1; -} - message GetNodeConfigServiceRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 3dabbb74..b03a101c 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -79,13 +79,11 @@ service CoreApi { } rpc GetNodeServiceFile (services.GetNodeServiceFileRequest) returns (services.GetNodeServiceFileResponse) { } - rpc SetNodeServiceFile (services.SetNodeServiceFileRequest) returns (services.SetNodeServiceFileResponse) { - } rpc ServiceAction (services.ServiceActionRequest) returns (services.ServiceActionResponse) { } // config services - rpc GetNodeConfigServiceConfigs (configservices.GetNodeConfigServiceConfigsRequest) returns (configservices.GetNodeConfigServiceConfigsResponse) { + rpc GetConfigServiceDefaults (configservices.GetConfigServiceDefaultsRequest) returns (configservices.GetConfigServiceDefaultsResponse) { } rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) { } @@ -135,8 +133,6 @@ service CoreApi { } rpc GetConfigServices (configservices.GetConfigServicesRequest) returns (configservices.GetConfigServicesResponse) { } - rpc GetConfigServiceDefaults (configservices.GetConfigServiceDefaultsRequest) returns (configservices.GetConfigServiceDefaultsResponse) { - } rpc GetServices (services.GetServicesRequest) returns (services.GetServicesResponse) { } rpc GetServiceDefaults (services.GetServiceDefaultsRequest) returns (services.GetServiceDefaultsResponse) { diff --git a/daemon/proto/core/api/grpc/services.proto b/daemon/proto/core/api/grpc/services.proto index f6766a75..602131a3 100644 --- a/daemon/proto/core/api/grpc/services.proto +++ b/daemon/proto/core/api/grpc/services.proto @@ -112,15 +112,6 @@ message GetNodeServiceFileResponse { string data = 1; } -message SetNodeServiceFileRequest { - int32 session_id = 1; - ServiceFileConfig config = 2; -} - -message SetNodeServiceFileResponse { - bool result = 1; -} - message ServiceActionRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index 7719f021..10d9022c 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -27,7 +27,6 @@ from core.api.grpc.wrappers import ( NodeType, Position, ServiceAction, - ServiceFileConfig, ServiceValidationMode, SessionLocation, SessionState, @@ -737,26 +736,6 @@ class TestGrpcw: # then assert data is not None - def test_set_node_service_file(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) - config = ServiceFileConfig( - node.id, "DefaultRoute", "defaultroute.sh", "echo hello" - ) - - # then - with client.context_connect(): - result = client.set_node_service_file(session.id, config) - - # then - assert result is True - service_file = session.services.get_service_file( - node, config.service, config.file - ) - assert service_file.data == config.data - def test_service_action(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From 805be3f8090ee625eb5f094183e68a4e8c4482c8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 15:42:32 -0700 Subject: [PATCH 036/110] grpc: removed get node config services, achieved with get node or get session --- daemon/core/api/grpc/client.py | 14 -------------- daemon/core/api/grpc/server.py | 17 ----------------- daemon/proto/core/api/grpc/configservices.proto | 9 --------- daemon/proto/core/api/grpc/core.proto | 2 -- 4 files changed, 42 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index b0a3ec11..c258c91e 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -25,7 +25,6 @@ from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsRequest, GetConfigServicesRequest, GetNodeConfigServiceRequest, - GetNodeConfigServicesRequest, SetNodeConfigServiceRequest, ) from core.api.grpc.core_pb2 import ExecuteScriptRequest @@ -1131,19 +1130,6 @@ class CoreGrpcClient: response = self.stub.GetNodeConfigService(request) return dict(response.config) - def get_node_config_services(self, session_id: int, node_id: int) -> List[str]: - """ - 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: list of config services - :raises grpc.RpcError: when session or node doesn't exist - """ - request = GetNodeConfigServicesRequest(session_id=session_id, node_id=node_id) - response = self.stub.GetNodeConfigServices(request) - return list(response.services) - def set_node_config_service( self, session_id: int, node_id: int, name: str, config: Dict[str, str] ) -> bool: diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 8fee64ef..534c1578 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -28,8 +28,6 @@ from core.api.grpc.configservices_pb2 import ( GetConfigServicesResponse, GetNodeConfigServiceRequest, GetNodeConfigServiceResponse, - GetNodeConfigServicesRequest, - GetNodeConfigServicesResponse, SetNodeConfigServiceRequest, SetNodeConfigServiceResponse, ) @@ -1404,21 +1402,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): templates=templates, config=config, modes=modes ) - def GetNodeConfigServices( - self, request: GetNodeConfigServicesRequest, context: ServicerContext - ) -> GetNodeConfigServicesResponse: - """ - Get configuration services for a given node. - - :param request: get node config services request - :param context: grpc context - :return: get node config services response - """ - session = self.get_session(request.session_id, context) - node = self.get_node(session, request.node_id, context, CoreNode) - services = node.config_services.keys() - return GetNodeConfigServicesResponse(services=services) - def SetNodeConfigService( self, request: SetNodeConfigServiceRequest, context: ServicerContext ) -> SetNodeConfigServiceResponse: diff --git a/daemon/proto/core/api/grpc/configservices.proto b/daemon/proto/core/api/grpc/configservices.proto index e8d93fb0..7a71ab8c 100644 --- a/daemon/proto/core/api/grpc/configservices.proto +++ b/daemon/proto/core/api/grpc/configservices.proto @@ -67,15 +67,6 @@ message GetNodeConfigServiceResponse { map config = 1; } -message GetNodeConfigServicesRequest { - int32 session_id = 1; - int32 node_id = 2; -} - -message GetNodeConfigServicesResponse { - repeated string services = 1; -} - message SetNodeConfigServiceRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index b03a101c..0466a449 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -87,8 +87,6 @@ service CoreApi { } rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) { } - rpc GetNodeConfigServices (configservices.GetNodeConfigServicesRequest) returns (configservices.GetNodeConfigServicesResponse) { - } rpc SetNodeConfigService (configservices.SetNodeConfigServiceRequest) returns (configservices.SetNodeConfigServiceResponse) { } From 42dc56c56bd0ac9a3e9ff296502f45e153e5467d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 15:57:53 -0700 Subject: [PATCH 037/110] grpc: removed set node config service, achieved with start session --- daemon/core/api/grpc/client.py | 20 --------------- daemon/core/api/grpc/server.py | 25 ------------------- .../proto/core/api/grpc/configservices.proto | 11 -------- daemon/proto/core/api/grpc/core.proto | 2 -- 4 files changed, 58 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index c258c91e..29e65daf 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -25,7 +25,6 @@ from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsRequest, GetConfigServicesRequest, GetNodeConfigServiceRequest, - SetNodeConfigServiceRequest, ) from core.api.grpc.core_pb2 import ExecuteScriptRequest from core.api.grpc.emane_pb2 import ( @@ -1130,25 +1129,6 @@ class CoreGrpcClient: response = self.stub.GetNodeConfigService(request) return dict(response.config) - def set_node_config_service( - self, session_id: int, node_id: int, name: str, config: Dict[str, str] - ) -> bool: - """ - 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: True for success, False otherwise - :raises grpc.RpcError: when session or node doesn't exist - """ - request = SetNodeConfigServiceRequest( - session_id=session_id, node_id=node_id, name=name, config=config - ) - response = self.stub.SetNodeConfigService(request) - return response.result - def get_emane_event_channel(self, session_id: int) -> wrappers.EmaneEventChannel: """ Retrieves the current emane event channel being used for a session. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 534c1578..bd6a758e 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -28,8 +28,6 @@ from core.api.grpc.configservices_pb2 import ( GetConfigServicesResponse, GetNodeConfigServiceRequest, GetNodeConfigServiceResponse, - SetNodeConfigServiceRequest, - SetNodeConfigServiceResponse, ) from core.api.grpc.core_pb2 import ExecuteScriptResponse from core.api.grpc.emane_pb2 import ( @@ -1402,29 +1400,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): templates=templates, config=config, modes=modes ) - def SetNodeConfigService( - self, request: SetNodeConfigServiceRequest, context: ServicerContext - ) -> SetNodeConfigServiceResponse: - """ - Set custom config, for a given configuration service, for a given node. - - :param request: set node config service request - :param context: grpc context - :return: set node config service response - """ - session = self.get_session(request.session_id, context) - node = self.get_node(session, request.node_id, context, CoreNode) - self.validate_service(request.name, context) - service = node.config_services.get(request.name) - if service: - service.set_config(request.config) - return SetNodeConfigServiceResponse(result=True) - else: - context.abort( - grpc.StatusCode.NOT_FOUND, - f"node {node.name} missing service {request.name}", - ) - def GetEmaneEventChannel( self, request: GetEmaneEventChannelRequest, context: ServicerContext ) -> GetEmaneEventChannelResponse: diff --git a/daemon/proto/core/api/grpc/configservices.proto b/daemon/proto/core/api/grpc/configservices.proto index 7a71ab8c..401a9198 100644 --- a/daemon/proto/core/api/grpc/configservices.proto +++ b/daemon/proto/core/api/grpc/configservices.proto @@ -66,14 +66,3 @@ message GetNodeConfigServiceRequest { message GetNodeConfigServiceResponse { map config = 1; } - -message SetNodeConfigServiceRequest { - int32 session_id = 1; - int32 node_id = 2; - string name = 3; - map config = 4; -} - -message SetNodeConfigServiceResponse { - bool result = 1; -} diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 0466a449..c0f735f3 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -87,8 +87,6 @@ service CoreApi { } rpc GetNodeConfigService (configservices.GetNodeConfigServiceRequest) returns (configservices.GetNodeConfigServiceResponse) { } - rpc SetNodeConfigService (configservices.SetNodeConfigServiceRequest) returns (configservices.SetNodeConfigServiceResponse) { - } // wlan rpc rpc GetWlanConfig (wlan.GetWlanConfigRequest) returns (wlan.GetWlanConfigResponse) { From 8108db545acae606e96f53e70d1ab9f5171af6a6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 16:58:51 -0700 Subject: [PATCH 038/110] grpc: combined get services and get config services into new get config call, that can be used to get all daemon configuration information --- daemon/core/api/grpc/client.py | 34 ++------ daemon/core/api/grpc/server.py | 80 +++++++------------ daemon/core/api/grpc/wrappers.py | 14 +++- daemon/core/gui/coreclient.py | 8 +- .../proto/core/api/grpc/configservices.proto | 8 -- daemon/proto/core/api/grpc/core.proto | 18 +++-- daemon/proto/core/api/grpc/services.proto | 8 -- daemon/tests/test_grpcw.py | 11 --- 8 files changed, 66 insertions(+), 115 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 29e65daf..93233ec6 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -23,10 +23,9 @@ from core.api.grpc import ( ) from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsRequest, - GetConfigServicesRequest, GetNodeConfigServiceRequest, ) -from core.api.grpc.core_pb2 import ExecuteScriptRequest +from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest from core.api.grpc.emane_pb2 import ( EmaneLinkRequest, GetEmaneConfigRequest, @@ -46,7 +45,6 @@ from core.api.grpc.services_pb2 import ( GetNodeServiceFileRequest, GetNodeServiceRequest, GetServiceDefaultsRequest, - GetServicesRequest, ServiceActionRequest, ServiceDefaults, SetServiceDefaultsRequest, @@ -814,19 +812,15 @@ class CoreGrpcClient: response = self.stub.MobilityAction(request) return response.result - def get_services(self) -> List[wrappers.Service]: + def get_config(self) -> wrappers.CoreConfig: """ - Get all currently loaded services. + Retrieve the current core configuration values. - :return: list of services, name and groups only + :return: core configuration """ - request = GetServicesRequest() - response = self.stub.GetServices(request) - services = [] - for service_proto in response.services: - service = wrappers.Service.from_proto(service_proto) - services.append(service) - return services + request = GetConfigRequest() + response = self.stub.GetConfig(request) + return wrappers.CoreConfig.from_proto(response) def get_service_defaults(self, session_id: int) -> List[wrappers.ServiceDefault]: """ @@ -1086,20 +1080,6 @@ class CoreGrpcClient: response = self.stub.GetInterfaces(request) return list(response.ifaces) - def get_config_services(self) -> List[wrappers.ConfigService]: - """ - Retrieve all known config services. - - :return: list of config services - """ - request = GetConfigServicesRequest() - response = self.stub.GetConfigServices(request) - services = [] - for service_proto in response.services: - service = wrappers.ConfigService.from_proto(service_proto) - services.append(service) - return services - def get_config_service_defaults(self, name: str) -> wrappers.ConfigServiceDefaults: """ Retrieves config service default values. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index bd6a758e..c71c0a2a 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -24,8 +24,6 @@ from core.api.grpc.configservices_pb2 import ( ConfigService, GetConfigServiceDefaultsRequest, GetConfigServiceDefaultsResponse, - GetConfigServicesRequest, - GetConfigServicesResponse, GetNodeConfigServiceRequest, GetNodeConfigServiceResponse, ) @@ -66,8 +64,6 @@ from core.api.grpc.services_pb2 import ( GetNodeServiceResponse, GetServiceDefaultsRequest, GetServiceDefaultsResponse, - GetServicesRequest, - GetServicesResponse, Service, ServiceAction, ServiceActionRequest, @@ -190,6 +186,35 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): context.abort(grpc.StatusCode.NOT_FOUND, f"unknown service {name}") return service + def GetConfig( + self, request: core_pb2.GetConfigRequest, context: ServicerContext + ) -> core_pb2.GetConfigResponse: + services = [] + for name in ServiceManager.services: + service = ServiceManager.services[name] + service_proto = Service(group=service.group, name=service.name) + services.append(service_proto) + config_services = [] + for service in self.coreemu.service_manager.services.values(): + service_proto = ConfigService( + name=service.name, + group=service.group, + executables=service.executables, + dependencies=service.dependencies, + directories=service.directories, + files=service.files, + startup=service.startup, + validate=service.validate, + shutdown=service.shutdown, + validation_mode=service.validation_mode.value, + validation_timer=service.validation_timer, + validation_period=service.validation_period, + ) + config_services.append(service_proto) + return core_pb2.GetConfigResponse( + services=services, config_services=config_services + ) + def StartSession( self, request: core_pb2.StartSessionRequest, context: ServicerContext ) -> core_pb2.StartSessionResponse: @@ -954,24 +979,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): result = False return MobilityActionResponse(result=result) - def GetServices( - self, request: GetServicesRequest, context: ServicerContext - ) -> GetServicesResponse: - """ - Retrieve all the services that are running - - :param request: get-service request - :param context: context object - :return: get-services response - """ - logger.debug("get services: %s", request) - services = [] - for name in ServiceManager.services: - service = ServiceManager.services[name] - service_proto = Service(group=service.group, name=service.name) - services.append(service_proto) - return GetServicesResponse(services=services) - def GetServiceDefaults( self, request: GetServiceDefaultsRequest, context: ServicerContext ) -> GetServiceDefaultsResponse: @@ -1318,35 +1325,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): else: return EmaneLinkResponse(result=False) - def GetConfigServices( - self, request: GetConfigServicesRequest, context: ServicerContext - ) -> GetConfigServicesResponse: - """ - Gets all currently known configuration services. - - :param request: get config services request - :param context: grpc context - :return: get config services response - """ - services = [] - for service in self.coreemu.service_manager.services.values(): - service_proto = ConfigService( - name=service.name, - group=service.group, - executables=service.executables, - dependencies=service.dependencies, - directories=service.directories, - files=service.files, - startup=service.startup, - validate=service.validate, - shutdown=service.shutdown, - validation_mode=service.validation_mode.value, - validation_timer=service.validation_timer, - validation_period=service.validation_period, - ) - services.append(service_proto) - return GetConfigServicesResponse(services=services) - def GetNodeConfigService( self, request: GetNodeConfigServiceRequest, context: ServicerContext ) -> GetNodeConfigServiceResponse: diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index 41f7d7b8..0dcb5b91 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -175,7 +175,7 @@ class ConfigServiceDefaults: @classmethod def from_proto( - cls, proto: configservices_pb2.GetConfigServicesResponse + cls, proto: configservices_pb2.GetConfigServiceDefaultsResponse ) -> "ConfigServiceDefaults": config = ConfigOption.from_dict(proto.config) modes = {x.name: dict(x.config) for x in proto.modes} @@ -886,6 +886,18 @@ class Session: self.options[key] = option +@dataclass +class CoreConfig: + services: List[Service] = field(default_factory=list) + config_services: List[ConfigService] = field(default_factory=list) + + @classmethod + def from_proto(cls, proto: core_pb2.GetConfigResponse) -> "CoreConfig": + services = [Service.from_proto(x) for x in proto.services] + config_services = [ConfigService.from_proto(x) for x in proto.config_services] + return CoreConfig(services=services, config_services=config_services) + + @dataclass class LinkEvent: message_type: MessageType diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 2cdf31d0..f22ef5b4 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -389,12 +389,12 @@ class CoreClient: """ try: self.client.connect() - # get all available services - for service in self.client.get_services(): + # get current core configurations services/config services + core_config = self.client.get_config() + for service in core_config.services: group_services = self.services.setdefault(service.group, set()) group_services.add(service.name) - # get config service informations - for service in self.client.get_config_services(): + for service in core_config.config_services: self.config_services[service.name] = service group_services = self.config_services_groups.setdefault( service.group, set() diff --git a/daemon/proto/core/api/grpc/configservices.proto b/daemon/proto/core/api/grpc/configservices.proto index 401a9198..189a2892 100644 --- a/daemon/proto/core/api/grpc/configservices.proto +++ b/daemon/proto/core/api/grpc/configservices.proto @@ -39,14 +39,6 @@ message ConfigMode { map config = 2; } -message GetConfigServicesRequest { - int32 session_id = 1; -} - -message GetConfigServicesResponse { - repeated ConfigService services = 1; -} - message GetConfigServiceDefaultsRequest { string name = 1; } diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index c0f735f3..8083a8a7 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -73,6 +73,8 @@ service CoreApi { } // service rpc + rpc GetServiceDefaults (services.GetServiceDefaultsRequest) returns (services.GetServiceDefaultsResponse) { + } rpc SetServiceDefaults (services.SetServiceDefaultsRequest) returns (services.SetServiceDefaultsResponse) { } rpc GetNodeService (services.GetNodeServiceRequest) returns (services.GetNodeServiceResponse) { @@ -127,15 +129,21 @@ service CoreApi { // globals rpc GetEmaneModels (emane.GetEmaneModelsRequest) returns (emane.GetEmaneModelsResponse) { } - rpc GetConfigServices (configservices.GetConfigServicesRequest) returns (configservices.GetConfigServicesResponse) { - } - rpc GetServices (services.GetServicesRequest) returns (services.GetServicesResponse) { - } - rpc GetServiceDefaults (services.GetServiceDefaultsRequest) returns (services.GetServiceDefaultsResponse) { + rpc GetConfig (GetConfigRequest) returns (GetConfigResponse) { } } // rpc request/response messages +message GetConfigRequest { +} + +message GetConfigResponse { + repeated services.Service services = 1; + repeated configservices.ConfigService config_services = 2; + repeated string emane_models = 3; +} + + message StartSessionRequest { int32 session_id = 1; repeated Node nodes = 2; diff --git a/daemon/proto/core/api/grpc/services.proto b/daemon/proto/core/api/grpc/services.proto index 602131a3..dc451c40 100644 --- a/daemon/proto/core/api/grpc/services.proto +++ b/daemon/proto/core/api/grpc/services.proto @@ -66,14 +66,6 @@ message NodeServiceConfig { map files = 4; } -message GetServicesRequest { - -} - -message GetServicesResponse { - repeated Service services = 1; -} - message GetServiceDefaultsRequest { int32 session_id = 1; } diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpcw.py index 10d9022c..720fb059 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpcw.py @@ -670,17 +670,6 @@ class TestGrpcw: # then assert result is True - def test_get_services(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - - # then - with client.context_connect(): - services = client.get_services() - - # then - assert len(services) > 0 - def test_get_service_defaults(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From 7e6b87101bb45d0519765d1ce45458a85cb95c78 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 4 May 2021 17:02:48 -0700 Subject: [PATCH 039/110] updated grpc wrapper client test to be just the grpc client test --- daemon/tests/{test_grpcw.py => test_grpc.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename daemon/tests/{test_grpcw.py => test_grpc.py} (99%) diff --git a/daemon/tests/test_grpcw.py b/daemon/tests/test_grpc.py similarity index 99% rename from daemon/tests/test_grpcw.py rename to daemon/tests/test_grpc.py index 720fb059..bd3c92d3 100644 --- a/daemon/tests/test_grpcw.py +++ b/daemon/tests/test_grpc.py @@ -44,7 +44,7 @@ from core.nodes.network import SwitchNode, WlanNode from core.xml.corexml import CoreXmlWriter -class TestGrpcw: +class TestGrpc: @pytest.mark.parametrize("definition", [False, True]) def test_start_session(self, grpc_server: CoreGrpcServer, definition): # given From d40435fa68288eba6c321eb4097da2c7130552d7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 6 May 2021 10:56:51 -0700 Subject: [PATCH 040/110] grpc: removed add session server, achieved with start session providing servers for session --- daemon/core/api/grpc/client.py | 18 ++-------------- daemon/core/api/grpc/server.py | 24 ++++++++-------------- daemon/core/api/grpc/wrappers.py | 16 +++++++++++++++ daemon/core/gui/coreclient.py | 9 ++++---- daemon/examples/grpc/distributed_switch.py | 10 ++++----- daemon/proto/core/api/grpc/core.proto | 19 +++++++---------- 6 files changed, 42 insertions(+), 54 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 93233ec6..e886c7e7 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -307,6 +307,7 @@ class CoreGrpcClient: ) config_service_configs.append(config_service_config) options = {k: v.value for k, v in session.options.items()} + servers = [x.to_proto() for x in session.servers] request = core_pb2.StartSessionRequest( session_id=session.id, nodes=nodes, @@ -325,6 +326,7 @@ class CoreGrpcClient: user=session.user, definition=definition, metadata=session.metadata, + servers=servers, ) response = self.stub.StartSession(request) return response.result, list(response.exceptions) @@ -421,22 +423,6 @@ class CoreGrpcClient: response = self.stub.SetSessionState(request) return response.result - def add_session_server(self, session_id: int, name: str, host: str) -> bool: - """ - Add distributed session server. - - :param session_id: id of session - :param name: name of server to add - :param host: host address to connect to - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.AddSessionServerRequest( - session_id=session_id, name=name, host=host - ) - response = self.stub.AddSessionServer(request) - return response.result - def alert( self, session_id: int, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index c71c0a2a..4c546d8e 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -244,6 +244,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.options.set_config(key, value) session.metadata = dict(request.metadata) + # add servers + for server in request.servers: + session.distributed.add_server(server.name, server.host) + # location if request.HasField("location"): grpcutils.session_location(session, request.location) @@ -477,6 +481,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): config_service_configs = grpcutils.get_node_config_service_configs(session) session_file = str(session.file_path) if session.file_path else None options = get_config_options(session.options.get_configs(), session.options) + servers = [ + core_pb2.Server(name=x.name, host=x.host) + for x in session.distributed.servers.values() + ] session_proto = core_pb2.Session( id=session.id, state=session.state.value, @@ -497,24 +505,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): metadata=session.metadata, file=session_file, options=options, + servers=servers, ) return core_pb2.GetSessionResponse(session=session_proto) - def AddSessionServer( - self, request: core_pb2.AddSessionServerRequest, context: ServicerContext - ) -> core_pb2.AddSessionServerResponse: - """ - Add distributed server to a session. - - :param request: get-session - request - :param context: context object - :return: add session server response - """ - session = self.get_session(request.session_id, context) - session.distributed.add_server(request.name, request.host) - return core_pb2.AddSessionServerResponse(result=True) - def SessionAlert( self, request: core_pb2.SessionAlertRequest, context: ServicerContext ) -> core_pb2.SessionAlertResponse: diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index 0dcb5b91..f82e9d28 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -184,6 +184,19 @@ class ConfigServiceDefaults: ) +@dataclass +class Server: + name: str + host: str + + @classmethod + def from_proto(cls, proto: core_pb2.Server) -> "Server": + return Server(name=proto.name, host=proto.host) + + def to_proto(self) -> core_pb2.Server: + return core_pb2.Server(name=self.name, host=self.host) + + @dataclass class Service: group: str @@ -775,6 +788,7 @@ class Session: metadata: Dict[str, str] = field(default_factory=dict) file: Path = None options: Dict[str, ConfigOption] = field(default_factory=dict) + servers: List[Server] = field(default_factory=list) @classmethod def from_proto(cls, proto: core_pb2.Session) -> "Session": @@ -812,6 +826,7 @@ class Session: node.mobility_config = ConfigOption.from_dict(mapped_config.config) file_path = Path(proto.file) if proto.file else None options = ConfigOption.from_dict(proto.options) + servers = [Server.from_proto(x) for x in proto.servers] return Session( id=proto.id, state=SessionState(proto.state), @@ -827,6 +842,7 @@ class Session: metadata=dict(proto.metadata), file=file_path, options=options, + servers=servers, ) def add_node( diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index f22ef5b4..2c05bd76 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -28,6 +28,7 @@ from core.api.grpc.wrappers import ( NodeServiceData, NodeType, Position, + Server, ServiceConfig, ServiceFileConfig, Session, @@ -433,10 +434,6 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("Edit Node Error", e) - def send_servers(self) -> None: - for server in self.servers.values(): - self.client.add_session_server(self.session.id, server.name, server.address) - def get_links(self, definition: bool = False) -> Tuple[List[Link], List[Link]]: if not definition: self.ifaces_manager.set_macs([x.link for x in self.links.values()]) @@ -457,10 +454,12 @@ class CoreClient: links, asym_links = self.get_links(definition) self.session.links = links self.session.metadata = self.get_metadata() + self.session.servers.clear() + for server in self.servers.values(): + self.session.servers.append(Server(name=server.name, host=server.address)) result = False exceptions = [] try: - self.send_servers() result, exceptions = self.client.start_session( self.session, asym_links, definition ) diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index 6503abbf..f7e650d0 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -2,7 +2,7 @@ import argparse import logging from core.api.grpc import client -from core.api.grpc.wrappers import NodeType, Position +from core.api.grpc.wrappers import NodeType, Position, Server def log_event(event): @@ -19,12 +19,10 @@ def main(args): # create session session = core.add_session() - logging.info("created session: %s", session.id) # add distributed server - server_name = "core2" - result = core.add_session_server(session.id, server_name, args.server) - logging.info("added session server: %s", result) + server = Server(name="core2", host=args.server) + session.servers.append(server) # handle events session may broadcast core.events(session.id, log_event) @@ -35,7 +33,7 @@ def main(args): position = Position(x=100, y=50) node1 = session.add_node(2, position=position) position = Position(x=200, y=50) - node2 = session.add_node(3, position=position, server=server_name) + node2 = session.add_node(3, position=position, server=server.name) # create links iface1 = interface_helper.create_iface(node1.id, 0) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 8083a8a7..039c889f 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -27,8 +27,6 @@ service CoreApi { } rpc SetSessionState (SetSessionStateRequest) returns (SetSessionStateResponse) { } - rpc AddSessionServer (AddSessionServerRequest) returns (AddSessionServerResponse) { - } rpc SessionAlert (SessionAlertRequest) returns (SessionAlertResponse) { } @@ -162,6 +160,7 @@ message StartSessionRequest { string user = 15; bool definition = 16; map metadata = 17; + repeated Server servers = 18; } message StartSessionResponse { @@ -226,16 +225,6 @@ message SetSessionStateResponse { bool result = 1; } -message AddSessionServerRequest { - int32 session_id = 1; - string name = 2; - string host = 3; -} - -message AddSessionServerResponse { - bool result = 1; -} - message SessionAlertRequest { int32 session_id = 1; ExceptionLevel.Enum level = 2; @@ -617,6 +606,7 @@ message Session { map metadata = 17; string file = 18; map options = 19; + repeated Server servers = 20; } message SessionSummary { @@ -707,3 +697,8 @@ message Geo { float lon = 2; float alt = 3; } + +message Server { + string name = 1; + string host = 2; +} From 598cb0f10dae24d5d5aa73455b2282356333c250 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 6 May 2021 11:39:18 -0700 Subject: [PATCH 041/110] grpc: removed set session state, states are a by product of actions done within a session and probably best to automate that instead of relying on clients responsibly setting proper values --- daemon/core/api/grpc/client.py | 15 -------------- daemon/core/api/grpc/server.py | 29 --------------------------- daemon/proto/core/api/grpc/core.proto | 11 ---------- daemon/tests/test_grpc.py | 13 ------------ 4 files changed, 68 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index e886c7e7..a676c7c2 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -408,21 +408,6 @@ class CoreGrpcClient: response = self.stub.GetSession(request) return wrappers.Session.from_proto(response.session) - def set_session_state(self, session_id: int, state: wrappers.SessionState) -> bool: - """ - Set session state. - - :param session_id: id of session - :param state: session state to transition to - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.SetSessionStateRequest( - session_id=session_id, state=state.value - ) - response = self.stub.SetSessionState(request) - return response.result - def alert( self, session_id: int, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 4c546d8e..90e95317 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -402,35 +402,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): sessions.append(session_summary) return core_pb2.GetSessionsResponse(sessions=sessions) - def SetSessionState( - self, request: core_pb2.SetSessionStateRequest, context: ServicerContext - ) -> core_pb2.SetSessionStateResponse: - """ - Set session state - - :param request: set-session-state request - :param context:context object - :return: set-session-state response - """ - logger.debug("set session state: %s", request) - session = self.get_session(request.session_id, context) - try: - state = EventTypes(request.state) - session.set_state(state) - if state == EventTypes.INSTANTIATION_STATE: - session.directory.mkdir(exist_ok=True) - session.instantiate() - elif state == EventTypes.SHUTDOWN_STATE: - session.shutdown() - elif state == EventTypes.DATACOLLECT_STATE: - session.data_collect() - elif state == EventTypes.DEFINITION_STATE: - session.clear() - result = True - except KeyError: - result = False - return core_pb2.SetSessionStateResponse(result=result) - def CheckSession( self, request: core_pb2.GetSessionRequest, context: ServicerContext ) -> core_pb2.CheckSessionResponse: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 039c889f..80cbecff 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -25,8 +25,6 @@ service CoreApi { } rpc CheckSession (CheckSessionRequest) returns (CheckSessionResponse) { } - rpc SetSessionState (SetSessionStateRequest) returns (SetSessionStateResponse) { - } rpc SessionAlert (SessionAlertRequest) returns (SessionAlertResponse) { } @@ -216,15 +214,6 @@ message GetSessionResponse { Session session = 1; } -message SetSessionStateRequest { - int32 session_id = 1; - SessionState.Enum state = 2; -} - -message SetSessionStateResponse { - bool result = 1; -} - message SessionAlertRequest { int32 session_id = 1; ExceptionLevel.Enum level = 2; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index bd3c92d3..73485386 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -243,19 +243,6 @@ class TestGrpc: assert len(sessions) == 1 assert found_session is not None - def test_set_session_state(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - result = client.set_session_state(session.id, SessionState.DEFINITION) - - # then - assert result is True - assert session.state == EventTypes.DEFINITION_STATE - def test_add_node(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() From 53ae6ac7849370f591485da801db92bfb7d3b420 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 6 May 2021 15:06:16 -0700 Subject: [PATCH 042/110] grpc: updated create session to return a session object with default values, avoids scripts needing to create and then join, can just create and leverage the returned object --- daemon/core/api/grpc/client.py | 8 +- daemon/core/api/grpc/grpcutils.py | 58 ++++++++++- daemon/core/api/grpc/server.py | 110 ++++++++++----------- daemon/core/gui/coreclient.py | 6 +- daemon/examples/grpc/distributed_switch.py | 2 +- daemon/examples/grpc/emane80211.py | 2 +- daemon/examples/grpc/peertopeer.py | 2 +- daemon/examples/grpc/switch.py | 2 +- daemon/examples/grpc/wlan.py | 2 +- daemon/proto/core/api/grpc/core.proto | 3 +- daemon/tests/test_grpc.py | 16 +-- docs/grpc.md | 12 +-- 12 files changed, 136 insertions(+), 87 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index a676c7c2..d22b198e 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -343,11 +343,7 @@ class CoreGrpcClient: response = self.stub.StopSession(request) return response.result - def add_session(self, session_id: int = None) -> wrappers.Session: - session_id = self.create_session(session_id) - return self.get_session(session_id) - - def create_session(self, session_id: int = None) -> int: + def create_session(self, session_id: int = None) -> wrappers.Session: """ Create a session. @@ -357,7 +353,7 @@ class CoreGrpcClient: """ request = core_pb2.CreateSessionRequest(session_id=session_id) response = self.stub.CreateSession(request) - return response.session_id + return wrappers.Session.from_proto(response.session) def delete_session(self, session_id: int) -> bool: """ diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 7f9099aa..1f46ba5b 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -7,7 +7,7 @@ import grpc from grpc import ServicerContext from core import utils -from core.api.grpc import common_pb2, core_pb2 +from core.api.grpc import common_pb2, core_pb2, wrappers from core.api.grpc.common_pb2 import MappedConfig from core.api.grpc.configservices_pb2 import ConfigServiceConfig from core.api.grpc.emane_pb2 import GetEmaneModelConfig @@ -28,7 +28,7 @@ from core.nodes.base import CoreNode, CoreNodeBase, NodeBase from core.nodes.docker import DockerNode from core.nodes.interface import CoreInterface from core.nodes.lxd import LxcNode -from core.nodes.network import WlanNode +from core.nodes.network import CtrlNet, PtpNet, WlanNode from core.services.coreservices import CoreService logger = logging.getLogger(__name__) @@ -657,3 +657,57 @@ def get_mobility_node( return session.get_node(node_id, EmaneNet) except CoreError: context.abort(grpc.StatusCode.NOT_FOUND, "node id is not for wlan or emane") + + +def convert_session(session: Session) -> wrappers.Session: + links = [] + nodes = [] + for _id in session.nodes: + node = session.nodes[_id] + if not isinstance(node, (PtpNet, CtrlNet)): + node_proto = get_node_proto(session, node) + nodes.append(node_proto) + node_links = get_links(node) + links.extend(node_links) + default_services = get_default_services(session) + x, y, z = session.location.refxyz + lat, lon, alt = session.location.refgeo + location = core_pb2.SessionLocation( + x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale + ) + hooks = get_hooks(session) + emane_models = get_emane_models(session) + emane_config = get_emane_config(session) + emane_model_configs = get_emane_model_configs(session) + wlan_configs = get_wlan_configs(session) + mobility_configs = get_mobility_configs(session) + service_configs = get_node_service_configs(session) + config_service_configs = get_node_config_service_configs(session) + session_file = str(session.file_path) if session.file_path else None + options = get_config_options(session.options.get_configs(), session.options) + servers = [ + core_pb2.Server(name=x.name, host=x.host) + for x in session.distributed.servers.values() + ] + return core_pb2.Session( + id=session.id, + state=session.state.value, + nodes=nodes, + links=links, + dir=str(session.directory), + user=session.user, + default_services=default_services, + location=location, + hooks=hooks, + emane_models=emane_models, + emane_config=emane_config, + emane_model_configs=emane_model_configs, + wlan_configs=wlan_configs, + service_configs=service_configs, + config_service_configs=config_service_configs, + mobility_configs=mobility_configs, + metadata=session.metadata, + file=session_file, + options=options, + servers=servers, + ) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 90e95317..b64b670c 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -91,7 +91,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, NodeBase -from core.nodes.network import CtrlNet, PtpNet, WlanNode +from core.nodes.network import WlanNode from core.services.coreservices import ServiceManager logger = logging.getLogger(__name__) @@ -359,9 +359,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.set_state(EventTypes.DEFINITION_STATE) session.location.setrefgeo(47.57917, -122.13232, 2.0) session.location.refscale = 150.0 - return core_pb2.CreateSessionResponse( - session_id=session.id, state=session.state.value - ) + session_proto = grpcutils.convert_session(session) + return core_pb2.CreateSessionResponse(session=session_proto) def DeleteSession( self, request: core_pb2.DeleteSessionRequest, context: ServicerContext @@ -427,57 +426,58 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logger.debug("get session: %s", request) session = self.get_session(request.session_id, context) - links = [] - nodes = [] - for _id in session.nodes: - node = session.nodes[_id] - if not isinstance(node, (PtpNet, CtrlNet)): - node_proto = grpcutils.get_node_proto(session, node) - nodes.append(node_proto) - node_links = get_links(node) - links.extend(node_links) - default_services = grpcutils.get_default_services(session) - x, y, z = session.location.refxyz - lat, lon, alt = session.location.refgeo - location = core_pb2.SessionLocation( - x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale - ) - hooks = grpcutils.get_hooks(session) - emane_models = grpcutils.get_emane_models(session) - emane_config = grpcutils.get_emane_config(session) - emane_model_configs = grpcutils.get_emane_model_configs(session) - wlan_configs = grpcutils.get_wlan_configs(session) - mobility_configs = grpcutils.get_mobility_configs(session) - service_configs = grpcutils.get_node_service_configs(session) - config_service_configs = grpcutils.get_node_config_service_configs(session) - session_file = str(session.file_path) if session.file_path else None - options = get_config_options(session.options.get_configs(), session.options) - servers = [ - core_pb2.Server(name=x.name, host=x.host) - for x in session.distributed.servers.values() - ] - session_proto = core_pb2.Session( - id=session.id, - state=session.state.value, - nodes=nodes, - links=links, - dir=str(session.directory), - user=session.user, - default_services=default_services, - location=location, - hooks=hooks, - emane_models=emane_models, - emane_config=emane_config, - emane_model_configs=emane_model_configs, - wlan_configs=wlan_configs, - service_configs=service_configs, - config_service_configs=config_service_configs, - mobility_configs=mobility_configs, - metadata=session.metadata, - file=session_file, - options=options, - servers=servers, - ) + # links = [] + # nodes = [] + # for _id in session.nodes: + # node = session.nodes[_id] + # if not isinstance(node, (PtpNet, CtrlNet)): + # node_proto = grpcutils.get_node_proto(session, node) + # nodes.append(node_proto) + # node_links = get_links(node) + # links.extend(node_links) + # default_services = grpcutils.get_default_services(session) + # x, y, z = session.location.refxyz + # lat, lon, alt = session.location.refgeo + # location = core_pb2.SessionLocation( + # x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale + # ) + # hooks = grpcutils.get_hooks(session) + # emane_models = grpcutils.get_emane_models(session) + # emane_config = grpcutils.get_emane_config(session) + # emane_model_configs = grpcutils.get_emane_model_configs(session) + # wlan_configs = grpcutils.get_wlan_configs(session) + # mobility_configs = grpcutils.get_mobility_configs(session) + # service_configs = grpcutils.get_node_service_configs(session) + # config_service_configs = grpcutils.get_node_config_service_configs(session) + # session_file = str(session.file_path) if session.file_path else None + # options = get_config_options(session.options.get_configs(), session.options) + # servers = [ + # core_pb2.Server(name=x.name, host=x.host) + # for x in session.distributed.servers.values() + # ] + # session_proto = core_pb2.Session( + # id=session.id, + # state=session.state.value, + # nodes=nodes, + # links=links, + # dir=str(session.directory), + # user=session.user, + # default_services=default_services, + # location=location, + # hooks=hooks, + # emane_models=emane_models, + # emane_config=emane_config, + # emane_model_configs=emane_model_configs, + # wlan_configs=wlan_configs, + # service_configs=service_configs, + # config_service_configs=config_service_configs, + # mobility_configs=mobility_configs, + # metadata=session.metadata, + # file=session_file, + # options=options, + # servers=servers, + # ) + session_proto = grpcutils.convert_session(session) return core_pb2.GetSessionResponse(session=session_proto) def SessionAlert( diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 2c05bd76..0ed7a356 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -357,9 +357,9 @@ class CoreClient: Create a new session """ try: - session_id = self.client.create_session() - logger.info("created session: %s", session_id) - self.join_session(session_id) + session = self.client.create_session() + logger.info("created session: %s", session.id) + self.join_session(session.id) location_config = self.app.guiconfig.location self.session.location = SessionLocation( x=location_config.x, diff --git a/daemon/examples/grpc/distributed_switch.py b/daemon/examples/grpc/distributed_switch.py index f7e650d0..52c327b4 100644 --- a/daemon/examples/grpc/distributed_switch.py +++ b/daemon/examples/grpc/distributed_switch.py @@ -18,7 +18,7 @@ def main(args): core.connect() # create session - session = core.add_session() + session = core.create_session() # add distributed server server = Server(name="core2", host=args.server) diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py index edb1f529..db8069c5 100644 --- a/daemon/examples/grpc/emane80211.py +++ b/daemon/examples/grpc/emane80211.py @@ -11,7 +11,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # create nodes position = Position(x=200, y=200) diff --git a/daemon/examples/grpc/peertopeer.py b/daemon/examples/grpc/peertopeer.py index 730128eb..d3c72dff 100644 --- a/daemon/examples/grpc/peertopeer.py +++ b/daemon/examples/grpc/peertopeer.py @@ -9,7 +9,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # create nodes position = Position(x=100, y=100) diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index ce8e2622..1b693cb6 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -9,7 +9,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # create nodes position = Position(x=200, y=200) diff --git a/daemon/examples/grpc/wlan.py b/daemon/examples/grpc/wlan.py index 561a772b..fbffb2c3 100644 --- a/daemon/examples/grpc/wlan.py +++ b/daemon/examples/grpc/wlan.py @@ -9,7 +9,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # create nodes position = Position(x=200, y=200) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 80cbecff..ebb6c3d2 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -179,8 +179,7 @@ message CreateSessionRequest { } message CreateSessionResponse { - int32 session_id = 1; - SessionState.Enum state = 2; + Session session = 1; } message DeleteSessionRequest { diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 73485386..0bb31221 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -8,7 +8,7 @@ import grpc import pytest from mock import patch -from core.api.grpc import core_pb2 +from core.api.grpc import core_pb2, wrappers from core.api.grpc.client import CoreGrpcClient, InterfaceHelper, MoveNodesStreamer from core.api.grpc.server import CoreGrpcServer from core.api.grpc.wrappers import ( @@ -50,8 +50,7 @@ class TestGrpc: # given client = CoreGrpcClient() with client.context_connect(): - session_id = client.create_session() - session = client.get_session(session_id) + session = client.create_session() position = Position(x=50, y=100) node1 = session.add_node(1, position=position) position = Position(x=100, y=100) @@ -181,14 +180,14 @@ class TestGrpc: # when with client.context_connect(): - created_session_id = client.create_session(session_id) + created_session = client.create_session(session_id) # then - assert isinstance(created_session_id, int) - session = grpc_server.coreemu.sessions.get(created_session_id) + 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 created_session.id == session_id assert session.id == session_id @pytest.mark.parametrize("session_id, expected", [(None, True), (6013, False)]) @@ -335,6 +334,7 @@ class TestGrpc: node = session.add_node(CoreNode, options=options) session.instantiate() expected_output = "hello world" + expected_status = 0 # then command = f"echo {expected_output}" @@ -342,7 +342,7 @@ class TestGrpc: output = client.node_command(session.id, node.id, command) # then - assert expected_output == output + assert (expected_status, expected_output) == output def test_get_node_terminal(self, grpc_server: CoreGrpcServer): # given diff --git a/docs/grpc.md b/docs/grpc.md index ca3ebe00..fafde661 100644 --- a/docs/grpc.md +++ b/docs/grpc.md @@ -109,7 +109,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # provide no events to listen to all events core.events(session.id, event_listener) @@ -141,7 +141,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # create nodes position = Position(x=100, y=100) @@ -180,7 +180,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # create nodes position = Position(x=100, y=100) @@ -211,7 +211,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # create nodes position = Position(x=200, y=200) @@ -245,7 +245,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # create nodes position = Position(x=200, y=200) @@ -310,7 +310,7 @@ core = client.CoreGrpcClient() core.connect() # add session -session = core.add_session() +session = core.create_session() # create nodes position = Position(x=200, y=200) From 1ddb7b7b248358682e7ce515efa8b25929f81c4e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 6 May 2021 16:05:13 -0700 Subject: [PATCH 043/110] daemon: small cleanup for service loading in CoreEmu --- daemon/core/emulator/coreemu.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index c2864157..e6d1615d 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -50,8 +50,7 @@ class CoreEmu: os.umask(0) # configuration - if config is None: - config = {} + config = config if config else {} self.config: Dict[str, str] = config # session management @@ -59,16 +58,8 @@ class CoreEmu: # load services self.service_errors: List[str] = [] - self.load_services() - - # config services self.service_manager: ConfigServiceManager = ConfigServiceManager() - config_services_path = Path(configservices.__file__).resolve().parent - self.service_manager.load(config_services_path) - custom_dir = self.config.get("custom_config_services_dir") - if custom_dir is not None: - custom_dir = Path(custom_dir) - self.service_manager.load(custom_dir) + self._load_services() # check executables exist on path self._validate_env() @@ -87,7 +78,7 @@ class CoreEmu: for requirement in get_requirements(use_ovs): utils.which(requirement, required=True) - def load_services(self) -> None: + def _load_services(self) -> None: """ Loads default and custom services for use within CORE. @@ -103,6 +94,14 @@ class CoreEmu: service_path = Path(service_path.strip()) custom_service_errors = ServiceManager.add_services(service_path) self.service_errors.extend(custom_service_errors) + # load default config services + config_services_path = Path(configservices.__file__).resolve().parent + self.service_manager.load(config_services_path) + # load custom config services + custom_dir = self.config.get("custom_config_services_dir") + if custom_dir is not None: + custom_dir = Path(custom_dir) + self.service_manager.load(custom_dir) def shutdown(self) -> None: """ From 50e3aadc6b967582055709efc2c7f681b9c96821 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 7 May 2021 10:40:18 -0700 Subject: [PATCH 044/110] daemon: refactoring to load emane models one time during startup, updates to account for this change --- daemon/core/api/grpc/client.py | 13 -- daemon/core/api/grpc/grpcutils.py | 25 +-- daemon/core/api/grpc/server.py | 93 ++-------- daemon/core/api/grpc/wrappers.py | 9 +- daemon/core/api/tlv/corehandlers.py | 29 ++- daemon/core/configservice/manager.py | 2 +- daemon/core/emane/emanemanager.py | 165 +++++++++--------- daemon/core/emane/linkmonitor.py | 6 +- daemon/core/emane/modelmanager.py | 41 +++++ daemon/core/emane/models/__init__.py | 0 daemon/core/emane/{ => models}/bypass.py | 0 daemon/core/emane/{ => models}/commeffect.py | 0 .../core/emane/{ => models}/ieee80211abg.py | 0 daemon/core/emane/{ => models}/rfpipe.py | 0 daemon/core/emane/{ => models}/tdma.py | 15 +- daemon/core/emulator/coreemu.py | 34 ++++ daemon/core/emulator/session.py | 8 +- daemon/core/gui/coreclient.py | 6 +- daemon/core/gui/dialogs/emaneconfig.py | 2 +- daemon/core/xml/corexml.py | 22 +-- daemon/core/xml/emanexml.py | 2 +- daemon/examples/grpc/emane80211.py | 2 +- daemon/examples/python/distributed_emane.py | 2 +- daemon/examples/python/emane80211.py | 2 +- daemon/proto/core/api/grpc/core.proto | 23 ++- daemon/proto/core/api/grpc/emane.proto | 8 - daemon/tests/emane/test_emane.py | 48 +++-- daemon/tests/test_conf.py | 2 +- daemon/tests/test_grpc.py | 26 +-- daemon/tests/test_gui.py | 8 +- docs/grpc.md | 4 +- docs/python.md | 6 +- 32 files changed, 271 insertions(+), 332 deletions(-) create mode 100644 daemon/core/emane/modelmanager.py create mode 100644 daemon/core/emane/models/__init__.py rename daemon/core/emane/{ => models}/bypass.py (100%) rename daemon/core/emane/{ => models}/commeffect.py (100%) rename daemon/core/emane/{ => models}/ieee80211abg.py (100%) rename daemon/core/emane/{ => models}/rfpipe.py (100%) rename daemon/core/emane/{ => models}/tdma.py (84%) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index d22b198e..b07d2c04 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -31,7 +31,6 @@ from core.api.grpc.emane_pb2 import ( GetEmaneConfigRequest, GetEmaneEventChannelRequest, GetEmaneModelConfigRequest, - GetEmaneModelsRequest, SetEmaneConfigRequest, SetEmaneModelConfigRequest, ) @@ -943,18 +942,6 @@ class CoreGrpcClient: response = self.stub.SetEmaneConfig(request) return response.result - def get_emane_models(self, session_id: int) -> List[str]: - """ - Get session emane models. - - :param session_id: session id - :return: list of emane models - :raises grpc.RpcError: when session doesn't exist - """ - request = GetEmaneModelsRequest(session_id=session_id) - response = self.stub.GetEmaneModels(request) - return list(response.models) - def get_emane_model_config( self, session_id: int, node_id: int, model: str, iface_id: int = -1 ) -> Dict[str, wrappers.ConfigOption]: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 1f46ba5b..67e11c5e 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -532,14 +532,11 @@ def get_nem_id( def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]: configs = [] - for _id in session.emane.node_configurations: - if _id == -1: - continue - model_configs = session.emane.node_configurations[_id] + for _id, model_configs in session.emane.node_configs.items(): for model_name in model_configs: - model = session.emane.models[model_name] - current_config = session.emane.get_model_config(_id, model_name) - config = get_config_options(current_config, model) + model_class = session.emane.get_model(model_name) + current_config = session.emane.get_config(_id, model_name) + config = get_config_options(current_config, model_class) node_id, iface_id = utils.parse_iface_config_id(_id) iface_id = iface_id if iface_id is not None else -1 model_config = GetEmaneModelConfig( @@ -591,15 +588,6 @@ def get_hooks(session: Session) -> List[core_pb2.Hook]: return hooks -def get_emane_models(session: Session) -> List[str]: - emane_models = [] - for model in session.emane.models.keys(): - if len(model.split("_")) != 2: - continue - emane_models.append(model) - return emane_models - - def get_default_services(session: Session) -> List[ServiceDefaults]: default_services = [] for name, services in session.services.default_services.items(): @@ -643,8 +631,7 @@ def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfi def get_emane_config(session: Session) -> Dict[str, common_pb2.ConfigOption]: - current_config = session.emane.get_configs() - return get_config_options(current_config, session.emane.emane_config) + return get_config_options(session.emane.config, session.emane.emane_config) def get_mobility_node( @@ -676,7 +663,6 @@ def convert_session(session: Session) -> wrappers.Session: x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale ) hooks = get_hooks(session) - emane_models = get_emane_models(session) emane_config = get_emane_config(session) emane_model_configs = get_emane_model_configs(session) wlan_configs = get_wlan_configs(session) @@ -699,7 +685,6 @@ def convert_session(session: Session) -> wrappers.Session: default_services=default_services, location=location, hooks=hooks, - emane_models=emane_models, emane_config=emane_config, emane_model_configs=emane_model_configs, wlan_configs=wlan_configs, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index b64b670c..8b5c5c1f 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -39,8 +39,6 @@ from core.api.grpc.emane_pb2 import ( GetEmaneEventChannelResponse, GetEmaneModelConfigRequest, GetEmaneModelConfigResponse, - GetEmaneModelsRequest, - GetEmaneModelsResponse, SetEmaneConfigRequest, SetEmaneConfigResponse, SetEmaneModelConfigRequest, @@ -79,6 +77,7 @@ from core.api.grpc.wlan_pb2 import ( WlanLinkRequest, WlanLinkResponse, ) +from core.emane.modelmanager import EmaneModelManager from core.emulator.coreemu import CoreEmu from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions from core.emulator.enumerations import ( @@ -211,8 +210,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): validation_period=service.validation_period, ) config_services.append(service_proto) + emane_models = [x.name for x in EmaneModelManager.models.values()] return core_pb2.GetConfigResponse( - services=services, config_services=config_services + services=services, + config_services=config_services, + emane_models=emane_models, ) def StartSession( @@ -264,11 +266,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) # emane configs - config = session.emane.get_configs() - config.update(request.emane_config) + session.emane.config.update(request.emane_config) for config in request.emane_model_configs: _id = utils.iface_config_id(config.node_id, config.iface_id) - session.emane.set_model_config(_id, config.model, config.config) + session.emane.set_config(_id, config.model, config.config) # wlan configs for config in request.wlan_configs: @@ -426,57 +427,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logger.debug("get session: %s", request) session = self.get_session(request.session_id, context) - # links = [] - # nodes = [] - # for _id in session.nodes: - # node = session.nodes[_id] - # if not isinstance(node, (PtpNet, CtrlNet)): - # node_proto = grpcutils.get_node_proto(session, node) - # nodes.append(node_proto) - # node_links = get_links(node) - # links.extend(node_links) - # default_services = grpcutils.get_default_services(session) - # x, y, z = session.location.refxyz - # lat, lon, alt = session.location.refgeo - # location = core_pb2.SessionLocation( - # x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale - # ) - # hooks = grpcutils.get_hooks(session) - # emane_models = grpcutils.get_emane_models(session) - # emane_config = grpcutils.get_emane_config(session) - # emane_model_configs = grpcutils.get_emane_model_configs(session) - # wlan_configs = grpcutils.get_wlan_configs(session) - # mobility_configs = grpcutils.get_mobility_configs(session) - # service_configs = grpcutils.get_node_service_configs(session) - # config_service_configs = grpcutils.get_node_config_service_configs(session) - # session_file = str(session.file_path) if session.file_path else None - # options = get_config_options(session.options.get_configs(), session.options) - # servers = [ - # core_pb2.Server(name=x.name, host=x.host) - # for x in session.distributed.servers.values() - # ] - # session_proto = core_pb2.Session( - # id=session.id, - # state=session.state.value, - # nodes=nodes, - # links=links, - # dir=str(session.directory), - # user=session.user, - # default_services=default_services, - # location=location, - # hooks=hooks, - # emane_models=emane_models, - # emane_config=emane_config, - # emane_model_configs=emane_model_configs, - # wlan_configs=wlan_configs, - # service_configs=service_configs, - # config_service_configs=config_service_configs, - # mobility_configs=mobility_configs, - # metadata=session.metadata, - # file=session_file, - # options=options, - # servers=servers, - # ) session_proto = grpcutils.convert_session(session) return core_pb2.GetSessionResponse(session=session_proto) @@ -1122,25 +1072,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logger.debug("set emane config: %s", request) session = self.get_session(request.session_id, context) - config = session.emane.get_configs() - config.update(request.config) + session.emane.config.update(request.config) return SetEmaneConfigResponse(result=True) - def GetEmaneModels( - self, request: GetEmaneModelsRequest, context: ServicerContext - ) -> GetEmaneModelsResponse: - """ - Retrieve all the EMANE models in the session - - :param request: get-emane-model request - :param context: context object - :return: get-EMANE-models response that has all the models - """ - logger.debug("get emane models: %s", request) - session = self.get_session(request.session_id, context) - models = grpcutils.get_emane_models(session) - return GetEmaneModelsResponse(models=models) - def GetEmaneModelConfig( self, request: GetEmaneModelConfigRequest, context: ServicerContext ) -> GetEmaneModelConfigResponse: @@ -1154,11 +1088,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logger.debug("get emane model config: %s", request) session = self.get_session(request.session_id, context) - model = session.emane.models.get(request.model) - if not model: - raise CoreError(f"invalid emane model: {request.model}") + model = session.emane.get_model(request.model) _id = utils.iface_config_id(request.node_id, request.iface_id) - current_config = session.emane.get_model_config(_id, request.model) + current_config = session.emane.get_config(_id, request.model) config = get_config_options(current_config, model) return GetEmaneModelConfigResponse(config=config) @@ -1177,7 +1109,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session = self.get_session(request.session_id, context) model_config = request.emane_model_config _id = utils.iface_config_id(model_config.node_id, model_config.iface_id) - session.emane.set_model_config(_id, model_config.model, model_config.config) + session.emane.set_config(_id, model_config.model, model_config.config) return SetEmaneModelConfigResponse(result=True) def SaveXml( @@ -1192,13 +1124,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logger.debug("save xml: %s", request) session = self.get_session(request.session_id, context) - _, temp_path = tempfile.mkstemp() session.save_xml(temp_path) - with open(temp_path, "r") as xml_file: data = xml_file.read() - return core_pb2.SaveXmlResponse(data=data) def OpenXml( diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index f82e9d28..a0277edd 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -783,7 +783,6 @@ class Session: x=0.0, y=0.0, z=0.0, lat=47.57917, lon=-122.13232, alt=2.0, scale=150.0 ) hooks: Dict[str, Hook] = field(default_factory=dict) - emane_models: List[str] = field(default_factory=list) emane_config: Dict[str, ConfigOption] = field(default_factory=dict) metadata: Dict[str, str] = field(default_factory=dict) file: Path = None @@ -837,7 +836,6 @@ class Session: default_services=default_services, location=SessionLocation.from_proto(proto.location), hooks=hooks, - emane_models=list(proto.emane_models), emane_config=ConfigOption.from_dict(proto.emane_config), metadata=dict(proto.metadata), file=file_path, @@ -906,12 +904,17 @@ class Session: class CoreConfig: services: List[Service] = field(default_factory=list) config_services: List[ConfigService] = field(default_factory=list) + emane_models: List[str] = field(default_factory=list) @classmethod def from_proto(cls, proto: core_pb2.GetConfigResponse) -> "CoreConfig": services = [Service.from_proto(x) for x in proto.services] config_services = [ConfigService.from_proto(x) for x in proto.config_services] - return CoreConfig(services=services, config_services=config_services) + return CoreConfig( + services=services, + config_services=config_services, + emane_models=list(proto.emane_models), + ) @dataclass diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index fc8f9891..631b5491 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -29,6 +29,7 @@ from core.api.tlv.enumerations import ( NodeTlvs, SessionTlvs, ) +from core.emane.modelmanager import EmaneModelManager from core.emulator.data import ( ConfigData, EventData, @@ -419,8 +420,7 @@ class CoreHandler(socketserver.BaseRequestHandler): tlv_data += coreapi.CoreRegisterTlv.pack( self.session.emane.config_type.value, self.session.emane.name ) - for model_name in self.session.emane.models: - model_class = self.session.emane.models[model_name] + for model_name, model_class in EmaneModelManager.models.items(): tlv_data += coreapi.CoreRegisterTlv.pack( model_class.config_type.value, model_class.name ) @@ -1048,7 +1048,7 @@ class CoreHandler(socketserver.BaseRequestHandler): replies = self.handle_config_mobility_models(message_type, config_data) elif config_data.object == self.session.emane.name: replies = self.handle_config_emane(message_type, config_data) - elif config_data.object in self.session.emane.models: + elif config_data.object in EmaneModelManager.models: replies = self.handle_config_emane_models(message_type, config_data) else: raise Exception("no handler for configuration: %s", config_data.object) @@ -1393,7 +1393,7 @@ class CoreHandler(socketserver.BaseRequestHandler): if message_type == ConfigFlags.REQUEST: logger.info("replying to configure request for %s model", object_name) typeflags = ConfigFlags.NONE.value - config = self.session.emane.get_configs() + config = self.session.emane.config config_response = ConfigShim.config_data( 0, node_id, typeflags, self.session.emane.emane_config, config ) @@ -1405,7 +1405,7 @@ class CoreHandler(socketserver.BaseRequestHandler): if values_str: config = ConfigShim.str_to_dict(values_str) - self.session.emane.set_configs(config) + self.session.emane.config = config return replies @@ -1424,12 +1424,12 @@ class CoreHandler(socketserver.BaseRequestHandler): logger.info("replying to configure request for model: %s", object_name) typeflags = ConfigFlags.NONE.value - model_class = self.session.emane.models.get(object_name) + model_class = self.session.emane.get_model(object_name) if not model_class: logger.warning("model class does not exist: %s", object_name) return [] - config = self.session.emane.get_model_config(node_id, object_name) + config = self.session.emane.get_config(node_id, object_name) config_response = ConfigShim.config_data( 0, node_id, typeflags, model_class, config ) @@ -1439,12 +1439,11 @@ class CoreHandler(socketserver.BaseRequestHandler): if not object_name: logger.warning("no configuration object for node: %s", node_id) return [] - parsed_config = {} if values_str: parsed_config = ConfigShim.str_to_dict(values_str) - - self.session.emane.set_model_config(node_id, object_name, parsed_config) + self.session.emane.node_models[node_id] = object_name + self.session.emane.set_config(node_id, object_name, parsed_config) return replies @@ -1853,7 +1852,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.broadcast_config(config_data) # send global emane config - config = self.session.emane.get_configs() + config = self.session.emane.config logger.debug("global emane config: values(%s)", config) config_data = ConfigShim.config_data( 0, None, ConfigFlags.UPDATE.value, self.session.emane.emane_config, config @@ -1861,11 +1860,9 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.broadcast_config(config_data) # send emane model configs - for node_id in self.session.emane.nodes(): - emane_configs = self.session.emane.get_all_configs(node_id) - for model_name in emane_configs: - config = emane_configs[model_name] - model_class = self.session.emane.models[model_name] + for node_id, model_configs in self.session.emane.node_configs.items(): + for model_name, config in model_configs.items(): + model_class = self.session.emane.get_model(model_name) logger.debug( "emane config: node(%s) class(%s) values(%s)", node_id, diff --git a/daemon/core/configservice/manager.py b/daemon/core/configservice/manager.py index 254ce719..02812ea2 100644 --- a/daemon/core/configservice/manager.py +++ b/daemon/core/configservice/manager.py @@ -31,7 +31,7 @@ class ConfigServiceManager: """ service_class = self.services.get(name) if service_class is None: - raise CoreError(f"service does not exit {name}") + raise CoreError(f"service does not exist {name}") return service_class def add(self, service: Type[ConfigService]) -> None: diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 68c88b50..3eabf54e 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -12,16 +12,12 @@ from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type from core import utils -from core.config import ConfigGroup, Configuration, ModelManager +from core.config import ConfigGroup, Configuration from core.emane import emanemanifest -from core.emane.bypass import EmaneBypassModel -from core.emane.commeffect import EmaneCommEffectModel from core.emane.emanemodel import EmaneModel -from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.linkmonitor import EmaneLinkMonitor +from core.emane.modelmanager import EmaneModelManager from core.emane.nodes import EmaneNet -from core.emane.rfpipe import EmaneRfPipeModel -from core.emane.tdma import EmaneTdmaModel from core.emulator.data import LinkData from core.emulator.enumerations import ( ConfigDataTypes, @@ -55,15 +51,9 @@ except ImportError: EventServiceException = None logger.debug("compatible emane python bindings not installed") -EMANE_MODELS = [ - EmaneRfPipeModel, - EmaneIeee80211abgModel, - EmaneCommEffectModel, - EmaneBypassModel, - EmaneTdmaModel, -] DEFAULT_EMANE_PREFIX = "/usr" DEFAULT_DEV = "ctrl0" +DEFAULT_LOG_LEVEL: int = 3 class EmaneState(Enum): @@ -78,7 +68,7 @@ class StartData: ifaces: List[CoreInterface] = field(default_factory=list) -class EmaneManager(ModelManager): +class EmaneManager: """ EMANE controller object. Lives in a Session instance and is used for building EMANE config files for all EMANE networks in this emulation, and for @@ -87,9 +77,6 @@ class EmaneManager(ModelManager): name: str = "emane" config_type: RegisterTlvs = RegisterTlvs.EMULATION_SERVER - NOT_READY: int = 2 - EVENTCFGVAR: str = "LIBEMANEEVENTSERVICECONFIG" - DEFAULT_LOG_LEVEL: int = 3 def __init__(self, session: "Session") -> None: """ @@ -116,7 +103,9 @@ class EmaneManager(ModelManager): # model for global EMANE configuration options self.emane_config: EmaneGlobalModel = EmaneGlobalModel(session) - self.set_configs(self.emane_config.default_values()) + self.config: Dict[str, str] = self.emane_config.default_values() + self.node_configs: Dict[int, Dict[str, Dict[str, str]]] = {} + self.node_models: Dict[int, str] = {} # link monitor self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self) @@ -124,14 +113,63 @@ class EmaneManager(ModelManager): self.service: Optional[EventService] = None self.eventchannel: Optional[Tuple[str, int, str]] = None self.event_device: Optional[str] = None - self.emane_check() def next_nem_id(self) -> int: - nem_id = int(self.get_config("nem_id_start")) + nem_id = int(self.config["nem_id_start"]) while nem_id in self.nems_to_ifaces: nem_id += 1 return nem_id + def get_config( + self, key: int, model: str, default: bool = True + ) -> Optional[Dict[str, str]]: + """ + Get the current or default configuration for an emane model. + + :param key: key to get configuration for + :param model: emane model to get configuration for + :param default: True to return default configuration when none exists, False + otherwise + :return: emane model configuration + :raises CoreError: when model does not exist + """ + model_class = self.get_model(model) + model_configs = self.node_configs.get(key) + config = None + if model_configs: + config = model_configs.get(model) + if config is None and default: + config = model_class.default_values() + return config + + def set_config(self, key: int, model: str, config: Dict[str, str] = None) -> None: + """ + Sets and update the provided configuration against the default model + or currently set emane model configuration. + + :param key: configuration key to set + :param model: model to set configuration for + :param config: configuration to update current configuration with + :return: nothing + :raises CoreError: when model does not exist + """ + self.get_model(model) + model_config = self.get_config(key, model) + config = config if config else {} + model_config.update(config) + model_configs = self.node_configs.setdefault(key, {}) + model_configs[model] = model_config + + def get_model(self, model_name: str) -> Type[EmaneModel]: + """ + Convenience method for getting globally loaded emane models. + + :param model_name: name of model to retrieve + :return: emane model class + :raises CoreError: when model does not exist + """ + return EmaneModelManager.get(model_name) + def get_iface_config( self, emane_net: EmaneNet, iface: CoreInterface ) -> Dict[str, str]: @@ -149,46 +187,28 @@ class EmaneManager(ModelManager): # try to retrieve interface specific configuration if iface.node_id is not None: key = utils.iface_config_id(iface.node.id, iface.node_id) - config = self.get_configs(node_id=key, config_type=model_name) + config = self.get_config(key, model_name, default=False) # attempt to retrieve node specific config, when iface config is not present if not config: - config = self.get_configs(node_id=iface.node.id, config_type=model_name) + config = self.get_config(iface.node.id, model_name, default=False) # attempt to get emane net specific config, when node config is not present 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) + config = self.get_config(emane_net.id, model_name, default=False) # return default config values, when a config is not present if not config: config = emane_net.model.default_values() return config def config_reset(self, node_id: int = None) -> None: - super().config_reset(node_id) - self.set_configs(self.emane_config.default_values()) - - def emane_check(self) -> None: - """ - Check if emane is installed and load models. - - :return: nothing - """ - # check for emane - path = utils.which("emane", required=False) - if not path: - logger.info("emane is not installed") - return - # get version - emane_version = utils.cmd("emane --version") - logger.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 is not None: - custom_models_path = Path(custom_models_path) - emane_models = utils.load_classes(custom_models_path, EmaneModel) - self.load_models(emane_models) + if node_id is None: + self.config = self.emane_config.default_values() + self.node_configs.clear() + self.node_models.clear() + else: + self.node_configs.get(node_id, {}).clear() + del self.node_models[node_id] def deleteeventservice(self) -> None: if self.service: @@ -207,13 +227,12 @@ class EmaneManager(ModelManager): The multicast group and/or port may be configured. """ self.deleteeventservice() - if shutdown: return # Get the control network to be used for events - group, port = self.get_config("eventservicegroup").split(":") - self.event_device = self.get_config("eventservicedevice") + group, port = self.config["eventservicegroup"].split(":") + self.event_device = self.config["eventservicedevice"] eventnetidx = self.session.get_control_net_index(self.event_device) if eventnetidx < 0: logger.error( @@ -238,19 +257,6 @@ class EmaneManager(ModelManager): except EventServiceException: logger.exception("error instantiating emane EventService") - def load_models(self, emane_models: List[Type[EmaneModel]]) -> None: - """ - Load EMANE models and make them available. - """ - for emane_model in emane_models: - logger.debug("loading emane model: %s", emane_model.__name__) - emane_prefix = self.session.options.get_config( - "emane_prefix", default=DEFAULT_EMANE_PREFIX - ) - emane_prefix = Path(emane_prefix) - emane_model.load(emane_prefix) - self.models[emane_model.name] = emane_model - def add_node(self, emane_net: EmaneNet) -> None: """ Add EMANE network object to this manager. @@ -302,7 +308,7 @@ class EmaneManager(ModelManager): # control network bridge required for EMANE 0.9.2 # - needs to exist when eventservice binds to it (initeventservice) - otadev = self.get_config("otamanagerdevice") + otadev = self.config["otamanagerdevice"] netidx = self.session.get_control_net_index(otadev) logger.debug("emane ota manager device: index(%s) otadev(%s)", netidx, otadev) if netidx < 0: @@ -315,7 +321,7 @@ class EmaneManager(ModelManager): self.session.add_remove_control_net( net_index=netidx, remove=False, conf_required=False ) - eventdev = self.get_config("eventservicedevice") + eventdev = self.config["eventservicedevice"] logger.debug("emane event service device: eventdev(%s)", eventdev) if eventdev != otadev: netidx = self.session.get_control_net_index(eventdev) @@ -408,7 +414,7 @@ class EmaneManager(ModelManager): logger.exception("error writing to emane nem file") def links_enabled(self) -> bool: - return self.get_config("link_enabled") == "1" + return self.config["link_enabled"] == "1" def poststartup(self) -> None: """ @@ -470,14 +476,12 @@ class EmaneManager(ModelManager): for node_id in self._emane_nets: emane_net = self._emane_nets[node_id] logger.debug("checking emane model for node: %s", node_id) - # skip nodes that already have a model set if emane_net.model: logger.debug( "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 model_name = self.node_models.get(node_id) @@ -485,9 +489,9 @@ class EmaneManager(ModelManager): logger.error("emane node(%s) has no node model", node_id) raise ValueError("emane node has no model set") - config = self.get_model_config(node_id=node_id, model_name=model_name) + config = self.get_config(node_id, model_name) logger.debug("setting emane model(%s) config(%s)", model_name, config) - model_class = self.models[model_name] + model_class = self.get_model(model_name) emane_net.setmodel(model_class, config) def get_nem_link( @@ -525,22 +529,19 @@ class EmaneManager(ModelManager): default_values = self.emane_config.default_values() for name in ["eventservicegroup", "eventservicedevice"]: a = default_values[name] - b = self.get_config(name) + b = self.config[name] if a != b: need_xml = True - if not need_xml: # reset to using default config self.initeventservice() return - try: - group, port = self.get_config("eventservicegroup").split(":") + group, port = self.config["eventservicegroup"].split(":") except ValueError: logger.exception("invalid eventservicegroup in EMANE config") return - - dev = self.get_config("eventservicedevice") + dev = self.config["eventservicedevice"] emanexml.create_event_service_xml(group, port, dev, self.session.directory) self.session.distributed.execute( lambda x: emanexml.create_event_service_xml( @@ -554,7 +555,7 @@ class EmaneManager(ModelManager): Add a control network even if the user has not configured one. """ logger.info("starting emane daemons...") - loglevel = str(EmaneManager.DEFAULT_LOG_LEVEL) + loglevel = str(DEFAULT_LOG_LEVEL) cfgloglevel = self.session.options.get_config_int("emane_log_level") realtime = self.session.options.get_config_bool("emane_realtime", default=True) if cfgloglevel: @@ -564,11 +565,11 @@ class EmaneManager(ModelManager): if realtime: emanecmd += " -r" if isinstance(node, CoreNode): - otagroup, _otaport = self.get_config("otamanagergroup").split(":") - otadev = self.get_config("otamanagerdevice") + otagroup, _otaport = self.config["otamanagergroup"].split(":") + otadev = self.config["otamanagerdevice"] otanetidx = self.session.get_control_net_index(otadev) - eventgroup, _eventport = self.get_config("eventservicegroup").split(":") - eventdev = self.get_config("eventservicedevice") + eventgroup, _eventport = self.config["eventservicegroup"].split(":") + eventdev = self.config["eventservicedevice"] eventservicenetidx = self.session.get_control_net_index(eventdev) # control network not yet started here diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index a4bbae34..0c29b7a8 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -189,9 +189,9 @@ class EmaneLinkMonitor: self.running: bool = False def start(self) -> None: - self.loss_threshold = int(self.emane_manager.get_config("loss_threshold")) - self.link_interval = int(self.emane_manager.get_config("link_interval")) - self.link_timeout = int(self.emane_manager.get_config("link_timeout")) + self.loss_threshold = int(self.emane_manager.config["loss_threshold"]) + self.link_interval = int(self.emane_manager.config["link_interval"]) + self.link_timeout = int(self.emane_manager.config["link_timeout"]) self.initialize() if not self.clients: logger.info("no valid emane models to monitor links") diff --git a/daemon/core/emane/modelmanager.py b/daemon/core/emane/modelmanager.py new file mode 100644 index 00000000..94ced08b --- /dev/null +++ b/daemon/core/emane/modelmanager.py @@ -0,0 +1,41 @@ +import logging +from pathlib import Path +from typing import Dict, List, Type + +from core import utils +from core.emane.emanemodel import EmaneModel +from core.errors import CoreError + +logger = logging.getLogger(__name__) + + +class EmaneModelManager: + models: Dict[str, Type[EmaneModel]] = {} + + @classmethod + def load(cls, path: Path, prefix: Path) -> List[str]: + """ + Load EMANE models and make them available. + """ + subdirs = [x for x in path.iterdir() if x.is_dir()] + subdirs.append(path) + errors = [] + for subdir in subdirs: + logger.debug("loading emane models from: %s", subdir) + models = utils.load_classes(subdir, EmaneModel) + for model in models: + logger.debug("loading emane model: %s", model.name) + try: + model.load(prefix) + cls.models[model.name] = model + except CoreError as e: + errors.append(model.name) + logger.debug("not loading service(%s): %s", model.name, e) + return errors + + @classmethod + def get(cls, name: str) -> Type[EmaneModel]: + model = cls.models.get(name) + if model is None: + raise CoreError(f"emame model does not exist {name}") + return model diff --git a/daemon/core/emane/models/__init__.py b/daemon/core/emane/models/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/daemon/core/emane/bypass.py b/daemon/core/emane/models/bypass.py similarity index 100% rename from daemon/core/emane/bypass.py rename to daemon/core/emane/models/bypass.py diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/models/commeffect.py similarity index 100% rename from daemon/core/emane/commeffect.py rename to daemon/core/emane/models/commeffect.py diff --git a/daemon/core/emane/ieee80211abg.py b/daemon/core/emane/models/ieee80211abg.py similarity index 100% rename from daemon/core/emane/ieee80211abg.py rename to daemon/core/emane/models/ieee80211abg.py diff --git a/daemon/core/emane/rfpipe.py b/daemon/core/emane/models/rfpipe.py similarity index 100% rename from daemon/core/emane/rfpipe.py rename to daemon/core/emane/models/rfpipe.py diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/models/tdma.py similarity index 84% rename from daemon/core/emane/tdma.py rename to daemon/core/emane/models/tdma.py index 060a06bc..0ba756e4 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/models/tdma.py @@ -51,17 +51,16 @@ class EmaneTdmaModel(emanemodel.EmaneModel): :return: nothing """ # get configured schedule - config = self.session.emane.get_configs(node_id=self.id, config_type=self.name) + config = self.session.emane.get_config(self.id, self.name) if not config: return - schedule = config[self.schedule_name] - - # get the set event device - event_device = self.session.emane.event_device - + schedule = Path(config[self.schedule_name]) + if not schedule.is_file(): + logger.warning("ignoring invalid tdma schedule: %s", schedule) + return # initiate tdma schedule + event_device = self.session.emane.event_device logger.info( "setting up tdma schedule: schedule(%s) device(%s)", schedule, event_device ) - args = f"emaneevent-tdmaschedule -i {event_device} {schedule}" - utils.cmd(args) + utils.cmd(f"emaneevent-tdmaschedule -i {event_device} {schedule}") diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index e6d1615d..0fd46626 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -9,12 +9,16 @@ from typing import Dict, List, Type import core.services from core import configservices, utils from core.configservice.manager import ConfigServiceManager +from core.emane import models +from core.emane.modelmanager import EmaneModelManager from core.emulator.session import Session from core.executables import get_requirements from core.services.coreservices import ServiceManager logger = logging.getLogger(__name__) +DEFAULT_EMANE_PREFIX: str = "/usr" + def signal_handler(signal_number: int, _) -> None: """ @@ -61,6 +65,10 @@ class CoreEmu: self.service_manager: ConfigServiceManager = ConfigServiceManager() self._load_services() + # check and load emane + self.has_emane: bool = False + self._load_emane() + # check executables exist on path self._validate_env() @@ -103,6 +111,32 @@ class CoreEmu: custom_dir = Path(custom_dir) self.service_manager.load(custom_dir) + def _load_emane(self) -> None: + """ + Check if emane is installed and load models. + + :return: nothing + """ + # check for emane + path = utils.which("emane", required=False) + self.has_emane = path is not None + if not self.has_emane: + logger.info("emane is not installed, emane functionality disabled") + return + # get version + emane_version = utils.cmd("emane --version") + logger.info("using emane: %s", emane_version) + prefix = self.config.get("emane_prefix", DEFAULT_EMANE_PREFIX) + prefix = Path(prefix) + default_path = Path(models.__file__).resolve().parent + EmaneModelManager.load(default_path, prefix) + # load custom models + custom_path = self.config.get("emane_models_dir") + if custom_path is not None: + logger.info("loading custom emane models: %s", custom_path) + custom_path = Path(custom_path) + EmaneModelManager.load(custom_path, prefix) + def shutdown(self) -> None: """ Shutdown all CORE session. diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index bc036343..ced59d56 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -560,12 +560,8 @@ class Session: # ensure default emane configuration if isinstance(node, EmaneNet) and options.emane: - model = self.emane.models.get(options.emane) - if not model: - raise CoreError( - f"node({node.name}) emane model({options.emane}) does not exist" - ) - node.model = model(self, node.id) + model_class = self.emane.get_model(options.emane) + node.model = model_class(self, node.id) if self.state == EventTypes.RUNTIME_STATE: self.emane.add_node(node) # set default wlan config if needed diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 0ed7a356..628569a2 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -77,6 +77,7 @@ class CoreClient: self.config_services: Dict[str, ConfigService] = {} # loaded configuration data + self.emane_models: List[str] = [] self.servers: Dict[str, CoreServer] = {} self.custom_nodes: Dict[str, NodeDraw] = {} self.custom_observers: Dict[str, Observer] = {} @@ -392,6 +393,7 @@ class CoreClient: self.client.connect() # get current core configurations services/config services core_config = self.client.get_config() + self.emane_models = core_config.emane_models for service in core_config.services: group_services = self.services.setdefault(service.group, set()) group_services.add(service.name) @@ -639,11 +641,11 @@ class CoreClient: image = "ubuntu:latest" emane = None if node_type == NodeType.EMANE: - if not self.session.emane_models: + if not self.emane_models: dialog = EmaneInstallDialog(self.app) dialog.show() return - emane = self.session.emane_models[0] + emane = self.emane_models[0] name = f"emane{node_id}" elif node_type == NodeType.WIRELESS_LAN: name = f"wlan{node_id}" diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index 35de0d66..ea7f5624 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -115,7 +115,7 @@ class EmaneConfigDialog(Dialog): self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(1) self.emane_models: List[str] = [ - x.split("_")[1] for x in self.app.core.session.emane_models + 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) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 745e14e0..629750bf 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -82,7 +82,7 @@ def create_iface_data(iface_element: etree.Element) -> InterfaceData: def create_emane_config(session: "Session") -> etree.Element: emane_configuration = etree.Element("emane_global_configuration") - config = session.emane.get_configs() + config = session.emane.config emulator_element = etree.SubElement(emane_configuration, "emulator") for emulator_config in session.emane.emane_config.emulator_config: value = config[emulator_config.id] @@ -379,19 +379,15 @@ class CoreXmlWriter: 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 node_id, model_configs in self.session.emane.node_configs.items(): node_id, iface_id = utils.parse_iface_config_id(node_id) - for model_name in all_configs: - config = all_configs[model_name] + for model_name, config in model_configs.items(): logger.debug( "writing emane config node(%s) model(%s)", node_id, model_name ) - model = self.session.emane.models[model_name] + model_class = self.session.emane.get_model(model_name) emane_configuration = create_emane_model_config( - node_id, model, config, iface_id + node_id, model_class, config, iface_id ) emane_configurations.append(emane_configuration) if emane_configurations.getchildren(): @@ -748,7 +744,7 @@ class CoreXmlReader: name = config.get("name") value = config.get("value") configs[name] = value - self.session.emane.set_configs(config=configs) + self.session.emane.config = configs def read_emane_configs(self) -> None: emane_configurations = self.scenario.find("emane_configurations") @@ -765,9 +761,7 @@ class CoreXmlReader: node = self.session.nodes.get(node_id) if not node: raise CoreXmlError(f"node for emane config doesn't exist: {node_id}") - model = self.session.emane.models.get(model_name) - if not model: - raise CoreXmlError(f"invalid emane model: {model_name}") + self.session.emane.get_model(model_name) if iface_id is not None and iface_id not in node.ifaces: raise CoreXmlError( f"invalid interface id({iface_id}) for node({node.name})" @@ -796,7 +790,7 @@ class CoreXmlReader: "reading emane configuration node(%s) model(%s)", node_id, model_name ) node_id = utils.iface_config_id(node_id, iface_id) - self.session.emane.set_model_config(node_id, model_name, configs) + self.session.emane.set_config(node_id, model_name, configs) def read_mobility_configs(self) -> None: mobility_configurations = self.scenario.find("mobility_configurations") diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 6c85bc94..ab7c2039 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -166,7 +166,7 @@ def build_platform_xml( if not isinstance(data.node, CoreNode) and name in transport_configs: value = control_net.brname else: - value = emane_manager.get_config(name) + value = emane_manager.config[name] add_param(platform_element, name, value) # create nem xml entries for all interfaces diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py index db8069c5..fbafbe07 100644 --- a/daemon/examples/grpc/emane80211.py +++ b/daemon/examples/grpc/emane80211.py @@ -1,7 +1,7 @@ # required imports from core.api.grpc import client from core.api.grpc.wrappers import NodeType, Position -from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.ieee80211abg import EmaneIeee80211abgModel # interface helper iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") diff --git a/daemon/examples/python/distributed_emane.py b/daemon/examples/python/distributed_emane.py index 4421283f..cdc9cbc3 100644 --- a/daemon/examples/python/distributed_emane.py +++ b/daemon/examples/python/distributed_emane.py @@ -6,7 +6,7 @@ with the GUI. import argparse import logging -from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu from core.emulator.data import IpPrefixes, NodeOptions diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index ae4f194b..0bcc0157 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -1,5 +1,5 @@ # required imports -from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu from core.emulator.data import IpPrefixes, NodeOptions diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index ebb6c3d2..ce66b038 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -123,8 +123,6 @@ service CoreApi { } // globals - rpc GetEmaneModels (emane.GetEmaneModelsRequest) returns (emane.GetEmaneModelsResponse) { - } rpc GetConfig (GetConfigRequest) returns (GetConfigResponse) { } } @@ -584,17 +582,16 @@ message Session { repeated services.ServiceDefaults default_services = 7; SessionLocation location = 8; repeated Hook hooks = 9; - repeated string emane_models = 10; - map emane_config = 11; - repeated emane.GetEmaneModelConfig emane_model_configs = 12; - map wlan_configs = 13; - repeated services.NodeServiceConfig service_configs = 14; - repeated configservices.ConfigServiceConfig config_service_configs = 15; - map mobility_configs = 16; - map metadata = 17; - string file = 18; - map options = 19; - repeated Server servers = 20; + map emane_config = 10; + repeated emane.GetEmaneModelConfig emane_model_configs = 11; + map wlan_configs = 12; + repeated services.NodeServiceConfig service_configs = 13; + repeated configservices.ConfigServiceConfig config_service_configs = 14; + map mobility_configs = 15; + map metadata = 16; + string file = 17; + map options = 18; + repeated Server servers = 19; } message SessionSummary { diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index 65ee1026..de739891 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -21,14 +21,6 @@ message SetEmaneConfigResponse { bool result = 1; } -message GetEmaneModelsRequest { - int32 session_id = 1; -} - -message GetEmaneModelsResponse { - repeated string models = 1; -} - message GetEmaneModelConfigRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index 29963401..5cb14bdc 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -9,13 +9,13 @@ from xml.etree import ElementTree import pytest from core import utils -from core.emane.bypass import EmaneBypassModel -from core.emane.commeffect import EmaneCommEffectModel from core.emane.emanemodel import EmaneModel -from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.bypass import EmaneBypassModel +from core.emane.models.commeffect import EmaneCommEffectModel +from core.emane.models.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.rfpipe import EmaneRfPipeModel +from core.emane.models.tdma import EmaneTdmaModel from core.emane.nodes import EmaneNet -from core.emane.rfpipe import EmaneRfPipeModel -from core.emane.tdma import EmaneTdmaModel from core.emulator.data import IpPrefixes, NodeOptions from core.emulator.session import Session from core.errors import CoreCommandError, CoreError @@ -100,14 +100,13 @@ class TestEmane: # create emane node for networking the core nodes session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions() + options = NodeOptions(emane=model.name) options.set_position(80, 50) emane_network = session.add_node(EmaneNet, options=options) - session.emane.set_model(emane_network, model) # configure tdma if model == EmaneTdmaModel: - session.emane.set_model_config( + session.emane.set_config( emane_network.id, EmaneTdmaModel.name, {"schedule": str(_SCHEDULE)} ) @@ -142,13 +141,13 @@ class TestEmane: """ # create emane node for networking the core nodes session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions() + options = NodeOptions(emane=EmaneIeee80211abgModel.name) options.set_position(80, 50) emane_network = session.add_node(EmaneNet, options=options) config_key = "txpower" config_value = "10" - session.emane.set_model( - emane_network, EmaneIeee80211abgModel, {config_key: config_value} + session.emane.set_config( + emane_network.id, EmaneIeee80211abgModel.name, {config_key: config_value} ) # create nodes @@ -174,7 +173,7 @@ class TestEmane: # save xml xml_file = tmpdir.join("session.xml") file_path = xml_file.strpath - session.save_xml(file_path) + session.save_xml(Path(file_path)) # verify xml file was created and can be parsed assert xml_file.isfile() @@ -190,12 +189,11 @@ class TestEmane: assert not session.get_node(node2_id, CoreNode) # load saved xml - session.open_xml(file_path, start=True) + session.open_xml(Path(file_path), start=True) # retrieve configuration we set originally - value = str( - session.emane.get_config(config_key, emane_id, EmaneIeee80211abgModel.name) - ) + config = session.emane.get_config(emane_id, EmaneIeee80211abgModel.name) + value = config[config_key] # verify nodes and configuration were restored assert session.get_node(node1_id, CoreNode) @@ -221,9 +219,9 @@ class TestEmane: session.add_link(node1.id, emane_node.id, iface1_data) session.add_link(node2.id, emane_node.id, iface2_data) - # set node specific conifg + # set node specific config datarate = "101" - session.emane.set_model_config( + session.emane.set_config( node1.id, EmaneRfPipeModel.name, {"datarate": datarate} ) @@ -233,7 +231,7 @@ class TestEmane: # save xml xml_file = tmpdir.join("session.xml") file_path = xml_file.strpath - session.save_xml(file_path) + session.save_xml(Path(file_path)) # verify xml file was created and can be parsed assert xml_file.isfile() @@ -251,7 +249,7 @@ class TestEmane: assert not session.get_node(emane_node.id, EmaneNet) # load saved xml - session.open_xml(file_path, start=True) + session.open_xml(Path(file_path), start=True) # verify nodes have been recreated assert session.get_node(node1.id, CoreNode) @@ -262,7 +260,7 @@ class TestEmane: node = session.nodes[node_id] links += node.links() assert len(links) == 2 - config = session.emane.get_model_config(node1.id, EmaneRfPipeModel.name) + config = session.emane.get_config(node1.id, EmaneRfPipeModel.name) assert config["datarate"] == datarate def test_xml_emane_interface_config( @@ -286,7 +284,7 @@ class TestEmane: # set node specific conifg datarate = "101" config_id = utils.iface_config_id(node1.id, iface1_data.id) - session.emane.set_model_config( + session.emane.set_config( config_id, EmaneRfPipeModel.name, {"datarate": datarate} ) @@ -296,7 +294,7 @@ class TestEmane: # save xml xml_file = tmpdir.join("session.xml") file_path = xml_file.strpath - session.save_xml(file_path) + session.save_xml(Path(file_path)) # verify xml file was created and can be parsed assert xml_file.isfile() @@ -314,7 +312,7 @@ class TestEmane: assert not session.get_node(emane_node.id, EmaneNet) # load saved xml - session.open_xml(file_path, start=True) + session.open_xml(Path(file_path), start=True) # verify nodes have been recreated assert session.get_node(node1.id, CoreNode) @@ -325,5 +323,5 @@ class TestEmane: node = session.nodes[node_id] links += node.links() assert len(links) == 2 - config = session.emane.get_model_config(config_id, EmaneRfPipeModel.name) + config = session.emane.get_config(config_id, EmaneRfPipeModel.name) assert config["datarate"] == datarate diff --git a/daemon/tests/test_conf.py b/daemon/tests/test_conf.py index 994a09f3..df16fb22 100644 --- a/daemon/tests/test_conf.py +++ b/daemon/tests/test_conf.py @@ -6,7 +6,7 @@ from core.config import ( Configuration, ModelManager, ) -from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emulator.enumerations import ConfigDataTypes from core.emulator.session import Session from core.location.mobility import BasicRangeModel diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 0bb31221..bcdd7104 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -33,7 +33,7 @@ from core.api.grpc.wrappers import ( ) from core.api.tlv.dataconversion import ConfigShim from core.api.tlv.enumerations import ConfigFlags -from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.data import EventData, IpPrefixes, NodeData, NodeOptions from core.emulator.enumerations import EventTypes, ExceptionLevels @@ -151,7 +151,7 @@ class TestGrpc: location_alt, ) assert real_session.location.refscale == location_scale - assert real_session.emane.get_config(emane_config_key) == emane_config_value + assert real_session.emane.config[emane_config_key] == emane_config_value set_wlan_config = real_session.mobility.get_model_config( wlan_node.id, BasicRangeModel.name ) @@ -543,7 +543,7 @@ class TestGrpc: # then assert result is True - config = session.emane.get_configs() + config = session.emane.config assert len(config) > 1 assert config[config_key] == config_value @@ -554,7 +554,7 @@ class TestGrpc: session.set_location(47.57917, -122.13232, 2.00000, 1.0) options = NodeOptions(emane=EmaneIeee80211abgModel.name) emane_network = session.add_node(EmaneNet, options=options) - session.emane.set_model(emane_network, EmaneIeee80211abgModel) + session.emane.node_models[emane_network.id] = EmaneIeee80211abgModel.name config_key = "bandwidth" config_value = "900000" option = ConfigOption( @@ -574,9 +574,7 @@ class TestGrpc: # then assert result is True - config = session.emane.get_model_config( - emane_network.id, EmaneIeee80211abgModel.name - ) + 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): @@ -586,7 +584,7 @@ class TestGrpc: session.set_location(47.57917, -122.13232, 2.00000, 1.0) options = NodeOptions(emane=EmaneIeee80211abgModel.name) emane_network = session.add_node(EmaneNet, options=options) - session.emane.set_model(emane_network, EmaneIeee80211abgModel) + session.emane.node_models[emane_network.id] = EmaneIeee80211abgModel.name # then with client.context_connect(): @@ -597,18 +595,6 @@ class TestGrpc: # then assert len(config) > 0 - def test_get_emane_models(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - models = client.get_emane_models(session.id) - - # then - assert len(models) > 0 - def test_get_mobility_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 8e9c943b..7b8a987d 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -22,7 +22,7 @@ from core.api.tlv.enumerations import ( NodeTlvs, SessionTlvs, ) -from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emulator.enumerations import EventTypes, MessageFlags, NodeTypes, RegisterTlvs from core.errors import CoreError from core.location.mobility import BasicRangeModel @@ -939,9 +939,7 @@ class TestGui: coretlv.handle_message(message) - config = coretlv.session.emane.get_model_config( - wlan.id, EmaneIeee80211abgModel.name - ) + config = coretlv.session.emane.get_config(wlan.id, EmaneIeee80211abgModel.name) assert config[config_key] == config_value def test_config_emane_request(self, coretlv: CoreHandler): @@ -973,5 +971,5 @@ class TestGui: coretlv.handle_message(message) - config = coretlv.session.emane.get_configs() + config = coretlv.session.emane.config assert config[config_key] == config_value diff --git a/docs/grpc.md b/docs/grpc.md index fafde661..5cc0e7ae 100644 --- a/docs/grpc.md +++ b/docs/grpc.md @@ -300,7 +300,7 @@ will use the defaults. When no configuration is used, the defaults are used. # required imports from core.api.grpc import client from core.api.grpc.core_pb2 import NodeType, Position -from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.ieee80211abg import EmaneIeee80211abgModel # interface helper iface_helper = client.InterfaceHelper(ip4_prefix="10.0.0.0/24", ip6_prefix="2001::/64") @@ -315,7 +315,7 @@ session = core.create_session() # create nodes position = Position(x=200, y=200) emane = session.add_node( - 1, _type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name + 1, _type=NodeType.EMANE, position=position, emane=EmaneIeee80211abgModel.name ) position = Position(x=100, y=100) node1 = session.add_node(2, model="mdr", position=position) diff --git a/docs/python.md b/docs/python.md index d6c418d4..fe776662 100644 --- a/docs/python.md +++ b/docs/python.md @@ -278,7 +278,7 @@ will use the defaults. When no configuration is used, the defaults are used. ```python # required imports -from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu from core.emulator.data import IpPrefixes, NodeOptions @@ -312,13 +312,13 @@ n2 = session.add_node(CoreNode, options=options) # configure general emane settings config = session.emane.get_configs() config.update({ - "eventservicettl": "2" + "eventservicettl": "2" }) # configure emane model settings # using a dict mapping currently support values as strings session.emane.set_model_config(emane.id, EmaneIeee80211abgModel.name, { - "unicastrate": "3", + "unicastrate": "3", }) # link nodes to emane From 13778e1d306926071d9f3e881a9a00a9e862a1a1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 7 May 2021 10:58:23 -0700 Subject: [PATCH 045/110] pygui: updated emane config dialog to sort emane models consistently --- daemon/core/gui/dialogs/emaneconfig.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index ea7f5624..ef446a62 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -114,10 +114,11 @@ class EmaneConfigDialog(Dialog): self.node: Node = node self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(1) + self.emane_models: List[str] = [ - x.split("_")[1] for x in self.app.core.emane_models + x.split("_")[1] for x in sorted(self.app.core.emane_models) ] - model = self.node.emane.split("_")[1] + model = self.emane_models[0] 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() From ca8b4f1f6e4975c614115f0e30d18353a705083f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 7 May 2021 12:01:08 -0700 Subject: [PATCH 046/110] bumped version on develop to denote major changes --- configure.ac | 2 +- daemon/pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 3fd076b6..5f65d6a0 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, 7.5.1) +AC_INIT(core, 8.0.0) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index a7e9c1d2..f56fd0d8 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "core" -version = "7.5.1" +version = "8.0.0" description = "CORE Common Open Research Emulator" authors = ["Boeing Research and Technology"] license = "BSD-2-Clause" From ad09bd55045f3126a883d4e6ed085dc9fb0cfd8e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 7 May 2021 13:10:05 -0700 Subject: [PATCH 047/110] initial changes to mark older style services as deprecated and make configurable services the defaults for the python gui, added attribute to still allow supporting the legacy gui for time being and logic to compensate --- daemon/core/api/tlv/corehandlers.py | 2 +- daemon/core/configservices/quaggaservices/services.py | 4 ++-- daemon/core/emane/emanemanager.py | 2 +- daemon/core/emulator/data.py | 1 + daemon/core/emulator/session.py | 10 +++++++--- daemon/core/gui/coreclient.py | 6 +++--- daemon/core/gui/dialogs/emaneconfig.py | 2 +- daemon/core/gui/dialogs/nodeservice.py | 2 +- daemon/core/gui/dialogs/serviceconfig.py | 2 +- daemon/core/gui/graph/node.py | 4 +++- daemon/tests/conftest.py | 1 + daemon/tests/test_grpc.py | 3 ++- 12 files changed, 24 insertions(+), 15 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 631b5491..527924c1 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -688,8 +688,8 @@ class CoreHandler(socketserver.BaseRequestHandler): options = NodeOptions( name=message.get_tlv(NodeTlvs.NAME.value), model=message.get_tlv(NodeTlvs.MODEL.value), + legacy=True, ) - options.set_position( x=message.get_tlv(NodeTlvs.X_POSITION.value), y=message.get_tlv(NodeTlvs.Y_POSITION.value), diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 07ce4644..922117cb 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -102,9 +102,9 @@ class Zebra(ConfigService): ip4s = [] ip6s = [] for ip4 in iface.ip4s: - ip4s.append(str(ip4.ip)) + ip4s.append(str(ip4)) for ip6 in iface.ip6s: - ip6s.append(str(ip6.ip)) + ip6s.append(str(ip6)) ifaces.append((iface, ip4s, ip6s, iface.control)) return dict( diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 3eabf54e..11eda990 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -208,7 +208,7 @@ class EmaneManager: self.node_models.clear() else: self.node_configs.get(node_id, {}).clear() - del self.node_models[node_id] + self.node_models.pop(node_id, None) def deleteeventservice(self) -> None: if self.service: diff --git a/daemon/core/emulator/data.py b/daemon/core/emulator/data.py index 68a92eea..f56ce569 100644 --- a/daemon/core/emulator/data.py +++ b/daemon/core/emulator/data.py @@ -91,6 +91,7 @@ class NodeOptions: server: str = None image: str = None emane: str = None + legacy: bool = False def set_position(self, x: float, y: float) -> None: """ diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index ced59d56..ae1bb185 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -549,12 +549,16 @@ class Session: # add services to needed nodes if isinstance(node, (CoreNode, PhysicalNode)): node.type = options.model - logger.debug("set node type: %s", node.type) - self.services.add_services(node, node.type, options.services) + if options.legacy or options.services: + logger.debug("set node type: %s", node.type) + self.services.add_services(node, node.type, options.services) # add config services + config_services = options.config_services + if not options.legacy and not config_services: + config_services = self.services.default_services.get(node.type, []) logger.info("setting node config services: %s", options.config_services) - for name in options.config_services: + for name in config_services: service_class = self.service_manager.get_service(name) node.add_config_service(service_class) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 628569a2..05fe7960 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -393,7 +393,7 @@ class CoreClient: self.client.connect() # get current core configurations services/config services core_config = self.client.get_config() - self.emane_models = core_config.emane_models + self.emane_models = sorted(core_config.emane_models) for service in core_config.services: group_services = self.services.setdefault(service.group, set()) group_services.add(service.name) @@ -664,12 +664,12 @@ class CoreClient: ) if nutils.is_custom(node): services = nutils.get_custom_services(self.app.guiconfig, model) - node.services = set(services) + node.config_services = set(services) # assign default services to CORE node else: services = self.session.default_services.get(model) if services: - node.services = services.copy() + node.config_services = services.copy() logger.info( "add node(%s) to session(%s), coordinates(%s, %s)", node.name, diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index ef446a62..9d9090b6 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -116,7 +116,7 @@ class EmaneConfigDialog(Dialog): self.radiovar.set(1) self.emane_models: List[str] = [ - x.split("_")[1] for x in sorted(self.app.core.emane_models) + x.split("_")[1] for x in self.app.core.emane_models ] model = self.emane_models[0] self.emane_model: tk.StringVar = tk.StringVar(value=model) diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index 09732e73..f27f9cf5 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: class NodeServiceDialog(Dialog): def __init__(self, app: "Application", node: Node) -> None: - title = f"{node.name} Services" + title = f"{node.name} Services (Deprecated)" super().__init__(app, title) self.node: Node = node self.groups: Optional[ListboxScroll] = None diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 16d3a951..16c3374e 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -28,7 +28,7 @@ class ServiceConfigDialog(Dialog): def __init__( self, master: tk.BaseWidget, app: "Application", service_name: str, node: Node ) -> None: - title = f"{service_name} Service" + title = f"{service_name} Service (Deprecated)" super().__init__(app, title, master=master) self.core: "CoreClient" = app.core self.node: Node = node diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 1b79e530..2bd4ae40 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -255,10 +255,12 @@ class CanvasNode: else: self.context.add_command(label="Configure", command=self.show_config) if nutils.is_container(self.core_node): - self.context.add_command(label="Services", command=self.show_services) self.context.add_command( label="Config Services", command=self.show_config_services ) + self.context.add_command( + label="Services (Deprecated)", command=self.show_services + ) if is_emane: self.context.add_command( label="EMANE Config", command=self.show_emane_config diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index a558fcec..5ced3fc8 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -78,6 +78,7 @@ def global_coreemu(patcher): def global_session(request, patcher, global_coreemu): mkdir = not request.config.getoption("mock") session = Session(1000, {"emane_prefix": "/usr"}, mkdir) + session.service_manager = global_coreemu.service_manager yield session session.shutdown() diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index bcdd7104..e836251e 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -702,7 +702,8 @@ class TestGrpc: # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) + options = NodeOptions(legacy=True) + node = session.add_node(CoreNode, options=options) service_name = "DefaultRoute" # then From 3a08b13d6e96b784b806d80626d7abd7f9b5a45a Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 7 May 2021 14:30:28 -0700 Subject: [PATCH 048/110] changes to rename core-pygui to core-gui to be the default gui, renamed core-gui to core-gui-legacy to denote its deprecation --- Makefile.am | 2 +- daemon/scripts/{core-pygui => core-gui} | 0 gui/.gitignore | 2 +- gui/Makefile.am | 6 +++--- gui/{core-gui.in => core-gui-legacy.in} | 0 5 files changed, 5 insertions(+), 5 deletions(-) rename daemon/scripts/{core-pygui => core-gui} (100%) rename gui/{core-gui.in => core-gui-legacy.in} (100%) diff --git a/Makefile.am b/Makefile.am index 7a3799fc..f5653be3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -123,7 +123,7 @@ all: change-files .PHONY: change-files change-files: - $(call change-files,gui/core-gui) + $(call change-files,gui/core-gui-legacy) $(call change-files,daemon/core/constants.py) $(call change-files,netns/setup.py) diff --git a/daemon/scripts/core-pygui b/daemon/scripts/core-gui similarity index 100% rename from daemon/scripts/core-pygui rename to daemon/scripts/core-gui diff --git a/gui/.gitignore b/gui/.gitignore index 682be43e..dea55215 100644 --- a/gui/.gitignore +++ b/gui/.gitignore @@ -1,2 +1,2 @@ -core-gui +core-gui-legacy version.tcl diff --git a/gui/Makefile.am b/gui/Makefile.am index 0d0d2b47..938769a2 100644 --- a/gui/Makefile.am +++ b/gui/Makefile.am @@ -14,8 +14,8 @@ TCL_FILES := $(wildcard *.tcl) ADDONS_FILES := $(wildcard addons/*) CONFIG_FILES := $(wildcard configs/*) -# CORE GUI script (/usr/local/bin/core-gui) -dist_bin_SCRIPTS = core-gui +# CORE GUI script (/usr/local/bin/core-gui-legacy) +dist_bin_SCRIPTS = core-gui-legacy # Tcl/Tk scripts (/usr/local/lib/core) coredir = $(CORE_LIB_DIR) @@ -38,4 +38,4 @@ dist-hook: DISTCLEANFILES = Makefile.in # files to include in source tarball not included elsewhere -EXTRA_DIST = core-gui.in +EXTRA_DIST = core-gui-legacy.in diff --git a/gui/core-gui.in b/gui/core-gui-legacy.in similarity index 100% rename from gui/core-gui.in rename to gui/core-gui-legacy.in From 85c5ad22e4ca728ce521fe5957943f2656b1114b Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 7 May 2021 22:49:58 -0700 Subject: [PATCH 049/110] daemon: adjustments to load local emane models and config services better using a full import --- daemon/core/configservice/manager.py | 26 ++++++++++++++++--- daemon/core/emane/modelmanager.py | 37 +++++++++++++++++++++++++--- daemon/core/emulator/coreemu.py | 15 +++++------ daemon/core/utils.py | 29 +++++++++++++--------- 4 files changed, 80 insertions(+), 27 deletions(-) diff --git a/daemon/core/configservice/manager.py b/daemon/core/configservice/manager.py index 02812ea2..1fd26e43 100644 --- a/daemon/core/configservice/manager.py +++ b/daemon/core/configservice/manager.py @@ -1,9 +1,10 @@ import logging import pathlib +import pkgutil from pathlib import Path from typing import Dict, List, Type -from core import utils +from core import configservices, utils from core.configservice.base import ConfigService from core.errors import CoreError @@ -61,12 +62,31 @@ class ConfigServiceManager: # make service available self.services[name] = service + def load_locals(self) -> List[str]: + """ + Search and add config service from local core module. + + :return: list of errors when loading services + """ + errors = [] + for module_info in pkgutil.walk_packages( + configservices.__path__, f"{configservices.__name__}." + ): + services = utils.load_module(module_info.name, ConfigService) + for service in services: + try: + self.add(service) + except CoreError as e: + errors.append(service.name) + logger.debug("not loading config service(%s): %s", service.name, e) + return errors + def load(self, path: Path) -> List[str]: """ - Search path provided for configurable services and add them for being managed. + Search path provided for config services and add them for being managed. :param path: path to search configurable services - :return: list errors when loading and adding services + :return: list errors when loading services """ path = pathlib.Path(path) subdirs = [x for x in path.iterdir() if x.is_dir()] diff --git a/daemon/core/emane/modelmanager.py b/daemon/core/emane/modelmanager.py index 94ced08b..989802c4 100644 --- a/daemon/core/emane/modelmanager.py +++ b/daemon/core/emane/modelmanager.py @@ -1,8 +1,10 @@ import logging +import pkgutil from pathlib import Path from typing import Dict, List, Type from core import utils +from core.emane import models as emane_models from core.emane.emanemodel import EmaneModel from core.errors import CoreError @@ -13,9 +15,36 @@ class EmaneModelManager: models: Dict[str, Type[EmaneModel]] = {} @classmethod - def load(cls, path: Path, prefix: Path) -> List[str]: + def load_locals(cls, emane_prefix: Path) -> List[str]: """ - Load EMANE models and make them available. + Load local core emane models and make them available. + + :param emane_prefix: installed emane prefix + :return: list of errors encountered loading emane models + """ + errors = [] + for module_info in pkgutil.walk_packages( + emane_models.__path__, f"{emane_models.__name__}." + ): + models = utils.load_module(module_info.name, EmaneModel) + for model in models: + logger.debug("loading emane model: %s", model.name) + try: + model.load(emane_prefix) + cls.models[model.name] = model + except CoreError as e: + errors.append(model.name) + logger.debug("not loading emane model(%s): %s", model.name, e) + return errors + + @classmethod + def load(cls, path: Path, emane_prefix: Path) -> List[str]: + """ + Search and load custom emane models and make them available. + + :param path: path to search for custom emane models + :param emane_prefix: installed emane prefix + :return: list of errors encountered loading emane models """ subdirs = [x for x in path.iterdir() if x.is_dir()] subdirs.append(path) @@ -26,11 +55,11 @@ class EmaneModelManager: for model in models: logger.debug("loading emane model: %s", model.name) try: - model.load(prefix) + model.load(emane_prefix) cls.models[model.name] = model except CoreError as e: errors.append(model.name) - logger.debug("not loading service(%s): %s", model.name, e) + logger.debug("not loading emane model(%s): %s", model.name, e) return errors @classmethod diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 0fd46626..179faf9c 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -7,9 +7,8 @@ from pathlib import Path from typing import Dict, List, Type import core.services -from core import configservices, utils +from core import utils from core.configservice.manager import ConfigServiceManager -from core.emane import models from core.emane.modelmanager import EmaneModelManager from core.emulator.session import Session from core.executables import get_requirements @@ -103,8 +102,7 @@ class CoreEmu: custom_service_errors = ServiceManager.add_services(service_path) self.service_errors.extend(custom_service_errors) # load default config services - config_services_path = Path(configservices.__file__).resolve().parent - self.service_manager.load(config_services_path) + self.service_manager.load_locals() # load custom config services custom_dir = self.config.get("custom_config_services_dir") if custom_dir is not None: @@ -126,16 +124,15 @@ class CoreEmu: # get version emane_version = utils.cmd("emane --version") logger.info("using emane: %s", emane_version) - prefix = self.config.get("emane_prefix", DEFAULT_EMANE_PREFIX) - prefix = Path(prefix) - default_path = Path(models.__file__).resolve().parent - EmaneModelManager.load(default_path, prefix) + emane_prefix = self.config.get("emane_prefix", DEFAULT_EMANE_PREFIX) + emane_prefix = Path(emane_prefix) + EmaneModelManager.load_locals(emane_prefix) # load custom models custom_path = self.config.get("emane_models_dir") if custom_path is not None: logger.info("loading custom emane models: %s", custom_path) custom_path = Path(custom_path) - EmaneModelManager.load(custom_path, prefix) + EmaneModelManager.load(custom_path, emane_prefix) def shutdown(self) -> None: """ diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 201797e1..b7ef1e5c 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -328,7 +328,22 @@ def load_config(file_path: Path, d: Dict[str, str]) -> None: logger.exception("error reading file to dict: %s", file_path) -def load_classes(path: Path, clazz: Generic[T]) -> T: +def load_module(import_statement: str, clazz: Generic[T]) -> List[T]: + classes = [] + try: + module = importlib.import_module(import_statement) + members = inspect.getmembers(module, lambda x: _is_class(module, x, clazz)) + for member in members: + valid_class = member[1] + classes.append(valid_class) + except Exception: + logger.exception( + "unexpected error during import, skipping: %s", import_statement + ) + return classes + + +def load_classes(path: Path, clazz: Generic[T]) -> List[T]: """ Dynamically load classes for use within CORE. @@ -352,16 +367,8 @@ def load_classes(path: Path, clazz: Generic[T]) -> T: continue import_statement = f"{path.name}.{p.stem}" logger.debug("importing custom module: %s", import_statement) - try: - module = importlib.import_module(import_statement) - members = inspect.getmembers(module, lambda x: _is_class(module, x, clazz)) - for member in members: - valid_class = member[1] - classes.append(valid_class) - except Exception: - logger.exception( - "unexpected error during import, skipping: %s", import_statement - ) + loaded = load_module(import_statement, clazz) + classes.extend(loaded) return classes From 5ffc3e2aa436e984d3437ad4c92ab617fda0fef0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 10 May 2021 12:07:55 -0700 Subject: [PATCH 050/110] pygui: fixed issue with loading recent xml files --- daemon/core/gui/menubar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index d612aaa8..e53c8915 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -86,7 +86,7 @@ class Menubar(tk.Menu): self.recent_menu = tk.Menu(menu) for i in self.app.guiconfig.recentfiles: self.recent_menu.add_command( - label=i, command=partial(self.open_recent_files, i) + label=i, command=partial(self.open_recent_files, Path(i)) ) menu.add_cascade(label="Recent Files", menu=self.recent_menu) menu.add_separator() @@ -285,7 +285,7 @@ class Menubar(tk.Menu): self.recent_menu.delete(0, tk.END) for i in self.app.guiconfig.recentfiles: self.recent_menu.add_command( - label=i, command=partial(self.open_recent_files, i) + label=i, command=partial(self.open_recent_files, Path(i)) ) def click_save(self, _event: tk.Event = None) -> None: From 208c746b679c8138f48009835c19033810306424 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 10 May 2021 12:39:20 -0700 Subject: [PATCH 051/110] daemon: fixed issue setting default config services if legacy services were specifically provided --- daemon/core/emulator/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index ae1bb185..6aac8f33 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -555,9 +555,9 @@ class Session: # add config services config_services = options.config_services - if not options.legacy and not config_services: + if not options.legacy and not config_services and not node.services: config_services = self.services.default_services.get(node.type, []) - logger.info("setting node config services: %s", options.config_services) + logger.info("setting node config services: %s", config_services) for name in config_services: service_class = self.service_manager.get_service(name) node.add_config_service(service_class) From 30291a84388b4f1943cbcfd5febce9a082a0f3f5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 10 May 2021 15:07:42 -0700 Subject: [PATCH 052/110] daemon: updates to switch from using ebtables to nftables for wlan linking/unlinking --- Makefile.am | 4 +- configure.ac | 6 +- daemon/core/executables.py | 4 +- daemon/core/gui/coreclient.py | 2 +- daemon/core/nodes/base.py | 6 +- daemon/core/nodes/network.py | 293 +++++++++++++++------------------- daemon/scripts/core-cleanup | 4 +- docs/architecture.md | 4 +- docs/devguide.md | 4 +- docs/distributed.md | 2 +- docs/gui.md | 2 +- docs/install.md | 10 +- docs/pygui.md | 2 +- tasks.py | 16 +- 14 files changed, 153 insertions(+), 206 deletions(-) diff --git a/Makefile.am b/Makefile.am index f5653be3..bd15cf09 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,7 +57,7 @@ fpm -s dir -t deb -n core-distributed \ -d "procps" \ -d "libc6 >= 2.14" \ -d "bash >= 3.0" \ - -d "ebtables" \ + -d "nftables" \ -d "iproute2" \ -d "libev4" \ -d "openssh-server" \ @@ -77,7 +77,7 @@ fpm -s dir -t rpm -n core-distributed \ -d "ethtool" \ -d "procps-ng" \ -d "bash >= 3.0" \ - -d "ebtables" \ + -d "nftables" \ -d "iproute" \ -d "libev" \ -d "net-tools" \ diff --git a/configure.ac b/configure.ac index 5f65d6a0..6b06966a 100644 --- a/configure.ac +++ b/configure.ac @@ -123,9 +123,9 @@ if test "x$enable_daemon" = "xyes"; then AC_MSG_ERROR([Could not locate sysctl (from procps package).]) fi - AC_CHECK_PROG(ebtables_path, ebtables, $as_dir, no, $SEARCHPATH) - if test "x$ebtables_path" = "xno" ; then - AC_MSG_ERROR([Could not locate ebtables (from ebtables package).]) + AC_CHECK_PROG(nftables_path, nft, $as_dir, no, $SEARCHPATH) + if test "x$nftables_path" = "xno" ; then + AC_MSG_ERROR([Could not locate nftables (from nftables package).]) fi AC_CHECK_PROG(ip_path, ip, $as_dir, no, $SEARCHPATH) diff --git a/daemon/core/executables.py b/daemon/core/executables.py index 16f159fc..3d0e80f6 100644 --- a/daemon/core/executables.py +++ b/daemon/core/executables.py @@ -7,15 +7,15 @@ 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" TEST: str = "test" +NFTABLES: str = "nft" COMMON_REQUIREMENTS: List[str] = [ BASH, - EBTABLES, + NFTABLES, ETHTOOL, IP, MOUNT, diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 05fe7960..059266bc 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -669,7 +669,7 @@ class CoreClient: else: services = self.session.default_services.get(model) if services: - node.config_services = services.copy() + node.config_services = set(services) logger.info( "add node(%s) to session(%s), coordinates(%s, %s)", node.name, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 9779829e..57477713 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -947,9 +947,9 @@ class CoreNetworkBase(NodeBase): will run on, default is None for localhost """ super().__init__(session, _id, name, server) - self.brname = None - self._linked = {} - self._linked_lock = threading.Lock() + self.brname: Optional[str] = None + self._linked: Dict[CoreInterface, Dict[CoreInterface, bool]] = {} + self._linked_lock: threading.Lock = threading.Lock() @abc.abstractmethod def startup(self) -> None: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 1389cb4f..42b30d16 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -21,7 +21,7 @@ from core.emulator.enumerations import ( RegisterTlvs, ) from core.errors import CoreCommandError, CoreError -from core.executables import EBTABLES, TC +from core.executables import NFTABLES, TC from core.nodes.base import CoreNetworkBase from core.nodes.interface import CoreInterface, GreTap, Veth from core.nodes.netclient import get_net_client @@ -36,31 +36,31 @@ if TYPE_CHECKING: WirelessModelType = Type[WirelessModel] LEARNING_DISABLED: int = 0 -ebtables_lock: threading.Lock = threading.Lock() +NFTABLES_LOCK: threading.Lock = threading.Lock() -class EbtablesQueue: +class NftablesQueue: """ - Helper class for queuing up ebtables commands into rate-limited + Helper class for queuing up nftables commands into rate-limited atomic commits. This improves performance and reliability when there are many WLAN link updates. """ # update rate is every 300ms rate: float = 0.3 - # ebtables - atomic_file: str = "/tmp/pycore.ebtables.atomic" + atomic_file: str = "/tmp/pycore.nftables.atomic" + chain: str = "forward" def __init__(self) -> None: """ Initialize the helper class, but don't start the update thread until a WLAN is instantiated. """ - self.doupdateloop: bool = False - self.updatethread: Optional[threading.Thread] = None + self.running: bool = False + self.run_thread: Optional[threading.Thread] = None # this lock protects cmds and updates lists - self.updatelock: threading.Lock = threading.Lock() - # list of pending ebtables commands + self.lock: threading.Lock = threading.Lock() + # list of pending nftables commands self.cmds: List[str] = [] # list of WLANs requiring update self.updates: List["CoreNetwork"] = [] @@ -68,192 +68,164 @@ class EbtablesQueue: # using this queue self.last_update_time: Dict["CoreNetwork", float] = {} - def startupdateloop(self, wlan: "CoreNetwork") -> None: + def start(self, net: "CoreNetwork") -> None: """ - Kick off the update loop; only needs to be invoked once. - + Start thread to listen for updates for the provided network. + :param net: network to start checking updates :return: nothing """ - with self.updatelock: - self.last_update_time[wlan] = time.monotonic() - if self.doupdateloop: + with self.lock: + self.last_update_time[net] = time.monotonic() + if self.running: return - self.doupdateloop = True - self.updatethread = threading.Thread(target=self.updateloop, daemon=True) - self.updatethread.start() + self.running = True + self.run_thread = threading.Thread(target=self.run, daemon=True) + self.run_thread.start() - def stopupdateloop(self, wlan: "CoreNetwork") -> None: + def stop(self, net: "CoreNetwork") -> None: """ - Kill the update loop thread if there are no more WLANs using it. - + Stop updates for network, when no networks remain, stop update thread. + :param net: network to stop watching updates :return: nothing """ - with self.updatelock: - try: - del self.last_update_time[wlan] - except KeyError: - logger.exception( - "error deleting last update time for wlan, ignored before: %s", wlan - ) - if len(self.last_update_time) > 0: - return - self.doupdateloop = False - if self.updatethread: - self.updatethread.join() - self.updatethread = None + with self.lock: + self.last_update_time.pop(net, None) + if self.last_update_time: + return + self.running = False + if self.run_thread: + self.run_thread.join() + self.run_thread = None - def ebatomiccmd(self, cmd: str) -> str: + def last_update(self, net: "CoreNetwork") -> float: """ - Helper for building ebtables atomic file command list. - - :param cmd: ebtable command - :return: ebtable atomic command - """ - return f"{EBTABLES} --atomic-file {self.atomic_file} {cmd}" - - def lastupdate(self, wlan: "CoreNetwork") -> float: - """ - Return the time elapsed since this WLAN was last updated. - - :param wlan: wlan entity + Return the time elapsed since this network was last updated. + :param net: network node :return: elpased time """ - try: - elapsed = time.monotonic() - self.last_update_time[wlan] - except KeyError: - self.last_update_time[wlan] = time.monotonic() + if net in self.last_update_time: + elapsed = time.monotonic() - self.last_update_time[net] + else: + self.last_update_time[net] = time.monotonic() elapsed = 0.0 - return elapsed - def updated(self, wlan: "CoreNetwork") -> None: + def updated(self, net: "CoreNetwork") -> None: """ - Keep track of when this WLAN was last updated. + Keep track of when this network was last updated. - :param wlan: wlan entity + :param net: network node :return: nothing """ - self.last_update_time[wlan] = time.monotonic() - self.updates.remove(wlan) + self.last_update_time[net] = time.monotonic() + self.updates.remove(net) - def updateloop(self) -> None: + def run(self) -> None: """ - Thread target that looks for WLANs needing update, and - rate limits the amount of ebtables activity. Only one userspace program - should use ebtables at any given time, or results can be unpredictable. + Thread target that looks for networks needing update, and + rate limits the amount of nftables activity. Only one userspace program + should use nftables at any given time, or results can be unpredictable. :return: nothing """ - while self.doupdateloop: - with self.updatelock: - for wlan in self.updates: - # Check if wlan is from a previously closed session. Because of the - # rate limiting scheme employed here, this may happen if a new session - # is started soon after closing a previous session. - # TODO: if these are WlanNodes, this will never throw an exception - try: - wlan.session - except Exception: - # Just mark as updated to remove from self.updates. - self.updated(wlan) + while self.running: + with self.lock: + for net in self.updates: + if not net.up: + self.updated(net) continue - - if self.lastupdate(wlan) > self.rate: - self.buildcmds(wlan) - self.ebcommit(wlan) - self.updated(wlan) - + if self.last_update(net) > self.rate: + self.build_cmds(net) + self.commit(net) + self.updated(net) time.sleep(self.rate) - def ebcommit(self, wlan: "CoreNetwork") -> None: + def commit(self, net: "CoreNetwork") -> None: """ - Perform ebtables atomic commit using commands built in the self.cmds list. - + Commit changes to nftables for the provided network. + :param net: network to commit nftables changes :return: nothing """ - # save kernel ebtables snapshot to a file - args = self.ebatomiccmd("--atomic-save") - wlan.host_cmd(args) + if not self.cmds: + return + # write out nft commands to file + for cmd in self.cmds: + net.host_cmd(f"echo {cmd} >> {self.atomic_file}", shell=True) + # read file as atomic change + net.host_cmd(f"{NFTABLES} -f {self.atomic_file}") + # remove file + net.host_cmd(f"rm -f {self.atomic_file}") + self.cmds.clear() - # modify the table file using queued ebtables commands - for c in self.cmds: - args = self.ebatomiccmd(c) - wlan.host_cmd(args) - self.cmds = [] - - # commit the table file to the kernel - args = self.ebatomiccmd("--atomic-commit") - wlan.host_cmd(args) - - try: - wlan.host_cmd(f"rm -f {self.atomic_file}") - except CoreCommandError: - logger.exception("error removing atomic file: %s", self.atomic_file) - - def ebchange(self, wlan: "CoreNetwork") -> None: + def update(self, net: "CoreNetwork") -> None: """ - Flag a change to the given WLAN's _linked dict, so the ebtables - chain will be rebuilt at the next interval. - + Flag this network has an update, so the nftables chain will be rebuilt. + :param net: wlan network :return: nothing """ - with self.updatelock: - if wlan not in self.updates: - self.updates.append(wlan) + with self.lock: + if net not in self.updates: + self.updates.append(net) - def buildcmds(self, wlan: "CoreNetwork") -> None: + def build_cmds(self, net: "CoreNetwork") -> None: """ - Inspect a _linked dict from a wlan, and rebuild the ebtables chain for that WLAN. - + Inspect linked nodes for a network, and rebuild the nftables chain commands. + :param net: network to build commands for :return: nothing """ - with wlan._linked_lock: - if wlan.has_ebtables_chain: - # flush the chain - self.cmds.append(f"-F {wlan.brname}") + with net._linked_lock: + if net.has_nftables_chain: + self.cmds.append(f"flush table bridge {net.brname}") else: - wlan.has_ebtables_chain = True - self.cmds.extend( - [ - f"-N {wlan.brname} -P {wlan.policy.value}", - f"-A FORWARD --logical-in {wlan.brname} -j {wlan.brname}", - ] + net.has_nftables_chain = True + policy = net.policy.value.lower() + self.cmds.append(f"add table bridge {net.brname}") + self.cmds.append( + f"add chain bridge {net.brname} {self.chain} {{type filter hook " + f"forward priority 0\\; policy {policy}\\;}}" ) + # add default rule to accept all traffic not for this bridge + self.cmds.append( + f"add rule bridge {net.brname} {self.chain} " + f"ibriport != {net.brname} accept" + ) # rebuild the chain - 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 {iface1.localname} -o {oface2.localname} -j ACCEPT", - f"-A {wlan.brname} -o {iface1.localname} -i {oface2.localname} -j ACCEPT", - ] + for iface1, v in net._linked.items(): + for iface2, linked in v.items(): + policy = None + if net.policy == NetworkPolicy.DROP and linked: + policy = "accept" + elif net.policy == NetworkPolicy.ACCEPT and not linked: + policy = "drop" + if policy: + self.cmds.append( + f"add rule bridge {net.brname} {self.chain} " + f"iif {iface1.localname} oif {iface2.localname} " + f"{policy}" ) - elif wlan.policy == NetworkPolicy.ACCEPT and not linked: - self.cmds.extend( - [ - f"-A {wlan.brname} -i {iface1.localname} -o {oface2.localname} -j DROP", - f"-A {wlan.brname} -o {iface1.localname} -i {oface2.localname} -j DROP", - ] + self.cmds.append( + f"add rule bridge {net.brname} {self.chain} " + f"oif {iface1.localname} iif {iface2.localname} " + f"{policy}" ) -# a global object because all WLANs share the same queue -# cannot have multiple threads invoking the ebtables commnd -ebq: EbtablesQueue = EbtablesQueue() +# a global object because all networks share the same queue +# cannot have multiple threads invoking the nftables commnd +nft_queue: NftablesQueue = NftablesQueue() -def ebtablescmds(call: Callable[..., str], cmds: List[str]) -> None: +def nftables_cmds(call: Callable[..., str], cmds: List[str]) -> None: """ - Run ebtable commands. + Run nftable commands. :param call: function to call commands :param cmds: commands to call :return: nothing """ - with ebtables_lock: - for args in cmds: - call(args) + with NFTABLES_LOCK: + for cmd in cmds: + call(cmd) class CoreNetwork(CoreNetworkBase): @@ -285,11 +257,11 @@ class CoreNetwork(CoreNetworkBase): if name is None: name = str(self.id) if policy is not None: - self.policy = policy + self.policy: NetworkPolicy = policy self.name: Optional[str] = name sessionid = self.session.short_session_id() self.brname: str = f"b.{self.id}.{sessionid}" - self.has_ebtables_chain: bool = False + self.has_nftables_chain: bool = False def host_cmd( self, @@ -324,9 +296,9 @@ class CoreNetwork(CoreNetworkBase): :raises CoreCommandError: when there is a command exception """ self.net_client.create_bridge(self.brname) - self.has_ebtables_chain = False + self.has_nftables_chain = False self.up = True - ebq.startupdateloop(self) + nft_queue.start(self) def shutdown(self) -> None: """ @@ -336,23 +308,19 @@ class CoreNetwork(CoreNetworkBase): """ if not self.up: return - ebq.stopupdateloop(self) + nft_queue.stop(self) try: self.net_client.delete_bridge(self.brname) - if self.has_ebtables_chain: - cmds = [ - f"{EBTABLES} -D FORWARD --logical-in {self.brname} -j {self.brname}", - f"{EBTABLES} -X {self.brname}", - ] - ebtablescmds(self.host_cmd, cmds) + if self.has_nftables_chain: + cmds = [f"{NFTABLES} delete table bridge {self.brname}"] + nftables_cmds(self.host_cmd, cmds) except CoreCommandError: - logger.exception("error during shutdown") + logging.exception("error during shutdown") # removes veth pairs used for bridge-to-bridge connections for iface in self.get_ifaces(): iface.shutdown() self.ifaces.clear() self._linked.clear() - del self.session self.up = False def attach(self, iface: CoreInterface) -> None: @@ -404,8 +372,7 @@ class CoreNetwork(CoreNetworkBase): def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None: """ - Unlink two interfaces, resulting in adding or removing ebtables - filtering rules. + Unlink two interfaces, resulting in adding or removing filtering rules. :param iface1: interface one :param iface2: interface two @@ -415,13 +382,12 @@ class CoreNetwork(CoreNetworkBase): if not self.linked(iface1, iface2): return self._linked[iface1][iface2] = False - - ebq.ebchange(self) + nft_queue.update(self) def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None: """ Link two interfaces together, resulting in adding or removing - ebtables filtering rules. + filtering rules. :param iface1: interface one :param iface2: interface two @@ -431,8 +397,7 @@ class CoreNetwork(CoreNetworkBase): if self.linked(iface1, iface2): return self._linked[iface1][iface2] = True - - ebq.ebchange(self) + nft_queue.update(self) def linkconfig( self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None @@ -986,7 +951,7 @@ class WlanNode(CoreNetwork): :return: nothing """ super().startup() - ebq.ebchange(self) + nft_queue.update(self) def attach(self, iface: CoreInterface) -> None: """ diff --git a/daemon/scripts/core-cleanup b/daemon/scripts/core-cleanup index 8182a917..c97d6843 100755 --- a/daemon/scripts/core-cleanup +++ b/daemon/scripts/core-cleanup @@ -63,8 +63,8 @@ eval "$ifcommand" | awk ' /b\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);} ' -ebtables -L FORWARD | awk ' - /^-.*b\./ {print "removing ebtables " $0; system("ebtables -D FORWARD " $0); print "removing ebtables chain " $4; system("ebtables -X " $4);} +nft list ruleset | awk ' + $3 ~ /^b\./ {print "removing nftables " $3; system("nft delete table bridge " $3);} ' rm -rf /tmp/pycore* diff --git a/docs/architecture.md b/docs/architecture.md index c262d2be..ceaf7cc2 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -51,8 +51,8 @@ filesystem in CORE. CORE combines these namespaces with Linux Ethernet bridging to form networks. Link characteristics are applied using Linux Netem queuing disciplines. -Ebtables is Ethernet frame filtering on Linux bridges. Wireless networks are -emulated by controlling which interfaces can send and receive with ebtables +Nftables provides Ethernet frame filtering on Linux bridges. Wireless networks are +emulated by controlling which interfaces can send and receive with nftables rules. ## Prior Work diff --git a/docs/devguide.md b/docs/devguide.md index ba34a211..e3b0ad18 100644 --- a/docs/devguide.md +++ b/docs/devguide.md @@ -104,7 +104,7 @@ vcmd -c /tmp/pycore.50160/n1 -- /sbin/ip -4 ro A script named *core-cleanup* is provided to clean up any running CORE emulations. It will attempt to kill any remaining vnoded processes, kill any EMANE processes, remove the :file:`/tmp/pycore.*` session directories, and remove -any bridges or *ebtables* rules. With a *-d* option, it will also kill any running CORE daemon. +any bridges or *nftables* rules. With a *-d* option, it will also kill any running CORE daemon. ### netns command @@ -121,5 +121,5 @@ ip link show type bridge # view the netem rules used for applying link effects tc qdisc show # view the rules that make the wireless LAN work -ebtables -L +nft list ruleset ``` diff --git a/docs/distributed.md b/docs/distributed.md index ad3d61f8..2d46ac96 100644 --- a/docs/distributed.md +++ b/docs/distributed.md @@ -172,7 +172,7 @@ will draw the link with a dashed line. Wireless nodes, i.e. those connected to a WLAN node, can be assigned to different emulation servers and participate in the same wireless network only if an EMANE model is used for the WLAN. The basic range model does -not work across multiple servers due to the Linux bridging and ebtables +not work across multiple servers due to the Linux bridging and nftables rules that are used. **NOTE: The basic range wireless model does not support distributed emulation, diff --git a/docs/gui.md b/docs/gui.md index 85bbb6cd..6a333752 100644 --- a/docs/gui.md +++ b/docs/gui.md @@ -544,7 +544,7 @@ on platform. See the table below for a brief overview of wireless model types. |Model|Type|Supported Platform(s)|Fidelity|Description| |-----|----|---------------------|--------|-----------| -|Basic|on/off|Linux|Low|Ethernet bridging with ebtables| +|Basic|on/off|Linux|Low|Ethernet bridging with nftables| |EMANE|Plug-in|Linux|High|TAP device connected to EMANE emulator with pluggable MAC and PHY radio types| To quickly build a wireless network, you can first place several router nodes diff --git a/docs/install.md b/docs/install.md index 5c66f420..11c21f38 100644 --- a/docs/install.md +++ b/docs/install.md @@ -15,20 +15,14 @@ containers, as a general rule you should select a machine having as much RAM and * Linux Kernel v3.3+ * iproute2 4.5+ is a requirement for bridge related commands -* ebtables not backed by nftables +* nftables compatible kernel and nft command line tool ### Supported Linux Distributions Plan is to support recent Ubuntu and CentOS LTS releases. Verified: * Ubuntu - 18.04, 20.04 -* CentOS - 7.8, 8.0* - -> **NOTE:** Ubuntu 20.04 requires installing legacy ebtables for WLAN -> functionality - -> **NOTE:** CentOS 8 does not provide legacy ebtables support, WLAN will not -> function properly +* CentOS - 7.8, 8.0 > **NOTE:** CentOS 8 does not have the netem kernel mod available by default diff --git a/docs/pygui.md b/docs/pygui.md index f3e2c592..2e9f726a 100644 --- a/docs/pygui.md +++ b/docs/pygui.md @@ -521,7 +521,7 @@ on platform. See the table below for a brief overview of wireless model types. |Model|Type|Supported Platform(s)|Fidelity|Description| |-----|----|---------------------|--------|-----------| -|Basic|on/off|Linux|Low|Ethernet bridging with ebtables| +|Basic|on/off|Linux|Low|Ethernet bridging with nftables| |EMANE|Plug-in|Linux|High|TAP device connected to EMANE emulator with pluggable MAC and PHY radio types| To quickly build a wireless network, you can first place several router nodes diff --git a/tasks.py b/tasks.py index 84fb5b72..12c1a458 100644 --- a/tasks.py +++ b/tasks.py @@ -159,14 +159,14 @@ def check_existing_core(c: Context, hide: bool) -> None: def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: if os_info.like == OsLike.DEBIAN: c.run( - "sudo apt install -y automake pkg-config gcc libev-dev ebtables " + "sudo apt install -y automake pkg-config gcc libev-dev nftables " "iproute2 ethtool tk python3-tk bash", hide=hide ) elif os_info.like == OsLike.REDHAT: c.run( "sudo yum install -y automake pkgconf-pkg-config gcc gcc-c++ " - "libev-devel iptables-ebtables iproute python3-devel python3-tkinter " + "libev-devel nftables iproute python3-devel python3-tkinter " "tk ethtool make bash", hide=hide ) @@ -179,18 +179,6 @@ def install_system(c: Context, os_info: OsInfo, hide: bool) -> None: print("sudo yum update") sys.exit(1) - # attempt to setup legacy ebtables when an nftables based version is found - r = c.run("ebtables -V", hide=hide) - if "nf_tables" in r.stdout: - if not c.run( - "sudo update-alternatives --set ebtables /usr/sbin/ebtables-legacy", - warn=True, - hide=hide - ): - print( - "\nWARNING: unable to setup ebtables-legacy, WLAN will not work" - ) - def install_grpcio(c: Context, hide: bool) -> None: c.run( From 11d8bb0674fc06bf941cdb4c8d3f1cdcb022ecf3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 10 May 2021 15:18:15 -0700 Subject: [PATCH 053/110] daemon: renamed network variables to not be named in a private way, since they were being used externally --- daemon/core/location/mobility.py | 12 ++++++------ daemon/core/nodes/base.py | 12 ++++++------ daemon/core/nodes/network.py | 28 ++++++++++++++-------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 428bc06b..7bf5c053 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -408,8 +408,8 @@ class BasicRangeModel(WirelessModel): a = min(iface, iface2) b = max(iface, iface2) - with self.wlan._linked_lock: - linked = self.wlan.linked(a, b) + with self.wlan.linked_lock: + linked = self.wlan.is_linked(a, b) if d > self.range: if linked: logger.debug("was linked, unlinking") @@ -508,10 +508,10 @@ class BasicRangeModel(WirelessModel): :return: all link data """ all_links = [] - with self.wlan._linked_lock: - for a in self.wlan._linked: - for b in self.wlan._linked[a]: - if self.wlan._linked[a][b]: + with self.wlan.linked_lock: + for a in self.wlan.linked: + for b in self.wlan.linked[a]: + if self.wlan.linked[a][b]: all_links.append(self.create_link_data(a, b, flags)) return all_links diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 57477713..926f4d7e 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -948,8 +948,8 @@ class CoreNetworkBase(NodeBase): """ super().__init__(session, _id, name, server) self.brname: Optional[str] = None - self._linked: Dict[CoreInterface, Dict[CoreInterface, bool]] = {} - self._linked_lock: threading.Lock = threading.Lock() + self.linked: Dict[CoreInterface, Dict[CoreInterface, bool]] = {} + self.linked_lock: threading.Lock = threading.Lock() @abc.abstractmethod def startup(self) -> None: @@ -1018,8 +1018,8 @@ class CoreNetworkBase(NodeBase): i = self.next_iface_id() self.ifaces[i] = iface iface.net_id = i - with self._linked_lock: - self._linked[iface] = {} + with self.linked_lock: + self.linked[iface] = {} def detach(self, iface: CoreInterface) -> None: """ @@ -1030,8 +1030,8 @@ class CoreNetworkBase(NodeBase): """ del self.ifaces[iface.net_id] iface.net_id = None - with self._linked_lock: - del self._linked[iface] + with self.linked_lock: + del self.linked[iface] def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 42b30d16..1920eed6 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -173,7 +173,7 @@ class NftablesQueue: :param net: network to build commands for :return: nothing """ - with net._linked_lock: + with net.linked_lock: if net.has_nftables_chain: self.cmds.append(f"flush table bridge {net.brname}") else: @@ -190,7 +190,7 @@ class NftablesQueue: f"ibriport != {net.brname} accept" ) # rebuild the chain - for iface1, v in net._linked.items(): + for iface1, v in net.linked.items(): for iface2, linked in v.items(): policy = None if net.policy == NetworkPolicy.DROP and linked: @@ -320,7 +320,7 @@ class CoreNetwork(CoreNetworkBase): for iface in self.get_ifaces(): iface.shutdown() self.ifaces.clear() - self._linked.clear() + self.linked.clear() self.up = False def attach(self, iface: CoreInterface) -> None: @@ -345,7 +345,7 @@ class CoreNetwork(CoreNetworkBase): iface.net_client.delete_iface(self.brname, iface.localname) super().detach(iface) - def linked(self, iface1: CoreInterface, iface2: CoreInterface) -> bool: + def is_linked(self, iface1: CoreInterface, iface2: CoreInterface) -> bool: """ Determine if the provided network interfaces are linked. @@ -359,7 +359,7 @@ class CoreNetwork(CoreNetworkBase): if self.ifaces[iface2.net_id] != iface2: raise ValueError(f"inconsistency for interface {iface2.name}") try: - linked = self._linked[iface1][iface2] + linked = self.linked[iface1][iface2] except KeyError: if self.policy == NetworkPolicy.ACCEPT: linked = True @@ -367,7 +367,7 @@ class CoreNetwork(CoreNetworkBase): linked = False else: raise Exception(f"unknown policy: {self.policy.value}") - self._linked[iface1][iface2] = linked + self.linked[iface1][iface2] = linked return linked def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None: @@ -378,10 +378,10 @@ class CoreNetwork(CoreNetworkBase): :param iface2: interface two :return: nothing """ - with self._linked_lock: - if not self.linked(iface1, iface2): + with self.linked_lock: + if not self.is_linked(iface1, iface2): return - self._linked[iface1][iface2] = False + self.linked[iface1][iface2] = False nft_queue.update(self) def link(self, iface1: CoreInterface, iface2: CoreInterface) -> None: @@ -393,10 +393,10 @@ class CoreNetwork(CoreNetworkBase): :param iface2: interface two :return: nothing """ - with self._linked_lock: - if self.linked(iface1, iface2): + with self.linked_lock: + if self.is_linked(iface1, iface2): return - self._linked[iface1][iface2] = True + self.linked[iface1][iface2] = True nft_queue.update(self) def linkconfig( @@ -503,8 +503,8 @@ class CoreNetwork(CoreNetworkBase): 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[iface] = {} + with net.linked_lock: + net.linked[iface] = {} iface.net = self iface.othernet = net return iface From 5286938e448c8eeb6b6e0d3d951b8d124e9f67e8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 10 May 2021 20:34:10 -0700 Subject: [PATCH 054/110] daemon: small cleanup to nftables management --- daemon/core/nodes/network.py | 64 ++++++++++++++---------------------- 1 file changed, 25 insertions(+), 39 deletions(-) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 1920eed6..4b8b6e90 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -7,7 +7,7 @@ import math import threading import time from pathlib import Path -from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Type import netaddr @@ -36,7 +36,6 @@ if TYPE_CHECKING: WirelessModelType = Type[WirelessModel] LEARNING_DISABLED: int = 0 -NFTABLES_LOCK: threading.Lock = threading.Lock() class NftablesQueue: @@ -63,7 +62,7 @@ class NftablesQueue: # list of pending nftables commands self.cmds: List[str] = [] # list of WLANs requiring update - self.updates: List["CoreNetwork"] = [] + self.updates: Set["CoreNetwork"] = set() # timestamps of last WLAN update; this keeps track of WLANs that are # using this queue self.last_update_time: Dict["CoreNetwork", float] = {} @@ -101,24 +100,11 @@ class NftablesQueue: """ Return the time elapsed since this network was last updated. :param net: network node - :return: elpased time + :return: elapsed time """ - if net in self.last_update_time: - elapsed = time.monotonic() - self.last_update_time[net] - else: - self.last_update_time[net] = time.monotonic() - elapsed = 0.0 - return elapsed - - def updated(self, net: "CoreNetwork") -> None: - """ - Keep track of when this network was last updated. - - :param net: network node - :return: nothing - """ - self.last_update_time[net] = time.monotonic() - self.updates.remove(net) + now = time.monotonic() + last_update = self.last_update_time.setdefault(net, now) + return now - last_update def run(self) -> None: """ @@ -130,14 +116,18 @@ class NftablesQueue: """ while self.running: with self.lock: + discard = set() for net in self.updates: if not net.up: - self.updated(net) + self.last_update_time[net] = time.monotonic() + discard.add(net) continue if self.last_update(net) > self.rate: self.build_cmds(net) self.commit(net) - self.updated(net) + self.last_update_time[net] = time.monotonic() + discard.add(net) + self.updates -= discard time.sleep(self.rate) def commit(self, net: "CoreNetwork") -> None: @@ -164,8 +154,18 @@ class NftablesQueue: :return: nothing """ with self.lock: - if net not in self.updates: - self.updates.append(net) + self.updates.add(net) + + def delete_table(self, net: "CoreNetwork") -> None: + """ + Delete nftable bridge rule table. + + :param net: network to delete table for + :param name: name of bridge table to delete + :return: nothing + """ + with self.lock: + net.host_cmd(f"{NFTABLES} delete table bridge {net.brname}") def build_cmds(self, net: "CoreNetwork") -> None: """ @@ -215,19 +215,6 @@ class NftablesQueue: nft_queue: NftablesQueue = NftablesQueue() -def nftables_cmds(call: Callable[..., str], cmds: List[str]) -> None: - """ - Run nftable commands. - - :param call: function to call commands - :param cmds: commands to call - :return: nothing - """ - with NFTABLES_LOCK: - for cmd in cmds: - call(cmd) - - class CoreNetwork(CoreNetworkBase): """ Provides linux bridge network functionality for core nodes. @@ -312,8 +299,7 @@ class CoreNetwork(CoreNetworkBase): try: self.net_client.delete_bridge(self.brname) if self.has_nftables_chain: - cmds = [f"{NFTABLES} delete table bridge {self.brname}"] - nftables_cmds(self.host_cmd, cmds) + nft_queue.delete_table(self) except CoreCommandError: logging.exception("error during shutdown") # removes veth pairs used for bridge-to-bridge connections From 5e843a7674c3be650f3b88164ce6a9503398feb1 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 10 May 2021 23:31:11 -0700 Subject: [PATCH 055/110] daemon: update to nftables update tracker to use a queue for more reactive changes --- daemon/core/nodes/network.py | 51 ++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 4b8b6e90..f9511924 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -6,8 +6,10 @@ import logging import math import threading import time +from collections import OrderedDict from pathlib import Path -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Type +from queue import Queue +from typing import TYPE_CHECKING, Dict, List, Optional, Type import netaddr @@ -38,6 +40,22 @@ if TYPE_CHECKING: LEARNING_DISABLED: int = 0 +class SetQueue(Queue): + """ + Set backed queue to avoid duplicate submissions. + """ + + def _init(self, maxsize): + self.queue: OrderedDict = OrderedDict() + + def _put(self, item): + self.queue[item] = None + + def _get(self): + key, _ = self.queue.popitem(last=False) + return key + + class NftablesQueue: """ Helper class for queuing up nftables commands into rate-limited @@ -62,7 +80,7 @@ class NftablesQueue: # list of pending nftables commands self.cmds: List[str] = [] # list of WLANs requiring update - self.updates: Set["CoreNetwork"] = set() + self.updates: SetQueue = SetQueue() # timestamps of last WLAN update; this keeps track of WLANs that are # using this queue self.last_update_time: Dict["CoreNetwork", float] = {} @@ -93,6 +111,7 @@ class NftablesQueue: return self.running = False if self.run_thread: + self.updates.put(None) self.run_thread.join() self.run_thread = None @@ -115,20 +134,16 @@ class NftablesQueue: :return: nothing """ while self.running: - with self.lock: - discard = set() - for net in self.updates: - if not net.up: - self.last_update_time[net] = time.monotonic() - discard.add(net) - continue - if self.last_update(net) > self.rate: - self.build_cmds(net) - self.commit(net) - self.last_update_time[net] = time.monotonic() - discard.add(net) - self.updates -= discard - time.sleep(self.rate) + net = self.updates.get() + if net is None: + break + if not net.up: + self.last_update_time[net] = time.monotonic() + elif self.last_update(net) > self.rate: + with self.lock: + self.build_cmds(net) + self.commit(net) + self.last_update_time[net] = time.monotonic() def commit(self, net: "CoreNetwork") -> None: """ @@ -153,15 +168,13 @@ class NftablesQueue: :param net: wlan network :return: nothing """ - with self.lock: - self.updates.add(net) + self.updates.put(net) def delete_table(self, net: "CoreNetwork") -> None: """ Delete nftable bridge rule table. :param net: network to delete table for - :param name: name of bridge table to delete :return: nothing """ with self.lock: From c2fdbbca32347b8f3c6cc37b9e3f1065b5755f20 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 11 May 2021 20:52:10 -0700 Subject: [PATCH 056/110] docs: updated installation page to note commands for enabling the core service, when desired --- docs/install.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/install.md b/docs/install.md index 11c21f38..dd6ea8eb 100644 --- a/docs/install.md +++ b/docs/install.md @@ -149,6 +149,15 @@ cd core ./install.sh -p -l ``` +### Enabling Service +After installation, the core service is not enabled by default. If you desire to use the +service, run the following commands. + +```shell +sudo systemctl enable core-daemon +sudo systemctl start core-daemon +``` + ### Unsupported Linux Distribution For unsupported OSs you could attempt to do the following to translate an installation to your use case. From 86cd0a8e18143ac3b62bd880ebd29228d75ba5f6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 11 May 2021 21:10:56 -0700 Subject: [PATCH 057/110] install: fixes to properly install/uninstall example files to desired directory as before --- tasks.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tasks.py b/tasks.py index 12c1a458..49c4e879 100644 --- a/tasks.py +++ b/tasks.py @@ -265,13 +265,14 @@ def install_service(c, verbose=False, prefix=DEFAULT_PREFIX): print(f"ERROR: systemd service path not found: {systemd_dir}") -def install_scripts(c, local=False, verbose=False, prefix=DEFAULT_PREFIX): +def install_core_files(c, local=False, verbose=False, prefix=DEFAULT_PREFIX): """ - install core script files, modified to leverage virtual environment + install core files (scripts, examples, and configuration) """ hide = not verbose python = get_python(c) bin_dir = Path(prefix).joinpath("bin") + # install scripts for script in Path("daemon/scripts").iterdir(): dest = bin_dir.joinpath(script.name) with open(script, "r") as f: @@ -290,7 +291,6 @@ def install_scripts(c, local=False, verbose=False, prefix=DEFAULT_PREFIX): # copy normal links else: c.run(f"sudo cp {script} {dest}", hide=hide) - # setup core python helper if not local: core_python = bin_dir.joinpath("core-python") @@ -303,12 +303,15 @@ def install_scripts(c, local=False, verbose=False, prefix=DEFAULT_PREFIX): c.run(f"sudo cp {temp.name} {core_python}", hide=hide) c.run(f"sudo chmod 755 {core_python}", hide=hide) os.unlink(temp.name) - # install core configuration file config_dir = "/etc/core" c.run(f"sudo mkdir -p {config_dir}", hide=hide) c.run(f"sudo cp -n daemon/data/core.conf {config_dir}", hide=hide) c.run(f"sudo cp -n daemon/data/logging.conf {config_dir}", hide=hide) + # install examples + examples_dir = f"{prefix}/share/core" + c.run(f"sudo mkdir -p {examples_dir}", hide=hide) + c.run(f"sudo cp -r daemon/examples {examples_dir}", hide=hide) @task( @@ -354,8 +357,8 @@ def install( install_type = "core" if local else "core virtual environment" with p.start(f"installing {install_type}"): install_poetry(c, dev, local, hide) - with p.start("installing scripts and /etc/core"): - install_scripts(c, local, hide, prefix) + with p.start("installing scripts, examples, and configuration"): + install_core_files(c, local, hide, prefix) with p.start("installing systemd service"): install_service(c, hide, prefix) if ospf: @@ -470,6 +473,9 @@ def uninstall( for script in Path("daemon/scripts").iterdir(): dest = bin_dir.joinpath(script.name) c.run(f"sudo rm -f {dest}", hide=hide) + with p.start("uninstalling examples"): + examples_dir = Path(prefix).joinpath("share/core") + c.run(f"sudo rm -rf {examples_dir}") # remove core-python symlink if not local: From e5d28b01c6800c6d135ef3ad2ed0c96e279fb7d7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 11 May 2021 22:59:21 -0700 Subject: [PATCH 058/110] pygui: removed host from gui, default node is now PC --- daemon/core/gui/nodeutils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daemon/core/gui/nodeutils.py b/daemon/core/gui/nodeutils.py index f829f48b..537cedf2 100644 --- a/daemon/core/gui/nodeutils.py +++ b/daemon/core/gui/nodeutils.py @@ -31,10 +31,9 @@ ANTENNA_ICON: Optional[PhotoImage] = None def setup() -> None: global ANTENNA_ICON nodes = [ - (ImageEnum.ROUTER, NodeType.DEFAULT, "Router", "router"), - (ImageEnum.HOST, NodeType.DEFAULT, "Host", "host"), (ImageEnum.PC, NodeType.DEFAULT, "PC", "PC"), (ImageEnum.MDR, NodeType.DEFAULT, "MDR", "mdr"), + (ImageEnum.ROUTER, NodeType.DEFAULT, "Router", "router"), (ImageEnum.PROUTER, NodeType.DEFAULT, "PRouter", "prouter"), (ImageEnum.DOCKER, NodeType.DOCKER, "Docker", None), (ImageEnum.LXC, NodeType.LXC, "LXC", None), From d16f6b234bb7961c1ced93fa1ad39859bae9f908 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 11 May 2021 23:57:35 -0700 Subject: [PATCH 059/110] daemon: fix for wlan to take loss value strings as floats --- daemon/core/location/mobility.py | 66 ++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 24 deletions(-) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 7bf5c053..ad7fc821 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -44,6 +44,43 @@ def get_mobility_node(session: "Session", node_id: int) -> Union[WlanNode, Emane return session.get_node(node_id, EmaneNet) +def get_config_int(current: int, config: Dict[str, str], name: str) -> Optional[int]: + """ + Convenience function to get config values as int. + + :param current: current config value to use when one is not provided + :param config: config to get values from + :param name: name of config value to get + :return: current config value when not provided, new value otherwise + """ + value = get_config_float(current, config, name) + if value is not None: + value = int(value) + return value + + +def get_config_float( + current: Union[int, float], config: Dict[str, str], name: str +) -> Optional[float]: + """ + Convenience function to get config values as float. + + :param current: current config value to use when one is not provided + :param config: config to get values from + :param name: name of config value to get + :return: current config value when not provided, new value otherwise + """ + value = config.get(name) + if value is not None: + if value == "": + value = None + else: + value = float(value) + else: + value = current + return value + + class MobilityManager(ModelManager): """ Member of session class for handling configuration data for mobility and @@ -295,25 +332,6 @@ class BasicRangeModel(WirelessModel): self.jitter: Optional[int] = None self.promiscuous: bool = False - def _get_config(self, current_value: int, config: Dict[str, str], name: str) -> int: - """ - Convenience for updating value to use from a provided configuration. - - :param current_value: current config value to use when one is not provided - :param config: config to get values from - :param name: name of config value to get - :return: current config value when not provided, new value otherwise - """ - value = config.get(name) - if value is not None: - if value == "": - value = None - else: - value = int(float(value)) - else: - value = current_value - return value - def setlinkparams(self) -> None: """ Apply link parameters to all interfaces. This is invoked from @@ -448,14 +466,14 @@ class BasicRangeModel(WirelessModel): :param config: values to update configuration :return: nothing """ - self.range = self._get_config(self.range, config, "range") + self.range = get_config_int(self.range, config, "range") if self.range is None: self.range = 0 logger.debug("wlan %s set range to %s", self.wlan.name, self.range) - self.bw = self._get_config(self.bw, config, "bandwidth") - self.delay = self._get_config(self.delay, config, "delay") - self.loss = self._get_config(self.loss, config, "error") - self.jitter = self._get_config(self.jitter, config, "jitter") + self.bw = get_config_int(self.bw, config, "bandwidth") + self.delay = get_config_int(self.delay, config, "delay") + self.loss = get_config_float(self.loss, config, "error") + self.jitter = get_config_int(self.jitter, config, "jitter") promiscuous = config.get("promiscuous", "0") == "1" if self.promiscuous and not promiscuous: self.wlan.net_client.set_mac_learning(self.wlan.brname, LEARNING_ENABLED) From e2a9f6b1f4cca9d365796edbaba81866dc4f9487 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 18 May 2021 09:48:38 -0700 Subject: [PATCH 060/110] daemon: initial changes to support one emane process per nem --- .../configservices/quaggaservices/services.py | 6 -- daemon/core/emane/emanemanager.py | 86 ++++++++--------- daemon/core/emane/linkmonitor.py | 24 +++-- daemon/core/xml/emanexml.py | 92 ++++++++++--------- 4 files changed, 109 insertions(+), 99 deletions(-) diff --git a/daemon/core/configservices/quaggaservices/services.py b/daemon/core/configservices/quaggaservices/services.py index 922117cb..fba892b4 100644 --- a/daemon/core/configservices/quaggaservices/services.py +++ b/daemon/core/configservices/quaggaservices/services.py @@ -227,12 +227,6 @@ class Ospfv3mdr(Ospfv3): name: str = "OSPFv3MDR" - def data(self) -> Dict[str, Any]: - for iface in self.node.get_ifaces(): - is_wireless = isinstance(iface.net, (WlanNode, EmaneNet)) - logger.info("MDR wireless: %s", is_wireless) - return dict() - def quagga_iface_config(self, iface: CoreInterface) -> str: config = super().quagga_iface_config(iface) if isinstance(iface.net, (WlanNode, EmaneNet)): diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 11eda990..337cd0de 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -114,10 +114,13 @@ class EmaneManager: self.eventchannel: Optional[Tuple[str, int, str]] = None self.event_device: Optional[str] = None - def next_nem_id(self) -> int: + def next_nem_id(self, iface: CoreInterface) -> int: nem_id = int(self.config["nem_id_start"]) while nem_id in self.nems_to_ifaces: nem_id += 1 + self.nems_to_ifaces[nem_id] = iface + self.ifaces_to_nems[iface] = nem_id + self.write_nem(iface, nem_id) return nem_id def get_config( @@ -385,26 +388,54 @@ class EmaneManager: return start_nodes def start_node(self, data: StartData) -> None: + node = data.node control_net = self.session.add_remove_control_net( 0, remove=False, conf_required=False ) - emanexml.build_platform_xml(self, control_net, data) - self.start_daemon(data.node) + if isinstance(node, CoreNode): + # setup ota device + otagroup, _otaport = self.config["otamanagergroup"].split(":") + otadev = self.config["otamanagerdevice"] + otanetidx = self.session.get_control_net_index(otadev) + eventgroup, _eventport = self.config["eventservicegroup"].split(":") + eventdev = self.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 + ) + if otanetidx > 0: + logger.info("adding ota device ctrl%d", otanetidx) + self.session.add_remove_control_iface( + node, otanetidx, remove=False, conf_required=False + ) + if eventservicenetidx >= 0: + logger.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 + logger.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: + node.node_net_client.create_route(eventgroup, eventdev) + # builds xmls and start emane daemons for iface in data.ifaces: + emanexml.build_platform_xml(self, control_net, node, iface) + self.start_daemon(node, iface) self.install_iface(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 get_nem_port(self, iface: CoreInterface) -> int: + nem_id = self.get_nem_id(iface) + return int(f"47{nem_id:03}") + def write_nem(self, iface: CoreInterface, nem_id: int) -> None: path = self.session.directory / "emane_nems" try: @@ -549,7 +580,7 @@ class EmaneManager: ) ) - def start_daemon(self, node: CoreNodeBase) -> None: + def start_daemon(self, node: CoreNodeBase, iface: CoreInterface) -> None: """ Start one EMANE daemon per node having a radio. Add a control network even if the user has not configured one. @@ -565,42 +596,15 @@ class EmaneManager: if realtime: emanecmd += " -r" if isinstance(node, CoreNode): - otagroup, _otaport = self.config["otamanagergroup"].split(":") - otadev = self.config["otamanagerdevice"] - otanetidx = self.session.get_control_net_index(otadev) - eventgroup, _eventport = self.config["eventservicegroup"].split(":") - eventdev = self.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 - ) - if otanetidx > 0: - logger.info("adding ota device ctrl%d", otanetidx) - self.session.add_remove_control_iface( - node, otanetidx, remove=False, conf_required=False - ) - if eventservicenetidx >= 0: - logger.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 - logger.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: - node.node_net_client.create_route(eventgroup, eventdev) # start emane - log_file = node.directory / f"{node.name}-emane.log" - platform_xml = node.directory / f"{node.name}-platform.xml" + log_file = node.directory / f"{iface.name}-emane.log" + platform_xml = node.directory / emanexml.platform_file_name(iface) args = f"{emanecmd} -f {log_file} {platform_xml}" node.cmd(args) logger.info("node(%s) emane daemon running: %s", node.name, args) else: - log_file = self.session.directory / f"{node.name}-emane.log" - platform_xml = self.session.directory / f"{node.name}-platform.xml" + log_file = self.session.directory / f"{iface.name}-emane.log" + platform_xml = self.session.directory / emanexml.platform_file_name(iface) args = f"{emanecmd} -f {log_file} {platform_xml}" node.host_cmd(args, cwd=self.session.directory) logger.info("node(%s) host emane daemon running: %s", node.name, args) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index 0c29b7a8..fd27c51d 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple from lxml import etree +from core.emane.nodes import EmaneNet from core.emulator.data import LinkData from core.emulator.enumerations import LinkTypes, MessageFlags from core.nodes.network import CtrlNet @@ -24,7 +25,6 @@ except ImportError: if TYPE_CHECKING: from core.emane.emanemanager import EmaneManager -DEFAULT_PORT: int = 47_000 MAC_COMPONENT_INDEX: int = 1 EMANE_RFPIPE: str = "rfpipemaclayer" EMANE_80211: str = "ieee80211abgmaclayer" @@ -79,10 +79,10 @@ class EmaneLink: class EmaneClient: - def __init__(self, address: str) -> None: + def __init__(self, address: str, port: int) -> None: self.address: str = address self.client: shell.ControlPortClient = shell.ControlPortClient( - self.address, DEFAULT_PORT + self.address, port ) self.nems: Dict[int, LossTable] = {} self.setup() @@ -204,22 +204,28 @@ class EmaneLinkMonitor: def initialize(self) -> None: addresses = self.get_addresses() - for address in addresses: - client = EmaneClient(address) + for address, port in addresses: + client = EmaneClient(address, port) if client.nems: self.clients.append(client) - def get_addresses(self) -> List[str]: + def get_addresses(self) -> List[Tuple[str, int]]: addresses = [] nodes = self.emane_manager.getnodes() for node in nodes: + control = None + ports = [] for iface in node.get_ifaces(): if isinstance(iface.net, CtrlNet): ip4 = iface.get_ip4() if ip4: - address = str(ip4.ip) - addresses.append(address) - break + control = str(ip4.ip) + if isinstance(iface.net, EmaneNet): + port = self.emane_manager.get_nem_port(iface) + ports.append(port) + if control: + for port in ports: + addresses.append((control, port)) return addresses def check_links(self) -> None: diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index ab7c2039..88c5aa38 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -18,7 +18,7 @@ from core.xml import corexml logger = logging.getLogger(__name__) if TYPE_CHECKING: - from core.emane.emanemanager import EmaneManager, StartData + from core.emane.emanemanager import EmaneManager from core.emane.emanemodel import EmaneModel _MAC_PREFIX = "02:02" @@ -146,7 +146,10 @@ def add_configurations( def build_platform_xml( - emane_manager: "EmaneManager", control_net: CtrlNet, data: "StartData" + emane_manager: "EmaneManager", + control_net: CtrlNet, + node: CoreNodeBase, + iface: CoreInterface, ) -> None: """ Create platform xml for a specific node. @@ -155,65 +158,64 @@ def build_platform_xml( configurations :param control_net: control net node for this emane network - :param data: start data for a node connected to emane and associated interfaces + :param node: node to create a platform xml for + :param iface: node interface to create platform xml for :return: the next nem id that can be used for creating platform xml files """ + # create nem xml entries for all interfaces + emane_net = iface.net + if not isinstance(emane_net, EmaneNet): + raise CoreError(f"emane interface not connected to emane net: {emane_net.name}") + nem_id = emane_manager.next_nem_id(iface) + config = emane_manager.get_iface_config(emane_net, iface) + emane_net.model.build_xml_files(config, iface) + # create top level platform element transport_configs = {"otamanagerdevice", "eventservicedevice"} platform_element = etree.Element("platform") for configuration in emane_manager.emane_config.emulator_config: name = configuration.id - if not isinstance(data.node, CoreNode) and name in transport_configs: + if not isinstance(node, CoreNode) and name in transport_configs: value = control_net.brname else: value = emane_manager.config[name] + if name == "controlportendpoint": + port = emane_manager.get_nem_port(iface) + value = f"0.0.0.0:{port}" add_param(platform_element, name, value) - # create nem xml entries for all interfaces - for iface in data.ifaces: - emane_net = iface.net - if not isinstance(emane_net, EmaneNet): - raise CoreError( - f"emane interface not connected to emane net: {emane_net.name}" - ) - nem_id = emane_manager.next_nem_id() - emane_manager.set_nem(nem_id, iface) - emane_manager.write_nem(iface, nem_id) - config = emane_manager.get_iface_config(emane_net, iface) - emane_net.model.build_xml_files(config, iface) + # build nem xml + nem_definition = nem_file_name(iface) + nem_element = etree.Element( + "nem", id=str(nem_id), name=iface.localname, definition=nem_definition + ) - # build nem xml - nem_definition = nem_file_name(iface) - nem_element = etree.Element( - "nem", id=str(nem_id), name=iface.localname, definition=nem_definition - ) + # check if this is an external transport + 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]) - # check if this is an external transport - 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]) + # define transport element + transport_name = transport_file_name(iface) + transport_element = etree.SubElement( + nem_element, "transport", definition=transport_name + ) + add_param(transport_element, "device", iface.name) - # define transport element - transport_name = transport_file_name(iface) - transport_element = etree.SubElement( - nem_element, "transport", definition=transport_name - ) - add_param(transport_element, "device", iface.name) + # add nem element to platform element + platform_element.append(nem_element) - # add nem element to platform element - platform_element.append(nem_element) - - # generate and assign interface mac address based on nem id - mac = _MAC_PREFIX + ":00:00:" - mac += f"{(nem_id >> 8) & 0xFF:02X}:{nem_id & 0xFF:02X}" - iface.set_mac(mac) + # generate and assign interface mac address based on 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" - file_name = f"{data.node.name}-platform.xml" - create_node_file(data.node, platform_element, doc_name, file_name) + file_name = platform_file_name(iface) + create_node_file(node, platform_element, doc_name, file_name) def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None: @@ -396,3 +398,7 @@ def phy_file_name(iface: CoreInterface) -> str: :return: phy xml file name """ return f"{iface.name}-phy.xml" + + +def platform_file_name(iface: CoreInterface) -> str: + return f"{iface.name}-platform.xml" From 071023b1d9ea13abee4c19ebdccd86f2ac61edd8 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 18 May 2021 21:29:38 -0700 Subject: [PATCH 061/110] added platform config to emane models, to replace global config, moved core specific emane global configs to session options --- daemon/core/emane/emanemodel.py | 39 ++++++++++++++++++++------ daemon/core/emane/models/bypass.py | 6 ++-- daemon/core/emane/models/commeffect.py | 13 +++++++-- daemon/core/emulator/sessionconfig.py | 36 ++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 13 deletions(-) diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index b6c037e0..54f8c72d 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -16,6 +16,8 @@ from core.nodes.interface import CoreInterface from core.xml import emanexml logger = logging.getLogger(__name__) +DEFAULT_DEV: str = "ctrl0" +MANIFEST_PATH: str = "share/emane/manifest" class EmaneModel(WirelessModel): @@ -25,6 +27,16 @@ class EmaneModel(WirelessModel): configurable parameters. Helper functions also live here. """ + # default platform configuration settings + platform_xml: str = "nemmanager.xml" + platform_defaults: Dict[str, str] = { + "eventservicedevice": DEFAULT_DEV, + "eventservicegroup": "224.1.2.8:45703", + "otamanagerdevice": DEFAULT_DEV, + "otamanagergroup": "224.1.2.8:45702", + } + platform_config: List[Configuration] = [] + # default mac configuration settings mac_library: Optional[str] = None mac_xml: Optional[str] = None @@ -57,20 +69,27 @@ class EmaneModel(WirelessModel): @classmethod def load(cls, emane_prefix: Path) -> None: """ - Called after being loaded within the EmaneManager. Provides configured emane_prefix for - parsing xml files. + Called after being loaded within the EmaneManager. Provides configured + emane_prefix for parsing xml files. :param emane_prefix: configured emane prefix path :return: nothing """ - manifest_path = "share/emane/manifest" + cls._load_platform_config(emane_prefix) # load mac configuration - mac_xml_path = emane_prefix / manifest_path / cls.mac_xml + mac_xml_path = emane_prefix / MANIFEST_PATH / cls.mac_xml cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults) # load phy configuration - phy_xml_path = emane_prefix / manifest_path / cls.phy_xml + phy_xml_path = emane_prefix / MANIFEST_PATH / cls.phy_xml cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults) + @classmethod + def _load_platform_config(cls, emane_prefix: Path) -> None: + platform_xml_path = emane_prefix / MANIFEST_PATH / cls.platform_xml + cls.platform_config = emanemanifest.parse( + platform_xml_path, cls.platform_defaults + ) + @classmethod def configurations(cls) -> List[Configuration]: """ @@ -78,7 +97,9 @@ class EmaneModel(WirelessModel): :return: all configurations """ - return cls.mac_config + cls.phy_config + cls.external_config + return ( + cls.platform_config + cls.mac_config + cls.phy_config + cls.external_config + ) @classmethod def config_groups(cls) -> List[ConfigGroup]: @@ -87,11 +108,13 @@ class EmaneModel(WirelessModel): :return: list of configuration groups. """ - mac_len = len(cls.mac_config) + platform_len = len(cls.platform_config) + mac_len = len(cls.mac_config) + platform_len phy_len = len(cls.phy_config) + mac_len config_len = len(cls.configurations()) return [ - ConfigGroup("MAC Parameters", 1, mac_len), + ConfigGroup("Platform Parameters", 1, platform_len), + ConfigGroup("MAC Parameters", platform_len + 1, mac_len), ConfigGroup("PHY Parameters", mac_len + 1, phy_len), ConfigGroup("External Parameters", phy_len + 1, config_len), ] diff --git a/daemon/core/emane/models/bypass.py b/daemon/core/emane/models/bypass.py index aebdde21..67b7707d 100644 --- a/daemon/core/emane/models/bypass.py +++ b/daemon/core/emane/models/bypass.py @@ -1,6 +1,7 @@ """ EMANE Bypass model for CORE """ +from pathlib import Path from typing import List, Set from core.config import Configuration @@ -30,6 +31,5 @@ class EmaneBypassModel(emanemodel.EmaneModel): phy_config: List[Configuration] = [] @classmethod - def load(cls, emane_prefix: str) -> None: - # ignore default logic - pass + def load(cls, emane_prefix: Path) -> None: + cls._load_platform_config(emane_prefix) diff --git a/daemon/core/emane/models/commeffect.py b/daemon/core/emane/models/commeffect.py index 2ce1715f..b73dc837 100644 --- a/daemon/core/emane/models/commeffect.py +++ b/daemon/core/emane/models/commeffect.py @@ -51,16 +51,25 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): @classmethod def load(cls, emane_prefix: Path) -> None: + cls._load_platform_config(emane_prefix) shim_xml_path = emane_prefix / "share/emane/manifest" / cls.shim_xml cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults) @classmethod def configurations(cls) -> List[Configuration]: - return cls.config_shim + return cls.platform_config + cls.config_shim @classmethod def config_groups(cls) -> List[ConfigGroup]: - return [ConfigGroup("CommEffect SHIM Parameters", 1, len(cls.configurations()))] + platform_len = len(cls.platform_config) + return [ + ConfigGroup("Platform Parameters", 1, platform_len), + ConfigGroup( + "CommEffect SHIM Parameters", + platform_len + 1, + len(cls.configurations()), + ), + ] def build_xml_files(self, config: Dict[str, str], iface: CoreInterface) -> None: """ diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py index f76f4638..f40161cb 100644 --- a/daemon/core/emulator/sessionconfig.py +++ b/daemon/core/emulator/sessionconfig.py @@ -59,6 +59,42 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions): Configuration( id="ovs", type=ConfigDataTypes.BOOL, default="0", label="Enable OVS" ), + Configuration( + id="platform_id_start", + type=ConfigDataTypes.INT32, + default="1", + label="EMANE Platform ID Start", + ), + Configuration( + id="nem_id_start", + type=ConfigDataTypes.INT32, + default="1", + label="EMANE NEM ID Start", + ), + Configuration( + id="link_enabled", + type=ConfigDataTypes.BOOL, + default="1", + label="EMANE Links?", + ), + Configuration( + id="loss_threshold", + type=ConfigDataTypes.INT32, + default="30", + label="EMANE Link Loss Threshold (%)", + ), + Configuration( + id="link_interval", + type=ConfigDataTypes.INT32, + default="1", + label="EMANE Link Check Interval (sec)", + ), + Configuration( + id="link_timeout", + type=ConfigDataTypes.INT32, + default="4", + label="EMANE Link Timeout (sec)", + ), ] config_type: RegisterTlvs = RegisterTlvs.UTILITY From 5bc3345d37af6770ea15e8d7e7a5a631ca3e926e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 19 May 2021 20:44:00 -0700 Subject: [PATCH 062/110] adjustments to remove global emane configuration, platform configurations can now be configured per nem, retrieve emane specific core settings from session options --- daemon/core/api/grpc/client.py | 29 --- daemon/core/api/grpc/grpcutils.py | 6 - daemon/core/api/grpc/server.py | 35 ---- daemon/core/api/grpc/wrappers.py | 7 - daemon/core/api/tlv/corehandlers.py | 40 ---- daemon/core/emane/emanemanager.py | 273 ++++++------------------- daemon/core/emane/linkmonitor.py | 7 +- daemon/core/nodes/netclient.py | 2 +- daemon/core/xml/corexml.py | 50 +---- daemon/core/xml/emanexml.py | 6 +- daemon/examples/grpc/emane80211.py | 5 +- daemon/proto/core/api/grpc/core.proto | 29 ++- daemon/proto/core/api/grpc/emane.proto | 17 -- daemon/scripts/core-cleanup | 1 + daemon/tests/test_grpc.py | 35 ---- daemon/tests/test_gui.py | 32 --- docs/grpc.md | 7 +- 17 files changed, 98 insertions(+), 483 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index b07d2c04..5ebed44e 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -28,10 +28,8 @@ from core.api.grpc.configservices_pb2 import ( from core.api.grpc.core_pb2 import ExecuteScriptRequest, GetConfigRequest from core.api.grpc.emane_pb2 import ( EmaneLinkRequest, - GetEmaneConfigRequest, GetEmaneEventChannelRequest, GetEmaneModelConfigRequest, - SetEmaneConfigRequest, SetEmaneModelConfigRequest, ) from core.api.grpc.mobility_pb2 import ( @@ -253,7 +251,6 @@ class CoreGrpcClient: if asymmetric_links: asymmetric_links = [x.to_proto() for x in asymmetric_links] hooks = [x.to_proto() for x in session.hooks.values()] - emane_config = {k: v.value for k, v in session.emane_config.items()} emane_model_configs = [] mobility_configs = [] wlan_configs = [] @@ -313,7 +310,6 @@ class CoreGrpcClient: links=links, location=session.location.to_proto(), hooks=hooks, - emane_config=emane_config, emane_model_configs=emane_model_configs, wlan_configs=wlan_configs, mobility_configs=mobility_configs, @@ -917,31 +913,6 @@ class CoreGrpcClient: response = self.stub.SetWlanConfig(request) return response.result - def get_emane_config(self, session_id: int) -> Dict[str, wrappers.ConfigOption]: - """ - Get session emane configuration. - - :param session_id: session id - :return: response with a list of configuration groups - :raises grpc.RpcError: when session doesn't exist - """ - request = GetEmaneConfigRequest(session_id=session_id) - response = self.stub.GetEmaneConfig(request) - return wrappers.ConfigOption.from_dict(response.config) - - def set_emane_config(self, session_id: int, config: Dict[str, str]) -> bool: - """ - Set session emane configuration. - - :param session_id: session id - :param config: emane configuration - :return: True for success, False otherwise - :raises grpc.RpcError: when session doesn't exist - """ - request = SetEmaneConfigRequest(session_id=session_id, config=config) - response = self.stub.SetEmaneConfig(request) - return response.result - def get_emane_model_config( self, session_id: int, node_id: int, model: str, iface_id: int = -1 ) -> Dict[str, wrappers.ConfigOption]: diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 67e11c5e..169819ba 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -630,10 +630,6 @@ def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfi return configs -def get_emane_config(session: Session) -> Dict[str, common_pb2.ConfigOption]: - return get_config_options(session.emane.config, session.emane.emane_config) - - def get_mobility_node( session: Session, node_id: int, context: ServicerContext ) -> Union[WlanNode, EmaneNet]: @@ -663,7 +659,6 @@ def convert_session(session: Session) -> wrappers.Session: x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale ) hooks = get_hooks(session) - emane_config = get_emane_config(session) emane_model_configs = get_emane_model_configs(session) wlan_configs = get_wlan_configs(session) mobility_configs = get_mobility_configs(session) @@ -685,7 +680,6 @@ def convert_session(session: Session) -> wrappers.Session: default_services=default_services, location=location, hooks=hooks, - emane_config=emane_config, emane_model_configs=emane_model_configs, wlan_configs=wlan_configs, service_configs=service_configs, diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 8b5c5c1f..e2851964 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -33,14 +33,10 @@ from core.api.grpc.emane_pb2 import ( EmaneLinkResponse, EmanePathlossesRequest, EmanePathlossesResponse, - GetEmaneConfigRequest, - GetEmaneConfigResponse, GetEmaneEventChannelRequest, GetEmaneEventChannelResponse, GetEmaneModelConfigRequest, GetEmaneModelConfigResponse, - SetEmaneConfigRequest, - SetEmaneConfigResponse, SetEmaneModelConfigRequest, SetEmaneModelConfigResponse, ) @@ -266,7 +262,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) # emane configs - session.emane.config.update(request.emane_config) for config in request.emane_model_configs: _id = utils.iface_config_id(config.node_id, config.iface_id) session.emane.set_config(_id, config.model, config.config) @@ -1045,36 +1040,6 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): node.updatemodel(config) return SetWlanConfigResponse(result=True) - def GetEmaneConfig( - self, request: GetEmaneConfigRequest, context: ServicerContext - ) -> GetEmaneConfigResponse: - """ - Retrieve EMANE configuration of a session - - :param request: get-EMANE-configuration request - :param context: context object - :return: get-EMANE-configuration response - """ - logger.debug("get emane config: %s", request) - session = self.get_session(request.session_id, context) - config = grpcutils.get_emane_config(session) - return GetEmaneConfigResponse(config=config) - - def SetEmaneConfig( - self, request: SetEmaneConfigRequest, context: ServicerContext - ) -> SetEmaneConfigResponse: - """ - Set EMANE configuration of a session - - :param request: set-EMANE-configuration request - :param context: context object - :return: set-EMANE-configuration response - """ - logger.debug("set emane config: %s", request) - session = self.get_session(request.session_id, context) - session.emane.config.update(request.config) - return SetEmaneConfigResponse(result=True) - def GetEmaneModelConfig( self, request: GetEmaneModelConfigRequest, context: ServicerContext ) -> GetEmaneModelConfigResponse: diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index a0277edd..802af3c3 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -783,7 +783,6 @@ class Session: x=0.0, y=0.0, z=0.0, lat=47.57917, lon=-122.13232, alt=2.0, scale=150.0 ) hooks: Dict[str, Hook] = field(default_factory=dict) - emane_config: Dict[str, ConfigOption] = field(default_factory=dict) metadata: Dict[str, str] = field(default_factory=dict) file: Path = None options: Dict[str, ConfigOption] = field(default_factory=dict) @@ -836,7 +835,6 @@ class Session: default_services=default_services, location=SessionLocation.from_proto(proto.location), hooks=hooks, - emane_config=ConfigOption.from_dict(proto.emane_config), metadata=dict(proto.metadata), file=file_path, options=options, @@ -889,11 +887,6 @@ class Session: self.links.append(link) return link - def set_emane(self, config: Dict[str, str]) -> None: - for key, value in config.items(): - option = ConfigOption(name=key, value=value) - self.emane_config[key] = option - def set_options(self, config: Dict[str, str]) -> None: for key, value in config.items(): option = ConfigOption(name=key, value=value) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 527924c1..a1c1d34a 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -1046,8 +1046,6 @@ class CoreHandler(socketserver.BaseRequestHandler): self.handle_config_mobility(message_type, config_data) elif config_data.object in self.session.mobility.models: replies = self.handle_config_mobility_models(message_type, config_data) - elif config_data.object == self.session.emane.name: - replies = self.handle_config_emane(message_type, config_data) elif config_data.object in EmaneModelManager.models: replies = self.handle_config_emane_models(message_type, config_data) else: @@ -1379,36 +1377,6 @@ class CoreHandler(socketserver.BaseRequestHandler): return replies - def handle_config_emane(self, message_type, config_data): - replies = [] - node_id = config_data.node - object_name = config_data.object - iface_id = config_data.iface_id - values_str = config_data.data_values - - node_id = utils.iface_config_id(node_id, iface_id) - logger.debug( - "received configure message for %s nodenum: %s", object_name, node_id - ) - if message_type == ConfigFlags.REQUEST: - logger.info("replying to configure request for %s model", object_name) - typeflags = ConfigFlags.NONE.value - config = self.session.emane.config - config_response = ConfigShim.config_data( - 0, node_id, typeflags, self.session.emane.emane_config, config - ) - replies.append(config_response) - elif message_type != ConfigFlags.RESET: - if not object_name: - logger.info("no configuration object for node %s", node_id) - return [] - - if values_str: - config = ConfigShim.str_to_dict(values_str) - self.session.emane.config = config - - return replies - def handle_config_emane_models(self, message_type, config_data): replies = [] node_id = config_data.node @@ -1851,14 +1819,6 @@ class CoreHandler(socketserver.BaseRequestHandler): ) self.session.broadcast_config(config_data) - # send global emane config - config = self.session.emane.config - logger.debug("global emane config: values(%s)", config) - config_data = ConfigShim.config_data( - 0, None, ConfigFlags.UPDATE.value, self.session.emane.emane_config, config - ) - self.session.broadcast_config(config_data) - # send emane model configs for node_id, model_configs in self.session.emane.node_configs.items(): for model_name, config in model_configs.items(): diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 337cd0de..b952d942 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -1,30 +1,21 @@ """ -emane.py: definition of an Emane class for implementing configuration control of an EMANE emulation. +Implements configuration and control of an EMANE emulation. """ import logging import os import threading -from collections import OrderedDict from dataclasses import dataclass, field from enum import Enum -from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type from core import utils -from core.config import ConfigGroup, Configuration -from core.emane import emanemanifest from core.emane.emanemodel import EmaneModel from core.emane.linkmonitor import EmaneLinkMonitor from core.emane.modelmanager import EmaneModelManager from core.emane.nodes import EmaneNet from core.emulator.data import LinkData -from core.emulator.enumerations import ( - ConfigDataTypes, - LinkTypes, - MessageFlags, - RegisterTlvs, -) +from core.emulator.enumerations import LinkTypes, MessageFlags, RegisterTlvs from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase from core.nodes.interface import CoreInterface, TunTap @@ -102,8 +93,6 @@ class EmaneManager: self.eventmonthread: Optional[threading.Thread] = None # model for global EMANE configuration options - self.emane_config: EmaneGlobalModel = EmaneGlobalModel(session) - self.config: Dict[str, str] = self.emane_config.default_values() self.node_configs: Dict[int, Dict[str, Dict[str, str]]] = {} self.node_models: Dict[int, str] = {} @@ -115,7 +104,7 @@ class EmaneManager: self.event_device: Optional[str] = None def next_nem_id(self, iface: CoreInterface) -> int: - nem_id = int(self.config["nem_id_start"]) + nem_id = self.session.options.get_config_int("nem_id_start") while nem_id in self.nems_to_ifaces: nem_id += 1 self.nems_to_ifaces[nem_id] = iface @@ -206,7 +195,6 @@ class EmaneManager: def config_reset(self, node_id: int = None) -> None: if node_id is None: - self.config = self.emane_config.default_values() self.node_configs.clear() self.node_models.clear() else: @@ -224,25 +212,20 @@ class EmaneManager: self.service = None self.event_device = None - def initeventservice(self, filename: str = None, shutdown: bool = False) -> None: + def initeventservice(self) -> None: """ Re-initialize the EMANE Event service. The multicast group and/or port may be configured. """ - self.deleteeventservice() - if shutdown: - return - # Get the control network to be used for events - group, port = self.config["eventservicegroup"].split(":") - self.event_device = self.config["eventservicedevice"] + group, port = "224.1.2.8:45703".split(":") + self.event_device = DEFAULT_DEV eventnetidx = self.session.get_control_net_index(self.event_device) if eventnetidx < 0: logger.error( "invalid emane event service device provided: %s", self.event_device ) return - # make sure the event control network is in place eventnet = self.session.add_remove_control_net( net_index=eventnetidx, remove=False, conf_required=False @@ -251,14 +234,13 @@ class EmaneManager: # direct EMANE events towards control net bridge self.event_device = eventnet.brname self.eventchannel = (group, int(port), self.event_device) - # disabled otachannel for event service # only needed for e.g. antennaprofile events xmit by models - logger.info("using %s for event service traffic", self.event_device) + logger.info("using %s for emane event service", self.event_device) try: self.service = EventService(eventchannel=self.eventchannel, otachannel=None) except EventServiceException: - logger.exception("error instantiating emane EventService") + logger.exception("error starting emane event service") def add_node(self, emane_net: EmaneNet) -> None: """ @@ -309,9 +291,8 @@ class EmaneManager: if EventService is None: raise CoreError("EMANE python bindings are not installed") - # control network bridge required for EMANE 0.9.2 - # - needs to exist when eventservice binds to it (initeventservice) - otadev = self.config["otamanagerdevice"] + # control network bridge required for emane + otadev = DEFAULT_DEV netidx = self.session.get_control_net_index(otadev) logger.debug("emane ota manager device: index(%s) otadev(%s)", netidx, otadev) if netidx < 0: @@ -320,25 +301,9 @@ class EmaneManager: otadev, ) return EmaneState.NOT_READY - self.session.add_remove_control_net( net_index=netidx, remove=False, conf_required=False ) - eventdev = self.config["eventservicedevice"] - logger.debug("emane event service device: eventdev(%s)", eventdev) - if eventdev != otadev: - netidx = self.session.get_control_net_index(eventdev) - logger.debug("emane event service device index: %s", netidx) - if netidx < 0: - logger.error( - "emane cannot start due to invalid event service device: %s", - eventdev, - ) - return EmaneState.NOT_READY - - self.session.add_remove_control_net( - net_index=netidx, remove=False, conf_required=False - ) self.check_node_models() return EmaneState.SUCCESS @@ -354,8 +319,9 @@ class EmaneManager: status = self.setup() if status != EmaneState.SUCCESS: return status - self.starteventmonitor() - self.buildeventservicexml() + self.initeventservice() + if self.service and self.doeventmonitor(): + self.starteventmonitor() with self._emane_node_lock: logger.info("emane building xmls...") start_data = self.get_start_data() @@ -392,40 +358,54 @@ class EmaneManager: control_net = self.session.add_remove_control_net( 0, remove=False, conf_required=False ) - if isinstance(node, CoreNode): - # setup ota device - otagroup, _otaport = self.config["otamanagergroup"].split(":") - otadev = self.config["otamanagerdevice"] - otanetidx = self.session.get_control_net_index(otadev) - eventgroup, _eventport = self.config["eventservicegroup"].split(":") - eventdev = self.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 - ) - if otanetidx > 0: - logger.info("adding ota device ctrl%d", otanetidx) - self.session.add_remove_control_iface( - node, otanetidx, remove=False, conf_required=False - ) - if eventservicenetidx >= 0: - logger.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 - logger.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: - node.node_net_client.create_route(eventgroup, eventdev) # builds xmls and start emane daemons for iface in data.ifaces: + if isinstance(node, CoreNode): + self.setup_ota(node, iface) emanexml.build_platform_xml(self, control_net, node, iface) self.start_daemon(node, iface) self.install_iface(iface) + def setup_ota(self, node: CoreNode, iface: CoreInterface) -> None: + if not isinstance(iface.net, EmaneNet): + raise CoreError( + f"emane interface not connected to emane net: {iface.net.name}" + ) + config = self.get_iface_config(iface.net, iface) + # setup ota device + otagroup, _otaport = config["otamanagergroup"].split(":") + otadev = config["otamanagerdevice"] + otanetidx = self.session.get_control_net_index(otadev) + eventgroup, _eventport = config["eventservicegroup"].split(":") + eventdev = 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 + ) + if otanetidx > 0: + logger.info("adding ota device ctrl%d", otanetidx) + self.session.add_remove_control_iface( + node, otanetidx, remove=False, conf_required=False + ) + if eventservicenetidx >= 0: + logger.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 + logger.info( + "node(%s) interface(%s) ota group(%s) dev(%s)", + node.name, + iface.name, + 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: + node.node_net_client.create_route(eventgroup, eventdev) + def get_iface(self, nem_id: int) -> Optional[CoreInterface]: return self.nems_to_ifaces.get(nem_id) @@ -445,7 +425,7 @@ class EmaneManager: logger.exception("error writing to emane nem file") def links_enabled(self) -> bool: - return self.config["link_enabled"] == "1" + return self.session.options.get_config_int("link_enabled") == 1 def poststartup(self) -> None: """ @@ -551,35 +531,6 @@ class EmaneManager: color=color, ) - def buildeventservicexml(self) -> None: - """ - Build the libemaneeventservice.xml file if event service options - were changed in the global config. - """ - need_xml = False - default_values = self.emane_config.default_values() - for name in ["eventservicegroup", "eventservicedevice"]: - a = default_values[name] - b = self.config[name] - if a != b: - need_xml = True - if not need_xml: - # reset to using default config - self.initeventservice() - return - try: - group, port = self.config["eventservicegroup"].split(":") - except ValueError: - logger.exception("invalid eventservicegroup in EMANE config") - return - dev = self.config["eventservicedevice"] - emanexml.create_event_service_xml(group, port, dev, self.session.directory) - self.session.distributed.execute( - lambda x: emanexml.create_event_service_xml( - group, port, dev, self.session.directory, x - ) - ) - def start_daemon(self, node: CoreNodeBase, iface: CoreInterface) -> None: """ Start one EMANE daemon per node having a radio. @@ -648,17 +599,7 @@ class EmaneManager: """ Start monitoring EMANE location events if configured to do so. """ - logger.info("emane start event monitor") - if not self.doeventmonitor(): - return - if self.service is None: - logger.error( - "Warning: EMANE events will not be generated " - "because the emaneeventservice\n binding was " - "unable to load " - "(install the python-emaneeventservice bindings)" - ) - return + logger.info("starting emane event monitor") self.doeventloop = True self.eventmonthread = threading.Thread( target=self.eventmonitorloop, daemon=True @@ -673,8 +614,7 @@ class EmaneManager: if self.service is not None: self.service.breakloop() # reset the service, otherwise nextEvent won"t work - self.initeventservice(shutdown=True) - + self.deleteeventservice() if self.eventmonthread is not None: self.eventmonthread.join() self.eventmonthread = None @@ -685,26 +625,17 @@ class EmaneManager: """ if self.service is None: return - logger.info( - "subscribing to EMANE location events. (%s)", - threading.currentThread().getName(), - ) - while self.doeventloop is True: + logger.info("subscribing to EMANE location events") + while self.doeventloop: _uuid, _seq, events = self.service.nextEvent() - # this occurs with 0.9.1 event service if not self.doeventloop: break - for event in events: nem, eid, data = event if eid == LocationEvent.IDENTIFIER: self.handlelocationevent(nem, eid, data) - - logger.info( - "unsubscribing from EMANE location events. (%s)", - threading.currentThread().getName(), - ) + logger.info("unsubscribing from EMANE location events") def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None: """ @@ -818,85 +749,3 @@ class EmaneManager: event.append(nem2, forward=rx2) self.service.publish(nem1, event) self.service.publish(nem2, event) - - -class EmaneGlobalModel: - """ - Global EMANE configuration options. - """ - - name: str = "emane" - bitmap: Optional[str] = None - - def __init__(self, session: "Session") -> None: - self.session: "Session" = session - self.core_config: List[Configuration] = [ - Configuration( - id="platform_id_start", - type=ConfigDataTypes.INT32, - default="1", - label="Starting Platform ID", - ), - Configuration( - id="nem_id_start", - type=ConfigDataTypes.INT32, - default="1", - label="Starting NEM ID", - ), - Configuration( - id="link_enabled", - type=ConfigDataTypes.BOOL, - default="1", - label="Enable Links?", - ), - Configuration( - id="loss_threshold", - type=ConfigDataTypes.INT32, - default="30", - label="Link Loss Threshold (%)", - ), - Configuration( - id="link_interval", - type=ConfigDataTypes.INT32, - default="1", - label="Link Check Interval (sec)", - ), - Configuration( - id="link_timeout", - type=ConfigDataTypes.INT32, - default="4", - label="Link Timeout (sec)", - ), - ] - self.emulator_config = None - self.parse_config() - - def parse_config(self) -> None: - emane_prefix = self.session.options.get_config( - "emane_prefix", default=DEFAULT_EMANE_PREFIX - ) - emane_prefix = Path(emane_prefix) - emulator_xml = emane_prefix / "share/emane/manifest/nemmanager.xml" - emulator_defaults = { - "eventservicedevice": DEFAULT_DEV, - "eventservicegroup": "224.1.2.8:45703", - "otamanagerdevice": DEFAULT_DEV, - "otamanagergroup": "224.1.2.8:45702", - } - self.emulator_config = emanemanifest.parse(emulator_xml, emulator_defaults) - - def configurations(self) -> List[Configuration]: - return self.emulator_config + self.core_config - - def config_groups(self) -> List[ConfigGroup]: - emulator_len = len(self.emulator_config) - config_len = len(self.configurations()) - return [ - ConfigGroup("Platform Attributes", 1, emulator_len), - ConfigGroup("CORE Configuration", emulator_len + 1, config_len), - ] - - def default_values(self) -> Dict[str, str]: - return OrderedDict( - [(config.id, config.default) for config in self.configurations()] - ) diff --git a/daemon/core/emane/linkmonitor.py b/daemon/core/emane/linkmonitor.py index fd27c51d..9b18bae2 100644 --- a/daemon/core/emane/linkmonitor.py +++ b/daemon/core/emane/linkmonitor.py @@ -189,9 +189,10 @@ class EmaneLinkMonitor: self.running: bool = False def start(self) -> None: - self.loss_threshold = int(self.emane_manager.config["loss_threshold"]) - self.link_interval = int(self.emane_manager.config["link_interval"]) - self.link_timeout = int(self.emane_manager.config["link_timeout"]) + options = self.emane_manager.session.options + self.loss_threshold = options.get_config_int("loss_threshold") + self.link_interval = options.get_config_int("link_interval") + self.link_timeout = options.get_config_int("link_timeout") self.initialize() if not self.clients: logger.info("no valid emane models to monitor links") diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 729550b6..c066910b 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -38,7 +38,7 @@ class LinuxNetClient: :param device: device to add route to :return: nothing """ - self.run(f"{IP} route add {route} dev {device}") + self.run(f"{IP} route replace {route} dev {device}") def device_up(self, device: str) -> None: """ diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 629750bf..647300fc 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -80,20 +80,6 @@ def create_iface_data(iface_element: etree.Element) -> InterfaceData: ) -def create_emane_config(session: "Session") -> etree.Element: - emane_configuration = etree.Element("emane_global_configuration") - config = session.emane.config - emulator_element = etree.SubElement(emane_configuration, "emulator") - for emulator_config in session.emane.emane_config.emulator_config: - value = config[emulator_config.id] - add_configuration(emulator_element, emulator_config.id, value) - core_element = etree.SubElement(emane_configuration, "core") - for core_config in session.emane.emane_config.core_config: - value = config[core_config.id] - add_configuration(core_element, core_config.id, value) - return emane_configuration - - def create_emane_model_config( node_id: int, model: "EmaneModelType", @@ -104,22 +90,22 @@ def create_emane_model_config( add_attribute(emane_element, "node", node_id) add_attribute(emane_element, "iface", iface_id) add_attribute(emane_element, "model", model.name) - + platform_element = etree.SubElement(emane_element, "platform") + for platform_config in model.platform_config: + value = config[platform_config.id] + add_configuration(platform_element, platform_config.id, value) mac_element = etree.SubElement(emane_element, "mac") for mac_config in model.mac_config: value = config[mac_config.id] add_configuration(mac_element, mac_config.id, value) - phy_element = etree.SubElement(emane_element, "phy") for phy_config in model.phy_config: value = config[phy_config.id] add_configuration(phy_element, phy_config.id, value) - external_element = etree.SubElement(emane_element, "external") for external_config in model.external_config: value = config[external_config.id] add_configuration(external_element, external_config.id, value) - return emane_element @@ -376,8 +362,6 @@ class CoreXmlWriter: self.scenario.append(metadata_elements) 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, model_configs in self.session.emane.node_configs.items(): node_id, iface_id = utils.parse_iface_config_id(node_id) @@ -591,7 +575,6 @@ class CoreXmlReader: self.read_session_origin() self.read_service_configs() self.read_mobility_configs() - self.read_emane_global_config() self.read_nodes() self.read_links() self.read_emane_configs() @@ -729,28 +712,10 @@ class CoreXmlReader: files.add(name) service.configs = tuple(files) - def read_emane_global_config(self) -> None: - emane_global_configuration = self.scenario.find("emane_global_configuration") - if emane_global_configuration is None: - return - emulator_configuration = emane_global_configuration.find("emulator") - configs = {} - for config in emulator_configuration.iterchildren(): - name = config.get("name") - value = config.get("value") - configs[name] = value - core_configuration = emane_global_configuration.find("core") - for config in core_configuration.iterchildren(): - name = config.get("name") - value = config.get("value") - configs[name] = value - self.session.emane.config = configs - def read_emane_configs(self) -> None: emane_configurations = self.scenario.find("emane_configurations") if emane_configurations is None: return - for emane_configuration in emane_configurations.iterchildren(): node_id = get_int(emane_configuration, "node") iface_id = get_int(emane_configuration, "iface") @@ -768,18 +733,21 @@ class CoreXmlReader: ) # read and set emane model configuration + platform_configuration = emane_configuration.find("platform") + for config in platform_configuration.iterchildren(): + name = config.get("name") + value = config.get("value") + configs[name] = value mac_configuration = emane_configuration.find("mac") for config in mac_configuration.iterchildren(): name = config.get("name") value = config.get("value") configs[name] = value - phy_configuration = emane_configuration.find("phy") for config in phy_configuration.iterchildren(): name = config.get("name") value = config.get("value") configs[name] = value - external_configuration = emane_configuration.find("external") for config in external_configuration.iterchildren(): name = config.get("name") diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 88c5aa38..97646699 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -162,7 +162,7 @@ def build_platform_xml( :param iface: node interface to create platform xml for :return: the next nem id that can be used for creating platform xml files """ - # create nem xml entries for all interfaces + # create model based xml files emane_net = iface.net if not isinstance(emane_net, EmaneNet): raise CoreError(f"emane interface not connected to emane net: {emane_net.name}") @@ -173,12 +173,12 @@ def build_platform_xml( # create top level platform element transport_configs = {"otamanagerdevice", "eventservicedevice"} platform_element = etree.Element("platform") - for configuration in emane_manager.emane_config.emulator_config: + for configuration in emane_net.model.platform_config: name = configuration.id if not isinstance(node, CoreNode) and name in transport_configs: value = control_net.brname else: - value = emane_manager.config[name] + value = config[configuration.id] if name == "controlportendpoint": port = emane_manager.get_nem_port(iface) value = f"0.0.0.0:{port}" diff --git a/daemon/examples/grpc/emane80211.py b/daemon/examples/grpc/emane80211.py index fbafbe07..00c5458f 100644 --- a/daemon/examples/grpc/emane80211.py +++ b/daemon/examples/grpc/emane80211.py @@ -30,8 +30,9 @@ iface1 = iface_helper.create_iface(node2.id, 0) session.add_link(node1=node2, node2=emane, iface1=iface1) # setup emane configurations using a dict mapping currently support values as strings -session.set_emane({"eventservicettl": "2"}) -emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"}) +emane.set_emane_model( + EmaneIeee80211abgModel.name, {"eventservicettl": "2", "unicastrate": "3"} +) # start session core.start_session(session) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index ce66b038..3c703866 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -95,10 +95,6 @@ service CoreApi { } // emane rpc - rpc GetEmaneConfig (emane.GetEmaneConfigRequest) returns (emane.GetEmaneConfigResponse) { - } - rpc SetEmaneConfig (emane.SetEmaneConfigRequest) returns (emane.SetEmaneConfigResponse) { - } rpc GetEmaneModelConfig (emane.GetEmaneModelConfigRequest) returns (emane.GetEmaneModelConfigResponse) { } rpc SetEmaneModelConfig (emane.SetEmaneModelConfigRequest) returns (emane.SetEmaneModelConfigResponse) { @@ -144,19 +140,18 @@ message StartSessionRequest { repeated Link links = 3; repeated Hook hooks = 4; SessionLocation location = 5; - map emane_config = 6; - repeated wlan.WlanConfig wlan_configs = 7; - repeated emane.EmaneModelConfig emane_model_configs = 8; - repeated mobility.MobilityConfig mobility_configs = 9; - repeated services.ServiceConfig service_configs = 10; - repeated services.ServiceFileConfig service_file_configs = 11; - repeated Link asymmetric_links = 12; - repeated configservices.ConfigServiceConfig config_service_configs = 13; - map options = 14; - string user = 15; - bool definition = 16; - map metadata = 17; - repeated Server servers = 18; + repeated wlan.WlanConfig wlan_configs = 6; + repeated emane.EmaneModelConfig emane_model_configs = 7; + repeated mobility.MobilityConfig mobility_configs = 8; + repeated services.ServiceConfig service_configs = 9; + repeated services.ServiceFileConfig service_file_configs = 10; + repeated Link asymmetric_links = 11; + repeated configservices.ConfigServiceConfig config_service_configs = 12; + map options = 13; + string user = 14; + bool definition = 15; + map metadata = 16; + repeated Server servers = 17; } message StartSessionResponse { diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index de739891..1e56a0aa 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -4,23 +4,6 @@ package emane; import "core/api/grpc/common.proto"; -message GetEmaneConfigRequest { - int32 session_id = 1; -} - -message GetEmaneConfigResponse { - map config = 1; -} - -message SetEmaneConfigRequest { - int32 session_id = 1; - map config = 2; -} - -message SetEmaneConfigResponse { - bool result = 1; -} - message GetEmaneModelConfigRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/scripts/core-cleanup b/daemon/scripts/core-cleanup index c97d6843..ced76634 100755 --- a/daemon/scripts/core-cleanup +++ b/daemon/scripts/core-cleanup @@ -61,6 +61,7 @@ eval "$ifcommand" | awk ' /tmp\./ {print "removing interface " $1; system("ip link del " $1);} /gt\./ {print "removing interface " $1; system("ip link del " $1);} /b\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);} + /ctrl[0-9]+\./ {print "removing bridge " $1; system("ip link set " $1 " down; ip link del " $1);} ' nft list ruleset | awk ' diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index e836251e..ebfe29eb 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -83,11 +83,6 @@ class TestGrpc: scale=location_scale, ) - # setup global emane config - emane_config_key = "platform_id_start" - emane_config_value = "2" - session.set_emane({emane_config_key: emane_config_value}) - # setup wlan config wlan_config_key = "range" wlan_config_value = "333" @@ -151,7 +146,6 @@ class TestGrpc: location_alt, ) assert real_session.location.refscale == location_scale - assert real_session.emane.config[emane_config_key] == emane_config_value set_wlan_config = real_session.mobility.get_model_config( wlan_node.id, BasicRangeModel.name ) @@ -518,35 +512,6 @@ class TestGrpc: assert config[range_key] == range_value assert wlan.model.range == int(range_value) - def test_get_emane_config(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - - # then - with client.context_connect(): - config = client.get_emane_config(session.id) - - # then - assert len(config) > 0 - - def test_set_emane_config(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - config_key = "platform_id_start" - config_value = "2" - - # then - with client.context_connect(): - result = client.set_emane_config(session.id, {config_key: config_value}) - - # then - assert result is True - config = session.emane.config - assert len(config) > 1 - assert config[config_key] == config_value - def test_set_emane_model_config(self, grpc_server: CoreGrpcServer): # given client = CoreGrpcClient() diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 7b8a987d..5f4ab487 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -941,35 +941,3 @@ class TestGui: config = coretlv.session.emane.get_config(wlan.id, EmaneIeee80211abgModel.name) assert config[config_key] == config_value - - def test_config_emane_request(self, coretlv: CoreHandler): - message = coreapi.CoreConfMessage.create( - 0, - [ - (ConfigTlvs.OBJECT, "emane"), - (ConfigTlvs.TYPE, ConfigFlags.REQUEST.value), - ], - ) - coretlv.handle_broadcast_config = mock.MagicMock() - - coretlv.handle_message(message) - - coretlv.handle_broadcast_config.assert_called_once() - - def test_config_emane_update(self, coretlv: CoreHandler): - config_key = "eventservicedevice" - config_value = "eth4" - values = {config_key: config_value} - message = coreapi.CoreConfMessage.create( - 0, - [ - (ConfigTlvs.OBJECT, "emane"), - (ConfigTlvs.TYPE, ConfigFlags.UPDATE.value), - (ConfigTlvs.VALUES, dict_to_str(values)), - ], - ) - - coretlv.handle_message(message) - - config = coretlv.session.emane.config - assert config[config_key] == config_value diff --git a/docs/grpc.md b/docs/grpc.md index 5cc0e7ae..aef79308 100644 --- a/docs/grpc.md +++ b/docs/grpc.md @@ -328,10 +328,11 @@ session.add_link(node1=node1, node2=emane, iface1=iface1) iface1 = iface_helper.create_iface(node2.id, 0) session.add_link(node1=node2, node2=emane, iface1=iface1) -# setting global emane configuration -session.set_emane({"eventservicettl": "2"}) # setting emane specific emane model configuration -emane.set_emane_model(EmaneIeee80211abgModel.name, {"unicastrate": "3"}) +emane.set_emane_model(EmaneIeee80211abgModel.name, { + "eventservicettl": "2", + "unicastrate": "3", +}) # start session core.start_session(session) From 6b5148566cf8ee1d81ffd4ae7fd653127ebbb64e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 19 May 2021 20:49:18 -0700 Subject: [PATCH 063/110] daemon: adjustment for emane monitor shutdown to avoid locking --- daemon/core/emane/emanemanager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index b952d942..eb8c743b 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -613,11 +613,11 @@ class EmaneManager: self.doeventloop = False if self.service is not None: self.service.breakloop() + if self.eventmonthread is not None: + self.eventmonthread.join() + self.eventmonthread = None # reset the service, otherwise nextEvent won"t work self.deleteeventservice() - if self.eventmonthread is not None: - self.eventmonthread.join() - self.eventmonthread = None def eventmonitorloop(self) -> None: """ From 4ff650af675979b6f1db3d11ecea91662749a332 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 20 May 2021 12:24:54 -0700 Subject: [PATCH 064/110] pygui: removed global emane configuration dialog --- daemon/core/gui/dialogs/emaneconfig.py | 54 +------------------------- 1 file changed, 2 insertions(+), 52 deletions(-) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index 9d9090b6..24ddf36c 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -19,40 +19,6 @@ if TYPE_CHECKING: from core.gui.app import Application -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) - session = self.app.core.session - self.config_frame = ConfigFrame( - self.top, self.app, session.emane_config, self.enabled - ) - self.config_frame.draw_config() - self.config_frame.grid(sticky=tk.NSEW, pady=PADY) - self.draw_buttons() - - def draw_buttons(self) -> None: - frame = ttk.Frame(self.top) - frame.grid(sticky=tk.EW) - for i in range(2): - frame.columnconfigure(i, weight=1) - 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=tk.EW, padx=PADX) - button = ttk.Button(frame, text="Cancel", command=self.destroy) - button.grid(row=0, column=1, sticky=tk.EW) - - def click_apply(self) -> None: - self.config_frame.parse_config() - self.destroy() - - class EmaneModelDialog(Dialog): def __init__( self, @@ -180,8 +146,7 @@ class EmaneConfigDialog(Dialog): def draw_emane_buttons(self) -> None: frame = ttk.Frame(self.top) frame.grid(sticky=tk.EW, pady=PADY) - for i in range(2): - frame.columnconfigure(i, weight=1) + frame.columnconfigure(0, weight=1) image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE) self.emane_model_button = ttk.Button( @@ -192,18 +157,7 @@ class EmaneConfigDialog(Dialog): command=self.click_model_config, ) self.emane_model_button.image = image - self.emane_model_button.grid(row=0, column=0, padx=PADX, sticky=tk.EW) - - image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE) - button = ttk.Button( - frame, - text="EMANE options", - image=image, - compound=tk.RIGHT, - command=self.click_emane_config, - ) - button.image = image - button.grid(row=0, column=1, sticky=tk.EW) + self.emane_model_button.grid(padx=PADX, sticky=tk.EW) def draw_apply_and_cancel(self) -> None: frame = ttk.Frame(self.top) @@ -216,10 +170,6 @@ class EmaneConfigDialog(Dialog): button = ttk.Button(frame, text="Cancel", command=self.destroy) button.grid(row=0, column=1, sticky=tk.EW) - def click_emane_config(self) -> None: - dialog = GlobalEmaneDialog(self, self.app) - dialog.show() - def click_model_config(self) -> None: """ draw emane model configuration From aea727ba42a0f4a7741eef3e7eaafea9eace1fb5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 20 May 2021 12:48:42 -0700 Subject: [PATCH 065/110] gui: adjustments to remove emane global options from legacy gui, since it is no longer applicable --- gui/wlan.tcl | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/gui/wlan.tcl b/gui/wlan.tcl index bea770a7..55f5319c 100644 --- a/gui/wlan.tcl +++ b/gui/wlan.tcl @@ -541,13 +541,7 @@ proc wlanConfigDialogHelper { wi target apply } { ttk::button $opts.model -text "model options" \ -image $plugin_img_edit -compound right -command "" -state disabled \ -command "configCap $target \[set g_selected_model\]" - # global EMANE model uses no node in config request message, although any - # config will be stored with the EMANE node having the lowest ID - ttk::button $opts.gen -text "EMANE options" \ - -image $plugin_img_edit -compound right \ - -command "configCap -1 emane" - #-command "popupPluginsCapConfigHelper $wi config $target" - pack $opts.model $opts.gen -side left -padx 4 -pady 4 + pack $opts.model -side left -padx 4 -pady 4 pack $opts -side top -anchor c -padx 4 -pady 4 # show correct tab basic/emane based on selection From ef0fa8c1a7d277ea091c837e58ca02f5654ff5c0 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 21 May 2021 22:57:27 -0700 Subject: [PATCH 066/110] daemon: updates to emane manager to setup ota/event control networks on nodes and host based on individual nem configurations --- daemon/core/emane/emanemanager.py | 93 ++++++++++++------------------- daemon/core/xml/emanexml.py | 14 +---- 2 files changed, 37 insertions(+), 70 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index eb8c743b..f61bf452 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -274,6 +274,9 @@ class EmaneManager: :return: SUCCESS, NOT_NEEDED, NOT_READY in order to delay session instantiation """ + # check if bindings were installed + if EventService is None: + raise CoreError("EMANE python bindings are not installed") logger.debug("emane setup") with self.session.nodes_lock: for node_id in self.session.nodes: @@ -286,24 +289,6 @@ class EmaneManager: if not self._emane_nets: logger.debug("no emane nodes in session") return EmaneState.NOT_NEEDED - - # check if bindings were installed - if EventService is None: - raise CoreError("EMANE python bindings are not installed") - - # control network bridge required for emane - otadev = DEFAULT_DEV - netidx = self.session.get_control_net_index(otadev) - logger.debug("emane ota manager device: index(%s) otadev(%s)", netidx, otadev) - if netidx < 0: - logger.error( - "EMANE cannot start, check core config. invalid OTA device provided: %s", - otadev, - ) - return EmaneState.NOT_READY - self.session.add_remove_control_net( - net_index=netidx, remove=False, conf_required=False - ) self.check_node_models() return EmaneState.SUCCESS @@ -322,14 +307,23 @@ class EmaneManager: self.initeventservice() if self.service and self.doeventmonitor(): self.starteventmonitor() + self.startup_nodes() + if self.links_enabled(): + self.link_monitor.start() + return EmaneState.SUCCESS + + def startup_nodes(self) -> None: with self._emane_node_lock: logger.info("emane building xmls...") start_data = self.get_start_data() for data in start_data: - self.start_node(data) - if self.links_enabled(): - self.link_monitor.start() - return EmaneState.SUCCESS + node = data.node + for iface in data.ifaces: + if isinstance(node, CoreNode): + self.setup_ota(node, iface) + emanexml.build_platform_xml(self, node, iface) + self.start_daemon(node, iface) + self.install_iface(iface) def get_start_data(self) -> List[StartData]: node_map = {} @@ -353,19 +347,6 @@ class EmaneManager: start_node.ifaces = sorted(start_node.ifaces, key=lambda x: x.node_id) return start_nodes - def start_node(self, data: StartData) -> None: - node = data.node - control_net = self.session.add_remove_control_net( - 0, remove=False, conf_required=False - ) - # builds xmls and start emane daemons - for iface in data.ifaces: - if isinstance(node, CoreNode): - self.setup_ota(node, iface) - emanexml.build_platform_xml(self, control_net, node, iface) - self.start_daemon(node, iface) - self.install_iface(iface) - def setup_ota(self, node: CoreNode, iface: CoreInterface) -> None: if not isinstance(iface.net, EmaneNet): raise CoreError( @@ -375,35 +356,27 @@ class EmaneManager: # setup ota device otagroup, _otaport = config["otamanagergroup"].split(":") otadev = config["otamanagerdevice"] - otanetidx = self.session.get_control_net_index(otadev) + ota_index = self.session.get_control_net_index(otadev) + self.session.add_remove_control_net(ota_index, conf_required=False) + self.session.add_remove_control_iface(node, ota_index, conf_required=False) + # setup event device eventgroup, _eventport = config["eventservicegroup"].split(":") eventdev = 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 - ) - if otanetidx > 0: - logger.info("adding ota device ctrl%d", otanetidx) - self.session.add_remove_control_iface( - node, otanetidx, remove=False, conf_required=False - ) - if eventservicenetidx >= 0: - logger.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 + event_index = self.session.get_control_net_index(eventdev) + self.session.add_remove_control_net(event_index, conf_required=False) + self.session.add_remove_control_iface(node, event_index, conf_required=False) + # setup multicast routes as needed logger.info( - "node(%s) interface(%s) ota group(%s) dev(%s)", + "node(%s) interface(%s) ota(%s:%s) event(%s:%s)", node.name, iface.name, otagroup, otadev, + eventgroup, + eventdev, ) 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: + if eventgroup != otagroup: node.node_net_client.create_route(eventgroup, eventdev) def get_iface(self, nem_id: int) -> Optional[CoreInterface]: @@ -536,7 +509,13 @@ class EmaneManager: Start one EMANE daemon per node having a radio. Add a control network even if the user has not configured one. """ - logger.info("starting emane daemons...") + nem = self.get_nem_id(iface) + logger.info( + "starting emane daemon node(%s) iface(%s) nem(%s)", + node.name, + iface.name, + nem, + ) loglevel = str(DEFAULT_LOG_LEVEL) cfgloglevel = self.session.options.get_config_int("emane_log_level") realtime = self.session.options.get_config_bool("emane_realtime", default=True) @@ -552,13 +531,11 @@ class EmaneManager: platform_xml = node.directory / emanexml.platform_file_name(iface) args = f"{emanecmd} -f {log_file} {platform_xml}" node.cmd(args) - logger.info("node(%s) emane daemon running: %s", node.name, args) else: log_file = self.session.directory / f"{iface.name}-emane.log" platform_xml = self.session.directory / emanexml.platform_file_name(iface) args = f"{emanecmd} -f {log_file} {platform_xml}" node.host_cmd(args, cwd=self.session.directory) - logger.info("node(%s) host emane daemon running: %s", node.name, args) def install_iface(self, iface: CoreInterface) -> None: emane_net = iface.net diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 97646699..e26d0488 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -12,7 +12,6 @@ from core.emulator.distributed import DistributedServer from core.errors import CoreError from core.nodes.base import CoreNode, CoreNodeBase from core.nodes.interface import CoreInterface -from core.nodes.network import CtrlNet from core.xml import corexml logger = logging.getLogger(__name__) @@ -146,18 +145,13 @@ def add_configurations( def build_platform_xml( - emane_manager: "EmaneManager", - control_net: CtrlNet, - node: CoreNodeBase, - iface: CoreInterface, + emane_manager: "EmaneManager", node: CoreNodeBase, iface: CoreInterface ) -> None: """ Create platform xml for a specific node. :param emane_manager: emane manager with emane configurations - :param control_net: control net node for this emane - network :param node: node to create a platform xml for :param iface: node interface to create platform xml for :return: the next nem id that can be used for creating platform xml files @@ -171,14 +165,10 @@ def build_platform_xml( emane_net.model.build_xml_files(config, iface) # create top level platform element - transport_configs = {"otamanagerdevice", "eventservicedevice"} platform_element = etree.Element("platform") for configuration in emane_net.model.platform_config: name = configuration.id - if not isinstance(node, CoreNode) and name in transport_configs: - value = control_net.brname - else: - value = config[configuration.id] + value = config[configuration.id] if name == "controlportendpoint": port = emane_manager.get_nem_port(iface) value = f"0.0.0.0:{port}" From bcd9cc7ac20c1a21e89e298bc5885523e7834a57 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 24 May 2021 21:41:05 -0700 Subject: [PATCH 067/110] daemon: updates to provide new logic for emane event services, creating one per unique control channel, added mapping for nems to associated service for generated events --- daemon/core/emane/emanemanager.py | 237 +++++++++++++------------ daemon/core/emane/models/commeffect.py | 8 +- daemon/core/emane/models/tdma.py | 11 +- daemon/core/emane/nodes.py | 28 ++- daemon/core/xml/emanexml.py | 4 +- 5 files changed, 146 insertions(+), 142 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index f61bf452..47279592 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -7,7 +7,7 @@ import os import threading from dataclasses import dataclass, field from enum import Enum -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union from core import utils from core.emane.emanemodel import EmaneModel @@ -27,15 +27,19 @@ if TYPE_CHECKING: from core.emulator.session import Session try: - from emane.events import EventService, PathlossEvent - from emane.events import LocationEvent + from emane.events import EventService, PathlossEvent, CommEffectEvent, LocationEvent from emane.events.eventserviceexception import EventServiceException except ImportError: try: - from emanesh.events import EventService - from emanesh.events import LocationEvent + from emanesh.events import ( + EventService, + PathlossEvent, + CommEffectEvent, + LocationEvent, + ) from emanesh.events.eventserviceexception import EventServiceException except ImportError: + CommEffectEvent = None EventService = None LocationEvent = None PathlossEvent = None @@ -59,6 +63,59 @@ class StartData: ifaces: List[CoreInterface] = field(default_factory=list) +class EmaneEventService: + def __init__( + self, manager: "EmaneManager", device: str, group: str, port: int + ) -> None: + self.manager: "EmaneManager" = manager + self.device: str = device + self.group: str = group + self.port: int = port + self.running: bool = False + self.thread: Optional[threading.Thread] = None + logger.info("starting emane event service %s %s:%s", device, group, port) + self.events: EventService = EventService( + eventchannel=(group, port, device), otachannel=None + ) + + def start(self) -> None: + self.running = True + self.thread = threading.Thread(target=self.run, daemon=True) + self.thread.start() + + def run(self) -> None: + """ + Run and monitor events. + """ + logger.info("subscribing to emane location events") + while self.running: + _uuid, _seq, events = self.events.nextEvent() + # this occurs with 0.9.1 event service + if not self.running: + break + for event in events: + nem, eid, data = event + if eid == LocationEvent.IDENTIFIER: + self.manager.handlelocationevent(nem, eid, data) + logger.info("unsubscribing from emane location events") + + def stop(self) -> None: + """ + Stop service and monitoring events. + """ + self.events.breakloop() + self.running = False + if self.thread: + self.thread.join() + self.thread = None + for fd in self.events._readFd, self.events._writeFd: + if fd >= 0: + os.close(fd) + for f in self.events._socket, self.events._socketOTA: + if f: + f.close() + + class EmaneManager: """ EMANE controller object. Lives in a Session instance and is used for @@ -98,10 +155,10 @@ class EmaneManager: # link monitor self.link_monitor: EmaneLinkMonitor = EmaneLinkMonitor(self) - - self.service: Optional[EventService] = None + # emane event monitoring + self.services: Dict[str, EmaneEventService] = {} + self.nem_service: Dict[int, EmaneEventService] = {} self.eventchannel: Optional[Tuple[str, int, str]] = None - self.event_device: Optional[str] = None def next_nem_id(self, iface: CoreInterface) -> int: nem_id = self.session.options.get_config_int("nem_id_start") @@ -201,47 +258,6 @@ class EmaneManager: self.node_configs.get(node_id, {}).clear() self.node_models.pop(node_id, None) - def deleteeventservice(self) -> None: - if self.service: - for fd in self.service._readFd, self.service._writeFd: - if fd >= 0: - os.close(fd) - for f in self.service._socket, self.service._socketOTA: - if f: - f.close() - self.service = None - self.event_device = None - - def initeventservice(self) -> None: - """ - Re-initialize the EMANE Event service. - The multicast group and/or port may be configured. - """ - # Get the control network to be used for events - group, port = "224.1.2.8:45703".split(":") - self.event_device = DEFAULT_DEV - eventnetidx = self.session.get_control_net_index(self.event_device) - if eventnetidx < 0: - logger.error( - "invalid emane event service device provided: %s", self.event_device - ) - return - # make sure the event control network is in place - eventnet = self.session.add_remove_control_net( - net_index=eventnetidx, remove=False, conf_required=False - ) - if eventnet is not None: - # direct EMANE events towards control net bridge - self.event_device = eventnet.brname - self.eventchannel = (group, int(port), self.event_device) - # disabled otachannel for event service - # only needed for e.g. antennaprofile events xmit by models - logger.info("using %s for emane event service", self.event_device) - try: - self.service = EventService(eventchannel=self.eventchannel, otachannel=None) - except EventServiceException: - logger.exception("error starting emane event service") - def add_node(self, emane_net: EmaneNet) -> None: """ Add EMANE network object to this manager. @@ -304,9 +320,6 @@ class EmaneManager: status = self.setup() if status != EmaneState.SUCCESS: return status - self.initeventservice() - if self.service and self.doeventmonitor(): - self.starteventmonitor() self.startup_nodes() if self.links_enabled(): self.link_monitor.start() @@ -319,9 +332,15 @@ class EmaneManager: for data in start_data: node = data.node for iface in data.ifaces: - if isinstance(node, CoreNode): - self.setup_ota(node, iface) - emanexml.build_platform_xml(self, node, iface) + nem_id = self.next_nem_id(iface) + logger.info( + "starting emane for node(%s) iface(%s) nem(%s)", + node.name, + iface.name, + nem_id, + ) + self.setup_control_channels(nem_id, node, iface) + emanexml.build_platform_xml(self, nem_id, node, iface) self.start_daemon(node, iface) self.install_iface(iface) @@ -347,7 +366,9 @@ class EmaneManager: start_node.ifaces = sorted(start_node.ifaces, key=lambda x: x.node_id) return start_nodes - def setup_ota(self, node: CoreNode, iface: CoreInterface) -> None: + def setup_control_channels( + self, nem_id: int, node: CoreNodeBase, iface: CoreInterface + ) -> None: if not isinstance(iface.net, EmaneNet): raise CoreError( f"emane interface not connected to emane net: {iface.net.name}" @@ -358,13 +379,35 @@ class EmaneManager: otadev = config["otamanagerdevice"] ota_index = self.session.get_control_net_index(otadev) self.session.add_remove_control_net(ota_index, conf_required=False) - self.session.add_remove_control_iface(node, ota_index, conf_required=False) + if isinstance(node, CoreNode): + self.session.add_remove_control_iface(node, ota_index, conf_required=False) # setup event device - eventgroup, _eventport = config["eventservicegroup"].split(":") + eventgroup, eventport = config["eventservicegroup"].split(":") eventdev = config["eventservicedevice"] event_index = self.session.get_control_net_index(eventdev) - self.session.add_remove_control_net(event_index, conf_required=False) - self.session.add_remove_control_iface(node, event_index, conf_required=False) + event_net = self.session.add_remove_control_net( + event_index, conf_required=False + ) + if isinstance(node, CoreNode): + self.session.add_remove_control_iface( + node, event_index, conf_required=False + ) + # initialize emane event services + service = self.services.get(event_net.brname) + if not service: + try: + service = EmaneEventService( + self, event_net.brname, eventgroup, int(eventport) + ) + self.services[event_net.brname] = service + self.nem_service[nem_id] = service + except EventServiceException: + raise CoreError( + "failed to start emane event services " + f"{event_net.brname} {eventgroup}:{eventport}" + ) + else: + self.nem_service[nem_id] = service # setup multicast routes as needed logger.info( "node(%s) interface(%s) ota(%s:%s) event(%s:%s)", @@ -451,7 +494,11 @@ class EmaneManager: node.cmd(kill_emaned, wait=False) else: node.host_cmd(kill_emaned, wait=False) - self.stopeventmonitor() + # stop emane event services + while self.services: + _, service = self.services.popitem() + service.stop() + self.nem_service.clear() def check_node_models(self) -> None: """ @@ -509,13 +556,6 @@ class EmaneManager: Start one EMANE daemon per node having a radio. Add a control network even if the user has not configured one. """ - nem = self.get_nem_id(iface) - logger.info( - "starting emane daemon node(%s) iface(%s) nem(%s)", - node.name, - iface.name, - nem, - ) loglevel = str(DEFAULT_LOG_LEVEL) cfgloglevel = self.session.options.get_config_int("emane_log_level") realtime = self.session.options.get_config_bool("emane_realtime", default=True) @@ -572,48 +612,6 @@ class EmaneManager: tmp = not self.doeventmonitor() return tmp - def starteventmonitor(self) -> None: - """ - Start monitoring EMANE location events if configured to do so. - """ - logger.info("starting emane event monitor") - self.doeventloop = True - self.eventmonthread = threading.Thread( - target=self.eventmonitorloop, daemon=True - ) - self.eventmonthread.start() - - def stopeventmonitor(self) -> None: - """ - Stop monitoring EMANE location events. - """ - self.doeventloop = False - if self.service is not None: - self.service.breakloop() - if self.eventmonthread is not None: - self.eventmonthread.join() - self.eventmonthread = None - # reset the service, otherwise nextEvent won"t work - self.deleteeventservice() - - def eventmonitorloop(self) -> None: - """ - Thread target that monitors EMANE location events. - """ - if self.service is None: - return - logger.info("subscribing to EMANE location events") - while self.doeventloop: - _uuid, _seq, events = self.service.nextEvent() - # this occurs with 0.9.1 event service - if not self.doeventloop: - break - for event in events: - nem, eid, data = event - if eid == LocationEvent.IDENTIFIER: - self.handlelocationevent(nem, eid, data) - logger.info("unsubscribing from EMANE location events") - def handlelocationevent(self, rxnemid: int, eid: int, data: str) -> None: """ Handle an EMANE location event. @@ -629,7 +627,6 @@ class EmaneManager: ): logger.warning("dropped invalid location event") continue - # yaw,pitch,roll,azimuth,elevation,velocity are unhandled lat = attrs["latitude"] lon = attrs["longitude"] @@ -724,5 +721,19 @@ class EmaneManager: event = PathlossEvent() event.append(nem1, forward=rx1) event.append(nem2, forward=rx2) - self.service.publish(nem1, event) - self.service.publish(nem2, event) + self.publish_event(nem1, event) + self.publish_event(nem2, event) + + def publish_event( + self, + nem_id: int, + event: Union[PathlossEvent, CommEffectEvent, LocationEvent], + send_all: bool = False, + ) -> None: + service = self.nem_service.get(nem_id) + if not service: + logger.error("no service to publish event nem(%s)", nem_id) + return + if send_all: + nem_id = 0 + service.events.publish(nem_id, event) diff --git a/daemon/core/emane/models/commeffect.py b/daemon/core/emane/models/commeffect.py index b73dc837..c3f0b07b 100644 --- a/daemon/core/emane/models/commeffect.py +++ b/daemon/core/emane/models/commeffect.py @@ -122,15 +122,9 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): Generate CommEffect events when a Link Message is received having link parameters. """ - service = self.session.emane.service - if service is None: - logger.warning("%s: EMANE event service unavailable", self.name) - return - if iface is None or iface2 is None: logger.warning("%s: missing NEM information", self.name) return - # TODO: batch these into multiple events per transmission # TODO: may want to split out seconds portion of delay and jitter event = CommEffectEvent() @@ -146,4 +140,4 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): unicast=int(convert_none(options.bandwidth)), broadcast=int(convert_none(options.bandwidth)), ) - service.publish(nem2, event) + self.session.emane.publish_event(nem2, event) diff --git a/daemon/core/emane/models/tdma.py b/daemon/core/emane/models/tdma.py index 0ba756e4..62843ec1 100644 --- a/daemon/core/emane/models/tdma.py +++ b/daemon/core/emane/models/tdma.py @@ -59,8 +59,9 @@ class EmaneTdmaModel(emanemodel.EmaneModel): logger.warning("ignoring invalid tdma schedule: %s", schedule) return # initiate tdma schedule - event_device = self.session.emane.event_device - logger.info( - "setting up tdma schedule: schedule(%s) device(%s)", schedule, event_device - ) - utils.cmd(f"emaneevent-tdmaschedule -i {event_device} {schedule}") + for service in self.session.emane.services.values(): + device = service.device + logger.info( + "setting up tdma schedule: schedule(%s) device(%s)", schedule, device + ) + utils.cmd(f"emaneevent-tdmaschedule -i {device} {schedule}") diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 1e43723b..12caf408 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -140,15 +140,12 @@ class EmaneNet(CoreNetworkBase): :param iface: interface to set nem position for """ - if self.session.emane.service is None: - logger.info("position service not available") - return 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) + self.session.emane.publish_event(nemid, event, send_all=True) def setnempositions(self, moved_ifaces: List[CoreInterface]) -> None: """ @@ -156,20 +153,21 @@ class EmaneNet(CoreNetworkBase): calculation. Generate an EMANE Location Event having several entries for each interface that has moved. """ - if len(moved_ifaces) == 0: + if not moved_ifaces: return - - if self.session.emane.service is None: - logger.info("position service not available") - return - - event = LocationEvent() + services = {} 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) - self.session.emane.service.publish(0, event) + if not position: + continue + nem_id, lon, lat, alt = position + service = self.session.emane.nem_service.get(nem_id) + if not service: + continue + event = services.setdefault(service, LocationEvent()) + event.append(nem_id, latitude=lat, longitude=lon, altitude=alt) + for service, event in services.items(): + service.events.publish(0, event) def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: links = super().links(flags) diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index e26d0488..9d0753cb 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -145,13 +145,14 @@ def add_configurations( def build_platform_xml( - emane_manager: "EmaneManager", node: CoreNodeBase, iface: CoreInterface + emane_manager: "EmaneManager", nem_id: int, node: CoreNodeBase, iface: CoreInterface ) -> None: """ Create platform xml for a specific node. :param emane_manager: emane manager with emane configurations + :param nem_id: nem id for current node/interface :param node: node to create a platform xml for :param iface: node interface to create platform xml for :return: the next nem id that can be used for creating platform xml files @@ -160,7 +161,6 @@ def build_platform_xml( emane_net = iface.net if not isinstance(emane_net, EmaneNet): raise CoreError(f"emane interface not connected to emane net: {emane_net.name}") - nem_id = emane_manager.next_nem_id(iface) config = emane_manager.get_iface_config(emane_net, iface) emane_net.model.build_xml_files(config, iface) From 8d5c3bd2126590759cc7f2dd5ad1afe295f4b61d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 25 May 2021 10:52:50 -0700 Subject: [PATCH 068/110] grpc: update grpc call for get emane event channel to take in a nem id, since channels may now be unique per nem --- daemon/core/api/grpc/client.py | 7 +++++-- daemon/core/api/grpc/server.py | 12 ++++++------ daemon/core/emane/emanemanager.py | 3 +-- daemon/proto/core/api/grpc/emane.proto | 1 + 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 5ebed44e..2785a037 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -1034,15 +1034,18 @@ class CoreGrpcClient: response = self.stub.GetNodeConfigService(request) return dict(response.config) - def get_emane_event_channel(self, session_id: int) -> wrappers.EmaneEventChannel: + def get_emane_event_channel( + self, session_id: int, nem_id: int + ) -> wrappers.EmaneEventChannel: """ Retrieves the current emane event channel being used for a session. :param session_id: session to get emane event channel for + :param nem_id: nem id for the desired event channel :return: emane event channel :raises grpc.RpcError: when session doesn't exist """ - request = GetEmaneEventChannelRequest(session_id=session_id) + request = GetEmaneEventChannelRequest(session_id=session_id, nem_id=nem_id) response = self.stub.GetEmaneEventChannel(request) return wrappers.EmaneEventChannel.from_proto(response) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index e2851964..8b0b903a 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -1241,12 +1241,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): self, request: GetEmaneEventChannelRequest, context: ServicerContext ) -> GetEmaneEventChannelResponse: session = self.get_session(request.session_id, context) - group = None - port = None - device = None - if session.emane.eventchannel: - group, port, device = session.emane.eventchannel - return GetEmaneEventChannelResponse(group=group, port=port, device=device) + service = session.emane.nem_service.get(request.nem_id) + if not service: + context.abort(grpc.StatusCode.NOT_FOUND, f"unknown nem id {request.nem_id}") + return GetEmaneEventChannelResponse( + group=service.group, port=service.port, device=service.device + ) def ExecuteScript(self, request, context): existing_sessions = set(self.coreemu.sessions.keys()) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 47279592..5a403c20 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -7,7 +7,7 @@ import os import threading from dataclasses import dataclass, field from enum import Enum -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Type, Union from core import utils from core.emane.emanemodel import EmaneModel @@ -158,7 +158,6 @@ class EmaneManager: # emane event monitoring self.services: Dict[str, EmaneEventService] = {} self.nem_service: Dict[int, EmaneEventService] = {} - self.eventchannel: Optional[Tuple[str, int, str]] = None def next_nem_id(self, iface: CoreInterface) -> int: nem_id = self.session.options.get_config_int("nem_id_start") diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index 1e56a0aa..5aa0c952 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -33,6 +33,7 @@ message GetEmaneModelConfig { message GetEmaneEventChannelRequest { int32 session_id = 1; + int32 nem_id = 2; } message GetEmaneEventChannelResponse { From 820539191d987453d986bac2bdf37edba42df5fe Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 25 May 2021 12:14:28 -0700 Subject: [PATCH 069/110] daemon: adjustments to emane model post startup to accept an interface, since settings may be unique per interface, updated tdma to use this information for trying to initialize its tdma schedule properly --- daemon/core/emane/emanemanager.py | 8 ++++---- daemon/core/emane/emanemodel.py | 3 ++- daemon/core/emane/models/tdma.py | 23 ++++++++++++----------- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 5a403c20..60f25023 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -446,17 +446,17 @@ class EmaneManager: """ Retransmit location events now that all NEMs are active. """ - if not self.genlocationevents(): - return + events_enabled = self.genlocationevents() with self._emane_node_lock: for node_id in sorted(self._emane_nets): emane_net = self._emane_nets[node_id] logger.debug( "post startup for emane node: %s - %s", emane_net.id, emane_net.name ) - emane_net.model.post_startup() for iface in emane_net.get_ifaces(): - iface.setposition() + emane_net.model.post_startup(iface) + if events_enabled: + iface.setposition() def reset(self) -> None: """ diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 54f8c72d..62e9fb0d 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -134,10 +134,11 @@ class EmaneModel(WirelessModel): emanexml.create_phy_xml(self, iface, config) emanexml.create_transport_xml(iface, config) - def post_startup(self) -> None: + def post_startup(self, iface: CoreInterface) -> None: """ Logic to execute after the emane manager is finished with startup. + :param iface: interface for post startup :return: nothing """ logger.debug("emane model(%s) has no post setup tasks", self.name) diff --git a/daemon/core/emane/models/tdma.py b/daemon/core/emane/models/tdma.py index 62843ec1..c23e3d73 100644 --- a/daemon/core/emane/models/tdma.py +++ b/daemon/core/emane/models/tdma.py @@ -9,7 +9,9 @@ from typing import Set from core import constants, utils from core.config import Configuration from core.emane import emanemodel +from core.emane.nodes import EmaneNet from core.emulator.enumerations import ConfigDataTypes +from core.nodes.interface import CoreInterface logger = logging.getLogger(__name__) @@ -44,22 +46,21 @@ class EmaneTdmaModel(emanemodel.EmaneModel): ) cls.mac_config.insert(0, config_item) - def post_startup(self) -> None: - """ - Logic to execute after the emane manager is finished with startup. - - :return: nothing - """ + def post_startup(self, iface: CoreInterface) -> None: # get configured schedule - config = self.session.emane.get_config(self.id, self.name) - if not config: - return + emane_net = self.session.get_node(self.id, EmaneNet) + config = self.session.emane.get_iface_config(emane_net, iface) schedule = Path(config[self.schedule_name]) if not schedule.is_file(): - logger.warning("ignoring invalid tdma schedule: %s", schedule) + logger.error("ignoring invalid tdma schedule: %s", schedule) return # initiate tdma schedule - for service in self.session.emane.services.values(): + nem_id = self.session.emane.get_nem_id(iface) + if not nem_id: + logger.error("could not find nem for interface") + return + service = self.session.emane.nem_service.get(nem_id) + if service: device = service.device logger.info( "setting up tdma schedule: schedule(%s) device(%s)", schedule, device From 795a5f5865542c47bd6e19ab8eaf66538ba3a6d6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 26 May 2021 09:54:32 -0700 Subject: [PATCH 070/110] daemon: refactoring for starting up and shutting down emane daemon per interface --- daemon/core/emane/emanemanager.py | 104 +++++++++++++----------------- daemon/core/xml/emanexml.py | 32 +++++---- 2 files changed, 60 insertions(+), 76 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 60f25023..01b9758c 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -5,9 +5,8 @@ Implements configuration and control of an EMANE emulation. import logging import os import threading -from dataclasses import dataclass, field from enum import Enum -from typing import TYPE_CHECKING, Dict, List, Optional, Set, Type, Union +from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type, Union from core import utils from core.emane.emanemodel import EmaneModel @@ -17,7 +16,7 @@ from core.emane.nodes import EmaneNet from core.emulator.data import LinkData from core.emulator.enumerations import LinkTypes, MessageFlags, RegisterTlvs from core.errors import CoreCommandError, CoreError -from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase +from core.nodes.base import CoreNetworkBase, CoreNode, NodeBase from core.nodes.interface import CoreInterface, TunTap from core.xml import emanexml @@ -57,12 +56,6 @@ class EmaneState(Enum): NOT_READY = 2 -@dataclass -class StartData: - node: CoreNodeBase - ifaces: List[CoreInterface] = field(default_factory=list) - - class EmaneEventService: def __init__( self, manager: "EmaneManager", device: str, group: str, port: int @@ -327,26 +320,24 @@ class EmaneManager: def startup_nodes(self) -> None: with self._emane_node_lock: logger.info("emane building xmls...") - start_data = self.get_start_data() - for data in start_data: - node = data.node - for iface in data.ifaces: - nem_id = self.next_nem_id(iface) - logger.info( - "starting emane for node(%s) iface(%s) nem(%s)", - node.name, - iface.name, - nem_id, - ) - self.setup_control_channels(nem_id, node, iface) - emanexml.build_platform_xml(self, nem_id, node, iface) - self.start_daemon(node, iface) - self.install_iface(iface) + for emane_net, iface in self.get_ifaces(): + nem_id = self.next_nem_id(iface) + nem_port = self.get_nem_port(iface) + logger.info( + "starting emane for node(%s) iface(%s) nem(%s)", + iface.node.name, + iface.name, + nem_id, + ) + config = self.get_iface_config(emane_net, iface) + self.setup_control_channels(nem_id, iface, config) + emanexml.build_platform_xml(nem_id, nem_port, emane_net, iface, config) + self.start_daemon(iface) + self.install_iface(emane_net, iface, config) - def get_start_data(self) -> List[StartData]: - node_map = {} - for node_id in sorted(self._emane_nets): - emane_net = self._emane_nets[node_id] + def get_ifaces(self) -> List[Tuple[EmaneNet, CoreInterface]]: + ifaces = [] + for emane_net in self._emane_nets.values(): if not emane_net.model: logger.error("emane net(%s) has no model", emane_net.name) continue @@ -358,21 +349,13 @@ class EmaneManager: iface.name, ) continue - start_node = node_map.setdefault(iface.node, StartData(iface.node)) - start_node.ifaces.append(iface) - start_nodes = sorted(node_map.values(), key=lambda x: x.node.id) - for start_node in start_nodes: - start_node.ifaces = sorted(start_node.ifaces, key=lambda x: x.node_id) - return start_nodes + ifaces.append((emane_net, iface)) + return sorted(ifaces, key=lambda x: (x[1].node.id, x[1].node_id)) def setup_control_channels( - self, nem_id: int, node: CoreNodeBase, iface: CoreInterface + self, nem_id: int, iface: CoreInterface, config: Dict[str, str] ) -> None: - if not isinstance(iface.net, EmaneNet): - raise CoreError( - f"emane interface not connected to emane net: {iface.net.name}" - ) - config = self.get_iface_config(iface.net, iface) + node = iface.node # setup ota device otagroup, _otaport = config["otamanagergroup"].split(":") otadev = config["otamanagerdevice"] @@ -467,6 +450,8 @@ class EmaneManager: self._emane_nets.clear() self.nems_to_ifaces.clear() self.ifaces_to_nems.clear() + self.nems_to_ifaces.clear() + self.services.clear() def shutdown(self) -> None: """ @@ -478,17 +463,19 @@ class EmaneManager: logger.info("stopping EMANE daemons") if self.links_enabled(): self.link_monitor.stop() - # shutdown interfaces and stop daemons - kill_emaned = "killall -q emane" - start_data = self.get_start_data() - for data in start_data: - node = data.node + # shutdown interfaces + nodes = set() + for _, iface in self.get_ifaces(): + node = iface.node if not node.up: continue - for iface in data.ifaces: - if isinstance(node, CoreNode): - iface.shutdown() - iface.poshook = None + nodes.add(node) + if isinstance(node, CoreNode): + iface.shutdown() + iface.poshook = None + kill_emaned = "killall -q emane" + # stop all emane daemons on associated nodes + for node in nodes: if isinstance(node, CoreNode): node.cmd(kill_emaned, wait=False) else: @@ -550,11 +537,14 @@ class EmaneManager: color=color, ) - def start_daemon(self, node: CoreNodeBase, iface: CoreInterface) -> 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. + Start emane daemon for a given nem/interface. + + :param iface: interface to start emane daemon for + :return: nothing """ + node = iface.node loglevel = str(DEFAULT_LOG_LEVEL) cfgloglevel = self.session.options.get_config_int("emane_log_level") realtime = self.session.options.get_config_bool("emane_realtime", default=True) @@ -576,13 +566,9 @@ class EmaneManager: args = f"{emanecmd} -f {log_file} {platform_xml}" node.host_cmd(args, cwd=self.session.directory) - def install_iface(self, iface: CoreInterface) -> None: - emane_net = iface.net - if not isinstance(emane_net, EmaneNet): - raise CoreError( - f"emane interface not connected to emane net: {emane_net.name}" - ) - config = self.get_iface_config(emane_net, iface) + def install_iface( + self, emane_net: EmaneNet, iface: CoreInterface, config: Dict[str, str] + ) -> None: external = config.get("external", "0") if isinstance(iface, TunTap) and external == "0": iface.set_ips() diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 9d0753cb..f8489b5b 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -17,7 +17,6 @@ from core.xml import corexml logger = logging.getLogger(__name__) if TYPE_CHECKING: - from core.emane.emanemanager import EmaneManager from core.emane.emanemodel import EmaneModel _MAC_PREFIX = "02:02" @@ -145,33 +144,29 @@ def add_configurations( def build_platform_xml( - emane_manager: "EmaneManager", nem_id: int, node: CoreNodeBase, iface: CoreInterface + nem_id: int, + nem_port: int, + emane_net: EmaneNet, + iface: CoreInterface, + config: Dict[str, str], ) -> None: """ - Create platform xml for a specific node. + Create platform xml for a nem/interface. - :param emane_manager: emane manager with emane - configurations :param nem_id: nem id for current node/interface - :param node: node to create a platform xml for + :param nem_port: control port to configure for emane + :param emane_net: emane network associate with node and interface :param iface: node interface to create platform xml for - :return: the next nem id that can be used for creating platform xml files + :param config: emane configuration for interface + :return: nothing """ - # create model based xml files - emane_net = iface.net - if not isinstance(emane_net, EmaneNet): - raise CoreError(f"emane interface not connected to emane net: {emane_net.name}") - config = emane_manager.get_iface_config(emane_net, iface) - emane_net.model.build_xml_files(config, iface) - # create top level platform element platform_element = etree.Element("platform") for configuration in emane_net.model.platform_config: name = configuration.id value = config[configuration.id] if name == "controlportendpoint": - port = emane_manager.get_nem_port(iface) - value = f"0.0.0.0:{port}" + value = f"0.0.0.0:{nem_port}" add_param(platform_element, name, value) # build nem xml @@ -180,6 +175,9 @@ def build_platform_xml( "nem", id=str(nem_id), name=iface.localname, definition=nem_definition ) + # create model based xml files + emane_net.model.build_xml_files(config, iface) + # check if this is an external transport if is_external(config): nem_element.set("transport", "external") @@ -205,7 +203,7 @@ def build_platform_xml( doc_name = "platform" file_name = platform_file_name(iface) - create_node_file(node, platform_element, doc_name, file_name) + create_node_file(iface.node, platform_element, doc_name, file_name) def create_transport_xml(iface: CoreInterface, config: Dict[str, str]) -> None: From 777097c85e91991e0070c79b145524bbf7eb639c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 26 May 2021 12:22:36 -0700 Subject: [PATCH 071/110] daemon: updated emane position hooks and updating nem position logic to live in emane manager --- daemon/core/emane/emanemanager.py | 93 +++++++++++++++++++++++++------ daemon/core/emane/emanemodel.py | 4 +- daemon/core/emane/nodes.py | 61 +------------------- 3 files changed, 78 insertions(+), 80 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 01b9758c..46f10a1b 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -321,19 +321,22 @@ class EmaneManager: with self._emane_node_lock: logger.info("emane building xmls...") for emane_net, iface in self.get_ifaces(): - nem_id = self.next_nem_id(iface) - nem_port = self.get_nem_port(iface) - logger.info( - "starting emane for node(%s) iface(%s) nem(%s)", - iface.node.name, - iface.name, - nem_id, - ) - config = self.get_iface_config(emane_net, iface) - self.setup_control_channels(nem_id, iface, config) - emanexml.build_platform_xml(nem_id, nem_port, emane_net, iface, config) - self.start_daemon(iface) - self.install_iface(emane_net, iface, config) + self.start_iface(emane_net, iface) + + def start_iface(self, emane_net: EmaneNet, iface: CoreInterface) -> None: + nem_id = self.next_nem_id(iface) + nem_port = self.get_nem_port(iface) + logger.info( + "starting emane for node(%s) iface(%s) nem(%s)", + iface.node.name, + iface.name, + nem_id, + ) + config = self.get_iface_config(emane_net, iface) + self.setup_control_channels(nem_id, iface, config) + emanexml.build_platform_xml(nem_id, nem_port, emane_net, iface, config) + self.start_daemon(iface) + self.install_iface(iface, config) def get_ifaces(self) -> List[Tuple[EmaneNet, CoreInterface]]: ifaces = [] @@ -414,6 +417,64 @@ class EmaneManager: nem_id = self.get_nem_id(iface) return int(f"47{nem_id:03}") + def get_nem_position( + self, iface: CoreInterface + ) -> Optional[Tuple[int, float, float, int]]: + """ + Retrieves nem position for a given interface. + + :param iface: interface to get nem emane position for + :return: nem position tuple, None otherwise + """ + nem_id = self.get_nem_id(iface) + if nem_id is None: + logger.info("nem for %s is unknown", iface.localname) + return + 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: + alt = node.position.alt + node.position.set_geo(lon, lat, alt) + # altitude must be an integer or warning is printed + alt = int(round(alt)) + return nem_id, lon, lat, alt + + def set_nem_position(self, iface: CoreInterface) -> None: + """ + Publish a NEM location change event using the EMANE event service. + + :param iface: interface to set nem position for + """ + position = self.get_nem_position(iface) + if position: + nemid, lon, lat, alt = position + event = LocationEvent() + event.append(nemid, latitude=lat, longitude=lon, altitude=alt) + self.publish_event(nemid, event, send_all=True) + + def set_nem_positions(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 interface that has moved. + """ + if not moved_ifaces: + return + services = {} + for iface in moved_ifaces: + position = self.get_nem_position(iface) + if not position: + continue + nem_id, lon, lat, alt = position + service = self.nem_service.get(nem_id) + if not service: + continue + event = services.setdefault(service, LocationEvent()) + event.append(nem_id, latitude=lat, longitude=lon, altitude=alt) + for service, event in services.items(): + service.events.publish(0, event) + def write_nem(self, iface: CoreInterface, nem_id: int) -> None: path = self.session.directory / "emane_nems" try: @@ -566,16 +627,14 @@ class EmaneManager: args = f"{emanecmd} -f {log_file} {platform_xml}" node.host_cmd(args, cwd=self.session.directory) - def install_iface( - self, emane_net: EmaneNet, iface: CoreInterface, config: Dict[str, str] - ) -> None: + def install_iface(self, iface: CoreInterface, config: Dict[str, str]) -> None: 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.poshook = self.set_nem_position iface.setposition() def doeventmonitor(self) -> bool: diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 62e9fb0d..92346676 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -7,7 +7,6 @@ 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.data import LinkOptions from core.emulator.enumerations import ConfigDataTypes from core.errors import CoreError @@ -153,8 +152,7 @@ class EmaneModel(WirelessModel): :return: nothing """ try: - emane_net = self.session.get_node(self.id, EmaneNet) - emane_net.setnempositions(moved_ifaces) + self.session.emane.set_nem_positions(moved_ifaces) except CoreError: logger.exception("error during update") diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 12caf408..76a93767 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -4,7 +4,7 @@ share the same MAC+PHY model. """ import logging -from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type +from typing import TYPE_CHECKING, Dict, List, Optional, Type from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.distributed import DistributedServer @@ -110,65 +110,6 @@ class EmaneNet(CoreNetworkBase): self.mobility = model(session=self.session, _id=self.id) self.mobility.update_config(config) - def _nem_position( - self, iface: CoreInterface - ) -> Optional[Tuple[int, float, float, float]]: - """ - Creates nem position for emane event for a given interface. - - :param iface: interface to get nem emane position for - :return: nem position tuple, None otherwise - """ - nem_id = self.session.emane.get_nem_id(iface) - ifname = iface.localname - if nem_id is None: - logger.info("nemid for %s is unknown", ifname) - return - 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: - alt = node.position.alt - node.position.set_geo(lon, lat, alt) - # altitude must be an integer or warning is printed - alt = int(round(alt)) - return nem_id, lon, lat, alt - - def setnemposition(self, iface: CoreInterface) -> None: - """ - Publish a NEM location change event using the EMANE event service. - - :param iface: interface to set nem position for - """ - 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.publish_event(nemid, event, send_all=True) - - 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 interface that has moved. - """ - if not moved_ifaces: - return - services = {} - for iface in moved_ifaces: - position = self._nem_position(iface) - if not position: - continue - nem_id, lon, lat, alt = position - service = self.session.emane.nem_service.get(nem_id) - if not service: - continue - event = services.setdefault(service, LocationEvent()) - event.append(nem_id, latitude=lat, longitude=lon, altitude=alt) - for service, event in services.items(): - service.events.publish(0, event) - def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]: links = super().links(flags) emane_manager = self.session.emane From 3fcefc4d79fb74aa7a84f33c38da5ee5dcbdac0f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 26 May 2021 12:29:46 -0700 Subject: [PATCH 072/110] daemon: updated emane shutdown to kill emane daemon for a given interface, instead of killall --- daemon/core/emane/emanemanager.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 46f10a1b..5f35f4d9 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -525,22 +525,17 @@ class EmaneManager: if self.links_enabled(): self.link_monitor.stop() # shutdown interfaces - nodes = set() for _, iface in self.get_ifaces(): node = iface.node if not node.up: continue - nodes.add(node) + kill_cmd = f'pkill -f "emane.+{iface.name}"' if isinstance(node, CoreNode): iface.shutdown() - iface.poshook = None - kill_emaned = "killall -q emane" - # stop all emane daemons on associated nodes - for node in nodes: - if isinstance(node, CoreNode): - node.cmd(kill_emaned, wait=False) + node.cmd(kill_cmd, wait=False) else: - node.host_cmd(kill_emaned, wait=False) + node.host_cmd(kill_cmd, wait=False) + iface.poshook = None # stop emane event services while self.services: _, service = self.services.popitem() From b51200e39783eef14eb30860c676a231e5c3fb92 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 26 May 2021 12:57:35 -0700 Subject: [PATCH 073/110] daemon: updated emane model platform configuration to remove controlportendpoint option, as this will be something core itself will define --- daemon/core/emane/emanemodel.py | 9 +++++++++ daemon/core/xml/emanexml.py | 5 +++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 92346676..21fcccb3 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -27,6 +27,7 @@ class EmaneModel(WirelessModel): """ # default platform configuration settings + platform_controlport: str = "controlportendpoint" platform_xml: str = "nemmanager.xml" platform_defaults: Dict[str, str] = { "eventservicedevice": DEFAULT_DEV, @@ -88,6 +89,14 @@ class EmaneModel(WirelessModel): cls.platform_config = emanemanifest.parse( platform_xml_path, cls.platform_defaults ) + # remove controlport configuration, since core will set this directly + controlport_index = None + for index, configuration in enumerate(cls.platform_config): + if configuration.id == cls.platform_controlport: + controlport_index = index + break + if controlport_index is not None: + cls.platform_config.pop(controlport_index) @classmethod def configurations(cls) -> List[Configuration]: diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index f8489b5b..c45259f7 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -165,9 +165,10 @@ def build_platform_xml( for configuration in emane_net.model.platform_config: name = configuration.id value = config[configuration.id] - if name == "controlportendpoint": - value = f"0.0.0.0:{nem_port}" add_param(platform_element, name, value) + add_param( + platform_element, emane_net.model.platform_controlport, f"0.0.0.0:{nem_port}" + ) # build nem xml nem_definition = nem_file_name(iface) From f928284fb779664f10c576c2ff699be0c33a9605 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 26 May 2021 14:48:28 -0700 Subject: [PATCH 074/110] daemon: fixed emane setup to move binding check to after validating if emane is needed --- daemon/core/emane/emanemanager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 5f35f4d9..4ffed725 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -282,9 +282,6 @@ class EmaneManager: :return: SUCCESS, NOT_NEEDED, NOT_READY in order to delay session instantiation """ - # check if bindings were installed - if EventService is None: - raise CoreError("EMANE python bindings are not installed") logger.debug("emane setup") with self.session.nodes_lock: for node_id in self.session.nodes: @@ -297,6 +294,9 @@ class EmaneManager: if not self._emane_nets: logger.debug("no emane nodes in session") return EmaneState.NOT_NEEDED + # check if bindings were installed + if EventService is None: + raise CoreError("EMANE python bindings are not installed") self.check_node_models() return EmaneState.SUCCESS From a63e3e8d96a5c998a85a995dba44ea6ea4b7ad7c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 26 May 2021 21:01:24 -0700 Subject: [PATCH 075/110] daemon: fix to also boot config services when a node is added during runtime --- daemon/core/emulator/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 6aac8f33..e4d41950 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -577,7 +577,7 @@ class Session: if self.state == EventTypes.RUNTIME_STATE and is_boot_node: self.write_nodes() self.add_remove_control_iface(node, remove=False) - self.services.boot_services(node) + self.boot_node(node) self.sdt.add_node(node) return node From 44d797c6337a64c165152ba6cbf9338e51b34cec Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 28 May 2021 22:18:13 -0700 Subject: [PATCH 076/110] pygui: fix so the emane config dialog will select the current emane configuration for a given emane node --- daemon/core/gui/dialogs/emaneconfig.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/daemon/core/gui/dialogs/emaneconfig.py b/daemon/core/gui/dialogs/emaneconfig.py index 24ddf36c..b3f6d9ce 100644 --- a/daemon/core/gui/dialogs/emaneconfig.py +++ b/daemon/core/gui/dialogs/emaneconfig.py @@ -80,11 +80,10 @@ class EmaneConfigDialog(Dialog): self.node: Node = node self.radiovar: tk.IntVar = tk.IntVar() self.radiovar.set(1) - self.emane_models: List[str] = [ x.split("_")[1] for x in self.app.core.emane_models ] - model = self.emane_models[0] + 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() @@ -147,7 +146,6 @@ class EmaneConfigDialog(Dialog): frame = ttk.Frame(self.top) frame.grid(sticky=tk.EW, pady=PADY) frame.columnconfigure(0, weight=1) - image = images.from_enum(ImageEnum.EDITNODE, width=images.BUTTON_SIZE) self.emane_model_button = ttk.Button( frame, From 7fcedf527f7b04c04e451c55f819fdfa82165642 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 3 Jun 2021 21:37:26 -0700 Subject: [PATCH 077/110] daemon: add check to catch infinity values resulting from geo transformations and throw an error --- daemon/core/emulator/session.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index e4d41950..514bc168 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -4,6 +4,7 @@ that manages a CORE session. """ import logging +import math import os import pwd import shutil @@ -610,13 +611,16 @@ class Session: lat = options.lat lon = options.lon alt = options.alt - # check if we need to generate position from lat/lon/alt has_empty_position = all(i is None for i in [x, y]) has_lat_lon_alt = all(i is not None for i in [lat, lon, alt]) using_lat_lon_alt = has_empty_position and has_lat_lon_alt if using_lat_lon_alt: x, y, _ = self.location.getxyz(lat, lon, alt) + if math.isinf(x) or math.isinf(y): + raise CoreError( + f"invalid geo for current reference/scale: {lon},{lat},{alt}" + ) node.setposition(x, y, None) node.position.set_geo(lon, lat, alt) self.broadcast_node(node) From 425a2ee141e579d23462d034167f55f4aa889daa Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 3 Jun 2021 21:37:56 -0700 Subject: [PATCH 078/110] core-cli: add position output for querying nodes --- daemon/scripts/core-cli | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index 30041188..f050cf90 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -210,22 +210,27 @@ def query_session(core: CoreGrpcClient, args: Namespace) -> None: def query_node(core: CoreGrpcClient, args: Namespace) -> None: session = core.get_session(args.id) node, ifaces, _ = core.get_node(args.id, args.node) - print("ID | Name | Type") - print(f"{node.id:<4} | {node.name:<7} | {node.type.name}") - print("Interfaces") - print("Connected To | ", end="") - print_iface_header() - for iface in ifaces: - if iface.net_id == node.id: - if iface.node_id: - name = session.nodes[iface.node_id].name + print("ID | Name | Type | XY") + xy_pos = f"{int(node.position.x)},{int(node.position.y)}" + print(f"{node.id:<4} | {node.name[:7]:<7} | {node.type.name[:7]:<7} | {xy_pos}") + if node.geo: + print("Geo") + print(f"{node.geo.lon:.7f},{node.geo.lat:.7f},{node.geo.alt:f}") + if ifaces: + print("Interfaces") + print("Connected To | ", end="") + print_iface_header() + for iface in ifaces: + if iface.net_id == node.id: + if iface.node_id: + name = session.nodes[iface.node_id].name + else: + name = session.nodes[iface.net2_id].name else: - name = session.nodes[iface.net2_id].name - else: - net_node = session.nodes.get(iface.net_id) - name = net_node.name if net_node else "" - print(f"{name:<12} | ", end="") - print_iface(iface) + net_node = session.nodes.get(iface.net_id) + name = net_node.name if net_node else "" + print(f"{name:<12} | ", end="") + print_iface(iface) @coreclient From 7198d2adc9a6b4c95a4391ab501e41c10fd85654 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 3 Jun 2021 21:48:07 -0700 Subject: [PATCH 079/110] pygui: fixed issue in emane configuration for node interfaces using the wrong values --- daemon/core/gui/dialogs/nodeconfig.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/core/gui/dialogs/nodeconfig.py b/daemon/core/gui/dialogs/nodeconfig.py index 686cd5ae..ee0d7b81 100644 --- a/daemon/core/gui/dialogs/nodeconfig.py +++ b/daemon/core/gui/dialogs/nodeconfig.py @@ -298,10 +298,9 @@ class NodeConfigDialog(Dialog): emane_node = self.canvas_node.has_emane_link(iface.id) if emane_node: emane_model = emane_node.emane.split("_")[1] + command = partial(self.click_emane_config, emane_model, iface.id) button = ttk.Button( - tab, - text=f"Configure EMANE {emane_model}", - command=lambda: self.click_emane_config(emane_model, iface.id), + tab, text=f"Configure EMANE {emane_model}", command=command ) button.grid(row=row, sticky=tk.EW, columnspan=3, pady=PADY) row += 1 @@ -367,6 +366,7 @@ class NodeConfigDialog(Dialog): button.grid(row=0, column=1, sticky=tk.EW) def click_emane_config(self, emane_model: str, iface_id: int) -> None: + logger.info("configuring emane: %s - %s", emane_model, iface_id) dialog = EmaneModelDialog(self, self.app, self.node, emane_model, iface_id) dialog.show() From 8678922c9257c8ffed5d23ae24f9aeb1145a4ec3 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 4 Jun 2021 18:55:54 -0700 Subject: [PATCH 080/110] grpc: removed global emane config from session protobuf --- daemon/proto/core/api/grpc/core.proto | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 3c703866..87cb722b 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -577,16 +577,15 @@ message Session { repeated services.ServiceDefaults default_services = 7; SessionLocation location = 8; repeated Hook hooks = 9; - map emane_config = 10; - repeated emane.GetEmaneModelConfig emane_model_configs = 11; - map wlan_configs = 12; - repeated services.NodeServiceConfig service_configs = 13; - repeated configservices.ConfigServiceConfig config_service_configs = 14; - map mobility_configs = 15; - map metadata = 16; - string file = 17; - map options = 18; - repeated Server servers = 19; + repeated emane.GetEmaneModelConfig emane_model_configs = 10; + map wlan_configs = 11; + repeated services.NodeServiceConfig service_configs = 12; + repeated configservices.ConfigServiceConfig config_service_configs = 13; + map mobility_configs = 14; + map metadata = 15; + string file = 16; + map options = 17; + repeated Server servers = 18; } message SessionSummary { From 54ac807a4f629cd8f6defebebacf784826ee1e26 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 12 Jul 2021 10:29:53 -0700 Subject: [PATCH 081/110] grpc: changes to support nodes containing configuration data, allowing for node creation with configs and querying nodes with their configs --- daemon/core/api/grpc/client.py | 93 +-------- daemon/core/api/grpc/grpcutils.py | 183 ++++++++++-------- daemon/core/api/grpc/server.py | 77 +++----- daemon/core/api/grpc/wrappers.py | 141 +++++++++++--- daemon/core/gui/coreclient.py | 13 +- .../core/gui/dialogs/configserviceconfig.py | 6 +- daemon/core/gui/dialogs/nodeconfigservice.py | 8 + daemon/core/gui/dialogs/nodeservice.py | 16 +- daemon/core/nodes/base.py | 1 + daemon/proto/core/api/grpc/core.proto | 37 ++-- daemon/proto/core/api/grpc/emane.proto | 6 + 11 files changed, 290 insertions(+), 291 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 2785a037..6057d655 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -11,16 +11,7 @@ from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tup import grpc -from core.api.grpc import ( - configservices_pb2, - core_pb2, - core_pb2_grpc, - emane_pb2, - mobility_pb2, - services_pb2, - wlan_pb2, - wrappers, -) +from core.api.grpc import core_pb2, core_pb2_grpc, emane_pb2, wrappers from core.api.grpc.configservices_pb2 import ( GetConfigServiceDefaultsRequest, GetNodeConfigServiceRequest, @@ -233,95 +224,17 @@ class CoreGrpcClient: self.proxy: bool = proxy def start_session( - self, - session: wrappers.Session, - asymmetric_links: List[wrappers.Link] = None, - definition: bool = False, + self, session: wrappers.Session, definition: bool = False ) -> Tuple[bool, List[str]]: """ Start a session. :param session: session to start - :param asymmetric_links: link configuration for asymmetric links :param definition: True to only define session data, False to start session :return: tuple of result and exception strings """ - nodes = [x.to_proto() for x in session.nodes.values()] - links = [x.to_proto() for x in session.links] - if asymmetric_links: - asymmetric_links = [x.to_proto() for x in asymmetric_links] - hooks = [x.to_proto() for x in session.hooks.values()] - emane_model_configs = [] - mobility_configs = [] - wlan_configs = [] - service_configs = [] - service_file_configs = [] - config_service_configs = [] - for node in session.nodes.values(): - for key, config in node.emane_model_configs.items(): - model, iface_id = key - config = wrappers.ConfigOption.to_dict(config) - if iface_id is None: - iface_id = -1 - emane_model_config = emane_pb2.EmaneModelConfig( - node_id=node.id, iface_id=iface_id, model=model, config=config - ) - emane_model_configs.append(emane_model_config) - if node.wlan_config: - config = wrappers.ConfigOption.to_dict(node.wlan_config) - wlan_config = wlan_pb2.WlanConfig(node_id=node.id, config=config) - wlan_configs.append(wlan_config) - if node.mobility_config: - config = wrappers.ConfigOption.to_dict(node.mobility_config) - mobility_config = mobility_pb2.MobilityConfig( - node_id=node.id, config=config - ) - mobility_configs.append(mobility_config) - for name, config in node.service_configs.items(): - service_config = services_pb2.ServiceConfig( - node_id=node.id, - service=name, - directories=config.dirs, - files=config.configs, - startup=config.startup, - validate=config.validate, - shutdown=config.shutdown, - ) - service_configs.append(service_config) - for service, file_configs in node.service_file_configs.items(): - for file, data in file_configs.items(): - service_file_config = services_pb2.ServiceFileConfig( - node_id=node.id, service=service, file=file, data=data - ) - service_file_configs.append(service_file_config) - for name, service_config in node.config_service_configs.items(): - config_service_config = configservices_pb2.ConfigServiceConfig( - node_id=node.id, - name=name, - templates=service_config.templates, - config=service_config.config, - ) - config_service_configs.append(config_service_config) - options = {k: v.value for k, v in session.options.items()} - servers = [x.to_proto() for x in session.servers] request = core_pb2.StartSessionRequest( - session_id=session.id, - nodes=nodes, - links=links, - location=session.location.to_proto(), - hooks=hooks, - emane_model_configs=emane_model_configs, - wlan_configs=wlan_configs, - mobility_configs=mobility_configs, - service_configs=service_configs, - service_file_configs=service_file_configs, - asymmetric_links=asymmetric_links, - config_service_configs=config_service_configs, - options=options, - user=session.user, - definition=definition, - metadata=session.metadata, - servers=servers, + session=session.to_proto(), definition=definition ) response = self.stub.StartSession(request) return response.result, list(response.exceptions) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 169819ba..c585a135 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -8,9 +8,8 @@ from grpc import ServicerContext from core import utils from core.api.grpc import common_pb2, core_pb2, wrappers -from core.api.grpc.common_pb2 import MappedConfig from core.api.grpc.configservices_pb2 import ConfigServiceConfig -from core.api.grpc.emane_pb2 import GetEmaneModelConfig +from core.api.grpc.emane_pb2 import NodeEmaneConfig from core.api.grpc.services_pb2 import ( NodeServiceConfig, NodeServiceData, @@ -252,12 +251,15 @@ def get_config_options( return results -def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node: +def get_node_proto( + session: Session, node: NodeBase, emane_configs: List[NodeEmaneConfig] +) -> core_pb2.Node: """ Convert CORE node to protobuf representation. :param session: session containing node :param node: node to convert + :param emane_configs: emane configs related to node :return: node proto """ node_type = session.get_node_type(node.__class__) @@ -283,6 +285,42 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node: image = None if isinstance(node, (DockerNode, LxcNode)): image = node.image + # check for wlan config + wlan_config = session.mobility.get_configs( + node.id, config_type=BasicRangeModel.name + ) + if wlan_config: + wlan_config = get_config_options(wlan_config, BasicRangeModel) + # check for mobility config + mobility_config = session.mobility.get_configs( + node.id, config_type=Ns2ScriptedMobility.name + ) + if mobility_config: + mobility_config = get_config_options(mobility_config, Ns2ScriptedMobility) + # check for service configs + custom_services = session.services.custom_services.get(node.id) + service_configs = {} + if custom_services: + for service in custom_services.values(): + service_proto = get_service_configuration(service) + service_configs[service.name] = NodeServiceConfig( + node_id=node.id, + service=service.name, + data=service_proto, + files=service.config_data, + ) + # check for config service configs + config_service_configs = {} + if isinstance(node, CoreNode): + for service in node.config_services.values(): + if not service.custom_templates and not service.custom_config: + continue + config_service_configs[service.name] = ConfigServiceConfig( + node_id=node.id, + name=service.name, + templates=service.custom_templates, + config=service.custom_config, + ) return core_pb2.Node( id=node.id, name=node.name, @@ -298,6 +336,11 @@ def get_node_proto(session: Session, node: NodeBase) -> core_pb2.Node: dir=node_dir, channel=channel, canvas=node.canvas, + wlan_config=wlan_config, + mobility_config=mobility_config, + service_configs=service_configs, + config_service_configs=config_service_configs, + emane_configs=emane_configs, ) @@ -530,8 +573,8 @@ def get_nem_id( return nem_id -def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]: - configs = [] +def get_emane_model_configs_dict(session: Session) -> Dict[int, List[NodeEmaneConfig]]: + configs = {} for _id, model_configs in session.emane.node_configs.items(): for model_name in model_configs: model_class = session.emane.get_model(model_name) @@ -539,42 +582,11 @@ def get_emane_model_configs(session: Session) -> List[GetEmaneModelConfig]: config = get_config_options(current_config, model_class) node_id, iface_id = utils.parse_iface_config_id(_id) iface_id = iface_id if iface_id is not None else -1 - model_config = GetEmaneModelConfig( - node_id=node_id, model=model_name, iface_id=iface_id, config=config + node_config = NodeEmaneConfig( + model=model_name, iface_id=iface_id, config=config ) - configs.append(model_config) - return configs - - -def get_wlan_configs(session: Session) -> Dict[int, MappedConfig]: - configs = {} - for node_id in session.mobility.node_configurations: - model_config = session.mobility.node_configurations[node_id] - if node_id == -1: - continue - for model_name in model_config: - if model_name != BasicRangeModel.name: - continue - current_config = session.mobility.get_model_config(node_id, model_name) - config = get_config_options(current_config, BasicRangeModel) - mapped_config = MappedConfig(config=config) - configs[node_id] = mapped_config - return configs - - -def get_mobility_configs(session: Session) -> Dict[int, MappedConfig]: - configs = {} - for node_id in session.mobility.node_configurations: - model_config = session.mobility.node_configurations[node_id] - if node_id == -1: - continue - for model_name in model_config: - if model_name != Ns2ScriptedMobility.name: - continue - current_config = session.mobility.get_model_config(node_id, model_name) - config = get_config_options(current_config, Ns2ScriptedMobility) - mapped_config = MappedConfig(config=config) - configs[node_id] = mapped_config + node_configs = configs.setdefault(node_id, []) + node_configs.append(node_config) return configs @@ -596,40 +608,6 @@ def get_default_services(session: Session) -> List[ServiceDefaults]: return default_services -def get_node_service_configs(session: Session) -> List[NodeServiceConfig]: - configs = [] - for node_id, service_configs in session.services.custom_services.items(): - for name in service_configs: - service = session.services.get_service(node_id, name) - service_proto = get_service_configuration(service) - config = NodeServiceConfig( - node_id=node_id, - service=name, - data=service_proto, - files=service.config_data, - ) - configs.append(config) - return configs - - -def get_node_config_service_configs(session: Session) -> List[ConfigServiceConfig]: - configs = [] - for node in session.nodes.values(): - if not isinstance(node, CoreNodeBase): - continue - for name, service in node.config_services.items(): - if not service.custom_templates and not service.custom_config: - continue - config_proto = ConfigServiceConfig( - node_id=node.id, - name=name, - templates=service.custom_templates, - config=service.custom_config, - ) - configs.append(config_proto) - return configs - - def get_mobility_node( session: Session, node_id: int, context: ServicerContext ) -> Union[WlanNode, EmaneNet]: @@ -645,10 +623,12 @@ def get_mobility_node( def convert_session(session: Session) -> wrappers.Session: links = [] nodes = [] + emane_configs = get_emane_model_configs_dict(session) for _id in session.nodes: node = session.nodes[_id] if not isinstance(node, (PtpNet, CtrlNet)): - node_proto = get_node_proto(session, node) + node_emane_configs = emane_configs.get(node.id, []) + node_proto = get_node_proto(session, node, node_emane_configs) nodes.append(node_proto) node_links = get_links(node) links.extend(node_links) @@ -659,11 +639,6 @@ def convert_session(session: Session) -> wrappers.Session: x=x, y=y, z=z, lat=lat, lon=lon, alt=alt, scale=session.location.refscale ) hooks = get_hooks(session) - emane_model_configs = get_emane_model_configs(session) - wlan_configs = get_wlan_configs(session) - mobility_configs = get_mobility_configs(session) - service_configs = get_node_service_configs(session) - config_service_configs = get_node_config_service_configs(session) session_file = str(session.file_path) if session.file_path else None options = get_config_options(session.options.get_configs(), session.options) servers = [ @@ -680,13 +655,51 @@ def convert_session(session: Session) -> wrappers.Session: default_services=default_services, location=location, hooks=hooks, - emane_model_configs=emane_model_configs, - wlan_configs=wlan_configs, - service_configs=service_configs, - config_service_configs=config_service_configs, - mobility_configs=mobility_configs, metadata=session.metadata, file=session_file, options=options, servers=servers, ) + + +def configure_node( + session: Session, node: core_pb2.Node, core_node: NodeBase, context: ServicerContext +) -> None: + for emane_config in node.emane_configs: + _id = utils.iface_config_id(node.id, emane_config.iface_id) + config = {k: v.value for k, v in emane_config.config.items()} + session.emane.set_config(_id, emane_config.model, config) + if node.wlan_config: + config = {k: v.value for k, v in node.wlan_config.items()} + session.mobility.set_model_config(node.id, BasicRangeModel.name, config) + if node.mobility_config: + config = {k: v.value for k, v in node.mobility_config.items()} + session.mobility.set_model_config(node.id, Ns2ScriptedMobility.name, config) + for service_name, service_config in node.service_configs.items(): + data = service_config.data + config = ServiceConfig( + node_id=node.id, + service=service_name, + startup=data.startup, + validate=data.validate, + shutdown=data.shutdown, + files=data.configs, + directories=data.dirs, + ) + service_configuration(session, config) + for file_name, file_data in service_config.files.items(): + session.services.set_service_file( + node.id, service_name, file_name, file_data + ) + if node.config_service_configs: + if not isinstance(core_node, CoreNode): + context.abort( + grpc.StatusCode.INVALID_ARGUMENT, + "invalid node type with config service configs", + ) + for service_name, service_config in node.config_service_configs.items(): + service = core_node.config_services[service_name] + if service_config.config: + service.set_config(service_config.config) + for name, template in service_config.templates.items(): + service.set_template(name, template) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 8b0b903a..dd455b31 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -224,7 +224,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :return: start session response """ logger.debug("start session: %s", request) - session = self.get_session(request.session_id, context) + session = self.get_session(request.session.id, context) # clear previous state and setup for creation session.clear() @@ -234,77 +234,51 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): state = EventTypes.CONFIGURATION_STATE session.directory.mkdir(exist_ok=True) session.set_state(state) - session.user = request.user + session.user = request.session.user # session options session.options.config_reset() - for key, value in request.options.items(): - session.options.set_config(key, value) - session.metadata = dict(request.metadata) + for option in request.session.options.values(): + session.options.set_config(option.name, option.value) + session.metadata = dict(request.session.metadata) # add servers - for server in request.servers: + for server in request.session.servers: session.distributed.add_server(server.name, server.host) # location - if request.HasField("location"): - grpcutils.session_location(session, request.location) + if request.session.HasField("location"): + grpcutils.session_location(session, request.session.location) # add all hooks - for hook in request.hooks: + for hook in request.session.hooks: state = EventTypes(hook.state) session.add_hook(state, hook.file, hook.data) # create nodes - _, exceptions = grpcutils.create_nodes(session, request.nodes) + _, exceptions = grpcutils.create_nodes(session, request.session.nodes) if exceptions: exceptions = [str(x) for x in exceptions] return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) - # emane configs - for config in request.emane_model_configs: - _id = utils.iface_config_id(config.node_id, config.iface_id) - session.emane.set_config(_id, config.model, config.config) - - # wlan configs - for config in request.wlan_configs: - session.mobility.set_model_config( - config.node_id, BasicRangeModel.name, config.config - ) - - # mobility configs - for config in request.mobility_configs: - session.mobility.set_model_config( - config.node_id, Ns2ScriptedMobility.name, config.config - ) - - # service configs - for config in request.service_configs: - grpcutils.service_configuration(session, config) - - # service file configs - for config in request.service_file_configs: - session.services.set_service_file( - config.node_id, config.service, config.file, config.data - ) - - # config service configs - for config in request.config_service_configs: - node = self.get_node(session, config.node_id, context, CoreNode) - service = node.config_services[config.name] - if config.config: - service.set_config(config.config) - for name, template in config.templates.items(): - service.set_template(name, template) + # check for configurations + for node in request.session.nodes: + core_node = self.get_node(session, node.id, context, NodeBase) + grpcutils.configure_node(session, node, core_node, context) # create links - _, exceptions = grpcutils.create_links(session, request.links) + links = [] + asym_links = [] + for link in request.session.links: + if link.options.unidirectional: + asym_links.append(link) + else: + links.append(link) + _, exceptions = grpcutils.create_links(session, links) if exceptions: exceptions = [str(x) for x in exceptions] return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) - - # asymmetric links - _, exceptions = grpcutils.edit_links(session, request.asymmetric_links) + _, exceptions = grpcutils.edit_links(session, asym_links) if exceptions: exceptions = [str(x) for x in exceptions] return core_pb2.StartSessionResponse(result=False, exceptions=exceptions) @@ -541,6 +515,7 @@ 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) + grpcutils.configure_node(session, request.node, node, context) source = request.source if request.source else None session.broadcast_node(node, MessageFlags.ADD, source) return core_pb2.AddNodeResponse(node_id=node.id) @@ -563,7 +538,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): iface = node.ifaces[iface_id] iface_proto = grpcutils.iface_to_proto(request.node_id, iface) ifaces.append(iface_proto) - node_proto = grpcutils.get_node_proto(session, node) + emane_configs = grpcutils.get_emane_model_configs_dict(session) + node_emane_configs = emane_configs.get(node.id, []) + node_proto = grpcutils.get_node_proto(session, node, node_emane_configs) links = get_links(node) return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links) diff --git a/daemon/core/api/grpc/wrappers.py b/daemon/core/api/grpc/wrappers.py index 802af3c3..ffeb6793 100644 --- a/daemon/core/api/grpc/wrappers.py +++ b/daemon/core/api/grpc/wrappers.py @@ -239,12 +239,26 @@ class NodeServiceData: configs=proto.configs, startup=proto.startup, validate=proto.validate, - validation_mode=proto.validation_mode, + validation_mode=ServiceValidationMode(proto.validation_mode), validation_timer=proto.validation_timer, shutdown=proto.shutdown, meta=proto.meta, ) + def to_proto(self) -> services_pb2.NodeServiceData: + return services_pb2.NodeServiceData( + executables=self.executables, + dependencies=self.dependencies, + dirs=self.dirs, + configs=self.configs, + startup=self.startup, + validate=self.validate, + validation_mode=self.validation_mode.value, + validation_timer=self.validation_timer, + shutdown=self.shutdown, + meta=self.meta, + ) + @dataclass class NodeServiceConfig: @@ -430,15 +444,27 @@ class ConfigOption: @classmethod def from_proto(cls, proto: common_pb2.ConfigOption) -> "ConfigOption": + config_type = ConfigOptionType(proto.type) if proto.type is not None else None return ConfigOption( label=proto.label, name=proto.name, value=proto.value, - type=ConfigOptionType(proto.type), + type=config_type, group=proto.group, select=proto.select, ) + def to_proto(self) -> common_pb2.ConfigOption: + config_type = self.type.value if self.type is not None else None + return common_pb2.ConfigOption( + label=self.label, + name=self.name, + value=self.value, + type=config_type, + select=self.select, + group=self.group, + ) + @dataclass class Interface: @@ -714,6 +740,23 @@ class Node: @classmethod def from_proto(cls, proto: core_pb2.Node) -> "Node": + service_configs = {} + service_file_configs = {} + for service, node_config in proto.service_configs.items(): + service_configs[service] = NodeServiceData.from_proto(node_config.data) + service_file_configs[service] = dict(node_config.files) + emane_configs = {} + for emane_config in proto.emane_configs: + iface_id = None if emane_config.iface_id == -1 else emane_config.iface_id + model = emane_config.model + key = (model, iface_id) + emane_configs[key] = ConfigOption.from_dict(emane_config.config) + config_service_configs = {} + for service, service_config in proto.config_service_configs.items(): + config_service_configs[service] = ConfigServiceData( + templates=dict(service_config.templates), + config=dict(service_config.config), + ) return Node( id=proto.id, name=proto.name, @@ -730,9 +773,43 @@ class Node: dir=proto.dir, channel=proto.channel, canvas=proto.canvas, + wlan_config=ConfigOption.from_dict(proto.wlan_config), + mobility_config=ConfigOption.from_dict(proto.mobility_config), + service_configs=service_configs, + service_file_configs=service_file_configs, + config_service_configs=config_service_configs, + emane_model_configs=emane_configs, ) def to_proto(self) -> core_pb2.Node: + emane_configs = [] + for key, config in self.emane_model_configs.items(): + model, iface_id = key + if iface_id is None: + iface_id = -1 + config = {k: v.to_proto() for k, v in config.items()} + emane_config = emane_pb2.NodeEmaneConfig( + iface_id=iface_id, model=model, config=config + ) + emane_configs.append(emane_config) + service_configs = {} + for service, service_data in self.service_configs.items(): + service_configs[service] = services_pb2.NodeServiceConfig( + service=service, data=service_data.to_proto() + ) + for service, file_configs in self.service_file_configs.items(): + service_config = service_configs.get(service) + if service_config: + service_config.files.update(file_configs) + else: + service_configs[service] = services_pb2.NodeServiceConfig( + service=service, files=file_configs + ) + config_service_configs = {} + for service, service_config in self.config_service_configs.items(): + config_service_configs[service] = configservices_pb2.ConfigServiceConfig( + templates=service_config.templates, config=service_config.config + ) return core_pb2.Node( id=self.id, name=self.name, @@ -748,6 +825,11 @@ class Node: dir=self.dir, channel=self.channel, canvas=self.canvas, + wlan_config={k: v.to_proto() for k, v in self.wlan_config.items()}, + mobility_config={k: v.to_proto() for k, v in self.mobility_config.items()}, + service_configs=service_configs, + config_service_configs=config_service_configs, + emane_configs=emane_configs, ) def set_wlan(self, config: Dict[str, str]) -> None: @@ -796,32 +878,6 @@ class Session: x.node_type: set(x.services) for x in proto.default_services } hooks = {x.file: Hook.from_proto(x) for x in proto.hooks} - # update nodes with their current configurations - for model in proto.emane_model_configs: - iface_id = None - if model.iface_id != -1: - iface_id = model.iface_id - node = nodes[model.node_id] - key = (model.model, iface_id) - node.emane_model_configs[key] = ConfigOption.from_dict(model.config) - for node_id, mapped_config in proto.wlan_configs.items(): - node = nodes[node_id] - node.wlan_config = ConfigOption.from_dict(mapped_config.config) - for config in proto.service_configs: - service = config.service - node = nodes[config.node_id] - node.service_configs[service] = NodeServiceData.from_proto(config.data) - for file, data in config.files.items(): - files = node.service_file_configs.setdefault(service, {}) - files[file] = data - for config in proto.config_service_configs: - node = nodes[config.node_id] - node.config_service_configs[config.name] = ConfigServiceData( - templates=dict(config.templates), config=dict(config.config) - ) - for node_id, mapped_config in proto.mobility_configs.items(): - node = nodes[node_id] - node.mobility_config = ConfigOption.from_dict(mapped_config.config) file_path = Path(proto.file) if proto.file else None options = ConfigOption.from_dict(proto.options) servers = [Server.from_proto(x) for x in proto.servers] @@ -841,6 +897,35 @@ class Session: servers=servers, ) + def to_proto(self) -> core_pb2.Session: + nodes = [x.to_proto() for x in self.nodes.values()] + links = [x.to_proto() for x in self.links] + hooks = [x.to_proto() for x in self.hooks.values()] + options = {k: v.to_proto() for k, v in self.options.items()} + servers = [x.to_proto() for x in self.servers] + default_services = [] + for node_type, services in self.default_services.items(): + default_service = services_pb2.ServiceDefaults( + node_type=node_type, services=services + ) + default_services.append(default_service) + file = str(self.file) if self.file else None + return core_pb2.Session( + id=self.id, + state=self.state.value, + nodes=nodes, + links=links, + dir=self.dir, + user=self.user, + default_services=default_services, + location=self.location.to_proto(), + hooks=hooks, + metadata=self.metadata, + file=file, + options=options, + servers=servers, + ) + def add_node( self, _id: int, diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 059266bc..ef550785 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -439,7 +439,7 @@ class CoreClient: def get_links(self, definition: bool = False) -> Tuple[List[Link], List[Link]]: if not definition: self.ifaces_manager.set_macs([x.link for x in self.links.values()]) - links, asym_links = [], [] + links = [] for edge in self.links.values(): link = edge.link if not definition: @@ -449,12 +449,11 @@ class CoreClient: link.iface2.mac = self.ifaces_manager.next_mac() links.append(link) if edge.asymmetric_link: - asym_links.append(edge.asymmetric_link) - return links, asym_links + links.append(edge.asymmetric_link) + return links def start_session(self, definition: bool = False) -> Tuple[bool, List[str]]: - links, asym_links = self.get_links(definition) - self.session.links = links + self.session.links = self.get_links(definition) self.session.metadata = self.get_metadata() self.session.servers.clear() for server in self.servers.values(): @@ -462,9 +461,7 @@ class CoreClient: result = False exceptions = [] try: - result, exceptions = self.client.start_session( - self.session, asym_links, definition - ) + result, exceptions = self.client.start_session(self.session, definition) logger.info( "start session(%s) definition(%s), result: %s", self.session.id, diff --git a/daemon/core/gui/dialogs/configserviceconfig.py b/daemon/core/gui/dialogs/configserviceconfig.py index 4935ddda..870f9639 100644 --- a/daemon/core/gui/dialogs/configserviceconfig.py +++ b/daemon/core/gui/dialogs/configserviceconfig.py @@ -310,9 +310,9 @@ class ConfigServiceConfigDialog(Dialog): current_listbox.itemconfig(current_listbox.curselection()[0], bg="") self.destroy() return - service_config = self.node.config_service_configs.get(self.service_name) - if not service_config: - service_config = ConfigServiceData() + service_config = self.node.config_service_configs.setdefault( + self.service_name, ConfigServiceData() + ) if self.config_frame: self.config_frame.parse_config() service_config.config = {x.name: x.value for x in self.config.values()} diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index de97ea15..7b55fba4 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -31,6 +31,7 @@ class NodeConfigServiceDialog(Dialog): if services is None: services = set(node.config_services) self.current_services: Set[str] = services + self.protocol("WM_DELETE_WINDOW", self.click_cancel) self.draw() def draw(self) -> None: @@ -131,13 +132,20 @@ class NodeConfigServiceDialog(Dialog): if self.is_custom_service(name): self.current.listbox.itemconfig(tk.END, bg="green") + def cleanup_custom_services(self) -> None: + for service in list(self.node.config_service_configs): + if service not in self.node.config_services: + self.node.config_service_configs.pop(service) + def click_save(self) -> None: self.node.config_services = self.current_services.copy() + self.cleanup_custom_services() logger.info("saved node config services: %s", self.node.config_services) self.destroy() def click_cancel(self) -> None: self.current_services = None + self.cleanup_custom_services() self.destroy() def click_remove(self) -> None: diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index f27f9cf5..dd9b4419 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -25,6 +25,7 @@ class NodeServiceDialog(Dialog): self.current: Optional[ListboxScroll] = None services = set(node.services) self.current_services: Set[str] = services + self.protocol("WM_DELETE_WINDOW", self.click_cancel) self.draw() def draw(self) -> None: @@ -77,7 +78,7 @@ class NodeServiceDialog(Dialog): button.grid(row=0, column=1, sticky=tk.EW, padx=PADX) button = ttk.Button(frame, text="Remove", command=self.click_remove) button.grid(row=0, column=2, sticky=tk.EW, padx=PADX) - button = ttk.Button(frame, text="Cancel", command=self.destroy) + button = ttk.Button(frame, text="Cancel", command=self.click_cancel) button.grid(row=0, column=3, sticky=tk.EW) # trigger group change @@ -125,8 +126,21 @@ class NodeServiceDialog(Dialog): "Service Configuration", "Select a service to configure", parent=self ) + def cleanup_custom_services(self) -> None: + for service in list(self.node.service_configs): + if service not in self.node.services: + self.node.service_configs.pop(service) + for service in list(self.node.service_file_configs): + if service not in self.node.services: + self.node.service_file_configs.pop(service) + + def click_cancel(self) -> None: + self.cleanup_custom_services() + self.destroy() + def click_save(self) -> None: self.node.services = self.current_services.copy() + self.cleanup_custom_services() self.destroy() def click_remove(self) -> None: diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 926f4d7e..960ec056 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -655,6 +655,7 @@ class CoreNode(CoreNodeBase): :param dir_path: path to create :return: nothing """ + logger.info("creating private directory: %s", dir_path) if not str(dir_path).startswith("/"): raise CoreError(f"private directory path not fully qualified: {dir_path}") host_path = self.host_path(dir_path, is_dir=True) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 87cb722b..3fb55f73 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -135,23 +135,8 @@ message GetConfigResponse { message StartSessionRequest { - int32 session_id = 1; - repeated Node nodes = 2; - repeated Link links = 3; - repeated Hook hooks = 4; - SessionLocation location = 5; - repeated wlan.WlanConfig wlan_configs = 6; - repeated emane.EmaneModelConfig emane_model_configs = 7; - repeated mobility.MobilityConfig mobility_configs = 8; - repeated services.ServiceConfig service_configs = 9; - repeated services.ServiceFileConfig service_file_configs = 10; - repeated Link asymmetric_links = 11; - repeated configservices.ConfigServiceConfig config_service_configs = 12; - map options = 13; - string user = 14; - bool definition = 15; - map metadata = 16; - repeated Server servers = 17; + Session session = 1; + bool definition = 2; } message StartSessionResponse { @@ -577,15 +562,10 @@ message Session { repeated services.ServiceDefaults default_services = 7; SessionLocation location = 8; repeated Hook hooks = 9; - repeated emane.GetEmaneModelConfig emane_model_configs = 10; - map wlan_configs = 11; - repeated services.NodeServiceConfig service_configs = 12; - repeated configservices.ConfigServiceConfig config_service_configs = 13; - map mobility_configs = 14; - map metadata = 15; - string file = 16; - map options = 17; - repeated Server servers = 18; + map metadata = 10; + string file = 11; + map options = 12; + repeated Server servers = 13; } message SessionSummary { @@ -612,6 +592,11 @@ message Node { string dir = 13; string channel = 14; int32 canvas = 15; + map wlan_config = 16; + map mobility_config = 17; + map service_configs = 18; + map config_service_configs= 19; + repeated emane.NodeEmaneConfig emane_configs = 20; } message Link { diff --git a/daemon/proto/core/api/grpc/emane.proto b/daemon/proto/core/api/grpc/emane.proto index 5aa0c952..b8579917 100644 --- a/daemon/proto/core/api/grpc/emane.proto +++ b/daemon/proto/core/api/grpc/emane.proto @@ -31,6 +31,12 @@ message GetEmaneModelConfig { map config = 4; } +message NodeEmaneConfig { + int32 iface_id = 1; + string model = 2; + map config = 3; +} + message GetEmaneEventChannelRequest { int32 session_id = 1; int32 nem_id = 2; From 4879d6e2974e14005058446767a4fa16977ce273 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 12 Jul 2021 11:01:36 -0700 Subject: [PATCH 082/110] pygui: small cleanup for removing service configuration when they are removed/toggled --- daemon/core/gui/dialogs/nodeconfigservice.py | 9 ++------- daemon/core/gui/dialogs/nodeservice.py | 14 ++++---------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/daemon/core/gui/dialogs/nodeconfigservice.py b/daemon/core/gui/dialogs/nodeconfigservice.py index 7b55fba4..cddbdb03 100644 --- a/daemon/core/gui/dialogs/nodeconfigservice.py +++ b/daemon/core/gui/dialogs/nodeconfigservice.py @@ -103,6 +103,7 @@ class NodeConfigServiceDialog(Dialog): self.current_services.add(name) elif not var.get() and name in self.current_services: self.current_services.remove(name) + self.node.config_service_configs.pop(name, None) self.draw_current_services() self.node.config_services = self.current_services.copy() @@ -132,20 +133,13 @@ class NodeConfigServiceDialog(Dialog): if self.is_custom_service(name): self.current.listbox.itemconfig(tk.END, bg="green") - def cleanup_custom_services(self) -> None: - for service in list(self.node.config_service_configs): - if service not in self.node.config_services: - self.node.config_service_configs.pop(service) - def click_save(self) -> None: self.node.config_services = self.current_services.copy() - self.cleanup_custom_services() logger.info("saved node config services: %s", self.node.config_services) self.destroy() def click_cancel(self) -> None: self.current_services = None - self.cleanup_custom_services() self.destroy() def click_remove(self) -> None: @@ -154,6 +148,7 @@ class NodeConfigServiceDialog(Dialog): service = self.current.listbox.get(cur[0]) self.current.listbox.delete(cur[0]) self.current_services.remove(service) + self.node.config_service_configs.pop(service, None) for checkbutton in self.services.frame.winfo_children(): if checkbutton["text"] == service: checkbutton.invoke() diff --git a/daemon/core/gui/dialogs/nodeservice.py b/daemon/core/gui/dialogs/nodeservice.py index dd9b4419..431d5c3d 100644 --- a/daemon/core/gui/dialogs/nodeservice.py +++ b/daemon/core/gui/dialogs/nodeservice.py @@ -99,6 +99,8 @@ class NodeServiceDialog(Dialog): self.current_services.add(name) elif not var.get() and name in self.current_services: self.current_services.remove(name) + self.node.service_configs.pop(name, None) + self.node.service_file_configs.pop(name, None) self.current.listbox.delete(0, tk.END) for name in sorted(self.current_services): self.current.listbox.insert(tk.END, name) @@ -126,21 +128,11 @@ class NodeServiceDialog(Dialog): "Service Configuration", "Select a service to configure", parent=self ) - def cleanup_custom_services(self) -> None: - for service in list(self.node.service_configs): - if service not in self.node.services: - self.node.service_configs.pop(service) - for service in list(self.node.service_file_configs): - if service not in self.node.services: - self.node.service_file_configs.pop(service) - def click_cancel(self) -> None: - self.cleanup_custom_services() self.destroy() def click_save(self) -> None: self.node.services = self.current_services.copy() - self.cleanup_custom_services() self.destroy() def click_remove(self) -> None: @@ -149,6 +141,8 @@ class NodeServiceDialog(Dialog): service = self.current.listbox.get(cur[0]) self.current.listbox.delete(cur[0]) self.current_services.remove(service) + self.node.service_configs.pop(service, None) + self.node.service_file_configs.pop(service, None) for checkbutton in self.services.frame.winfo_children(): if checkbutton["text"] == service: checkbutton.invoke() From 8d303bdc2afebe6554b321cce7c200fd1a8ea211 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 14 Jul 2021 13:45:49 -0700 Subject: [PATCH 083/110] pygui: changes to allow adding directory paths that do not exist within gui for customizing services --- daemon/core/gui/dialogs/serviceconfig.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/daemon/core/gui/dialogs/serviceconfig.py b/daemon/core/gui/dialogs/serviceconfig.py index 16c3374e..6f6b0d24 100644 --- a/daemon/core/gui/dialogs/serviceconfig.py +++ b/daemon/core/gui/dialogs/serviceconfig.py @@ -1,7 +1,7 @@ import logging import tkinter as tk from pathlib import Path -from tkinter import filedialog, ttk +from tkinter import filedialog, messagebox, ttk from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple import grpc @@ -576,12 +576,13 @@ class ServiceConfigDialog(Dialog): self.directory_entry.insert("end", d) def add_directory(self) -> None: - directory = self.directory_entry.get() - directory = Path(directory) - if directory.is_dir(): + directory = Path(self.directory_entry.get()) + if directory.is_absolute(): if str(directory) not in self.temp_directories: self.dir_list.listbox.insert("end", directory) self.temp_directories.append(str(directory)) + else: + messagebox.showerror("Add Directory", "Path must be absolute!", parent=self) def remove_directory(self) -> None: d = self.directory_entry.get() From 13478392004660b8e36a488f6a36533fcc43ff0e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 14 Jul 2021 14:44:23 -0700 Subject: [PATCH 084/110] pygui: adjustments to have save as update the current session to the newly defined file, saves will then continue to use the new save as name --- daemon/core/gui/coreclient.py | 21 +++++++++++++++------ daemon/core/gui/menubar.py | 14 ++++++++------ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index ef550785..3c4690c3 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -292,14 +292,17 @@ class CoreClient: logger.info("exception event: %s", event) self.app.statusbar.add_alert(event) + def update_session_title(self) -> None: + title_file = self.session.file.name if self.session.file else "" + self.master.title(f"CORE Session({self.session.id}) {title_file}") + def join_session(self, session_id: int) -> None: logger.info("joining session(%s)", session_id) self.reset() try: self.session = self.client.get_session(session_id) self.session.user = self.user - title_file = self.session.file.name if self.session.file else "" - self.master.title(f"CORE Session({self.session.id}) {title_file}") + self.update_session_title() self.handling_events = self.client.events( self.session.id, self.handle_events ) @@ -542,15 +545,16 @@ class CoreClient: def get_xml_dir(self) -> str: return str(self.session.file.parent) if self.session.file else str(XMLS_PATH) - def save_xml(self, file_path: str = None) -> None: + def save_xml(self, file_path: Path = None) -> bool: """ Save core session as to an xml file """ if not file_path and not self.session.file: logger.error("trying to save xml for session with no file") - return + return False if not file_path: - file_path = str(self.session.file) + file_path = self.session.file + result = False try: if not self.is_runtime(): logger.debug("sending session data to the daemon") @@ -562,10 +566,15 @@ class CoreClient: "Failed to define session", message, ) - self.client.save_xml(self.session.id, file_path) + self.client.save_xml(self.session.id, str(file_path)) + if self.session.file != file_path: + self.session.file = file_path + self.update_session_title() logger.info("saved xml file %s", file_path) + result = True except grpc.RpcError as e: self.app.show_grpc_exception("Save XML Error", e) + return result def open_xml(self, file_path: Path) -> None: """ diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index e53c8915..5b64ad78 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -78,7 +78,7 @@ class Menubar(tk.Menu): self.app.bind_all("", lambda e: self.click_new()) menu.add_command(label="Save", accelerator="Ctrl+S", command=self.click_save) self.app.bind_all("", self.click_save) - menu.add_command(label="Save As...", command=self.click_save_xml) + menu.add_command(label="Save As...", command=self.click_save_as) menu.add_command( label="Open...", command=self.click_open_xml, accelerator="Ctrl+O" ) @@ -290,11 +290,12 @@ class Menubar(tk.Menu): def click_save(self, _event: tk.Event = None) -> None: if self.core.session.file: - self.core.save_xml() + if self.core.save_xml(): + self.add_recent_file_to_gui_config(self.core.session.file) else: - self.click_save_xml() + self.click_save_as() - def click_save_xml(self, _event: tk.Event = None) -> None: + def click_save_as(self, _event: tk.Event = None) -> None: init_dir = self.core.get_xml_dir() file_path = filedialog.asksaveasfilename( initialdir=init_dir, @@ -303,8 +304,9 @@ class Menubar(tk.Menu): defaultextension=".xml", ) if file_path: - self.add_recent_file_to_gui_config(file_path) - self.core.save_xml(file_path) + file_path = Path(file_path) + if self.core.save_xml(file_path): + self.add_recent_file_to_gui_config(file_path) def click_open_xml(self, _event: tk.Event = None) -> None: init_dir = self.core.get_xml_dir() From aa5bb08a1670a4d807a05d049e84c4976b927056 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 14 Jul 2021 16:39:18 -0700 Subject: [PATCH 085/110] grpc: fixed issue for start session not attempting to create directory when using definition --- daemon/core/api/grpc/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index dd455b31..ed77ceeb 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -232,7 +232,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): state = EventTypes.DEFINITION_STATE else: state = EventTypes.CONFIGURATION_STATE - session.directory.mkdir(exist_ok=True) + session.directory.mkdir(exist_ok=True) session.set_state(state) session.user = request.session.user From 150d4bd6ea54a0a1b8a0d8bc0eabef8a3d6c290f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 15 Jul 2021 11:26:36 -0700 Subject: [PATCH 086/110] pygui: added option to exit after directory creation --- daemon/scripts/core-gui | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/daemon/scripts/core-gui b/daemon/scripts/core-gui index 7bb44125..ff7795a3 100755 --- a/daemon/scripts/core-gui +++ b/daemon/scripts/core-gui @@ -6,17 +6,21 @@ from logging.handlers import TimedRotatingFileHandler from core.gui import appconfig, images from core.gui.app import Application -if __name__ == "__main__": + +def main() -> None: # parse flags parser = argparse.ArgumentParser(description=f"CORE Python GUI") parser.add_argument("-l", "--level", choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], default="INFO", help="logging level") parser.add_argument("-p", "--proxy", action="store_true", help="enable proxy") parser.add_argument("-s", "--session", type=int, help="session id to join") + parser.add_argument("--create-dir", action="store_true", help="create gui directory and exit") args = parser.parse_args() # check home directory exists and create if necessary appconfig.check_directory() + if args.create_dir: + return # setup logging log_format = "%(asctime)s - %(levelname)s - %(module)s:%(funcName)s - %(message)s" @@ -30,3 +34,7 @@ if __name__ == "__main__": images.load_all() app = Application(args.proxy, args.session) app.mainloop() + + +if __name__ == "__main__": + main() From 631cbbc73e3418be44da377c918f2fcbe1af5f8f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 15 Jul 2021 16:42:19 -0700 Subject: [PATCH 087/110] pygui: add configuration and ip options to enable/disable ip4/ip6 assignment, improved gui configuration to handle new fields that do not already exist --- daemon/core/gui/appconfig.py | 33 ++++++++++++----------------- daemon/core/gui/dialogs/ipdialog.py | 33 +++++++++++++++++++++++------ daemon/core/gui/interface.py | 15 +++++++++---- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/daemon/core/gui/appconfig.py b/daemon/core/gui/appconfig.py index 8ce7b2b0..04f2fdcb 100644 --- a/daemon/core/gui/appconfig.py +++ b/daemon/core/gui/appconfig.py @@ -120,25 +120,18 @@ class IpConfigs(yaml.YAMLObject): yaml_tag: str = "!IpConfigs" yaml_loader: Type[yaml.SafeLoader] = yaml.SafeLoader - def __init__( - self, - ip4: str = None, - ip6: str = None, - ip4s: List[str] = None, - ip6s: List[str] = None, - ) -> None: - if ip4s is None: - ip4s = ["10.0.0.0", "192.168.0.0", "172.16.0.0"] - self.ip4s: List[str] = ip4s - if ip6s is None: - ip6s = ["2001::", "2002::", "a::"] - self.ip6s: List[str] = ip6s - if ip4 is None: - ip4 = self.ip4s[0] - self.ip4: str = ip4 - if ip6 is None: - ip6 = self.ip6s[0] - self.ip6: str = ip6 + def __init__(self, **kwargs) -> None: + self.__setstate__(kwargs) + + def __setstate__(self, kwargs): + self.ip4s: List[str] = kwargs.get( + "ip4s", ["10.0.0.0", "192.168.0.0", "172.16.0.0"] + ) + self.ip4: str = kwargs.get("ip4", self.ip4s[0]) + self.ip6s: List[str] = kwargs.get("ip6s", ["2001::", "2002::", "a::"]) + self.ip6: str = kwargs.get("ip6", self.ip6s[0]) + self.enable_ip4: bool = kwargs.get("enable_ip4", True) + self.enable_ip6: bool = kwargs.get("enable_ip6", True) class GuiConfig(yaml.YAMLObject): @@ -223,7 +216,7 @@ def check_directory() -> None: def read() -> GuiConfig: with CONFIG_PATH.open("r") as f: - return yaml.load(f, Loader=yaml.SafeLoader) + return yaml.safe_load(f) def save(config: GuiConfig) -> None: diff --git a/daemon/core/gui/dialogs/ipdialog.py b/daemon/core/gui/dialogs/ipdialog.py index a09ca097..68b5ab36 100644 --- a/daemon/core/gui/dialogs/ipdialog.py +++ b/daemon/core/gui/dialogs/ipdialog.py @@ -23,6 +23,8 @@ class IpConfigDialog(Dialog): self.ip4_listbox: Optional[ListboxScroll] = None self.ip6_entry: Optional[ttk.Entry] = None self.ip6_listbox: Optional[ListboxScroll] = None + self.enable_ip4 = tk.BooleanVar(value=self.app.guiconfig.ips.enable_ip4) + self.enable_ip6 = tk.BooleanVar(value=self.app.guiconfig.ips.enable_ip6) self.draw() def draw(self) -> None: @@ -36,10 +38,19 @@ class IpConfigDialog(Dialog): frame.rowconfigure(0, weight=1) frame.grid(sticky=tk.NSEW, pady=PADY) + ip4_checkbox = ttk.Checkbutton( + frame, text="Enable IP4?", variable=self.enable_ip4 + ) + ip4_checkbox.grid(row=0, column=0, sticky=tk.EW) + ip6_checkbox = ttk.Checkbutton( + frame, text="Enable IP6?", variable=self.enable_ip6 + ) + ip6_checkbox.grid(row=0, column=1, sticky=tk.EW) + ip4_frame = ttk.LabelFrame(frame, text="IPv4", padding=FRAME_PAD) ip4_frame.columnconfigure(0, weight=1) - ip4_frame.rowconfigure(0, weight=1) - ip4_frame.grid(row=0, column=0, stick="nsew") + ip4_frame.rowconfigure(1, weight=1) + ip4_frame.grid(row=1, column=0, stick=tk.NSEW) self.ip4_listbox = ListboxScroll(ip4_frame) self.ip4_listbox.listbox.bind("<>", self.select_ip4) self.ip4_listbox.grid(sticky=tk.NSEW, pady=PADY) @@ -63,7 +74,7 @@ class IpConfigDialog(Dialog): ip6_frame = ttk.LabelFrame(frame, text="IPv6", padding=FRAME_PAD) ip6_frame.columnconfigure(0, weight=1) ip6_frame.rowconfigure(0, weight=1) - ip6_frame.grid(row=0, column=1, stick="nsew") + ip6_frame.grid(row=1, column=1, stick=tk.NSEW) self.ip6_listbox = ListboxScroll(ip6_frame) self.ip6_listbox.listbox.bind("<>", self.select_ip6) self.ip6_listbox.grid(sticky=tk.NSEW, pady=PADY) @@ -86,7 +97,7 @@ class IpConfigDialog(Dialog): # draw buttons frame = ttk.Frame(self.top) - frame.grid(stick="ew") + frame.grid(stick=tk.EW) for i in range(2): frame.columnconfigure(i, weight=1) button = ttk.Button(frame, text="Save", command=self.click_save) @@ -142,10 +153,18 @@ class IpConfigDialog(Dialog): ip6 = self.ip6_listbox.listbox.get(index) ip6s.append(ip6) ip_config = self.app.guiconfig.ips - ip_config.ip4 = self.ip4 - ip_config.ip6 = self.ip6 + ip_changed = False + if ip_config.ip4 != self.ip4: + ip_config.ip4 = self.ip4 + ip_changed = True + if ip_config.ip6 != self.ip6: + ip_config.ip6 = self.ip6 + ip_changed = True ip_config.ip4s = ip4s ip_config.ip6s = ip6s - self.app.core.ifaces_manager.update_ips(self.ip4, self.ip6) + ip_config.enable_ip4 = self.enable_ip4.get() + ip_config.enable_ip6 = self.enable_ip6.get() + if ip_changed: + self.app.core.ifaces_manager.update_ips(self.ip4, self.ip6) self.app.save_config() self.destroy() diff --git a/daemon/core/gui/interface.py b/daemon/core/gui/interface.py index 8076e202..d4d09443 100644 --- a/daemon/core/gui/interface.py +++ b/daemon/core/gui/interface.py @@ -160,11 +160,18 @@ class InterfaceManager: index += 1 return index - def get_ips(self, node: Node) -> [str, str]: + def get_ips(self, node: Node) -> [Optional[str], Optional[str]]: + enable_ip4 = self.app.guiconfig.ips.enable_ip4 + enable_ip6 = self.app.guiconfig.ips.enable_ip6 + ip4, ip6 = None, None + if not enable_ip4 and not enable_ip6: + return ip4, ip6 index = self.next_index(node) - ip4 = self.current_subnets.ip4[index] - ip6 = self.current_subnets.ip6[index] - return str(ip4), str(ip6) + if enable_ip4: + ip4 = str(self.current_subnets.ip4[index]) + if enable_ip6: + ip6 = str(self.current_subnets.ip6[index]) + return ip4, ip6 def get_subnets(self, iface: Interface) -> Subnets: ip4_subnet = self.ip4_subnets From 8e905b6a379d8399e32d340b3f97b92dc67bed8e Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 26 Aug 2021 10:42:02 -0700 Subject: [PATCH 088/110] pygui: updates to improve draw order when joining a session, should help avoid some undesired visuals while reading in bigger scenarios --- daemon/core/gui/coreclient.py | 42 +------------------ daemon/core/gui/graph/manager.py | 69 ++++++++++++++++++++++++++++---- 2 files changed, 63 insertions(+), 48 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 3c4690c3..65d51fb0 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -43,7 +43,6 @@ from core.gui.dialogs.mobilityplayer import MobilityPlayer from core.gui.dialogs.sessions import SessionsDialog from core.gui.graph.edges import CanvasEdge from core.gui.graph.node import CanvasNode -from core.gui.graph.shape import Shape from core.gui.interface import InterfaceManager from core.gui.nodeutils import NodeDraw @@ -317,45 +316,6 @@ class CoreClient: def is_runtime(self) -> bool: return self.session and self.session.state == SessionState.RUNTIME - def parse_metadata(self) -> None: - # canvas setting - config = self.session.metadata - canvas_config = config.get("canvas") - logger.debug("canvas metadata: %s", canvas_config) - if canvas_config: - canvas_config = json.loads(canvas_config) - self.app.manager.parse_metadata(canvas_config) - - # load saved shapes - shapes_config = config.get("shapes") - if shapes_config: - shapes_config = json.loads(shapes_config) - for shape_config in shapes_config: - logger.debug("loading shape: %s", shape_config) - Shape.from_metadata(self.app, shape_config) - - # load edges config - edges_config = config.get("edges") - if edges_config: - edges_config = json.loads(edges_config) - logger.info("edges config: %s", edges_config) - for edge_config in edges_config: - edge = self.links[edge_config["token"]] - edge.width = edge_config["width"] - edge.color = edge_config["color"] - edge.redraw() - - # read hidden nodes - hidden = config.get("hidden") - if hidden: - hidden = json.loads(hidden) - for _id in hidden: - canvas_node = self.canvas_nodes.get(_id) - if canvas_node: - canvas_node.hide() - else: - logger.warning("invalid node to hide: %s", _id) - def create_new_session(self) -> None: """ Create a new session @@ -439,7 +399,7 @@ class CoreClient: except grpc.RpcError as e: self.app.show_grpc_exception("Edit Node Error", e) - def get_links(self, definition: bool = False) -> Tuple[List[Link], List[Link]]: + def get_links(self, definition: bool = False) -> List[Link]: if not definition: self.ifaces_manager.set_macs([x.link for x in self.links.values()]) links = [] diff --git a/daemon/core/gui/graph/manager.py b/daemon/core/gui/graph/manager.py index 7a2c0ed8..08352a25 100644 --- a/daemon/core/gui/graph/manager.py +++ b/daemon/core/gui/graph/manager.py @@ -1,3 +1,4 @@ +import json import logging import tkinter as tk from copy import deepcopy @@ -16,6 +17,7 @@ from core.gui.graph.edges import ( from core.gui.graph.enums import GraphMode from core.gui.graph.graph import CanvasGraph from core.gui.graph.node import CanvasNode +from core.gui.graph.shape import Shape from core.gui.graph.shapeutils import ShapeType from core.gui.nodeutils import NodeDraw @@ -207,7 +209,7 @@ class CanvasManager: edge.delete() def join(self, session: Session) -> None: - # clear out all canvas + # clear out all canvases for canvas_id in self.notebook.tabs(): self.notebook.forget(canvas_id) self.canvases.clear() @@ -234,6 +236,10 @@ class CanvasManager: self.draw_session(session) def draw_session(self, session: Session) -> None: + # draw canvas configurations and shapes + self.parse_metadata_canvas(session.metadata) + self.parse_metadata_shapes(session.metadata) + # create session nodes for core_node in session.nodes.values(): # add node, avoiding ignored nodes @@ -256,11 +262,14 @@ class CanvasManager: else: self.add_wired_edge(node1, node2, link) - # parse metadata and organize canvases - self.core.parse_metadata() + # organize canvas order for canvas in self.canvases.values(): canvas.organize() + # parse metada for edge configs and hidden nodes + self.parse_metadata_edges(session.metadata) + self.parse_metadata_hidden(session.metadata) + # create a default canvas if none were created prior if not self.canvases: self.add_canvas() @@ -279,16 +288,22 @@ class CanvasManager: canvases=canvases, ) - def parse_metadata(self, config: Dict[str, Any]) -> None: + def parse_metadata_canvas(self, metadata: Dict[str, Any]) -> None: + # canvas setting + canvas_config = metadata.get("canvas") + logger.debug("canvas metadata: %s", canvas_config) + if not canvas_config: + return + canvas_config = json.loads(canvas_config) # get configured dimensions and gridlines option dimensions = self.default_dimensions - dimensions = config.get("dimensions", dimensions) - gridlines = config.get("gridlines", True) + dimensions = canvas_config.get("dimensions", dimensions) + gridlines = canvas_config.get("gridlines", True) self.show_grid.set(gridlines) self.redraw_canvases(dimensions) # get background configurations - for canvas_config in config.get("canvases", []): + for canvas_config in canvas_config.get("canvases", []): canvas_id = canvas_config.get("id") if canvas_id is None: logger.error("canvas config id not provided") @@ -296,6 +311,46 @@ class CanvasManager: canvas = self.get(canvas_id) canvas.parse_metadata(canvas_config) + def parse_metadata_shapes(self, metadata: Dict[str, Any]) -> None: + # load saved shapes + shapes_config = metadata.get("shapes") + if not shapes_config: + return + shapes_config = json.loads(shapes_config) + for shape_config in shapes_config: + logger.debug("loading shape: %s", shape_config) + Shape.from_metadata(self.app, shape_config) + + def parse_metadata_edges(self, metadata: Dict[str, Any]) -> None: + # load edges config + edges_config = metadata.get("edges") + if not edges_config: + return + edges_config = json.loads(edges_config) + logger.info("edges config: %s", edges_config) + for edge_config in edges_config: + edge_token = edge_config["token"] + edge = self.core.links.get(edge_token) + if edge: + edge.width = edge_config["width"] + edge.color = edge_config["color"] + edge.redraw() + else: + logger.warning("invalid edge token to configure: %s", edge_token) + + def parse_metadata_hidden(self, metadata: Dict[str, Any]) -> None: + # read hidden nodes + hidden_config = metadata.get("hidden") + if not hidden_config: + return + hidden_config = json.loads(hidden_config) + for node_id in hidden_config: + canvas_node = self.core.canvas_nodes.get(node_id) + if canvas_node: + canvas_node.hide() + else: + logger.warning("invalid node to hide: %s", node_id) + def add_core_node(self, core_node: Node) -> None: # get canvas tab for node canvas_id = core_node.canvas if core_node.canvas > 0 else 1 From ac5bbf5c6d02606153e68419b9fcf1deff19e30c Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 26 Aug 2021 15:12:51 -0700 Subject: [PATCH 089/110] pygui: updates to limit copy/paste to the graph widget itself and some small related cleanup to graph hot key functionalities --- daemon/core/gui/coreclient.py | 4 +--- daemon/core/gui/graph/graph.py | 44 +++++++++++++++++++++------------- daemon/core/gui/graph/node.py | 2 +- daemon/core/gui/menubar.py | 25 ++++++++----------- 4 files changed, 40 insertions(+), 35 deletions(-) diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 65d51fb0..63c20544 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -233,9 +233,7 @@ class CoreClient: canvas_node.update_icon(node.icon) elif event.message_type == MessageType.DELETE: canvas_node = self.canvas_nodes[node.id] - canvas_node.canvas.clear_selection() - canvas_node.canvas.select_object(canvas_node.id) - canvas_node.canvas.delete_selected_objects() + canvas_node.canvas_delete() elif event.message_type == MessageType.ADD: if node.id in self.session.nodes: logger.error("core node already exists: %s", node) diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 93401f95..1362a2e0 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -103,10 +103,15 @@ class CanvasGraph(tk.Canvas): """ Bind any mouse events or hot keys to the matching action """ + self.bind("", self.copy_selected) + self.bind("", self.paste_selected) + self.bind("", self.cut_selected) + self.bind("", self.delete_selected) + self.bind("", self.hide_selected) self.bind("", self.click_press) self.bind("", self.click_release) self.bind("", self.click_motion) - self.bind("", self.press_delete) + self.bind("", self.delete_selected) self.bind("", self.ctrl_click) self.bind("", self.double_click) self.bind("", self.zoom) @@ -277,7 +282,7 @@ class CanvasGraph(tk.Canvas): if select_id is not None: self.move(select_id, x_offset, y_offset) - def delete_selected_objects(self) -> None: + def delete_selected_objects(self, _event: tk.Event = None) -> None: edges = set() nodes = [] for object_id in self.selection: @@ -307,7 +312,7 @@ class CanvasGraph(tk.Canvas): self.selection.clear() self.core.deleted_canvas_nodes(nodes) - def hide_selected_objects(self) -> None: + def hide_selected(self, _event: tk.Event = None) -> None: for object_id in self.selection: # delete selection box selection_id = self.selection[object_id] @@ -487,17 +492,6 @@ class CanvasGraph(tk.Canvas): if self.select_box and self.manager.mode == GraphMode.SELECT: self.select_box.shape_motion(x, y) - def press_delete(self, _event: tk.Event) -> None: - """ - delete selected nodes and any data that relates to it - """ - logger.debug("press delete key") - if not self.app.core.is_runtime(): - self.delete_selected_objects() - self.app.default_info() - else: - logger.debug("node deletion is disabled during runtime state") - def double_click(self, event: tk.Event) -> None: selected = self.get_selected(event) if selected is not None and selected in self.shapes: @@ -673,7 +667,7 @@ class CanvasGraph(tk.Canvas): edge.complete(dst) return edge - def copy(self) -> None: + def copy_selected(self, _event: tk.Event = None) -> None: if self.core.is_runtime(): logger.debug("copy is disabled during runtime state") return @@ -684,7 +678,25 @@ class CanvasGraph(tk.Canvas): canvas_node = self.nodes[node_id] self.to_copy.append(canvas_node) - def paste(self) -> None: + def cut_selected(self, _event: tk.Event = None) -> None: + if self.core.is_runtime(): + logger.debug("cut is disabled during runtime state") + return + self.copy_selected() + self.delete_selected() + + def delete_selected(self, _event: tk.Event = None) -> None: + """ + delete selected nodes and any data that relates to it + """ + logger.debug("press delete key") + if self.core.is_runtime(): + logger.debug("node deletion is disabled during runtime state") + return + self.delete_selected_objects() + self.app.default_info() + + def paste_selected(self, _event: tk.Event = None) -> None: if self.core.is_runtime(): logger.debug("paste is disabled during runtime state") return diff --git a/daemon/core/gui/graph/node.py b/daemon/core/gui/graph/node.py index 2bd4ae40..b0a78268 100644 --- a/daemon/core/gui/graph/node.py +++ b/daemon/core/gui/graph/node.py @@ -338,7 +338,7 @@ class CanvasNode: def canvas_copy(self) -> None: self.canvas.clear_selection() self.canvas.select_object(self.id) - self.canvas.copy() + self.canvas.copy_selected() def show_config(self) -> None: dialog = NodeConfigDialog(self.app, self) diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index 5b64ad78..ff0c298a 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -122,11 +122,6 @@ class Menubar(tk.Menu): ) menu.add_command(label="Hide", accelerator="Ctrl+H", command=self.click_hide) self.add_cascade(label="Edit", menu=menu) - self.app.master.bind_all("", self.click_cut) - self.app.master.bind_all("", self.click_copy) - self.app.master.bind_all("", self.click_paste) - self.app.master.bind_all("", self.click_delete) - self.app.master.bind_all("", self.click_hide) self.edit_menu = menu def draw_canvas_menu(self) -> None: @@ -409,24 +404,24 @@ class Menubar(tk.Menu): def click_copy(self, _event: tk.Event = None) -> None: canvas = self.manager.current() - canvas.copy() + canvas.copy_selected() - def click_paste(self, _event: tk.Event = None) -> None: + def click_paste(self, event: tk.Event = None) -> None: canvas = self.manager.current() - canvas.paste() + canvas.paste_selected(event) - def click_delete(self, _event: tk.Event = None) -> None: + def click_delete(self, event: tk.Event = None) -> None: canvas = self.manager.current() - canvas.delete_selected_objects() + canvas.delete_selected(event) - def click_hide(self, _event: tk.Event = None) -> None: + def click_hide(self, event: tk.Event = None) -> None: canvas = self.manager.current() - canvas.hide_selected_objects() + canvas.hide_selected(event) - def click_cut(self, _event: tk.Event = None) -> None: + def click_cut(self, event: tk.Event = None) -> None: canvas = self.manager.current() - canvas.copy() - canvas.delete_selected_objects() + canvas.copy_selected(event) + canvas.delete_selected(event) def click_show_hidden(self, _event: tk.Event = None) -> None: for canvas in self.manager.all(): From b96dc621cde4426ed61c2c1c4f4048107acc50d5 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 27 Aug 2021 16:58:44 -0700 Subject: [PATCH 090/110] grpc: refactoring for editing/moving nodes, they are now two separate processes, with specific logic to carry each out --- daemon/core/api/grpc/client.py | 54 +++++++++------ daemon/core/api/grpc/server.py | 96 +++++++++++++++------------ daemon/core/api/tlv/corehandlers.py | 26 ++++++-- daemon/core/emulator/session.py | 62 ++++++----------- daemon/core/gui/coreclient.py | 2 +- daemon/proto/core/api/grpc/core.proto | 23 +++++-- daemon/scripts/core-cli | 24 +++++-- daemon/tests/test_grpc.py | 65 ++++++++++++------ daemon/tests/test_nodes.py | 25 +++++-- 9 files changed, 227 insertions(+), 150 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 6057d655..e3aab86a 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -441,13 +441,7 @@ class CoreGrpcClient: return node, ifaces, links def edit_node( - self, - session_id: int, - node_id: int, - position: wrappers.Position = None, - icon: str = None, - geo: wrappers.Geo = None, - source: str = None, + self, session_id: int, node_id: int, icon: str = None, source: str = None ) -> bool: """ Edit a node's icon and/or location, can only use position(x,y) or @@ -455,28 +449,50 @@ class CoreGrpcClient: :param session_id: session id :param node_id: node id - :param position: x,y location for node :param icon: path to icon for gui to use for node - :param geo: lon,lat,alt location for node :param source: application source :return: True for success, False otherwise :raises grpc.RpcError: when session or node doesn't exist """ - if position and geo: - raise CoreError("cannot edit position and geo at same time") - position_proto = position.to_proto() if position else None - geo_proto = geo.to_proto() if geo else None request = core_pb2.EditNodeRequest( - session_id=session_id, - node_id=node_id, - position=position_proto, - icon=icon, - source=source, - geo=geo_proto, + session_id=session_id, node_id=node_id, icon=icon, source=source ) response = self.stub.EditNode(request) return response.result + def move_node( + self, + session_id: int, + node_id: int, + position: wrappers.Position = None, + geo: wrappers.Geo = None, + source: str = None, + ) -> bool: + """ + Move node using provided position or geo location. + + :param session_id: session id + :param node_id: node id + :param position: x,y position to move to + :param geo: geospatial position to move to + :param source: source generating motion + :return: nothing + :raises grpc.RpcError: when session or nodes do not exist + """ + if not position and not geo: + raise CoreError("must provide position or geo to move node") + position = position.to_proto() if position else None + geo = geo.to_proto() if geo else None + request = core_pb2.MoveNodeRequest( + session_id=session_id, + node_id=node_id, + position=position, + geo=geo, + source=source, + ) + response = self.stub.MoveNode(request) + return response.result + def move_nodes(self, streamer: MoveNodesStreamer) -> None: """ Stream node movements using the provided iterator. diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index ed77ceeb..d8bb8078 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -75,7 +75,7 @@ from core.api.grpc.wlan_pb2 import ( ) from core.emane.modelmanager import EmaneModelManager from core.emulator.coreemu import CoreEmu -from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import ( EventTypes, ExceptionLevels, @@ -165,6 +165,26 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): except CoreError as e: context.abort(grpc.StatusCode.NOT_FOUND, str(e)) + def move_node( + self, + context: ServicerContext, + session_id: int, + node_id: int, + geo: core_pb2.Geo = None, + position: core_pb2.Position = None, + source: str = None, + ): + if not geo and not position: + raise CoreError("move node must provide a geo or position to move") + session = self.get_session(session_id, context) + node = self.get_node(session, node_id, context, NodeBase) + if geo: + session.set_node_geo(node, geo.lon, geo.lat, geo.alt) + else: + session.set_node_pos(node, position.x, position.y) + source = source if source else None + session.broadcast_node(node, source=source) + def validate_service( self, name: str, context: ServicerContext ) -> Type[ConfigService]: @@ -544,6 +564,23 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): links = get_links(node) return core_pb2.GetNodeResponse(node=node_proto, ifaces=ifaces, links=links) + def MoveNode( + self, request: core_pb2.MoveNodeRequest, context: ServicerContext + ) -> core_pb2.MoveNodeResponse: + """ + Move node, either by x,y position or geospatial. + + :param request: move node request + :param context: context object + :return: move nodes response + """ + geo = request.geo if request.HasField("geo") else None + position = request.position if request.HasField("position") else None + self.move_node( + context, request.session_id, request.node_id, geo, position, request.source + ) + return core_pb2.MoveNodeResponse(result=True) + def MoveNodes( self, request_iterator: Iterable[core_pb2.MoveNodesRequest], @@ -557,27 +594,16 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): :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: - logger.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 - logger.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) + geo = request.geo if request.HasField("geo") else None + position = request.position if request.HasField("position") else None + self.move_node( + context, + request.session_id, + request.node_id, + geo, + position, + request.source, + ) return core_pb2.MoveNodesResponse() def EditNode( @@ -593,28 +619,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logger.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(icon=request.icon) - if request.HasField("position"): - x = request.position.x - y = request.position.y - options.set_position(x, y) - has_geo = request.HasField("geo") - if has_geo: - lat = request.geo.lat - lon = request.geo.lon - alt = request.geo.alt - options.set_location(lat, lon, alt) - result = True - try: - session.edit_node(node.id, options) - source = None - if request.source: - source = request.source - if not has_geo: - session.broadcast_node(node, source=source) - except CoreError: - result = False - return core_pb2.EditNodeResponse(result=result) + node.icon = request.icon or None + source = request.source or None + session.broadcast_node(node, source=source) + return core_pb2.EditNodeResponse(result=True) def DeleteNode( self, request: core_pb2.DeleteNodeRequest, context: ServicerContext diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index a1c1d34a..73a42af6 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -716,12 +716,15 @@ class CoreHandler(socketserver.BaseRequestHandler): if message.flags & MessageFlags.ADD.value: node = self.session.add_node(_class, node_id, options) - if node: - if message.flags & MessageFlags.STRING.value: - self.node_status_request[node.id] = True - - if self.session.state == EventTypes.RUNTIME_STATE: - self.send_node_emulation_id(node.id) + has_geo = all( + i is not None for i in [options.lon, options.lat, options.alt] + ) + if has_geo: + self.session.broadcast_node(node) + if message.flags & MessageFlags.STRING.value: + self.node_status_request[node.id] = True + if self.session.state == EventTypes.RUNTIME_STATE: + self.send_node_emulation_id(node.id) elif message.flags & MessageFlags.DELETE.value: with self._shutdown_lock: result = self.session.delete_node(node_id) @@ -739,7 +742,16 @@ class CoreHandler(socketserver.BaseRequestHandler): replies.append(coreapi.CoreNodeMessage.pack(flags, tlvdata)) # node update else: - self.session.edit_node(node_id, options) + node = self.session.get_node(node_id, NodeBase) + node.icon = options.icon + has_geo = all( + i is not None for i in [options.lon, options.lat, options.alt] + ) + if has_geo: + self.session.set_node_geo(node, options.lon, options.lat, options.alt) + self.session.broadcast_node(node) + else: + self.session.set_node_pos(node, options.x, options.y) return replies diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 514bc168..72161717 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -545,7 +545,11 @@ class Session: node.canvas = options.canvas # set node position and broadcast it - self.set_node_position(node, options) + has_geo = all(i is not None for i in [options.lon, options.lat, options.alt]) + if has_geo: + self.set_node_geo(node, options.lon, options.lat, options.alt) + else: + self.set_node_pos(node, options.x, options.y) # add services to needed nodes if isinstance(node, (CoreNode, PhysicalNode)): @@ -583,49 +587,21 @@ class Session: self.sdt.add_node(node) return node - def edit_node(self, node_id: int, options: NodeOptions) -> None: - """ - Edit node information. + def set_node_pos(self, node: NodeBase, x: float, y: float) -> None: + node.setposition(x, y, None) + self.sdt.edit_node( + node, node.position.lon, node.position.lat, node.position.alt + ) - :param node_id: id of node to update - :param options: data to update node with - :return: nothing - :raises core.CoreError: when node to update does not exist - """ - node = self.get_node(node_id, NodeBase) - node.icon = options.icon - self.set_node_position(node, options) - self.sdt.edit_node(node, options.lon, options.lat, options.alt) - - def set_node_position(self, node: NodeBase, options: NodeOptions) -> None: - """ - Set position for a node, use lat/lon/alt if needed. - - :param node: node to set position for - :param options: data for node - :return: nothing - """ - # extract location values - x = options.x - y = options.y - lat = options.lat - lon = options.lon - alt = options.alt - # check if we need to generate position from lat/lon/alt - has_empty_position = all(i is None for i in [x, y]) - has_lat_lon_alt = all(i is not None for i in [lat, lon, alt]) - using_lat_lon_alt = has_empty_position and has_lat_lon_alt - if using_lat_lon_alt: - x, y, _ = self.location.getxyz(lat, lon, alt) - if math.isinf(x) or math.isinf(y): - raise CoreError( - f"invalid geo for current reference/scale: {lon},{lat},{alt}" - ) - node.setposition(x, y, None) - node.position.set_geo(lon, lat, alt) - self.broadcast_node(node) - elif not has_empty_position: - node.setposition(x, y, None) + def set_node_geo(self, node: NodeBase, lon: float, lat: float, alt: float) -> None: + x, y, _ = self.location.getxyz(lat, lon, alt) + if math.isinf(x) or math.isinf(y): + raise CoreError( + f"invalid geo for current reference/scale: {lon},{lat},{alt}" + ) + node.setposition(x, y, None) + node.position.set_geo(lon, lat, alt) + self.sdt.edit_node(node, lon, lat, alt) def start_mobility(self, node_ids: List[int] = None) -> None: """ diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 63c20544..41753e5d 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -391,7 +391,7 @@ class CoreClient: def edit_node(self, core_node: Node) -> None: try: - self.client.edit_node( + self.client.move_node( self.session.id, core_node.id, core_node.position, source=GUI_SOURCE ) except grpc.RpcError as e: diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 3fb55f73..df66254c 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -49,6 +49,8 @@ service CoreApi { } rpc GetNodeTerminal (GetNodeTerminalRequest) returns (GetNodeTerminalResponse) { } + rpc MoveNode (MoveNodeRequest) returns (MoveNodeResponse) { + } rpc MoveNodes (stream MoveNodesRequest) returns (MoveNodesResponse) { } @@ -329,10 +331,8 @@ message GetNodeResponse { message EditNodeRequest { int32 session_id = 1; int32 node_id = 2; - Position position = 3; - string icon = 4; - string source = 5; - Geo geo = 6; + string icon = 3; + string source = 4; } message EditNodeResponse { @@ -358,6 +358,21 @@ message GetNodeTerminalResponse { string terminal = 1; } + +message MoveNodeRequest { + int32 session_id = 1; + int32 node_id = 2; + string source = 3; + oneof move_type { + Position position = 4; + Geo geo = 5; + } +} + +message MoveNodeResponse { + bool result = 1; +} + message MoveNodesRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/scripts/core-cli b/daemon/scripts/core-cli index f050cf90..fbbe6ede 100755 --- a/daemon/scripts/core-cli +++ b/daemon/scripts/core-cli @@ -268,6 +268,13 @@ def add_node(core: CoreGrpcClient, args: Namespace) -> None: @coreclient def edit_node(core: CoreGrpcClient, args: Namespace) -> None: + session_id = get_current_session(core, args.session) + result = core.edit_node(session_id, args.id, args.icon) + print(f"edit node: {result}") + + +@coreclient +def move_node(core: CoreGrpcClient, args: Namespace) -> None: session_id = get_current_session(core, args.session) pos = None if args.pos: @@ -277,8 +284,8 @@ def edit_node(core: CoreGrpcClient, args: Namespace) -> None: if args.geo: lon, lat, alt = args.geo geo = Geo(lon=lon, lat=lat, alt=alt) - result = core.edit_node(session_id, args.id, pos, args.icon, geo) - print(f"edit node: {result}") + result = core.move_node(session_id, args.id, pos, geo) + print(f"move node: {result}") @coreclient @@ -377,13 +384,18 @@ def setup_node_parser(parent: _SubParsersAction) -> None: edit_parser = subparsers.add_parser("edit", help="edit a node") edit_parser.formatter_class = ArgumentDefaultsHelpFormatter - 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("-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("-i", "--id", type=int, help="id to use", required=True) edit_parser.add_argument("-ic", "--icon", help="icon to use, optional") edit_parser.set_defaults(func=edit_node) + move_parser = subparsers.add_parser("move", help="move a node") + move_parser.formatter_class = ArgumentDefaultsHelpFormatter + move_parser.add_argument("-i", "--id", type=int, help="id to use, optional", required=True) + group = move_parser.add_mutually_exclusive_group(required=True) + 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") + move_parser.set_defaults(func=move_node) + delete_parser = subparsers.add_parser("delete", help="delete a node") delete_parser.formatter_class = ArgumentDefaultsHelpFormatter delete_parser.add_argument("-i", "--id", type=int, help="node id", required=True) diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index ebfe29eb..575a502d 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -266,36 +266,63 @@ class TestGrpc: 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 - x, y = 10, 10 with client.context_connect(): - position = Position(x=x, y=y) - result = client.edit_node(session.id, node.id, position) + result = client.edit_node(session.id, node.id, icon) # then assert result is True - assert node.position.x == x - assert node.position.y == y - - def test_edit_node_exception(self, grpc_server: CoreGrpcServer): - # given - client = CoreGrpcClient() - session = grpc_server.coreemu.create_session() - node = session.add_node(CoreNode) - - # then - x, y = 10, 10 - with client.context_connect(): - position = Position(x=x, y=y) - geo = Geo(lat=0, lon=0, alt=0) - with pytest.raises(CoreError): - client.edit_node(session.id, node.id, position, geo=geo) + assert node.icon == icon @pytest.mark.parametrize("node_id, expected", [(1, True), (2, False)]) def test_delete_node( diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 8ed21f27..3f0fbab1 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -24,19 +24,30 @@ class TestNodes: assert node.alive() assert node.up - def test_node_update(self, session: Session): + def test_node_set_pos(self, session: Session): # given node = session.add_node(CoreNode) - position_value = 100 - update_options = NodeOptions() - update_options.set_position(x=position_value, y=position_value) + x, y = 100.0, 50.0 # when - session.edit_node(node.id, update_options) + session.set_node_pos(node, x, y) # then - assert node.position.x == position_value - assert node.position.y == position_value + assert node.position.x == x + assert node.position.y == y + + def test_node_set_geo(self, session: Session): + # given + node = session.add_node(CoreNode) + lon, lat, alt = 0.0, 0.0, 0.0 + + # when + session.set_node_geo(node, lon, lat, alt) + + # then + assert node.position.lon == lon + assert node.position.lat == lat + assert node.position.alt == alt def test_node_delete(self, session: Session): # given From bd896d1336cc67dbb8a5d4e6a17d8847e6c5e18d Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 17 Sep 2021 14:34:37 -0700 Subject: [PATCH 091/110] daemon: added capability to config services to shadow directory structures, from a given path, or from a local source, files may be templates or a straight copy and can be sourced from node named unique paths for node specific files, also refactored and renamed file creation related functions for nodes --- daemon/core/configservice/base.py | 122 ++++++++++++++++---- daemon/core/configservices/simpleservice.py | 49 -------- daemon/core/emulator/session.py | 4 +- daemon/core/nodes/base.py | 106 +++++++++++++---- daemon/core/nodes/docker.py | 20 ++-- daemon/core/nodes/lxd.py | 19 +-- daemon/core/services/coreservices.py | 8 +- daemon/tests/conftest.py | 2 +- daemon/tests/test_config_services.py | 6 +- daemon/tests/test_gui.py | 6 +- 10 files changed, 212 insertions(+), 130 deletions(-) delete mode 100644 daemon/core/configservices/simpleservice.py diff --git a/daemon/core/configservice/base.py b/daemon/core/configservice/base.py index 64a5dd03..386ab26d 100644 --- a/daemon/core/configservice/base.py +++ b/daemon/core/configservice/base.py @@ -3,8 +3,9 @@ import enum import inspect import logging import time +from dataclasses import dataclass from pathlib import Path -from typing import Any, Dict, List +from typing import Any, Dict, List, Optional from mako import exceptions from mako.lookup import TemplateLookup @@ -28,6 +29,14 @@ class ConfigServiceBootError(Exception): pass +@dataclass +class ShadowDir: + path: str + src: Optional[str] = None + templates: bool = False + has_node_paths: bool = False + + class ConfigService(abc.ABC): """ Base class for creating configurable services. @@ -39,6 +48,9 @@ class ConfigService(abc.ABC): # time to wait in seconds for determining if service started successfully validation_timer: int = 5 + # directories to shadow and copy files from + shadow_directories: List[ShadowDir] = [] + def __init__(self, node: CoreNode) -> None: """ Create ConfigService instance. @@ -135,6 +147,7 @@ class ConfigService(abc.ABC): :raises ConfigServiceBootError: when there is an error starting service """ logger.info("node(%s) service(%s) starting...", self.node.name, self.name) + self.create_shadow_dirs() self.create_dirs() self.create_files() wait = self.validation_mode == ConfigServiceMode.BLOCKING @@ -169,6 +182,64 @@ class ConfigService(abc.ABC): self.stop() self.start() + def create_shadow_dirs(self) -> None: + """ + Creates a shadow of a host system directory recursively + to be mapped and live within a node. + + :return: nothing + :raises CoreError: when there is a failure creating a directory or file + """ + for shadow_dir in self.shadow_directories: + # setup shadow and src paths, using node unique paths when configured + shadow_path = Path(shadow_dir.path) + if shadow_dir.src is None: + src_path = shadow_path + else: + src_path = Path(shadow_dir.src) + if shadow_dir.has_node_paths: + src_path = src_path / self.node.name + # validate shadow and src paths + if not shadow_path.is_absolute(): + raise CoreError(f"shadow dir({shadow_path}) is not absolute") + if not src_path.is_absolute(): + raise CoreError(f"shadow source dir({src_path}) is not absolute") + if not src_path.is_dir(): + raise CoreError(f"shadow source dir({src_path}) does not exist") + # create root of the shadow path within node + logger.info( + "node(%s) creating shadow directory(%s) src(%s) node paths(%s) " + "templates(%s)", + self.node.name, + shadow_path, + src_path, + shadow_dir.has_node_paths, + shadow_dir.templates, + ) + self.node.create_dir(shadow_path) + # find all directories and files to create + dir_paths = [] + file_paths = [] + for path in src_path.rglob("*"): + shadow_src_path = shadow_path / path.relative_to(src_path) + if path.is_dir(): + dir_paths.append(shadow_src_path) + else: + file_paths.append((path, shadow_src_path)) + # create all directories within node + for path in dir_paths: + self.node.create_dir(path) + # create all files within node, from templates when configured + data = self.data() + templates = TemplateLookup(directories=src_path) + for path, dst_path in file_paths: + if shadow_dir.templates: + template = templates.get_template(path.name) + rendered = self._render(template, data) + self.node.create_file(dst_path, rendered) + else: + self.node.copy_file(path, dst_path) + def create_dirs(self) -> None: """ Creates directories for service. @@ -176,10 +247,11 @@ class ConfigService(abc.ABC): :return: nothing :raises CoreError: when there is a failure creating a directory """ - for directory in self.directories: + logger.debug("creating config service directories") + for directory in sorted(self.directories): dir_path = Path(directory) try: - self.node.privatedir(dir_path) + self.node.create_dir(dir_path) except (CoreCommandError, CoreError): raise CoreError( f"node({self.node.name}) service({self.name}) " @@ -221,17 +293,21 @@ class ConfigService(abc.ABC): :return: mapping of files to templates """ templates = {} - for name in self.files: - basename = Path(name).name - if name in self.custom_templates: - template = self.custom_templates[name] - template = self.clean_text(template) - elif self.templates.has_template(basename): - template = self.templates.get_template(basename).source + for file in self.files: + file_path = Path(file) + if file_path.is_absolute(): + template_path = str(file_path.relative_to("/")) else: - template = self.get_text_template(name) + template_path = str(file_path) + if file in self.custom_templates: + template = self.custom_templates[file] template = self.clean_text(template) - templates[name] = template + elif self.templates.has_template(template_path): + template = self.templates.get_template(template_path).source + else: + template = self.get_text_template(file) + template = self.clean_text(template) + templates[file] = template return templates def create_files(self) -> None: @@ -241,24 +317,20 @@ class ConfigService(abc.ABC): :return: nothing """ data = self.data() - for name in self.files: - file_path = Path(name) - if name in self.custom_templates: - text = self.custom_templates[name] + for file in sorted(self.files): + logger.debug( + "node(%s) service(%s) template(%s)", self.node.name, self.name, file + ) + file_path = Path(file) + if file in self.custom_templates: + text = self.custom_templates[file] rendered = self.render_text(text, data) elif self.templates.has_template(file_path.name): rendered = self.render_template(file_path.name, data) else: - text = self.get_text_template(name) + text = self.get_text_template(file) rendered = self.render_text(text, data) - logger.debug( - "node(%s) service(%s) template(%s): \n%s", - self.node.name, - self.name, - name, - rendered, - ) - self.node.nodefile(file_path, rendered) + self.node.create_file(file_path, rendered) def run_startup(self, wait: bool) -> None: """ diff --git a/daemon/core/configservices/simpleservice.py b/daemon/core/configservices/simpleservice.py deleted file mode 100644 index 4370977d..00000000 --- a/daemon/core/configservices/simpleservice.py +++ /dev/null @@ -1,49 +0,0 @@ -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: 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( - id="value3", - type=ConfigDataTypes.STRING, - label="Multiple Choice", - options=["value1", "value2", "value3"], - ), - ] - 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"}, - } - - def get_text_template(self, name: str) -> str: - if name == "test1.sh": - return """ - # sample script 1 - # node id(${node.id}) name(${node.name}) - # config: ${config} - echo hello - """ - elif name == "test2.sh": - return """ - # sample script 2 - # node id(${node.id}) name(${node.name}) - # config: ${config} - echo hello2 - """ diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 72161717..6dad8e2d 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -690,11 +690,11 @@ class Session: :param data: file data :return: nothing """ - node = self.get_node(node_id, CoreNodeBase) + node = self.get_node(node_id, CoreNode) if src_path is not None: node.addfile(src_path, file_path) elif data is not None: - node.nodefile(file_path, data) + node.create_file(file_path, data) def clear(self) -> None: """ diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 960ec056..2ec7fb7f 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -237,7 +237,17 @@ class CoreNodeBase(NodeBase): raise NotImplementedError @abc.abstractmethod - def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: + def create_dir(self, dir_path: Path) -> None: + """ + Create a node private directory. + + :param dir_path: path to create + :return: nothing + """ + raise NotImplementedError + + @abc.abstractmethod + def create_file(self, file_path: Path, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. @@ -248,6 +258,19 @@ class CoreNodeBase(NodeBase): """ raise NotImplementedError + @abc.abstractmethod + def copy_file(self, src_path: Path, dst_path: Path, mode: int = None) -> None: + """ + Copy source file to node host destination, updating the file mode when + provided. + + :param src_path: source file to copy + :param dst_path: node host destination + :param mode: file mode + :return: nothing + """ + raise NotImplementedError + @abc.abstractmethod def addfile(self, src_path: Path, file_path: Path) -> None: """ @@ -567,7 +590,7 @@ class CoreNode(CoreNodeBase): # create private directories for dir_path in PRIVATE_DIRS: - self.privatedir(dir_path) + self.create_dir(dir_path) def shutdown(self) -> None: """ @@ -648,19 +671,23 @@ class CoreNode(CoreNodeBase): else: return f"ssh -X -f {self.server.host} xterm -e {terminal}" - def privatedir(self, dir_path: Path) -> None: + def create_dir(self, dir_path: Path) -> None: """ - Create a private directory. + Create a node private directory. :param dir_path: path to create :return: nothing """ - logger.info("creating private directory: %s", dir_path) - if not str(dir_path).startswith("/"): + if not dir_path.is_absolute(): raise CoreError(f"private directory path not fully qualified: {dir_path}") - host_path = self.host_path(dir_path, is_dir=True) - self.host_cmd(f"mkdir -p {host_path}") - self.mount(host_path, dir_path) + logger.debug("node(%s) creating private directory: %s", self.name, dir_path) + parent_path = self._find_parent_path(dir_path) + if parent_path: + self.host_cmd(f"mkdir -p {parent_path}") + else: + host_path = self.host_path(dir_path, is_dir=True) + self.host_cmd(f"mkdir -p {host_path}") + self.mount(host_path, dir_path) def mount(self, src_path: Path, target_path: Path) -> None: """ @@ -880,16 +907,40 @@ class CoreNode(CoreNodeBase): self.host_cmd(f"mkdir -p {directory}") self.server.remote_put(src_path, file_path) - def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: + def _find_parent_path(self, path: Path) -> Optional[Path]: """ - Create a node file with a given mode. + Check if there is an existing mounted parent directory created for this node. - :param file_path: name of file to create + :param path: existing parent path to use + :return: exist parent path if exists, None otherwise + """ + logger.debug("looking for existing parent: %s", path) + existing_path = None + for parent in path.parents: + node_path = self.host_path(parent, is_dir=True) + if node_path == self.directory: + break + if self.path_exists(str(node_path)): + relative_path = path.relative_to(parent) + existing_path = node_path / relative_path + break + return existing_path + + def create_file(self, file_path: Path, contents: str, mode: int = 0o644) -> None: + """ + Create file within a node at the given path, using contents and mode. + + :param file_path: desired path for file :param contents: contents of file - :param mode: mode for file + :param mode: mode to create file with :return: nothing """ - host_path = self.host_path(file_path) + logger.debug("node(%s) create file(%s) mode(%o)", self.name, file_path, mode) + host_path = self._find_parent_path(file_path) + if host_path: + self.host_cmd(f"mkdir -p {host_path.parent}") + else: + host_path = self.host_path(file_path) directory = host_path.parent if self.server is None: if not directory.exists(): @@ -901,26 +952,35 @@ class CoreNode(CoreNodeBase): self.host_cmd(f"mkdir -m {0o755:o} -p {directory}") self.server.remote_put_temp(host_path, contents) self.host_cmd(f"chmod {mode:o} {host_path}") - logger.debug("node(%s) added file: %s; mode: 0%o", self.name, host_path, mode) - def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None: + def copy_file(self, src_path: Path, dst_path: Path, mode: int = None) -> None: """ - Copy a file to a node, following symlinks and preserving metadata. - Change file mode if specified. + Copy source file to node host destination, updating the file mode when + provided. - :param file_path: file name to copy file to - :param src_path: file to copy - :param mode: mode to copy to + :param src_path: source file to copy + :param dst_path: node host destination + :param mode: file mode :return: nothing """ - host_path = self.host_path(file_path) + logger.debug( + "node(%s) copying file src(%s) to dst(%s) mode(%o)", + self.name, + src_path, + dst_path, + mode or 0, + ) + host_path = self._find_parent_path(dst_path) + if host_path: + self.host_cmd(f"mkdir -p {host_path.parent}") + else: + host_path = self.host_path(dst_path) if self.server is None: shutil.copy2(src_path, host_path) else: self.server.remote_put(src_path, host_path) if mode is not None: self.host_cmd(f"chmod {mode:o} {host_path}") - logger.info("node(%s) copied file: %s; mode: %s", self.name, host_path, mode) class CoreNetworkBase(NodeBase): diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index 8118807f..6dca41e1 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -164,7 +164,7 @@ class DockerNode(CoreNode): """ return f"docker exec -it {self.name} bash" - def privatedir(self, dir_path: str) -> None: + def create_dir(self, dir_path: Path) -> None: """ Create a private directory. @@ -187,7 +187,7 @@ class DockerNode(CoreNode): logger.debug("mounting source(%s) target(%s)", src_path, target_path) raise Exception("not supported") - def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: + def create_file(self, file_path: Path, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. @@ -196,7 +196,7 @@ class DockerNode(CoreNode): :param mode: mode for file :return: nothing """ - logger.debug("nodefile filename(%s) mode(%s)", file_path, mode) + logger.debug("node(%s) create file(%s) mode(%o)", self.name, file_path, mode) temp = NamedTemporaryFile(delete=False) temp.write(contents.encode("utf-8")) temp.close() @@ -211,26 +211,26 @@ class DockerNode(CoreNode): if self.server is not None: self.host_cmd(f"rm -f {temp_path}") temp_path.unlink() - logger.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode) - def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None: + def copy_file(self, src_path: Path, dst_path: Path, mode: int = None) -> None: """ Copy a file to a node, following symlinks and preserving metadata. Change file mode if specified. - :param file_path: file name to copy file to + :param dst_path: file name to copy file to :param src_path: file to copy :param mode: mode to copy to :return: nothing """ logger.info( - "node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode + "node file copy file(%s) source(%s) mode(%o)", dst_path, src_path, mode or 0 ) - self.cmd(f"mkdir -p {file_path.parent}") + self.cmd(f"mkdir -p {dst_path.parent}") if self.server: temp = NamedTemporaryFile(delete=False) temp_path = Path(temp.name) src_path = temp_path self.server.remote_put(src_path, temp_path) - self.client.copy_file(src_path, file_path) - self.cmd(f"chmod {mode:o} {file_path}") + self.client.copy_file(src_path, dst_path) + if mode is not None: + self.cmd(f"chmod {mode:o} {dst_path}") diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index 0f1a2799..54fc8341 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -140,7 +140,7 @@ class LxcNode(CoreNode): """ return f"lxc exec {self.name} -- {sh}" - def privatedir(self, dir_path: Path) -> None: + def create_dir(self, dir_path: Path) -> None: """ Create a private directory. @@ -163,7 +163,7 @@ class LxcNode(CoreNode): logger.debug("mounting source(%s) target(%s)", src_path, target_path) raise Exception("not supported") - def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None: + def create_file(self, file_path: Path, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. @@ -172,7 +172,7 @@ class LxcNode(CoreNode): :param mode: mode for file :return: nothing """ - logger.debug("nodefile filename(%s) mode(%s)", file_path, mode) + logger.debug("node(%s) create file(%s) mode(%o)", self.name, file_path, mode) temp = NamedTemporaryFile(delete=False) temp.write(contents.encode("utf-8")) temp.close() @@ -189,27 +189,28 @@ class LxcNode(CoreNode): temp_path.unlink() logger.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode) - def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None: + def copy_file(self, src_path: Path, dst_path: Path, mode: int = None) -> None: """ Copy a file to a node, following symlinks and preserving metadata. Change file mode if specified. - :param file_path: file name to copy file to + :param dst_path: file name to copy file to :param src_path: file to copy :param mode: mode to copy to :return: nothing """ logger.info( - "node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode + "node file copy file(%s) source(%s) mode(%o)", dst_path, src_path, mode or 0 ) - self.cmd(f"mkdir -p {file_path.parent}") + self.cmd(f"mkdir -p {dst_path.parent}") if self.server: temp = NamedTemporaryFile(delete=False) temp_path = Path(temp.name) src_path = temp_path self.server.remote_put(src_path, temp_path) - self.client.copy_file(src_path, file_path) - self.cmd(f"chmod {mode:o} {file_path}") + self.client.copy_file(src_path, dst_path) + if mode is not None: + self.cmd(f"chmod {mode:o} {dst_path}") def add_iface(self, iface: CoreInterface, iface_id: int) -> None: super().add_iface(iface, iface_id) diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 25cfbf12..b12f21c4 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -492,7 +492,7 @@ class CoreServices: for directory in service.dirs: dir_path = Path(directory) try: - node.privatedir(dir_path) + node.create_dir(dir_path) except (CoreCommandError, CoreError) as e: logger.warning( "error mounting private dir '%s' for service '%s': %s", @@ -553,7 +553,7 @@ class CoreServices: src = src.split("\n")[0] src = utils.expand_corepath(src, node.session, node) # TODO: glob here - node.nodefilecopy(file_path, src, mode=0o644) + node.copy_file(src, file_path, mode=0o644) return True return False @@ -750,7 +750,7 @@ class CoreServices: continue else: cfg = service.generate_config(node, file_name) - node.nodefile(file_path, cfg) + node.create_file(file_path, cfg) def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None: """ @@ -771,7 +771,7 @@ class CoreServices: cfg = service.config_data.get(file_name) if cfg is None: cfg = service.generate_config(node, file_name) - node.nodefile(file_path, cfg) + node.create_file(file_path, cfg) class CoreService: diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 5ced3fc8..98552540 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -60,7 +60,7 @@ def patcher(request): patch_manager.patch_obj( LinuxNetClient, "get_mac", return_value="00:00:00:00:00:00" ) - patch_manager.patch_obj(CoreNode, "nodefile") + patch_manager.patch_obj(CoreNode, "create_file") patch_manager.patch_obj(Session, "write_state") patch_manager.patch_obj(Session, "write_nodes") yield patch_manager diff --git a/daemon/tests/test_config_services.py b/daemon/tests/test_config_services.py index 432f2089..598450c1 100644 --- a/daemon/tests/test_config_services.py +++ b/daemon/tests/test_config_services.py @@ -70,7 +70,7 @@ class TestConfigServices: # then directory = Path(MyService.directories[0]) - node.privatedir.assert_called_with(directory) + node.create_dir.assert_called_with(directory) def test_create_files_custom(self): # given @@ -84,7 +84,7 @@ class TestConfigServices: # then file_path = Path(MyService.files[0]) - node.nodefile.assert_called_with(file_path, text) + node.create_file.assert_called_with(file_path, text) def test_create_files_text(self): # given @@ -96,7 +96,7 @@ class TestConfigServices: # then file_path = Path(MyService.files[0]) - node.nodefile.assert_called_with(file_path, TEMPLATE_TEXT) + node.create_file.assert_called_with(file_path, TEMPLATE_TEXT) def test_run_startup(self): # given diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 5f4ab487..b14f1fb1 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -441,10 +441,8 @@ class TestGui: coretlv.handle_message(message) if not request.config.getoption("mock"): - directory = str(file_path.parent) - created_directory = directory[1:].replace("/", ".") - create_path = node.directory / created_directory / file_path.name - assert create_path.exists() + expected_path = node.directory / "var.log/test" / file_path.name + assert expected_path.exists() def test_exec_node_tty(self, coretlv: CoreHandler): coretlv.dispatch_replies = mock.MagicMock() From 35549401eb6cf1b3fe22f24bdda41adcdc4d6822 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 17 Sep 2021 14:46:01 -0700 Subject: [PATCH 092/110] adding initial Dockerfile for convenience to run CORE within Docker, including GUI --- Dockerfile | 101 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..13332ecf --- /dev/null +++ b/Dockerfile @@ -0,0 +1,101 @@ +# syntax=docker/dockerfile:1 +FROM ubuntu:20.04 +LABEL Description="CORE Docker Image" + +# define variables +ARG DEBIAN_FRONTEND=noninteractive +ARG PREFIX=/usr/local +ARG BRANCH=develop +ARG CORE_TARBALL=core.tar.gz +ARG OSPF_TARBALL=ospf.tar.gz + +# install system dependencies +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + automake \ + bash \ + ca-certificates \ + ethtool \ + gawk \ + gcc \ + g++ \ + iproute2 \ + iputils-ping \ + libc-dev \ + libev-dev \ + libreadline-dev \ + libtool \ + libtk-img \ + make \ + nftables \ + python3 \ + python3-pip \ + python3-tk \ + pkg-config \ + systemctl \ + tk \ + wget \ + xauth \ + xterm \ + && apt-get clean +# install python dependencies +RUN python3 -m pip install \ + grpcio==1.27.2 \ + grpcio-tools==1.27.2 \ + poetry==1.1.7 +# retrieve, build, and install core +RUN wget -q -O ${CORE_TARBALL} https://api.github.com/repos/coreemu/core/tarball/${BRANCH} && \ + tar xf ${CORE_TARBALL} && \ + cd coreemu-core* && \ + ./bootstrap.sh && \ + ./configure && \ + make -j $(nproc) && \ + make install && \ + cd daemon && \ + python3 -m poetry build -f wheel && \ + python3 -m pip install dist/* && \ + cp scripts/* ${PREFIX}/bin && \ + mkdir /etc/core && \ + cp -n data/core.conf /etc/core && \ + cp -n data/logging.conf /etc/core && \ + mkdir -p ${PREFIX}/share/core && \ + cp -r examples ${PREFIX}/share/core && \ + echo '\ +[Unit]\n\ +Description=Common Open Research Emulator Service\n\ +After=network.target\n\ +\n\ +[Service]\n\ +Type=simple\n\ +ExecStart=/usr/local/bin/core-daemon\n\ +TasksMax=infinity\n\ +\n\ +[Install]\n\ +WantedBy=multi-user.target\ +' > /lib/systemd/system/core-daemon.service && \ + cd ../.. && \ + rm ${CORE_TARBALL} && \ + rm -rf coreemu-core* +# retrieve, build, and install ospf mdr +RUN wget -q -O ${OSPF_TARBALL} https://github.com/USNavalResearchLaboratory/ospf-mdr/tarball/master && \ + tar xf ${OSPF_TARBALL} && \ + cd USNavalResearchLaboratory-ospf-mdr* && \ + ./bootstrap.sh && \ + ./configure --disable-doc --enable-user=root --enable-group=root \ + --with-cflags=-ggdb --sysconfdir=/usr/local/etc/quagga --enable-vtysh \ + --localstatedir=/var/run/quagga && \ + make -j $(nproc) && \ + make install && \ + cd .. && \ + rm ${OSPF_TARBALL} && \ + rm -rf USNavalResearchLaboratory-ospf-mdr* +# retrieve and install emane packages +RUN wget -q https://adjacentlink.com/downloads/emane/emane-1.2.7-release-1.ubuntu-20_04.amd64.tar.gz && \ + tar xf emane*.tar.gz && \ + cd emane-1.2.7-release-1/debs/ubuntu-20_04/amd64 && \ + apt-get install -y ./emane*.deb ./python3-emane_*.deb && \ + cd ../../../.. && \ + rm emane-1.2.7-release-1.ubuntu-20_04.amd64.tar.gz && \ + rm -rf emane-1.2.7-release-1 +CMD ["systemctl", "start", "core-daemon"] +# sudo docker run -itd --name core -e DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix:rw --privileged core From 0a88851be3485460eeda1a7a7bd2d3c3b27e35e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Sep 2021 20:27:09 +0000 Subject: [PATCH 093/110] build(deps): bump lxml from 4.5.1 to 4.6.3 in /daemon Bumps [lxml](https://github.com/lxml/lxml) from 4.5.1 to 4.6.3. - [Release notes](https://github.com/lxml/lxml/releases) - [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt) - [Commits](https://github.com/lxml/lxml/compare/lxml-4.5.1...lxml-4.6.3) --- updated-dependencies: - dependency-name: lxml dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- daemon/poetry.lock | 79 +++++++++++++++++++++++++++---------------- daemon/pyproject.toml | 2 +- 2 files changed, 51 insertions(+), 30 deletions(-) diff --git a/daemon/poetry.lock b/daemon/poetry.lock index 32900734..e91c961e 100644 --- a/daemon/poetry.lock +++ b/daemon/poetry.lock @@ -256,7 +256,7 @@ xdg_home = ["appdirs (>=1.4.0)"] [[package]] name = "lxml" -version = "4.5.1" +version = "4.6.3" description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." category = "main" optional = false @@ -572,7 +572,7 @@ testing = ["jaraco.itertools", "func-timeout"] [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "11f841a55c991316ada6d11eafbda9b4caa762a27bfcde9971746b9725fb1005" +content-hash = "5704f81f5f2e3875010f80591c37198ed5c6fdec3414549f9798b2653bd0d557" [metadata.files] appdirs = [ @@ -795,33 +795,54 @@ isort = [ {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, ] lxml = [ - {file = "lxml-4.5.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ee2be8b8f72a2772e72ab926a3bccebf47bb727bda41ae070dc91d1fb759b726"}, - {file = "lxml-4.5.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:fadd2a63a2bfd7fb604508e553d1cf68eca250b2fbdbd81213b5f6f2fbf23529"}, - {file = "lxml-4.5.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:4f282737d187ae723b2633856085c31ae5d4d432968b7f3f478a48a54835f5c4"}, - {file = "lxml-4.5.1-cp27-cp27m-win32.whl", hash = "sha256:7fd88cb91a470b383aafad554c3fe1ccf6dfb2456ff0e84b95335d582a799804"}, - {file = "lxml-4.5.1-cp27-cp27m-win_amd64.whl", hash = "sha256:0790ddca3f825dd914978c94c2545dbea5f56f008b050e835403714babe62a5f"}, - {file = "lxml-4.5.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9144ce36ca0824b29ebc2e02ca186e54040ebb224292072250467190fb613b96"}, - {file = "lxml-4.5.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:a636346c6c0e1092ffc202d97ec1843a75937d8c98aaf6771348ad6422e44bb0"}, - {file = "lxml-4.5.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:f95d28193c3863132b1f55c1056036bf580b5a488d908f7d22a04ace8935a3a9"}, - {file = "lxml-4.5.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:b26719890c79a1dae7d53acac5f089d66fd8cc68a81f4e4bd355e45470dc25e1"}, - {file = "lxml-4.5.1-cp35-cp35m-win32.whl", hash = "sha256:a9e3b8011388e7e373565daa5e92f6c9cb844790dc18e43073212bb3e76f7007"}, - {file = "lxml-4.5.1-cp35-cp35m-win_amd64.whl", hash = "sha256:2754d4406438c83144f9ffd3628bbe2dcc6d62b20dbc5c1ec4bc4385e5d44b42"}, - {file = "lxml-4.5.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:925baf6ff1ef2c45169f548cc85204433e061360bfa7d01e1be7ae38bef73194"}, - {file = "lxml-4.5.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:a87dbee7ad9dce3aaefada2081843caf08a44a8f52e03e0a4cc5819f8398f2f4"}, - {file = "lxml-4.5.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:51bb4edeb36d24ec97eb3e6a6007be128b720114f9a875d6b370317d62ac80b9"}, - {file = "lxml-4.5.1-cp36-cp36m-win32.whl", hash = "sha256:c79e5debbe092e3c93ca4aee44c9a7631bdd407b2871cb541b979fd350bbbc29"}, - {file = "lxml-4.5.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b7462cdab6fffcda853338e1741ce99706cdf880d921b5a769202ea7b94e8528"}, - {file = "lxml-4.5.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06748c7192eab0f48e3d35a7adae609a329c6257495d5e53878003660dc0fec6"}, - {file = "lxml-4.5.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:1aa7a6197c1cdd65d974f3e4953764eee3d9c7b67e3966616b41fab7f8f516b7"}, - {file = "lxml-4.5.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:afb53edf1046599991fb4a7d03e601ab5f5422a5435c47ee6ba91ec3b61416a6"}, - {file = "lxml-4.5.1-cp37-cp37m-win32.whl", hash = "sha256:2d1ddce96cf15f1254a68dba6935e6e0f1fe39247de631c115e84dd404a6f031"}, - {file = "lxml-4.5.1-cp37-cp37m-win_amd64.whl", hash = "sha256:22c6d34fdb0e65d5f782a4d1a1edb52e0a8365858dafb1c08cb1d16546cf0786"}, - {file = "lxml-4.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c47a8a5d00060122ca5908909478abce7bbf62d812e3fc35c6c802df8fb01fe7"}, - {file = "lxml-4.5.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b77975465234ff49fdad871c08aa747aae06f5e5be62866595057c43f8d2f62c"}, - {file = "lxml-4.5.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:2b02c106709466a93ed424454ce4c970791c486d5fcdf52b0d822a7e29789626"}, - {file = "lxml-4.5.1-cp38-cp38-win32.whl", hash = "sha256:7eee37c1b9815e6505847aa5e68f192e8a1b730c5c7ead39ff317fde9ce29448"}, - {file = "lxml-4.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:d8d40e0121ca1606aa9e78c28a3a7d88a05c06b3ca61630242cded87d8ce55fa"}, - {file = "lxml-4.5.1.tar.gz", hash = "sha256:27ee0faf8077c7c1a589573b1450743011117f1aa1a91d5ae776bbc5ca6070f2"}, + {file = "lxml-4.6.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:df7c53783a46febb0e70f6b05df2ba104610f2fb0d27023409734a3ecbb78fb2"}, + {file = "lxml-4.6.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:1b7584d421d254ab86d4f0b13ec662a9014397678a7c4265a02a6d7c2b18a75f"}, + {file = "lxml-4.6.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:079f3ae844f38982d156efce585bc540c16a926d4436712cf4baee0cce487a3d"}, + {file = "lxml-4.6.3-cp27-cp27m-win32.whl", hash = "sha256:bc4313cbeb0e7a416a488d72f9680fffffc645f8a838bd2193809881c67dd106"}, + {file = "lxml-4.6.3-cp27-cp27m-win_amd64.whl", hash = "sha256:8157dadbb09a34a6bd95a50690595e1fa0af1a99445e2744110e3dca7831c4ee"}, + {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7728e05c35412ba36d3e9795ae8995e3c86958179c9770e65558ec3fdfd3724f"}, + {file = "lxml-4.6.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:4bff24dfeea62f2e56f5bab929b4428ae6caba2d1eea0c2d6eb618e30a71e6d4"}, + {file = "lxml-4.6.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:64812391546a18896adaa86c77c59a4998f33c24788cadc35789e55b727a37f4"}, + {file = "lxml-4.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c1a40c06fd5ba37ad39caa0b3144eb3772e813b5fb5b084198a985431c2f1e8d"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:74f7d8d439b18fa4c385f3f5dfd11144bb87c1da034a466c5b5577d23a1d9b51"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:f90ba11136bfdd25cae3951af8da2e95121c9b9b93727b1b896e3fa105b2f586"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:4c61b3a0db43a1607d6264166b230438f85bfed02e8cff20c22e564d0faff354"}, + {file = "lxml-4.6.3-cp35-cp35m-manylinux2014_x86_64.whl", hash = "sha256:5c8c163396cc0df3fd151b927e74f6e4acd67160d6c33304e805b84293351d16"}, + {file = "lxml-4.6.3-cp35-cp35m-win32.whl", hash = "sha256:f2380a6376dfa090227b663f9678150ef27543483055cc327555fb592c5967e2"}, + {file = "lxml-4.6.3-cp35-cp35m-win_amd64.whl", hash = "sha256:c4f05c5a7c49d2fb70223d0d5bcfbe474cf928310ac9fa6a7c6dddc831d0b1d4"}, + {file = "lxml-4.6.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d2e35d7bf1c1ac8c538f88d26b396e73dd81440d59c1ef8522e1ea77b345ede4"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:289e9ca1a9287f08daaf796d96e06cb2bc2958891d7911ac7cae1c5f9e1e0ee3"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bccbfc27563652de7dc9bdc595cb25e90b59c5f8e23e806ed0fd623755b6565d"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:d916d31fd85b2f78c76400d625076d9124de3e4bda8b016d25a050cc7d603f24"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:820628b7b3135403540202e60551e741f9b6d3304371712521be939470b454ec"}, + {file = "lxml-4.6.3-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:c47ff7e0a36d4efac9fd692cfa33fbd0636674c102e9e8d9b26e1b93a94e7617"}, + {file = "lxml-4.6.3-cp36-cp36m-win32.whl", hash = "sha256:5a0a14e264069c03e46f926be0d8919f4105c1623d620e7ec0e612a2e9bf1c04"}, + {file = "lxml-4.6.3-cp36-cp36m-win_amd64.whl", hash = "sha256:92e821e43ad382332eade6812e298dc9701c75fe289f2a2d39c7960b43d1e92a"}, + {file = "lxml-4.6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:efd7a09678fd8b53117f6bae4fa3825e0a22b03ef0a932e070c0bdbb3a35e654"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:efac139c3f0bf4f0939f9375af4b02c5ad83a622de52d6dfa8e438e8e01d0eb0"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0fbcf5565ac01dff87cbfc0ff323515c823081c5777a9fc7703ff58388c258c3"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:36108c73739985979bf302006527cf8a20515ce444ba916281d1c43938b8bb96"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:122fba10466c7bd4178b07dba427aa516286b846b2cbd6f6169141917283aae2"}, + {file = "lxml-4.6.3-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:cdaf11d2bd275bf391b5308f86731e5194a21af45fbaaaf1d9e8147b9160ea92"}, + {file = "lxml-4.6.3-cp37-cp37m-win32.whl", hash = "sha256:3439c71103ef0e904ea0a1901611863e51f50b5cd5e8654a151740fde5e1cade"}, + {file = "lxml-4.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:4289728b5e2000a4ad4ab8da6e1db2e093c63c08bdc0414799ee776a3f78da4b"}, + {file = "lxml-4.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b007cbb845b28db4fb8b6a5cdcbf65bacb16a8bd328b53cbc0698688a68e1caa"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:76fa7b1362d19f8fbd3e75fe2fb7c79359b0af8747e6f7141c338f0bee2f871a"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:26e761ab5b07adf5f555ee82fb4bfc35bf93750499c6c7614bd64d12aaa67927"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:e1cbd3f19a61e27e011e02f9600837b921ac661f0c40560eefb366e4e4fb275e"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:66e575c62792c3f9ca47cb8b6fab9e35bab91360c783d1606f758761810c9791"}, + {file = "lxml-4.6.3-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:1b38116b6e628118dea5b2186ee6820ab138dbb1e24a13e478490c7db2f326ae"}, + {file = "lxml-4.6.3-cp38-cp38-win32.whl", hash = "sha256:89b8b22a5ff72d89d48d0e62abb14340d9e99fd637d046c27b8b257a01ffbe28"}, + {file = "lxml-4.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:2a9d50e69aac3ebee695424f7dbd7b8c6d6eb7de2a2eb6b0f6c7db6aa41e02b7"}, + {file = "lxml-4.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ce256aaa50f6cc9a649c51be3cd4ff142d67295bfc4f490c9134d0f9f6d58ef0"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:7610b8c31688f0b1be0ef882889817939490a36d0ee880ea562a4e1399c447a1"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f8380c03e45cf09f8557bdaa41e1fa7c81f3ae22828e1db470ab2a6c96d8bc23"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:3082c518be8e97324390614dacd041bb1358c882d77108ca1957ba47738d9d59"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:884ab9b29feaca361f7f88d811b1eea9bfca36cf3da27768d28ad45c3ee6f969"}, + {file = "lxml-4.6.3-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:6f12e1427285008fd32a6025e38e977d44d6382cf28e7201ed10d6c1698d2a9a"}, + {file = "lxml-4.6.3-cp39-cp39-win32.whl", hash = "sha256:33bb934a044cf32157c12bfcfbb6649807da20aa92c062ef51903415c704704f"}, + {file = "lxml-4.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:542d454665a3e277f76954418124d67516c5f88e51a900365ed54a9806122b83"}, + {file = "lxml-4.6.3.tar.gz", hash = "sha256:39b78571b3b30645ac77b95f7c69d1bffc4cf8c3b157c435a34da72e78c82468"}, ] mako = [ {file = "Mako-1.1.3-py2.py3-none-any.whl", hash = "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9"}, diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 720ae27d..61be9d1d 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -21,7 +21,7 @@ dataclasses = { version = "*", python = "~3.6" } fabric = "2.5.0" grpcio = "1.27.2" invoke = "1.4.1" -lxml = "4.5.1" +lxml = "4.6.3" mako = "1.1.3" netaddr = "0.7.19" pillow = "8.3.2" From 18163c577f3f65c3087724de715b65d4c35cc819 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 22 Sep 2021 20:27:28 +0000 Subject: [PATCH 094/110] build(deps): bump pyyaml from 5.3.1 to 5.4 in /daemon Bumps [pyyaml](https://github.com/yaml/pyyaml) from 5.3.1 to 5.4. - [Release notes](https://github.com/yaml/pyyaml/releases) - [Changelog](https://github.com/yaml/pyyaml/blob/master/CHANGES) - [Commits](https://github.com/yaml/pyyaml/compare/5.3.1...5.4) --- updated-dependencies: - dependency-name: pyyaml dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- daemon/poetry.lock | 40 ++++++++++++++++++++++++---------------- daemon/pyproject.toml | 2 +- 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/daemon/poetry.lock b/daemon/poetry.lock index 32900734..581e506e 100644 --- a/daemon/poetry.lock +++ b/daemon/poetry.lock @@ -507,11 +507,11 @@ testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xm [[package]] name = "pyyaml" -version = "5.3.1" +version = "5.4" description = "YAML parser and emitter for Python" category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" [[package]] name = "six" @@ -572,7 +572,7 @@ testing = ["jaraco.itertools", "func-timeout"] [metadata] lock-version = "1.1" python-versions = "^3.6" -content-hash = "11f841a55c991316ada6d11eafbda9b4caa762a27bfcde9971746b9725fb1005" +content-hash = "4644196b67a215289cbb3f03428b96b80208a669c24e6bb9768001f9c573a1ba" [metadata.files] appdirs = [ @@ -1062,19 +1062,27 @@ pytest = [ {file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"}, ] pyyaml = [ - {file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"}, - {file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win32.whl", hash = "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2"}, - {file = "PyYAML-5.3.1-cp35-cp35m-win_amd64.whl", hash = "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win32.whl", hash = "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2"}, - {file = "PyYAML-5.3.1-cp36-cp36m-win_amd64.whl", hash = "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win32.whl", hash = "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a"}, - {file = "PyYAML-5.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf"}, - {file = "PyYAML-5.3.1-cp38-cp38-win32.whl", hash = "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97"}, - {file = "PyYAML-5.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee"}, - {file = "PyYAML-5.3.1-cp39-cp39-win32.whl", hash = "sha256:ad9c67312c84def58f3c04504727ca879cb0013b2517c85a9a253f0cb6380c0a"}, - {file = "PyYAML-5.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:6034f55dab5fea9e53f436aa68fa3ace2634918e8b5994d82f3621c04ff5ed2e"}, - {file = "PyYAML-5.3.1.tar.gz", hash = "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d"}, + {file = "PyYAML-5.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f7a21e3d99aa3095ef0553e7ceba36fb693998fbb1226f1392ce33681047465f"}, + {file = "PyYAML-5.4-cp27-cp27m-win32.whl", hash = "sha256:52bf0930903818e600ae6c2901f748bc4869c0c406056f679ab9614e5d21a166"}, + {file = "PyYAML-5.4-cp27-cp27m-win_amd64.whl", hash = "sha256:a36a48a51e5471513a5aea920cdad84cbd56d70a5057cca3499a637496ea379c"}, + {file = "PyYAML-5.4-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5e7ac4e0e79a53451dc2814f6876c2fa6f71452de1498bbe29c0b54b69a986f4"}, + {file = "PyYAML-5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc552b6434b90d9dbed6a4f13339625dc466fd82597119897e9489c953acbc22"}, + {file = "PyYAML-5.4-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0dc9f2eb2e3c97640928dec63fd8dc1dd91e6b6ed236bd5ac00332b99b5c2ff9"}, + {file = "PyYAML-5.4-cp36-cp36m-win32.whl", hash = "sha256:5a3f345acff76cad4aa9cb171ee76c590f37394186325d53d1aa25318b0d4a09"}, + {file = "PyYAML-5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:f3790156c606299ff499ec44db422f66f05a7363b39eb9d5b064f17bd7d7c47b"}, + {file = "PyYAML-5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:124fd7c7bc1e95b1eafc60825f2daf67c73ce7b33f1194731240d24b0d1bf628"}, + {file = "PyYAML-5.4-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:8b818b6c5a920cbe4203b5a6b14256f0e5244338244560da89b7b0f1313ea4b6"}, + {file = "PyYAML-5.4-cp37-cp37m-win32.whl", hash = "sha256:737bd70e454a284d456aa1fa71a0b429dd527bcbf52c5c33f7c8eee81ac16b89"}, + {file = "PyYAML-5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:7242790ab6c20316b8e7bb545be48d7ed36e26bbe279fd56f2c4a12510e60b4b"}, + {file = "PyYAML-5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cc547d3ead3754712223abb7b403f0a184e4c3eae18c9bb7fd15adef1597cc4b"}, + {file = "PyYAML-5.4-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8635d53223b1f561b081ff4adecb828fd484b8efffe542edcfdff471997f7c39"}, + {file = "PyYAML-5.4-cp38-cp38-win32.whl", hash = "sha256:26fcb33776857f4072601502d93e1a619f166c9c00befb52826e7b774efaa9db"}, + {file = "PyYAML-5.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2243dd033fd02c01212ad5c601dafb44fbb293065f430b0d3dbf03f3254d615"}, + {file = "PyYAML-5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:31ba07c54ef4a897758563e3a0fcc60077698df10180abe4b8165d9895c00ebf"}, + {file = "PyYAML-5.4-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:02c78d77281d8f8d07a255e57abdbf43b02257f59f50cc6b636937d68efa5dd0"}, + {file = "PyYAML-5.4-cp39-cp39-win32.whl", hash = "sha256:fdc6b2cb4b19e431994f25a9160695cc59a4e861710cc6fc97161c5e845fc579"}, + {file = "PyYAML-5.4-cp39-cp39-win_amd64.whl", hash = "sha256:8bf38641b4713d77da19e91f8b5296b832e4db87338d6aeffe422d42f1ca896d"}, + {file = "PyYAML-5.4.tar.gz", hash = "sha256:3c49e39ac034fd64fd576d63bb4db53cda89b362768a67f07749d55f128ac18a"}, ] six = [ {file = "six-1.15.0-py2.py3-none-any.whl", hash = "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"}, diff --git a/daemon/pyproject.toml b/daemon/pyproject.toml index 720ae27d..f7d9cbbd 100644 --- a/daemon/pyproject.toml +++ b/daemon/pyproject.toml @@ -27,7 +27,7 @@ netaddr = "0.7.19" pillow = "8.3.2" protobuf = "3.12.2" pyproj = "2.6.1.post1" -pyyaml = "5.3.1" +pyyaml = "5.4" [tool.poetry.dev-dependencies] black = "==19.3b0" From 04fb3322b55b763bf4e5b32a12fc1df7ae017904 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 27 Oct 2021 08:23:54 -0700 Subject: [PATCH 095/110] daemon: updated ovs net client to use proper set_mac_learning function, due to code changes prior --- daemon/core/nodes/netclient.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index c066910b..2f87f5dd 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -361,14 +361,15 @@ class OvsNetClient(LinuxNetClient): return True return False - def disable_mac_learning(self, name: str) -> None: + def set_mac_learning(self, name: str, value: int) -> None: """ - Disable mac learning for a OVS bridge. + Set mac learning for an OVS bridge. :param name: bridge name + :param value: ageing time value :return: nothing """ - self.run(f"{OVS_VSCTL} set bridge {name} other_config:mac-aging-time=0") + self.run(f"{OVS_VSCTL} set bridge {name} other_config:mac-aging-time={value}") def get_net_client(use_ovs: bool, run: Callable[..., str]) -> LinuxNetClient: From 2b895034324ee548ecb83fbbd12396394defb2e9 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 29 Oct 2021 14:11:11 -0700 Subject: [PATCH 096/110] updates to sample1 scenarios to remove any custom service configurations, since the defaults should be used, fixes an issue specifically in sample1.imn --- daemon/core/gui/data/xmls/sample1.xml | 1628 ------------------------- gui/configs/sample1.imn | 48 - 2 files changed, 1676 deletions(-) diff --git a/daemon/core/gui/data/xmls/sample1.xml b/daemon/core/gui/data/xmls/sample1.xml index c9e12940..c4f75c47 100644 --- a/daemon/core/gui/data/xmls/sample1.xml +++ b/daemon/core/gui/data/xmls/sample1.xml @@ -223,1634 +223,6 @@ - - - /usr/local/etc/quagga - /var/run/quagga - - - sh quaggaboot.sh zebra - - - pidof zebra - - - killall zebra - - - CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then - ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf - fi - # if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then - ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf - fi -} - -bootdaemon() -{ - QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH) - if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then - echo "ERROR: Quagga's '$1' daemon not found in search path:" - echo " $QUAGGA_SBIN_SEARCH" - return 1 - fi - - flags="" - - if [ "$1" = "xpimd" ] && \ - grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then - flags="$flags -6" - fi - - $QUAGGA_SBIN_DIR/$1 $flags -d - if [ "$?" != "0" ]; then - echo "ERROR: Quagga's '$1' daemon failed to start!:" - return 1 - fi -} - -bootquagga() -{ - QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH) - if [ "z$QUAGGA_BIN_DIR" = "z" ]; then - echo "ERROR: Quagga's 'vtysh' program not found in search path:" - echo " $QUAGGA_BIN_SEARCH" - return 1 - fi - - # fix /var/run/quagga permissions - id -u quagga 2>/dev/null >/dev/null - if [ "$?" = "0" ]; then - chown quagga $QUAGGA_STATE_DIR - fi - - bootdaemon "zebra" - for r in rip ripng ospf6 ospf bgp babel; do - if grep -q "^router \<${r}\>" $QUAGGA_CONF; then - bootdaemon "${r}d" - fi - done - - if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then - bootdaemon "xpimd" - fi - - $QUAGGA_BIN_DIR/vtysh -b -} - -if [ "$1" != "zebra" ]; then - echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!" - exit 1 -fi -confcheck -bootquagga -]]> - - - - - - - pidof ospfd - - - killall ospfd - - - - - pidof ospf6d - - - killall ospf6d - - - - - sh ipforward.sh - - - - - - - - /usr/local/etc/quagga - /var/run/quagga - - - sh quaggaboot.sh zebra - - - pidof zebra - - - killall zebra - - - CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then - ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf - fi - # if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then - ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf - fi -} - -bootdaemon() -{ - QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH) - if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then - echo "ERROR: Quagga's '$1' daemon not found in search path:" - echo " $QUAGGA_SBIN_SEARCH" - return 1 - fi - - flags="" - - if [ "$1" = "xpimd" ] && \ - grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then - flags="$flags -6" - fi - - $QUAGGA_SBIN_DIR/$1 $flags -d - if [ "$?" != "0" ]; then - echo "ERROR: Quagga's '$1' daemon failed to start!:" - return 1 - fi -} - -bootquagga() -{ - QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH) - if [ "z$QUAGGA_BIN_DIR" = "z" ]; then - echo "ERROR: Quagga's 'vtysh' program not found in search path:" - echo " $QUAGGA_BIN_SEARCH" - return 1 - fi - - # fix /var/run/quagga permissions - id -u quagga 2>/dev/null >/dev/null - if [ "$?" = "0" ]; then - chown quagga $QUAGGA_STATE_DIR - fi - - bootdaemon "zebra" - for r in rip ripng ospf6 ospf bgp babel; do - if grep -q "^router \<${r}\>" $QUAGGA_CONF; then - bootdaemon "${r}d" - fi - done - - if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then - bootdaemon "xpimd" - fi - - $QUAGGA_BIN_DIR/vtysh -b -} - -if [ "$1" != "zebra" ]; then - echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!" - exit 1 -fi -confcheck -bootquagga -]]> - - - - - - - pidof ospfd - - - killall ospfd - - - - - pidof ospf6d - - - killall ospf6d - - - - - sh ipforward.sh - - - - - - - - /usr/local/etc/quagga - /var/run/quagga - - - sh quaggaboot.sh zebra - - - pidof zebra - - - killall zebra - - - CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then - ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf - fi - # if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then - ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf - fi -} - -bootdaemon() -{ - QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH) - if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then - echo "ERROR: Quagga's '$1' daemon not found in search path:" - echo " $QUAGGA_SBIN_SEARCH" - return 1 - fi - - flags="" - - if [ "$1" = "xpimd" ] && \ - grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then - flags="$flags -6" - fi - - $QUAGGA_SBIN_DIR/$1 $flags -d - if [ "$?" != "0" ]; then - echo "ERROR: Quagga's '$1' daemon failed to start!:" - return 1 - fi -} - -bootquagga() -{ - QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH) - if [ "z$QUAGGA_BIN_DIR" = "z" ]; then - echo "ERROR: Quagga's 'vtysh' program not found in search path:" - echo " $QUAGGA_BIN_SEARCH" - return 1 - fi - - # fix /var/run/quagga permissions - id -u quagga 2>/dev/null >/dev/null - if [ "$?" = "0" ]; then - chown quagga $QUAGGA_STATE_DIR - fi - - bootdaemon "zebra" - for r in rip ripng ospf6 ospf bgp babel; do - if grep -q "^router \<${r}\>" $QUAGGA_CONF; then - bootdaemon "${r}d" - fi - done - - if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then - bootdaemon "xpimd" - fi - - $QUAGGA_BIN_DIR/vtysh -b -} - -if [ "$1" != "zebra" ]; then - echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!" - exit 1 -fi -confcheck -bootquagga -]]> - - - - - - - pidof ospfd - - - killall ospfd - - - - - pidof ospf6d - - - killall ospf6d - - - - - sh ipforward.sh - - - - - - - - /usr/local/etc/quagga - /var/run/quagga - - - sh quaggaboot.sh zebra - - - pidof zebra - - - killall zebra - - - CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then - ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf - fi - # if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then - ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf - fi -} - -bootdaemon() -{ - QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH) - if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then - echo "ERROR: Quagga's '$1' daemon not found in search path:" - echo " $QUAGGA_SBIN_SEARCH" - return 1 - fi - - flags="" - - if [ "$1" = "xpimd" ] && \ - grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then - flags="$flags -6" - fi - - $QUAGGA_SBIN_DIR/$1 $flags -d - if [ "$?" != "0" ]; then - echo "ERROR: Quagga's '$1' daemon failed to start!:" - return 1 - fi -} - -bootquagga() -{ - QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH) - if [ "z$QUAGGA_BIN_DIR" = "z" ]; then - echo "ERROR: Quagga's 'vtysh' program not found in search path:" - echo " $QUAGGA_BIN_SEARCH" - return 1 - fi - - # fix /var/run/quagga permissions - id -u quagga 2>/dev/null >/dev/null - if [ "$?" = "0" ]; then - chown quagga $QUAGGA_STATE_DIR - fi - - bootdaemon "zebra" - for r in rip ripng ospf6 ospf bgp babel; do - if grep -q "^router \<${r}\>" $QUAGGA_CONF; then - bootdaemon "${r}d" - fi - done - - if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then - bootdaemon "xpimd" - fi - - $QUAGGA_BIN_DIR/vtysh -b -} - -if [ "$1" != "zebra" ]; then - echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!" - exit 1 -fi -confcheck -bootquagga -]]> - - - - - - - pidof ospf6d - - - killall ospf6d - - - - - sh ipforward.sh - - - - - - - - /usr/local/etc/quagga - /var/run/quagga - - - sh quaggaboot.sh zebra - - - pidof zebra - - - killall zebra - - - CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then - ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf - fi - # if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then - ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf - fi -} - -bootdaemon() -{ - QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH) - if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then - echo "ERROR: Quagga's '$1' daemon not found in search path:" - echo " $QUAGGA_SBIN_SEARCH" - return 1 - fi - - flags="" - - if [ "$1" = "xpimd" ] && \ - grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then - flags="$flags -6" - fi - - $QUAGGA_SBIN_DIR/$1 $flags -d - if [ "$?" != "0" ]; then - echo "ERROR: Quagga's '$1' daemon failed to start!:" - return 1 - fi -} - -bootquagga() -{ - QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH) - if [ "z$QUAGGA_BIN_DIR" = "z" ]; then - echo "ERROR: Quagga's 'vtysh' program not found in search path:" - echo " $QUAGGA_BIN_SEARCH" - return 1 - fi - - # fix /var/run/quagga permissions - id -u quagga 2>/dev/null >/dev/null - if [ "$?" = "0" ]; then - chown quagga $QUAGGA_STATE_DIR - fi - - bootdaemon "zebra" - for r in rip ripng ospf6 ospf bgp babel; do - if grep -q "^router \<${r}\>" $QUAGGA_CONF; then - bootdaemon "${r}d" - fi - done - - if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then - bootdaemon "xpimd" - fi - - $QUAGGA_BIN_DIR/vtysh -b -} - -if [ "$1" != "zebra" ]; then - echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!" - exit 1 -fi -confcheck -bootquagga -]]> - - - - - - - pidof ospf6d - - - killall ospf6d - - - - - sh ipforward.sh - - - - - - - - /usr/local/etc/quagga - /var/run/quagga - - - sh quaggaboot.sh zebra - - - pidof zebra - - - killall zebra - - - CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then - ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf - fi - # if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then - ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf - fi -} - -bootdaemon() -{ - QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH) - if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then - echo "ERROR: Quagga's '$1' daemon not found in search path:" - echo " $QUAGGA_SBIN_SEARCH" - return 1 - fi - - flags="" - - if [ "$1" = "xpimd" ] && \ - grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then - flags="$flags -6" - fi - - $QUAGGA_SBIN_DIR/$1 $flags -d - if [ "$?" != "0" ]; then - echo "ERROR: Quagga's '$1' daemon failed to start!:" - return 1 - fi -} - -bootquagga() -{ - QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH) - if [ "z$QUAGGA_BIN_DIR" = "z" ]; then - echo "ERROR: Quagga's 'vtysh' program not found in search path:" - echo " $QUAGGA_BIN_SEARCH" - return 1 - fi - - # fix /var/run/quagga permissions - id -u quagga 2>/dev/null >/dev/null - if [ "$?" = "0" ]; then - chown quagga $QUAGGA_STATE_DIR - fi - - bootdaemon "zebra" - for r in rip ripng ospf6 ospf bgp babel; do - if grep -q "^router \<${r}\>" $QUAGGA_CONF; then - bootdaemon "${r}d" - fi - done - - if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then - bootdaemon "xpimd" - fi - - $QUAGGA_BIN_DIR/vtysh -b -} - -if [ "$1" != "zebra" ]; then - echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!" - exit 1 -fi -confcheck -bootquagga -]]> - - - - - - - pidof ospf6d - - - killall ospf6d - - - - - sh ipforward.sh - - - - - - - - /usr/local/etc/quagga - /var/run/quagga - - - sh quaggaboot.sh zebra - - - pidof zebra - - - killall zebra - - - CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then - ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf - fi - # if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then - ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf - fi -} - -bootdaemon() -{ - QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH) - if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then - echo "ERROR: Quagga's '$1' daemon not found in search path:" - echo " $QUAGGA_SBIN_SEARCH" - return 1 - fi - - flags="" - - if [ "$1" = "xpimd" ] && \ - grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then - flags="$flags -6" - fi - - $QUAGGA_SBIN_DIR/$1 $flags -d - if [ "$?" != "0" ]; then - echo "ERROR: Quagga's '$1' daemon failed to start!:" - return 1 - fi -} - -bootquagga() -{ - QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH) - if [ "z$QUAGGA_BIN_DIR" = "z" ]; then - echo "ERROR: Quagga's 'vtysh' program not found in search path:" - echo " $QUAGGA_BIN_SEARCH" - return 1 - fi - - # fix /var/run/quagga permissions - id -u quagga 2>/dev/null >/dev/null - if [ "$?" = "0" ]; then - chown quagga $QUAGGA_STATE_DIR - fi - - bootdaemon "zebra" - for r in rip ripng ospf6 ospf bgp babel; do - if grep -q "^router \<${r}\>" $QUAGGA_CONF; then - bootdaemon "${r}d" - fi - done - - if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then - bootdaemon "xpimd" - fi - - $QUAGGA_BIN_DIR/vtysh -b -} - -if [ "$1" != "zebra" ]; then - echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!" - exit 1 -fi -confcheck -bootquagga -]]> - - - - - - - pidof ospfd - - - killall ospfd - - - - - pidof ospf6d - - - killall ospf6d - - - - - sh ipforward.sh - - - - - - - - /usr/local/etc/quagga - /var/run/quagga - - - sh quaggaboot.sh zebra - - - pidof zebra - - - killall zebra - - - CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then - ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf - fi - # if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then - ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf - fi -} - -bootdaemon() -{ - QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH) - if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then - echo "ERROR: Quagga's '$1' daemon not found in search path:" - echo " $QUAGGA_SBIN_SEARCH" - return 1 - fi - - flags="" - - if [ "$1" = "xpimd" ] && \ - grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then - flags="$flags -6" - fi - - $QUAGGA_SBIN_DIR/$1 $flags -d - if [ "$?" != "0" ]; then - echo "ERROR: Quagga's '$1' daemon failed to start!:" - return 1 - fi -} - -bootquagga() -{ - QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH) - if [ "z$QUAGGA_BIN_DIR" = "z" ]; then - echo "ERROR: Quagga's 'vtysh' program not found in search path:" - echo " $QUAGGA_BIN_SEARCH" - return 1 - fi - - # fix /var/run/quagga permissions - id -u quagga 2>/dev/null >/dev/null - if [ "$?" = "0" ]; then - chown quagga $QUAGGA_STATE_DIR - fi - - bootdaemon "zebra" - for r in rip ripng ospf6 ospf bgp babel; do - if grep -q "^router \<${r}\>" $QUAGGA_CONF; then - bootdaemon "${r}d" - fi - done - - if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then - bootdaemon "xpimd" - fi - - $QUAGGA_BIN_DIR/vtysh -b -} - -if [ "$1" != "zebra" ]; then - echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!" - exit 1 -fi -confcheck -bootquagga -]]> - - - - - - - pidof ospf6d - - - killall ospf6d - - - - - sh ipforward.sh - - - - - - - - sh defaultroute.sh - - - - - - - - sh defaultroute.sh - - - - - - - - sh defaultroute.sh - - - - - - - - sh defaultroute.sh - - - - - - - - /etc/ssh - /var/run/sshd - - - sh startsshd.sh - - - killall sshd - - - - - - - - - /usr/local/etc/quagga - /var/run/quagga - - - sh quaggaboot.sh zebra - - - pidof zebra - - - killall zebra - - - CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/Quagga.conf ]; then - ln -s $CONF_DIR/Quagga.conf /etc/quagga/Quagga.conf - fi - # if /etc/quagga exists, point /etc/quagga/vtysh.conf -> CONF_DIR - if [ "$CONF_DIR" != "/etc/quagga" ] && [ -d /etc/quagga ] && [ ! -e /etc/quagga/vtysh.conf ]; then - ln -s $CONF_DIR/vtysh.conf /etc/quagga/vtysh.conf - fi -} - -bootdaemon() -{ - QUAGGA_SBIN_DIR=$(searchforprog $1 $QUAGGA_SBIN_SEARCH) - if [ "z$QUAGGA_SBIN_DIR" = "z" ]; then - echo "ERROR: Quagga's '$1' daemon not found in search path:" - echo " $QUAGGA_SBIN_SEARCH" - return 1 - fi - - flags="" - - if [ "$1" = "xpimd" ] && \ - grep -E -q '^[[:space:]]*router[[:space:]]+pim6[[:space:]]*$' $QUAGGA_CONF; then - flags="$flags -6" - fi - - $QUAGGA_SBIN_DIR/$1 $flags -d - if [ "$?" != "0" ]; then - echo "ERROR: Quagga's '$1' daemon failed to start!:" - return 1 - fi -} - -bootquagga() -{ - QUAGGA_BIN_DIR=$(searchforprog 'vtysh' $QUAGGA_BIN_SEARCH) - if [ "z$QUAGGA_BIN_DIR" = "z" ]; then - echo "ERROR: Quagga's 'vtysh' program not found in search path:" - echo " $QUAGGA_BIN_SEARCH" - return 1 - fi - - # fix /var/run/quagga permissions - id -u quagga 2>/dev/null >/dev/null - if [ "$?" = "0" ]; then - chown quagga $QUAGGA_STATE_DIR - fi - - bootdaemon "zebra" - for r in rip ripng ospf6 ospf bgp babel; do - if grep -q "^router \<${r}\>" $QUAGGA_CONF; then - bootdaemon "${r}d" - fi - done - - if grep -E -q '^[[:space:]]*router[[:space:]]+pim6?[[:space:]]*$' $QUAGGA_CONF; then - bootdaemon "xpimd" - fi - - $QUAGGA_BIN_DIR/vtysh -b -} - -if [ "$1" != "zebra" ]; then - echo "WARNING: '$1': all Quagga daemons are launched by the 'zebra' service!" - exit 1 -fi -confcheck -bootquagga -]]> - - - - - - - pidof ospfd - - - killall ospfd - - - - - pidof ospf6d - - - killall ospf6d - - - - - sh ipforward.sh - - - - - diff --git a/gui/configs/sample1.imn b/gui/configs/sample1.imn index 912f1e71..da646f1d 100644 --- a/gui/configs/sample1.imn +++ b/gui/configs/sample1.imn @@ -106,54 +106,6 @@ node n5 { interface-peer {eth0 n10} interface-peer {eth1 n15} services {zebra OSPFv2 OSPFv3MDR IPForward} - custom-config { - custom-config-id service:zebra - custom-command zebra - config { - files=('/usr/local/etc/quagga/Quagga.conf', 'quaggaboot.sh', ) - } - } - custom-config { - custom-config-id service:zebra:/usr/local/etc/quagga/Quagga.conf - custom-command /usr/local/etc/quagga/Quagga.conf - config { - interface eth0 - ip address 10.0.0.5/32 - ipv6 address a::3/128 - ipv6 ospf6 instance-id 65 - ipv6 ospf6 hello-interval 2 - ipv6 ospf6 dead-interval 6 - ipv6 ospf6 retransmit-interval 5 - ipv6 ospf6 network manet-designated-router - ipv6 ospf6 diffhellos - ipv6 ospf6 adjacencyconnectivity uniconnected - ipv6 ospf6 lsafullness mincostlsa - ! - interface eth1 - ip address 10.0.6.2/24 - !ip ospf hello-interval 2 - !ip ospf dead-interval 6 - !ip ospf retransmit-interval 5 - !ip ospf network point-to-point - ipv6 address a:6::2/64 - ! - router ospf - router-id 10.0.0.5 - network 10.0.0.5/32 area 0 - network 10.0.6.0/24 area 0 - redistribute connected metric-type 1 - redistribute ospf6 metric-type 1 - ! - router ospf6 - router-id 10.0.0.5 - interface eth0 area 0.0.0.0 - redistribute connected - redistribute ospf - ! - - - } - } } node n6 { From 1ce6e513180d5c331e3a377aeb022e8d8fc7ff8f Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Fri, 29 Oct 2021 14:56:50 -0700 Subject: [PATCH 097/110] pygui: updates to properly save and load canvas size for each canvas separately --- daemon/core/gui/dialogs/canvassizeandscale.py | 4 ++-- daemon/core/gui/graph/graph.py | 4 ++++ daemon/core/gui/graph/manager.py | 20 ++++++------------- daemon/core/gui/menubar.py | 2 +- 4 files changed, 13 insertions(+), 17 deletions(-) diff --git a/daemon/core/gui/dialogs/canvassizeandscale.py b/daemon/core/gui/dialogs/canvassizeandscale.py index 8155cb57..863d1174 100644 --- a/daemon/core/gui/dialogs/canvassizeandscale.py +++ b/daemon/core/gui/dialogs/canvassizeandscale.py @@ -24,7 +24,7 @@ class SizeAndScaleDialog(Dialog): super().__init__(app, "Canvas Size and Scale") self.manager: CanvasManager = self.app.manager self.section_font: font.Font = font.Font(weight=font.BOLD) - width, height = self.manager.current_dimensions + width, height = self.manager.current().current_dimensions self.pixel_width: tk.IntVar = tk.IntVar(value=width) self.pixel_height: tk.IntVar = tk.IntVar(value=height) location = self.app.core.session.location @@ -189,7 +189,7 @@ class SizeAndScaleDialog(Dialog): def click_apply(self) -> None: width, height = self.pixel_width.get(), self.pixel_height.get() - self.manager.redraw_canvases((width, height)) + self.manager.redraw_canvas((width, height)) location = self.app.core.session.location location.x = self.x.get() location.y = self.y.get() diff --git a/daemon/core/gui/graph/graph.py b/daemon/core/gui/graph/graph.py index 1362a2e0..e3225a4d 100644 --- a/daemon/core/gui/graph/graph.py +++ b/daemon/core/gui/graph/graph.py @@ -827,6 +827,7 @@ class CanvasGraph(tk.Canvas): wallpaper=wallpaper_path, wallpaper_style=self.scale_option.get(), fit_image=self.adjust_to_dim.get(), + dimensions=self.current_dimensions, ) def parse_metadata(self, config: Dict[str, Any]) -> None: @@ -834,6 +835,9 @@ class CanvasGraph(tk.Canvas): self.adjust_to_dim.set(fit_image) wallpaper_style = config.get("wallpaper_style", 1) self.scale_option.set(wallpaper_style) + dimensions = config.get("dimensions") + if dimensions: + self.redraw_canvas(dimensions) wallpaper = config.get("wallpaper") if wallpaper: wallpaper = Path(wallpaper) diff --git a/daemon/core/gui/graph/manager.py b/daemon/core/gui/graph/manager.py index 08352a25..8acce4c9 100644 --- a/daemon/core/gui/graph/manager.py +++ b/daemon/core/gui/graph/manager.py @@ -89,7 +89,6 @@ class CanvasManager: self.app.guiconfig.preferences.width, self.app.guiconfig.preferences.height, ) - self.current_dimensions: Tuple[int, int] = self.default_dimensions self.show_node_labels: ShowVar = ShowNodeLabels( self, tags.NODE_LABEL, value=True ) @@ -274,19 +273,15 @@ class CanvasManager: if not self.canvases: self.add_canvas() - def redraw_canvases(self, dimensions: Tuple[int, int]) -> None: - for canvas in self.canvases.values(): - canvas.redraw_canvas(dimensions) - if canvas.wallpaper: - canvas.redraw_wallpaper() + def redraw_canvas(self, dimensions: Tuple[int, int]) -> None: + canvas = self.current() + canvas.redraw_canvas(dimensions) + if canvas.wallpaper: + canvas.redraw_wallpaper() def get_metadata(self) -> Dict[str, Any]: canvases = [x.get_metadata() for x in self.all()] - return dict( - gridlines=self.app.manager.show_grid.get(), - dimensions=self.app.manager.current_dimensions, - canvases=canvases, - ) + return dict(gridlines=self.show_grid.get(), canvases=canvases) def parse_metadata_canvas(self, metadata: Dict[str, Any]) -> None: # canvas setting @@ -296,11 +291,8 @@ class CanvasManager: return canvas_config = json.loads(canvas_config) # get configured dimensions and gridlines option - dimensions = self.default_dimensions - dimensions = canvas_config.get("dimensions", dimensions) gridlines = canvas_config.get("gridlines", True) self.show_grid.set(gridlines) - self.redraw_canvases(dimensions) # get background configurations for canvas_config in canvas_config.get("canvases", []): diff --git a/daemon/core/gui/menubar.py b/daemon/core/gui/menubar.py index ff0c298a..e2df2f92 100644 --- a/daemon/core/gui/menubar.py +++ b/daemon/core/gui/menubar.py @@ -453,7 +453,7 @@ class Menubar(tk.Menu): dialog.show() def click_autogrid(self) -> None: - width, height = self.manager.current_dimensions + width, height = self.manager.current().current_dimensions padding = (images.NODE_SIZE / 2) + 10 layout_size = padding + images.NODE_SIZE col_count = width // layout_size From b78c07bd24756054fd5652737466fb453950be37 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Thu, 11 Nov 2021 10:59:11 -0800 Subject: [PATCH 098/110] docs: update node types to include lxc/docker type documentation, instead of being hidden within examples --- daemon/examples/docker/daemon.json | 5 -- .../docker/README.md => docs/docker.md | 47 +++++++++++-------- docs/index.md | 6 +-- daemon/examples/lxd/README.md => docs/lxc.md | 14 ++---- docs/nodetypes.md | 32 +++++++++---- 5 files changed, 58 insertions(+), 46 deletions(-) delete mode 100644 daemon/examples/docker/daemon.json rename daemon/examples/docker/README.md => docs/docker.md (59%) rename daemon/examples/lxd/README.md => docs/lxc.md (64%) diff --git a/daemon/examples/docker/daemon.json b/daemon/examples/docker/daemon.json deleted file mode 100644 index 8fefb9ab..00000000 --- a/daemon/examples/docker/daemon.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "bridge": "none", - "iptables": false - -} diff --git a/daemon/examples/docker/README.md b/docs/docker.md similarity index 59% rename from daemon/examples/docker/README.md rename to docs/docker.md index 17c6cb90..0c730369 100644 --- a/daemon/examples/docker/README.md +++ b/docs/docker.md @@ -1,28 +1,40 @@ -# Docker Support +# Docker Node Support -Information on how Docker can be leveraged and included to create -nodes based on Docker containers and images to interface with -existing CORE nodes, when needed. +## Overview + +Provided below is some information for helping setup and use Docker +nodes within a CORE scenario. ## Installation +### Debian Systems + ```shell sudo apt install docker.io ``` +### RHEL Systems + + ## Configuration Custom configuration required to avoid iptable rules being added and removing the need for the default docker network, since core will be orchestrating connections between nodes. -Place the file below in **/etc/docker/** -* daemon.json +Place the file below in **/etc/docker/docker.json** + +```json +{ + "bridge": "none", + "iptables": false +} +``` ## Group Setup -To use Docker nodes within the python GUI, you will need to make sure the user running the GUI is a member of the -docker group. +To use Docker nodes within the python GUI, you will need to make sure the +user running the GUI is a member of the docker group. ```shell # add group if does not exist @@ -35,18 +47,10 @@ sudo usermod -aG docker $USER newgrp docker ``` -## Tools and Versions Tested With +## Image Requirements -* Docker version 18.09.5, build e8ff056 -* nsenter from util-linux 2.31.1 - -## Examples - -This directory provides a few small examples creating Docker nodes -and linking them to themselves or with standard CORE nodes. - -Images used by nodes need to have networking tools installed for CORE to automate -setup and configuration of the container. +Images used by Docker nodes in CORE need to have networking tools installed for +CORE to automate setup and configuration of the network within the container. Example Dockerfile: ``` @@ -59,3 +63,8 @@ Build image: ```shell sudo docker build -t . ``` + +## Tools and Versions Tested With + +* Docker version 18.09.5, build e8ff056 +* nsenter from util-linux 2.31.1 diff --git a/docs/index.md b/docs/index.md index 5814e141..05882860 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,15 +20,15 @@ networking scenarios, security studies, and increasing the size of physical test | Topic | Description| |-------|------------| -|[Architecture](architecture.md)|Overview of the architecture| |[Installation](install.md)|How to install CORE and its requirements| +|[Architecture](architecture.md)|Overview of the architecture| +|[Node Types](nodetypes.md)|Overview of node types supported within CORE| |[GUI](gui.md)|How to use the GUI| |[(BETA) Python GUI](pygui.md)|How to use the BETA python based GUI| |[Python API](python.md)|Covers how to control core directly using python| |[gRPC API](grpc.md)|Covers how control core using gRPC| |[Distributed](distributed.md)|Details for running CORE across multiple servers| -|[Node Types](nodetypes.md)|Overview of node types supported within CORE| -|[CTRLNET](ctrlnet.md)|How to use control networks to communicate with nodes from host| +|[Control Network](ctrlnet.md)|How to use control networks to communicate with nodes from host| |[Services](services.md)|Overview of provided services and creating custom ones| |[EMANE](emane.md)|Overview of EMANE integration and integrating custom EMANE models| |[Performance](performance.md)|Notes on performance when using CORE| diff --git a/daemon/examples/lxd/README.md b/docs/lxc.md similarity index 64% rename from daemon/examples/lxd/README.md rename to docs/lxc.md index 4e3952ee..0df9e9d0 100644 --- a/daemon/examples/lxd/README.md +++ b/docs/lxc.md @@ -1,11 +1,12 @@ -# LXD Support +# LXC Support -Information on how LXD can be leveraged and included to create -nodes based on LXC containers and images to interface with -existing CORE nodes, when needed. +LXC nodes are provided by way of LXD to create nodes using predefined +images and provide file system separation. ## Installation +### Debian Systems + ```shell sudo snap install lxd ``` @@ -38,8 +39,3 @@ newgrp lxd * LXD 3.14 * nsenter from util-linux 2.31.1 - -## Examples - -This directory provides a few small examples creating LXC nodes -using LXD and linking them to themselves or with standard CORE nodes. diff --git a/docs/nodetypes.md b/docs/nodetypes.md index ffa27855..ad8e3ff9 100644 --- a/docs/nodetypes.md +++ b/docs/nodetypes.md @@ -5,18 +5,30 @@ ## Overview -Different node types can be configured in CORE, and each node type has a -*machine type* that indicates how the node will be represented at run time. -Different machine types allow for different options. +Different node types can be used within CORE, each with their own +tradeoffs and functionality. -## Netns Nodes +## CORE Nodes -The *netns* machine type is the default. This is for nodes that will be -backed by Linux network namespaces. This machine type uses very little -system resources in order to emulate a network. Another reason this is -designated as the default machine type is because this technology typically -requires no changes to the kernel; it is available out-of-the-box from the -latest mainstream Linux distributions. +CORE nodes are the standard node type typically used in CORE. They are +backed by Linux network namespaces. They use very little system resources +in order to emulate a network. They do however share the hosts file system +as they do not get their own. CORE nodes will have a directory uniquely +created for them as a place to keep their files and mounted directories +(`/tmp/pycore./ Date: Mon, 15 Nov 2021 14:33:05 -0800 Subject: [PATCH 099/110] docs: adding initial documentation to help cover using config services --- docs/configservices.md | 206 +++++++++++++++++++++++++++++++++++++++++ docs/index.md | 1 + docs/services.md | 5 +- 3 files changed, 209 insertions(+), 3 deletions(-) create mode 100644 docs/configservices.md diff --git a/docs/configservices.md b/docs/configservices.md new file mode 100644 index 00000000..93718f3f --- /dev/null +++ b/docs/configservices.md @@ -0,0 +1,206 @@ +# CORE Config Services + +* Table of Contents +{:toc} + +## Overview + +Config services are a newer version of services for CORE, that leverage a +templating engine, for more robust service file creation. They also +have the power of configuration key/value pairs that values that can be +defined and displayed within the GUI, to help further tweak a service, +as needed. + +CORE services are a convenience for creating reusable dynamic scripts +to run on nodes, for carrying out specific task(s). + +This boilds down to the following functions: +* generating files the service will use, either directly for commands or for configuration +* command(s) for starting a service +* command(s) for validating a service +* command(s) for stopping a service + +Most CORE nodes will have a default set of services to run, associated with +them. You can however customize the set of services a node will use. Or even +further define a new node type within the GUI, with a set of services, that +will allow quickly dragging and dropping that node type during creation. + +## Available Services + +| Service Group | Services | +|---|---| +|[BIRD](services/bird.md)|BGP, OSPF, RADV, RIP, Static| +|[EMANE](services/emane.md)|Transport Service| +|[FRR](services/frr.md)|BABEL, BGP, OSPFv2, OSPFv3, PIMD, RIP, RIPNG, Zebra| +|[NRL](services/nrl.md)|arouted, MGEN Sink, MGEN Actor, NHDP, OLSR, OLSRORG, OLSRv2, SMF| +|[Quagga](services/quagga.md)|BABEL, BGP, OSPFv2, OSPFv3, OSPFv3 MDR, RIP, RIPNG, XPIMD, Zebra| +|[SDN](services/sdn.md)|OVS, RYU| +|[Security](services/security.md)|Firewall, IPsec, NAT, VPN Client, VPN Server| +|[Utility](services/utility.md)|ATD, Routing Utils, DHCP, FTP, IP Forward, PCAP, RADVD, SSF, UCARP| +|[XORP](services/xorp.md)|BGP, OLSR, OSPFv2, OSPFv3, PIMSM4, PIMSM6, RIP, RIPNG, Router Manager| + +## Node Types and Default Services + +Here are the default node types and their services: + +| Node Type | Services | +|---|---| +| *router* | zebra, OSFPv2, OSPFv3, and IPForward services for IGP link-state routing. | +| *PC* | DefaultRoute service for having a default route when connected directly to a router. | +| *mdr* | zebra, OSPFv3MDR, and IPForward services for wireless-optimized MANET Designated Router routing. | +| *prouter* | a physical router, having the same default services as the *router* node type; for incorporating Linux testbed machines into an emulation. | + +Configuration files can be automatically generated by each service. For +example, CORE automatically generates routing protocol configuration for the +router nodes in order to simplify the creation of virtual networks. + +To change the services associated with a node, double-click on the node to +invoke its configuration dialog and click on the *Services...* button, +or right-click a node a choose *Services...* from the menu. +Services are enabled or disabled by clicking on their names. The button next to +each service name allows you to customize all aspects of this service for this +node. For example, special route redistribution commands could be inserted in +to the Quagga routing configuration associated with the zebra service. + +To change the default services associated with a node type, use the Node Types +dialog available from the *Edit* button at the end of the Layer-3 nodes +toolbar, or choose *Node types...* from the *Session* menu. Note that +any new services selected are not applied to existing nodes if the nodes have +been customized. + +The node types are saved in the GUI config file **~/.coregui/config.yaml**. +Keep this in mind when changing the default services for +existing node types; it may be better to simply create a new node type. It is +recommended that you do not change the default built-in node types. + +## New Services + +Services can save time required to configure nodes, especially if a number +of nodes require similar configuration procedures. New services can be +introduced to automate tasks. + +### Creating New Services + +1. Modify the example service shown below + to do what you want. It could generate config/script files, mount per-node + directories, start processes/scripts, etc. Your file can define one or more + classes to be imported. You can create multiple Python files that will be imported. + +2. Put these files in a directory such as ~/.coregui/custom_services + Note that the last component of this directory name **myservices** should not + be named something like **services** which conflicts with an existing module. + +3. Add a **custom_config_services_dir = ~/.coregui/custom_services** entry to the + /etc/core/core.conf file. + + **NOTE:** + The directory name used in **custom_services_dir** should be unique and + should not correspond to + any existing Python module name. For example, don't use the name **subprocess** + or **services**. + +4. Restart the CORE daemon (core-daemon). Any import errors (Python syntax) + should be displayed in the terminal (or service log, like journalctl). + +5. Start using your custom service on your nodes. You can create a new node + type that uses your service, or change the default services for an existing + node type, or change individual nodes. . + +### Example Custom Service + +Below is the skeleton for a custom service with some documentation. Most +people would likely only setup the required class variables **(name/group)**. +Then define the **files** to generate and implement the +**get_text_template** function to dynamically create the files wanted. Finally, +the **startup** commands would be supplied, which typically tend to be +running the shell files generated. + +```python +from typing import Dict, List + +from core.config import Configuration +from core.configservice.base import ConfigService, ConfigServiceMode, ShadowDir +from core.emulator.enumerations import ConfigDataTypes + +# class that subclasses ConfigService +class ExampleService(ConfigService): + # unique name for your service within CORE + name: str = "Example" + # the group your service is associated with, used for display in GUI + group: str = "ExampleGroup" + # directories that the service should shadow mount, hiding the system directory + directories: List[str] = [ + "/usr/local/core", + ] + # files that this service should generate, defaults to nodes home directory + # or can provide an absolute path to a mounted directory + files: List[str] = [ + "example-start.sh", + "/usr/local/core/file1", + ] + # executables that should exist on path, that this service depends on + executables: List[str] = [] + # other services that this service depends on, can be used to define service start order + dependencies: List[str] = [] + # commands to run to start this service + startup: List[str] = [] + # commands to run to validate this service + validate: List[str] = [] + # commands to run to stop this service + shutdown: List[str] = [] + # validation mode, blocking, non-blocking, and timer + validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING + # configurable values that this service can use, for file generation + default_configs: List[Configuration] = [ + Configuration(id="value1", type=ConfigDataTypes.STRING, label="Text"), + Configuration(id="value2", type=ConfigDataTypes.BOOL, label="Boolean"), + Configuration( + id="value3", + type=ConfigDataTypes.STRING, + label="Multiple Choice", + options=["value1", "value2", "value3"], + ), + ] + # sets of values to set for the configuration defined above, can be used to + # provide convenient sets of values to typically use + 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"}, + } + # defines directories that this service can help shadow within a node + shadow_directories: List[ShadowDir] = [ + ShadowDir(path="/user/local/core", src="/opt/core") + ] + + def get_text_template(self, name: str) -> str: + if name == "example-start.sh": + return """ + # sample script 1 + # node id(${node.id}) name(${node.name}) + # config: ${config} + echo hello + """ +``` + +#### Validation Mode + +Validation modes are used to determine if a service has started up successfully. + +* blocking - startup commands are expected to run til completion and return 0 exit code +* non-blocking - startup commands are ran, but do not wait for completion +* timer - startup commands are ran, and an arbitrary amount of time is waited to consider started + +#### Shadow Directories + +Shadow directories provide a convenience for copying a directory and the files within +it to a nodes home directory, to allow a unique set of per node files. + +* `ShadowDir(path="/user/local/core")` - copies files at the given location into the node +* `ShadowDir(path="/user/local/core", src="/opt/core")` - copies files to the given location, + but sourced from the provided location +* `ShadowDir(path="/user/local/core", templates=True)` - copies files and treats them as + templates for generation +* `ShadowDir(path="/user/local/core", has_node_paths=True)` - copies files from the given + location, and looks for unique node names directories within it, using a directory named + default, when not preset diff --git a/docs/index.md b/docs/index.md index 05882860..0bfe5a26 100644 --- a/docs/index.md +++ b/docs/index.md @@ -29,6 +29,7 @@ networking scenarios, security studies, and increasing the size of physical test |[gRPC API](grpc.md)|Covers how control core using gRPC| |[Distributed](distributed.md)|Details for running CORE across multiple servers| |[Control Network](ctrlnet.md)|How to use control networks to communicate with nodes from host| +|[Config Services](configservices.md)|Overview of provided config services and creating custom ones| |[Services](services.md)|Overview of provided services and creating custom ones| |[EMANE](emane.md)|Overview of EMANE integration and integrating custom EMANE models| |[Performance](performance.md)|Notes on performance when using CORE| diff --git a/docs/services.md b/docs/services.md index ccd8e9a5..477a828c 100644 --- a/docs/services.md +++ b/docs/services.md @@ -149,12 +149,11 @@ ideas for a service before adding a new service type. to do what you want. It could generate config/script files, mount per-node directories, start processes/scripts, etc. sample.py is a Python file that defines one or more classes to be imported. You can create multiple Python - files that will be imported. Add any new filenames to the __init__.py file. + files that will be imported. 2. Put these files in a directory such as /home/username/.core/myservices Note that the last component of this directory name **myservices** should not - be named something like **services** which conflicts with an existing Python - name (the syntax 'from myservices import *' is used). + be named something like **services** which conflicts with an existing module. 3. Add a **custom_services_dir = /home/username/.core/myservices** entry to the /etc/core/core.conf file. From 22e92111d04378da8c5a7ac9381341d5dd46ee94 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Mon, 15 Nov 2021 16:40:30 -0800 Subject: [PATCH 100/110] daemon: updates to provide config types for configurable values, without the need to specify the enumerated type --- daemon/core/config.py | 38 +++++- .../securityservices/services.py | 45 ++----- daemon/core/emane/emanemodel.py | 13 +- daemon/core/emane/models/bypass.py | 6 +- daemon/core/emane/models/tdma.py | 6 +- daemon/core/emulator/sessionconfig.py | 111 +++++------------- daemon/core/location/mobility.py | 100 ++++------------ daemon/tests/test_conf.py | 8 +- daemon/tests/test_config_services.py | 14 +-- docs/configservices.md | 14 +-- 10 files changed, 115 insertions(+), 240 deletions(-) diff --git a/daemon/core/config.py b/daemon/core/config.py index 7d04947e..b705e8b6 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -36,7 +36,7 @@ class ConfigGroup: @dataclass class Configuration: """ - Represents a configuration options. + Represents a configuration option. """ id: str @@ -71,6 +71,42 @@ class Configuration: ) +@dataclass +class ConfigBool(Configuration): + """ + Represents a boolean configuration option. + """ + + type: ConfigDataTypes = ConfigDataTypes.BOOL + + +@dataclass +class ConfigFloat(Configuration): + """ + Represents a float configuration option. + """ + + type: ConfigDataTypes = ConfigDataTypes.FLOAT + + +@dataclass +class ConfigInt(Configuration): + """ + Represents an integer configuration option. + """ + + type: ConfigDataTypes = ConfigDataTypes.INT32 + + +@dataclass +class ConfigString(Configuration): + """ + Represents a string configuration option. + """ + + type: ConfigDataTypes = ConfigDataTypes.STRING + + class ConfigurableOptions: """ Provides a base for defining configuration options within CORE. diff --git a/daemon/core/configservices/securityservices/services.py b/daemon/core/configservices/securityservices/services.py index 9da27010..e866617c 100644 --- a/daemon/core/configservices/securityservices/services.py +++ b/daemon/core/configservices/securityservices/services.py @@ -1,8 +1,7 @@ from typing import Any, Dict, List -from core.config import Configuration +from core.config import ConfigString, Configuration from core.configservice.base import ConfigService, ConfigServiceMode -from core.emulator.enumerations import ConfigDataTypes GROUP_NAME: str = "Security" @@ -19,24 +18,9 @@ class VpnClient(ConfigService): shutdown: List[str] = ["killall openvpn"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING default_configs: List[Configuration] = [ - Configuration( - id="keydir", - type=ConfigDataTypes.STRING, - label="Key Dir", - default="/etc/core/keys", - ), - Configuration( - id="keyname", - type=ConfigDataTypes.STRING, - label="Key Name", - default="client1", - ), - Configuration( - id="server", - type=ConfigDataTypes.STRING, - label="Server", - default="10.0.2.10", - ), + ConfigString(id="keydir", label="Key Dir", default="/etc/core/keys"), + ConfigString(id="keyname", label="Key Name", default="client1"), + ConfigString(id="server", label="Server", default="10.0.2.10"), ] modes: Dict[str, Dict[str, str]] = {} @@ -53,24 +37,9 @@ class VpnServer(ConfigService): shutdown: List[str] = ["killall openvpn"] validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING default_configs: List[Configuration] = [ - Configuration( - id="keydir", - type=ConfigDataTypes.STRING, - label="Key Dir", - default="/etc/core/keys", - ), - Configuration( - id="keyname", - type=ConfigDataTypes.STRING, - label="Key Name", - default="server", - ), - Configuration( - id="subnet", - type=ConfigDataTypes.STRING, - label="Subnet", - default="10.0.200.0", - ), + ConfigString(id="keydir", label="Key Dir", default="/etc/core/keys"), + ConfigString(id="keyname", label="Key Name", default="server"), + ConfigString(id="subnet", label="Subnet", default="10.0.200.0"), ] modes: Dict[str, Dict[str, str]] = {} diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 21fcccb3..cc5b0f4d 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -5,10 +5,9 @@ import logging from pathlib import Path from typing import Dict, List, Optional, Set -from core.config import ConfigGroup, Configuration +from core.config import ConfigBool, ConfigGroup, ConfigString, Configuration from core.emane import emanemanifest from core.emulator.data import LinkOptions -from core.emulator.enumerations import ConfigDataTypes from core.errors import CoreError from core.location.mobility import WirelessModel from core.nodes.interface import CoreInterface @@ -55,13 +54,9 @@ class EmaneModel(WirelessModel): # support for external configurations external_config: List[Configuration] = [ - Configuration("external", ConfigDataTypes.BOOL, default="0"), - Configuration( - "platformendpoint", ConfigDataTypes.STRING, default="127.0.0.1:40001" - ), - Configuration( - "transportendpoint", ConfigDataTypes.STRING, default="127.0.0.1:50002" - ), + ConfigBool(id="external", default="0"), + ConfigString(id="platformendpoint", default="127.0.0.1:40001"), + ConfigString(id="transportendpoint", default="127.0.0.1:50002"), ] config_ignore: Set[str] = set() diff --git a/daemon/core/emane/models/bypass.py b/daemon/core/emane/models/bypass.py index 67b7707d..25841114 100644 --- a/daemon/core/emane/models/bypass.py +++ b/daemon/core/emane/models/bypass.py @@ -4,9 +4,8 @@ EMANE Bypass model for CORE from pathlib import Path from typing import List, Set -from core.config import Configuration +from core.config import ConfigBool, Configuration from core.emane import emanemodel -from core.emulator.enumerations import ConfigDataTypes class EmaneBypassModel(emanemodel.EmaneModel): @@ -18,9 +17,8 @@ class EmaneBypassModel(emanemodel.EmaneModel): # mac definitions mac_library: str = "bypassmaclayer" mac_config: List[Configuration] = [ - Configuration( + ConfigBool( id="none", - type=ConfigDataTypes.BOOL, default="0", label="There are no parameters for the bypass model.", ) diff --git a/daemon/core/emane/models/tdma.py b/daemon/core/emane/models/tdma.py index c23e3d73..c6ac631b 100644 --- a/daemon/core/emane/models/tdma.py +++ b/daemon/core/emane/models/tdma.py @@ -7,10 +7,9 @@ from pathlib import Path from typing import Set from core import constants, utils -from core.config import Configuration +from core.config import ConfigString from core.emane import emanemodel from core.emane.nodes import EmaneNet -from core.emulator.enumerations import ConfigDataTypes from core.nodes.interface import CoreInterface logger = logging.getLogger(__name__) @@ -38,9 +37,8 @@ class EmaneTdmaModel(emanemodel.EmaneModel): / "share/emane/xml/models/mac/tdmaeventscheduler/tdmabasemodelpcr.xml" ) super().load(emane_prefix) - config_item = Configuration( + config_item = ConfigString( id=cls.schedule_name, - type=ConfigDataTypes.STRING, default=str(cls.default_schedule), label="TDMA schedule file (core)", ) diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py index f40161cb..893f71c4 100644 --- a/daemon/core/emulator/sessionconfig.py +++ b/daemon/core/emulator/sessionconfig.py @@ -1,7 +1,14 @@ from typing import Any, List -from core.config import ConfigurableManager, ConfigurableOptions, Configuration -from core.emulator.enumerations import ConfigDataTypes, RegisterTlvs +from core.config import ( + ConfigBool, + ConfigInt, + ConfigString, + ConfigurableManager, + ConfigurableOptions, + Configuration, +) +from core.emulator.enumerations import RegisterTlvs from core.plugins.sdt import Sdt @@ -12,89 +19,27 @@ class SessionConfig(ConfigurableManager, ConfigurableOptions): name: str = "session" options: List[Configuration] = [ - Configuration( - id="controlnet", type=ConfigDataTypes.STRING, label="Control Network" + ConfigString(id="controlnet", label="Control Network"), + ConfigString(id="controlnet0", label="Control Network 0"), + ConfigString(id="controlnet1", label="Control Network 1"), + ConfigString(id="controlnet2", label="Control Network 2"), + ConfigString(id="controlnet3", label="Control Network 3"), + ConfigString(id="controlnet_updown_script", label="Control Network Script"), + ConfigBool(id="enablerj45", default="1", label="Enable RJ45s"), + ConfigBool(id="preservedir", default="0", label="Preserve session dir"), + ConfigBool(id="enablesdt", default="0", label="Enable SDT3D output"), + ConfigString(id="sdturl", default=Sdt.DEFAULT_SDT_URL, label="SDT3D URL"), + ConfigBool(id="ovs", default="0", label="Enable OVS"), + ConfigInt(id="platform_id_start", default="1", label="EMANE Platform ID Start"), + ConfigInt(id="nem_id_start", default="1", label="EMANE NEM ID Start"), + ConfigBool(id="link_enabled", default="1", label="EMANE Links?"), + ConfigInt( + id="loss_threshold", default="30", label="EMANE Link Loss Threshold (%)" ), - Configuration( - id="controlnet0", type=ConfigDataTypes.STRING, label="Control Network 0" - ), - Configuration( - id="controlnet1", type=ConfigDataTypes.STRING, label="Control Network 1" - ), - Configuration( - id="controlnet2", type=ConfigDataTypes.STRING, label="Control Network 2" - ), - Configuration( - id="controlnet3", type=ConfigDataTypes.STRING, label="Control Network 3" - ), - Configuration( - id="controlnet_updown_script", - type=ConfigDataTypes.STRING, - label="Control Network Script", - ), - Configuration( - id="enablerj45", - type=ConfigDataTypes.BOOL, - default="1", - label="Enable RJ45s", - ), - Configuration( - id="preservedir", - type=ConfigDataTypes.BOOL, - default="0", - label="Preserve session dir", - ), - Configuration( - id="enablesdt", - type=ConfigDataTypes.BOOL, - default="0", - label="Enable SDT3D output", - ), - Configuration( - id="sdturl", - type=ConfigDataTypes.STRING, - default=Sdt.DEFAULT_SDT_URL, - label="SDT3D URL", - ), - Configuration( - id="ovs", type=ConfigDataTypes.BOOL, default="0", label="Enable OVS" - ), - Configuration( - id="platform_id_start", - type=ConfigDataTypes.INT32, - default="1", - label="EMANE Platform ID Start", - ), - Configuration( - id="nem_id_start", - type=ConfigDataTypes.INT32, - default="1", - label="EMANE NEM ID Start", - ), - Configuration( - id="link_enabled", - type=ConfigDataTypes.BOOL, - default="1", - label="EMANE Links?", - ), - Configuration( - id="loss_threshold", - type=ConfigDataTypes.INT32, - default="30", - label="EMANE Link Loss Threshold (%)", - ), - Configuration( - id="link_interval", - type=ConfigDataTypes.INT32, - default="1", - label="EMANE Link Check Interval (sec)", - ), - Configuration( - id="link_timeout", - type=ConfigDataTypes.INT32, - default="4", - label="EMANE Link Timeout (sec)", + ConfigInt( + id="link_interval", default="1", label="EMANE Link Check Interval (sec)" ), + ConfigInt(id="link_timeout", default="4", label="EMANE Link Timeout (sec)"), ] config_type: RegisterTlvs = RegisterTlvs.UTILITY diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index ad7fc821..a92eae3d 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -12,16 +12,18 @@ from pathlib import Path from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union from core import utils -from core.config import ConfigGroup, ConfigurableOptions, Configuration, ModelManager +from core.config import ( + ConfigBool, + ConfigGroup, + ConfigInt, + ConfigString, + ConfigurableOptions, + Configuration, + ModelManager, +) from core.emane.nodes import EmaneNet from core.emulator.data import EventData, LinkData, LinkOptions -from core.emulator.enumerations import ( - ConfigDataTypes, - EventTypes, - LinkTypes, - MessageFlags, - RegisterTlvs, -) +from core.emulator.enumerations import EventTypes, LinkTypes, MessageFlags, RegisterTlvs from core.errors import CoreError from core.executables import BASH from core.nodes.base import CoreNode @@ -274,39 +276,12 @@ class BasicRangeModel(WirelessModel): name: str = "basic_range" options: List[Configuration] = [ - Configuration( - id="range", - type=ConfigDataTypes.UINT32, - default="275", - label="wireless range (pixels)", - ), - Configuration( - id="bandwidth", - type=ConfigDataTypes.UINT64, - default="54000000", - label="bandwidth (bps)", - ), - Configuration( - id="jitter", - type=ConfigDataTypes.UINT64, - default="0", - label="transmission jitter (usec)", - ), - Configuration( - id="delay", - type=ConfigDataTypes.UINT64, - default="5000", - label="transmission delay (usec)", - ), - Configuration( - id="error", type=ConfigDataTypes.STRING, default="0", label="loss (%)" - ), - Configuration( - id="promiscuous", - type=ConfigDataTypes.BOOL, - default="0", - label="promiscuous mode", - ), + ConfigInt(id="range", default="275", label="wireless range (pixels)"), + ConfigInt(id="bandwidth", default="54000000", label="bandwidth (bps)"), + ConfigInt(id="jitter", default="0", label="transmission jitter (usec)"), + ConfigInt(id="delay", default="5000", label="transmission delay (usec)"), + ConfigString(id="error", default="0", label="loss (%)"), + ConfigBool(id="promiscuous", default="0", label="promiscuous mode"), ] @classmethod @@ -887,41 +862,14 @@ class Ns2ScriptedMobility(WayPointMobility): name: str = "ns2script" options: List[Configuration] = [ - Configuration( - id="file", type=ConfigDataTypes.STRING, label="mobility script file" - ), - Configuration( - id="refresh_ms", - type=ConfigDataTypes.UINT32, - default="50", - label="refresh time (ms)", - ), - Configuration(id="loop", type=ConfigDataTypes.BOOL, default="1", label="loop"), - Configuration( - id="autostart", - type=ConfigDataTypes.STRING, - label="auto-start seconds (0.0 for runtime)", - ), - Configuration( - id="map", - type=ConfigDataTypes.STRING, - label="node mapping (optional, e.g. 0:1,1:2,2:3)", - ), - Configuration( - id="script_start", - type=ConfigDataTypes.STRING, - label="script file to run upon start", - ), - Configuration( - id="script_pause", - type=ConfigDataTypes.STRING, - label="script file to run upon pause", - ), - Configuration( - id="script_stop", - type=ConfigDataTypes.STRING, - label="script file to run upon stop", - ), + ConfigString(id="file", label="mobility script file"), + ConfigInt(id="refresh_ms", default="50", label="refresh time (ms)"), + ConfigBool(id="loop", default="1", label="loop"), + ConfigString(id="autostart", label="auto-start seconds (0.0 for runtime)"), + ConfigString(id="map", label="node mapping (optional, e.g. 0:1,1:2,2:3)"), + ConfigString(id="script_start", label="script file to run upon start"), + ConfigString(id="script_pause", label="script file to run upon pause"), + ConfigString(id="script_stop", label="script file to run upon stop"), ] @classmethod diff --git a/daemon/tests/test_conf.py b/daemon/tests/test_conf.py index df16fb22..2c74841d 100644 --- a/daemon/tests/test_conf.py +++ b/daemon/tests/test_conf.py @@ -1,13 +1,12 @@ import pytest from core.config import ( + ConfigString, ConfigurableManager, ConfigurableOptions, - Configuration, ModelManager, ) from core.emane.models.ieee80211abg import EmaneIeee80211abgModel -from core.emulator.enumerations import ConfigDataTypes from core.emulator.session import Session from core.location.mobility import BasicRangeModel from core.nodes.network import WlanNode @@ -16,10 +15,7 @@ from core.nodes.network import WlanNode class TestConfigurableOptions(ConfigurableOptions): name1 = "value1" name2 = "value2" - options = [ - Configuration(id=name1, type=ConfigDataTypes.STRING, label=name1), - Configuration(id=name2, type=ConfigDataTypes.STRING, label=name2), - ] + options = [ConfigString(id=name1, label=name1), ConfigString(id=name2, label=name2)] class TestConf: diff --git a/daemon/tests/test_config_services.py b/daemon/tests/test_config_services.py index 598450c1..876b7f32 100644 --- a/daemon/tests/test_config_services.py +++ b/daemon/tests/test_config_services.py @@ -3,13 +3,12 @@ from unittest import mock import pytest -from core.config import Configuration +from core.config import ConfigBool, ConfigString from core.configservice.base import ( ConfigService, ConfigServiceBootError, ConfigServiceMode, ) -from core.emulator.enumerations import ConfigDataTypes from core.errors import CoreCommandError, CoreError TEMPLATE_TEXT = "echo hello" @@ -27,13 +26,10 @@ class MyService(ConfigService): shutdown = [f"pkill {files[0]}"] validation_mode = ConfigServiceMode.BLOCKING default_configs = [ - Configuration(id="value1", type=ConfigDataTypes.STRING, label="Text"), - Configuration(id="value2", type=ConfigDataTypes.BOOL, label="Boolean"), - Configuration( - id="value3", - type=ConfigDataTypes.STRING, - label="Multiple Choice", - options=["value1", "value2", "value3"], + ConfigString(id="value1", label="Text"), + ConfigBool(id="value2", label="Boolean"), + ConfigString( + id="value3", label="Multiple Choice", options=["value1", "value2", "value3"] ), ] modes = { diff --git a/docs/configservices.md b/docs/configservices.md index 93718f3f..42cf1478 100644 --- a/docs/configservices.md +++ b/docs/configservices.md @@ -118,9 +118,8 @@ running the shell files generated. ```python from typing import Dict, List -from core.config import Configuration +from core.config import ConfigString, ConfigBool, Configuration from core.configservice.base import ConfigService, ConfigServiceMode, ShadowDir -from core.emulator.enumerations import ConfigDataTypes # class that subclasses ConfigService class ExampleService(ConfigService): @@ -152,14 +151,9 @@ class ExampleService(ConfigService): validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING # configurable values that this service can use, for file generation default_configs: List[Configuration] = [ - Configuration(id="value1", type=ConfigDataTypes.STRING, label="Text"), - Configuration(id="value2", type=ConfigDataTypes.BOOL, label="Boolean"), - Configuration( - id="value3", - type=ConfigDataTypes.STRING, - label="Multiple Choice", - options=["value1", "value2", "value3"], - ), + ConfigString(id="value1", label="Text"), + ConfigBool(id="value2", label="Boolean"), + ConfigString(id="value3", label="Multiple Choice", options=["value1", "value2", "value3"]), ] # sets of values to set for the configuration defined above, can be used to # provide convenient sets of values to typically use From 49709261fc1f3da211195b89dc97609af0537948 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Nov 2021 10:15:58 -0800 Subject: [PATCH 101/110] daemon: updated wlan config option to use float type for range, instead of string --- daemon/core/location/mobility.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index a92eae3d..4c61c065 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -14,6 +14,7 @@ from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union from core import utils from core.config import ( ConfigBool, + ConfigFloat, ConfigGroup, ConfigInt, ConfigString, @@ -280,7 +281,7 @@ class BasicRangeModel(WirelessModel): ConfigInt(id="bandwidth", default="54000000", label="bandwidth (bps)"), ConfigInt(id="jitter", default="0", label="transmission jitter (usec)"), ConfigInt(id="delay", default="5000", label="transmission delay (usec)"), - ConfigString(id="error", default="0", label="loss (%)"), + ConfigFloat(id="error", default="0.0", label="loss (%)"), ConfigBool(id="promiscuous", default="0", label="promiscuous mode"), ] From c055103c1b23e19dc54eba5a353e1e135885f8e7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Nov 2021 12:52:31 -0800 Subject: [PATCH 102/110] readme: updated to add a quick start section --- README.md | 31 ++++++++++++++++++++++++++++++- docs/install.md | 9 ++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 62f21628..72f29f1b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ CORE: Common Open Research Emulator -Copyright (c)2005-2020 the Boeing Company. +Copyright (c)2005-2021 the Boeing Company. See the LICENSE file included in this distribution. @@ -14,6 +14,35 @@ networks to live networks. CORE consists of a GUI for drawing topologies of lightweight virtual machines, and Python modules for scripting network emulation. +## Quick Start + +The following should get you up and running on Ubuntu 18+ and CentOS 7+ +from a clean install, it will prompt you for sudo password. This would +install CORE into a python3 virtual environment and install +[OSPF MDR](https://github.com/USNavalResearchLaboratory/ospf-mdr) from source. +For more details on installation see [here](https://coreemu.github.io/core/install.html). + +```shell +git clone https://github.com/coreemu/core.git +cd core +``` + +Ubuntu: +```shell +./install.sh +``` + +CentOS: +```shell +./install.sh -p /usr +``` + +To additionally install EMANE: +```shell +reload +inv install-emane +``` + ## Documentation & Support We are leveraging GitHub hosted documentation and Discord for persistent diff --git a/docs/install.md b/docs/install.md index dd6ea8eb..4f2d6449 100644 --- a/docs/install.md +++ b/docs/install.md @@ -91,7 +91,14 @@ After the installation complete it will have installed the following scripts. Please make sure to uninstall any previous installations of CORE cleanly before proceeding to install. -Previous install was built from source: +Clearing out a current install from 7.0.0+, making sure to provide options +used for install (`-l` or `-p`). +```shell +cd +inv uninstall +``` + +Previous install was built from source for CORE release older than 7.0.0: ```shell cd sudo make uninstall From 2d0732d610544c7d58cf74afe331e43a761b36c6 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Nov 2021 14:01:46 -0800 Subject: [PATCH 103/110] install: updated version of ospf mdr to install, to the latest commits --- tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index e4fc1c05..c6e77807 100644 --- a/tasks.py +++ b/tasks.py @@ -15,7 +15,7 @@ from invoke import task, Context DAEMON_DIR: str = "daemon" DEFAULT_PREFIX: str = "/usr/local" EMANE_CHECKOUT: str = "v1.2.5" -OSPFMDR_CHECKOUT: str = "e2b4e416b7001c5dca0224fe728249c9688e4c7a" +OSPFMDR_CHECKOUT: str = "f21688cdcac30fb10b1ebac0063eb24e4583e9b4" REDHAT_LIKE = { "redhat", "fedora", From f090c98c54e4c1cce23f3046f7dbaa959f7e1133 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Tue, 16 Nov 2021 14:33:24 -0800 Subject: [PATCH 104/110] readme: fixed command added in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 72f29f1b..877218ab 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ CentOS: To additionally install EMANE: ```shell -reload +reset inv install-emane ``` From f98594e927f43e8a42768d739ea3b104ab4f1078 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 17 Nov 2021 15:55:05 -0800 Subject: [PATCH 105/110] install: updates to install docs and update to task for installing emane to be limited to installing for the python virtual environment, recommend official packages instead --- docs/install.md | 97 +++++++++++++++++++------------------------------ tasks.py | 30 ++++++--------- 2 files changed, 50 insertions(+), 77 deletions(-) diff --git a/docs/install.md b/docs/install.md index 4f2d6449..4745a2fe 100644 --- a/docs/install.md +++ b/docs/install.md @@ -24,6 +24,16 @@ Verified: * Ubuntu - 18.04, 20.04 * CentOS - 7.8, 8.0 +> **NOTE:** Ubuntu 20.04 requires installing legacy ebtables for WLAN functionality + +Enabling ebtables legacy: +```shell +sudo apt install ebtables +update-alternatives --set ebtables /usr/sbin/ebtables-legacy +``` + +> **NOTE:** CentOS 8 does not provide legacy ebtables support, WLAN will not function properly + > **NOTE:** CentOS 8 does not have the netem kernel mod available by default CentOS 8 Enabled netem: @@ -175,17 +185,18 @@ an installation to your use case. ```shell cd -# Usage: inv[oke] [--core-opts] install [--options] [other tasks here ...] +#Usage: inv[oke] [--core-opts] install [--options] [other tasks here ...] # -# Docstring: -# install core, poetry, scripts, service, and ospf mdr +#Docstring: +# install core, poetry, scripts, service, and ospf mdr # -# Options: -# -d, --dev install development mode -# -i STRING, --install-type=STRING -# -l, --local determines if core will install to local system, default is False -# -p STRING, --prefix=STRING prefix where scripts are installed, default is /usr/local -# -v, --verbose enable verbose +#Options: +# -d, --dev install development mode +# -i STRING, --install-type=STRING used to force an install type, can be one of the following (redhat, debian) +# -l, --local determines if core will install to local system, default is False +# -o, --[no-]ospf disable ospf installation +# -p STRING, --prefix=STRING prefix where scripts are installed, default is /usr/local +# -v, --verbose enable verbose # install virtual environment inv install -p @@ -218,49 +229,16 @@ python3