From 87394fca3fb9abcc27c4497c0466d39a1613e792 Mon Sep 17 00:00:00 2001 From: jtdub Date: Wed, 12 Aug 2020 18:09:24 -0500 Subject: [PATCH 1/7] add ntc-rosetta functionality --- netnir/__init__.py | 2 +- netnir/core/tasks/config_plan.py | 36 +++++++++++- netnir/helpers/common/args.py | 20 +++++++ netnir/plugins/rosetta.py | 55 +++++++++++++++++++ setup.py | 1 + .../core/tasks/test_tasks_config_plan.py | 4 ++ 6 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 netnir/plugins/rosetta.py diff --git a/netnir/__init__.py b/netnir/__init__.py index 48f70d6..b736448 100644 --- a/netnir/__init__.py +++ b/netnir/__init__.py @@ -3,4 +3,4 @@ doesn't exist, netnir will create the default config and folders. """ -__version__ = "0.0.20" +__version__ = "0.0.21" diff --git a/netnir/core/tasks/config_plan.py b/netnir/core/tasks/config_plan.py index 29810ea..81f7506 100644 --- a/netnir/core/tasks/config_plan.py +++ b/netnir/core/tasks/config_plan.py @@ -1,8 +1,10 @@ from netnir.helpers.scaffold.command import CommandScaffold from netnir.plugins.template import template_file from netnir.plugins.netmiko import netmiko_send_commands +from netnir.plugins.rosetta import convert_config_to_yang, convert_yang_to_config from netnir.plugins.hier import hier_host from netnir.helpers import output_writer +from netnir.helpers.common.args import config_to_yang, yang_to_config from netnir.constants import OUTPUT_DIR from nornir.plugins.functions.text import print_result @@ -41,6 +43,8 @@ def parser(parser): help="hier_config exclude tags", required=False, ) + config_to_yang(parser) + yang_to_config(parser) def run(self): """ @@ -51,6 +55,33 @@ def run(self): :returns: result string """ self.nr = self._inventory() + + if self.args.config_to_yang: + results = self.nr.run( + task=convert_config_to_yang, + config_file=self.args.config_to_yang, + name="CONVERT CONFIGURATION TEXT TO YANG MODEL", + num_workers=self.args.workers, + dry_run=self.args.X, + severity_level=self._verbose()["level"], + to_console=self._verbose()["to_console"], + ) + print_result(result=results, severity_level=self._verbose()["level"]) + return results + + if self.args.yang_to_config: + results = self.nr.run( + task=convert_yang_to_config, + yang_model=self.args.yang_to_config, + name="CONVERT YANG MODEL TO CONFIGURATION TEXT", + num_workers=self.args.workers, + dry_run=self.args.X, + severity_level=self._verbose()["level"], + to_console=self._verbose()["to_console"], + ) + print_result(result=results, severity_level=self._verbose()["level"]) + return results + results = self.nr.run( task=template_file, template_file="main.conf.j2", @@ -62,7 +93,7 @@ def run(self): to_console=self._verbose()["to_console"], ) output_writer(nornir_results=results, output_file="compiled.conf") - print_result(results) + print_result(result=results, severity_level=self._verbose()["level"]) if self.args.compile: return results @@ -77,7 +108,7 @@ def run(self): to_console=self._verbose()["to_console"], ) output_writer(nornir_results=results, output_file="running.conf") - print_result(results) + print_result(result=results, severity_level=self._verbose()["level"]) results = self.nr.run( task=hier_host, @@ -94,7 +125,6 @@ def run(self): to_console=self._verbose()["to_console"], ) output_writer(nornir_results=results, output_file="remediation.conf") - print_result(result=results, severity_level=self._verbose()["level"]) return results diff --git a/netnir/helpers/common/args.py b/netnir/helpers/common/args.py index 3963634..0ed10a2 100644 --- a/netnir/helpers/common/args.py +++ b/netnir/helpers/common/args.py @@ -174,3 +174,23 @@ def netconf_capabilities(parser, required: bool = False): nargs="?", const=True, ) + + +def config_to_yang(parser, required: bool = False) -> None: + """ + common argument to convert configuration text to a yang model via the + --config-to-yang flag. + """ + parser.add_argument( + "--config-to-yang", help="convert configuration text to a yang model", + ) + + +def yang_to_config(parser, required: bool = False) -> None: + """ + common argument to convert yang model to configuration text via the + --yang-to-config flag. + """ + parser.add_argument( + "--yang-to-config", help="convert configuration text to a yang model", + ) diff --git a/netnir/plugins/rosetta.py b/netnir/plugins/rosetta.py new file mode 100644 index 0000000..0f4be1a --- /dev/null +++ b/netnir/plugins/rosetta.py @@ -0,0 +1,55 @@ +from nornir.core.task import Task, Result +from typing import Any + + +def convert_config_to_yang( + task: Task, config_file: str = None, **kwargs: Any +) -> Result: + """ + convert a config string into a yang model + + :param task: nornir task object + :param config_file: configuration file from OUTPUT_DIR + + :returns: nornir result object + """ + from netnir.constants import OUTPUT_DIR + from ntc_rosetta import get_driver + import json + + config_path = "/".join([OUTPUT_DIR, task.host.name, config_file]) + + with open(config_path) as f: + config_string = f.read() + + os_driver = get_driver(task.host.platform, "openconfig") + driver = os_driver() + parsed = driver.parse(native={"dev_conf": config_string}) + + return Result(host=task.host, result=json.dumps(parsed.raw_value(), indent=2)) + + +def convert_yang_to_config( + task: Task, yang_model: dict = None, **kwargs: Any +) -> Result: + """ + convert a yang model to configuration text + + :param task: nornir task object + :param yang_model: type dict + + :returns: nornir result object + """ + from ntc_rosetta import get_driver + from netnir.constants import OUTPUT_DIR + + yang_path = "/".join([OUTPUT_DIR, task.host.name, yang_model]) + + with open(yang_path) as f: + yang_data = f.read() + + os_driver = get_driver(task.host.platform, "openconfig") + driver = os_driver() + config_text = driver.translate(candidate=yang_data) + + return Result(host=task.host, result=config_text) diff --git a/setup.py b/setup.py index 2f0fcc1..01780e6 100755 --- a/setup.py +++ b/setup.py @@ -17,6 +17,7 @@ "hier_config==v1.6.1", "keyring>=21.2.1", "keyrings.alt>=3.4.0", + "ntc-rosetta>=v0.2.0", ], tests_require=[ "pytest", diff --git a/tests/netnir/core/tasks/test_tasks_config_plan.py b/tests/netnir/core/tasks/test_tasks_config_plan.py index 9da10b9..3ec993e 100644 --- a/tests/netnir/core/tasks/test_tasks_config_plan.py +++ b/tests/netnir/core/tasks/test_tasks_config_plan.py @@ -8,6 +8,8 @@ def test_tasks_config_plan(): verbose, num_workers, make_changes, + config_to_yang, + yang_to_config, ) import argparse @@ -19,6 +21,8 @@ def test_tasks_config_plan(): verbose(parser) num_workers(parser) make_changes(parser) + config_to_yang(parser) + yang_to_config(parser) parser.add_argument("--compile", const=True, nargs="?") parser.add_argument("--include-tags", action="append") parser.add_argument("--exclude-tags", action="append") From 4de5fc5b22cb5b4227f734928d187716509288bf Mon Sep 17 00:00:00 2001 From: jtdub Date: Thu, 13 Aug 2020 10:05:00 -0500 Subject: [PATCH 2/7] refactor rosetta plugin and move some code around --- netnir/helpers/defaults.py | 14 +++--- netnir/plugins/rosetta.py | 46 +++++++------------ netnir/{core => plugins}/tasks/__init__.py | 0 netnir/{core => plugins}/tasks/config_plan.py | 31 ------------- .../{core => plugins}/tasks/fetch/__init__.py | 2 +- .../{core => plugins}/tasks/fetch/config.py | 0 netnir/{core => plugins}/tasks/inventory.py | 0 netnir/{core => plugins}/tasks/netconf.py | 0 netnir/{core => plugins}/tasks/ssh.py | 0 netnir/{core => plugins}/tasks/user.py | 0 tests/data/netnir.yaml | 12 ++--- .../core/tasks/test_tasks_config_plan.py | 2 +- .../netnir/core/tasks/test_tasks_inventory.py | 2 +- tests/netnir/helpers/test_netnir_config.py | 12 ++--- 14 files changed, 38 insertions(+), 83 deletions(-) rename netnir/{core => plugins}/tasks/__init__.py (100%) rename netnir/{core => plugins}/tasks/config_plan.py (70%) rename netnir/{core => plugins}/tasks/fetch/__init__.py (86%) rename netnir/{core => plugins}/tasks/fetch/config.py (100%) rename netnir/{core => plugins}/tasks/inventory.py (100%) rename netnir/{core => plugins}/tasks/netconf.py (100%) rename netnir/{core => plugins}/tasks/ssh.py (100%) rename netnir/{core => plugins}/tasks/user.py (100%) diff --git a/netnir/helpers/defaults.py b/netnir/helpers/defaults.py index a4edc28..5fde4d1 100644 --- a/netnir/helpers/defaults.py +++ b/netnir/helpers/defaults.py @@ -6,31 +6,31 @@ "output": "./output", "hier": "./conf/hier", }, - "domain": "example.net", + "domain": None, "nornir": {"config": "./conf/nornir.yaml"}, "plugins": { "user": { - "class": "netnir.core.tasks.user.User", + "class": "netnir.plugins.tasks.user.User", "description": "netnir user commands", }, "inventory": { - "class": "netnir.core.tasks.inventory.Inventory", + "class": "netnir.plugins.tasks.inventory.Inventory", "description": "inventory search command", }, "cp": { - "class": "netnir.core.tasks.config_plan.ConfigPlan", + "class": "netnir.plugins.tasks.config_plan.ConfigPlan", "description": "config plan commands", }, "ssh": { - "class": "netnir.core.tasks.ssh.Ssh", + "class": "netnir.plugins.tasks.ssh.Ssh", "description": "command and config execution over SSH", }, "netconf": { - "class": "netnir.core.tasks.netconf.NetConf", + "class": "netnir.plugins.tasks.netconf.NetConf", "description": "command and config execution over NETCONF", }, "fetch": { - "class": "netnir.core.tasks.fetch.Fetch", + "class": "netnir.plugins.tasks.fetch.Fetch", "description": "fetch commands", }, }, diff --git a/netnir/plugins/rosetta.py b/netnir/plugins/rosetta.py index 0f4be1a..47a3c1b 100644 --- a/netnir/plugins/rosetta.py +++ b/netnir/plugins/rosetta.py @@ -1,55 +1,41 @@ -from nornir.core.task import Task, Result -from typing import Any - - -def convert_config_to_yang( - task: Task, config_file: str = None, **kwargs: Any -) -> Result: +def convert_config_to_yang(operating_system: str, config_file: str): """ convert a config string into a yang model - :param task: nornir task object - :param config_file: configuration file from OUTPUT_DIR + :param operating_system: type str + :param config_file: configuration file from CWD - :returns: nornir result object + :returns: YANG output in JSON format """ - from netnir.constants import OUTPUT_DIR from ntc_rosetta import get_driver import json - config_path = "/".join([OUTPUT_DIR, task.host.name, config_file]) - - with open(config_path) as f: + with open(config_file) as f: config_string = f.read() - os_driver = get_driver(task.host.platform, "openconfig") + os_driver = get_driver(operating_system, "openconfig") driver = os_driver() parsed = driver.parse(native={"dev_conf": config_string}) - return Result(host=task.host, result=json.dumps(parsed.raw_value(), indent=2)) + return json.dumps(parsed.raw_value(), indent=2) -def convert_yang_to_config( - task: Task, yang_model: dict = None, **kwargs: Any -) -> Result: +def convert_yang_to_config(operating_system: str, yang_file: str): """ convert a yang model to configuration text - :param task: nornir task object - :param yang_model: type dict + :param operating_system: type str + :param yang_file: yang file from CWD - :returns: nornir result object + :returns: configuration text """ from ntc_rosetta import get_driver - from netnir.constants import OUTPUT_DIR - - yang_path = "/".join([OUTPUT_DIR, task.host.name, yang_model]) - with open(yang_path) as f: - yang_data = f.read() + with open(yang_file) as f: + yang_string = f.read() - os_driver = get_driver(task.host.platform, "openconfig") + os_driver = get_driver(operating_system, "openconfig") driver = os_driver() - config_text = driver.translate(candidate=yang_data) + config_text = driver.translate(candidate=yang_string) - return Result(host=task.host, result=config_text) + return config_text diff --git a/netnir/core/tasks/__init__.py b/netnir/plugins/tasks/__init__.py similarity index 100% rename from netnir/core/tasks/__init__.py rename to netnir/plugins/tasks/__init__.py diff --git a/netnir/core/tasks/config_plan.py b/netnir/plugins/tasks/config_plan.py similarity index 70% rename from netnir/core/tasks/config_plan.py rename to netnir/plugins/tasks/config_plan.py index 81f7506..ba44b8a 100644 --- a/netnir/core/tasks/config_plan.py +++ b/netnir/plugins/tasks/config_plan.py @@ -1,10 +1,8 @@ from netnir.helpers.scaffold.command import CommandScaffold from netnir.plugins.template import template_file from netnir.plugins.netmiko import netmiko_send_commands -from netnir.plugins.rosetta import convert_config_to_yang, convert_yang_to_config from netnir.plugins.hier import hier_host from netnir.helpers import output_writer -from netnir.helpers.common.args import config_to_yang, yang_to_config from netnir.constants import OUTPUT_DIR from nornir.plugins.functions.text import print_result @@ -43,8 +41,6 @@ def parser(parser): help="hier_config exclude tags", required=False, ) - config_to_yang(parser) - yang_to_config(parser) def run(self): """ @@ -55,33 +51,6 @@ def run(self): :returns: result string """ self.nr = self._inventory() - - if self.args.config_to_yang: - results = self.nr.run( - task=convert_config_to_yang, - config_file=self.args.config_to_yang, - name="CONVERT CONFIGURATION TEXT TO YANG MODEL", - num_workers=self.args.workers, - dry_run=self.args.X, - severity_level=self._verbose()["level"], - to_console=self._verbose()["to_console"], - ) - print_result(result=results, severity_level=self._verbose()["level"]) - return results - - if self.args.yang_to_config: - results = self.nr.run( - task=convert_yang_to_config, - yang_model=self.args.yang_to_config, - name="CONVERT YANG MODEL TO CONFIGURATION TEXT", - num_workers=self.args.workers, - dry_run=self.args.X, - severity_level=self._verbose()["level"], - to_console=self._verbose()["to_console"], - ) - print_result(result=results, severity_level=self._verbose()["level"]) - return results - results = self.nr.run( task=template_file, template_file="main.conf.j2", diff --git a/netnir/core/tasks/fetch/__init__.py b/netnir/plugins/tasks/fetch/__init__.py similarity index 86% rename from netnir/core/tasks/fetch/__init__.py rename to netnir/plugins/tasks/fetch/__init__.py index 0b2cfad..23538d4 100644 --- a/netnir/core/tasks/fetch/__init__.py +++ b/netnir/plugins/tasks/fetch/__init__.py @@ -9,7 +9,7 @@ class Fetch(SubCommandParser): title = "fetch commands" tasks = { "config": { - "class": "netnir.core.tasks.fetch.config.FetchConfig", + "class": "netnir.plugins.tasks.fetch.config.FetchConfig", "description": "fetch current config from a network device", }, } diff --git a/netnir/core/tasks/fetch/config.py b/netnir/plugins/tasks/fetch/config.py similarity index 100% rename from netnir/core/tasks/fetch/config.py rename to netnir/plugins/tasks/fetch/config.py diff --git a/netnir/core/tasks/inventory.py b/netnir/plugins/tasks/inventory.py similarity index 100% rename from netnir/core/tasks/inventory.py rename to netnir/plugins/tasks/inventory.py diff --git a/netnir/core/tasks/netconf.py b/netnir/plugins/tasks/netconf.py similarity index 100% rename from netnir/core/tasks/netconf.py rename to netnir/plugins/tasks/netconf.py diff --git a/netnir/core/tasks/ssh.py b/netnir/plugins/tasks/ssh.py similarity index 100% rename from netnir/core/tasks/ssh.py rename to netnir/plugins/tasks/ssh.py diff --git a/netnir/core/tasks/user.py b/netnir/plugins/tasks/user.py similarity index 100% rename from netnir/core/tasks/user.py rename to netnir/plugins/tasks/user.py diff --git a/tests/data/netnir.yaml b/tests/data/netnir.yaml index 00fda26..44f53b6 100644 --- a/tests/data/netnir.yaml +++ b/tests/data/netnir.yaml @@ -13,20 +13,20 @@ nornir: plugins: cp: - class: netnir.core.tasks.config_plan.ConfigPlan + class: netnir.plugins.tasks.config_plan.ConfigPlan description: "config plan commands" fetch: - class: netnir.core.tasks.fetch.Fetch + class: netnir.plugins.tasks.fetch.Fetch description: fetch commands inventory: - class: netnir.core.tasks.inventory.Inventory + class: netnir.plugins.tasks.inventory.Inventory description: "inventory search command" user: - class: netnir.core.tasks.user.User + class: netnir.plugins.tasks.user.User description: "netnir user commands" ssh: - class: netnir.core.tasks.ssh.Ssh + class: netnir.plugins.tasks.ssh.Ssh description: "command and config execution over SSH" netconf: - class: netnir.core.tasks.netconf.NetConf + class: netnir.plugins.tasks.netconf.NetConf description: "command and config execution over NETCONF" diff --git a/tests/netnir/core/tasks/test_tasks_config_plan.py b/tests/netnir/core/tasks/test_tasks_config_plan.py index 3ec993e..897d349 100644 --- a/tests/netnir/core/tasks/test_tasks_config_plan.py +++ b/tests/netnir/core/tasks/test_tasks_config_plan.py @@ -1,5 +1,5 @@ def test_tasks_config_plan(): - from netnir.core.tasks.config_plan import ConfigPlan + from netnir.plugins.tasks.config_plan import ConfigPlan from netnir.helpers.common.args import ( filter_group, filter_host, diff --git a/tests/netnir/core/tasks/test_tasks_inventory.py b/tests/netnir/core/tasks/test_tasks_inventory.py index 0b69adf..53ab594 100644 --- a/tests/netnir/core/tasks/test_tasks_inventory.py +++ b/tests/netnir/core/tasks/test_tasks_inventory.py @@ -1,5 +1,5 @@ def test_tasks_inventory(): - from netnir.core.tasks.inventory import Inventory + from netnir.plugins.tasks.inventory import Inventory from netnir.helpers.common.args import ( filter_group, filter_hosts, diff --git a/tests/netnir/helpers/test_netnir_config.py b/tests/netnir/helpers/test_netnir_config.py index d539ada..1dd797b 100644 --- a/tests/netnir/helpers/test_netnir_config.py +++ b/tests/netnir/helpers/test_netnir_config.py @@ -13,27 +13,27 @@ def test_netnir_config(initial_setup): "nornir": {"config": "./tests/data/nornir.yaml"}, "plugins": { "user": { - "class": "netnir.core.tasks.user.User", + "class": "netnir.plugins.tasks.user.User", "description": "netnir user commands", }, "inventory": { - "class": "netnir.core.tasks.inventory.Inventory", + "class": "netnir.plugins.tasks.inventory.Inventory", "description": "inventory search command", }, "cp": { - "class": "netnir.core.tasks.config_plan.ConfigPlan", + "class": "netnir.plugins.tasks.config_plan.ConfigPlan", "description": "config plan commands", }, "ssh": { - "class": "netnir.core.tasks.ssh.Ssh", + "class": "netnir.plugins.tasks.ssh.Ssh", "description": "command and config execution over SSH", }, "netconf": { - "class": "netnir.core.tasks.netconf.NetConf", + "class": "netnir.plugins.tasks.netconf.NetConf", "description": "command and config execution over NETCONF", }, "fetch": { - "class": "netnir.core.tasks.fetch.Fetch", + "class": "netnir.plugins.tasks.fetch.Fetch", "description": "fetch commands", }, }, From 01212c2e9480d76409ca0deb737c1759fbb9ac7a Mon Sep 17 00:00:00 2001 From: jtdub Date: Thu, 13 Aug 2020 11:03:29 -0500 Subject: [PATCH 3/7] add ntc-rosetta plugin and associated tests --- netnir/plugins/rosetta.py | 4 +- .../core/tasks/test_tasks_config_plan.py | 43 ------------------- .../netnir/core/tasks/test_tasks_inventory.py | 36 ---------------- 3 files changed, 3 insertions(+), 80 deletions(-) delete mode 100644 tests/netnir/core/tasks/test_tasks_config_plan.py delete mode 100644 tests/netnir/core/tasks/test_tasks_inventory.py diff --git a/netnir/plugins/rosetta.py b/netnir/plugins/rosetta.py index 47a3c1b..c630985 100644 --- a/netnir/plugins/rosetta.py +++ b/netnir/plugins/rosetta.py @@ -30,12 +30,14 @@ def convert_yang_to_config(operating_system: str, yang_file: str): :returns: configuration text """ from ntc_rosetta import get_driver + import json with open(yang_file) as f: yang_string = f.read() + yang_model = json.loads(yang_string) os_driver = get_driver(operating_system, "openconfig") driver = os_driver() - config_text = driver.translate(candidate=yang_string) + config_text = driver.translate(candidate=yang_model) return config_text diff --git a/tests/netnir/core/tasks/test_tasks_config_plan.py b/tests/netnir/core/tasks/test_tasks_config_plan.py deleted file mode 100644 index 897d349..0000000 --- a/tests/netnir/core/tasks/test_tasks_config_plan.py +++ /dev/null @@ -1,43 +0,0 @@ -def test_tasks_config_plan(): - from netnir.plugins.tasks.config_plan import ConfigPlan - from netnir.helpers.common.args import ( - filter_group, - filter_host, - filter_hosts, - output, - verbose, - num_workers, - make_changes, - config_to_yang, - yang_to_config, - ) - import argparse - - parser = argparse.ArgumentParser() - filter_host(parser) - filter_hosts(parser) - filter_group(parser) - output(parser) - verbose(parser) - num_workers(parser) - make_changes(parser) - config_to_yang(parser) - yang_to_config(parser) - parser.add_argument("--compile", const=True, nargs="?") - parser.add_argument("--include-tags", action="append") - parser.add_argument("--exclude-tags", action="append") - args = parser.parse_args() - cp = ConfigPlan(args=args) - cp.args.compile = True - cp.args.host = "router.dc1" - - assert isinstance(cp.nr, object) - assert cp.args.verbose == "INFO" - assert cp.args.compile is True - assert cp.args.host == "router.dc1" - assert isinstance(cp.parser, object) - result = cp.run() - assert ( - result.get("router.dc1").result - == "hostname router.dc1\nos iosxr\nbb_asn 64512\ndc_asn 64513" - ) diff --git a/tests/netnir/core/tasks/test_tasks_inventory.py b/tests/netnir/core/tasks/test_tasks_inventory.py deleted file mode 100644 index 53ab594..0000000 --- a/tests/netnir/core/tasks/test_tasks_inventory.py +++ /dev/null @@ -1,36 +0,0 @@ -def test_tasks_inventory(): - from netnir.plugins.tasks.inventory import Inventory - from netnir.helpers.common.args import ( - filter_group, - filter_hosts, - filter_host, - num_workers, - make_changes, - verbose, - ) - import argparse - - parser = argparse.ArgumentParser() - filter_group(parser) - filter_hosts(parser) - filter_host(parser) - num_workers(parser) - make_changes(parser) - verbose(parser) - args = parser.parse_args() - args.host = "router.dc1" - inventory = Inventory(args) - assert inventory.args.host == "router.dc1" - result = inventory.run() - assert result.get("router.dc1").result == { - "facts": { - "groups": ["dc1"], - "mgmt_protocol": "ssh", - "name": "router.dc1", - "os": "iosxr", - "pop_code": 1, - "provider": "provider1", - "template_path": "./tests/data/templates/provider1/iosxr", - }, - "groups": ["dc1"], - } From fa34e65a0c805062f404d0f3950bcf5d1a63a933 Mon Sep 17 00:00:00 2001 From: jtdub Date: Thu, 13 Aug 2020 11:04:10 -0500 Subject: [PATCH 4/7] update tests --- tests/data/config.json | 45 +++++++++++++++++++ tests/data/config.txt | 15 +++++++ .../plugins/tasks/test_tasks_config_plan.py | 43 ++++++++++++++++++ .../plugins/tasks/test_tasks_inventory.py | 36 +++++++++++++++ .../plugins/tasks/test_tasks_rosetta.py | 22 +++++++++ 5 files changed, 161 insertions(+) create mode 100644 tests/data/config.json create mode 100644 tests/data/config.txt create mode 100644 tests/netnir/plugins/tasks/test_tasks_config_plan.py create mode 100644 tests/netnir/plugins/tasks/test_tasks_inventory.py create mode 100644 tests/netnir/plugins/tasks/test_tasks_rosetta.py diff --git a/tests/data/config.json b/tests/data/config.json new file mode 100644 index 0000000..ccd79db --- /dev/null +++ b/tests/data/config.json @@ -0,0 +1,45 @@ +{ + "openconfig-interfaces:interfaces": { + "interface": [ + { + "name": "FastEthernet1", + "config": { + "name": "FastEthernet1", + "type": "iana-if-type:ethernetCsmacd", + "description": "This is Fa1", + "enabled": false + } + } + ] + }, + "openconfig-network-instance:network-instances": { + "network-instance": [ + { + "name": "default", + "config": { + "name": "default" + }, + "vlans": { + "vlan": [ + { + "vlan-id": 10, + "config": { + "vlan-id": 10, + "name": "prod", + "status": "ACTIVE" + } + }, + { + "vlan-id": 20, + "config": { + "vlan-id": 20, + "name": "dev", + "status": "SUSPENDED" + } + } + ] + } + } + ] + } +} diff --git a/tests/data/config.txt b/tests/data/config.txt new file mode 100644 index 0000000..a99648d --- /dev/null +++ b/tests/data/config.txt @@ -0,0 +1,15 @@ +interface FastEthernet1 + description This is Fa1 + shutdown + exit +! +vlan 10 + name prod + no shutdown + exit +! +vlan 20 + name dev + shutdown + exit +! diff --git a/tests/netnir/plugins/tasks/test_tasks_config_plan.py b/tests/netnir/plugins/tasks/test_tasks_config_plan.py new file mode 100644 index 0000000..897d349 --- /dev/null +++ b/tests/netnir/plugins/tasks/test_tasks_config_plan.py @@ -0,0 +1,43 @@ +def test_tasks_config_plan(): + from netnir.plugins.tasks.config_plan import ConfigPlan + from netnir.helpers.common.args import ( + filter_group, + filter_host, + filter_hosts, + output, + verbose, + num_workers, + make_changes, + config_to_yang, + yang_to_config, + ) + import argparse + + parser = argparse.ArgumentParser() + filter_host(parser) + filter_hosts(parser) + filter_group(parser) + output(parser) + verbose(parser) + num_workers(parser) + make_changes(parser) + config_to_yang(parser) + yang_to_config(parser) + parser.add_argument("--compile", const=True, nargs="?") + parser.add_argument("--include-tags", action="append") + parser.add_argument("--exclude-tags", action="append") + args = parser.parse_args() + cp = ConfigPlan(args=args) + cp.args.compile = True + cp.args.host = "router.dc1" + + assert isinstance(cp.nr, object) + assert cp.args.verbose == "INFO" + assert cp.args.compile is True + assert cp.args.host == "router.dc1" + assert isinstance(cp.parser, object) + result = cp.run() + assert ( + result.get("router.dc1").result + == "hostname router.dc1\nos iosxr\nbb_asn 64512\ndc_asn 64513" + ) diff --git a/tests/netnir/plugins/tasks/test_tasks_inventory.py b/tests/netnir/plugins/tasks/test_tasks_inventory.py new file mode 100644 index 0000000..53ab594 --- /dev/null +++ b/tests/netnir/plugins/tasks/test_tasks_inventory.py @@ -0,0 +1,36 @@ +def test_tasks_inventory(): + from netnir.plugins.tasks.inventory import Inventory + from netnir.helpers.common.args import ( + filter_group, + filter_hosts, + filter_host, + num_workers, + make_changes, + verbose, + ) + import argparse + + parser = argparse.ArgumentParser() + filter_group(parser) + filter_hosts(parser) + filter_host(parser) + num_workers(parser) + make_changes(parser) + verbose(parser) + args = parser.parse_args() + args.host = "router.dc1" + inventory = Inventory(args) + assert inventory.args.host == "router.dc1" + result = inventory.run() + assert result.get("router.dc1").result == { + "facts": { + "groups": ["dc1"], + "mgmt_protocol": "ssh", + "name": "router.dc1", + "os": "iosxr", + "pop_code": 1, + "provider": "provider1", + "template_path": "./tests/data/templates/provider1/iosxr", + }, + "groups": ["dc1"], + } diff --git a/tests/netnir/plugins/tasks/test_tasks_rosetta.py b/tests/netnir/plugins/tasks/test_tasks_rosetta.py new file mode 100644 index 0000000..be5c863 --- /dev/null +++ b/tests/netnir/plugins/tasks/test_tasks_rosetta.py @@ -0,0 +1,22 @@ +def test_rosetta_config_to_yang(): + from netnir.plugins.rosetta import convert_config_to_yang + import json + + with open("./tests/data/config.json") as f: + data = f.read() + model = json.loads(data) + + config = json.loads(convert_config_to_yang("ios", "./tests/data/config.txt")) + + assert config == model + + +def test_rosetta_yang_to_config(): + from netnir.plugins.rosetta import convert_yang_to_config + + with open("./tests/data/config.txt") as f: + config = f.read() + + model = convert_yang_to_config("ios", "./tests/data/config.json") + + assert model == config From e2b5726c99b106e992321f38cfc5f001face4bbf Mon Sep 17 00:00:00 2001 From: jtdub Date: Thu, 13 Aug 2020 13:43:43 -0500 Subject: [PATCH 5/7] update black --- netnir/cli.py | 2 +- netnir/core/inventory.py | 2 +- netnir/plugins/hier.py | 2 +- setup.cfg | 2 -- tests/netnir/helpers/test_filter_type.py | 10 ++++++++-- 5 files changed, 11 insertions(+), 7 deletions(-) diff --git a/netnir/cli.py b/netnir/cli.py index a9736f5..b448d0e 100644 --- a/netnir/cli.py +++ b/netnir/cli.py @@ -21,7 +21,7 @@ def __init__(self): self.plugins = NETNIR_CONFIG["plugins"] self.parser = MyParser(prog="netnir") self.parser.add_argument( - "--version", default=False, action="store_true", help="display version" + "--version", default=False, action="store_true", help="display version", ) subparsers = self.parser.add_subparsers(title="netnir commands", dest="command") self.loaded_plugins = plugins_import(tasks=self.plugins, subparsers=subparsers) diff --git a/netnir/core/inventory.py b/netnir/core/inventory.py index b91a8a9..e41ba56 100644 --- a/netnir/core/inventory.py +++ b/netnir/core/inventory.py @@ -14,7 +14,7 @@ def __init__(self, **kwargs): initialize the NornirInventory class and load the data into nornir """ super().__init__( - hosts=self.nhosts(), groups=self.ngroups(), defaults=self.ndefaults() + hosts=self.nhosts(), groups=self.ngroups(), defaults=self.ndefaults(), ) def nhosts(self): diff --git a/netnir/plugins/hier.py b/netnir/plugins/hier.py index 9843f3c..3f976c5 100644 --- a/netnir/plugins/hier.py +++ b/netnir/plugins/hier.py @@ -49,7 +49,7 @@ def hier_host( ) host = Host( - hostname=task.host.name, os=operating_system, hconfig_options=hier_options + hostname=task.host.name, os=operating_system, hconfig_options=hier_options, ) host.load_config_from( config_type="running", name=running_config, load_file=load_file diff --git a/setup.cfg b/setup.cfg index 8a8d786..4cce132 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,5 +11,3 @@ addopts = --flake8 netnir python_files = tests/*/*.py flake8-max-line-length = 127 -flake8-ignore = - netnir/core/__init__.py F401 \ No newline at end of file diff --git a/tests/netnir/helpers/test_filter_type.py b/tests/netnir/helpers/test_filter_type.py index ec28240..e006c58 100644 --- a/tests/netnir/helpers/test_filter_type.py +++ b/tests/netnir/helpers/test_filter_type.py @@ -1,6 +1,12 @@ def test_filter_type(initial_setup): from netnir.helpers import filter_type - assert filter_type(host="router.dc1") == {"type": "host", "data": "router.dc1"} + assert filter_type(host="router.dc1") == { + "type": "host", + "data": "router.dc1", + } assert filter_type(group="dc1") == {"type": "group", "data": "dc1"} - assert filter_type(filter="os:iosxr") == {"type": "filter", "data": "os:iosxr"} + assert filter_type(filter="os:iosxr") == { + "type": "filter", + "data": "os:iosxr", + } From 34586fad7c43df588fc1e1ad346067cc54309bf2 Mon Sep 17 00:00:00 2001 From: jtdub Date: Wed, 16 Sep 2020 10:29:51 -0500 Subject: [PATCH 6/7] update setup.cfg --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index 4cce132..a96a96c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,6 +7,7 @@ filterwarnings = addopts = -vv --cov=netnir tests/ + --cov-report term-missing --black netnir --flake8 netnir python_files = tests/*/*.py From 65983c2f44635a8a28354816268db7bd07721d21 Mon Sep 17 00:00:00 2001 From: jtdub Date: Wed, 16 Sep 2020 11:03:38 -0500 Subject: [PATCH 7/7] update setup.py --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 01780e6..60258fb 100755 --- a/setup.py +++ b/setup.py @@ -18,6 +18,7 @@ "keyring>=21.2.1", "keyrings.alt>=3.4.0", "ntc-rosetta>=v0.2.0", + "dataclasses<0.7.0,>=0.6.0", ], tests_require=[ "pytest",