-
Notifications
You must be signed in to change notification settings - Fork 48
Added integration with napalm-yang #264
base: develop
Are you sure you want to change the base?
Changes from 7 commits
7774d86
6b7ad6c
561d6dd
b7bcf9d
a0b59b4
45a1904
b4a3502
d72633f
f0aa889
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ | |
# local modules | ||
import napalm_base.exceptions | ||
import napalm_base.helpers | ||
import napalm_base.yang | ||
|
||
import napalm_base.constants as c | ||
|
||
|
@@ -93,6 +94,13 @@ def __raise_clean_exception(exc_type, exc_value, exc_traceback): | |
# Traceback should already be attached to exception; no need to re-attach | ||
raise exc_value | ||
|
||
@property | ||
def yang(self): | ||
if not hasattr(self, "config"): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just before this, we can: if not HAS_NY:
raise SomeException('Please install napalm-yang for this fancy stuff') |
||
self.config = napalm_base.yang.Yang("config", self) | ||
self.state = napalm_base.yang.Yang("state", self) | ||
return self | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks strange...you are accessing an attribute (i.e. using it as a getter), but then causing it to behave as a setter? So you are basically creating a method that looks like an attribute and returns the object. Can you provide some more details on why here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, so that's certainly a hack. Ideally we would have something like:
But right now we can't do that because If you have a better solution let me know, this is the best I could come up with so far. We could probably use a wrapper or a metaclass but figured this was simpler/easier to read (if we go with this hack I will add details to the docstrings so people reading this knows why the hack). |
||
def open(self): | ||
""" | ||
Opens a connection to the device. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# Copyright 2017 Dravetech AB. All rights reserved. | ||
# | ||
# The contents of this file are licensed under the Apache License, Version 2.0 | ||
# (the "License"); you may not use this file except in compliance with the | ||
# License. You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||
# License for the specific language governing permissions and limitations under | ||
# the License. | ||
|
||
# Python3 support | ||
from __future__ import print_function | ||
from __future__ import unicode_literals | ||
|
||
import napalm_yang | ||
|
||
|
||
# TODO we probably need to adapt the validate framework as well | ||
|
||
|
||
class Yang(object): | ||
|
||
def __init__(self, mode, device): | ||
self._mode = mode | ||
self.device = device | ||
self.device.running = napalm_yang.base.Root() | ||
|
||
if mode == "config": | ||
self.device.candidate = napalm_yang.base.Root() | ||
|
||
for model in napalm_yang.SUPPORTED_MODELS: | ||
# We are going to dynamically attach a getter for each | ||
# supported YANG model. | ||
module_name = model[0].replace("-", "_") | ||
funcname = "get_{}".format(module_name) | ||
setattr(Yang, funcname, yang_get_wrapper(module_name)) | ||
funcname = "model_{}".format(module_name) | ||
setattr(Yang, funcname, yang_model_wrapper(module_name)) | ||
|
||
def translate(self, merge=False, replace=False, profile=None): | ||
if profile is None: | ||
profile = self.device.profile | ||
|
||
if merge: | ||
return self.device.candidate.translate_config(profile=profile, | ||
merge=self.device.running) | ||
elif replace: | ||
return self.device.candidate.translate_config(profile=profile, | ||
replace=self.device.running) | ||
else: | ||
return self.device.candidate.translate_config(profile=profile) | ||
|
||
def diff(self): | ||
return napalm_yang.utils.diff(self.device.candidate, self.device.running) | ||
|
||
|
||
def yang_get_wrapper(module): | ||
""" | ||
This method basically implements the getter for YANG models. | ||
|
||
The method abstracts loading the model into the root objects (candidate | ||
and running) and calls the parsers. | ||
""" | ||
module = getattr(napalm_yang.models, module) | ||
|
||
def method(self, **kwargs): | ||
# This is the class for the model | ||
instance = module() | ||
|
||
# We attach it to the running object | ||
self.device.running.add_model(instance) | ||
|
||
# We get the correct method (parse_config or parse_state) | ||
parsefunc = getattr(self.device.running, "parse_{}".format(self._mode)) | ||
|
||
# We parse *only* the model that corresponds to this call | ||
running_attrs = [getattr(self.device.running, a) for a in instance.elements().keys()] | ||
parsefunc(device=self.device, attrs=running_attrs) | ||
|
||
# If we are in configuration mode and the user requests it | ||
# we create a candidate as well | ||
if kwargs.pop("candidate"): | ||
instance = module() | ||
self.device.candidate.add_model(instance) | ||
parsefunc = getattr(self.device.candidate, "parse_{}".format(self._mode)) | ||
attrs = [getattr(self.device.candidate, a) for a in instance.elements().keys()] | ||
parsefunc(device=self.device, attrs=attrs) | ||
|
||
# In addition to populate the running object, we return a dict with the contents | ||
# of the parsed model | ||
f = kwargs.get("filter", True) | ||
return {a._yang_name: a.get(filter=f) for a in running_attrs} | ||
|
||
return method | ||
|
||
|
||
def yang_model_wrapper(module): | ||
""" | ||
This method basically implements the getter for YANG models. | ||
|
||
The method abstracts loading the model into the root objects (candidate | ||
and running) and calls the parsers. | ||
""" | ||
module = getattr(napalm_yang.models, module) | ||
|
||
def method(self, **kwargs): | ||
root = napalm_yang.base.Root() | ||
root.add_model(module) | ||
return napalm_yang.utils.model_to_dict(root, self._mode) | ||
|
||
return method |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ jtextfsm | |
jinja2 | ||
netaddr | ||
pyYAML | ||
napalm-yang |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
from napalm_base import get_network_driver | ||
|
||
import json | ||
|
||
|
||
def pretty_print(dictionary): | ||
print(json.dumps(dictionary, sort_keys=True, indent=4)) | ||
|
||
|
||
eos_configuration = { | ||
'hostname': '127.0.0.1', | ||
'username': 'vagrant', | ||
'password': 'vagrant', | ||
'optional_args': {'port': 12443} | ||
} | ||
|
||
eos = get_network_driver("eos") | ||
eos_device = eos(**eos_configuration) | ||
|
||
eos_device.open() | ||
pretty_print(eos_device.yang.config.get_openconfig_interfaces(candidate=True)) | ||
print(eos_device.yang.config.get_openconfig_network_instance()) | ||
|
||
print("# Raw translation") | ||
print(eos_device.yang.config.translate()) | ||
print("-------------") | ||
|
||
print("# Merge without changes, should be empty") | ||
print(eos_device.yang.config.translate(merge=True)) | ||
print("-------------") | ||
|
||
print("# Replace without changes") | ||
print(eos_device.yang.config.translate(replace=True)) | ||
print("-------------") | ||
|
||
|
||
print("# Change a description") | ||
eos_device.candidate.interfaces.interface["Ethernet1"].config.description = "This is a new description" # noqa | ||
pretty_print(eos_device.config.diff()) | ||
print("-------------") | ||
|
||
print("# Merge change") | ||
merge_config = eos_device.yang.config.translate(merge=True) | ||
print(merge_config) | ||
print("-------------") | ||
|
||
print("# Replace change") | ||
replace_config = eos_device.yang.config.translate(replace=True) | ||
print(replace_config) | ||
print("-------------") | ||
|
||
print("# Let's replace the current interfaces configuration from the device") | ||
eos_device.load_merge_candidate(config=replace_config) | ||
print(eos_device.compare_config()) | ||
eos_device.discard_config() | ||
print("-------------") | ||
|
||
print("# Let's merge the current interfaces configuration from the device") | ||
eos_device.load_merge_candidate(config=merge_config) | ||
print(eos_device.compare_config()) | ||
eos_device.discard_config() | ||
print("-------------") | ||
|
||
eos_device.close() | ||
|
||
print("# For reference, you can also print the model for both the config and the state parts of the model") # noqa | ||
pretty_print(eos_device.yang.config.model_openconfig_vlan()) | ||
pretty_print(eos_device.yang.state.model_openconfig_vlan()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we want to avoid
napalm-yang
as a dependency fornapalm-base
, we can check if it is installed, i.e.: