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
|
node_dir = None
|
||||||
config_services = []
|
config_services = []
|
||||||
if isinstance(node, CoreNodeBase):
|
if isinstance(node, CoreNodeBase):
|
||||||
node_dir = node.nodedir
|
node_dir = str(node.nodedir)
|
||||||
config_services = [x for x in node.config_services]
|
config_services = [x for x in node.config_services]
|
||||||
channel = None
|
channel = None
|
||||||
if isinstance(node, CoreNode):
|
if isinstance(node, CoreNode):
|
||||||
channel = node.ctrlchnlname
|
channel = str(node.ctrlchnlname)
|
||||||
emane_model = None
|
emane_model = None
|
||||||
if isinstance(node, EmaneNet):
|
if isinstance(node, EmaneNet):
|
||||||
emane_model = node.model.name
|
emane_model = node.model.name
|
||||||
|
|
|
@ -6,6 +6,7 @@ import tempfile
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from concurrent import futures
|
from concurrent import futures
|
||||||
|
from pathlib import Path
|
||||||
from typing import Iterable, Optional, Pattern, Type
|
from typing import Iterable, Optional, Pattern, Type
|
||||||
|
|
||||||
import grpc
|
import grpc
|
||||||
|
@ -221,8 +222,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
# clear previous state and setup for creation
|
# clear previous state and setup for creation
|
||||||
session.clear()
|
session.clear()
|
||||||
if not os.path.exists(session.session_dir):
|
session.session_dir.mkdir(exist_ok=True)
|
||||||
os.mkdir(session.session_dir)
|
|
||||||
session.set_state(EventTypes.CONFIGURATION_STATE)
|
session.set_state(EventTypes.CONFIGURATION_STATE)
|
||||||
|
|
||||||
# location
|
# location
|
||||||
|
@ -366,12 +366,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
sessions = []
|
sessions = []
|
||||||
for session_id in self.coreemu.sessions:
|
for session_id in self.coreemu.sessions:
|
||||||
session = self.coreemu.sessions[session_id]
|
session = self.coreemu.sessions[session_id]
|
||||||
|
session_file = str(session.file_path) if session.file_path else None
|
||||||
session_summary = core_pb2.SessionSummary(
|
session_summary = core_pb2.SessionSummary(
|
||||||
id=session_id,
|
id=session_id,
|
||||||
state=session.state.value,
|
state=session.state.value,
|
||||||
nodes=session.get_node_count(),
|
nodes=session.get_node_count(),
|
||||||
file=session.file_name,
|
file=session_file,
|
||||||
dir=session.session_dir,
|
dir=str(session.session_dir),
|
||||||
)
|
)
|
||||||
sessions.append(session_summary)
|
sessions.append(session_summary)
|
||||||
return core_pb2.GetSessionsResponse(sessions=sessions)
|
return core_pb2.GetSessionsResponse(sessions=sessions)
|
||||||
|
@ -423,14 +424,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
"""
|
"""
|
||||||
logging.debug("set session state: %s", request)
|
logging.debug("set session state: %s", request)
|
||||||
session = self.get_session(request.session_id, context)
|
session = self.get_session(request.session_id, context)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
state = EventTypes(request.state)
|
state = EventTypes(request.state)
|
||||||
session.set_state(state)
|
session.set_state(state)
|
||||||
|
|
||||||
if state == EventTypes.INSTANTIATION_STATE:
|
if state == EventTypes.INSTANTIATION_STATE:
|
||||||
if not os.path.exists(session.session_dir):
|
session.session_dir.mkdir(exist_ok=True)
|
||||||
os.mkdir(session.session_dir)
|
|
||||||
session.instantiate()
|
session.instantiate()
|
||||||
elif state == EventTypes.SHUTDOWN_STATE:
|
elif state == EventTypes.SHUTDOWN_STATE:
|
||||||
session.shutdown()
|
session.shutdown()
|
||||||
|
@ -438,11 +436,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
session.data_collect()
|
session.data_collect()
|
||||||
elif state == EventTypes.DEFINITION_STATE:
|
elif state == EventTypes.DEFINITION_STATE:
|
||||||
session.clear()
|
session.clear()
|
||||||
|
|
||||||
result = True
|
result = True
|
||||||
except KeyError:
|
except KeyError:
|
||||||
result = False
|
result = False
|
||||||
|
|
||||||
return core_pb2.SetSessionStateResponse(result=result)
|
return core_pb2.SetSessionStateResponse(result=result)
|
||||||
|
|
||||||
def SetSessionUser(
|
def SetSessionUser(
|
||||||
|
@ -573,12 +569,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
mobility_configs = grpcutils.get_mobility_configs(session)
|
mobility_configs = grpcutils.get_mobility_configs(session)
|
||||||
service_configs = grpcutils.get_node_service_configs(session)
|
service_configs = grpcutils.get_node_service_configs(session)
|
||||||
config_service_configs = grpcutils.get_node_config_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(
|
session_proto = core_pb2.Session(
|
||||||
id=session.id,
|
id=session.id,
|
||||||
state=session.state.value,
|
state=session.state.value,
|
||||||
nodes=nodes,
|
nodes=nodes,
|
||||||
links=links,
|
links=links,
|
||||||
dir=session.session_dir,
|
dir=str(session.session_dir),
|
||||||
user=session.user,
|
user=session.user,
|
||||||
default_services=default_services,
|
default_services=default_services,
|
||||||
location=location,
|
location=location,
|
||||||
|
@ -591,7 +588,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
config_service_configs=config_service_configs,
|
config_service_configs=config_service_configs,
|
||||||
mobility_configs=mobility_configs,
|
mobility_configs=mobility_configs,
|
||||||
metadata=session.metadata,
|
metadata=session.metadata,
|
||||||
file=session.file_name,
|
file=session_file,
|
||||||
)
|
)
|
||||||
return core_pb2.GetSessionResponse(session=session_proto)
|
return core_pb2.GetSessionResponse(session=session_proto)
|
||||||
|
|
||||||
|
@ -1508,15 +1505,15 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
"""
|
"""
|
||||||
logging.debug("open xml: %s", request)
|
logging.debug("open xml: %s", request)
|
||||||
session = self.coreemu.create_session()
|
session = self.coreemu.create_session()
|
||||||
|
|
||||||
temp = tempfile.NamedTemporaryFile(delete=False)
|
temp = tempfile.NamedTemporaryFile(delete=False)
|
||||||
temp.write(request.data.encode("utf-8"))
|
temp.write(request.data.encode("utf-8"))
|
||||||
temp.close()
|
temp.close()
|
||||||
|
temp_path = Path(temp.name)
|
||||||
|
file_path = Path(request.file)
|
||||||
try:
|
try:
|
||||||
session.open_xml(temp.name, request.start)
|
session.open_xml(temp_path, request.start)
|
||||||
session.name = os.path.basename(request.file)
|
session.name = file_path.name
|
||||||
session.file_name = request.file
|
session.file_path = file_path
|
||||||
return core_pb2.OpenXmlResponse(session_id=session.id, result=True)
|
return core_pb2.OpenXmlResponse(session_id=session.id, result=True)
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.exception("error opening session file")
|
logging.exception("error opening session file")
|
||||||
|
@ -1733,12 +1730,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
|
|
||||||
def ExecuteScript(self, request, context):
|
def ExecuteScript(self, request, context):
|
||||||
existing_sessions = set(self.coreemu.sessions.keys())
|
existing_sessions = set(self.coreemu.sessions.keys())
|
||||||
|
file_path = Path(request.script)
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(
|
||||||
target=utils.execute_file,
|
target=utils.execute_file,
|
||||||
args=(
|
args=(file_path, {"coreemu": self.coreemu}),
|
||||||
request.script,
|
|
||||||
{"__file__": request.script, "coreemu": self.coreemu},
|
|
||||||
),
|
|
||||||
daemon=True,
|
daemon=True,
|
||||||
)
|
)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
|
@ -3,7 +3,6 @@ socket server request handlers leveraged by core servers.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import shlex
|
import shlex
|
||||||
import shutil
|
import shutil
|
||||||
import socketserver
|
import socketserver
|
||||||
|
@ -11,6 +10,7 @@ import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
|
from pathlib import Path
|
||||||
from queue import Empty, Queue
|
from queue import Empty, Queue
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
@ -167,39 +167,27 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
date_list = []
|
date_list = []
|
||||||
thumb_list = []
|
thumb_list = []
|
||||||
num_sessions = 0
|
num_sessions = 0
|
||||||
|
|
||||||
with self._sessions_lock:
|
with self._sessions_lock:
|
||||||
for _id in self.coreemu.sessions:
|
for _id in self.coreemu.sessions:
|
||||||
session = self.coreemu.sessions[_id]
|
session = self.coreemu.sessions[_id]
|
||||||
num_sessions += 1
|
num_sessions += 1
|
||||||
id_list.append(str(_id))
|
id_list.append(str(_id))
|
||||||
|
|
||||||
name = session.name
|
name = session.name
|
||||||
if not name:
|
if not name:
|
||||||
name = ""
|
name = ""
|
||||||
name_list.append(name)
|
name_list.append(name)
|
||||||
|
file_name = str(session.file_path) if session.file_path else ""
|
||||||
file_name = session.file_name
|
file_list.append(str(file_name))
|
||||||
if not file_name:
|
|
||||||
file_name = ""
|
|
||||||
file_list.append(file_name)
|
|
||||||
|
|
||||||
node_count_list.append(str(session.get_node_count()))
|
node_count_list.append(str(session.get_node_count()))
|
||||||
|
|
||||||
date_list.append(time.ctime(session.state_time))
|
date_list.append(time.ctime(session.state_time))
|
||||||
|
thumb = str(session.thumbnail) if session.thumbnail else ""
|
||||||
thumb = session.thumbnail
|
|
||||||
if not thumb:
|
|
||||||
thumb = ""
|
|
||||||
thumb_list.append(thumb)
|
thumb_list.append(thumb)
|
||||||
|
|
||||||
session_ids = "|".join(id_list)
|
session_ids = "|".join(id_list)
|
||||||
names = "|".join(name_list)
|
names = "|".join(name_list)
|
||||||
files = "|".join(file_list)
|
files = "|".join(file_list)
|
||||||
node_counts = "|".join(node_count_list)
|
node_counts = "|".join(node_count_list)
|
||||||
dates = "|".join(date_list)
|
dates = "|".join(date_list)
|
||||||
thumbs = "|".join(thumb_list)
|
thumbs = "|".join(thumb_list)
|
||||||
|
|
||||||
if num_sessions > 0:
|
if num_sessions > 0:
|
||||||
tlv_data = b""
|
tlv_data = b""
|
||||||
if len(session_ids) > 0:
|
if len(session_ids) > 0:
|
||||||
|
@ -221,7 +209,6 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
message = coreapi.CoreSessionMessage.pack(flags, tlv_data)
|
message = coreapi.CoreSessionMessage.pack(flags, tlv_data)
|
||||||
else:
|
else:
|
||||||
message = None
|
message = None
|
||||||
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def handle_broadcast_event(self, event_data):
|
def handle_broadcast_event(self, event_data):
|
||||||
|
@ -931,22 +918,18 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
if message.flags & MessageFlags.STRING.value:
|
if message.flags & MessageFlags.STRING.value:
|
||||||
old_session_ids = set(self.coreemu.sessions.keys())
|
old_session_ids = set(self.coreemu.sessions.keys())
|
||||||
sys.argv = shlex.split(execute_server)
|
sys.argv = shlex.split(execute_server)
|
||||||
file_name = sys.argv[0]
|
file_path = Path(sys.argv[0])
|
||||||
|
if file_path.suffix == ".xml":
|
||||||
if os.path.splitext(file_name)[1].lower() == ".xml":
|
|
||||||
session = self.coreemu.create_session()
|
session = self.coreemu.create_session()
|
||||||
try:
|
try:
|
||||||
session.open_xml(file_name)
|
session.open_xml(file_path)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.coreemu.delete_session(session.id)
|
self.coreemu.delete_session(session.id)
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
thread = threading.Thread(
|
thread = threading.Thread(
|
||||||
target=utils.execute_file,
|
target=utils.execute_file,
|
||||||
args=(
|
args=(file_path, {"coreemu": self.coreemu}),
|
||||||
file_name,
|
|
||||||
{"__file__": file_name, "coreemu": self.coreemu},
|
|
||||||
),
|
|
||||||
daemon=True,
|
daemon=True,
|
||||||
)
|
)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
@ -1465,10 +1448,12 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
:return: reply messages
|
:return: reply messages
|
||||||
"""
|
"""
|
||||||
if message.flags & MessageFlags.ADD.value:
|
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_name = message.get_tlv(FileTlvs.NAME.value)
|
||||||
file_type = message.get_tlv(FileTlvs.TYPE.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)
|
data = message.get_tlv(FileTlvs.DATA.value)
|
||||||
compressed_data = message.get_tlv(FileTlvs.COMPRESSED_DATA.value)
|
compressed_data = message.get_tlv(FileTlvs.COMPRESSED_DATA.value)
|
||||||
|
|
||||||
|
@ -1478,7 +1463,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
)
|
)
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
if source_name and data:
|
if src_path and data:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"ignoring invalid File message: source and data TLVs are both present"
|
"ignoring invalid File message: source and data TLVs are both present"
|
||||||
)
|
)
|
||||||
|
@ -1490,7 +1475,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
if file_type.startswith("service:"):
|
if file_type.startswith("service:"):
|
||||||
_, service_name = file_type.split(":")[:2]
|
_, service_name = file_type.split(":")[:2]
|
||||||
self.session.services.set_service_file(
|
self.session.services.set_service_file(
|
||||||
node_num, service_name, file_name, data
|
node_id, service_name, file_name, data
|
||||||
)
|
)
|
||||||
return ()
|
return ()
|
||||||
elif file_type.startswith("hook:"):
|
elif file_type.startswith("hook:"):
|
||||||
|
@ -1500,19 +1485,20 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
return ()
|
return ()
|
||||||
state = int(state)
|
state = int(state)
|
||||||
state = EventTypes(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 ()
|
return ()
|
||||||
|
|
||||||
# writing a file to the host
|
# writing a file to the host
|
||||||
if node_num is None:
|
if node_id is None:
|
||||||
if source_name is not None:
|
if src_path is not None:
|
||||||
shutil.copy2(source_name, file_name)
|
shutil.copy2(src_path, file_name)
|
||||||
else:
|
else:
|
||||||
with open(file_name, "w") as open_file:
|
with file_name.open("w") as f:
|
||||||
open_file.write(data)
|
f.write(data)
|
||||||
return ()
|
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:
|
else:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -1567,26 +1553,32 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
"dropping unhandled event message for node: %s", node.name
|
"dropping unhandled event message for node: %s", node.name
|
||||||
)
|
)
|
||||||
return ()
|
return ()
|
||||||
self.session.set_state(event_type)
|
|
||||||
|
|
||||||
if event_type == EventTypes.DEFINITION_STATE:
|
if event_type == EventTypes.DEFINITION_STATE:
|
||||||
|
self.session.set_state(event_type)
|
||||||
# clear all session objects in order to receive new definitions
|
# clear all session objects in order to receive new definitions
|
||||||
self.session.clear()
|
self.session.clear()
|
||||||
|
elif event_type == EventTypes.CONFIGURATION_STATE:
|
||||||
|
self.session.set_state(event_type)
|
||||||
elif event_type == EventTypes.INSTANTIATION_STATE:
|
elif event_type == EventTypes.INSTANTIATION_STATE:
|
||||||
|
self.session.set_state(event_type)
|
||||||
if len(self.handler_threads) > 1:
|
if len(self.handler_threads) > 1:
|
||||||
# TODO: sync handler threads here before continuing
|
# TODO: sync handler threads here before continuing
|
||||||
time.sleep(2.0) # XXX
|
time.sleep(2.0) # XXX
|
||||||
# done receiving node/link configuration, ready to instantiate
|
# done receiving node/link configuration, ready to instantiate
|
||||||
self.session.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:
|
for _id in self.session.nodes:
|
||||||
self.send_node_emulation_id(_id)
|
self.send_node_emulation_id(_id)
|
||||||
elif event_type == EventTypes.RUNTIME_STATE:
|
elif event_type == EventTypes.RUNTIME_STATE:
|
||||||
|
self.session.set_state(event_type)
|
||||||
logging.warning("Unexpected event message: RUNTIME state received")
|
logging.warning("Unexpected event message: RUNTIME state received")
|
||||||
elif event_type == EventTypes.DATACOLLECT_STATE:
|
elif event_type == EventTypes.DATACOLLECT_STATE:
|
||||||
self.session.data_collect()
|
self.session.data_collect()
|
||||||
elif event_type == EventTypes.SHUTDOWN_STATE:
|
elif event_type == EventTypes.SHUTDOWN_STATE:
|
||||||
|
self.session.set_state(event_type)
|
||||||
logging.warning("Unexpected event message: SHUTDOWN state received")
|
logging.warning("Unexpected event message: SHUTDOWN state received")
|
||||||
elif event_type in {
|
elif event_type in {
|
||||||
EventTypes.START,
|
EventTypes.START,
|
||||||
|
@ -1613,13 +1605,13 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
name,
|
name,
|
||||||
)
|
)
|
||||||
elif event_type == EventTypes.FILE_OPEN:
|
elif event_type == EventTypes.FILE_OPEN:
|
||||||
filename = event_data.name
|
file_path = Path(event_data.name)
|
||||||
self.session.open_xml(filename, start=False)
|
self.session.open_xml(file_path, start=False)
|
||||||
self.send_objects()
|
self.send_objects()
|
||||||
return ()
|
return ()
|
||||||
elif event_type == EventTypes.FILE_SAVE:
|
elif event_type == EventTypes.FILE_SAVE:
|
||||||
filename = event_data.name
|
file_path = Path(event_data.name)
|
||||||
self.session.save_xml(filename)
|
self.session.save_xml(file_path)
|
||||||
elif event_type == EventTypes.SCHEDULED:
|
elif event_type == EventTypes.SCHEDULED:
|
||||||
etime = event_data.time
|
etime = event_data.time
|
||||||
node_id = event_data.node
|
node_id = event_data.node
|
||||||
|
@ -1733,20 +1725,16 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
session = self.session
|
session = self.session
|
||||||
else:
|
else:
|
||||||
session = self.coreemu.sessions.get(session_id)
|
session = self.coreemu.sessions.get(session_id)
|
||||||
|
|
||||||
if session is None:
|
if session is None:
|
||||||
logging.warning("session %s not found", session_id)
|
logging.warning("session %s not found", session_id)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if names is not None:
|
if names is not None:
|
||||||
session.name = names[index]
|
session.name = names[index]
|
||||||
|
|
||||||
if files is not None:
|
if files is not None:
|
||||||
session.file_name = files[index]
|
session.file_path = Path(files[index])
|
||||||
|
|
||||||
if thumb:
|
if thumb:
|
||||||
|
thumb = Path(thumb)
|
||||||
session.set_thumbnail(thumb)
|
session.set_thumbnail(thumb)
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
session.set_user(user)
|
session.set_user(user)
|
||||||
elif (
|
elif (
|
||||||
|
|
|
@ -2,8 +2,8 @@ import abc
|
||||||
import enum
|
import enum
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from mako import exceptions
|
from mako import exceptions
|
||||||
|
@ -46,7 +46,7 @@ class ConfigService(abc.ABC):
|
||||||
"""
|
"""
|
||||||
self.node: CoreNode = node
|
self.node: CoreNode = node
|
||||||
class_file = inspect.getfile(self.__class__)
|
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.templates: TemplateLookup = TemplateLookup(directories=templates_path)
|
||||||
self.config: Dict[str, Configuration] = {}
|
self.config: Dict[str, Configuration] = {}
|
||||||
self.custom_templates: Dict[str, str] = {}
|
self.custom_templates: Dict[str, str] = {}
|
||||||
|
@ -176,9 +176,10 @@ class ConfigService(abc.ABC):
|
||||||
:raises CoreError: when there is a failure creating a directory
|
:raises CoreError: when there is a failure creating a directory
|
||||||
"""
|
"""
|
||||||
for directory in self.directories:
|
for directory in self.directories:
|
||||||
|
dir_path = Path(directory)
|
||||||
try:
|
try:
|
||||||
self.node.privatedir(directory)
|
self.node.privatedir(dir_path)
|
||||||
except (CoreCommandError, ValueError):
|
except (CoreCommandError, CoreError):
|
||||||
raise CoreError(
|
raise CoreError(
|
||||||
f"node({self.node.name}) service({self.name}) "
|
f"node({self.node.name}) service({self.name}) "
|
||||||
f"failure to create service directory: {directory}"
|
f"failure to create service directory: {directory}"
|
||||||
|
@ -220,7 +221,7 @@ class ConfigService(abc.ABC):
|
||||||
"""
|
"""
|
||||||
templates = {}
|
templates = {}
|
||||||
for name in self.files:
|
for name in self.files:
|
||||||
basename = pathlib.Path(name).name
|
basename = Path(name).name
|
||||||
if name in self.custom_templates:
|
if name in self.custom_templates:
|
||||||
template = self.custom_templates[name]
|
template = self.custom_templates[name]
|
||||||
template = self.clean_text(template)
|
template = self.clean_text(template)
|
||||||
|
@ -240,12 +241,12 @@ class ConfigService(abc.ABC):
|
||||||
"""
|
"""
|
||||||
data = self.data()
|
data = self.data()
|
||||||
for name in self.files:
|
for name in self.files:
|
||||||
basename = pathlib.Path(name).name
|
file_path = Path(name)
|
||||||
if name in self.custom_templates:
|
if name in self.custom_templates:
|
||||||
text = self.custom_templates[name]
|
text = self.custom_templates[name]
|
||||||
rendered = self.render_text(text, data)
|
rendered = self.render_text(text, data)
|
||||||
elif self.templates.has_template(basename):
|
elif self.templates.has_template(file_path.name):
|
||||||
rendered = self.render_template(basename, data)
|
rendered = self.render_template(file_path.name, data)
|
||||||
else:
|
else:
|
||||||
text = self.get_text_template(name)
|
text = self.get_text_template(name)
|
||||||
rendered = self.render_text(text, data)
|
rendered = self.render_text(text, data)
|
||||||
|
@ -256,7 +257,7 @@ class ConfigService(abc.ABC):
|
||||||
name,
|
name,
|
||||||
rendered,
|
rendered,
|
||||||
)
|
)
|
||||||
self.node.nodefile(name, rendered)
|
self.node.nodefile(file_path, rendered)
|
||||||
|
|
||||||
def run_startup(self, wait: bool) -> None:
|
def run_startup(self, wait: bool) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import pathlib
|
import pathlib
|
||||||
|
from pathlib import Path
|
||||||
from typing import Dict, List, Type
|
from typing import Dict, List, Type
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
|
@ -55,10 +56,10 @@ class ConfigServiceManager:
|
||||||
except CoreError as e:
|
except CoreError as e:
|
||||||
raise CoreError(f"config service({service.name}): {e}")
|
raise CoreError(f"config service({service.name}): {e}")
|
||||||
|
|
||||||
# make service available
|
# make service available
|
||||||
self.services[name] = service
|
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.
|
Search path provided for configurable services and add them for being managed.
|
||||||
|
|
||||||
|
@ -71,7 +72,7 @@ class ConfigServiceManager:
|
||||||
service_errors = []
|
service_errors = []
|
||||||
for subdir in subdirs:
|
for subdir in subdirs:
|
||||||
logging.debug("loading config services from: %s", subdir)
|
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:
|
for service in services:
|
||||||
try:
|
try:
|
||||||
self.add(service)
|
self.add(service)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
COREDPY_VERSION = "@PACKAGE_VERSION@"
|
from pathlib import Path
|
||||||
CORE_CONF_DIR = "@CORE_CONF_DIR@"
|
|
||||||
CORE_DATA_DIR = "@CORE_DATA_DIR@"
|
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 logging
|
||||||
import os
|
from pathlib import Path
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
@ -48,8 +48,8 @@ class EmaneCommEffectModel(emanemodel.EmaneModel):
|
||||||
external_config: List[Configuration] = []
|
external_config: List[Configuration] = []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: str) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
shim_xml_path = os.path.join(emane_prefix, "share/emane/manifest", cls.shim_xml)
|
shim_xml_path = emane_prefix / "share/emane/manifest" / cls.shim_xml
|
||||||
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
|
cls.config_shim = emanemanifest.parse(shim_xml_path, cls.shim_defaults)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -8,6 +8,7 @@ import threading
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, Type
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
|
@ -175,17 +176,15 @@ class EmaneManager(ModelManager):
|
||||||
if not path:
|
if not path:
|
||||||
logging.info("emane is not installed")
|
logging.info("emane is not installed")
|
||||||
return
|
return
|
||||||
|
|
||||||
# get version
|
# get version
|
||||||
emane_version = utils.cmd("emane --version")
|
emane_version = utils.cmd("emane --version")
|
||||||
logging.info("using emane: %s", emane_version)
|
logging.info("using emane: %s", emane_version)
|
||||||
|
|
||||||
# load default emane models
|
# load default emane models
|
||||||
self.load_models(EMANE_MODELS)
|
self.load_models(EMANE_MODELS)
|
||||||
|
|
||||||
# load custom models
|
# load custom models
|
||||||
custom_models_path = self.session.options.get_config("emane_models_dir")
|
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)
|
emane_models = utils.load_classes(custom_models_path, EmaneModel)
|
||||||
self.load_models(emane_models)
|
self.load_models(emane_models)
|
||||||
|
|
||||||
|
@ -246,6 +245,7 @@ class EmaneManager(ModelManager):
|
||||||
emane_prefix = self.session.options.get_config(
|
emane_prefix = self.session.options.get_config(
|
||||||
"emane_prefix", default=DEFAULT_EMANE_PREFIX
|
"emane_prefix", default=DEFAULT_EMANE_PREFIX
|
||||||
)
|
)
|
||||||
|
emane_prefix = Path(emane_prefix)
|
||||||
emane_model.load(emane_prefix)
|
emane_model.load(emane_prefix)
|
||||||
self.models[emane_model.name] = emane_model
|
self.models[emane_model.name] = emane_model
|
||||||
|
|
||||||
|
@ -398,9 +398,9 @@ class EmaneManager(ModelManager):
|
||||||
return self.ifaces_to_nems.get(iface)
|
return self.ifaces_to_nems.get(iface)
|
||||||
|
|
||||||
def write_nem(self, iface: CoreInterface, nem_id: int) -> None:
|
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:
|
try:
|
||||||
with open(path, "a") as f:
|
with path.open("a") as f:
|
||||||
f.write(f"{iface.node.name} {iface.name} {nem_id}\n")
|
f.write(f"{iface.node.name} {iface.name} {nem_id}\n")
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.exception("error writing to emane nem file")
|
logging.exception("error writing to emane nem file")
|
||||||
|
@ -590,18 +590,17 @@ class EmaneManager(ModelManager):
|
||||||
if eventservicenetidx >= 0 and eventgroup != otagroup:
|
if eventservicenetidx >= 0 and eventgroup != otagroup:
|
||||||
node.node_net_client.create_route(eventgroup, eventdev)
|
node.node_net_client.create_route(eventgroup, eventdev)
|
||||||
# start emane
|
# start emane
|
||||||
log_file = os.path.join(node.nodedir, f"{node.name}-emane.log")
|
log_file = node.nodedir / f"{node.name}-emane.log"
|
||||||
platform_xml = os.path.join(node.nodedir, f"{node.name}-platform.xml")
|
platform_xml = node.nodedir / f"{node.name}-platform.xml"
|
||||||
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||||
node.cmd(args)
|
node.cmd(args)
|
||||||
logging.info("node(%s) emane daemon running: %s", node.name, args)
|
logging.info("node(%s) emane daemon running: %s", node.name, args)
|
||||||
else:
|
else:
|
||||||
path = self.session.session_dir
|
log_file = self.session.session_dir / f"{node.name}-emane.log"
|
||||||
log_file = os.path.join(path, f"{node.name}-emane.log")
|
platform_xml = self.session.session_dir / f"{node.name}-platform.xml"
|
||||||
platform_xml = os.path.join(path, f"{node.name}-platform.xml")
|
args = f"{emanecmd} -f {log_file} {platform_xml}"
|
||||||
emanecmd += f" -f {log_file} {platform_xml}"
|
node.host_cmd(args, cwd=self.session.session_dir)
|
||||||
node.host_cmd(emanecmd, cwd=path)
|
logging.info("node(%s) host emane daemon running: %s", node.name, args)
|
||||||
logging.info("node(%s) host emane daemon running: %s", node.name, emanecmd)
|
|
||||||
|
|
||||||
def install_iface(self, iface: CoreInterface) -> None:
|
def install_iface(self, iface: CoreInterface) -> None:
|
||||||
emane_net = iface.net
|
emane_net = iface.net
|
||||||
|
@ -869,7 +868,8 @@ class EmaneGlobalModel:
|
||||||
emane_prefix = self.session.options.get_config(
|
emane_prefix = self.session.options.get_config(
|
||||||
"emane_prefix", default=DEFAULT_EMANE_PREFIX
|
"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 = {
|
emulator_defaults = {
|
||||||
"eventservicedevice": DEFAULT_DEV,
|
"eventservicedevice": DEFAULT_DEV,
|
||||||
"eventservicegroup": "224.1.2.8:45703",
|
"eventservicegroup": "224.1.2.8:45703",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from core.config import Configuration
|
from core.config import Configuration
|
||||||
|
@ -71,9 +72,10 @@ def _get_default(config_type_name: str, config_value: List[str]) -> str:
|
||||||
return config_default
|
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 manifest_path: absolute manifest file path
|
||||||
:param defaults: used to override default values for configurations
|
: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 []
|
return []
|
||||||
|
|
||||||
# load configuration file
|
# load configuration file
|
||||||
manifest_file = manifest.Manifest(manifest_path)
|
manifest_file = manifest.Manifest(str(manifest_path))
|
||||||
manifest_configurations = manifest_file.getAllConfiguration()
|
manifest_configurations = manifest_file.getAllConfiguration()
|
||||||
|
|
||||||
configurations = []
|
configurations = []
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
Defines Emane Models used within CORE.
|
Defines Emane Models used within CORE.
|
||||||
"""
|
"""
|
||||||
import logging
|
import logging
|
||||||
import os
|
from pathlib import Path
|
||||||
from typing import Dict, List, Optional, Set
|
from typing import Dict, List, Optional, Set
|
||||||
|
|
||||||
from core.config import ConfigGroup, Configuration
|
from core.config import ConfigGroup, Configuration
|
||||||
|
@ -53,7 +53,7 @@ class EmaneModel(WirelessModel):
|
||||||
config_ignore: Set[str] = set()
|
config_ignore: Set[str] = set()
|
||||||
|
|
||||||
@classmethod
|
@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
|
Called after being loaded within the EmaneManager. Provides configured emane_prefix for
|
||||||
parsing xml files.
|
parsing xml files.
|
||||||
|
@ -63,11 +63,10 @@ class EmaneModel(WirelessModel):
|
||||||
"""
|
"""
|
||||||
manifest_path = "share/emane/manifest"
|
manifest_path = "share/emane/manifest"
|
||||||
# load mac configuration
|
# 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)
|
cls.mac_config = emanemanifest.parse(mac_xml_path, cls.mac_defaults)
|
||||||
|
|
||||||
# load phy configuration
|
# 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)
|
cls.phy_config = emanemanifest.parse(phy_xml_path, cls.phy_defaults)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
ieee80211abg.py: EMANE IEEE 802.11abg model for CORE
|
ieee80211abg.py: EMANE IEEE 802.11abg model for CORE
|
||||||
"""
|
"""
|
||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
from core.emane import emanemodel
|
from core.emane import emanemodel
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ class EmaneIeee80211abgModel(emanemodel.EmaneModel):
|
||||||
mac_xml: str = "ieee80211abgmaclayer.xml"
|
mac_xml: str = "ieee80211abgmaclayer.xml"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: str) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
cls.mac_defaults["pcrcurveuri"] = os.path.join(
|
cls.mac_defaults["pcrcurveuri"] = str(
|
||||||
emane_prefix, "share/emane/xml/models/mac/ieee80211abg/ieee80211pcr.xml"
|
emane_prefix / "share/emane/xml/models/mac/ieee80211abg/ieee80211pcr.xml"
|
||||||
)
|
)
|
||||||
super().load(emane_prefix)
|
super().load(emane_prefix)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
rfpipe.py: EMANE RF-PIPE model for CORE
|
rfpipe.py: EMANE RF-PIPE model for CORE
|
||||||
"""
|
"""
|
||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
from core.emane import emanemodel
|
from core.emane import emanemodel
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ class EmaneRfPipeModel(emanemodel.EmaneModel):
|
||||||
mac_xml: str = "rfpipemaclayer.xml"
|
mac_xml: str = "rfpipemaclayer.xml"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: str) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
cls.mac_defaults["pcrcurveuri"] = os.path.join(
|
cls.mac_defaults["pcrcurveuri"] = str(
|
||||||
emane_prefix, "share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"
|
emane_prefix / "share/emane/xml/models/mac/rfpipe/rfpipepcr.xml"
|
||||||
)
|
)
|
||||||
super().load(emane_prefix)
|
super().load(emane_prefix)
|
||||||
|
|
|
@ -3,7 +3,7 @@ tdma.py: EMANE TDMA model bindings for CORE
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
from pathlib import Path
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
|
||||||
from core import constants, utils
|
from core import constants, utils
|
||||||
|
@ -22,27 +22,25 @@ class EmaneTdmaModel(emanemodel.EmaneModel):
|
||||||
|
|
||||||
# add custom schedule options and ignore it when writing emane xml
|
# add custom schedule options and ignore it when writing emane xml
|
||||||
schedule_name: str = "schedule"
|
schedule_name: str = "schedule"
|
||||||
default_schedule: str = os.path.join(
|
default_schedule: Path = (
|
||||||
constants.CORE_DATA_DIR, "examples", "tdma", "schedule.xml"
|
constants.CORE_DATA_DIR / "examples" / "tdma" / "schedule.xml"
|
||||||
)
|
)
|
||||||
config_ignore: Set[str] = {schedule_name}
|
config_ignore: Set[str] = {schedule_name}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls, emane_prefix: str) -> None:
|
def load(cls, emane_prefix: Path) -> None:
|
||||||
cls.mac_defaults["pcrcurveuri"] = os.path.join(
|
cls.mac_defaults["pcrcurveuri"] = str(
|
||||||
emane_prefix,
|
emane_prefix
|
||||||
"share/emane/xml/models/mac/tdmaeventscheduler/tdmabasemodelpcr.xml",
|
/ "share/emane/xml/models/mac/tdmaeventscheduler/tdmabasemodelpcr.xml"
|
||||||
)
|
)
|
||||||
super().load(emane_prefix)
|
super().load(emane_prefix)
|
||||||
cls.mac_config.insert(
|
config_item = Configuration(
|
||||||
0,
|
_id=cls.schedule_name,
|
||||||
Configuration(
|
_type=ConfigDataTypes.STRING,
|
||||||
_id=cls.schedule_name,
|
default=str(cls.default_schedule),
|
||||||
_type=ConfigDataTypes.STRING,
|
label="TDMA schedule file (core)",
|
||||||
default=cls.default_schedule,
|
|
||||||
label="TDMA schedule file (core)",
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
cls.mac_config.insert(0, config_item)
|
||||||
|
|
||||||
def post_startup(self) -> None:
|
def post_startup(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -3,6 +3,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
from typing import Dict, List, Type
|
from typing import Dict, List, Type
|
||||||
|
|
||||||
import core.services
|
import core.services
|
||||||
|
@ -60,10 +61,11 @@ class CoreEmu:
|
||||||
|
|
||||||
# config services
|
# config services
|
||||||
self.service_manager: ConfigServiceManager = ConfigServiceManager()
|
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)
|
self.service_manager.load(config_services_path)
|
||||||
custom_dir = self.config.get("custom_config_services_dir")
|
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)
|
self.service_manager.load(custom_dir)
|
||||||
|
|
||||||
# check executables exist on path
|
# check executables exist on path
|
||||||
|
@ -91,13 +93,12 @@ class CoreEmu:
|
||||||
"""
|
"""
|
||||||
# load default services
|
# load default services
|
||||||
self.service_errors = core.services.load()
|
self.service_errors = core.services.load()
|
||||||
|
|
||||||
# load custom services
|
# load custom services
|
||||||
service_paths = self.config.get("custom_services_dir")
|
service_paths = self.config.get("custom_services_dir")
|
||||||
logging.debug("custom service paths: %s", service_paths)
|
logging.debug("custom service paths: %s", service_paths)
|
||||||
if service_paths:
|
if service_paths is not None:
|
||||||
for service_path in service_paths.split(","):
|
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)
|
custom_service_errors = ServiceManager.add_services(service_path)
|
||||||
self.service_errors.extend(custom_service_errors)
|
self.service_errors.extend(custom_service_errors)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import TYPE_CHECKING, Callable, Dict, Tuple
|
from typing import TYPE_CHECKING, Callable, Dict, Tuple
|
||||||
|
|
||||||
|
@ -79,23 +80,23 @@ class DistributedServer:
|
||||||
stdout, stderr = e.streams_for_display()
|
stdout, stderr = e.streams_for_display()
|
||||||
raise CoreCommandError(e.result.exited, cmd, stdout, stderr)
|
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.
|
Push file to remote server.
|
||||||
|
|
||||||
:param source: source file to push
|
:param src_path: source file to push
|
||||||
:param destination: destination file location
|
:param dst_path: destination file location
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
with self.lock:
|
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
|
Remote push file contents to a remote server, using a temp file as an
|
||||||
intermediate step.
|
intermediate step.
|
||||||
|
|
||||||
:param destination: file destination for data
|
:param dst_path: file destination for data
|
||||||
:param data: data to store in remote file
|
:param data: data to store in remote file
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
@ -103,7 +104,7 @@ class DistributedServer:
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
temp.write(data.encode("utf-8"))
|
temp.write(data.encode("utf-8"))
|
||||||
temp.close()
|
temp.close()
|
||||||
self.conn.put(temp.name, destination)
|
self.conn.put(temp.name, str(dst_path))
|
||||||
os.unlink(temp.name)
|
os.unlink(temp.name)
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,13 +171,11 @@ class DistributedController:
|
||||||
tunnels = self.tunnels[key]
|
tunnels = self.tunnels[key]
|
||||||
for tunnel in tunnels:
|
for tunnel in tunnels:
|
||||||
tunnel.shutdown()
|
tunnel.shutdown()
|
||||||
|
|
||||||
# remove all remote session directories
|
# remove all remote session directories
|
||||||
for name in self.servers:
|
for name in self.servers:
|
||||||
server = self.servers[name]
|
server = self.servers[name]
|
||||||
cmd = f"rm -rf {self.session.session_dir}"
|
cmd = f"rm -rf {self.session.session_dir}"
|
||||||
server.remote_cmd(cmd)
|
server.remote_cmd(cmd)
|
||||||
|
|
||||||
# clear tunnels
|
# clear tunnels
|
||||||
self.tunnels.clear()
|
self.tunnels.clear()
|
||||||
|
|
||||||
|
@ -212,14 +211,12 @@ class DistributedController:
|
||||||
tunnel = self.tunnels.get(key)
|
tunnel = self.tunnels.get(key)
|
||||||
if tunnel is not None:
|
if tunnel is not None:
|
||||||
return tunnel
|
return tunnel
|
||||||
|
|
||||||
# local to server
|
# local to server
|
||||||
logging.info(
|
logging.info(
|
||||||
"local tunnel node(%s) to remote(%s) key(%s)", node.name, host, key
|
"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 = GreTap(session=self.session, remoteip=host, key=key)
|
||||||
local_tap.net_client.set_iface_master(node.brname, local_tap.localname)
|
local_tap.net_client.set_iface_master(node.brname, local_tap.localname)
|
||||||
|
|
||||||
# server to local
|
# server to local
|
||||||
logging.info(
|
logging.info(
|
||||||
"remote tunnel node(%s) to local(%s) key(%s)", node.name, self.address, key
|
"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
|
session=self.session, remoteip=self.address, key=key, server=server
|
||||||
)
|
)
|
||||||
remote_tap.net_client.set_iface_master(node.brname, remote_tap.localname)
|
remote_tap.net_client.set_iface_master(node.brname, remote_tap.localname)
|
||||||
|
|
||||||
# save tunnels for shutdown
|
# save tunnels for shutdown
|
||||||
tunnel = (local_tap, remote_tap)
|
tunnel = (local_tap, remote_tap)
|
||||||
self.tunnels[key] = tunnel
|
self.tunnels[key] = tunnel
|
||||||
|
|
|
@ -103,13 +103,13 @@ class Session:
|
||||||
self.id: int = _id
|
self.id: int = _id
|
||||||
|
|
||||||
# define and create session directory when desired
|
# 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:
|
if mkdir:
|
||||||
os.mkdir(self.session_dir)
|
self.session_dir.mkdir()
|
||||||
|
|
||||||
self.name: Optional[str] = None
|
self.name: Optional[str] = None
|
||||||
self.file_name: Optional[str] = None
|
self.file_path: Optional[Path] = None
|
||||||
self.thumbnail: Optional[str] = None
|
self.thumbnail: Optional[Path] = None
|
||||||
self.user: Optional[str] = None
|
self.user: Optional[str] = None
|
||||||
self.event_loop: EventLoop = EventLoop()
|
self.event_loop: EventLoop = EventLoop()
|
||||||
self.link_colors: Dict[int, str] = {}
|
self.link_colors: Dict[int, str] = {}
|
||||||
|
@ -641,42 +641,39 @@ class Session:
|
||||||
logging.info("session(%s) checking if active: %s", self.id, result)
|
logging.info("session(%s) checking if active: %s", self.id, result)
|
||||||
return 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.
|
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
|
:param start: instantiate session if true, false otherwise
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logging.info("opening xml: %s", file_name)
|
logging.info("opening xml: %s", file_path)
|
||||||
|
|
||||||
# clear out existing session
|
# clear out existing session
|
||||||
self.clear()
|
self.clear()
|
||||||
|
|
||||||
# set state and read xml
|
# set state and read xml
|
||||||
state = EventTypes.CONFIGURATION_STATE if start else EventTypes.DEFINITION_STATE
|
state = EventTypes.CONFIGURATION_STATE if start else EventTypes.DEFINITION_STATE
|
||||||
self.set_state(state)
|
self.set_state(state)
|
||||||
self.name = os.path.basename(file_name)
|
self.name = file_path.name
|
||||||
self.file_name = file_name
|
self.file_path = file_path
|
||||||
CoreXmlReader(self).read(file_name)
|
CoreXmlReader(self).read(file_path)
|
||||||
|
|
||||||
# start session if needed
|
# start session if needed
|
||||||
if start:
|
if start:
|
||||||
self.set_state(EventTypes.INSTANTIATION_STATE)
|
self.set_state(EventTypes.INSTANTIATION_STATE)
|
||||||
self.instantiate()
|
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.
|
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
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
CoreXmlWriter(self).write(file_name)
|
CoreXmlWriter(self).write(file_path)
|
||||||
|
|
||||||
def add_hook(
|
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:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Store a hook from a received file message.
|
Store a hook from a received file message.
|
||||||
|
@ -684,11 +681,11 @@ class Session:
|
||||||
:param state: when to run hook
|
:param state: when to run hook
|
||||||
:param file_name: file name for hook
|
:param file_name: file name for hook
|
||||||
:param data: hook data
|
:param data: hook data
|
||||||
:param source_name: source name
|
:param src_name: source name
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logging.info(
|
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
|
hook = file_name, data
|
||||||
state_hooks = self.hooks.setdefault(state, [])
|
state_hooks = self.hooks.setdefault(state, [])
|
||||||
|
@ -700,22 +697,22 @@ class Session:
|
||||||
self.run_hook(hook)
|
self.run_hook(hook)
|
||||||
|
|
||||||
def add_node_file(
|
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:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Add a file to a node.
|
Add a file to a node.
|
||||||
|
|
||||||
:param node_id: node to add file to
|
:param node_id: node to add file to
|
||||||
:param source_name: source file name
|
:param src_path: source file path
|
||||||
:param file_name: file name to add
|
:param file_path: file path to add
|
||||||
:param data: file data
|
:param data: file data
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
node = self.get_node(node_id, CoreNodeBase)
|
node = self.get_node(node_id, CoreNodeBase)
|
||||||
if source_name is not None:
|
if src_path is not None:
|
||||||
node.addfile(source_name, file_name)
|
node.addfile(src_path, file_path)
|
||||||
elif data is not None:
|
elif data is not None:
|
||||||
node.nodefile(file_name, data)
|
node.nodefile(file_path, data)
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -879,9 +876,9 @@ class Session:
|
||||||
:param state: state to write to file
|
:param state: state to write to file
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
state_file = os.path.join(self.session_dir, "state")
|
state_file = self.session_dir / "state"
|
||||||
try:
|
try:
|
||||||
with open(state_file, "w") as f:
|
with state_file.open("w") as f:
|
||||||
f.write(f"{state.value} {state.name}\n")
|
f.write(f"{state.value} {state.name}\n")
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.exception("error writing state file: %s", state.name)
|
logging.exception("error writing state file: %s", state.name)
|
||||||
|
@ -907,12 +904,12 @@ class Session:
|
||||||
"""
|
"""
|
||||||
file_name, data = hook
|
file_name, data = hook
|
||||||
logging.info("running hook %s", file_name)
|
logging.info("running hook %s", file_name)
|
||||||
file_path = os.path.join(self.session_dir, file_name)
|
file_path = self.session_dir / file_name
|
||||||
log_path = os.path.join(self.session_dir, f"{file_name}.log")
|
log_path = self.session_dir / f"{file_name}.log"
|
||||||
try:
|
try:
|
||||||
with open(file_path, "w") as f:
|
with file_path.open("w") as f:
|
||||||
f.write(data)
|
f.write(data)
|
||||||
with open(log_path, "w") as f:
|
with log_path.open("w") as f:
|
||||||
args = ["/bin/sh", file_name]
|
args = ["/bin/sh", file_name]
|
||||||
subprocess.check_call(
|
subprocess.check_call(
|
||||||
args,
|
args,
|
||||||
|
@ -983,10 +980,10 @@ class Session:
|
||||||
"""
|
"""
|
||||||
self.emane.poststartup()
|
self.emane.poststartup()
|
||||||
# create session deployed xml
|
# create session deployed xml
|
||||||
xml_file_name = os.path.join(self.session_dir, "session-deployed.xml")
|
|
||||||
xml_writer = corexml.CoreXmlWriter(self)
|
xml_writer = corexml.CoreXmlWriter(self)
|
||||||
corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario)
|
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]:
|
def get_environment(self, state: bool = True) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
|
@ -1001,9 +998,9 @@ class Session:
|
||||||
env["CORE_PYTHON"] = sys.executable
|
env["CORE_PYTHON"] = sys.executable
|
||||||
env["SESSION"] = str(self.id)
|
env["SESSION"] = str(self.id)
|
||||||
env["SESSION_SHORT"] = self.short_session_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_NAME"] = str(self.name)
|
||||||
env["SESSION_FILENAME"] = str(self.file_name)
|
env["SESSION_FILENAME"] = str(self.file_path)
|
||||||
env["SESSION_USER"] = str(self.user)
|
env["SESSION_USER"] = str(self.user)
|
||||||
if state:
|
if state:
|
||||||
env["SESSION_STATE"] = str(self.state)
|
env["SESSION_STATE"] = str(self.state)
|
||||||
|
@ -1011,8 +1008,8 @@ class Session:
|
||||||
# /etc/core/environment
|
# /etc/core/environment
|
||||||
# /home/user/.core/environment
|
# /home/user/.core/environment
|
||||||
# /tmp/pycore.<session id>/environment
|
# /tmp/pycore.<session id>/environment
|
||||||
core_env_path = Path(constants.CORE_CONF_DIR) / "environment"
|
core_env_path = constants.CORE_CONF_DIR / "environment"
|
||||||
session_env_path = Path(self.session_dir) / "environment"
|
session_env_path = self.session_dir / "environment"
|
||||||
if self.user:
|
if self.user:
|
||||||
user_home_path = Path(f"~{self.user}").expanduser()
|
user_home_path = Path(f"~{self.user}").expanduser()
|
||||||
user_env1 = user_home_path / ".core" / "environment"
|
user_env1 = user_home_path / ".core" / "environment"
|
||||||
|
@ -1028,20 +1025,20 @@ class Session:
|
||||||
logging.exception("error reading environment file: %s", path)
|
logging.exception("error reading environment file: %s", path)
|
||||||
return env
|
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.
|
Set the thumbnail filename. Move files from /tmp to session dir.
|
||||||
|
|
||||||
:param thumb_file: tumbnail file to set for session
|
:param thumb_file: tumbnail file to set for session
|
||||||
:return: nothing
|
: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)
|
logging.error("thumbnail file to set does not exist: %s", thumb_file)
|
||||||
self.thumbnail = None
|
self.thumbnail = None
|
||||||
return
|
return
|
||||||
destination_file = os.path.join(self.session_dir, os.path.basename(thumb_file))
|
dst_path = self.session_dir / thumb_file.name
|
||||||
shutil.copy(thumb_file, destination_file)
|
shutil.copy(thumb_file, dst_path)
|
||||||
self.thumbnail = destination_file
|
self.thumbnail = dst_path
|
||||||
|
|
||||||
def set_user(self, user: str) -> None:
|
def set_user(self, user: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1054,7 +1051,7 @@ class Session:
|
||||||
if user:
|
if user:
|
||||||
try:
|
try:
|
||||||
uid = pwd.getpwnam(user).pw_uid
|
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)
|
os.chown(self.session_dir, uid, gid)
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.exception("failed to set permission on %s", self.session_dir)
|
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.
|
Write nodes to a 'nodes' file in the session dir.
|
||||||
The 'nodes' file lists: number, name, api-type, class-type
|
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:
|
try:
|
||||||
with self.nodes_lock:
|
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():
|
for _id, node in self.nodes.items():
|
||||||
f.write(f"{_id} {node.name} {node.apitype} {type(node)}\n")
|
f.write(f"{_id} {node.name} {node.apitype} {type(node)}\n")
|
||||||
except IOError:
|
except IOError:
|
||||||
|
@ -1268,15 +1265,13 @@ class Session:
|
||||||
# stop event loop
|
# stop event loop
|
||||||
self.event_loop.stop()
|
self.event_loop.stop()
|
||||||
|
|
||||||
# stop node services
|
# stop mobility and node services
|
||||||
with self.nodes_lock:
|
with self.nodes_lock:
|
||||||
funcs = []
|
funcs = []
|
||||||
for node_id in self.nodes:
|
for node in self.nodes.values():
|
||||||
node = self.nodes[node_id]
|
if isinstance(node, CoreNodeBase) and node.up:
|
||||||
if not isinstance(node, CoreNodeBase) or not node.up:
|
args = (node,)
|
||||||
continue
|
funcs.append((self.services.stop_services, args, {}))
|
||||||
args = (node,)
|
|
||||||
funcs.append((self.services.stop_services, args, {}))
|
|
||||||
utils.threadpool(funcs)
|
utils.threadpool(funcs)
|
||||||
|
|
||||||
# shutdown emane
|
# shutdown emane
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
|
from pathlib import Path
|
||||||
from tkinter import filedialog, ttk
|
from tkinter import filedialog, ttk
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
||||||
|
|
||||||
|
@ -579,11 +579,12 @@ class ServiceConfigDialog(Dialog):
|
||||||
self.directory_entry.insert("end", d)
|
self.directory_entry.insert("end", d)
|
||||||
|
|
||||||
def add_directory(self) -> None:
|
def add_directory(self) -> None:
|
||||||
d = self.directory_entry.get()
|
directory = self.directory_entry.get()
|
||||||
if os.path.isdir(d):
|
directory = Path(directory)
|
||||||
if d not in self.temp_directories:
|
if directory.is_dir():
|
||||||
self.dir_list.listbox.insert("end", d)
|
if str(directory) not in self.temp_directories:
|
||||||
self.temp_directories.append(d)
|
self.dir_list.listbox.insert("end", directory)
|
||||||
|
self.temp_directories.append(directory)
|
||||||
|
|
||||||
def remove_directory(self) -> None:
|
def remove_directory(self) -> None:
|
||||||
d = self.directory_entry.get()
|
d = self.directory_entry.get()
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from pathlib import Path
|
||||||
from tkinter import filedialog, messagebox
|
from tkinter import filedialog, messagebox
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
@ -272,12 +272,12 @@ class Menubar(tk.Menu):
|
||||||
menu.add_command(label="About", command=self.click_about)
|
menu.add_command(label="About", command=self.click_about)
|
||||||
self.add_cascade(label="Help", menu=menu)
|
self.add_cascade(label="Help", menu=menu)
|
||||||
|
|
||||||
def open_recent_files(self, filename: str) -> None:
|
def open_recent_files(self, file_path: Path) -> None:
|
||||||
if os.path.isfile(filename):
|
if file_path.is_file():
|
||||||
logging.debug("Open recent file %s", filename)
|
logging.debug("Open recent file %s", file_path)
|
||||||
self.open_xml_task(filename)
|
self.open_xml_task(file_path)
|
||||||
else:
|
else:
|
||||||
logging.warning("File does not exist %s", filename)
|
logging.warning("File does not exist %s", file_path)
|
||||||
|
|
||||||
def update_recent_files(self) -> None:
|
def update_recent_files(self) -> None:
|
||||||
self.recent_menu.delete(0, tk.END)
|
self.recent_menu.delete(0, tk.END)
|
||||||
|
@ -286,7 +286,7 @@ class Menubar(tk.Menu):
|
||||||
label=i, command=partial(self.open_recent_files, i)
|
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:
|
if self.core.session.file:
|
||||||
self.core.save_xml()
|
self.core.save_xml()
|
||||||
else:
|
else:
|
||||||
|
@ -314,7 +314,7 @@ class Menubar(tk.Menu):
|
||||||
if file_path:
|
if file_path:
|
||||||
self.open_xml_task(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.add_recent_file_to_gui_config(file_path)
|
||||||
self.prompt_save_running_session()
|
self.prompt_save_running_session()
|
||||||
task = ProgressTask(self.app, "Open XML", self.core.open_xml, args=(file_path,))
|
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 = ExecutePythonDialog(self.app)
|
||||||
dialog.show()
|
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
|
recent_files = self.app.guiconfig.recentfiles
|
||||||
num_files = len(recent_files)
|
file_path = str(file_path)
|
||||||
if num_files == 0:
|
if file_path in recent_files:
|
||||||
recent_files.insert(0, file_path)
|
recent_files.remove(file_path)
|
||||||
elif 0 < num_files <= MAX_FILES:
|
recent_files.insert(0, file_path)
|
||||||
if file_path in recent_files:
|
if len(recent_files) > MAX_FILES:
|
||||||
recent_files.remove(file_path)
|
recent_files.pop()
|
||||||
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")
|
|
||||||
self.app.save_config()
|
self.app.save_config()
|
||||||
self.app.menubar.update_recent_files()
|
self.app.menubar.update_recent_files()
|
||||||
|
|
||||||
|
|
|
@ -419,7 +419,7 @@ class BasicRangeModel(WirelessModel):
|
||||||
self.wlan.link(a, b)
|
self.wlan.link(a, b)
|
||||||
self.sendlinkmsg(a, b)
|
self.sendlinkmsg(a, b)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logging.exception("error getting interfaces during calclinkS")
|
logging.exception("error getting interfaces during calclink")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def calcdistance(
|
def calcdistance(
|
||||||
|
@ -920,7 +920,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
:param _id: object id
|
:param _id: object id
|
||||||
"""
|
"""
|
||||||
super().__init__(session, _id)
|
super().__init__(session, _id)
|
||||||
self.file: Optional[str] = None
|
self.file: Optional[Path] = None
|
||||||
self.autostart: Optional[str] = None
|
self.autostart: Optional[str] = None
|
||||||
self.nodemap: Dict[int, int] = {}
|
self.nodemap: Dict[int, int] = {}
|
||||||
self.script_start: Optional[str] = None
|
self.script_start: Optional[str] = None
|
||||||
|
@ -928,7 +928,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
self.script_stop: Optional[str] = None
|
self.script_stop: Optional[str] = None
|
||||||
|
|
||||||
def update_config(self, config: Dict[str, str]) -> None:
|
def update_config(self, config: Dict[str, str]) -> None:
|
||||||
self.file = config["file"]
|
self.file = Path(config["file"])
|
||||||
logging.info(
|
logging.info(
|
||||||
"ns-2 scripted mobility configured for WLAN %d using file: %s",
|
"ns-2 scripted mobility configured for WLAN %d using file: %s",
|
||||||
self.id,
|
self.id,
|
||||||
|
@ -953,15 +953,15 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
|
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
filename = self.findfile(self.file)
|
file_path = self.findfile(self.file)
|
||||||
try:
|
try:
|
||||||
f = open(filename, "r")
|
f = file_path.open("r")
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
"ns-2 scripted mobility failed to load file: %s", self.file
|
"ns-2 scripted mobility failed to load file: %s", self.file
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
logging.info("reading ns-2 script file: %s", filename)
|
logging.info("reading ns-2 script file: %s", file_path)
|
||||||
ln = 0
|
ln = 0
|
||||||
ix = iy = iz = None
|
ix = iy = iz = None
|
||||||
inodenum = None
|
inodenum = None
|
||||||
|
@ -977,13 +977,13 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
# waypoints:
|
# waypoints:
|
||||||
# $ns_ at 1.00 "$node_(6) setdest 500.0 178.0 25.0"
|
# $ns_ at 1.00 "$node_(6) setdest 500.0 178.0 25.0"
|
||||||
parts = line.split()
|
parts = line.split()
|
||||||
time = float(parts[2])
|
line_time = float(parts[2])
|
||||||
nodenum = parts[3][1 + parts[3].index("(") : parts[3].index(")")]
|
nodenum = parts[3][1 + parts[3].index("(") : parts[3].index(")")]
|
||||||
x = float(parts[5])
|
x = float(parts[5])
|
||||||
y = float(parts[6])
|
y = float(parts[6])
|
||||||
z = None
|
z = None
|
||||||
speed = float(parts[7].strip('"'))
|
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_(":
|
elif line[:7] == "$node_(":
|
||||||
# initial position (time=0, speed=0):
|
# initial position (time=0, speed=0):
|
||||||
# $node_(6) set X_ 780.0
|
# $node_(6) set X_ 780.0
|
||||||
|
@ -1011,31 +1011,31 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
if ix is not None and iy is not None:
|
if ix is not None and iy is not None:
|
||||||
self.addinitial(self.map(inodenum), ix, iy, iz)
|
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
|
Locate a script file. If the specified file doesn't exist, look in the
|
||||||
same directory as the scenario file, or in gui directories.
|
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
|
:return: absolute path to the file
|
||||||
:raises CoreError: when file is not found
|
:raises CoreError: when file is not found
|
||||||
"""
|
"""
|
||||||
file_path = Path(file_name).expanduser()
|
file_path = file_path.expanduser()
|
||||||
if file_path.exists():
|
if file_path.exists():
|
||||||
return str(file_path)
|
return file_path
|
||||||
if self.session.file_name:
|
if self.session.file_path:
|
||||||
file_path = Path(self.session.file_name).parent / file_name
|
session_file_path = self.session.file_path.parent / file_path
|
||||||
if file_path.exists():
|
if session_file_path.exists():
|
||||||
return str(file_path)
|
return session_file_path
|
||||||
if self.session.user:
|
if self.session.user:
|
||||||
user_path = Path(f"~{self.session.user}").expanduser()
|
user_path = Path(f"~{self.session.user}").expanduser()
|
||||||
file_path = user_path / ".core" / "configs" / file_name
|
configs_path = user_path / ".core" / "configs" / file_path
|
||||||
if file_path.exists():
|
if configs_path.exists():
|
||||||
return str(file_path)
|
return configs_path
|
||||||
file_path = user_path / ".coregui" / "mobility" / file_name
|
mobility_path = user_path / ".coregui" / "mobility" / file_path
|
||||||
if file_path.exists():
|
if mobility_path.exists():
|
||||||
return str(file_path)
|
return mobility_path
|
||||||
raise CoreError(f"invalid file: {file_name}")
|
raise CoreError(f"invalid file: {file_path}")
|
||||||
|
|
||||||
def parsemap(self, mapstr: str) -> None:
|
def parsemap(self, mapstr: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1047,7 +1047,6 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
self.nodemap = {}
|
self.nodemap = {}
|
||||||
if mapstr.strip() == "":
|
if mapstr.strip() == "":
|
||||||
return
|
return
|
||||||
|
|
||||||
for pair in mapstr.split(","):
|
for pair in mapstr.split(","):
|
||||||
parts = pair.split(":")
|
parts = pair.split(":")
|
||||||
try:
|
try:
|
||||||
|
@ -1152,6 +1151,7 @@ class Ns2ScriptedMobility(WayPointMobility):
|
||||||
filename = self.script_stop
|
filename = self.script_stop
|
||||||
if filename is None or filename == "":
|
if filename is None or filename == "":
|
||||||
return
|
return
|
||||||
|
filename = Path(filename)
|
||||||
filename = self.findfile(filename)
|
filename = self.findfile(filename)
|
||||||
args = f"{BASH} {filename} {typestr}"
|
args = f"{BASH} {filename} {typestr}"
|
||||||
utils.cmd(
|
utils.cmd(
|
||||||
|
|
|
@ -3,9 +3,9 @@ Defines the base logic for nodes used within core.
|
||||||
"""
|
"""
|
||||||
import abc
|
import abc
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import shutil
|
import shutil
|
||||||
import threading
|
import threading
|
||||||
|
from pathlib import Path
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ if TYPE_CHECKING:
|
||||||
CoreServices = List[Union[CoreService, Type[CoreService]]]
|
CoreServices = List[Union[CoreService, Type[CoreService]]]
|
||||||
ConfigServiceType = Type[ConfigService]
|
ConfigServiceType = Type[ConfigService]
|
||||||
|
|
||||||
|
PRIVATE_DIRS: List[Path] = [Path("/var/run"), Path("/var/log")]
|
||||||
|
|
||||||
|
|
||||||
class NodeBase(abc.ABC):
|
class NodeBase(abc.ABC):
|
||||||
"""
|
"""
|
||||||
|
@ -97,7 +99,7 @@ class NodeBase(abc.ABC):
|
||||||
self,
|
self,
|
||||||
args: str,
|
args: str,
|
||||||
env: Dict[str, str] = None,
|
env: Dict[str, str] = None,
|
||||||
cwd: str = None,
|
cwd: Path = None,
|
||||||
wait: bool = True,
|
wait: bool = True,
|
||||||
shell: bool = False,
|
shell: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
@ -221,7 +223,7 @@ class CoreNodeBase(NodeBase):
|
||||||
"""
|
"""
|
||||||
super().__init__(session, _id, name, server)
|
super().__init__(session, _id, name, server)
|
||||||
self.config_services: Dict[str, "ConfigService"] = {}
|
self.config_services: Dict[str, "ConfigService"] = {}
|
||||||
self.nodedir: Optional[str] = None
|
self.nodedir: Optional[Path] = None
|
||||||
self.tmpnodedir: bool = False
|
self.tmpnodedir: bool = False
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
|
@ -233,11 +235,11 @@ class CoreNodeBase(NodeBase):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abc.abstractmethod
|
@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.
|
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 contents: contents of file
|
||||||
:param mode: mode for file
|
:param mode: mode for file
|
||||||
:return: nothing
|
:return: nothing
|
||||||
|
@ -245,12 +247,12 @@ class CoreNodeBase(NodeBase):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def addfile(self, srcname: str, filename: str) -> None:
|
def addfile(self, src_path: Path, file_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Add a file.
|
Add a file.
|
||||||
|
|
||||||
:param srcname: source file name
|
:param src_path: source file path
|
||||||
:param filename: file name to add
|
:param file_path: file name to add
|
||||||
:return: nothing
|
:return: nothing
|
||||||
:raises CoreCommandError: when a non-zero exit status occurs
|
:raises CoreCommandError: when a non-zero exit status occurs
|
||||||
"""
|
"""
|
||||||
|
@ -302,6 +304,21 @@ class CoreNodeBase(NodeBase):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
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:
|
def add_config_service(self, service_class: "ConfigServiceType") -> None:
|
||||||
"""
|
"""
|
||||||
Adds a configuration service to the node.
|
Adds a configuration service to the node.
|
||||||
|
@ -346,7 +363,7 @@ class CoreNodeBase(NodeBase):
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
if self.nodedir is None:
|
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.host_cmd(f"mkdir -p {self.nodedir}")
|
||||||
self.tmpnodedir = True
|
self.tmpnodedir = True
|
||||||
else:
|
else:
|
||||||
|
@ -458,7 +475,7 @@ class CoreNode(CoreNodeBase):
|
||||||
session: "Session",
|
session: "Session",
|
||||||
_id: int = None,
|
_id: int = None,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
nodedir: str = None,
|
nodedir: Path = None,
|
||||||
server: "DistributedServer" = None,
|
server: "DistributedServer" = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -472,14 +489,12 @@ class CoreNode(CoreNodeBase):
|
||||||
will run on, default is None for localhost
|
will run on, default is None for localhost
|
||||||
"""
|
"""
|
||||||
super().__init__(session, _id, name, server)
|
super().__init__(session, _id, name, server)
|
||||||
self.nodedir: Optional[str] = nodedir
|
self.nodedir: Optional[Path] = nodedir
|
||||||
self.ctrlchnlname: str = os.path.abspath(
|
self.ctrlchnlname: Path = self.session.session_dir / self.name
|
||||||
os.path.join(self.session.session_dir, self.name)
|
|
||||||
)
|
|
||||||
self.client: Optional[VnodeClient] = None
|
self.client: Optional[VnodeClient] = None
|
||||||
self.pid: Optional[int] = None
|
self.pid: Optional[int] = None
|
||||||
self.lock: RLock = RLock()
|
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.node_net_client: LinuxNetClient = self.create_node_net_client(
|
||||||
self.session.use_ovs()
|
self.session.use_ovs()
|
||||||
)
|
)
|
||||||
|
@ -549,8 +564,8 @@ class CoreNode(CoreNodeBase):
|
||||||
self.up = True
|
self.up = True
|
||||||
|
|
||||||
# create private directories
|
# create private directories
|
||||||
self.privatedir("/var/run")
|
for dir_path in PRIVATE_DIRS:
|
||||||
self.privatedir("/var/log")
|
self.privatedir(dir_path)
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -561,29 +576,24 @@ class CoreNode(CoreNodeBase):
|
||||||
# nothing to do if node is not up
|
# nothing to do if node is not up
|
||||||
if not self.up:
|
if not self.up:
|
||||||
return
|
return
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
try:
|
try:
|
||||||
# unmount all targets (NOTE: non-persistent mount namespaces are
|
# unmount all targets (NOTE: non-persistent mount namespaces are
|
||||||
# removed by the kernel when last referencing process is killed)
|
# removed by the kernel when last referencing process is killed)
|
||||||
self._mounts = []
|
self._mounts = []
|
||||||
|
|
||||||
# shutdown all interfaces
|
# shutdown all interfaces
|
||||||
for iface in self.get_ifaces():
|
for iface in self.get_ifaces():
|
||||||
iface.shutdown()
|
iface.shutdown()
|
||||||
|
|
||||||
# kill node process if present
|
# kill node process if present
|
||||||
try:
|
try:
|
||||||
self.host_cmd(f"kill -9 {self.pid}")
|
self.host_cmd(f"kill -9 {self.pid}")
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
logging.exception("error killing process")
|
logging.exception("error killing process")
|
||||||
|
|
||||||
# remove node directory if present
|
# remove node directory if present
|
||||||
try:
|
try:
|
||||||
self.host_cmd(f"rm -rf {self.ctrlchnlname}")
|
self.host_cmd(f"rm -rf {self.ctrlchnlname}")
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
logging.exception("error removing node directory")
|
logging.exception("error removing node directory")
|
||||||
|
|
||||||
# clear interface data, close client, and mark self and not up
|
# clear interface data, close client, and mark self and not up
|
||||||
self.ifaces.clear()
|
self.ifaces.clear()
|
||||||
self.client.close()
|
self.client.close()
|
||||||
|
@ -636,35 +646,32 @@ class CoreNode(CoreNodeBase):
|
||||||
else:
|
else:
|
||||||
return f"ssh -X -f {self.server.host} xterm -e {terminal}"
|
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.
|
Create a private directory.
|
||||||
|
|
||||||
:param path: path to create
|
:param dir_path: path to create
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
if path[0] != "/":
|
if not str(dir_path).startswith("/"):
|
||||||
raise ValueError(f"path not fully qualified: {path}")
|
raise CoreError(f"private directory path not fully qualified: {dir_path}")
|
||||||
hostpath = os.path.join(
|
host_path = self.host_path(dir_path, is_dir=True)
|
||||||
self.nodedir, os.path.normpath(path).strip("/").replace("/", ".")
|
self.host_cmd(f"mkdir -p {host_path}")
|
||||||
)
|
self.mount(host_path, dir_path)
|
||||||
self.host_cmd(f"mkdir -p {hostpath}")
|
|
||||||
self.mount(hostpath, path)
|
|
||||||
|
|
||||||
def mount(self, source: str, target: str) -> None:
|
def mount(self, src_path: Path, target_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Create and mount a directory.
|
Create and mount a directory.
|
||||||
|
|
||||||
:param source: source directory to mount
|
:param src_path: source directory to mount
|
||||||
:param target: target directory to create
|
:param target_path: target directory to create
|
||||||
:return: nothing
|
:return: nothing
|
||||||
:raises CoreCommandError: when a non-zero exit status occurs
|
:raises CoreCommandError: when a non-zero exit status occurs
|
||||||
"""
|
"""
|
||||||
source = os.path.abspath(source)
|
logging.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path)
|
||||||
logging.debug("node(%s) mounting: %s at %s", self.name, source, target)
|
self.cmd(f"mkdir -p {target_path}")
|
||||||
self.cmd(f"mkdir -p {target}")
|
self.cmd(f"{MOUNT} -n --bind {src_path} {target_path}")
|
||||||
self.cmd(f"{MOUNT} -n --bind {source} {target}")
|
self._mounts.append((src_path, target_path))
|
||||||
self._mounts.append((source, target))
|
|
||||||
|
|
||||||
def next_iface_id(self) -> int:
|
def next_iface_id(self) -> int:
|
||||||
"""
|
"""
|
||||||
|
@ -851,86 +858,66 @@ class CoreNode(CoreNodeBase):
|
||||||
self.ifup(iface_id)
|
self.ifup(iface_id)
|
||||||
return self.get_iface(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.
|
Add a file.
|
||||||
|
|
||||||
:param srcname: source file name
|
:param src_path: source file path
|
||||||
:param filename: file name to add
|
:param file_path: file name to add
|
||||||
:return: nothing
|
:return: nothing
|
||||||
:raises CoreCommandError: when a non-zero exit status occurs
|
:raises CoreCommandError: when a non-zero exit status occurs
|
||||||
"""
|
"""
|
||||||
logging.info("adding file from %s to %s", srcname, filename)
|
logging.info("adding file from %s to %s", src_path, file_path)
|
||||||
directory = os.path.dirname(filename)
|
directory = file_path.parent
|
||||||
if self.server is None:
|
if self.server is None:
|
||||||
self.client.check_cmd(f"mkdir -p {directory}")
|
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")
|
self.client.check_cmd("sync")
|
||||||
else:
|
else:
|
||||||
self.host_cmd(f"mkdir -p {directory}")
|
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:
|
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
||||||
"""
|
|
||||||
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:
|
|
||||||
"""
|
"""
|
||||||
Create a node file with a given mode.
|
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 contents: contents of file
|
||||||
:param mode: mode for file
|
:param mode: mode for file
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
hostfilename = self.hostfilename(filename)
|
host_path = self.host_path(file_path)
|
||||||
dirname, _basename = os.path.split(hostfilename)
|
directory = host_path.parent
|
||||||
if self.server is None:
|
if self.server is None:
|
||||||
if not os.path.isdir(dirname):
|
if not directory.exists():
|
||||||
os.makedirs(dirname, mode=0o755)
|
directory.mkdir(parents=True, mode=0o755)
|
||||||
with open(hostfilename, "w") as open_file:
|
with host_path.open("w") as f:
|
||||||
open_file.write(contents)
|
f.write(contents)
|
||||||
os.chmod(open_file.name, mode)
|
host_path.chmod(mode)
|
||||||
else:
|
else:
|
||||||
self.host_cmd(f"mkdir -m {0o755:o} -p {dirname}")
|
self.host_cmd(f"mkdir -m {0o755:o} -p {directory}")
|
||||||
self.server.remote_put_temp(hostfilename, contents)
|
self.server.remote_put_temp(host_path, contents)
|
||||||
self.host_cmd(f"chmod {mode:o} {hostfilename}")
|
self.host_cmd(f"chmod {mode:o} {host_path}")
|
||||||
logging.debug(
|
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, host_path, mode)
|
||||||
"node(%s) added file: %s; mode: 0%o", self.name, hostfilename, 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.
|
Copy a file to a node, following symlinks and preserving metadata.
|
||||||
Change file mode if specified.
|
Change file mode if specified.
|
||||||
|
|
||||||
:param filename: file name to copy file to
|
:param file_path: file name to copy file to
|
||||||
:param srcfilename: file to copy
|
:param src_path: file to copy
|
||||||
:param mode: mode to copy to
|
:param mode: mode to copy to
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
hostfilename = self.hostfilename(filename)
|
host_path = self.host_path(file_path)
|
||||||
if self.server is None:
|
if self.server is None:
|
||||||
shutil.copy2(srcfilename, hostfilename)
|
shutil.copy2(src_path, host_path)
|
||||||
else:
|
else:
|
||||||
self.server.remote_put(srcfilename, hostfilename)
|
self.server.remote_put(src_path, host_path)
|
||||||
if mode is not None:
|
if mode is not None:
|
||||||
self.host_cmd(f"chmod {mode:o} {hostfilename}")
|
self.host_cmd(f"chmod {mode:o} {host_path}")
|
||||||
logging.info(
|
logging.info("node(%s) copied file: %s; mode: %s", self.name, host_path, mode)
|
||||||
"node(%s) copied file: %s; mode: %s", self.name, hostfilename, mode
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class CoreNetworkBase(NodeBase):
|
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.
|
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.
|
The control channel can be accessed via calls using the vcmd shell.
|
||||||
"""
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.executables import BASH, VCMD
|
from core.executables import BASH, VCMD
|
||||||
|
@ -13,7 +14,7 @@ class VnodeClient:
|
||||||
Provides client functionality for interacting with a virtual node.
|
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.
|
Create a VnodeClient instance.
|
||||||
|
|
||||||
|
@ -21,7 +22,7 @@ class VnodeClient:
|
||||||
:param ctrlchnlname: control channel name
|
:param ctrlchnlname: control channel name
|
||||||
"""
|
"""
|
||||||
self.name: str = name
|
self.name: str = name
|
||||||
self.ctrlchnlname: str = ctrlchnlname
|
self.ctrlchnlname: Path = ctrlchnlname
|
||||||
|
|
||||||
def _verify_connection(self) -> None:
|
def _verify_connection(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
||||||
|
|
||||||
|
@ -63,8 +63,8 @@ class DockerClient:
|
||||||
logging.debug("node(%s) pid: %s", self.name, self.pid)
|
logging.debug("node(%s) pid: %s", self.name, self.pid)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def copy_file(self, source: str, destination: str) -> str:
|
def copy_file(self, src_path: Path, dst_path: Path) -> str:
|
||||||
args = f"docker cp {source} {self.name}:{destination}"
|
args = f"docker cp {src_path} {self.name}:{dst_path}"
|
||||||
return self.run(args)
|
return self.run(args)
|
||||||
|
|
||||||
|
|
||||||
|
@ -162,77 +162,73 @@ class DockerNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
return f"docker exec -it {self.name} bash"
|
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.
|
Create a private directory.
|
||||||
|
|
||||||
:param path: path to create
|
:param dir_path: path to create
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logging.debug("creating node dir: %s", path)
|
logging.debug("creating node dir: %s", dir_path)
|
||||||
args = f"mkdir -p {path}"
|
args = f"mkdir -p {dir_path}"
|
||||||
self.cmd(args)
|
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.
|
Create and mount a directory.
|
||||||
|
|
||||||
:param source: source directory to mount
|
:param src_path: source directory to mount
|
||||||
:param target: target directory to create
|
:param target_path: target directory to create
|
||||||
:return: nothing
|
:return: nothing
|
||||||
:raises CoreCommandError: when a non-zero exit status occurs
|
: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")
|
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.
|
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 contents: contents of file
|
||||||
:param mode: mode for file
|
:param mode: mode for file
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logging.debug("nodefile filename(%s) mode(%s)", filename, mode)
|
logging.debug("nodefile filename(%s) mode(%s)", file_path, mode)
|
||||||
directory = os.path.dirname(filename)
|
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
temp.write(contents.encode("utf-8"))
|
temp.write(contents.encode("utf-8"))
|
||||||
temp.close()
|
temp.close()
|
||||||
|
temp_path = Path(temp.name)
|
||||||
if directory:
|
directory = file_path.name
|
||||||
|
if str(directory) != ".":
|
||||||
self.cmd(f"mkdir -m {0o755:o} -p {directory}")
|
self.cmd(f"mkdir -m {0o755:o} -p {directory}")
|
||||||
if self.server is not None:
|
if self.server is not None:
|
||||||
self.server.remote_put(temp.name, temp.name)
|
self.server.remote_put(temp_path, temp_path)
|
||||||
self.client.copy_file(temp.name, filename)
|
self.client.copy_file(temp_path, file_path)
|
||||||
self.cmd(f"chmod {mode:o} {filename}")
|
self.cmd(f"chmod {mode:o} {file_path}")
|
||||||
if self.server is not None:
|
if self.server is not None:
|
||||||
self.host_cmd(f"rm -f {temp.name}")
|
self.host_cmd(f"rm -f {temp_path}")
|
||||||
os.unlink(temp.name)
|
temp_path.unlink()
|
||||||
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode)
|
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.
|
Copy a file to a node, following symlinks and preserving metadata.
|
||||||
Change file mode if specified.
|
Change file mode if specified.
|
||||||
|
|
||||||
:param filename: file name to copy file to
|
:param file_path: file name to copy file to
|
||||||
:param srcfilename: file to copy
|
:param src_path: file to copy
|
||||||
:param mode: mode to copy to
|
:param mode: mode to copy to
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logging.info(
|
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 {file_path.parent}")
|
||||||
self.cmd(f"mkdir -p {directory}")
|
if self.server:
|
||||||
|
|
||||||
if self.server is None:
|
|
||||||
source = srcfilename
|
|
||||||
else:
|
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
source = temp.name
|
temp_path = Path(temp.name)
|
||||||
self.server.remote_put(source, temp.name)
|
src_path = temp_path
|
||||||
|
self.server.remote_put(src_path, temp_path)
|
||||||
self.client.copy_file(source, filename)
|
self.client.copy_file(src_path, file_path)
|
||||||
self.cmd(f"chmod {mode:o} {filename}")
|
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 logging
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
|
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
@ -79,7 +80,7 @@ class CoreInterface:
|
||||||
self,
|
self,
|
||||||
args: str,
|
args: str,
|
||||||
env: Dict[str, str] = None,
|
env: Dict[str, str] = None,
|
||||||
cwd: str = None,
|
cwd: Path = None,
|
||||||
wait: bool = True,
|
wait: bool = True,
|
||||||
shell: bool = False,
|
shell: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
||||||
|
|
||||||
|
@ -57,11 +57,10 @@ class LxdClient:
|
||||||
args = self.create_cmd(cmd)
|
args = self.create_cmd(cmd)
|
||||||
return utils.cmd(args, wait=wait, shell=shell)
|
return utils.cmd(args, wait=wait, shell=shell)
|
||||||
|
|
||||||
def copy_file(self, source: str, destination: str) -> None:
|
def copy_file(self, src_path: Path, dst_path: Path) -> None:
|
||||||
if destination[0] != "/":
|
if not str(dst_path).startswith("/"):
|
||||||
destination = os.path.join("/root/", destination)
|
dst_path = Path("/root/") / dst_path
|
||||||
|
args = f"lxc file push {src_path} {self.name}/{dst_path}"
|
||||||
args = f"lxc file push {source} {self.name}/{destination}"
|
|
||||||
self.run(args)
|
self.run(args)
|
||||||
|
|
||||||
|
|
||||||
|
@ -139,81 +138,76 @@ class LxcNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
return f"lxc exec {self.name} -- {sh}"
|
return f"lxc exec {self.name} -- {sh}"
|
||||||
|
|
||||||
def privatedir(self, path: str) -> None:
|
def privatedir(self, dir_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Create a private directory.
|
Create a private directory.
|
||||||
|
|
||||||
:param path: path to create
|
:param dir_path: path to create
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logging.info("creating node dir: %s", path)
|
logging.info("creating node dir: %s", dir_path)
|
||||||
args = f"mkdir -p {path}"
|
args = f"mkdir -p {dir_path}"
|
||||||
self.cmd(args)
|
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.
|
Create and mount a directory.
|
||||||
|
|
||||||
:param source: source directory to mount
|
:param src_path: source directory to mount
|
||||||
:param target: target directory to create
|
:param target_path: target directory to create
|
||||||
:return: nothing
|
:return: nothing
|
||||||
:raises CoreCommandError: when a non-zero exit status occurs
|
: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")
|
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.
|
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 contents: contents of file
|
||||||
:param mode: mode for file
|
:param mode: mode for file
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logging.debug("nodefile filename(%s) mode(%s)", filename, mode)
|
logging.debug("nodefile filename(%s) mode(%s)", file_path, mode)
|
||||||
|
|
||||||
directory = os.path.dirname(filename)
|
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
temp.write(contents.encode("utf-8"))
|
temp.write(contents.encode("utf-8"))
|
||||||
temp.close()
|
temp.close()
|
||||||
|
temp_path = Path(temp.name)
|
||||||
if directory:
|
directory = file_path.parent
|
||||||
|
if str(directory) != ".":
|
||||||
self.cmd(f"mkdir -m {0o755:o} -p {directory}")
|
self.cmd(f"mkdir -m {0o755:o} -p {directory}")
|
||||||
if self.server is not None:
|
if self.server is not None:
|
||||||
self.server.remote_put(temp.name, temp.name)
|
self.server.remote_put(temp_path, temp_path)
|
||||||
self.client.copy_file(temp.name, filename)
|
self.client.copy_file(temp_path, file_path)
|
||||||
self.cmd(f"chmod {mode:o} {filename}")
|
self.cmd(f"chmod {mode:o} {file_path}")
|
||||||
if self.server is not None:
|
if self.server is not None:
|
||||||
self.host_cmd(f"rm -f {temp.name}")
|
self.host_cmd(f"rm -f {temp_path}")
|
||||||
os.unlink(temp.name)
|
temp_path.unlink()
|
||||||
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode)
|
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.
|
Copy a file to a node, following symlinks and preserving metadata.
|
||||||
Change file mode if specified.
|
Change file mode if specified.
|
||||||
|
|
||||||
:param filename: file name to copy file to
|
:param file_path: file name to copy file to
|
||||||
:param srcfilename: file to copy
|
:param src_path: file to copy
|
||||||
:param mode: mode to copy to
|
:param mode: mode to copy to
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logging.info(
|
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 {file_path.parent}")
|
||||||
self.cmd(f"mkdir -p {directory}")
|
if self.server:
|
||||||
|
|
||||||
if self.server is None:
|
|
||||||
source = srcfilename
|
|
||||||
else:
|
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
source = temp.name
|
temp_path = Path(temp.name)
|
||||||
self.server.remote_put(source, temp.name)
|
src_path = temp_path
|
||||||
|
self.server.remote_put(src_path, temp_path)
|
||||||
self.client.copy_file(source, filename)
|
self.client.copy_file(src_path, file_path)
|
||||||
self.cmd(f"chmod {mode:o} {filename}")
|
self.cmd(f"chmod {mode:o} {file_path}")
|
||||||
|
|
||||||
def add_iface(self, iface: CoreInterface, iface_id: int) -> None:
|
def add_iface(self, iface: CoreInterface, iface_id: int) -> None:
|
||||||
super().add_iface(iface, iface_id)
|
super().add_iface(iface, iface_id)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
||||||
import math
|
import math
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
|
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
@ -292,7 +293,7 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
self,
|
self,
|
||||||
args: str,
|
args: str,
|
||||||
env: Dict[str, str] = None,
|
env: Dict[str, str] = None,
|
||||||
cwd: str = None,
|
cwd: Path = None,
|
||||||
wait: bool = True,
|
wait: bool = True,
|
||||||
shell: bool = False,
|
shell: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
@ -333,9 +334,7 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
"""
|
"""
|
||||||
if not self.up:
|
if not self.up:
|
||||||
return
|
return
|
||||||
|
|
||||||
ebq.stopupdateloop(self)
|
ebq.stopupdateloop(self)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.net_client.delete_bridge(self.brname)
|
self.net_client.delete_bridge(self.brname)
|
||||||
if self.has_ebtables_chain:
|
if self.has_ebtables_chain:
|
||||||
|
@ -346,11 +345,9 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
ebtablescmds(self.host_cmd, cmds)
|
ebtablescmds(self.host_cmd, cmds)
|
||||||
except CoreCommandError:
|
except CoreCommandError:
|
||||||
logging.exception("error during shutdown")
|
logging.exception("error during shutdown")
|
||||||
|
|
||||||
# removes veth pairs used for bridge-to-bridge connections
|
# removes veth pairs used for bridge-to-bridge connections
|
||||||
for iface in self.get_ifaces():
|
for iface in self.get_ifaces():
|
||||||
iface.shutdown()
|
iface.shutdown()
|
||||||
|
|
||||||
self.ifaces.clear()
|
self.ifaces.clear()
|
||||||
self._linked.clear()
|
self._linked.clear()
|
||||||
del self.session
|
del self.session
|
||||||
|
@ -389,10 +386,8 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
# check if the network interfaces are attached to this network
|
# check if the network interfaces are attached to this network
|
||||||
if self.ifaces[iface1.net_id] != iface1:
|
if self.ifaces[iface1.net_id] != iface1:
|
||||||
raise ValueError(f"inconsistency for interface {iface1.name}")
|
raise ValueError(f"inconsistency for interface {iface1.name}")
|
||||||
|
|
||||||
if self.ifaces[iface2.net_id] != iface2:
|
if self.ifaces[iface2.net_id] != iface2:
|
||||||
raise ValueError(f"inconsistency for interface {iface2.name}")
|
raise ValueError(f"inconsistency for interface {iface2.name}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
linked = self._linked[iface1][iface2]
|
linked = self._linked[iface1][iface2]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -403,7 +398,6 @@ class CoreNetwork(CoreNetworkBase):
|
||||||
else:
|
else:
|
||||||
raise Exception(f"unknown policy: {self.policy.value}")
|
raise Exception(f"unknown policy: {self.policy.value}")
|
||||||
self._linked[iface1][iface2] = linked
|
self._linked[iface1][iface2] = linked
|
||||||
|
|
||||||
return linked
|
return linked
|
||||||
|
|
||||||
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
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 logging
|
||||||
import os
|
|
||||||
import threading
|
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.data import InterfaceData, LinkOptions
|
||||||
from core.emulator.distributed import DistributedServer
|
from core.emulator.distributed import DistributedServer
|
||||||
|
@ -26,15 +26,15 @@ class PhysicalNode(CoreNodeBase):
|
||||||
session: "Session",
|
session: "Session",
|
||||||
_id: int = None,
|
_id: int = None,
|
||||||
name: str = None,
|
name: str = None,
|
||||||
nodedir: str = None,
|
nodedir: Path = None,
|
||||||
server: DistributedServer = None,
|
server: DistributedServer = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(session, _id, name, server)
|
super().__init__(session, _id, name, server)
|
||||||
if not self.server:
|
if not self.server:
|
||||||
raise CoreError("physical nodes must be assigned to a remote 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.lock: threading.RLock = threading.RLock()
|
||||||
self._mounts: List[Tuple[str, str]] = []
|
self._mounts: List[Tuple[Path, Path]] = []
|
||||||
|
|
||||||
def startup(self) -> None:
|
def startup(self) -> None:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
|
@ -44,15 +44,12 @@ class PhysicalNode(CoreNodeBase):
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
if not self.up:
|
if not self.up:
|
||||||
return
|
return
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
while self._mounts:
|
while self._mounts:
|
||||||
_source, target = self._mounts.pop(-1)
|
_, target_path = self._mounts.pop(-1)
|
||||||
self.umount(target)
|
self.umount(target_path)
|
||||||
|
|
||||||
for iface in self.get_ifaces():
|
for iface in self.get_ifaces():
|
||||||
iface.shutdown()
|
iface.shutdown()
|
||||||
|
|
||||||
self.rmnodedir()
|
self.rmnodedir()
|
||||||
|
|
||||||
def path_exists(self, path: str) -> bool:
|
def path_exists(self, path: str) -> bool:
|
||||||
|
@ -186,55 +183,40 @@ class PhysicalNode(CoreNodeBase):
|
||||||
self.adopt_iface(iface, iface_id, iface_data.mac, ips)
|
self.adopt_iface(iface, iface_id, iface_data.mac, ips)
|
||||||
return iface
|
return iface
|
||||||
|
|
||||||
def privatedir(self, path: str) -> None:
|
def privatedir(self, dir_path: Path) -> None:
|
||||||
if path[0] != "/":
|
if not str(dir_path).startswith("/"):
|
||||||
raise ValueError(f"path not fully qualified: {path}")
|
raise CoreError(f"private directory path not fully qualified: {dir_path}")
|
||||||
hostpath = os.path.join(
|
host_path = self.host_path(dir_path, is_dir=True)
|
||||||
self.nodedir, os.path.normpath(path).strip("/").replace("/", ".")
|
self.host_cmd(f"mkdir -p {host_path}")
|
||||||
)
|
self.mount(host_path, dir_path)
|
||||||
os.mkdir(hostpath)
|
|
||||||
self.mount(hostpath, path)
|
|
||||||
|
|
||||||
def mount(self, source: str, target: str) -> None:
|
def mount(self, src_path: Path, target_path: Path) -> None:
|
||||||
source = os.path.abspath(source)
|
logging.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path)
|
||||||
logging.info("mounting %s at %s", source, target)
|
self.cmd(f"mkdir -p {target_path}")
|
||||||
os.makedirs(target)
|
self.host_cmd(f"{MOUNT} --bind {src_path} {target_path}", cwd=self.nodedir)
|
||||||
self.host_cmd(f"{MOUNT} --bind {source} {target}", cwd=self.nodedir)
|
self._mounts.append((src_path, target_path))
|
||||||
self._mounts.append((source, target))
|
|
||||||
|
|
||||||
def umount(self, target: str) -> None:
|
def umount(self, target_path: Path) -> None:
|
||||||
logging.info("unmounting '%s'", target)
|
logging.info("unmounting '%s'", target_path)
|
||||||
try:
|
try:
|
||||||
self.host_cmd(f"{UMOUNT} -l {target}", cwd=self.nodedir)
|
self.host_cmd(f"{UMOUNT} -l {target_path}", cwd=self.nodedir)
|
||||||
except CoreCommandError:
|
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:
|
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
||||||
dirname, basename = os.path.split(filename)
|
host_path = self.host_path(file_path)
|
||||||
if not basename:
|
directory = host_path.parent
|
||||||
raise ValueError("no basename for filename: " + filename)
|
if not directory.is_dir():
|
||||||
|
directory.mkdir(parents=True, mode=0o755)
|
||||||
if dirname and dirname[0] == "/":
|
with host_path.open("w") as f:
|
||||||
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:
|
|
||||||
f.write(contents)
|
f.write(contents)
|
||||||
os.chmod(f.name, mode)
|
host_path.chmod(mode)
|
||||||
logging.info("created nodefile: '%s'; mode: 0%o", f.name, mode)
|
logging.info("created nodefile: '%s'; mode: 0%o", host_path, mode)
|
||||||
|
|
||||||
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
|
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
|
||||||
return self.host_cmd(args, wait=wait)
|
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")
|
raise CoreError("physical node does not support addfile")
|
||||||
|
|
||||||
|
|
||||||
|
@ -464,10 +446,10 @@ class Rj45Node(CoreNodeBase):
|
||||||
def termcmdstring(self, sh: str) -> str:
|
def termcmdstring(self, sh: str) -> str:
|
||||||
raise CoreError("rj45 does not support terminal commands")
|
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")
|
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")
|
raise CoreError("rj45 does not support nodefile")
|
||||||
|
|
||||||
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
|
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
|
Services available to nodes can be put in this directory. Everything listed in
|
||||||
__all__ is automatically loaded by the main core module.
|
__all__ is automatically loaded by the main core module.
|
||||||
"""
|
"""
|
||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
from core.services.coreservices import ServiceManager
|
from core.services.coreservices import ServiceManager
|
||||||
|
|
||||||
_PATH = os.path.abspath(os.path.dirname(__file__))
|
_PATH: Path = Path(__file__).resolve().parent
|
||||||
|
|
||||||
|
|
||||||
def load():
|
def load():
|
||||||
|
|
|
@ -10,6 +10,7 @@ services.
|
||||||
import enum
|
import enum
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
from typing import (
|
from typing import (
|
||||||
TYPE_CHECKING,
|
TYPE_CHECKING,
|
||||||
Dict,
|
Dict,
|
||||||
|
@ -264,7 +265,7 @@ class ServiceManager:
|
||||||
return service
|
return service
|
||||||
|
|
||||||
@classmethod
|
@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.
|
Method for retrieving all CoreServices from a given path.
|
||||||
|
|
||||||
|
@ -276,7 +277,6 @@ class ServiceManager:
|
||||||
for service in services:
|
for service in services:
|
||||||
if not service.name:
|
if not service.name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cls.add(service)
|
cls.add(service)
|
||||||
except (CoreError, ValueError) as e:
|
except (CoreError, ValueError) as e:
|
||||||
|
@ -488,9 +488,10 @@ class CoreServices:
|
||||||
|
|
||||||
# create service directories
|
# create service directories
|
||||||
for directory in service.dirs:
|
for directory in service.dirs:
|
||||||
|
dir_path = Path(directory)
|
||||||
try:
|
try:
|
||||||
node.privatedir(directory)
|
node.privatedir(dir_path)
|
||||||
except (CoreCommandError, ValueError) as e:
|
except (CoreCommandError, CoreError) as e:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
"error mounting private dir '%s' for service '%s': %s",
|
"error mounting private dir '%s' for service '%s': %s",
|
||||||
directory,
|
directory,
|
||||||
|
@ -534,14 +535,14 @@ class CoreServices:
|
||||||
"node(%s) service(%s) failed validation" % (node.name, service.name)
|
"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
|
Given a configured service filename and config, determine if the
|
||||||
config references an existing file that should be copied.
|
config references an existing file that should be copied.
|
||||||
Returns True for local files, False for generated.
|
Returns True for local files, False for generated.
|
||||||
|
|
||||||
:param node: node to copy service for
|
: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
|
:param cfg: configuration string
|
||||||
:return: True if successful, False otherwise
|
:return: True if successful, False otherwise
|
||||||
"""
|
"""
|
||||||
|
@ -550,7 +551,7 @@ class CoreServices:
|
||||||
src = src.split("\n")[0]
|
src = src.split("\n")[0]
|
||||||
src = utils.expand_corepath(src, node.session, node)
|
src = utils.expand_corepath(src, node.session, node)
|
||||||
# TODO: glob here
|
# TODO: glob here
|
||||||
node.nodefilecopy(filename, src, mode=0o644)
|
node.nodefilecopy(file_path, src, mode=0o644)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -729,8 +730,8 @@ class CoreServices:
|
||||||
config_files = service.configs
|
config_files = service.configs
|
||||||
if not service.custom:
|
if not service.custom:
|
||||||
config_files = service.get_configs(node)
|
config_files = service.get_configs(node)
|
||||||
|
|
||||||
for file_name in config_files:
|
for file_name in config_files:
|
||||||
|
file_path = Path(file_name)
|
||||||
logging.debug(
|
logging.debug(
|
||||||
"generating service config custom(%s): %s", service.custom, file_name
|
"generating service config custom(%s): %s", service.custom, file_name
|
||||||
)
|
)
|
||||||
|
@ -738,18 +739,16 @@ class CoreServices:
|
||||||
cfg = service.config_data.get(file_name)
|
cfg = service.config_data.get(file_name)
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
cfg = service.generate_config(node, file_name)
|
cfg = service.generate_config(node, file_name)
|
||||||
|
|
||||||
# cfg may have a file:/// url for copying from a file
|
# cfg may have a file:/// url for copying from a file
|
||||||
try:
|
try:
|
||||||
if self.copy_service_file(node, file_name, cfg):
|
if self.copy_service_file(node, file_path, cfg):
|
||||||
continue
|
continue
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.exception("error copying service file: %s", file_name)
|
logging.exception("error copying service file: %s", file_name)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
cfg = service.generate_config(node, file_name)
|
cfg = service.generate_config(node, file_name)
|
||||||
|
node.nodefile(file_path, cfg)
|
||||||
node.nodefile(file_name, cfg)
|
|
||||||
|
|
||||||
def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None:
|
def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -762,17 +761,15 @@ class CoreServices:
|
||||||
config_files = service.configs
|
config_files = service.configs
|
||||||
if not service.custom:
|
if not service.custom:
|
||||||
config_files = service.get_configs(node)
|
config_files = service.get_configs(node)
|
||||||
|
|
||||||
for file_name in config_files:
|
for file_name in config_files:
|
||||||
|
file_path = Path(file_name)
|
||||||
if file_name[:7] == "file:///":
|
if file_name[:7] == "file:///":
|
||||||
# TODO: implement this
|
# TODO: implement this
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
cfg = service.config_data.get(file_name)
|
cfg = service.config_data.get(file_name)
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
cfg = service.generate_config(node, file_name)
|
cfg = service.generate_config(node, file_name)
|
||||||
|
node.nodefile(file_path, cfg)
|
||||||
node.nodefile(file_name, cfg)
|
|
||||||
|
|
||||||
|
|
||||||
class CoreService:
|
class CoreService:
|
||||||
|
|
|
@ -46,11 +46,10 @@ IFACE_CONFIG_FACTOR: int = 1000
|
||||||
|
|
||||||
|
|
||||||
def execute_file(
|
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:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Provides an alternative way to run execfile to be compatible for
|
Provides a way to execute a file.
|
||||||
both python2/3.
|
|
||||||
|
|
||||||
:param path: path of file to execute
|
:param path: path of file to execute
|
||||||
:param exec_globals: globals values to pass to execution
|
:param exec_globals: globals values to pass to execution
|
||||||
|
@ -59,10 +58,10 @@ def execute_file(
|
||||||
"""
|
"""
|
||||||
if exec_globals is None:
|
if exec_globals is None:
|
||||||
exec_globals = {}
|
exec_globals = {}
|
||||||
exec_globals.update({"__file__": path, "__name__": "__main__"})
|
exec_globals.update({"__file__": str(path), "__name__": "__main__"})
|
||||||
with open(path, "rb") as f:
|
with path.open("rb") as f:
|
||||||
data = compile(f.read(), path, "exec")
|
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:
|
def hashkey(value: Union[str, int]) -> int:
|
||||||
|
@ -92,24 +91,19 @@ def _detach_init() -> None:
|
||||||
os.setsid()
|
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.
|
Check if file is a valid python module.
|
||||||
|
|
||||||
:param path: path to file
|
:param path: path to file
|
||||||
:param file_name: file name to check
|
|
||||||
:return: True if a valid python module file, False otherwise
|
:return: True if a valid python module file, False otherwise
|
||||||
"""
|
"""
|
||||||
file_path = os.path.join(path, file_name)
|
if not path.is_file():
|
||||||
if not os.path.isfile(file_path):
|
|
||||||
return False
|
return False
|
||||||
|
if path.name.startswith("_"):
|
||||||
if file_name.startswith("_"):
|
|
||||||
return False
|
return False
|
||||||
|
if not path.suffix == ".py":
|
||||||
if not file_name.endswith(".py"):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,13 +118,10 @@ def _is_class(module: Any, member: Type, clazz: Type) -> bool:
|
||||||
"""
|
"""
|
||||||
if not inspect.isclass(member):
|
if not inspect.isclass(member):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not issubclass(member, clazz):
|
if not issubclass(member, clazz):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if member.__module__ != module.__name__:
|
if member.__module__ != module.__name__:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@ -196,7 +187,7 @@ def mute_detach(args: str, **kwargs: Dict[str, Any]) -> int:
|
||||||
def cmd(
|
def cmd(
|
||||||
args: str,
|
args: str,
|
||||||
env: Dict[str, str] = None,
|
env: Dict[str, str] = None,
|
||||||
cwd: str = None,
|
cwd: Path = None,
|
||||||
wait: bool = True,
|
wait: bool = True,
|
||||||
shell: bool = False,
|
shell: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
|
@ -282,7 +273,7 @@ def file_demunge(pathname: str, header: str) -> None:
|
||||||
|
|
||||||
def expand_corepath(
|
def expand_corepath(
|
||||||
pathname: str, session: "Session" = None, node: "CoreNode" = None
|
pathname: str, session: "Session" = None, node: "CoreNode" = None
|
||||||
) -> str:
|
) -> Path:
|
||||||
"""
|
"""
|
||||||
Expand a file path given session information.
|
Expand a file path given session information.
|
||||||
|
|
||||||
|
@ -294,14 +285,12 @@ def expand_corepath(
|
||||||
if session is not None:
|
if session is not None:
|
||||||
pathname = pathname.replace("~", f"/home/{session.user}")
|
pathname = pathname.replace("~", f"/home/{session.user}")
|
||||||
pathname = pathname.replace("%SESSION%", str(session.id))
|
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)
|
pathname = pathname.replace("%SESSION_USER%", session.user)
|
||||||
|
|
||||||
if node is not None:
|
if node is not None:
|
||||||
pathname = pathname.replace("%NODE%", str(node.id))
|
pathname = pathname.replace("%NODE%", str(node.id))
|
||||||
pathname = pathname.replace("%NODENAME%", node.name)
|
pathname = pathname.replace("%NODENAME%", node.name)
|
||||||
|
return Path(pathname)
|
||||||
return pathname
|
|
||||||
|
|
||||||
|
|
||||||
def sysctl_devname(devname: str) -> Optional[str]:
|
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)
|
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.
|
Dynamically load classes for use within CORE.
|
||||||
|
|
||||||
|
@ -347,24 +336,19 @@ def load_classes(path: str, clazz: Generic[T]) -> T:
|
||||||
"""
|
"""
|
||||||
# validate path exists
|
# validate path exists
|
||||||
logging.debug("attempting to load modules from path: %s", path)
|
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)
|
logging.warning("invalid custom module directory specified" ": %s", path)
|
||||||
# check if path is in sys.path
|
# check if path is in sys.path
|
||||||
parent_path = os.path.dirname(path)
|
parent = str(path.parent)
|
||||||
if parent_path not in sys.path:
|
if parent not in sys.path:
|
||||||
logging.debug("adding parent path to allow imports: %s", parent_path)
|
logging.debug("adding parent path to allow imports: %s", parent)
|
||||||
sys.path.append(parent_path)
|
sys.path.append(parent)
|
||||||
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
# import and add all service modules in the path
|
# import and add all service modules in the path
|
||||||
classes = []
|
classes = []
|
||||||
for module_name in module_names:
|
for p in path.iterdir():
|
||||||
import_statement = f"{base_module}.{module_name}"
|
if not _valid_module(p):
|
||||||
|
continue
|
||||||
|
import_statement = f"{path.name}.{p.stem}"
|
||||||
logging.debug("importing custom module: %s", import_statement)
|
logging.debug("importing custom module: %s", import_statement)
|
||||||
try:
|
try:
|
||||||
module = importlib.import_module(import_statement)
|
module = importlib.import_module(import_statement)
|
||||||
|
@ -376,20 +360,19 @@ def load_classes(path: str, clazz: Generic[T]) -> T:
|
||||||
logging.exception(
|
logging.exception(
|
||||||
"unexpected error during import, skipping: %s", import_statement
|
"unexpected error during import, skipping: %s", import_statement
|
||||||
)
|
)
|
||||||
|
|
||||||
return classes
|
return classes
|
||||||
|
|
||||||
|
|
||||||
def load_logging_config(config_path: str) -> None:
|
def load_logging_config(config_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Load CORE logging configuration file.
|
Load CORE logging configuration file.
|
||||||
|
|
||||||
:param config_path: path to logging config file
|
:param config_path: path to logging config file
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
with open(config_path, "r") as log_config_file:
|
with config_path.open("r") as f:
|
||||||
log_config = json.load(log_config_file)
|
log_config = json.load(f)
|
||||||
logging.config.dictConfig(log_config)
|
logging.config.dictConfig(log_config)
|
||||||
|
|
||||||
|
|
||||||
def threadpool(
|
def threadpool(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, Type, TypeVar
|
from typing import TYPE_CHECKING, Any, Dict, Generic, List, Optional, Type, TypeVar
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
@ -25,7 +26,7 @@ T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
def write_xml_file(
|
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:
|
) -> None:
|
||||||
xml_data = etree.tostring(
|
xml_data = etree.tostring(
|
||||||
xml_element,
|
xml_element,
|
||||||
|
@ -34,8 +35,8 @@ def write_xml_file(
|
||||||
encoding="UTF-8",
|
encoding="UTF-8",
|
||||||
doctype=doctype,
|
doctype=doctype,
|
||||||
)
|
)
|
||||||
with open(file_path, "wb") as xml_file:
|
with file_path.open("wb") as f:
|
||||||
xml_file.write(xml_data)
|
f.write(xml_data)
|
||||||
|
|
||||||
|
|
||||||
def get_type(element: etree.Element, name: str, _type: Generic[T]) -> Optional[T]:
|
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_session_metadata()
|
||||||
self.write_default_services()
|
self.write_default_services()
|
||||||
|
|
||||||
def write(self, file_name: str) -> None:
|
def write(self, path: Path) -> None:
|
||||||
self.scenario.set("name", file_name)
|
self.scenario.set("name", str(path))
|
||||||
|
|
||||||
# write out generated xml
|
# write out generated xml
|
||||||
xml_tree = etree.ElementTree(self.scenario)
|
xml_tree = etree.ElementTree(self.scenario)
|
||||||
xml_tree.write(
|
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:
|
def write_session_origin(self) -> None:
|
||||||
|
@ -580,8 +580,8 @@ class CoreXmlReader:
|
||||||
self.session: "Session" = session
|
self.session: "Session" = session
|
||||||
self.scenario: Optional[etree.ElementTree] = None
|
self.scenario: Optional[etree.ElementTree] = None
|
||||||
|
|
||||||
def read(self, file_name: str) -> None:
|
def read(self, file_path: Path) -> None:
|
||||||
xml_tree = etree.parse(file_name)
|
xml_tree = etree.parse(str(file_path))
|
||||||
self.scenario = xml_tree.getroot()
|
self.scenario = xml_tree.getroot()
|
||||||
|
|
||||||
# read xml session content
|
# read xml session content
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
|
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(
|
def create_file(
|
||||||
xml_element: etree.Element,
|
xml_element: etree.Element,
|
||||||
doc_name: str,
|
doc_name: str,
|
||||||
file_path: str,
|
file_path: Path,
|
||||||
server: DistributedServer = None,
|
server: DistributedServer = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -71,10 +71,11 @@ def create_file(
|
||||||
)
|
)
|
||||||
if server:
|
if server:
|
||||||
temp = NamedTemporaryFile(delete=False)
|
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()
|
temp.close()
|
||||||
server.remote_put(temp.name, file_path)
|
server.remote_put(temp_path, file_path)
|
||||||
os.unlink(temp.name)
|
temp_path.unlink()
|
||||||
else:
|
else:
|
||||||
corexml.write_xml_file(xml_element, file_path, doctype=doctype)
|
corexml.write_xml_file(xml_element, file_path, doctype=doctype)
|
||||||
|
|
||||||
|
@ -92,9 +93,9 @@ def create_node_file(
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if isinstance(node, CoreNode):
|
if isinstance(node, CoreNode):
|
||||||
file_path = os.path.join(node.nodedir, file_name)
|
file_path = node.nodedir / file_name
|
||||||
else:
|
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)
|
create_file(xml_element, doc_name, file_path, node.server)
|
||||||
|
|
||||||
|
|
||||||
|
@ -316,7 +317,7 @@ def create_event_service_xml(
|
||||||
group: str,
|
group: str,
|
||||||
port: str,
|
port: str,
|
||||||
device: str,
|
device: str,
|
||||||
file_directory: str,
|
file_directory: Path,
|
||||||
server: DistributedServer = None,
|
server: DistributedServer = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -340,8 +341,7 @@ def create_event_service_xml(
|
||||||
):
|
):
|
||||||
sub_element = etree.SubElement(event_element, name)
|
sub_element = etree.SubElement(event_element, name)
|
||||||
sub_element.text = value
|
sub_element.text = value
|
||||||
file_name = "libemaneeventservice.xml"
|
file_path = file_directory / "libemaneeventservice.xml"
|
||||||
file_path = os.path.join(file_directory, file_name)
|
|
||||||
create_file(event_element, "emaneeventmsgsvc", file_path, server)
|
create_file(event_element, "emaneeventmsgsvc", file_path, server)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from core import constants
|
from core import constants
|
||||||
from core.api.grpc.server import CoreGrpcServer
|
from core.api.grpc.server import CoreGrpcServer
|
||||||
|
@ -148,7 +149,8 @@ def main():
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
cfg = get_merged_config(f"{CORE_CONF_DIR}/core.conf")
|
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()
|
banner()
|
||||||
try:
|
try:
|
||||||
cored(cfg)
|
cored(cfg)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Unit tests for testing CORE EMANE networks.
|
Unit tests for testing CORE EMANE networks.
|
||||||
"""
|
"""
|
||||||
import os
|
from pathlib import Path
|
||||||
from tempfile import TemporaryFile
|
from tempfile import TemporaryFile
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
@ -28,7 +28,8 @@ _EMANE_MODELS = [
|
||||||
EmaneCommEffectModel,
|
EmaneCommEffectModel,
|
||||||
EmaneTdmaModel,
|
EmaneTdmaModel,
|
||||||
]
|
]
|
||||||
_DIR = os.path.dirname(os.path.abspath(__file__))
|
_DIR: Path = Path(__file__).resolve().parent
|
||||||
|
_SCHEDULE: Path = _DIR / "../../examples/tdma/schedule.xml"
|
||||||
|
|
||||||
|
|
||||||
def ping(
|
def ping(
|
||||||
|
@ -107,9 +108,7 @@ class TestEmane:
|
||||||
# configure tdma
|
# configure tdma
|
||||||
if model == EmaneTdmaModel:
|
if model == EmaneTdmaModel:
|
||||||
session.emane.set_model_config(
|
session.emane.set_model_config(
|
||||||
emane_network.id,
|
emane_network.id, EmaneTdmaModel.name, {"schedule": str(_SCHEDULE)}
|
||||||
EmaneTdmaModel.name,
|
|
||||||
{"schedule": os.path.join(_DIR, "../../examples/tdma/schedule.xml")},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# create nodes
|
# create nodes
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from pathlib import Path
|
||||||
from unittest import mock
|
from unittest import mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -68,7 +69,8 @@ class TestConfigServices:
|
||||||
service.create_dirs()
|
service.create_dirs()
|
||||||
|
|
||||||
# then
|
# 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):
|
def test_create_files_custom(self):
|
||||||
# given
|
# given
|
||||||
|
@ -81,7 +83,8 @@ class TestConfigServices:
|
||||||
service.create_files()
|
service.create_files()
|
||||||
|
|
||||||
# then
|
# 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):
|
def test_create_files_text(self):
|
||||||
# given
|
# given
|
||||||
|
@ -92,7 +95,8 @@ class TestConfigServices:
|
||||||
service.create_files()
|
service.create_files()
|
||||||
|
|
||||||
# then
|
# 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):
|
def test_run_startup(self):
|
||||||
# given
|
# given
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
Unit tests for testing basic CORE networks.
|
Unit tests for testing basic CORE networks.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import threading
|
import threading
|
||||||
from typing import Type
|
from pathlib import Path
|
||||||
|
from typing import List, Type
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@ from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility
|
||||||
from core.nodes.base import CoreNode, NodeBase
|
from core.nodes.base import CoreNode, NodeBase
|
||||||
from core.nodes.network import HubNode, PtpNet, SwitchNode, WlanNode
|
from core.nodes.network import HubNode, PtpNet, SwitchNode, WlanNode
|
||||||
|
|
||||||
_PATH = os.path.abspath(os.path.dirname(__file__))
|
_PATH: Path = Path(__file__).resolve().parent
|
||||||
_MOBILITY_FILE = os.path.join(_PATH, "mobility.scen")
|
_MOBILITY_FILE: Path = _PATH / "mobility.scen"
|
||||||
_WIRED = [PtpNet, HubNode, SwitchNode]
|
_WIRED: List = [PtpNet, HubNode, SwitchNode]
|
||||||
|
|
||||||
|
|
||||||
def ping(from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes):
|
def ping(from_node: CoreNode, to_node: CoreNode, ip_prefixes: IpPrefixes):
|
||||||
|
@ -195,7 +195,7 @@ class TestCore:
|
||||||
|
|
||||||
# configure mobility script for session
|
# configure mobility script for session
|
||||||
config = {
|
config = {
|
||||||
"file": _MOBILITY_FILE,
|
"file": str(_MOBILITY_FILE),
|
||||||
"refresh_ms": "50",
|
"refresh_ms": "50",
|
||||||
"loop": "1",
|
"loop": "1",
|
||||||
"autostart": "0.0",
|
"autostart": "0.0",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
"""
|
"""
|
||||||
Tests for testing tlv message handling.
|
Tests for testing tlv message handling.
|
||||||
"""
|
"""
|
||||||
import os
|
|
||||||
import time
|
import time
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
@ -425,7 +425,7 @@ class TestGui:
|
||||||
assert file_data == service_file.data
|
assert file_data == service_file.data
|
||||||
|
|
||||||
def test_file_node_file_copy(self, request, coretlv: CoreHandler):
|
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 = coretlv.session.add_node(CoreNode)
|
||||||
node.makenodedir()
|
node.makenodedir()
|
||||||
file_data = "echo hello"
|
file_data = "echo hello"
|
||||||
|
@ -433,7 +433,7 @@ class TestGui:
|
||||||
MessageFlags.ADD.value,
|
MessageFlags.ADD.value,
|
||||||
[
|
[
|
||||||
(FileTlvs.NODE, node.id),
|
(FileTlvs.NODE, node.id),
|
||||||
(FileTlvs.NAME, file_name),
|
(FileTlvs.NAME, str(file_path)),
|
||||||
(FileTlvs.DATA, file_data),
|
(FileTlvs.DATA, file_data),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -441,10 +441,10 @@ class TestGui:
|
||||||
coretlv.handle_message(message)
|
coretlv.handle_message(message)
|
||||||
|
|
||||||
if not request.config.getoption("mock"):
|
if not request.config.getoption("mock"):
|
||||||
directory, basename = os.path.split(file_name)
|
directory = str(file_path.parent)
|
||||||
created_directory = directory[1:].replace("/", ".")
|
created_directory = directory[1:].replace("/", ".")
|
||||||
create_path = os.path.join(node.nodedir, created_directory, basename)
|
create_path = node.nodedir / created_directory / file_path.name
|
||||||
assert os.path.exists(create_path)
|
assert create_path.exists()
|
||||||
|
|
||||||
def test_exec_node_tty(self, coretlv: CoreHandler):
|
def test_exec_node_tty(self, coretlv: CoreHandler):
|
||||||
coretlv.dispatch_replies = mock.MagicMock()
|
coretlv.dispatch_replies = mock.MagicMock()
|
||||||
|
@ -547,20 +547,21 @@ class TestGui:
|
||||||
0,
|
0,
|
||||||
[(EventTlvs.TYPE, EventTypes.FILE_SAVE.value), (EventTlvs.NAME, file_path)],
|
[(EventTlvs.TYPE, EventTypes.FILE_SAVE.value), (EventTlvs.NAME, file_path)],
|
||||||
)
|
)
|
||||||
|
|
||||||
coretlv.handle_message(message)
|
coretlv.handle_message(message)
|
||||||
|
assert Path(file_path).exists()
|
||||||
assert os.path.exists(file_path)
|
|
||||||
|
|
||||||
def test_event_open_xml(self, coretlv: CoreHandler, tmpdir):
|
def test_event_open_xml(self, coretlv: CoreHandler, tmpdir):
|
||||||
xml_file = tmpdir.join("coretlv.session.xml")
|
xml_file = tmpdir.join("coretlv.session.xml")
|
||||||
file_path = xml_file.strpath
|
file_path = Path(xml_file.strpath)
|
||||||
node = coretlv.session.add_node(CoreNode)
|
node = coretlv.session.add_node(CoreNode)
|
||||||
coretlv.session.save_xml(file_path)
|
coretlv.session.save_xml(file_path)
|
||||||
coretlv.session.delete_node(node.id)
|
coretlv.session.delete_node(node.id)
|
||||||
message = coreapi.CoreEventMessage.create(
|
message = coreapi.CoreEventMessage.create(
|
||||||
0,
|
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)
|
coretlv.handle_message(message)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from mock import MagicMock
|
from mock import MagicMock
|
||||||
|
@ -9,8 +9,8 @@ from core.errors import CoreCommandError
|
||||||
from core.nodes.base import CoreNode
|
from core.nodes.base import CoreNode
|
||||||
from core.services.coreservices import CoreService, ServiceDependencies, ServiceManager
|
from core.services.coreservices import CoreService, ServiceDependencies, ServiceManager
|
||||||
|
|
||||||
_PATH = os.path.abspath(os.path.dirname(__file__))
|
_PATH: Path = Path(__file__).resolve().parent
|
||||||
_SERVICES_PATH = os.path.join(_PATH, "myservices")
|
_SERVICES_PATH = _PATH / "myservices"
|
||||||
|
|
||||||
SERVICE_ONE = "MyService"
|
SERVICE_ONE = "MyService"
|
||||||
SERVICE_TWO = "MyService2"
|
SERVICE_TWO = "MyService2"
|
||||||
|
@ -64,15 +64,15 @@ class TestServices:
|
||||||
ServiceManager.add_services(_SERVICES_PATH)
|
ServiceManager.add_services(_SERVICES_PATH)
|
||||||
my_service = ServiceManager.get(SERVICE_ONE)
|
my_service = ServiceManager.get(SERVICE_ONE)
|
||||||
node = session.add_node(CoreNode)
|
node = session.add_node(CoreNode)
|
||||||
file_name = my_service.configs[0]
|
file_path = Path(my_service.configs[0])
|
||||||
file_path = node.hostfilename(file_name)
|
file_path = node.host_path(file_path)
|
||||||
|
|
||||||
# when
|
# when
|
||||||
session.services.create_service_files(node, my_service)
|
session.services.create_service_files(node, my_service)
|
||||||
|
|
||||||
# then
|
# then
|
||||||
if not request.config.getoption("mock"):
|
if not request.config.getoption("mock"):
|
||||||
assert os.path.exists(file_path)
|
assert file_path.exists()
|
||||||
|
|
||||||
def test_service_validate(self, session: Session):
|
def test_service_validate(self, session: Session):
|
||||||
# given
|
# given
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from pathlib import Path
|
||||||
from tempfile import TemporaryFile
|
from tempfile import TemporaryFile
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ class TestXml:
|
||||||
|
|
||||||
# save xml
|
# save xml
|
||||||
xml_file = tmpdir.join("session.xml")
|
xml_file = tmpdir.join("session.xml")
|
||||||
file_path = xml_file.strpath
|
file_path = Path(xml_file.strpath)
|
||||||
session.save_xml(file_path)
|
session.save_xml(file_path)
|
||||||
|
|
||||||
# verify xml file was created and can be parsed
|
# verify xml file was created and can be parsed
|
||||||
|
@ -85,7 +86,7 @@ class TestXml:
|
||||||
|
|
||||||
# save xml
|
# save xml
|
||||||
xml_file = tmpdir.join("session.xml")
|
xml_file = tmpdir.join("session.xml")
|
||||||
file_path = xml_file.strpath
|
file_path = Path(xml_file.strpath)
|
||||||
session.save_xml(file_path)
|
session.save_xml(file_path)
|
||||||
|
|
||||||
# verify xml file was created and can be parsed
|
# verify xml file was created and can be parsed
|
||||||
|
@ -148,7 +149,7 @@ class TestXml:
|
||||||
|
|
||||||
# save xml
|
# save xml
|
||||||
xml_file = tmpdir.join("session.xml")
|
xml_file = tmpdir.join("session.xml")
|
||||||
file_path = xml_file.strpath
|
file_path = Path(xml_file.strpath)
|
||||||
session.save_xml(file_path)
|
session.save_xml(file_path)
|
||||||
|
|
||||||
# verify xml file was created and can be parsed
|
# verify xml file was created and can be parsed
|
||||||
|
@ -210,7 +211,7 @@ class TestXml:
|
||||||
|
|
||||||
# save xml
|
# save xml
|
||||||
xml_file = tmpdir.join("session.xml")
|
xml_file = tmpdir.join("session.xml")
|
||||||
file_path = xml_file.strpath
|
file_path = Path(xml_file.strpath)
|
||||||
session.save_xml(file_path)
|
session.save_xml(file_path)
|
||||||
|
|
||||||
# verify xml file was created and can be parsed
|
# verify xml file was created and can be parsed
|
||||||
|
@ -261,7 +262,7 @@ class TestXml:
|
||||||
|
|
||||||
# save xml
|
# save xml
|
||||||
xml_file = tmpdir.join("session.xml")
|
xml_file = tmpdir.join("session.xml")
|
||||||
file_path = xml_file.strpath
|
file_path = Path(xml_file.strpath)
|
||||||
session.save_xml(file_path)
|
session.save_xml(file_path)
|
||||||
|
|
||||||
# verify xml file was created and can be parsed
|
# verify xml file was created and can be parsed
|
||||||
|
@ -321,7 +322,7 @@ class TestXml:
|
||||||
|
|
||||||
# save xml
|
# save xml
|
||||||
xml_file = tmpdir.join("session.xml")
|
xml_file = tmpdir.join("session.xml")
|
||||||
file_path = xml_file.strpath
|
file_path = Path(xml_file.strpath)
|
||||||
session.save_xml(file_path)
|
session.save_xml(file_path)
|
||||||
|
|
||||||
# verify xml file was created and can be parsed
|
# verify xml file was created and can be parsed
|
||||||
|
@ -390,7 +391,7 @@ class TestXml:
|
||||||
|
|
||||||
# save xml
|
# save xml
|
||||||
xml_file = tmpdir.join("session.xml")
|
xml_file = tmpdir.join("session.xml")
|
||||||
file_path = xml_file.strpath
|
file_path = Path(xml_file.strpath)
|
||||||
session.save_xml(file_path)
|
session.save_xml(file_path)
|
||||||
|
|
||||||
# verify xml file was created and can be parsed
|
# verify xml file was created and can be parsed
|
||||||
|
@ -471,7 +472,7 @@ class TestXml:
|
||||||
|
|
||||||
# save xml
|
# save xml
|
||||||
xml_file = tmpdir.join("session.xml")
|
xml_file = tmpdir.join("session.xml")
|
||||||
file_path = xml_file.strpath
|
file_path = Path(xml_file.strpath)
|
||||||
session.save_xml(file_path)
|
session.save_xml(file_path)
|
||||||
|
|
||||||
# verify xml file was created and can be parsed
|
# verify xml file was created and can be parsed
|
||||||
|
|
Loading…
Reference in a new issue