forked from mirrors/thatmattlove-hyperglass
Start output plugin feature
This commit is contained in:
parent
76bf5eb380
commit
3c012f7ed1
8 changed files with 144 additions and 11 deletions
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
12
hyperglass/plugins/__init__.py
Normal file
12
hyperglass/plugins/__init__.py
Normal 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",
|
||||
)
|
||||
6
hyperglass/plugins/_builtin/__init__.py
Normal file
6
hyperglass/plugins/_builtin/__init__.py
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
"""Built-in hyperglass plugins."""
|
||||
|
||||
# Local
|
||||
from .remove_command import RemoveCommand
|
||||
|
||||
__all__ = ("RemoveCommand",)
|
||||
23
hyperglass/plugins/_builtin/remove_command.py
Normal file
23
hyperglass/plugins/_builtin/remove_command.py
Normal 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)
|
||||
29
hyperglass/plugins/_output.py
Normal file
29
hyperglass/plugins/_output.py
Normal 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
|
||||
47
hyperglass/plugins/_register.py
Normal file
47
hyperglass/plugins/_register.py
Normal 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__)
|
||||
18
hyperglass/plugins/main.py
Normal file
18
hyperglass/plugins/main.py
Normal 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)
|
||||
Loading…
Add table
Reference in a new issue