daemon: Session cleanup, removed unused functions, used context managers for writing files, made variables used externally no longer private
This commit is contained in:
parent
178d12b327
commit
23d957679e
11 changed files with 99 additions and 220 deletions
|
@ -930,8 +930,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer):
|
||||||
logging.debug("get hooks: %s", request)
|
logging.debug("get hooks: %s", request)
|
||||||
session = self.get_session(request.session_id, context)
|
session = self.get_session(request.session_id, context)
|
||||||
hooks = []
|
hooks = []
|
||||||
for state in session._hooks:
|
for state in session.hooks:
|
||||||
state_hooks = session._hooks[state]
|
state_hooks = session.hooks[state]
|
||||||
for file_name, file_data in state_hooks:
|
for file_name, file_data in state_hooks:
|
||||||
hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data)
|
hook = core_pb2.Hook(state=state.value, file=file_name, data=file_data)
|
||||||
hooks.append(hook)
|
hooks.append(hook)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import threading
|
||||||
import time
|
import time
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
from queue import Empty, Queue
|
from queue import Empty, Queue
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from core import utils
|
from core import utils
|
||||||
from core.api.tlv import coreapi, dataconversion, structutils
|
from core.api.tlv import coreapi, dataconversion, structutils
|
||||||
|
@ -39,6 +40,7 @@ from core.emulator.enumerations import (
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
RegisterTlvs,
|
RegisterTlvs,
|
||||||
)
|
)
|
||||||
|
from core.emulator.session import Session
|
||||||
from core.errors import CoreCommandError, CoreError
|
from core.errors import CoreCommandError, CoreError
|
||||||
from core.location.mobility import BasicRangeModel
|
from core.location.mobility import BasicRangeModel
|
||||||
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
|
from core.nodes.base import CoreNode, CoreNodeBase, NodeBase
|
||||||
|
@ -83,7 +85,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
thread.start()
|
thread.start()
|
||||||
self.handler_threads.append(thread)
|
self.handler_threads.append(thread)
|
||||||
|
|
||||||
self.session = None
|
self.session: Optional[Session] = None
|
||||||
self.coreemu = server.coreemu
|
self.coreemu = server.coreemu
|
||||||
utils.close_onexec(request.fileno())
|
utils.close_onexec(request.fileno())
|
||||||
socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
|
socketserver.BaseRequestHandler.__init__(self, request, client_address, server)
|
||||||
|
@ -176,7 +178,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
|
|
||||||
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 = session.thumbnail
|
thumb = session.thumbnail
|
||||||
if not thumb:
|
if not thumb:
|
||||||
|
@ -1819,7 +1821,7 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
"""
|
"""
|
||||||
# find all nodes and links
|
# find all nodes and links
|
||||||
links_data = []
|
links_data = []
|
||||||
with self.session._nodes_lock:
|
with self.session.nodes_lock:
|
||||||
for node_id in self.session.nodes:
|
for node_id in self.session.nodes:
|
||||||
node = self.session.nodes[node_id]
|
node = self.session.nodes[node_id]
|
||||||
self.session.broadcast_node(node, MessageFlags.ADD)
|
self.session.broadcast_node(node, MessageFlags.ADD)
|
||||||
|
@ -1897,8 +1899,8 @@ class CoreHandler(socketserver.BaseRequestHandler):
|
||||||
# TODO: send location info
|
# TODO: send location info
|
||||||
|
|
||||||
# send hook scripts
|
# send hook scripts
|
||||||
for state in sorted(self.session._hooks.keys()):
|
for state in sorted(self.session.hooks.keys()):
|
||||||
for file_name, config_data in self.session._hooks[state]:
|
for file_name, config_data in self.session.hooks[state]:
|
||||||
file_data = FileData(
|
file_data = FileData(
|
||||||
message_type=MessageFlags.ADD,
|
message_type=MessageFlags.ADD,
|
||||||
name=str(file_name),
|
name=str(file_name),
|
||||||
|
|
|
@ -279,7 +279,7 @@ class EmaneManager(ModelManager):
|
||||||
logging.debug("emane setup")
|
logging.debug("emane setup")
|
||||||
|
|
||||||
# TODO: drive this from the session object
|
# TODO: drive this from the session object
|
||||||
with self.session._nodes_lock:
|
with self.session.nodes_lock:
|
||||||
for node_id in self.session.nodes:
|
for node_id in self.session.nodes:
|
||||||
node = self.session.nodes[node_id]
|
node = self.session.nodes[node_id]
|
||||||
if isinstance(node, EmaneNet):
|
if isinstance(node, EmaneNet):
|
||||||
|
|
|
@ -6,7 +6,6 @@ that manages a CORE session.
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pwd
|
import pwd
|
||||||
import random
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
|
@ -113,15 +112,13 @@ class Session:
|
||||||
|
|
||||||
# dict of nodes: all nodes and nets
|
# dict of nodes: all nodes and nets
|
||||||
self.nodes: Dict[int, NodeBase] = {}
|
self.nodes: Dict[int, NodeBase] = {}
|
||||||
self._nodes_lock = threading.Lock()
|
self.nodes_lock = threading.Lock()
|
||||||
|
|
||||||
|
# states and hooks handlers
|
||||||
self.state: EventTypes = EventTypes.DEFINITION_STATE
|
self.state: EventTypes = EventTypes.DEFINITION_STATE
|
||||||
self._state_time: float = time.monotonic()
|
self.state_time: float = time.monotonic()
|
||||||
self._state_file: str = os.path.join(self.session_dir, "state")
|
self.hooks: Dict[EventTypes, Tuple[str, str]] = {}
|
||||||
|
self.state_hooks: Dict[EventTypes, List[Callable[[EventTypes], None]]] = {}
|
||||||
# hooks handlers
|
|
||||||
self._hooks: Dict[EventTypes, Tuple[str, str]] = {}
|
|
||||||
self._state_hooks: Dict[EventTypes, Callable[[int], None]] = {}
|
|
||||||
self.add_state_hook(
|
self.add_state_hook(
|
||||||
state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook
|
state=EventTypes.RUNTIME_STATE, hook=self.runtime_state_hook
|
||||||
)
|
)
|
||||||
|
@ -154,15 +151,6 @@ class Session:
|
||||||
self.emane: EmaneManager = EmaneManager(self)
|
self.emane: EmaneManager = EmaneManager(self)
|
||||||
self.sdt: Sdt = Sdt(self)
|
self.sdt: Sdt = Sdt(self)
|
||||||
|
|
||||||
# initialize default node services
|
|
||||||
self.services.default_services = {
|
|
||||||
"mdr": ("zebra", "OSPFv3MDR", "IPForward"),
|
|
||||||
"PC": ("DefaultRoute",),
|
|
||||||
"prouter": (),
|
|
||||||
"router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"),
|
|
||||||
"host": ("DefaultRoute", "SSH"),
|
|
||||||
}
|
|
||||||
|
|
||||||
# config services
|
# config services
|
||||||
self.service_manager: Optional[ConfigServiceManager] = None
|
self.service_manager: Optional[ConfigServiceManager] = None
|
||||||
|
|
||||||
|
@ -473,7 +461,7 @@ class Session:
|
||||||
f"cannot update link node1({type(node1)}) node2({type(node2)})"
|
f"cannot update link node1({type(node1)}) node2({type(node2)})"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _next_node_id(self) -> int:
|
def next_node_id(self) -> int:
|
||||||
"""
|
"""
|
||||||
Find the next valid node id, starting from 1.
|
Find the next valid node id, starting from 1.
|
||||||
|
|
||||||
|
@ -506,7 +494,7 @@ class Session:
|
||||||
|
|
||||||
# determine node id
|
# determine node id
|
||||||
if not _id:
|
if not _id:
|
||||||
_id = self._next_node_id()
|
_id = self.next_node_id()
|
||||||
|
|
||||||
# generate name if not provided
|
# generate name if not provided
|
||||||
if not options:
|
if not options:
|
||||||
|
@ -692,7 +680,7 @@ class Session:
|
||||||
"setting state hook: %s - %s source(%s)", state, file_name, source_name
|
"setting state hook: %s - %s source(%s)", state, file_name, source_name
|
||||||
)
|
)
|
||||||
hook = file_name, data
|
hook = file_name, data
|
||||||
state_hooks = self._hooks.setdefault(state, [])
|
state_hooks = self.hooks.setdefault(state, [])
|
||||||
state_hooks.append(hook)
|
state_hooks.append(hook)
|
||||||
|
|
||||||
# immediately run a hook if it is in the current state
|
# immediately run a hook if it is in the current state
|
||||||
|
@ -727,7 +715,7 @@ class Session:
|
||||||
self.emane.shutdown()
|
self.emane.shutdown()
|
||||||
self.delete_nodes()
|
self.delete_nodes()
|
||||||
self.distributed.shutdown()
|
self.distributed.shutdown()
|
||||||
self.del_hooks()
|
self.hooks.clear()
|
||||||
self.emane.reset()
|
self.emane.reset()
|
||||||
self.emane.config_reset()
|
self.emane.config_reset()
|
||||||
self.location.reset()
|
self.location.reset()
|
||||||
|
@ -795,7 +783,6 @@ class Session:
|
||||||
:param event_data: event data to send out
|
:param event_data: event data to send out
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for handler in self.event_handlers:
|
for handler in self.event_handlers:
|
||||||
handler(event_data)
|
handler(event_data)
|
||||||
|
|
||||||
|
@ -806,7 +793,6 @@ class Session:
|
||||||
:param exception_data: exception data to send out
|
:param exception_data: exception data to send out
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for handler in self.exception_handlers:
|
for handler in self.exception_handlers:
|
||||||
handler(exception_data)
|
handler(exception_data)
|
||||||
|
|
||||||
|
@ -837,7 +823,6 @@ class Session:
|
||||||
:param file_data: file data to send out
|
:param file_data: file data to send out
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for handler in self.file_handlers:
|
for handler in self.file_handlers:
|
||||||
handler(file_data)
|
handler(file_data)
|
||||||
|
|
||||||
|
@ -848,7 +833,6 @@ class Session:
|
||||||
:param config_data: config data to send out
|
:param config_data: config data to send out
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for handler in self.config_handlers:
|
for handler in self.config_handlers:
|
||||||
handler(config_data)
|
handler(config_data)
|
||||||
|
|
||||||
|
@ -859,7 +843,6 @@ class Session:
|
||||||
:param link_data: link data to send out
|
:param link_data: link data to send out
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for handler in self.link_handlers:
|
for handler in self.link_handlers:
|
||||||
handler(link_data)
|
handler(link_data)
|
||||||
|
|
||||||
|
@ -871,22 +854,14 @@ class Session:
|
||||||
:param send_event: if true, generate core API event messages
|
:param send_event: if true, generate core API event messages
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
state_name = state.name
|
|
||||||
if self.state == state:
|
if self.state == state:
|
||||||
logging.info(
|
|
||||||
"session(%s) is already in state: %s, skipping change",
|
|
||||||
self.id,
|
|
||||||
state_name,
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
self.state = state
|
self.state = state
|
||||||
self._state_time = time.monotonic()
|
self.state_time = time.monotonic()
|
||||||
logging.info("changing session(%s) to state %s", self.id, state_name)
|
logging.info("changing session(%s) to state %s", self.id, state.name)
|
||||||
self.write_state(state)
|
self.write_state(state)
|
||||||
self.run_hooks(state)
|
self.run_hooks(state)
|
||||||
self.run_state_hooks(state)
|
self.run_state_hooks(state)
|
||||||
|
|
||||||
if send_event:
|
if send_event:
|
||||||
event_data = EventData(event_type=state, time=str(time.monotonic()))
|
event_data = EventData(event_type=state, time=str(time.monotonic()))
|
||||||
self.broadcast_event(event_data)
|
self.broadcast_event(event_data)
|
||||||
|
@ -898,10 +873,10 @@ 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")
|
||||||
try:
|
try:
|
||||||
state_file = open(self._state_file, "w")
|
with open(state_file, "w") as f:
|
||||||
state_file.write(f"{state.value} {state.name}\n")
|
f.write(f"{state.value} {state.name}\n")
|
||||||
state_file.close()
|
|
||||||
except IOError:
|
except IOError:
|
||||||
logging.exception("error writing state file: %s", state.name)
|
logging.exception("error writing state file: %s", state.name)
|
||||||
|
|
||||||
|
@ -913,61 +888,10 @@ class Session:
|
||||||
:param state: state to run hooks for
|
:param state: state to run hooks for
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
hooks = self.hooks.get(state, [])
|
||||||
# check that state change hooks exist
|
for hook in hooks:
|
||||||
if state not in self._hooks:
|
|
||||||
return
|
|
||||||
|
|
||||||
# retrieve all state hooks
|
|
||||||
hooks = self._hooks.get(state, [])
|
|
||||||
|
|
||||||
# execute all state hooks
|
|
||||||
if hooks:
|
|
||||||
for hook in hooks:
|
|
||||||
self.run_hook(hook)
|
|
||||||
else:
|
|
||||||
logging.info("no state hooks for %s", state)
|
|
||||||
|
|
||||||
def set_hook(
|
|
||||||
self, hook_type: str, file_name: str, source_name: str, data: str
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Store a hook from a received file message.
|
|
||||||
|
|
||||||
:param hook_type: hook type
|
|
||||||
:param file_name: file name for hook
|
|
||||||
:param source_name: source name
|
|
||||||
:param data: hook data
|
|
||||||
:return: nothing
|
|
||||||
"""
|
|
||||||
logging.info(
|
|
||||||
"setting state hook: %s - %s from %s", hook_type, file_name, source_name
|
|
||||||
)
|
|
||||||
|
|
||||||
_hook_id, state = hook_type.split(":")[:2]
|
|
||||||
if not state.isdigit():
|
|
||||||
logging.error("error setting hook having state '%s'", state)
|
|
||||||
return
|
|
||||||
|
|
||||||
state = int(state)
|
|
||||||
hook = file_name, data
|
|
||||||
|
|
||||||
# append hook to current state hooks
|
|
||||||
state_hooks = self._hooks.setdefault(state, [])
|
|
||||||
state_hooks.append(hook)
|
|
||||||
|
|
||||||
# immediately run a hook if it is in the current state
|
|
||||||
# (this allows hooks in the definition and configuration states)
|
|
||||||
if self.state == state:
|
|
||||||
logging.info("immediately running new state hook")
|
|
||||||
self.run_hook(hook)
|
self.run_hook(hook)
|
||||||
|
|
||||||
def del_hooks(self) -> None:
|
|
||||||
"""
|
|
||||||
Clear the hook scripts dict.
|
|
||||||
"""
|
|
||||||
self._hooks.clear()
|
|
||||||
|
|
||||||
def run_hook(self, hook: Tuple[str, str]) -> None:
|
def run_hook(self, hook: Tuple[str, str]) -> None:
|
||||||
"""
|
"""
|
||||||
Run a hook.
|
Run a hook.
|
||||||
|
@ -977,37 +901,23 @@ 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)
|
||||||
# write data to hook file
|
log_path = os.path.join(self.session_dir, f"{file_name}.log")
|
||||||
try:
|
try:
|
||||||
hook_file = open(os.path.join(self.session_dir, file_name), "w")
|
with open(file_path, "w") as f:
|
||||||
hook_file.write(data)
|
f.write(data)
|
||||||
hook_file.close()
|
with open(log_path, "w") as f:
|
||||||
except IOError:
|
args = ["/bin/sh", file_name]
|
||||||
logging.exception("error writing hook '%s'", file_name)
|
subprocess.check_call(
|
||||||
|
args,
|
||||||
# setup hook stdout and stderr
|
stdout=f,
|
||||||
try:
|
stderr=subprocess.STDOUT,
|
||||||
stdout = open(os.path.join(self.session_dir, file_name + ".log"), "w")
|
close_fds=True,
|
||||||
stderr = subprocess.STDOUT
|
cwd=self.session_dir,
|
||||||
except IOError:
|
env=self.get_environment(),
|
||||||
logging.exception("error setting up hook stderr and stdout")
|
)
|
||||||
stdout = None
|
except (IOError, subprocess.CalledProcessError):
|
||||||
stderr = None
|
logging.exception("error running hook: %s", file_path)
|
||||||
|
|
||||||
# execute hook file
|
|
||||||
try:
|
|
||||||
args = ["/bin/sh", file_name]
|
|
||||||
subprocess.check_call(
|
|
||||||
args,
|
|
||||||
stdout=stdout,
|
|
||||||
stderr=stderr,
|
|
||||||
close_fds=True,
|
|
||||||
cwd=self.session_dir,
|
|
||||||
env=self.get_environment(),
|
|
||||||
)
|
|
||||||
except (OSError, subprocess.CalledProcessError):
|
|
||||||
logging.exception("error running hook: %s", file_name)
|
|
||||||
|
|
||||||
def run_state_hooks(self, state: EventTypes) -> None:
|
def run_state_hooks(self, state: EventTypes) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -1016,17 +926,16 @@ class Session:
|
||||||
:param state: state to run hooks for
|
:param state: state to run hooks for
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
for hook in self._state_hooks.get(state, []):
|
for hook in self.state_hooks.get(state, []):
|
||||||
try:
|
self.run_state_hook(state, hook)
|
||||||
hook(state)
|
|
||||||
except Exception:
|
def run_state_hook(self, state: EventTypes, hook: Callable[[EventTypes], None]):
|
||||||
message = (
|
try:
|
||||||
f"exception occured when running {state.name} state hook: {hook}"
|
hook(state)
|
||||||
)
|
except Exception:
|
||||||
logging.exception(message)
|
message = f"exception occurred when running {state.name} state hook: {hook}"
|
||||||
self.exception(
|
logging.exception(message)
|
||||||
ExceptionLevels.ERROR, "Session.run_state_hooks", message
|
self.exception(ExceptionLevels.ERROR, "Session.run_state_hooks", message)
|
||||||
)
|
|
||||||
|
|
||||||
def add_state_hook(
|
def add_state_hook(
|
||||||
self, state: EventTypes, hook: Callable[[EventTypes], None]
|
self, state: EventTypes, hook: Callable[[EventTypes], None]
|
||||||
|
@ -1038,15 +947,16 @@ class Session:
|
||||||
:param hook: hook callback for the state
|
:param hook: hook callback for the state
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
hooks = self._state_hooks.setdefault(state, [])
|
hooks = self.state_hooks.setdefault(state, [])
|
||||||
if hook in hooks:
|
if hook in hooks:
|
||||||
raise CoreError("attempting to add duplicate state hook")
|
raise CoreError("attempting to add duplicate state hook")
|
||||||
hooks.append(hook)
|
hooks.append(hook)
|
||||||
|
|
||||||
if self.state == state:
|
if self.state == state:
|
||||||
hook(state)
|
self.run_state_hook(state, hook)
|
||||||
|
|
||||||
def del_state_hook(self, state: int, hook: Callable[[int], None]) -> None:
|
def del_state_hook(
|
||||||
|
self, state: EventTypes, hook: Callable[[EventTypes], None]
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a state hook.
|
Delete a state hook.
|
||||||
|
|
||||||
|
@ -1054,24 +964,23 @@ class Session:
|
||||||
:param hook: hook to delete
|
:param hook: hook to delete
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
hooks = self._state_hooks.setdefault(state, [])
|
hooks = self.state_hooks.get(state, [])
|
||||||
hooks.remove(hook)
|
if hook in hooks:
|
||||||
|
hooks.remove(hook)
|
||||||
|
|
||||||
def runtime_state_hook(self, state: EventTypes) -> None:
|
def runtime_state_hook(self, _state: EventTypes) -> None:
|
||||||
"""
|
"""
|
||||||
Runtime state hook check.
|
Runtime state hook check.
|
||||||
|
|
||||||
:param state: state to check
|
:param _state: state to check
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
if state == EventTypes.RUNTIME_STATE:
|
self.emane.poststartup()
|
||||||
self.emane.poststartup()
|
# create session deployed xml
|
||||||
|
xml_file_name = os.path.join(self.session_dir, "session-deployed.xml")
|
||||||
# create session deployed xml
|
xml_writer = corexml.CoreXmlWriter(self)
|
||||||
xml_file_name = os.path.join(self.session_dir, "session-deployed.xml")
|
corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario)
|
||||||
xml_writer = corexml.CoreXmlWriter(self)
|
xml_writer.write(xml_file_name)
|
||||||
corexmldeployment.CoreXmlDeployment(self, xml_writer.scenario)
|
|
||||||
xml_writer.write(xml_file_name)
|
|
||||||
|
|
||||||
def get_environment(self, state: bool = True) -> Dict[str, str]:
|
def get_environment(self, state: bool = True) -> Dict[str, str]:
|
||||||
"""
|
"""
|
||||||
|
@ -1090,10 +999,8 @@ class Session:
|
||||||
env["SESSION_FILENAME"] = str(self.file_name)
|
env["SESSION_FILENAME"] = str(self.file_name)
|
||||||
env["SESSION_USER"] = str(self.user)
|
env["SESSION_USER"] = str(self.user)
|
||||||
env["SESSION_NODE_COUNT"] = str(self.get_node_count())
|
env["SESSION_NODE_COUNT"] = str(self.get_node_count())
|
||||||
|
|
||||||
if state:
|
if state:
|
||||||
env["SESSION_STATE"] = str(self.state)
|
env["SESSION_STATE"] = str(self.state)
|
||||||
|
|
||||||
# attempt to read and add environment config file
|
# attempt to read and add environment config file
|
||||||
environment_config_file = os.path.join(constants.CORE_CONF_DIR, "environment")
|
environment_config_file = os.path.join(constants.CORE_CONF_DIR, "environment")
|
||||||
try:
|
try:
|
||||||
|
@ -1104,7 +1011,6 @@ class Session:
|
||||||
"environment configuration file does not exist: %s",
|
"environment configuration file does not exist: %s",
|
||||||
environment_config_file,
|
environment_config_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
# attempt to read and add user environment file
|
# attempt to read and add user environment file
|
||||||
if self.user:
|
if self.user:
|
||||||
environment_user_file = os.path.join(
|
environment_user_file = os.path.join(
|
||||||
|
@ -1117,7 +1023,6 @@ class Session:
|
||||||
"user core environment settings file not present: %s",
|
"user core environment settings file not present: %s",
|
||||||
environment_user_file,
|
environment_user_file,
|
||||||
)
|
)
|
||||||
|
|
||||||
return env
|
return env
|
||||||
|
|
||||||
def set_thumbnail(self, thumb_file: str) -> None:
|
def set_thumbnail(self, thumb_file: str) -> None:
|
||||||
|
@ -1131,7 +1036,6 @@ class Session:
|
||||||
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))
|
destination_file = os.path.join(self.session_dir, os.path.basename(thumb_file))
|
||||||
shutil.copy(thumb_file, destination_file)
|
shutil.copy(thumb_file, destination_file)
|
||||||
self.thumbnail = destination_file
|
self.thumbnail = destination_file
|
||||||
|
@ -1151,20 +1055,8 @@ class Session:
|
||||||
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)
|
||||||
|
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
||||||
def get_node_id(self) -> int:
|
|
||||||
"""
|
|
||||||
Return a unique, new node id.
|
|
||||||
"""
|
|
||||||
with self._nodes_lock:
|
|
||||||
while True:
|
|
||||||
node_id = random.randint(1, 0xFFFF)
|
|
||||||
if node_id not in self.nodes:
|
|
||||||
break
|
|
||||||
return node_id
|
|
||||||
|
|
||||||
def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT:
|
def create_node(self, _class: Type[NT], *args: Any, **kwargs: Any) -> NT:
|
||||||
"""
|
"""
|
||||||
Create an emulation node.
|
Create an emulation node.
|
||||||
|
@ -1176,7 +1068,7 @@ class Session:
|
||||||
:raises core.CoreError: when id of the node to create already exists
|
:raises core.CoreError: when id of the node to create already exists
|
||||||
"""
|
"""
|
||||||
node = _class(self, *args, **kwargs)
|
node = _class(self, *args, **kwargs)
|
||||||
with self._nodes_lock:
|
with self.nodes_lock:
|
||||||
if node.id in self.nodes:
|
if node.id in self.nodes:
|
||||||
node.shutdown()
|
node.shutdown()
|
||||||
raise CoreError(f"duplicate node id {node.id} for {node.name}")
|
raise CoreError(f"duplicate node id {node.id} for {node.name}")
|
||||||
|
@ -1192,9 +1084,9 @@ class Session:
|
||||||
:return: node for the given id
|
:return: node for the given id
|
||||||
:raises core.CoreError: when node does not exist
|
:raises core.CoreError: when node does not exist
|
||||||
"""
|
"""
|
||||||
if _id not in self.nodes:
|
node = self.nodes.get(_id)
|
||||||
|
if node is None:
|
||||||
raise CoreError(f"unknown node id {_id}")
|
raise CoreError(f"unknown node id {_id}")
|
||||||
node = self.nodes[_id]
|
|
||||||
if not isinstance(node, _class):
|
if not isinstance(node, _class):
|
||||||
actual = node.__class__.__name__
|
actual = node.__class__.__name__
|
||||||
expected = _class.__name__
|
expected = _class.__name__
|
||||||
|
@ -1210,7 +1102,7 @@ class Session:
|
||||||
"""
|
"""
|
||||||
# delete node and check for session shutdown if a node was removed
|
# delete node and check for session shutdown if a node was removed
|
||||||
node = None
|
node = None
|
||||||
with self._nodes_lock:
|
with self.nodes_lock:
|
||||||
if _id in self.nodes:
|
if _id in self.nodes:
|
||||||
node = self.nodes.pop(_id)
|
node = self.nodes.pop(_id)
|
||||||
logging.info("deleted node(%s)", node.name)
|
logging.info("deleted node(%s)", node.name)
|
||||||
|
@ -1224,7 +1116,7 @@ class Session:
|
||||||
"""
|
"""
|
||||||
Clear the nodes dictionary, and call shutdown for each node.
|
Clear the nodes dictionary, and call shutdown for each node.
|
||||||
"""
|
"""
|
||||||
with self._nodes_lock:
|
with self.nodes_lock:
|
||||||
funcs = []
|
funcs = []
|
||||||
while self.nodes:
|
while self.nodes:
|
||||||
_, node = self.nodes.popitem()
|
_, node = self.nodes.popitem()
|
||||||
|
@ -1237,29 +1129,15 @@ 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")
|
||||||
try:
|
try:
|
||||||
with self._nodes_lock:
|
with self.nodes_lock:
|
||||||
file_path = os.path.join(self.session_dir, "nodes")
|
|
||||||
with open(file_path, "w") as f:
|
with open(file_path, "w") as f:
|
||||||
for _id in self.nodes.keys():
|
for _id, node in self.nodes.items():
|
||||||
node = self.nodes[_id]
|
|
||||||
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:
|
||||||
logging.exception("error writing nodes file")
|
logging.exception("error writing nodes file")
|
||||||
|
|
||||||
def dump_session(self) -> None:
|
|
||||||
"""
|
|
||||||
Log information about the session in its current state.
|
|
||||||
"""
|
|
||||||
logging.info("session id=%s name=%s state=%s", self.id, self.name, self.state)
|
|
||||||
logging.info(
|
|
||||||
"file=%s thumbnail=%s node_count=%s/%s",
|
|
||||||
self.file_name,
|
|
||||||
self.thumbnail,
|
|
||||||
self.get_node_count(),
|
|
||||||
len(self.nodes),
|
|
||||||
)
|
|
||||||
|
|
||||||
def exception(
|
def exception(
|
||||||
self, level: ExceptionLevels, source: str, text: str, node_id: int = None
|
self, level: ExceptionLevels, source: str, text: str, node_id: int = None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -1327,17 +1205,15 @@ class Session:
|
||||||
|
|
||||||
:return: created node count
|
:return: created node count
|
||||||
"""
|
"""
|
||||||
with self._nodes_lock:
|
with self.nodes_lock:
|
||||||
count = 0
|
count = 0
|
||||||
for node_id in self.nodes:
|
for node in self.nodes.values():
|
||||||
node = self.nodes[node_id]
|
|
||||||
is_p2p_ctrlnet = isinstance(node, (PtpNet, CtrlNet))
|
is_p2p_ctrlnet = isinstance(node, (PtpNet, CtrlNet))
|
||||||
is_tap = isinstance(node, GreTapBridge) and not isinstance(
|
is_tap = isinstance(node, GreTapBridge) and not isinstance(
|
||||||
node, TunnelNode
|
node, TunnelNode
|
||||||
)
|
)
|
||||||
if is_p2p_ctrlnet or is_tap:
|
if is_p2p_ctrlnet or is_tap:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
@ -1359,7 +1235,6 @@ class Session:
|
||||||
if self.state == EventTypes.RUNTIME_STATE:
|
if self.state == EventTypes.RUNTIME_STATE:
|
||||||
logging.info("valid runtime state found, returning")
|
logging.info("valid runtime state found, returning")
|
||||||
return
|
return
|
||||||
|
|
||||||
# start event loop and set to runtime
|
# start event loop and set to runtime
|
||||||
self.event_loop.run()
|
self.event_loop.run()
|
||||||
self.set_state(EventTypes.RUNTIME_STATE, send_event=True)
|
self.set_state(EventTypes.RUNTIME_STATE, send_event=True)
|
||||||
|
@ -1375,7 +1250,7 @@ class Session:
|
||||||
self.event_loop.stop()
|
self.event_loop.stop()
|
||||||
|
|
||||||
# stop node services
|
# stop node services
|
||||||
with self._nodes_lock:
|
with self.nodes_lock:
|
||||||
funcs = []
|
funcs = []
|
||||||
for node_id in self.nodes:
|
for node_id in self.nodes:
|
||||||
node = self.nodes[node_id]
|
node = self.nodes[node_id]
|
||||||
|
@ -1447,7 +1322,7 @@ class Session:
|
||||||
|
|
||||||
:return: service boot exceptions
|
:return: service boot exceptions
|
||||||
"""
|
"""
|
||||||
with self._nodes_lock:
|
with self.nodes_lock:
|
||||||
funcs = []
|
funcs = []
|
||||||
start = time.monotonic()
|
start = time.monotonic()
|
||||||
for _id in self.nodes:
|
for _id in self.nodes:
|
||||||
|
@ -1545,7 +1420,6 @@ class Session:
|
||||||
else:
|
else:
|
||||||
prefix_spec = CtrlNet.DEFAULT_PREFIX_LIST[net_index]
|
prefix_spec = CtrlNet.DEFAULT_PREFIX_LIST[net_index]
|
||||||
logging.debug("prefix spec: %s", prefix_spec)
|
logging.debug("prefix spec: %s", prefix_spec)
|
||||||
|
|
||||||
server_interface = self.get_control_net_server_interfaces()[net_index]
|
server_interface = self.get_control_net_server_interfaces()[net_index]
|
||||||
|
|
||||||
# return any existing controlnet bridge
|
# return any existing controlnet bridge
|
||||||
|
@ -1685,7 +1559,7 @@ class Session:
|
||||||
if not in runtime.
|
if not in runtime.
|
||||||
"""
|
"""
|
||||||
if self.state == EventTypes.RUNTIME_STATE:
|
if self.state == EventTypes.RUNTIME_STATE:
|
||||||
return time.monotonic() - self._state_time
|
return time.monotonic() - self.state_time
|
||||||
else:
|
else:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
|
@ -1708,7 +1582,6 @@ class Session:
|
||||||
"""
|
"""
|
||||||
event_time = float(event_time)
|
event_time = float(event_time)
|
||||||
current_time = self.runtime()
|
current_time = self.runtime()
|
||||||
|
|
||||||
if current_time > 0:
|
if current_time > 0:
|
||||||
if event_time <= current_time:
|
if event_time <= current_time:
|
||||||
logging.warning(
|
logging.warning(
|
||||||
|
@ -1718,11 +1591,9 @@ class Session:
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
event_time = event_time - current_time
|
event_time = event_time - current_time
|
||||||
|
|
||||||
self.event_loop.add_event(
|
self.event_loop.add_event(
|
||||||
event_time, self.run_event, node=node, name=name, data=data
|
event_time, self.run_event, node=node, name=name, data=data
|
||||||
)
|
)
|
||||||
|
|
||||||
if not name:
|
if not name:
|
||||||
name = ""
|
name = ""
|
||||||
logging.info(
|
logging.info(
|
||||||
|
@ -1732,8 +1603,6 @@ class Session:
|
||||||
data,
|
data,
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: if data is None, this blows up, but this ties into how event functions
|
|
||||||
# are ran, need to clean that up
|
|
||||||
def run_event(
|
def run_event(
|
||||||
self, node_id: int = None, name: str = None, data: str = None
|
self, node_id: int = None, name: str = None, data: str = None
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -1745,10 +1614,12 @@ class Session:
|
||||||
:param data: event data
|
:param data: event data
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
if data is None:
|
||||||
|
logging.warning("no data for event node(%s) name(%s)", node_id, name)
|
||||||
|
return
|
||||||
now = self.runtime()
|
now = self.runtime()
|
||||||
if not name:
|
if not name:
|
||||||
name = ""
|
name = ""
|
||||||
|
|
||||||
logging.info("running event %s at time %s cmd=%s", name, now, data)
|
logging.info("running event %s at time %s cmd=%s", name, now, data)
|
||||||
if not node_id:
|
if not node_id:
|
||||||
utils.mute_detach(data)
|
utils.mute_detach(data)
|
||||||
|
|
|
@ -63,7 +63,7 @@ class NodeBase:
|
||||||
|
|
||||||
self.session: "Session" = session
|
self.session: "Session" = session
|
||||||
if _id is None:
|
if _id is None:
|
||||||
_id = session.get_node_id()
|
_id = session.next_node_id()
|
||||||
self.id: int = _id
|
self.id: int = _id
|
||||||
if name is None:
|
if name is None:
|
||||||
name = f"o{self.id}"
|
name = f"o{self.id}"
|
||||||
|
|
|
@ -215,7 +215,7 @@ class Sdt:
|
||||||
for layer in CORE_LAYERS:
|
for layer in CORE_LAYERS:
|
||||||
self.cmd(f"layer {layer}")
|
self.cmd(f"layer {layer}")
|
||||||
|
|
||||||
with self.session._nodes_lock:
|
with self.session.nodes_lock:
|
||||||
for node_id in self.session.nodes:
|
for node_id in self.session.nodes:
|
||||||
node = self.session.nodes[node_id]
|
node = self.session.nodes[node_id]
|
||||||
if isinstance(node, CoreNetworkBase):
|
if isinstance(node, CoreNetworkBase):
|
||||||
|
|
|
@ -325,7 +325,13 @@ class CoreServices:
|
||||||
"""
|
"""
|
||||||
self.session = session
|
self.session = session
|
||||||
# dict of default services tuples, key is node type
|
# dict of default services tuples, key is node type
|
||||||
self.default_services = {}
|
self.default_services = {
|
||||||
|
"mdr": ("zebra", "OSPFv3MDR", "IPForward"),
|
||||||
|
"PC": ("DefaultRoute",),
|
||||||
|
"prouter": (),
|
||||||
|
"router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"),
|
||||||
|
"host": ("DefaultRoute", "SSH"),
|
||||||
|
}
|
||||||
# dict of node ids to dict of custom services by name
|
# dict of node ids to dict of custom services by name
|
||||||
self.custom_services = {}
|
self.custom_services = {}
|
||||||
|
|
||||||
|
|
|
@ -320,8 +320,8 @@ class CoreXmlWriter:
|
||||||
def write_session_hooks(self) -> None:
|
def write_session_hooks(self) -> None:
|
||||||
# hook scripts
|
# hook scripts
|
||||||
hooks = etree.Element("session_hooks")
|
hooks = etree.Element("session_hooks")
|
||||||
for state in sorted(self.session._hooks, key=lambda x: x.value):
|
for state in sorted(self.session.hooks, key=lambda x: x.value):
|
||||||
for file_name, data in self.session._hooks[state]:
|
for file_name, data in self.session.hooks[state]:
|
||||||
hook = etree.SubElement(hooks, "hook")
|
hook = etree.SubElement(hooks, "hook")
|
||||||
add_attribute(hook, "name", file_name)
|
add_attribute(hook, "name", file_name)
|
||||||
add_attribute(hook, "state", state.value)
|
add_attribute(hook, "state", state.value)
|
||||||
|
|
|
@ -133,7 +133,7 @@ class TestGrpc:
|
||||||
assert wlan_node.id in session.nodes
|
assert wlan_node.id in session.nodes
|
||||||
assert session.nodes[node1.id].netif(0) is not None
|
assert session.nodes[node1.id].netif(0) is not None
|
||||||
assert session.nodes[node2.id].netif(0) is not None
|
assert session.nodes[node2.id].netif(0) is not None
|
||||||
hook_file, hook_data = session._hooks[EventTypes.RUNTIME_STATE][0]
|
hook_file, hook_data = session.hooks[EventTypes.RUNTIME_STATE][0]
|
||||||
assert hook_file == hook.file
|
assert hook_file == hook.file
|
||||||
assert hook_data == hook.data
|
assert hook_data == hook.data
|
||||||
assert session.location.refxyz == (location_x, location_y, location_z)
|
assert session.location.refxyz == (location_x, location_y, location_z)
|
||||||
|
|
|
@ -382,7 +382,7 @@ class TestGui:
|
||||||
|
|
||||||
def test_file_hook_add(self, coretlv: CoreHandler):
|
def test_file_hook_add(self, coretlv: CoreHandler):
|
||||||
state = EventTypes.DATACOLLECT_STATE
|
state = EventTypes.DATACOLLECT_STATE
|
||||||
assert coretlv.session._hooks.get(state) is None
|
assert coretlv.session.hooks.get(state) is None
|
||||||
file_name = "test.sh"
|
file_name = "test.sh"
|
||||||
file_data = "echo hello"
|
file_data = "echo hello"
|
||||||
message = coreapi.CoreFileMessage.create(
|
message = coreapi.CoreFileMessage.create(
|
||||||
|
@ -396,7 +396,7 @@ class TestGui:
|
||||||
|
|
||||||
coretlv.handle_message(message)
|
coretlv.handle_message(message)
|
||||||
|
|
||||||
hooks = coretlv.session._hooks.get(state)
|
hooks = coretlv.session.hooks.get(state)
|
||||||
assert len(hooks) == 1
|
assert len(hooks) == 1
|
||||||
name, data = hooks[0]
|
name, data = hooks[0]
|
||||||
assert file_name == name
|
assert file_name == name
|
||||||
|
|
|
@ -48,7 +48,7 @@ class TestXml:
|
||||||
session.open_xml(file_path, start=True)
|
session.open_xml(file_path, start=True)
|
||||||
|
|
||||||
# verify nodes have been recreated
|
# verify nodes have been recreated
|
||||||
runtime_hooks = session._hooks.get(state)
|
runtime_hooks = session.hooks.get(state)
|
||||||
assert runtime_hooks
|
assert runtime_hooks
|
||||||
runtime_hook = runtime_hooks[0]
|
runtime_hook = runtime_hooks[0]
|
||||||
assert file_name == runtime_hook[0]
|
assert file_name == runtime_hook[0]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue