Introduction

Yangify is a framework that allows you to easily write code that can map structured and non-structured data into data modelled using YANG models. Yangify can also do the opposite operation and convert data modelled with YANG models into other structured or non-structured data. This allows you to easily write code that can parse native output/data/configuration from network devices and map them into YANG models and vice-versa.

In addition, it can provide the necessary commands to go merge and replace a running configuration using a candidate object. This can prove useful to do smart replace/merge operations in systems that otherwise might not be able to do so.

Note that Yangify doesn’t provide the code to do that, Yangify only provides the framework. However, it provides a few reference implementations for documentation, demostrational and testing purposes.

For an actual implementation of Parsers/Translators using Yangify you can refer to rosetta.

The Datamodel

Before we even start we need a datamodel. Yangify uses under the hoods yangson. Yangson uses RFC 7895 to define the models to use so we are going to need a file describing our library and a list of paths where to find the modules being implemented/imported by the library:

[1]:
from yangson.datamodel import DataModel
dm = DataModel.from_file("yang/yang-library-data.json", ["yang/yang-modules/ietf", "yang/yang-modules/openconfig"])

The datamodel can be reused across many parsers and translators.

Parsing

You will see more details about this if you go to the tutorial but this is an example of how you could use a Parser written with Yangify to parse IOS configuration:

[2]:
import tutorial_parser

from yangify import parser
from yangify.parser.text_tree import parse_indented_config

class IOSParser(parser.RootParser):
    class Yangify(parser.ParserData):
        def init(self) -> None:
            self.root_native = parse_indented_config(self.native.splitlines())
            self.native = self.root_native

    interfaces = tutorial_parser.Interfaces
    vlans = tutorial_parser.Vlans

with open("data/ios/config.txt", "r") as f:
    config = f.read()
[3]:
%cat data/ios/config.txt
interface FastEthernet1
   description This is Fa1
   shutdown
   exit
!
interface FastEthernet1.1
   description This is Fa1.1
   exit
!
interface FastEthernet1.2
   description This is Fa1.2
   exit
!
interface FastEthernet3
   description This is Fa3
   no shutdown
   exit
!
interface FastEthernet4
   shutdown
   exit
!
vlan 10
   name prod
   no shutdown
   exit
!
vlan 20
   name dev
   shutdown
   exit
!
[4]:
p = IOSParser(dm, native=config)
result = p.process()
import json
print(json.dumps(result.raw_value(), indent=4))
{
    "openconfig-interfaces:interfaces": {
        "interface": [
            {
                "name": "FastEthernet1",
                "config": {
                    "name": "FastEthernet1",
                    "type": "iana-if-type:ethernetCsmacd",
                    "description": "This is Fa1",
                    "enabled": false
                },
                "subinterfaces": {
                    "subinterface": [
                        {
                            "index": 1,
                            "config": {
                                "index": 1,
                                "description": "This is Fa1.1"
                            }
                        },
                        {
                            "index": 2,
                            "config": {
                                "index": 2,
                                "description": "This is Fa1.2"
                            }
                        }
                    ]
                }
            },
            {
                "name": "FastEthernet3",
                "config": {
                    "name": "FastEthernet3",
                    "type": "iana-if-type:ethernetCsmacd",
                    "description": "This is Fa3",
                    "enabled": true
                }
            },
            {
                "name": "FastEthernet4",
                "config": {
                    "name": "FastEthernet4",
                    "type": "iana-if-type:ethernetCsmacd",
                    "enabled": false
                }
            }
        ]
    },
    "openconfig-vlan:vlans": {
        "vlan": [
            {
                "vlan-id": 10,
                "config": {
                    "vlan-id": 10,
                    "name": "prod",
                    "status": "ACTIVE"
                }
            },
            {
                "vlan-id": 20,
                "config": {
                    "vlan-id": 20,
                    "name": "dev",
                    "status": "SUSPENDED"
                }
            }
        ]
    }
}

Translating

You will see more details about this if you go to the tutorial but this is an example of how you could use a Translator written with Yangify to translate models into IOS configuration:

[5]:
import tutorial_translator

from yangify import translator
from yangify.translator.config_tree import ConfigTree


class IOSTranslator(translator.RootTranslator):
    class Yangify(translator.TranslatorData):
        def init(self) -> None:
            self.root_result = ConfigTree()
            self.result = self.root_result

        def post(self) -> None:
            self.root_result = self.root_result.to_string()

    interfaces = tutorial_translator.Interfaces
    vlans = tutorial_translator.Vlans
[6]:
%cat data/ios/data.json
{
  "openconfig-interfaces:interfaces": {
    "interface": [
      {
        "name": "FastEthernet1",
        "config": {
          "name": "FastEthernet1",
          "type": "iana-if-type:ethernetCsmacd",
          "description": "This is Fa1",
          "enabled": false
        },
        "subinterfaces": {
          "subinterface": [
            {
              "index": 1,
              "config": {
                "index": 1,
                "description": "This is Fa1.1"
              }
            },
            {
              "index": 2,
              "config": {
                "index": 2,
                "description": "This is Fa1.2"
              }
            }
          ]
        }
      },
      {
        "name": "FastEthernet3",
        "config": {
          "name": "FastEthernet3",
          "type": "iana-if-type:ethernetCsmacd",
          "description": "This is Fa3",
          "enabled": true
        }
      },
      {
        "name": "FastEthernet4",
        "config": {
          "name": "FastEthernet4",
          "type": "iana-if-type:ethernetCsmacd",
          "enabled": false
        }
      }
    ]
  },
  "openconfig-vlan:vlans": {
    "vlan": [
      {
        "vlan-id": 10,
        "config": {
          "vlan-id": 10,
          "name": "prod",
          "status": "ACTIVE"
        }
      },
      {
        "vlan-id": 20,
        "config": {
          "vlan-id": 20,
          "name": "dev",
          "status": "SUSPENDED"
        }
      }
    ]
  }
}
[7]:
import json
with open("data/ios/data.json", "r") as f:
    data = json.load(f)
[8]:
p = IOSTranslator(dm, candidate=data)
print(p.process())
interface FastEthernet1
   description This is Fa1
   shutdown
   exit
!
interface FastEthernet1.1
   description This is Fa1.1
   exit
!
interface FastEthernet1.2
   description This is Fa1.2
   exit
!
interface FastEthernet3
   description This is Fa3
   no shutdown
   exit
!
interface FastEthernet4
   shutdown
   exit
!
vlan 10
   name prod
   no shutdown
   exit
!
vlan 20
   name dev
   shutdown
   exit
!