Skip to content
This repository has been archived by the owner on Sep 17, 2019. It is now read-only.

Added integration with napalm-yang #264

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion napalm_base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from napalm_base.exceptions import ModuleImportError
from napalm_base.mock import MockDriver
from napalm_base.utils import py23_compat
from napalm_base import yang

try:
__version__ = pkg_resources.get_distribution('napalm-base').version
Expand All @@ -46,7 +47,8 @@

__all__ = [
'get_network_driver', # export the function
'NetworkDriver' # also export the base class
'NetworkDriver', # also export the base class
'yang',
]


Expand Down
15 changes: 15 additions & 0 deletions napalm_base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
import napalm_base.exceptions
import napalm_base.helpers


try:
import napalm_yang
except ImportError:
napalm_yang = None

import napalm_base.constants as c

from napalm_base import validate
Expand Down Expand Up @@ -70,6 +76,15 @@ def __del__(self):
except Exception:
pass

@property
def yang(self):
if not napalm_yang:
raise ImportError("No module named napalm_yang. Please install `napalm-yang`")

if not hasattr(self, "_yang"):
self._yang = napalm_base.yang.Yang(self)
return self._yang

def open(self):
"""
Opens a connection to the device.
Expand Down
113 changes: 113 additions & 0 deletions napalm_base/yang.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# 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, device):
self.device = device
self.device.running = napalm_yang.base.Root()
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, data="config", candidate=False, filter=True):
# 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(data))

# 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 candidate:
instance = module()
self.device.candidate.add_model(instance)
import pdb
pdb.set_trace()
parsefunc = getattr(self.device.candidate, "parse_{}".format(data))
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
return {a._yang_name: a.get(filter=filter) 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, data="config"):
root = napalm_yang.base.Root()
root.add_model(module)
return napalm_yang.utils.model_to_dict(root, data)

return method
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ napalm-panos
napalm-pluribus
napalm-ros
napalm-vyos
napalm-yang
-r requirements.txt
68 changes: 68 additions & 0 deletions test.py
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.get_openconfig_interfaces(candidate=True))
print(eos_device.yang.get_openconfig_network_instance())

print("# Raw translation")
print(eos_device.yang.translate())
print("-------------")

print("# Merge without changes, should be empty")
print(eos_device.yang.translate(merge=True))
print("-------------")

print("# Replace without changes")
print(eos_device.yang.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.yang.diff())
print("-------------")

print("# Merge change")
merge_config = eos_device.yang.translate(merge=True)
print(merge_config)
print("-------------")

print("# Replace change")
replace_config = eos_device.yang.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.model_openconfig_vlan())
pretty_print(eos_device.yang.model_openconfig_vlan(data="state"))
Empty file added test/yang/test_yang.py
Empty file.