From 47ed12959dc1ac3d65b211f467ce23408fe36c48 Mon Sep 17 00:00:00 2001 From: "A. Wilcox" Date: Thu, 22 Oct 2020 14:26:20 -0500 Subject: Implement module RPCs and RPC logging --- doc/roadmap.rst | 26 +++++++++++++------------- ncserver/base/modman.py | 12 ++++++++++++ ncserver/module/system.py | 17 +++++++++++++++++ ncserver/server.py | 36 +++++++++++++++++++++++++++++++++++- 4 files changed, 77 insertions(+), 14 deletions(-) diff --git a/doc/roadmap.rst b/doc/roadmap.rst index f5c1957..d708a3b 100644 --- a/doc/roadmap.rst +++ b/doc/roadmap.rst @@ -22,7 +22,7 @@ * [X] Respond to state requests -* [ ] Handle per-module RPC requests +* [X] Handle per-module RPC requests The module management system will be the heart of ``ncserver``. Similar to @@ -60,36 +60,36 @@ Resolved TBDs -[/] Logging system +[X] Logging system ------------------ * [X] Output format * [X] Configuration -* [/] Event logging +* [X] Event logging - * [/] auth + * [X] auth - * [ ] Connection established + * [X] Connection established * [X] Authentication succeess * [X] Authentication failure - * [ ] Connection closed + * [X] Connection closed - * [ ] operational + * [X] operational - * [ ] RPC executed + * [X] RPC executed - * [ ] RPC unknown + * [X] RPC unknown - * [ ] RPC failure + * [X] RPC failure - * [ ] RPC success + * [X] RPC success - * [/] config + * [X] config * [X] Configuration editing @@ -97,7 +97,7 @@ Resolved TBDs * [X] Configuration reading - * [ ] Configuration operation denied + * [X] Configuration operation denied This system is reponsible for all event logging for the NETCONF APK project. See the `logging specification`_ for more details. diff --git a/ncserver/base/modman.py b/ncserver/base/modman.py index 3bf8fcb..a47fa3e 100644 --- a/ncserver/base/modman.py +++ b/ncserver/base/modman.py @@ -127,6 +127,7 @@ class ModuleManager: self.imports = dict(self.M_IMPORTS) self.library = {'ncserver.base.modman': '{}@{}'.format(self.M_NAME, self.M_REVISION)} + self.rpcs = dict() nsmap_add(self.M_PREFIX, self.M_NS) self.logger = getLogger('base/ModuleManager') @@ -195,6 +196,9 @@ class ModuleManager: else: self.imports[imname] = improps + if getattr(mod, 'M_RPCS', None) is not None: + self.rpcs.update(mod.M_RPCS) + self.logger.info(_("Loading module '%s' with ABI %d for namespace %s"), name, mod.M_ABI_VERSION, mod.M_NS) @@ -235,6 +239,14 @@ class ModuleManager: return capabs + def has_rpc(self, name: str) -> bool: + """Determine if an RPC is implemented by a module.""" + return name in self.rpcs + + def rpc(self, name: str): + """Return an RPC that is implemented by a module.""" + return self.rpcs.get(name, None) + def running(self, node): """Return running configuration information.""" # ietf-yang-library is state-only. diff --git a/ncserver/module/system.py b/ncserver/module/system.py index 3898576..c27b3a0 100644 --- a/ncserver/module/system.py +++ b/ncserver/module/system.py @@ -522,3 +522,20 @@ def edit(session, rpc, node, def_op): methods[name](session, rpc, subsystem, def_op) else: raise error.UnknownElementAppError(rpc, subsystem) + + +def rpc_system_shutdown(session, rpc, *params): + """Shutdown the system.""" + subprocess.Popen(["/sbin/poweroff"]) + return util.elm('nc:ok') + + +def rpc_system_restart(session, rpc, *params): + """Restart the system.""" + subprocess.Popen(["/sbin/reboot"]) + return util.elm('nc:ok') + + +M_RPCS = {'system-shutdown': rpc_system_shutdown, + 'system-restart': rpc_system_restart} +"""The RPCs implemented by this module.""" diff --git a/ncserver/server.py b/ncserver/server.py index f081217..796e85d 100644 --- a/ncserver/server.py +++ b/ncserver/server.py @@ -20,7 +20,8 @@ import paramiko from netconf import error, util from netconf.server import NetconfSSHServer, SSHAuthorizedKeysController -from ncserver.base.log import configure_logging, LOG_AUTH, LOG_CONFIG +from ncserver.base.log import configure_logging, LOG_AUTH, LOG_CONFIG, \ + LOG_OPERATIONAL from ncserver.base.modman import ModuleManager from ncserver.base.util import user_for_session from ncserver.config import ConfigManager @@ -100,6 +101,12 @@ class Server: for module in self.config.get_list('server', 'modules'): self.modman.load_module(module) + def __getattr__(self, attr): + """Maybe pass RPC calls on.""" + if attr.startswith("rpc_"): + return self._rpc_wrapper + raise AttributeError("'Server' object has no attribute '" + attr + "'") + def close(self): """Close all connections.""" self.server.close() @@ -117,6 +124,33 @@ class Server: for capab in our_capabs: util.subelm(capabilities, 'capability').text = capab + def _rpc_wrapper(self, session, rpc, *params): + """Handle module RPCs.""" + name = QName(rpc[0].tag).localname + LOG_OPERATIONAL.info('RPC %s invoked by %s on session %d', + name, user_for_session(session), session.session_id) + if self.modman.has_rpc(name): + try: + result = self.modman.rpc(name)(session, rpc, *params) + LOG_OPERATIONAL.info( + 'RPC %s invoked by %s on session %d completed', + name, user_for_session(session), session.session_id + ) + return result + except error.RPCServerError as rpc_error: + LOG_OPERATIONAL.error( + 'RPC %s invoked by %s on session %d encountered error %s', + name, user_for_session(session), session.session_id, + rpc_error + ) + raise + else: + LOG_OPERATIONAL.warning( + 'RPC %s invoked by %s on session %d is unknown', + name, user_for_session(session), session.session_id + ) + raise error.OperationNotSupportedProtoError(rpc) + def rpc_get(self, session, rpc, filter_or_none): # pylint: disable=W0613 """Handle the RPC.""" log_read(session, rpc) -- cgit v1.2.3-70-g09d2