daemon: refactoring to remove usage of os.path where possible and pathlib.Path instead
This commit is contained in:
		
							parent
							
								
									d0a55dd471
								
							
						
					
					
						commit
						1c970bbe00
					
				
					 38 changed files with 520 additions and 606 deletions
				
			
		|  | @ -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 | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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 ( | ||||
|  |  | |||
|  | @ -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: | ||||
|         """ | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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@") | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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", | ||||
|  |  | |||
|  | @ -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 = [] | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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: | ||||
|         """ | ||||
|  |  | |||
|  | @ -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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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.<session id>/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 | ||||
|  |  | |||
|  | @ -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() | ||||
|  |  | |||
|  | @ -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() | ||||
| 
 | ||||
|  |  | |||
|  | @ -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( | ||||
|  |  | |||
|  | @ -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): | ||||
|  |  | |||
|  | @ -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: | ||||
|         """ | ||||
|  |  | |||
|  | @ -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}") | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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(): | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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( | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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", | ||||
|  |  | |||
|  | @ -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) | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue