Skip to content

Commit

Permalink
xlate/openconfig: add cache datastore option
Browse files Browse the repository at this point in the history
This change adds a runtime option for the OpenConfig translator to
use a cache datastore. It may improve the response speed to retrieve
operational states of OpenConfig models. However, it also introduces
differences between the operational states and actual states of the
device as a side effect.
  • Loading branch information
ryoma-hoson authored and noguchiko committed Nov 15, 2022
1 parent 453afe1 commit 5ca82a8
Show file tree
Hide file tree
Showing 15 changed files with 1,823 additions and 130 deletions.
6 changes: 4 additions & 2 deletions src/xlate/openconfig/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ sudo pip3 install .

```sh
$ gsxlated-openconfig -h
usage: gsxlated-openconfig [-h] [-v] operational-modes-file
usage: gsxlated-openconfig [-h] [-v] [-c {none,in-memory}] operational-modes-file

positional arguments:
operational-modes-file
Expand All @@ -59,10 +59,12 @@ positional arguments:
options:
-h, --help show this help message and exit
-v, --verbose enable detailed output
-c {none,in-memory}, --cache-datastore {none,in-memory}
select cache datastore
```
Example:
```sh
gsxlated-openconfig operational-modes.json
gsxlated-openconfig -c none operational-modes.json
```
67 changes: 67 additions & 0 deletions src/xlate/openconfig/goldstone/xlate/openconfig/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Cache datastore for OpenConfig translators."""


from abc import abstractmethod
import logging


logger = logging.getLogger(__name__)


class CacheDataNotExistError(Exception):
pass


class Cache:
"""Base for cache datastore.
It is an abstract class. You should implement a subclass for each datastore type.
"""

@abstractmethod
def get(self, module):
"""Get operational state data of a module from the cache datastore.
Args:
module (str): Module name.
Returns:
dict: Operational state data of a module.
Raises:
CacheDataNotExistError: Data for the module does not exist in the cache datastore.
"""
pass

@abstractmethod
def set(self, module, data):
"""Set operational state data of an OpenConfig model to the cache datastore.
Args:
module (str): Module name.
data (dict): Operational state data of a model.
"""
pass


class InMemoryCache(Cache):
"""In-memory cache datastore.
Attribute:
_data (dict): Operational state data for modules. A key is a module name.
"""

def __init__(self):
self._data = {}

def get(self, module):
try:
return self._data[module]
except KeyError as e:
logger.error("%s is not cached.", module)
raise CacheDataNotExistError(
f"Cache data for {module} does not exist in the cache datastore"
) from e

def set(self, module, data):
self._data[module] = data
90 changes: 90 additions & 0 deletions src/xlate/openconfig/goldstone/xlate/openconfig/cache_updater.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"""Cache data updater for OpenConfig translators."""

import asyncio
import logging
from .interfaces import InterfacesObjectTree
from .platform import PlatformObjectTree
from .terminal_device import TerminalDeviceObjectTree
from .telemetry import TelemetryObjectTree
from goldstone.lib.errors import Error as ConnectorError
from goldstone.lib.connector.sysrepo import Connector


logger = logging.getLogger(__name__)


DEFAULT_UPDATE_INTERVAL = 5


class CacheUpdater:
"""Cache data updater for OpenConfig translators.
Args:
cache (Cache): Cache datastore instance to use.
operational_modes (dict): Supported operational-modes.
update_interval (int): Interval seconds between executions of the update task.
"""

def __init__(
self, cache, operational_modes, update_interval=DEFAULT_UPDATE_INTERVAL
):
self._cache = cache
self._operational_modes = operational_modes
self._update_interval = update_interval
self._connector = Connector()
self._required_data = []
self._object_trees = {
"openconfig-interfaces": InterfacesObjectTree(),
"openconfig-platform": PlatformObjectTree(self._operational_modes),
"openconfig-terminal-device": TerminalDeviceObjectTree(
self._operational_modes
),
"openconfig-telemetry": TelemetryObjectTree(),
}
for _, object_tree in self._object_trees.items():
for data in object_tree.required_data():
if not data in self._required_data:
self._required_data.append(data)

def _get_gs(self):
"""Get operational state data of Goldstone primitive models from the central datastore.
Returns:
dict: Operational state data of Goldstone primitive models.
"""
gs = {}
for d in self._required_data:
try:
gs[d["name"]] = self._connector.get_operational(
d["xpath"], d["default"]
)
except ConnectorError as e:
logger.error("Failed to get source data from %s. %s", d["name"], e)
return gs

async def _update(self):
"""Update cache datastore."""
gs = self._get_gs()
for module, object_tree in self._object_trees.items():
try:
self._cache.set(module, object_tree.create(gs))
except Exception as e:
logger.error("Failed to update cache data for %s. %s", module, e)

async def _update_loop(self):
"""Update task coroutine."""
while True:
await asyncio.sleep(self._update_interval)
await self._update()

async def start(self):
"""Start a service.
Returns:
list: List of coroutine objects.
"""
return [self._update_loop()]

async def stop(self):
"""Stop a service."""
pass
22 changes: 17 additions & 5 deletions src/xlate/openconfig/goldstone/xlate/openconfig/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .lib import (
OpenConfigChangeHandler,
OpenConfigObjectFactory,
OpenConfigObjectTree,
OpenConfigServer,
)
from .platform import ComponentNameResolver
Expand Down Expand Up @@ -471,11 +472,24 @@ def create(self, gs):
return result


class InterfacesObjectTree(OpenConfigObjectTree):
"""OpenConfigObjectTree for the openconfig-interfaces module.
It creates an operational state tree of the openconfig-interfaces module.
"""

def __init__(self):
super().__init__()
self.objects = {
"interfaces": {"interface": InterfaceFactory(ComponentNameResolver())}
}


class InterfaceServer(OpenConfigServer):
"""InterfaceServer provides a service for the openconfig-interfaces module to central datastore."""

def __init__(self, conn, reconciliation_interval=10):
super().__init__(conn, "openconfig-interfaces", reconciliation_interval)
def __init__(self, conn, cache, reconciliation_interval=10):
super().__init__(conn, "openconfig-interfaces", cache, reconciliation_interval)
self.handlers = {
"interfaces": {
"interface": {
Expand All @@ -498,9 +512,7 @@ def __init__(self, conn, reconciliation_interval=10):
}
}
}
self.objects = {
"interfaces": {"interface": InterfaceFactory(ComponentNameResolver())}
}
self._object_tree = InterfacesObjectTree()

async def reconcile(self):
# NOTE: This should be implemented as a separated class of function to remove the dependency from the
Expand Down
Loading

0 comments on commit 5ca82a8

Please sign in to comment.