diff --git a/configure.ac b/configure.ac index 5d04356e..43a5815f 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # this defines the CORE version number, must be static for AC_INIT -AC_INIT(core, 5.5.2) +AC_INIT(core, 6.0.0) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 7b4756b1..5c4ee25e 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -145,7 +145,7 @@ def handle_exception_event(event): """ return core_pb2.ExceptionEvent( node_id=event.node, - level=event.level, + level=event.level.value, source=event.source, date=event.date, text=event.text, diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 009cb067..79c2e1dc 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -299,7 +299,7 @@ class CoreHandler(socketserver.BaseRequestHandler): [ (ExceptionTlvs.NODE, exception_data.node), (ExceptionTlvs.SESSION, exception_data.session), - (ExceptionTlvs.LEVEL, exception_data.level), + (ExceptionTlvs.LEVEL, exception_data.level.value), (ExceptionTlvs.SOURCE, exception_data.source), (ExceptionTlvs.DATE, exception_data.date), (ExceptionTlvs.TEXT, exception_data.text), diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 1a01bdaa..bc2f750e 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1411,13 +1411,12 @@ class Session: """ Generate and broadcast an exception event. - :param str level: exception level + :param core.emulator.enumerations.ExceptionLevel level: exception level :param str source: source name :param int node_id: node related to exception :param str text: exception message :return: nothing """ - exception_data = ExceptionData( node=node_id, session=str(self.id), @@ -1426,7 +1425,6 @@ class Session: date=time.ctime(), text=text, ) - self.broadcast_exception(exception_data) def instantiate(self): diff --git a/daemon/core/gui/coreclient.py b/daemon/core/gui/coreclient.py index 83062983..65789b23 100644 --- a/daemon/core/gui/coreclient.py +++ b/daemon/core/gui/coreclient.py @@ -167,7 +167,7 @@ class CoreClient: elif event.HasField("config_event"): logging.info("config event: %s", event) elif event.HasField("exception_event"): - self.handle_exception_event(event.exception_event) + self.handle_exception_event(event) else: logging.info("unhandled event: %s", event) @@ -204,7 +204,7 @@ class CoreClient: def handle_throughputs(self, event): if event.session_id != self.session_id: - logging.warn( + logging.warning( "ignoring throughput event session(%s) current(%s)", event.session_id, self.session_id, diff --git a/daemon/core/gui/dialogs/alerts.py b/daemon/core/gui/dialogs/alerts.py index cb91881c..7e82da73 100644 --- a/daemon/core/gui/dialogs/alerts.py +++ b/daemon/core/gui/dialogs/alerts.py @@ -4,9 +4,7 @@ check engine light import tkinter as tk from tkinter import ttk -from grpc import RpcError - -from core.api.grpc import core_pb2 +from core.api.grpc.core_pb2 import ExceptionLevel from core.gui.dialogs.dialog import Dialog from core.gui.themes import PADX, PADY from core.gui.widgets import CodeText @@ -18,6 +16,7 @@ class AlertsDialog(Dialog): self.app = app self.tree = None self.codetext = None + self.alarm_map = {} self.draw() def draw(self): @@ -48,25 +47,31 @@ class AlertsDialog(Dialog): self.tree.bind("<>", self.click_select) for alarm in self.app.statusbar.core_alarms: - level = self.get_level(alarm.level) - self.tree.insert( + exception = alarm.exception_event + level_name = ExceptionLevel.Enum.Name(exception.level) + insert_id = self.tree.insert( "", tk.END, - text=str(alarm.date), + text=exception.date, values=( - alarm.date, - level + " (%s)" % alarm.level, + exception.date, + level_name, alarm.session_id, - alarm.node_id, - alarm.source, + exception.node_id, + exception.source, ), - tags=(level,), + tags=(level_name,), ) + self.alarm_map[insert_id] = alarm - self.tree.tag_configure("ERROR", background="#ff6666") - self.tree.tag_configure("FATAL", background="#d9d9d9") - self.tree.tag_configure("WARNING", background="#ffff99") - self.tree.tag_configure("NOTICE", background="#85e085") + error_name = ExceptionLevel.Enum.Name(ExceptionLevel.ERROR) + self.tree.tag_configure(error_name, background="#ff6666") + fatal_name = ExceptionLevel.Enum.Name(ExceptionLevel.FATAL) + self.tree.tag_configure(fatal_name, background="#d9d9d9") + warning_name = ExceptionLevel.Enum.Name(ExceptionLevel.WARNING) + self.tree.tag_configure(warning_name, background="#ffff99") + notice_name = ExceptionLevel.Enum.Name(ExceptionLevel.NOTICE) + self.tree.tag_configure(notice_name, background="#85e085") yscrollbar = ttk.Scrollbar(frame, orient="vertical", command=self.tree.yview) yscrollbar.grid(row=0, column=1, sticky="ns") @@ -105,40 +110,13 @@ class AlertsDialog(Dialog): dialog = DaemonLog(self, self.app) dialog.show() - def get_level(self, level): - if level == core_pb2.ExceptionLevel.ERROR: - return "ERROR" - if level == core_pb2.ExceptionLevel.FATAL: - return "FATAL" - if level == core_pb2.ExceptionLevel.WARNING: - return "WARNING" - if level == core_pb2.ExceptionLevel.NOTICE: - return "NOTICE" - def click_select(self, event): - current = self.tree.selection() - values = self.tree.item(current)["values"] - time = values[0] - level = values[1] - session_id = values[2] - node_id = values[3] - source = values[4] - text = "DATE: %s\nLEVEL: %s\nNODE: %s (%s)\nSESSION: %s\nSOURCE: %s\n\n" % ( - time, - level, - node_id, - self.app.core.canvas_nodes[node_id].core_node.name, - session_id, - source, - ) - try: - sid = self.app.core.session_id - self.app.core.client.get_node(sid, node_id) - text = text + "node created" - except RpcError: - text = text + "node not created" + current = self.tree.selection()[0] + alarm = self.alarm_map[current] + self.codetext.text.config(state=tk.NORMAL) self.codetext.text.delete("1.0", "end") - self.codetext.text.insert("1.0", text) + self.codetext.text.insert("1.0", alarm.exception_event.text) + self.codetext.text.config(state=tk.DISABLED) class DaemonLog(Dialog): diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 3193d954..4946875e 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -380,12 +380,13 @@ class CoreNodeBase(NodeBase): return common - def cmd(self, args, wait=True): + def cmd(self, args, wait=True, shell=False): """ Runs a command within a node container. :param str args: command to run :param bool wait: True to wait for status, False otherwise + :param bool shell: True to use shell, False otherwise :return: combined stdout and stderr :rtype: str :raises CoreCommandError: when a non-zero exit status occurs @@ -561,19 +562,20 @@ class CoreNode(CoreNodeBase): finally: self.rmnodedir() - def cmd(self, args, wait=True): + def cmd(self, args, wait=True, shell=False): """ Runs a command that is used to configure and setup the network within a node. :param str args: command to run :param bool wait: True to wait for status, False otherwise + :param bool shell: True to use shell, False otherwise :return: combined stdout and stderr :rtype: str :raises CoreCommandError: when a non-zero exit status occurs """ if self.server is None: - return self.client.check_cmd(args, wait=wait) + return self.client.check_cmd(args, wait=wait, shell=shell) else: args = self.client.create_cmd(args) return self.server.remote_cmd(args, wait=wait) diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index 1e949c04..66b61c37 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -53,16 +53,17 @@ class VnodeClient: def create_cmd(self, args): return f"{VCMD_BIN} -c {self.ctrlchnlname} -- {args}" - def check_cmd(self, args, wait=True): + def check_cmd(self, args, wait=True, shell=False): """ Run command and return exit status and combined stdout and stderr. :param str args: command to run :param bool wait: True to wait for command status, False otherwise + :param bool shell: True to use shell, False otherwise :return: combined stdout and stderr :rtype: str :raises core.CoreCommandError: when there is a non-zero exit status """ self._verify_connection() args = self.create_cmd(args) - return utils.cmd(args, wait=wait) + return utils.cmd(args, wait=wait, shell=shell) diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index 20d6ec20..b56fcc5c 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -43,9 +43,9 @@ class DockerClient: def stop_container(self): self.run(f"docker rm -f {self.name}") - def check_cmd(self, cmd): + def check_cmd(self, cmd, wait=True, shell=False): logging.info("docker cmd output: %s", cmd) - return utils.cmd(f"docker exec {self.name} {cmd}") + return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell) def create_ns_cmd(self, cmd): return f"nsenter -t {self.pid} -u -i -p -n {cmd}" @@ -148,10 +148,10 @@ class DockerNode(CoreNode): self.client.stop_container() self.up = False - def nsenter_cmd(self, args, wait=True): + def nsenter_cmd(self, args, wait=True, shell=False): if self.server is None: args = self.client.create_ns_cmd(args) - return utils.cmd(args, wait=wait) + return utils.cmd(args, wait=wait, shell=shell) else: args = self.client.create_ns_cmd(args) return self.server.remote_cmd(args, wait=wait) diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index fc2ee5cc..2d7a6d3d 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -47,9 +47,9 @@ class LxdClient: def create_ns_cmd(self, cmd): return f"nsenter -t {self.pid} -m -u -i -p -n {cmd}" - def check_cmd(self, cmd, wait=True): + def check_cmd(self, cmd, wait=True, shell=False): args = self.create_cmd(cmd) - return utils.cmd(args, wait=wait) + return utils.cmd(args, wait=wait, shell=shell) def copy_file(self, source, destination): if destination[0] != "/": diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index b201493f..4a7250f0 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -126,7 +126,10 @@ class LinuxNetClient: :param str device: device to flush :return: nothing """ - self.run(f"{IP_BIN} -6 address flush dev {device}") + self.run( + f"[ -e /sys/class/net/{device} ] && {IP_BIN} -6 address flush dev {device} || true", + shell=True, + ) def device_mac(self, device, mac): """ diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 5342215b..a4d404ec 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -181,7 +181,7 @@ class EbtablesQueue: def ebchange(self, wlan): """ - Flag a change to the given WLAN"s _linked dict, so the ebtables + Flag a change to the given WLAN's _linked dict, so the ebtables chain will be rebuilt at the next interval. :return: nothing @@ -197,8 +197,17 @@ class EbtablesQueue: :return: nothing """ with wlan._linked_lock: - # flush the chain - self.cmds.append(f"-F {wlan.brname}") + if wlan.has_ebtables_chain: + # flush the chain + self.cmds.append(f"-F {wlan.brname}") + else: + wlan.has_ebtables_chain = True + self.cmds.extend( + [ + f"-N {wlan.brname} -P {wlan.policy}", + f"-A FORWARD --logical-in {wlan.brname} -j {wlan.brname}", + ] + ) # rebuild the chain for netif1, v in wlan._linked.items(): for netif2, linked in v.items(): @@ -297,14 +306,7 @@ class CoreNetwork(CoreNetworkBase): :raises CoreCommandError: when there is a command exception """ self.net_client.create_bridge(self.brname) - - # create a new ebtables chain for this bridge - cmds = [ - f"{EBTABLES_BIN} -N {self.brname} -P {self.policy}", - f"{EBTABLES_BIN} -A FORWARD --logical-in {self.brname} -j {self.brname}", - ] - ebtablescmds(self.host_cmd, cmds) - + self.has_ebtables_chain = False self.up = True def shutdown(self): @@ -320,11 +322,12 @@ class CoreNetwork(CoreNetworkBase): try: self.net_client.delete_bridge(self.brname) - cmds = [ - f"{EBTABLES_BIN} -D FORWARD --logical-in {self.brname} -j {self.brname}", - f"{EBTABLES_BIN} -X {self.brname}", - ] - ebtablescmds(self.host_cmd, cmds) + if self.has_ebtables_chain: + cmds = [ + f"{EBTABLES_BIN} -D FORWARD --logical-in {self.brname} -j {self.brname}", + f"{EBTABLES_BIN} -X {self.brname}", + ] + ebtablescmds(self.host_cmd, cmds) except CoreCommandError: logging.exception("error during shutdown") diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 684ccbb9..4372dd88 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -14,7 +14,7 @@ import time from core import utils from core.constants import which from core.emulator.data import FileData -from core.emulator.enumerations import MessageFlags, RegisterTlvs +from core.emulator.enumerations import ExceptionLevels, MessageFlags, RegisterTlvs from core.errors import CoreCommandError @@ -628,7 +628,13 @@ class CoreServices: for args in service.shutdown: try: node.cmd(args) - except CoreCommandError: + except CoreCommandError as e: + self.session.exception( + ExceptionLevels.ERROR, + "services", + node.id, + f"error stopping service {service.name}: {e.stderr}", + ) logging.exception("error running stop command %s", args) status = -1 return status diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 89e46f9b..2e2685ac 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -1101,19 +1101,26 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() queue = Queue() + exception_level = ExceptionLevels.FATAL + source = "test" + node_id = None + text = "exception message" def handle_event(event_data): assert event_data.session_id == session.id assert event_data.HasField("exception_event") + exception_event = event_data.exception_event + assert exception_event.level == exception_level.value + assert exception_event.node_id == 0 + assert exception_event.source == source + assert exception_event.text == text queue.put(event_data) # then with client.context_connect(): client.events(session.id, handle_event) time.sleep(0.1) - session.exception( - ExceptionLevels.FATAL.value, "test", None, "exception message" - ) + session.exception(exception_level, source, node_id, text) # then queue.get(timeout=5)