diff --git a/src/xlate/openconfig/README.md b/src/xlate/openconfig/README.md index 0aa62092..146fb42f 100644 --- a/src/xlate/openconfig/README.md +++ b/src/xlate/openconfig/README.md @@ -47,7 +47,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 @@ -56,10 +56,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 ``` diff --git a/src/xlate/openconfig/goldstone/xlate/openconfig/cache.py b/src/xlate/openconfig/goldstone/xlate/openconfig/cache.py new file mode 100644 index 00000000..fc76f2e8 --- /dev/null +++ b/src/xlate/openconfig/goldstone/xlate/openconfig/cache.py @@ -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 diff --git a/src/xlate/openconfig/goldstone/xlate/openconfig/cache_updater.py b/src/xlate/openconfig/goldstone/xlate/openconfig/cache_updater.py new file mode 100644 index 00000000..3b2e195c --- /dev/null +++ b/src/xlate/openconfig/goldstone/xlate/openconfig/cache_updater.py @@ -0,0 +1,88 @@ +"""Cache data updater for OpenConfig translators.""" + +import asyncio +import logging +from .interfaces import InterfacesObjectTree +from .platform import PlatformObjectTree +from .terminal_device import TerminalDeviceObjectTree +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 + ), + } + 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 diff --git a/src/xlate/openconfig/goldstone/xlate/openconfig/interfaces.py b/src/xlate/openconfig/goldstone/xlate/openconfig/interfaces.py index 22c5365b..2282f54b 100644 --- a/src/xlate/openconfig/goldstone/xlate/openconfig/interfaces.py +++ b/src/xlate/openconfig/goldstone/xlate/openconfig/interfaces.py @@ -15,6 +15,7 @@ from .lib import ( OpenConfigChangeHandler, OpenConfigObjectFactory, + OpenConfigObjectTree, OpenConfigServer, ) from .platform import ComponentNameResolver @@ -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": { @@ -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 diff --git a/src/xlate/openconfig/goldstone/xlate/openconfig/lib.py b/src/xlate/openconfig/goldstone/xlate/openconfig/lib.py index d114c3b9..cec674c6 100644 --- a/src/xlate/openconfig/goldstone/xlate/openconfig/lib.py +++ b/src/xlate/openconfig/goldstone/xlate/openconfig/lib.py @@ -13,7 +13,9 @@ The classes this framework library provides are: - OpenConfigServer: provides a service for an OpenConfig module. e.g. "openconfig-interfaces" - It has "OpenConfigObjectFactory"s and "OpenConfigChangeHandler"s. + It has "OpenConfigObjectTree"s and "OpenConfigChangeHandler"s. +- OpenConfigObjectTree: creates a data tree instance that includes operational states for an OpenConfig module. + It has "OpenConfigObjectFactory"s. - OpenConfigObjectFactory: creates OpenConfig objects by translating Goldstone operational state data. - OpenConfigChangeHandler: configure a device with provided OpenConfig configuration state data. @@ -27,6 +29,7 @@ import libyang from goldstone.lib.core import ChangeHandler, ServerBase from goldstone.lib.errors import Error, InvalArgError, NotFoundError +from .cache import CacheDataNotExistError logger = logging.getLogger(__name__) @@ -180,7 +183,7 @@ class OpenConfigObjectFactory: @abstractmethod def required_data(self): - """Return required data list to create OpenConfig objects. + """Return a list of required data to create OpenConfig objects. Returns: list: List of required data dictionaries. @@ -205,6 +208,64 @@ def create(self, gs): pass +class OpenConfigObjectTree: + """Data tree factory base for OpenConfig modules. + + It creates an operational state data tree of an OpenConfig module. It has "OpenConfigObjectFactory"s related to the + module. You should implement the "self.objects" attribute in your subclass. + + Attributes: + objects (dict): OpenConfigObjectFactory instances for an OpenConfig module. + A dictionary key represents a data node. + """ + + def __init__(self): + self.objects = {} + + def _create_tree(self, gs, subtree): + result = {} + for k, v in subtree.items(): + if isinstance(v, dict): + result[k] = self._create_tree(gs, v) + elif isinstance(v, OpenConfigObjectFactory): + result[k] = v.create(gs) + return result + + def _get_factories(self, subtree): + result = [] + for _, v in subtree.items(): + if isinstance(v, dict): + result += self._get_factories(v) + elif isinstance(v, OpenConfigObjectFactory): + result.append(v) + return result + + def required_data(self): + """Return a list of required data to create OpenConfig objects. + + Returns: + list: List of required data dictionaries. See OpenConfigObjectFactory.required_data(). + """ + required_data = [] + factories = self._get_factories(self.objects) + for factory in factories: + for data in factory.required_data(): + if not data in required_data: + required_data.append(data) + return required_data + + def create(self, gs): + """Create an operational state data tree of an OpenConfig module. + + Args: + gs (dict): Data from Goldstone native/primitive models. + + Returns: + dict: Operational state data tree of an Openconfig module. + """ + return self._create_tree(gs, self.objects) + + class OpenConfigServer(ServerBase): """Server base for OpenConfig translators. @@ -222,6 +283,8 @@ class OpenConfigServer(ServerBase): Args: conn (Connector): Connection to the central datastore. module (str): YANG module name of the service. e.g. "openconfig-interfaces" + cache (Cache): Cache datastore instance to store operational state data of OpenConfig modules. + If it is None, OpenConfigServer does not use cached data. reconciliation_interval (int): Interval seconds between executions of the reconcile task. Attributes: @@ -239,21 +302,16 @@ class OpenConfigServer(ServerBase): } } } - objects (dict): "OpenConfigObjectFactory" instances for each OpenConfig subtree. - e.g. - { - "interfaces": { - "interface": InterfaceFactory(ComponentNameResolver()) # InterfaceFactory for /interfaces/interface - } - } """ - def __init__(self, conn, module, reconciliation_interval=10): + def __init__(self, conn, module, cache, reconciliation_interval=10): super().__init__(conn, module) + self.module = module self.reconciliation_interval = reconciliation_interval self.reconcile_task = None self.handlers = {} - self.objects = {} + self._cache = cache + self._object_tree = None async def reconcile(self): """Reconcile between OpenConfig configuration state and Goldstone configuration state.""" @@ -307,22 +365,12 @@ async def post(self, user): logger.error("Failed to apply changes. %s", e) raise e - async def _create_objects(self, factory): - required_data = factory.required_data() - src = {} + def _get_gs(self, required_data): + gs = {} for d in required_data: data = self.get_operational_data(d["xpath"], d["default"]) - src[d["name"]] = data - return factory.create(src) - - async def _create_tree(self, subtree): - result = {} - for k, v in subtree.items(): - if isinstance(v, dict): - result[k] = await self._create_tree(v) - elif isinstance(v, OpenConfigObjectFactory): - result[k] = await self._create_objects(v) - return result + gs[d["name"]] = data + return gs async def oper_cb(self, xpath, priv): """Callback function to get operational state of the service. @@ -335,9 +383,33 @@ async def oper_cb(self, xpath, priv): {"name": "Ethernet1/0/2", "state": {"oper-status": "DOWN"}}, ]}} """ - try: - result = await self._create_tree(self.objects) - except Exception as e: - logger.error("Operational state creation failed. %s", e) - raise e + if self._cache is None: + try: + required_data = self._object_tree.required_data() + gs = self._get_gs(required_data) + result = self._object_tree.create(gs) + except Exception as e: + logger.error( + "Failed to create operational state data for %s by an error: %s", + self.module, + e, + ) + raise e + else: + try: + result = self._cache.get(self.module) + except CacheDataNotExistError as e: + logger.error( + "The operational state data for %s was not cached in the cache datastore. %s", + self.module, + e, + ) + raise e + except Exception as e: + logger.error( + "Failed to get operational state data for %s from the cache datastore by an error: %s", + self.module, + e, + ) + raise e return result diff --git a/src/xlate/openconfig/goldstone/xlate/openconfig/main.py b/src/xlate/openconfig/goldstone/xlate/openconfig/main.py index 02323485..8e800ad9 100644 --- a/src/xlate/openconfig/goldstone/xlate/openconfig/main.py +++ b/src/xlate/openconfig/goldstone/xlate/openconfig/main.py @@ -11,11 +11,19 @@ from .interfaces import InterfaceServer from .platform import PlatformServer from .terminal_device import TerminalDeviceServer +from .cache import InMemoryCache +from .cache_updater import CacheUpdater logger = logging.getLogger(__name__) +CACHES = { + "none": None, + "in-memory": InMemoryCache, +} + + def load_operational_modes(operational_modes_file): try: with open(operational_modes_file, "r", encoding="utf-8") as f: @@ -43,17 +51,20 @@ def load_operational_modes(operational_modes_file): def main(): - async def _main(operational_modes): + async def _main(cache, operational_modes): loop = asyncio.get_event_loop() stop_event = asyncio.Event() loop.add_signal_handler(signal.SIGINT, stop_event.set) loop.add_signal_handler(signal.SIGTERM, stop_event.set) conn = Connector() - ifserver = InterfaceServer(conn) - pfserver = PlatformServer(conn, operational_modes) - tdserver = TerminalDeviceServer(conn, operational_modes) + ifserver = InterfaceServer(conn, cache) + pfserver = PlatformServer(conn, cache, operational_modes) + tdserver = TerminalDeviceServer(conn, cache, operational_modes) servers = [ifserver, pfserver, tdserver] + if cache is not None: + cacheupdater = CacheUpdater(cache, operational_modes) + servers.append(cacheupdater) try: tasks = list( @@ -86,6 +97,13 @@ async def _main(operational_modes): metavar="operational-modes-file", help="path to operational-modes config file", ) + parser.add_argument( + "-c", + "--cache-datastore", + choices=CACHES.keys(), + default="none", + help="select cache datastore", + ) args = parser.parse_args() fmt = "%(levelname)s %(module)s %(funcName)s l.%(lineno)d | %(message)s" @@ -101,9 +119,12 @@ async def _main(operational_modes): else: logging.basicConfig(level=logging.INFO, format=fmt) + cache = CACHES[args.cache_datastore] + if cache is not None: + cache = cache() operational_modes = load_operational_modes(args.operational_modes_file) - asyncio.run(_main(operational_modes)) + asyncio.run(_main(cache, operational_modes)) if __name__ == "__main__": diff --git a/src/xlate/openconfig/goldstone/xlate/openconfig/platform.py b/src/xlate/openconfig/goldstone/xlate/openconfig/platform.py index ef9cd2a4..a1ab2460 100644 --- a/src/xlate/openconfig/goldstone/xlate/openconfig/platform.py +++ b/src/xlate/openconfig/goldstone/xlate/openconfig/platform.py @@ -20,6 +20,7 @@ from .lib import ( OpenConfigChangeHandler, OpenConfigObjectFactory, + OpenConfigObjectTree, OpenConfigServer, ) @@ -1724,6 +1725,26 @@ def create(self, gs): return result +class PlatformObjectTree(OpenConfigObjectTree): + """OpenConfigObjectTree for the openconfig-platform module. + + It creates an operational state data tree of the openconfig-platform module. + + Args: + operational_modes (dict): Supported operational-modes. + """ + + def __init__(self, operational_modes): + super().__init__() + self.objects = { + "components": { + "component": ComponentFactory( + operational_modes, ComponentNameResolver() + ) + } + } + + class PlatformServer(OpenConfigServer): """PlatformServer provides a service for the openconfig-platform module to central datastore. @@ -1735,8 +1756,8 @@ class PlatformServer(OpenConfigServer): cnr (ComponentNameResolver): OpenConfig component name resolver. """ - def __init__(self, conn, operational_modes, reconciliation_interval=10): - super().__init__(conn, "openconfig-platform", reconciliation_interval) + def __init__(self, conn, cache, operational_modes, reconciliation_interval=10): + super().__init__(conn, "openconfig-platform", cache, reconciliation_interval) self.handlers = { "components": { "component": { @@ -1767,11 +1788,7 @@ def __init__(self, conn, operational_modes, reconciliation_interval=10): } self.operational_modes = operational_modes self.cnr = ComponentNameResolver() - self.objects = { - "components": { - "component": ComponentFactory(self.operational_modes, self.cnr) - } - } + self._object_tree = PlatformObjectTree(self.operational_modes) async def reconcile(self): # TODO: implement diff --git a/src/xlate/openconfig/goldstone/xlate/openconfig/terminal_device.py b/src/xlate/openconfig/goldstone/xlate/openconfig/terminal_device.py index a7e82a42..924e86bd 100644 --- a/src/xlate/openconfig/goldstone/xlate/openconfig/terminal_device.py +++ b/src/xlate/openconfig/goldstone/xlate/openconfig/terminal_device.py @@ -18,7 +18,7 @@ import logging import struct import base64 -from .lib import OpenConfigObjectFactory, OpenConfigServer +from .lib import OpenConfigObjectFactory, OpenConfigServer, OpenConfigObjectTree from .platform import ComponentFactory, ComponentNameResolver @@ -855,6 +855,33 @@ def create(self, gs): return operational_modes +class TerminalDeviceObjectTree(OpenConfigObjectTree): + """OpenConfigObjectTree for the openconfig-terminal-device module. + + It creates an operational state data tree of the openconfig-terminal-device module. + + Args: + operational_modes (dict): Supported operational-modes. + """ + + def __init__(self, operational_modes): + super().__init__() + cnr = ComponentNameResolver() + self.objects = { + "terminal-device": { + "logical-channels": { + "channel": LogicalChannelFactory( + cnr, + ComponentFactory(operational_modes, cnr), + ) + }, + "operational-modes": { + "mode": OperationalModeFactory(operational_modes) + }, + } + } + + class TerminalDeviceServer(OpenConfigServer): """TerminalDeviceServer provides a service for the openconfig-terminal-device module to central datastore. @@ -865,20 +892,13 @@ class TerminalDeviceServer(OpenConfigServer): operational_modes (dict): Suppoerted operational-modes. """ - def __init__(self, conn, operational_modes, reconciliation_interval=10): - super().__init__(conn, "openconfig-terminal-device", reconciliation_interval) + def __init__(self, conn, cache, operational_modes, reconciliation_interval=10): + super().__init__( + conn, "openconfig-terminal-device", cache, reconciliation_interval + ) self.handlers = {"terminal-device": {}} self.operational_modes = operational_modes - cnr = ComponentNameResolver() - cf = ComponentFactory(self.operational_modes, cnr) - self.objects = { - "terminal-device": { - "logical-channels": {"channel": LogicalChannelFactory(cnr, cf)}, - "operational-modes": { - "mode": OperationalModeFactory(self.operational_modes) - }, - } - } + self._object_tree = TerminalDeviceObjectTree(self.operational_modes) async def reconcile(self): pass diff --git a/src/xlate/openconfig/tests/lib.py b/src/xlate/openconfig/tests/lib.py index 6f775359..319ec399 100644 --- a/src/xlate/openconfig/tests/lib.py +++ b/src/xlate/openconfig/tests/lib.py @@ -1,5 +1,6 @@ """Library for tests for translator services.""" +# pylint: disable=C0103 import unittest import os @@ -11,7 +12,6 @@ from queue import Empty from goldstone.lib.core import ServerBase, ChangeHandler, NoOp from goldstone.lib.connector.sysrepo import Connector -from goldstone.lib.util import call def load_operational_modes(): @@ -197,6 +197,10 @@ async def evloop(): conn.stop() +class NoneClass: + pass + + class XlateTestCase(unittest.IsolatedAsyncioTestCase): """Test case base class for translator servers. @@ -205,10 +209,22 @@ class XlateTestCase(unittest.IsolatedAsyncioTestCase): XLATE_SERVER_OPT (list): Arguments that will be given to the server. XLATE_MODULES (list): Module names the server will provide. MOCK_MODULES (list): Module names the server will use. + CACHE_DATASTORE (Cache): Cache class for XLATE_SERVER and CACHE_UPDATER. + CACHE_UPDATER (CacheUpdater): CacheUpdater class to test. """ + XLATE_SERVER = NoneClass + XLATE_SERVER_OPT = [] + XLATE_MODULES = [] + MOCK_MODULES = [] + CACHE_DATASTORE = NoneClass + CACHE_UPDATER = NoneClass + async def asyncSetUp(self): - logging.basicConfig(level=logging.DEBUG) + logging.basicConfig(level=logging.CRITICAL) + # NOTE: Enable for debugging. + # logging.basicConfig(level=logging.DEBUG) + # self.maxDiff = None self.conn = Connector() for module in self.MOCK_MODULES: @@ -221,10 +237,27 @@ async def asyncSetUp(self): self.process = Process(target=run_mock_server, args=(self.q, self.MOCK_MODULES)) self.process.start() - self.server = self.XLATE_SERVER( - self.conn, reconciliation_interval=1, *self.XLATE_SERVER_OPT - ) - self.tasks = list(asyncio.create_task(c) for c in await self.server.start()) + cache = None + if self.CACHE_DATASTORE is not NoneClass: + cache = self.CACHE_DATASTORE() + self.servers = [] + if self.XLATE_SERVER is not NoneClass: + self.server = self.XLATE_SERVER( + self.conn, cache, reconciliation_interval=1, *self.XLATE_SERVER_OPT + ) + self.servers.append(self.server) + if self.CACHE_UPDATER is not NoneClass: + operational_mode = load_operational_modes() + self.cache_updater = self.CACHE_UPDATER( + cache, + operational_mode, + update_interval=0.1, + ) + self.servers.append(self.cache_updater) + + self.tasks = [] + for server in self.servers: + self.tasks += list(asyncio.create_task(c) for c in await server.start()) async def run_xlate_test(self, test): """Run a test as a thread. @@ -262,7 +295,8 @@ def set_mock_change_handler(self, server, path, handler): ) async def asyncTearDown(self): - await call(self.server.stop) + for server in self.servers: + await server.stop() self.tasks = [t.cancel() for t in self.tasks] self.conn.stop() self.q.put({"type": "stop"}) diff --git a/src/xlate/openconfig/tests/test.py b/src/xlate/openconfig/tests/test.py index 365c4c92..20b26999 100644 --- a/src/xlate/openconfig/tests/test.py +++ b/src/xlate/openconfig/tests/test.py @@ -9,4 +9,4 @@ sys.path.insert(0, "../../../lib/") sys.path.insert(0, "../") testsuite = unittest.TestLoader().discover(".") - unittest.TextTestRunner().run(testsuite) + unittest.TextTestRunner(verbosity=2).run(testsuite) diff --git a/src/xlate/openconfig/tests/test_cache.py b/src/xlate/openconfig/tests/test_cache.py new file mode 100644 index 00000000..cbf19baf --- /dev/null +++ b/src/xlate/openconfig/tests/test_cache.py @@ -0,0 +1,1348 @@ +"""Tests of the cache feature for OpenConfig translators.""" + +# pylint: disable=W0212 + +import unittest +import time +from goldstone.xlate.openconfig.cache import InMemoryCache, CacheDataNotExistError +from goldstone.xlate.openconfig.cache_updater import CacheUpdater +from tests.lib import XlateTestCase, load_operational_modes + +operational_modes = load_operational_modes() + + +class TestInMemoryCache(unittest.TestCase): + """Tests for InMemoryCache.""" + + def test_get(self): + inmemory_cache = InMemoryCache() + data = { + "interfaces": { + "interface": [ + {"name": "Ethernet1/0/1", "state": {"oper-status": "UP"}}, + {"name": "Ethernet1/0/2", "state": {"oper-status": "DOWN"}}, + ] + } + } + inmemory_cache._data["openconfig-interfaces"] = data + self.assertEqual(inmemory_cache.get("openconfig-interfaces"), data) + + def test_get_not_exist(self): + inmemory_cache = InMemoryCache() + with self.assertRaises(CacheDataNotExistError): + inmemory_cache.get("openconfig-interfaces") + + def test_set(self): + inmemory_cache = InMemoryCache() + data = { + "interfaces": { + "interface": [ + {"name": "Ethernet1/0/1", "state": {"oper-status": "UP"}}, + {"name": "Ethernet1/0/2", "state": {"oper-status": "DOWN"}}, + ] + } + } + inmemory_cache.set("openconfig-interfaces", data) + self.assertEqual(inmemory_cache._data["openconfig-interfaces"], data) + + def test_set_exist(self): + inmemory_cache = InMemoryCache() + data = { + "interfaces": { + "interface": [ + {"name": "Ethernet1/0/1", "state": {"oper-status": "UP"}}, + {"name": "Ethernet1/0/2", "state": {"oper-status": "DOWN"}}, + ] + } + } + inmemory_cache._data["openconfig-interfaces"] = data + updated_data = { + "interfaces": { + "interface": [ + {"name": "Ethernet1/0/1", "state": {"oper-status": "DOWN"}}, + {"name": "Ethernet1/0/2", "state": {"oper-status": "UP"}}, + ] + } + } + inmemory_cache.set("openconfig-interfaces", updated_data) + self.assertEqual(inmemory_cache._data["openconfig-interfaces"], updated_data) + + +class TestCacheUpdater(XlateTestCase): + """Tests for CacheUpdater.""" + + MOCK_MODULES = [ + "goldstone-interfaces", + "goldstone-platform", + "goldstone-transponder", + "goldstone-gearbox", + "goldstone-system", + ] + CACHE_DATASTORE = InMemoryCache + CACHE_UPDATER = CacheUpdater + WAIT_MOCK = 2 + + async def test_create_cache_interfaces(self): + mock_data_interfaces = { + "interfaces": { + "interface": [ + { + "name": "Ethernet1/0/1", + "state": { + "name": "Ethernet1/0/1", + "description": "Ethernet interface.", + "admin-status": "UP", + "oper-status": "UP", + "counters": { + "in-octets": 1000, + "in-unicast-pkts": 100, + "in-broadcast-pkts": 200, + "in-multicast-pkts": 300, + "in-discards": 400, + "in-errors": 500, + "in-unknown-protos": 600, + "out-octets": 2000, + "out-unicast-pkts": 100, + "out-broadcast-pkts": 200, + "out-multicast-pkts": 300, + "out-discards": 400, + "out-errors": 500, + }, + }, + "ethernet": { + "state": { + "mtu": 10000, + "fec": "RS", + } + }, + "component-connection": {"platform": {"component": "port1"}}, + }, + { + "name": "Ethernet1/0/2", + "state": { + "name": "Ethernet1/0/2", + "description": "Ethernet interface.", + "admin-status": "DOWN", + "oper-status": "DOWN", + }, + "ethernet": { + "state": { + "mtu": 10000, + "fec": "NONE", + } + }, + "component-connection": {"platform": {"component": "port2"}}, + }, + { + "name": "Ethernet1/1/1", + "state": { + "name": "Ethernet1/1/1", + }, + "component-connection": { + "transponder": { + "module": "piu1", + "host-interface": "1", + } + }, + }, + { + "name": "Ethernet1/1/2", + "state": { + "name": "Ethernet1/1/2", + }, + "component-connection": { + "transponder": { + "module": "piu1", + "host-interface": "2", + } + }, + }, + ] + } + } + self.set_mock_oper_data("goldstone-interfaces", mock_data_interfaces) + mock_data_platform = { + "components": { + "component": [ + { + "name": "port1", + "state": { + "name": "port1", + "type": "TRANSCEIVER", + }, + "transceiver": { + "state": { + "presence": "PRESENT", + } + }, + }, + { + "name": "port2", + "state": { + "name": "port2", + "type": "TRANSCEIVER", + }, + "transceiver": { + "state": { + "presence": "PRESENT", + } + }, + }, + ] + } + } + self.set_mock_oper_data("goldstone-platform", mock_data_platform) + mock_data_transponder = { + "modules": { + "module": [ + { + "name": "piu1", + "state": { + "name": "piu1", + "localtion": "1", + }, + "network-interface": [ + { + "name": "1", + "state": { + "name": "1", + "line-rate": "400g", + "modulation-format": "dp-16-qam", + "fec-type": "ofec", + "client-signal-mapping-type": "flexo-lr", + "current-pre-fec-ber": "OiIFOA==", + "current-ber-period": 1000000, + }, + }, + ], + "host-interface": [ + { + "name": "1", + "state": { + "name": "1", + "index": 1, + "signal-rate": "100-gbe", + }, + }, + { + "name": "2", + "state": { + "name": "2", + "index": 2, + "signal-rate": "100-gbe", + }, + }, + ], + } + ] + } + } + self.set_mock_oper_data("goldstone-transponder", mock_data_transponder) + + mock_data_gearbox = { + "gearboxes": { + "gearbox": [ + { + "name": "1", + "state": { + "name": "1", + }, + "connections": { + "connection": [ + { + "client-interface": "Ethernet1/0/1", + "line-interface": "Ethernet1/1/1", + }, + { + "client-interface": "Ethernet1/0/2", + "line-interface": "Ethernet1/1/2", + }, + ] + }, + } + ] + } + } + self.set_mock_oper_data("goldstone-gearbox", mock_data_gearbox) + + def test(): + time.sleep(self.WAIT_MOCK) # wait for the mock server and cache updating + data = self.cache_updater._cache.get("openconfig-interfaces") + expected = { + "interfaces": { + "interface": [ + { + "name": "Ethernet1/0/1", + "state": { + "name": "Ethernet1/0/1", + "type": "iana-if-type:ethernetCsmacd", + "mtu": 10000, + "description": "Ethernet interface.", + "enabled": True, + "admin-status": "UP", + "oper-status": "UP", + "counters": { + "in-octets": 1000, + "in-pkts": 2100, + "in-unicast-pkts": 100, + "in-broadcast-pkts": 200, + "in-multicast-pkts": 300, + "in-discards": 400, + "in-errors": 500, + "in-unknown-protos": 600, + "out-octets": 2000, + "out-pkts": 1500, + "out-unicast-pkts": 100, + "out-broadcast-pkts": 200, + "out-multicast-pkts": 300, + "out-discards": 400, + "out-errors": 500, + }, + "hardware-port": "client-port1", + }, + "ethernet": { + "state": { + "fec-mode": "FEC_RS528", + }, + }, + }, + { + "name": "Ethernet1/0/2", + "state": { + "name": "Ethernet1/0/2", + "type": "iana-if-type:ethernetCsmacd", + "mtu": 10000, + "description": "Ethernet interface.", + "enabled": False, + "admin-status": "DOWN", + "oper-status": "DOWN", + "hardware-port": "client-port2", + }, + "ethernet": { + "state": { + "fec-mode": "FEC_DISABLED", + }, + }, + }, + { + "name": "Ethernet1/1/1", + "state": { + "name": "Ethernet1/1/1", + "type": "iana-if-type:ethernetCsmacd", + }, + }, + { + "name": "Ethernet1/1/2", + "state": { + "name": "Ethernet1/1/2", + "type": "iana-if-type:ethernetCsmacd", + }, + }, + ] + } + } + self.assertEqual(data, expected) + + await self.run_xlate_test(test) + + async def test_create_cache_platform(self): + mock_data_platform = { + "components": { + "component": [ + { + "name": "SYS", + "state": { + "type": "SYS", + "id": 1, + "description": "System Information", + }, + "sys": { + "state": { + "onie-info": { + "manufacturer": "Manufacturer", + "serial-number": "Serial number", + "part-number": "Part number", + } + } + }, + }, + { + "name": "THERMAL SENSOR1", + "state": {"type": "THERMAL"}, + "thermal": {"state": {"temperature": 10000}}, + }, + { + "name": "THERMAL SENSOR2", + "state": {"type": "THERMAL"}, + "thermal": {"state": {"temperature": 20000}}, + }, + { + "name": "port1", + "state": { + "name": "port1", + "type": "TRANSCEIVER", + "id": 200, + "description": "QSFP-28 transceiver information.", + }, + "transceiver": { + "state": { + "presence": "PRESENT", + "vendor": "Vendor Name", + "serial": "Serial number", + "model": "Model number", + } + }, + }, + { + "name": "fan", + "state": { + "name": "fan", + "type": "FAN", + "id": 300, + "description": "Fan information.", + }, + "fan": {"state": {"fan-state": "PRESENT", "status": "RUNNING"}}, + }, + { + "name": "power supply", + "state": { + "name": "power supply", + "type": "PSU", + "id": 400, + "description": "PSU information.", + }, + "psu": { + "state": { + "psu-state": "PRESENT", + "status": "RUNNING", + "serial": "Serial number", + "model": "Model number", + "output-power": 50000, + } + }, + }, + ] + } + } + self.set_mock_oper_data("goldstone-platform", mock_data_platform) + mock_data_system = { + "system": {"state": {"software-version": "Software version"}} + } + self.set_mock_oper_data("goldstone-system", mock_data_system) + mock_data_interface = { + "interfaces": { + "interface": [ + { + "name": "Ethernet1/0/1", + "state": { + "name": "Ethernet1/0/1", + "oper-status": "UP", + "admin-status": "UP", + }, + "component-connection": {"platform": {"component": "port1"}}, + }, + { + "name": "Ethernet1/1/1", + "state": { + "name": "Ethernet1/1/1", + }, + "component-connection": { + "transponder": { + "module": "piu1", + "host-interface": "1", + } + }, + }, + ] + } + } + self.set_mock_oper_data("goldstone-interfaces", mock_data_interface) + mock_data_transponder = { + "modules": { + "module": [ + { + "name": "piu1", + "state": { + "name": "piu1", + "oper-status": "ready", + "id": 100, + "description": "CFP2-DCO module information.", + "vendor-name": "Vendor Name", + "firmware-version": "Firmware Version", + "vendor-serial-number": "Vendor Serial number", + "vendor-part-number": "Vendor Part number", + "location": "1", + "temp": 1.3372036854775807, + "admin-status": "up", + }, + "network-interface": [ + { + "name": "1", + "state": { + "name": "1", + "oper-status": "ready", + "id": 10, + "description": "CFP2-DCO network-interface information.", + "index": 1, + "current-chromatic-dispersion": 2000, + "current-input-power": 1.3372036854775807, + "current-output-power": 2.3372036854775807, + "tx-laser-freq": 100000000, + "output-power": 3.3372036854775807, + "line-rate": "100g", + "modulation-format": "dp-qpsk", + "fec-type": "sc-fec", + "client-signal-mapping-type": "otu4-lr", + }, + } + ], + "host-interface": [ + { + "name": "1", + "state": { + "name": "1", + "index": 1, + "signal-rate": "100-gbe", + }, + }, + ], + } + ] + } + } + self.set_mock_oper_data("goldstone-transponder", mock_data_transponder) + mock_data_gearbox = { + "gearboxes": { + "gearbox": [ + { + "name": "1", + "state": { + "name": "1", + }, + "connections": { + "connection": [ + { + "client-interface": "Ethernet1/0/1", + "line-interface": "Ethernet1/1/1", + }, + ] + }, + } + ] + } + } + self.set_mock_oper_data("goldstone-gearbox", mock_data_gearbox) + + def test(): + time.sleep(self.WAIT_MOCK) # wait for the mock server and cache updating + data = self.cache_updater._cache.get("openconfig-platform") + expected = { + "components": { + "component": [ + { + "name": "CHASSIS", + "state": { + "name": "CHASSIS", + "type": "openconfig-platform-types:CHASSIS", + "oper-status": "openconfig-platform-types:ACTIVE", + "id": "1", + "description": "System Information", + "mfg-name": "Manufacturer", + "software-version": "Software version", + "serial-no": "Serial number", + "part-no": "Part number", + "temperature": {"instant": 10.0}, + "removable": False, + }, + "subcomponents": { + "subcomponent": [ + { + "name": "line-piu1", + "state": {"name": "line-piu1"}, + }, + { + "name": "client-port1", + "state": {"name": "client-port1"}, + }, + {"name": "fan", "state": {"name": "fan"}}, + { + "name": "power supply", + "state": {"name": "power supply"}, + }, + ] + }, + }, + { + "name": "line-piu1", + "state": { + "name": "line-piu1", + "type": "openconfig-platform-types:PORT", + "oper-status": "openconfig-platform-types:ACTIVE", + "id": "100", + "description": "CFP2-DCO module information.", + "location": "1", + "parent": "CHASSIS", + "removable": False, + }, + "subcomponents": { + "subcomponent": [ + { + "name": "transceiver-line-piu1", + "state": {"name": "transceiver-line-piu1"}, + } + ] + }, + "port": { + "optical-port": { + "state": { + "admin-state": "ENABLED", + "optical-port-type": "openconfig-transport-types:TERMINAL_LINE", + } + } + }, + }, + { + "name": "transceiver-line-piu1", + "state": { + "name": "transceiver-line-piu1", + "type": "openconfig-platform-types:TRANSCEIVER", + "oper-status": "openconfig-platform-types:ACTIVE", + "id": "100", + "description": "CFP2-DCO module information.", + "mfg-name": "Vendor Name", + "software-version": "Firmware Version", + "serial-no": "Vendor Serial number", + "part-no": "Vendor Part number", + "location": "1", + "parent": "line-piu1", + "temperature": {"instant": 1.3}, + "removable": True, + }, + "subcomponents": { + "subcomponent": [ + { + "name": "och-transceiver-line-piu1-1", + "state": { + "name": "och-transceiver-line-piu1-1" + }, + } + ] + }, + }, + { + "name": "och-transceiver-line-piu1-1", + "state": { + "name": "och-transceiver-line-piu1-1", + "type": "openconfig-transport-types:OPTICAL_CHANNEL", + "oper-status": "openconfig-platform-types:ACTIVE", + "id": "10", + "description": "CFP2-DCO network-interface information.", + "location": "1", + "parent": "transceiver-line-piu1", + "removable": False, + }, + "optical-channel": { + "state": { + "chromatic-dispersion": {"instant": 2000.0}, + "input-power": {"instant": 1.34}, + "output-power": {"instant": 2.34}, + "frequency": 100, + "target-output-power": 3.34, + "operational-mode": 100, + } + }, + "properties": { + "property": [ + { + "name": "CROSS_CONNECTION", + "state": { + "name": "CROSS_CONNECTION", + "value": "PRESET", + }, + }, + { + "name": "latency", + "state": {"name": "latency", "value": None}, + }, + ] + }, + }, + { + "name": "client-port1", + "state": { + "name": "client-port1", + "type": "openconfig-platform-types:PORT", + "oper-status": "openconfig-platform-types:ACTIVE", + "id": "200", + "description": "QSFP-28 transceiver information.", + "parent": "CHASSIS", + "removable": False, + }, + "subcomponents": { + "subcomponent": [ + { + "name": "transceiver-client-port1", + "state": {"name": "transceiver-client-port1"}, + } + ] + }, + "port": { + "optical-port": { + "state": { + "admin-state": "ENABLED", + "optical-port-type": "openconfig-transport-types:TERMINAL_CLIENT", + } + } + }, + }, + { + "name": "transceiver-client-port1", + "state": { + "name": "transceiver-client-port1", + "type": "openconfig-platform-types:TRANSCEIVER", + "oper-status": "openconfig-platform-types:ACTIVE", + "id": "200", + "description": "QSFP-28 transceiver information.", + "mfg-name": "Vendor Name", + "serial-no": "Serial number", + "part-no": "Model number", + "parent": "client-port1", + "removable": True, + }, + }, + { + "name": "fan", + "state": { + "name": "fan", + "type": "openconfig-platform-types:FAN", + "oper-status": "openconfig-platform-types:ACTIVE", + "id": "300", + "description": "Fan information.", + "parent": "CHASSIS", + }, + }, + { + "name": "power supply", + "state": { + "name": "power supply", + "type": "openconfig-platform-types:POWER_SUPPLY", + "oper-status": "openconfig-platform-types:ACTIVE", + "id": "400", + "description": "PSU information.", + "serial-no": "Serial number", + "part-no": "Model number", + "parent": "CHASSIS", + "used-power": 50, + }, + }, + ] + } + } + self.assertEqual(data, expected) + + await self.run_xlate_test(test) + + async def test_create_cache_terminal_device(self): + mock_data_interface = { + "interfaces": { + "interface": [ + { + "name": "Ethernet1/0/1", + "state": { + "name": "Ethernet1/0/1", + }, + "component-connection": {"platform": {"component": "port1"}}, + }, + { + "name": "Ethernet1/0/2", + "state": { + "name": "Ethernet1/0/2", + }, + "component-connection": {"platform": {"component": "port2"}}, + }, + { + "name": "Ethernet1/0/3", + "state": { + "name": "Ethernet1/0/3", + }, + "component-connection": {"platform": {"component": "port3"}}, + }, + { + "name": "Ethernet1/0/4", + "state": { + "name": "Ethernet1/0/4", + }, + "component-connection": {"platform": {"component": "port4"}}, + }, + { + "name": "Ethernet1/1/1", + "state": { + "name": "Ethernet1/1/1", + }, + "component-connection": { + "transponder": { + "module": "piu1", + "host-interface": "1", + } + }, + }, + { + "name": "Ethernet1/1/2", + "state": { + "name": "Ethernet1/1/2", + }, + "component-connection": { + "transponder": { + "module": "piu1", + "host-interface": "2", + } + }, + }, + { + "name": "Ethernet1/1/3", + "state": { + "name": "Ethernet1/1/3", + }, + "component-connection": { + "transponder": { + "module": "piu1", + "host-interface": "3", + } + }, + }, + { + "name": "Ethernet1/1/4", + "state": { + "name": "Ethernet1/1/4", + }, + "component-connection": { + "transponder": { + "module": "piu1", + "host-interface": "4", + } + }, + }, + ] + } + } + self.set_mock_oper_data("goldstone-interfaces", mock_data_interface) + mock_data_platform = { + "components": { + "component": [ + { + "name": "port1", + "state": { + "name": "port1", + "type": "TRANSCEIVER", + }, + "transceiver": { + "state": { + "presence": "PRESENT", + } + }, + }, + { + "name": "port2", + "state": { + "name": "port2", + "type": "TRANSCEIVER", + }, + "transceiver": { + "state": { + "presence": "PRESENT", + } + }, + }, + { + "name": "port3", + "state": { + "name": "port3", + "type": "TRANSCEIVER", + }, + "transceiver": { + "state": { + "presence": "PRESENT", + } + }, + }, + { + "name": "port4", + "state": { + "name": "port4", + "type": "TRANSCEIVER", + }, + "transceiver": { + "state": { + "presence": "PRESENT", + } + }, + }, + ] + } + } + self.set_mock_oper_data("goldstone-platform", mock_data_platform) + mock_data_transponder = { + "modules": { + "module": [ + { + "name": "piu1", + "state": { + "name": "piu1", + "localtion": "1", + }, + "network-interface": [ + { + "name": "1", + "state": { + "name": "1", + "line-rate": "400g", + "modulation-format": "dp-16-qam", + "fec-type": "ofec", + "client-signal-mapping-type": "flexo-lr", + "current-pre-fec-ber": "OiIFOA==", + "current-ber-period": 1000000, + }, + }, + ], + "host-interface": [ + { + "name": "1", + "state": { + "name": "1", + "index": 1, + "signal-rate": "100-gbe", + }, + }, + { + "name": "2", + "state": { + "name": "2", + "index": 2, + "signal-rate": "100-gbe", + }, + }, + { + "name": "3", + "state": { + "name": "3", + "index": 3, + "signal-rate": "100-gbe", + }, + }, + { + "name": "4", + "state": { + "name": "4", + "index": 4, + "signal-rate": "100-gbe", + }, + }, + ], + } + ] + } + } + self.set_mock_oper_data("goldstone-transponder", mock_data_transponder) + mock_data_gearbox = { + "gearboxes": { + "gearbox": [ + { + "name": "1", + "state": { + "name": "1", + }, + "connections": { + "connection": [ + { + "client-interface": "Ethernet1/0/1", + "line-interface": "Ethernet1/1/1", + }, + { + "client-interface": "Ethernet1/0/2", + "line-interface": "Ethernet1/1/2", + }, + { + "client-interface": "Ethernet1/0/3", + "line-interface": "Ethernet1/1/3", + }, + { + "client-interface": "Ethernet1/0/4", + "line-interface": "Ethernet1/1/4", + }, + ] + }, + } + ] + } + } + self.set_mock_oper_data("goldstone-gearbox", mock_data_gearbox) + + def test(): + time.sleep(self.WAIT_MOCK) # wait for the mock server and cache updating + data = self.cache_updater._cache.get("openconfig-terminal-device") + expected = { + "terminal-device": { + "logical-channels": { + "channel": [ + # Client signal for client-port1 + { + "index": 0, + "state": { + "index": 0, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_100G", + "trib-protocol": "openconfig-transport-types:PROT_100GE", + "logical-channel-type": "openconfig-transport-types:PROT_ETHERNET", + }, + "ingress": { + "state": { + "transceiver": "transceiver-client-port1", + }, + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "LOGICAL_CHANNEL", + "logical-channel": 1, + "mapping": "openconfig-transport-types:GMP", + "allocation": 100.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + # Lower order ODU for client-port1 + { + "index": 1, + "state": { + "index": 1, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_100G", + "trib-protocol": "openconfig-transport-types:PROT_ODU4", + "logical-channel-type": "openconfig-transport-types:PROT_OTN", + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "LOGICAL_CHANNEL", + "logical-channel": 8, + "mapping": "openconfig-transport-types:GMP", + "allocation": 100.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + # Client signal for client-port2 + { + "index": 2, + "state": { + "index": 2, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_100G", + "trib-protocol": "openconfig-transport-types:PROT_100GE", + "logical-channel-type": "openconfig-transport-types:PROT_ETHERNET", + }, + "ingress": { + "state": { + "transceiver": "transceiver-client-port2", + }, + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "LOGICAL_CHANNEL", + "logical-channel": 3, + "mapping": "openconfig-transport-types:GMP", + "allocation": 100.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + # Lower order ODU for client-port2 + { + "index": 3, + "state": { + "index": 3, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_100G", + "trib-protocol": "openconfig-transport-types:PROT_ODU4", + "logical-channel-type": "openconfig-transport-types:PROT_OTN", + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "LOGICAL_CHANNEL", + "logical-channel": 8, + "mapping": "openconfig-transport-types:GMP", + "allocation": 100.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + # Client signal for client-port3 + { + "index": 4, + "state": { + "index": 4, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_100G", + "trib-protocol": "openconfig-transport-types:PROT_100GE", + "logical-channel-type": "openconfig-transport-types:PROT_ETHERNET", + }, + "ingress": { + "state": { + "transceiver": "transceiver-client-port3", + }, + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "LOGICAL_CHANNEL", + "logical-channel": 5, + "mapping": "openconfig-transport-types:GMP", + "allocation": 100.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + # Lower order ODU for client-port3 + { + "index": 5, + "state": { + "index": 5, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_100G", + "trib-protocol": "openconfig-transport-types:PROT_ODU4", + "logical-channel-type": "openconfig-transport-types:PROT_OTN", + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "LOGICAL_CHANNEL", + "logical-channel": 8, + "mapping": "openconfig-transport-types:GMP", + "allocation": 100.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + # Client signal for client-port4 + { + "index": 6, + "state": { + "index": 6, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_100G", + "trib-protocol": "openconfig-transport-types:PROT_100GE", + "logical-channel-type": "openconfig-transport-types:PROT_ETHERNET", + }, + "ingress": { + "state": { + "transceiver": "transceiver-client-port4", + }, + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "LOGICAL_CHANNEL", + "logical-channel": 7, + "mapping": "openconfig-transport-types:GMP", + "allocation": 100.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + # Lower order ODU for client-port4 + { + "index": 7, + "state": { + "index": 7, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_100G", + "trib-protocol": "openconfig-transport-types:PROT_ODU4", + "logical-channel-type": "openconfig-transport-types:PROT_OTN", + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "LOGICAL_CHANNEL", + "logical-channel": 8, + "mapping": "openconfig-transport-types:GMP", + "allocation": 100.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + # Higher order ODU for och-transceiver-line-piu1-1 + { + "index": 8, + "state": { + "index": 8, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_400G", + "trib-protocol": "openconfig-transport-types:PROT_ODUCN", + "logical-channel-type": "openconfig-transport-types:PROT_OTN", + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "LOGICAL_CHANNEL", + "logical-channel": 9, + "mapping": "openconfig-transport-types:GMP", + "allocation": 400.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + # OTU for och-transceiver-line-piu1-1 + { + "index": 9, + "state": { + "index": 9, + "description": "", + "admin-state": "ENABLED", + "rate-class": "openconfig-transport-types:TRIB_RATE_400G", + "trib-protocol": "openconfig-transport-types:PROT_OTUCN", + "logical-channel-type": "openconfig-transport-types:PROT_OTN", + }, + "otn": { + "state": { + "tributary-slot-granularity": "openconfig-transport-types:TRIB_SLOT_5G", + "pre-fec-ber": { + "instant": "0.000618058722466230", + "interval": 1000000000, + }, + } + }, + "logical-channel-assignments": { + "assignment": [ + { + "index": 0, + "state": { + "index": 0, + "assignment-type": "OPTICAL_CHANNEL", + "optical-channel": "och-transceiver-line-piu1-1", + "mapping": "openconfig-transport-types:GMP", + "allocation": 400.000, + "tributary-slot-index": 0, + }, + } + ] + }, + }, + ] + }, + "operational-modes": { + "mode": [ + { + "mode-id": 100, + "state": { + "mode-id": 100, + "description": "100G, DP-QPSK, SC-FEC, OTU4-LR, 28.0GHz", + "vendor-id": "example vendor", + }, + }, + { + "mode-id": 101, + "state": { + "mode-id": 101, + "description": "100G, DP-QPSK, oFEC, FlexO-LR, 31.6GHz", + "vendor-id": "example vendor", + }, + }, + { + "mode-id": 200, + "state": { + "mode-id": 200, + "description": "200G, DP-16QAM, oFEC, FlexO-LR, 31.6GHz", + "vendor-id": "example vendor", + }, + }, + { + "mode-id": 201, + "state": { + "mode-id": 201, + "description": "200G, DP-QPSK, oFEC, FlexO-LR, 63.1GHz", + "vendor-id": "example vendor", + }, + }, + { + "mode-id": 300, + "state": { + "mode-id": 300, + "description": "300G, DP-8QAM, oFEC, FlexO-LR, 63.1GHz", + "vendor-id": "example vendor", + }, + }, + { + "mode-id": 400, + "state": { + "mode-id": 400, + "description": "400G, DP-16QAM, oFEC, FlexO-LR, 63.1GHz", + "vendor-id": "example vendor", + }, + }, + ] + }, + } + } + self.assertEqual(data, expected) + + await self.run_xlate_test(test) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/xlate/openconfig/tests/test_interface.py b/src/xlate/openconfig/tests/test_interface.py index 31ff5210..0d0b6062 100644 --- a/src/xlate/openconfig/tests/test_interface.py +++ b/src/xlate/openconfig/tests/test_interface.py @@ -1,4 +1,4 @@ -"""Tests of OpenConfig translater for openconfig-interfaces.""" +"""Tests of OpenConfig translator for openconfig-interfaces.""" import unittest @@ -399,7 +399,7 @@ def tearDown(self): def test_set_interface_not_configured(self): # Target interface has not been configured. - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/config/enabled" value = True change = Change(sysrepo.ChangeCreated(xpath, value)) @@ -440,7 +440,7 @@ def test_set_interface_configured(self): ) self.conn.apply() - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/config/enabled" value = True change = Change(sysrepo.ChangeCreated(xpath, value)) @@ -483,7 +483,7 @@ def test_set_item_configured(self): ) self.conn.apply() - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/config/enabled" value = True change = Change(sysrepo.ChangeCreated(xpath, value)) @@ -516,7 +516,7 @@ def test_set_item_configured(self): def test_delete_interface_not_configured(self): # Target interface has not been configured. - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/config/enabled" change = Change(sysrepo.ChangeDeleted(xpath)) user = { @@ -553,7 +553,7 @@ def test_delete_interface_configured(self): ) self.conn.apply() - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/config/enabled" change = Change(sysrepo.ChangeDeleted(xpath)) user = { @@ -592,7 +592,7 @@ def test_delete_item_configured(self): ) self.conn.apply() - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/config/enabled" change = Change(sysrepo.ChangeDeleted(xpath)) user = { @@ -641,7 +641,7 @@ def tearDown(self): def test_set_interface_not_configured(self): # Target interface has not been configured. - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/ethernet/config/fec-mode" value = "FEC_FC" change = Change(sysrepo.ChangeCreated(xpath, value)) @@ -682,7 +682,7 @@ def test_set_interface_configured(self): ) self.conn.apply() - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/ethernet/config/fec-mode" value = "FEC_FC" change = Change(sysrepo.ChangeCreated(xpath, value)) @@ -725,7 +725,7 @@ def test_set_item_configured(self): ) self.conn.apply() - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/ethernet/config/fec-mode" value = "FEC_FC" change = Change(sysrepo.ChangeCreated(xpath, value)) @@ -758,7 +758,7 @@ def test_set_item_configured(self): def test_delete_interface_not_configured(self): # Target interface has not been configured. - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/ethernet/config/fec-mode" change = Change(sysrepo.ChangeDeleted(xpath)) user = { @@ -795,7 +795,7 @@ def test_delete_interface_configured(self): ) self.conn.apply() - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/ethernet/config/fec-mode" change = Change(sysrepo.ChangeDeleted(xpath)) user = { @@ -834,7 +834,7 @@ def test_delete_item_configured(self): ) self.conn.apply() - server = InterfaceServer(self.conn) + server = InterfaceServer(self.conn, None) xpath = "/openconfig-interfaces:interfaces/interface[name='Ethernet1/0/1']/ethernet/config/fec-mode" change = Change(sysrepo.ChangeDeleted(xpath)) user = { diff --git a/src/xlate/openconfig/tests/test_platform.py b/src/xlate/openconfig/tests/test_platform.py index f0ad59dd..13dd4f8f 100644 --- a/src/xlate/openconfig/tests/test_platform.py +++ b/src/xlate/openconfig/tests/test_platform.py @@ -1,4 +1,4 @@ -"""Tests of OpenConfig translater for openconfig-platform.""" +"""Tests of OpenConfig translator for openconfig-platform.""" import unittest @@ -2853,7 +2853,7 @@ def tearDown(self): # Test for terminal line port. def test_set_terminal_line_module_not_configured(self): # Target module has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='line-piu1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -2899,7 +2899,7 @@ def test_set_terminal_line_module_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='line-piu1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -2950,7 +2950,7 @@ def test_set_terminal_line_item_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='line-piu1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -2988,7 +2988,7 @@ def test_set_terminal_line_item_configured(self): def test_delete_terminal_line_module_not_configured(self): # Target module has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='line-piu1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -3033,7 +3033,7 @@ def test_delete_terminal_line_module_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='line-piu1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -3083,7 +3083,7 @@ def test_delete_terminal_line_item_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='line-piu1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -3199,7 +3199,7 @@ def join_mock_servers(self): def test_set_terminal_client_interface_not_configured(self): # Target interface has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='client-port1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -3258,7 +3258,7 @@ def test_set_terminal_client_interface_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='client-port1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -3326,7 +3326,7 @@ def test_set_terminal_client_item_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='client-port1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -3376,7 +3376,7 @@ def test_set_terminal_client_item_configured(self): def test_delete_terminal_client_interface_not_configured(self): # Target interface has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='client-port1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -3429,7 +3429,7 @@ def test_delete_terminal_client_interface_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='client-port1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -3490,7 +3490,7 @@ def test_delete_terminal_client_item_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='client-port1']/port/" "openconfig-transport-line-common:optical-port/config/admin-state" @@ -3553,7 +3553,7 @@ def tearDown(self): def test_set_module_not_configured(self): # Target module has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/frequency" @@ -3610,7 +3610,7 @@ def test_set_module_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/frequency" @@ -3673,7 +3673,7 @@ def test_set_netif_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/frequency" @@ -3742,7 +3742,7 @@ def test_set_item_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/frequency" @@ -3796,7 +3796,7 @@ def test_set_item_configured(self): def test_delete_module_not_configured(self): # Target module has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/frequency" @@ -3843,7 +3843,7 @@ def test_delete_module_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/frequency" @@ -3896,7 +3896,7 @@ def test_delete_netif_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/frequency" @@ -3955,7 +3955,7 @@ def test_delete_item_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/frequency" @@ -4018,7 +4018,7 @@ def tearDown(self): def test_set_module_not_configured(self): # Target module has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/target-output-power" @@ -4075,7 +4075,7 @@ def test_set_module_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/target-output-power" @@ -4138,7 +4138,7 @@ def test_set_netif_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/target-output-power" @@ -4207,7 +4207,7 @@ def test_set_item_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/target-output-power" @@ -4260,7 +4260,7 @@ def test_set_item_configured(self): def test_delete_module_not_configured(self): # Target module has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/target-output-power" @@ -4307,7 +4307,7 @@ def test_delete_module_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/target-output-power" @@ -4360,7 +4360,7 @@ def test_delete_netif_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/target-output-power" @@ -4419,7 +4419,7 @@ def test_delete_item_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/target-output-power" @@ -4482,7 +4482,7 @@ def tearDown(self): def test_set_module_not_configured(self): # Target module has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/operational-mode" @@ -4561,7 +4561,7 @@ def test_set_module_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/operational-mode" @@ -4646,7 +4646,7 @@ def test_set_netif_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/operational-mode" @@ -4752,7 +4752,7 @@ def test_set_items_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/operational-mode" @@ -4843,7 +4843,7 @@ def test_set_items_configured(self): def test_delete_module_not_configured(self): # Target module has not been configured. - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/operational-mode" @@ -4898,7 +4898,7 @@ def test_delete_module_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/operational-mode" @@ -4959,7 +4959,7 @@ def test_delete_netif_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/operational-mode" @@ -5041,7 +5041,7 @@ def test_delete_items_configured(self): ) self.conn.apply() - server = PlatformServer(self.conn, operational_modes) + server = PlatformServer(self.conn, None, operational_modes) xpath = ( "/openconfig-platform:components/component[name='och-transceiver-line-piu1-1']/" "openconfig-terminal-device:optical-channel/config/operational-mode" diff --git a/src/xlate/openconfig/tests/test_terminal_device.py b/src/xlate/openconfig/tests/test_terminal_device.py index cced98b1..298a98f5 100644 --- a/src/xlate/openconfig/tests/test_terminal_device.py +++ b/src/xlate/openconfig/tests/test_terminal_device.py @@ -1,4 +1,4 @@ -"""Tests of OpenConfig translater for openconfig-terminal-device.""" +"""Tests of OpenConfig translator for openconfig-terminal-device.""" import unittest @@ -3120,7 +3120,6 @@ def test_create_4x100gbe_client_400g_line_flexo_no_transponder(self): }, }, ] - self.maxDiff = None self.assertEqual(data, expected) def test_create_twice(self):