1
0
Fork 1
mirror of https://github.com/thatmattlove/hyperglass.git synced 2026-01-17 08:48:05 +00:00

Start output plugin feature

This commit is contained in:
thatmattlove 2021-09-11 00:47:01 -07:00
parent 76bf5eb380
commit 3c012f7ed1
8 changed files with 144 additions and 11 deletions

View file

@ -14,6 +14,7 @@ from gunicorn.glogging import Logger # type: ignore
# Local
from .log import log, setup_lib_logging
from .plugins import init_plugins
from .constants import MIN_NODE_VERSION, MIN_PYTHON_VERSION, __version__
from .util.frontend import get_node_version
@ -120,15 +121,10 @@ def on_starting(server: Arbiter):
required = ".".join((str(v) for v in MIN_PYTHON_VERSION))
log.info("Python {} detected ({} required)", python_version, required)
async def runner():
# Standard Library
from asyncio import gather
await gather(build_ui(), cache_config())
check_redis_instance()
aiorun(build_ui())
cache_config()
init_plugins()
log.success(
"Started hyperglass {v} on http://{h}:{p} with {w} workers",

View file

@ -103,20 +103,22 @@ class Device(HyperglassModel, extra="allow"):
return device_id, {"name": display_name, "display_name": None, **values}
def _validate_directive_attrs(self) -> None:
# Get all commands associated with the device.
commands = [
@property
def directive_commands(self) -> List[str]:
"""Get all commands associated with the device."""
return [
command
for directive in self.commands
for rule in directive.rules
for command in rule.commands
]
def _validate_directive_attrs(self) -> None:
# Set of all keys except for built-in key `target`.
keys = {
key
for group in [get_fmt_keys(command) for command in commands]
for group in [get_fmt_keys(command) for command in self.directive_commands]
for key in group
if key != "target"
}

View file

@ -0,0 +1,12 @@
"""hyperglass Plugins."""
# Local
from .main import init_plugins
from ._output import OutputPlugin
from ._register import register_output_plugin
__all__ = (
"OutputPlugin",
"register_output_plugin",
"init_plugins",
)

View file

@ -0,0 +1,6 @@
"""Built-in hyperglass plugins."""
# Local
from .remove_command import RemoveCommand
__all__ = ("RemoveCommand",)

View file

@ -0,0 +1,23 @@
"""Remove anything before the command if found in output."""
# Project
from hyperglass.models.config.devices import Device
# Local
from .._output import OutputPlugin
class RemoveCommand(OutputPlugin):
"""Remove anything before the command if found in output."""
def process(self, device_output: str, device: Device) -> str:
"""Remove anything before the command if found in output."""
output = device_output.strip().split("\n")
for command in device.directive_commands:
for line in output:
if command in line:
idx = output.index(line) + 1
output = output[idx:]
return "\n".join(output)

View file

@ -0,0 +1,29 @@
"""Device output plugins."""
# Standard Library
import abc
# Project
from hyperglass.models import HyperglassModel
from hyperglass.models.config.devices import Device
class OutputPlugin(HyperglassModel, abc.ABC):
"""Plugin to interact with device command output."""
def __eq__(self, other: "OutputPlugin"):
"""Other plugin is equal to this plugin."""
return other and self.__repr_name__ == other.__repr_name__
def __ne__(self, other: "OutputPlugin"):
"""Other plugin is not equal to this plugin."""
return not self.__eq__(other)
def __hash__(self) -> int:
"""Create a hashed representation of this plugin's name."""
return hash(self.__repr_name__)
@abc.abstractmethod
def process(self, device_output: str, device: Device) -> str:
"""Process/manipulate output from a device."""
pass

View file

@ -0,0 +1,47 @@
"""Plugin registration."""
# Standard Library
import json
import codecs
import pickle
from typing import List, Generator
# Project
from hyperglass.log import log
from hyperglass.cache import SyncCache
from hyperglass.configuration import REDIS_CONFIG, params
# Local
from ._output import OutputPlugin
CACHE = SyncCache(db=params.cache.database, **REDIS_CONFIG)
def get_plugins() -> Generator[OutputPlugin, None, None]:
"""Retrieve plugins from cache."""
# Retrieve plugins from cache.
raw = CACHE.get("hyperglass.plugins.output")
if isinstance(raw, List):
for plugin in raw:
yield pickle.loads(codecs.decode(plugin.encode(), "base64"))
def add_plugin(plugin: OutputPlugin) -> None:
"""Add a plugin to currently active plugins."""
# Create a set of plugins so duplicate plugins are not mistakenly added.
plugins = {
# Create a base64 representation of a picked plugin.
codecs.encode(pickle.dumps(p), "base64").decode()
# Merge current plugins with the new plugin.
for p in [*get_plugins(), plugin]
}
# Add plugins from cache.
CACHE.set("hyperglass.plugins.output", json.dumps(list(plugins)))
def register_output_plugin(plugin: OutputPlugin):
"""Register an output plugin."""
if issubclass(plugin, OutputPlugin):
plugin = plugin()
add_plugin(plugin)
log.info("Registered output plugin '{}'", plugin.__class__.__name__)

View file

@ -0,0 +1,18 @@
"""Register all plugins."""
# Standard Library
from inspect import isclass
# Local
from . import _builtin
from ._output import OutputPlugin
from ._register import register_output_plugin
def init_plugins() -> None:
"""Initialize all plugins."""
for name in dir(_builtin):
plugin = getattr(_builtin, name)
if isclass(plugin):
if issubclass(plugin, OutputPlugin):
register_output_plugin(plugin)