1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

Add directives to global state

This commit is contained in:
thatmattlove
2021-09-17 09:04:59 -07:00
parent 9a57e4a728
commit 7adb6ae0ec
9 changed files with 236 additions and 11 deletions

View File

@@ -12,6 +12,7 @@ per-file-ignores=
hyperglass/models/api/*.py:N805,E0213,R0903,E501,C0301 hyperglass/models/api/*.py:N805,E0213,R0903,E501,C0301
hyperglass/models/commands/*.py:N805,E0213,R0903,E501,C0301 hyperglass/models/commands/*.py:N805,E0213,R0903,E501,C0301
hyperglass/parsing/models/*.py:N805,E0213,R0903 hyperglass/parsing/models/*.py:N805,E0213,R0903
hyperglass/defaults/*/*.py:E501
hyperglass/configuration/models/*.py:N805,E0213,R0903,E501,C0301 hyperglass/configuration/models/*.py:N805,E0213,R0903,E501,C0301
# Disable unused import warning for modules # Disable unused import warning for modules
hyperglass/*/__init__.py:F401 hyperglass/*/__init__.py:F401

View File

@@ -0,0 +1,25 @@
"""Built-in hyperglass directives."""
# Standard Library
import pkgutil
import importlib
from pathlib import Path
# Project
from hyperglass.log import log
from hyperglass.state import use_state
def register_builtin_directives() -> None:
"""Find all directives and register them with global state manager."""
directives_dir = Path(__file__).parent
state = use_state()
for _, name, __ in pkgutil.iter_modules([directives_dir]):
module = importlib.import_module(f"hyperglass.defaults.directives.{name}")
if not all((hasattr(module, "__all__"), len(getattr(module, "__all__", ())) > 0)):
# Warn if there is no __all__ export or if it is empty.
log.warning("Module '{!s}' is missing an '__all__' export", module)
exports = (getattr(module, p) for p in module.__all__ if hasattr(module, p))
state.add_directive(*exports)

View File

@@ -0,0 +1,165 @@
"""Default Juniper Directives."""
# Project
from hyperglass.models.directive import Rule, Text, NativeDirective
__all__ = (
"JuniperBGPRoute",
"JuniperBGPASPath",
"JuniperBGPCommunity",
"JuniperPing",
"JuniperTraceroute",
"JuniperBGPRouteTable",
"JuniperBGPASPathTable",
"JuniperBGPCommunityTable",
)
JuniperBGPRoute = NativeDirective(
id="__hyperglass_juniper_bgp_route__",
name="BGP Route",
rules=[
Rule(
condition="0.0.0.0/0",
action="permit",
command="show route protocol table inet.0 {target} detail",
),
Rule(
condition="::/0",
action="permit",
command="show route protocol table inet6.0 {target} detail",
),
],
field=Text(description="IP Address, Prefix, or Hostname"),
platforms=["juniper"],
)
JuniperBGPASPath = NativeDirective(
id="__hyperglass_juniper_bgp_aspath__",
name="BGP AS Path",
rules=[
Rule(
condition="*",
action="permit",
commands=[
'show route protocol table inet.0 aspath-regex "{target}"',
'show route protocol table inet6.0 aspath-regex "{target}"',
],
)
],
field=Text(description="AS Path Regular Expression"),
platforms=["juniper"],
)
JuniperBGPCommunity = NativeDirective(
id="__hyperglass_juniper_bgp_community__",
name="BGP Community",
rules=[
Rule(
condition="*",
action="permit",
commands=[
'show route protocol table inet.0 community "{target}" detail',
'show route protocol table inet6.0 community "{target}" detail',
],
)
],
field=Text(description="AS Path Regular Expression"),
platforms=["juniper"],
)
JuniperPing = NativeDirective(
id="__hyperglass_juniper_ping__",
name="Ping",
rules=[
Rule(
condition="0.0.0.0/0",
action="permit",
command="ping inet {target} count 5 source {source4}",
),
Rule(
condition="::/0",
action="permit",
command="ping inet6 {target} count 5 source {source6}",
),
],
field=Text(description="IP Address, Prefix, or Hostname"),
platforms=["juniper"],
)
JuniperTraceroute = NativeDirective(
id="__hyperglass_juniper_traceroute__",
name="Traceroute",
rules=[
Rule(
condition="0.0.0.0/0",
action="permit",
command="traceroute inet {target} wait 1 source {source4}",
),
Rule(
condition="::/0",
action="permit",
command="traceroute inet6 {target} wait 1 source {source6}",
),
],
field=Text(description="IP Address, Prefix, or Hostname"),
platforms=["juniper"],
)
# Table Output Directives
JuniperBGPRouteTable = NativeDirective(
id="__hyperglass_juniper_bgp_route_table__",
name="BGP Route",
rules=[
Rule(
condition="0.0.0.0/0",
action="permit",
command="show route protocol bgp table inet.0 {target} best detail | display xml",
),
Rule(
condition="::/0",
action="permit",
command="show route protocol bgp table inet6.0 {target} best detail | display xml",
),
],
field=Text(description="IP Address, Prefix, or Hostname"),
table_output=True,
platforms=["juniper"],
)
JuniperBGPASPathTable = NativeDirective(
id="__hyperglass_juniper_bgp_aspath_table__",
name="BGP AS Path",
rules=[
Rule(
condition="*",
action="permit",
commands=[
'show route protocol bgp table inet.0 aspath-regex "{target}" detail | display xml',
'show route protocol bgp table inet6.0 aspath-regex "{target}" detail | display xml',
],
)
],
field=Text(description="AS Path Regular Expression"),
table_output=True,
platforms=["juniper"],
)
JuniperBGPCommunityTable = NativeDirective(
id="__hyperglass_juniper_bgp_community_table__",
name="BGP Community",
rules=[
Rule(
condition="*",
action="permit",
commands=[
"show route protocol bgp table inet.0 community {target} detail | display xml",
"show route protocol bgp table inet6.0 community {target} detail | display xml",
],
)
],
field=Text(description="AS Path Regular Expression"),
table_output=True,
platforms=["juniper"],
)

View File

@@ -21,6 +21,7 @@ from .plugins import (
) )
from .constants import MIN_NODE_VERSION, MIN_PYTHON_VERSION, __version__ from .constants import MIN_NODE_VERSION, MIN_PYTHON_VERSION, __version__
from .util.frontend import get_node_version from .util.frontend import get_node_version
from .defaults.directives import register_builtin_directives
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
# Local # Local
@@ -88,6 +89,8 @@ def on_starting(server: "Arbiter"):
state = use_state() state = use_state()
register_builtin_directives()
register_all_plugins(state.devices) register_all_plugins(state.devices)
asyncio.run(build_ui()) asyncio.run(build_ui())

View File

@@ -214,7 +214,16 @@ class HyperglassMultiModel(GenericModel, t.Generic[MultiModelT]):
for o in (*self, *to_add) for o in (*self, *to_add)
if getattr(o, unique_by) == v if getattr(o, unique_by) == v
} }
self.__root__ = list(unique_by_objects.values()) new: t.List[MultiModelT] = list(unique_by_objects.values())
else: else:
self.__root__ = [*self.__root__, *to_add] new: t.List[MultiModelT] = [*self.__root__, *to_add]
self.__root__ = new
self._count = len(self.__root__) self._count = len(self.__root__)
for item in new:
log.debug(
"Added {} '{!s}' to {}",
item.__class__.__name__,
getattr(item, self.accessor),
self.__class__.__name__,
)

View File

@@ -122,9 +122,9 @@ class BGPRoutePluginJuniper(OutputPlugin):
__hyperglass_builtin__: bool = PrivateAttr(True) __hyperglass_builtin__: bool = PrivateAttr(True)
platforms: Sequence[str] = ("juniper",) platforms: Sequence[str] = ("juniper",)
directives: Sequence[str] = ( directives: Sequence[str] = (
"__hyperglass_juniper_bgp_route__", "__hyperglass_juniper_bgp_route_table__",
"__hyperglass_juniper_bgp_aspath__", "__hyperglass_juniper_bgp_aspath_table__",
"__hyperglass_juniper_bgp_community__", "__hyperglass_juniper_bgp_community_table__",
) )
def process(self, output: "OutputType", device: "Device") -> "OutputType": def process(self, output: "OutputType", device: "Device") -> "OutputType":

View File

@@ -14,6 +14,7 @@ from ..settings import Settings
if t.TYPE_CHECKING: if t.TYPE_CHECKING:
# Project # Project
from hyperglass.models.ui import UIParameters from hyperglass.models.ui import UIParameters
from hyperglass.models.directive import Directives
from hyperglass.models.config.params import Params from hyperglass.models.config.params import Params
from hyperglass.models.config.devices import Devices from hyperglass.models.config.devices import Devices
@@ -58,6 +59,11 @@ def use_state(attr: t.Literal["cache", "redis"]) -> "RedisManager":
"""Directly access hyperglass Redis cache manager.""" """Directly access hyperglass Redis cache manager."""
@t.overload
def use_state(attr: t.Literal["directives"]) -> "Directives":
"""Access all hyperglass directives."""
@t.overload @t.overload
def use_state(attr=None) -> "HyperglassState": def use_state(attr=None) -> "HyperglassState":
"""Access entire global state. """Access entire global state.

View File

@@ -8,7 +8,6 @@ from redis import Redis, ConnectionPool
# Project # Project
from hyperglass.util import repr_from_attrs from hyperglass.util import repr_from_attrs
from hyperglass.configuration import params, devices, ui_params
# Local # Local
from .redis import RedisManager from .redis import RedisManager
@@ -36,11 +35,6 @@ class StateManager:
redis = Redis(connection_pool=connection_pool) redis = Redis(connection_pool=connection_pool)
self.redis = RedisManager(instance=redis, namespace=self._namespace) self.redis = RedisManager(instance=redis, namespace=self._namespace)
# Add configuration objects.
self.redis.set("params", params)
self.redis.set("devices", devices)
self.redis.set("ui_params", ui_params)
def __repr__(self) -> str: def __repr__(self) -> str:
"""Represent state manager by name and namespace.""" """Represent state manager by name and namespace."""
return repr_from_attrs(self, ("redis", "namespace")) return repr_from_attrs(self, ("redis", "namespace"))

View File

@@ -5,6 +5,9 @@ import codecs
import pickle import pickle
import typing as t import typing as t
# Project
from hyperglass.configuration import params, devices, ui_params, directives
# Local # Local
from .manager import StateManager from .manager import StateManager
@@ -13,6 +16,7 @@ if t.TYPE_CHECKING:
from hyperglass.models.ui import UIParameters from hyperglass.models.ui import UIParameters
from hyperglass.models.system import HyperglassSystem from hyperglass.models.system import HyperglassSystem
from hyperglass.plugins._base import HyperglassPlugin from hyperglass.plugins._base import HyperglassPlugin
from hyperglass.models.directive import Directive, Directives
from hyperglass.models.config.params import Params from hyperglass.models.config.params import Params
from hyperglass.models.config.devices import Devices from hyperglass.models.config.devices import Devices
@@ -26,6 +30,13 @@ class HyperglassState(StateManager):
def __init__(self, *, settings: "HyperglassSystem") -> None: def __init__(self, *, settings: "HyperglassSystem") -> None:
"""Initialize state store and reset plugins.""" """Initialize state store and reset plugins."""
super().__init__(settings=settings) super().__init__(settings=settings)
# Add configuration objects.
self.redis.set("params", params)
self.redis.set("devices", devices)
self.redis.set("ui_params", ui_params)
self.redis.set("directives", directives)
# Ensure plugins are empty. # Ensure plugins are empty.
self.reset_plugins("output") self.reset_plugins("output")
self.reset_plugins("input") self.reset_plugins("input")
@@ -57,6 +68,12 @@ class HyperglassState(StateManager):
"""Remove all plugins of `_type`.""" """Remove all plugins of `_type`."""
self.redis.set(("plugins", _type), []) self.redis.set(("plugins", _type), [])
def add_directive(self, *directives: t.Union["Directive", t.Dict[str, t.Any]]) -> None:
"""Add a directive."""
current = self.directives
current.add(*directives, unique_by="id")
self.redis.set("directives", current)
def clear(self) -> None: def clear(self) -> None:
"""Delete all cache keys.""" """Delete all cache keys."""
self.redis.instance.flushdb(asynchronous=True) self.redis.instance.flushdb(asynchronous=True)
@@ -76,6 +93,11 @@ class HyperglassState(StateManager):
"""UI parameters, built from params.""" """UI parameters, built from params."""
return self.redis.get("ui_params", raise_if_none=True) return self.redis.get("ui_params", raise_if_none=True)
@property
def directives(self) -> "Directives":
"""All directives."""
return self.redis.get("directives", raise_if_none=True)
def plugins(self, _type: str) -> t.List[PluginT]: def plugins(self, _type: str) -> t.List[PluginT]:
"""Get plugins by type.""" """Get plugins by type."""
current = self.redis.get(("plugins", _type), raise_if_none=False, value_if_none=[]) current = self.redis.get(("plugins", _type), raise_if_none=False, value_if_none=[])