cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
2838
Views
0
Helpful
15
Replies

YDK wrapper fro YAML

horseinthesky
Level 1
Level 1

Hello.
I'm trying to implement this solution to be able to describe YANG models in YAML and build appropriate models using YDK modules.
But I'm kind of stuck. Here is what I'm testing right now:

from ydk.services import CRUDService, CodecService
from ydk.providers import NetconfServiceProvider, CodecServiceProvider
from ydk.types import Empty
from ydk.models.cisco_ios_xe.Cisco_IOS_XE_native import Native
from ydk.filters import YFilter
import yaml
import logging

logger = logging.getLogger('ydk')
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter(("%(asctime)s - %(name)s - "
                               "%(levelname)s - %(message)s"))
handler.setFormatter(formatter)
logger.addHandler(handler)


def instantiate(binding, model_key, model_value, action='assign'):
    if isinstance(model_value, str) and model_value.lower() == 'empty':
        if action == 'return':
            return Empty()
        elif action == 'assign':
            setattr(binding, model_key, Empty())
    elif any(isinstance(model_value, x) for x in [str, bool, int]):
        if action == 'return':
            return model_value
        elif action == 'assign':
            setattr(binding, model_key, model_value)
    elif isinstance(model_value, list):
        list_obj = getattr(binding, model_key.lower())

        for el in model_value:
            list_obj.append(instantiate(binding, model_key, el, action='return'))
    elif isinstance(model_value, dict):
        # special case handling enum type
        if all([x is None for x in model_value.values()]):
            enum_name = ''.join([x.capitalize() for x in model_key.split('_')]) + 'Enum'
            enum_class = getattr(binding, enum_name)

            for el in model_value.keys():
                enum = getattr(enum_class, el)

                if action == 'return':
                    return enum
                elif action == 'assign':
                    setattr(binding, model_key, enum)
        else:
            container = getattr(binding, model_key, None)
            if container and not isinstance(container, list):
                container_instance = container
            elif container.__class__.__name__ == 'YLeafList':
                container_instance = container
            else:
                model_key_camelized = ''.join([x.capitalize() for x in model_key.split('_')])
                container_instance = getattr(binding, model_key_camelized)()

            for k, v in model_value.items():
                instantiate(container_instance, k, v, action='assign')

            if action == 'return':
                return container_instance
            elif action == 'assign':
                setattr(binding, model_key, container_instance)
    else:
        raise ValueError('Unexpected YAML value: {} of type {}'.format(model_value, type(model_value)))


if __name__ == '__main__':
    with open('interfaces2.yml') as f:
        config = yaml.load(f)['config']

    for model_name, model_data in config.items():
        for k, v in model_data.items():
            binding = globals().get(model_name.capitalize())()
            instantiate(binding, k, v)

    # codec = CodecService()
    # provider = CodecServiceProvider(type='xml')
    # print(codec.encode(provider, native))
    provider = NetconfServiceProvider(
        address='10.10.30.6',
        port=830,
        username='admin',
        password='admin',
        protocol='ssh'
    )
    crud = CRUDService()
    crud.create(provider, binding)

And config file is:

config:
  native:
    interface:
      gigabitethernet:
        - name: "2"
          description: Configured via ydk
          ip:
            address:
              primary:
                address: 172.16.2.0
                mask: 255.255.255.254
              secondary:
                - address: 192.168.20.1
                  mask: 255.255.255.254
secondary: - address: 192.168.20.3 mask: 255.255.255.254
secondary: - name: "3" description: Configured via ydk ip: address: primary: address: 172.16.3.0 mask: 255.255.255.254 - name: "4" description: Configured via ydk ip: address: primary: address: 172.16.4.0 mask: 255.255.255.254 loopback: - name: 0 description: ROUTER ID ip: address: primary: address: 1.1.1.6 mask: 255.255.255.255


The problem is that Interface class has camel case namings for classes of particular interface type, GigabitEthernet for instance. This is why I've got:

python3 xe_intf_test2.py
Traceback (most recent call last):
  File "xe_intf_test2.py", line 75, in <module>
    instantiate(binding, k, v)
  File "xe_intf_test2.py", line 58, in instantiate
    instantiate(container_instance, k, v, action='assign')
  File "xe_intf_test2.py", line 33, in instantiate
    list_obj.append(instantiate(binding, model_key, el, action='return'))
  File "xe_intf_test2.py", line 55, in instantiate
    container_instance = getattr(binding, model_key_camelized)()
AttributeError: 'Interface' object has no attribute 'Gigabitethernet'

Do you have any idea or even implementation of this?

1 Accepted Solution

Accepted Solutions

Here is my script after debugging:

import yaml
import json

from ydk.services import CodecService
from ydk.providers import CodecServiceProvider

if __name__ == '__main__':
    with open("test_yaml_config.yml", 'r') as yaml_config:
        yaml_object = yaml.safe_load(yaml_config)

    json_config = json.dumps(yaml_object['config'], indent=2).replace('null', '[null]')
    print("\nGOT INITIAL JSON CONFIGURATION:\n%s" % json_config)

    codec = CodecService()
    codec_provider = CodecServiceProvider(type='json')
    config_entity = codec.decode(codec_provider, json_config)

    # Back to json
    json_payload = codec.encode(codec_provider, config_entity)
    print("\nJSON CONFIGURATION AFTER ENTITY DECODE:\n%s" % json_payload)

    # Back to yaml  , default_flow_style=False
    yaml_payload = yaml.dump(json.loads(json_payload)).replace('null', '')
    print("\nYAML CONFIGURATION AFTER JSON CONVERSION:\n%s" % yaml_payload)

The output:

(venv) Yakovs-Air:tests ygorelik$ python test_yaml_codec.py 

GOT INITIAL JSON CONFIGURATION:

{

  "Cisco-IOS-XE-native:native": {

    "interface": {

      "GigabitEthernet": [

        {

          "name": "2",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.2.0",

                "mask": "255.255.255.254"

              },

              "secondary": [

                {

                  "address": "192.168.20.1",

                  "mask": "255.255.255.254",

                  "secondary": [null]

                },

                {

                  "address": "192.168.20.3",

                  "mask": "255.255.255.254",

                  "secondary": [null]

                }

              ]

            }

          }

        },

        {

          "name": "3",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.3.0",

                "mask": "255.255.255.254"

              }

            }

          }

        },

        {

          "name": "4",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.4.0",

                "mask": "255.255.255.254"

              }

            }

          }

        }

      ],

      "Loopback": [

        {

          "name": 0,

          "description": "ROUTER ID",

          "ip": {

            "address": {

              "primary": {

                "address": "1.1.1.6",

                "mask": "255.255.255.255"

              }

            }

          }

        }

      ]

    }

  }

}

 

JSON CONFIGURATION AFTER ENTITY DECODE:

{

  "Cisco-IOS-XE-native:native": {

    "interface": {

      "GigabitEthernet": [

        {

          "name": "2",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "secondary": [

                {

                  "address": "192.168.20.1",

                  "mask": "255.255.255.254",

                  "secondary": [null]

                },

                {

                  "address": "192.168.20.3",

                  "mask": "255.255.255.254",

                  "secondary": [null]

                }

              ],

              "primary": {

                "address": "172.16.2.0",

                "mask": "255.255.255.254"

              }

            }

          }

        },

        {

          "name": "3",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.3.0",

                "mask": "255.255.255.254"

              }

            }

          }

        },

        {

          "name": "4",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.4.0",

                "mask": "255.255.255.254"

              }

            }

          }

        }

      ],

      "Loopback": [

        {

          "name": 0,

          "description": "ROUTER ID",

          "ip": {

            "address": {

              "primary": {

                "address": "1.1.1.6",

                "mask": "255.255.255.255"

              }

            }

          }

        }

      ]

    }

  }

}

 

YAML CONFIGURATION AFTER JSON CONVERSION:

Cisco-IOS-XE-native:native:

  interface:

    GigabitEthernet:

    - description: Configured via ydk

      ip:

        address:

          primary:

            address: 172.16.2.0

            mask: 255.255.255.254

          secondary:

          - address: 192.168.20.1

            mask: 255.255.255.254

            secondary:

            - 

          - address: 192.168.20.3

            mask: 255.255.255.254

            secondary:

            - 

      name: '2'

    - description: Configured via ydk

      ip:

        address:

          primary:

            address: 172.16.3.0

            mask: 255.255.255.254

      name: '3'

    - description: Configured via ydk

      ip:

        address:

          primary:

            address: 172.16.4.0

            mask: 255.255.255.254

      name: '4'

    Loopback:

    - description: ROUTER ID

      ip:

        address:

          primary:

            address: 1.1.1.6

            mask: 255.255.255.255

      name: 0

 

Now you have full cycle of encoding and decoding using YAML encoding.

 

Yan Gorelik
YDK Solutions

View solution in original post

15 Replies 15

yangorelik
Spotlight
Spotlight

As I understand, you are looking for new codec service, which would convert YAML encoded string to YDK entity objects and back. It is not simple, but definitely doable goal. You just need get familiar with YDK API and learn how to parse/build Entity objects. In YDK-0.8.4 all services including Codec are implemented in C++. Python and Go implement mostly wrappers for C++ code. You can take as a base algorithm implemented in XmlSubtreeCodec.cpp and JsonSubtreeCodec.cpp and repeat it in your application in Python. You also can utilize yaml Python package, while parsing/creating the YDK entities. Later on your YAML codec can be included to YDK and become available for all the YDK users.

Yan Gorelik
YDK Solutions

Unfortunately, I am not a developer and don't know C++ like at all. I wonder if it is possible to do such a thing in Python.
The solution I've found is 3 years old, written in Python2 and doesn't work anymore. 

Could you please share reference to solution that you have found. Maybe it is not too difficult to revive it.

Yan Gorelik
YDK Solutions

horseinthesky
Level 1
Level 1

I was able to make it work for the IOS XE Native model:

from ydk.types import Empty
from ydk.filters import YFilter


def instantiate(binding, model_key, model_value, action='assign'):
    if model_value == 'empty':
        if action == 'return':
            return Empty()
        elif action == 'assign':
            setattr(binding, model_key, Empty())
    elif any(isinstance(model_value, x) for x in [str, bool, int]):
        if action == 'return':
            return model_value
        elif action == 'assign':
            setattr(binding, model_key, model_value)
    elif isinstance(model_value, list):
        model_key_joined = ''.join([x for x in model_key.split(',')])
        list_obj = getattr(binding, model_key_joined.lower())
        for el in model_value:
            obj = instantiate(binding, model_key, el, action='return')
            obj.yfilter = YFilter.replace
            list_obj.append(obj)
    elif isinstance(model_value, dict):
        # special case handling enum type
        if all([x is None for x in model_value.values()]):
            enum_name = ''.join([x.capitalize() for x in model_key.split(',')]) + 'Enum'
            enum_class = getattr(binding, enum_name)
            for el in model_value.keys():
                enum = getattr(enum_class, el)
                if action == 'return':
                    return enum
                elif action == 'assign':
                    setattr(binding, model_key, enum)
        else:
            container = getattr(binding, model_key, None)
            if container and container.__class__.__name__ not in ['YList', 'YLeafList']:
                container_instance = container
            else:
                model_key_camelized = ''.join([x.capitalize() for x in model_key.split(',')])
                container_instance = getattr(binding, model_key_camelized)()

            for k, v in model_value.items():
                instantiate(container_instance, k, v, action='assign')

            if action == 'return':
                return container_instance
            elif action == 'assign':
                setattr(binding, model_key, container_instance)
    else:
        raise ValueError('Unexpected YAML value: {} of type {}'.format(model_value, type(model_value)))

 

config:
  native:
    interface:
      gigabit,ethernet:
        - name: "2"
          description: Configured via ydk
          ip:
            address:
              primary:
                address: 172.16.2.0
                mask: 255.255.255.254
              secondary:
                - address: 192.168.20.1
                  mask: 255.255.255.254
                  secondary: empty
                - address: 192.168.20.3
                  mask: 255.255.255.254
                  secondary: empty
        - name: "3"
          description: Configured via ydk
          ip:
            address:
              primary:
                address: 172.16.3.0
                mask: 255.255.255.254
        - name: "4"
          description: Configured via ydk
          ip:
            address:
              primary:
                address: 172.16.4.0
                mask: 255.255.255.254
      loopback:
        - name: 0
          description: ROUTER ID
          ip:
            address:
              primary:
                address: 1.1.1.6
                mask: 255.255.255.255
    router:
      bgp:
        - id: 6
          bgp:
            router_id: 1.1.1.6
          neighbor:
            - id: 10.10.30.5
              remote_as: 5

With this scpirt:

from ydk.services import CRUDService, CodecService
from ydk.providers import NetconfServiceProvider, CodecServiceProvider
from ydk.models.cisco_ios_xe.Cisco_IOS_XE_native import Native

import yaml
import logging

from recursive import instantiate

logger = logging.getLogger('ydk')
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = logging.Formatter(("%(asctime)s - %(name)s - "
                               "%(levelname)s - %(message)s"))
handler.setFormatter(formatter)
logger.addHandler(handler)

if __name__ == '__main__':
    with open('xe_config_native.yml') as f:
        config = yaml.load(f)['config']

    for model_name, model_data in config.items():
        binding = globals().get(model_name.capitalize())()
        for k, v in model_data.items():
            instantiate(binding, k, v)

    # codec = CodecService()
    # provider = CodecServiceProvider(type='xml')
    # print(codec.encode(provider, binding))
    provider = NetconfServiceProvider(
        address='10.10.30.6',
        port=830,
        username='admin',
        password='admin',
        protocol='ssh'
    )
    crud = CRUDService()
    crud.create(provider, binding)

 

 

And this config:

config:
  native:
    interface:
      gigabit,ethernet:
        - name: "2"
          description: Configured via ydk
          ip:
            address:
              primary:
                address: 172.16.2.0
                mask: 255.255.255.254
              secondary:
                - address: 192.168.20.1
                  mask: 255.255.255.254
                  secondary: empty
                - address: 192.168.20.3
                  mask: 255.255.255.254
                  secondary: empty
        - name: "3"
          description: Configured via ydk
          ip:
            address:
              primary:
                address: 172.16.3.0
                mask: 255.255.255.254
        - name: "4"
          description: Configured via ydk
          ip:
            address:
              primary:
                address: 172.16.4.0
                mask: 255.255.255.254
      loopback:
        - name: 0
          description: ROUTER ID
          ip:
            address:
              primary:
                address: 1.1.1.6
                mask: 255.255.255.255
    router:
      bgp:
        - id: 6
          bgp:
            router_id: 1.1.1.6
          neighbor:
            - id: 10.10.30.5
              remote_as: 5

But it fails with any other model cuz of they have mutual exclusive namings =(

That is expected result, because algorithm of encoding is incorrect. Instead of going through this exercise I suggest to use yaml package API, which allows translate YAML formatted string to JSON. Then you can use YDK Codec Service to decode it to entity objects with following use of CRUD and Netconf services.

from ydk.services import CRUDService, CodecService
from ydk.providers import NetconfServiceProvider, CodecServiceProvider
from ydk.models.cisco_ios_xe.Cisco_IOS_XE_native import Native

import yaml
import json

with open("config.yaml", 'r') as yaml_config:
    yaml_object = yaml.safe_load(yaml_config) # yaml_object will be a list or a dict
    json_str = json.dump(yaml_object, indent=2)

codec = CodecService()
codec_provider = CodecServiceProvider(type='json')
binding = codec.decode(codec_provider, json_str)

netconf_provider = NetconfServiceProvider(
address='10.10.30.6',
port=830,
username='admin',
password='admin',
protocol='ssh'
)
crud = CRUDService()
crud.create(netconf_provider, binding)

You need to keep in mind that content of config file must be YANG model compliant in order for the Codec Servise to work properly. That means you should use YANG node names and notation as they appear in the YANG model. Your YAML config file is not YANG compliant and should be refactored. For example, the beginning should be like this:

"Cisco-IOS-XE-native:native": # top level container must include module name as a prefix
    interface:
      GigabitEthernet: # need to follow YANG model names of nodes
        - name: "2"
description: Configured via ydk
...

Note. I did not test neither the script above nor tried to refactor your config file; leaving it up to you.

Yan Gorelik
YDK Solutions

Got it. My idea was to simplify YANG model as much as possible to allow people not familiar with this to work with this but I guess it is too complex.
I'll try to implement the approach you proposed.

How should I fill Empty() values?

If I leave keys empty in YAML I get this JSON:

{
  "Cisco-IOS-XE-native:native": {
    "interface": {
      "GigabitEthernet": [
        {
          "name": "2",
          "description": "Configured via ydk",
          "ip": {
            "address": {
              "primary": {
                "address": "172.16.2.0",
                "mask": "255.255.255.254"
              },
              "secondary": [
                {
                  "address": "192.168.20.1",
                  "mask": "255.255.255.254",
                  "secondary": null
                },
                {
                  "address": "192.168.20.3",
                  "mask": "255.255.255.254",
                  "secondary": null
                }
              ]
            }
          }
        },

But binding creation fails with:

2019-12-18 15:58:39,861 - ydk - ERROR - Data is invalid according to the yang model. Libyang error: Invalid JSON data (unexpected value). Path: '/Cisco-IOS-XE-native:native/interface/GigabitEthernet[name='2']/ip/address/secondary[address='192.168.20.1']/secondary'
2019-12-18 15:58:39,861 - ydk - ERROR - Parsing failed with message Invalid JSON data (unexpected value).
Traceback (most recent call last):
  File "xe_config_native_json.py", line 27, in <module>
    binding = codec.decode(codec_provider, json_str)
  File "/usr/local/lib/python3.6/dist-packages/ydk/errors/error_handler.py", line 112, in helper
    return func(self, provider, entity, *args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/ydk/services/codec_service.py", line 140, in decode
    return self._decode(provider, payload_holder, subtree)
  File "/usr/local/lib/python3.6/dist-packages/ydk/services/codec_service.py", line 174, in _decode
    root_data_node = codec_service.decode(root_schema, payload, provider.encoding)
RuntimeError: YCoreError: YCodecError:Invalid JSON data (unexpected value).. Path: /Cisco-IOS-XE-native:native/interface/GigabitEthernet[name='2']/ip/address/secondary[address='192.168.20.1']/secondary

 

And how to apply YFilter to containers?

 

Leaf secondary is not mandatory, so you simply remove it from configuration. I do not understand, how this leaf got into your YAML configuration when it is not in the YANG model. And that is why you are getting the error message.

The Empty() value is represented by empty string, not NULL.

The filter API is described here. The filters are used to associate specific YANG node with Netconf operation; most commonly used filters are Read and Delete. The last is used with CRUD operation update to retain a configuration tree while deleting marked node. You can find multiple examples of filter application in public documentation and ydk-gen unit tests.

Yan Gorelik
YDK Solutions

I look at the model and it is said that secondary is mandatory attribute.
https://github.com/YangModels/yang/blob/f2496fca75fee74036ea4de9b29d64314bbdf7dc/vendor/cisco/xe/1693/Cisco-IOS-XE-interfaces.yang#L1205

If I remove it from yml I have:

2019-12-19 17:24:10,188 - ydk - INFO - ============= Sending RPC to device =============
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <target>
    <running/>
  </target>
  <config><native xmlns="http://cisco.com/ns/yang/Cisco-IOS-XE-native" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="merge">
  <interface>
    <GigabitEthernet>
      <name>2</name>
      <description>Configured via ydk</description>
      <ip>
        <address>
          <secondary>
            <address>192.168.20.1</address>
            <mask>255.255.255.254</mask>
          </secondary>
          <secondary>
            <address>192.168.20.3</address>
            <mask>255.255.255.254</mask>
          </secondary>
          <primary>
            <address>172.16.2.0</address>
            <mask>255.255.255.254</mask>
          </primary>
        </address>
      </ip>
    </GigabitEthernet>
    <GigabitEthernet>
      <name>3</name>
      <description>Configured via ydk</description>
      <ip>
        <address>
          <primary>
            <address>172.16.3.0</address>
            <mask>255.255.255.254</mask>
          </primary>
        </address>
      </ip>
    </GigabitEthernet>
    <GigabitEthernet>
      <name>4</name>
      <description>Configured via ydk</description>
      <ip>
        <address>
          <primary>
            <address>172.16.4.0</address>
            <mask>255.255.255.254</mask>
          </primary>
        </address>
      </ip>
    </GigabitEthernet>
    <Loopback>
      <name>0</name>
      <description>ROUTER ID</description>
      <ip>
        <address>
          <primary>
            <address>1.1.1.6</address>
            <mask>255.255.255.255</mask>
          </primary>
        </address>
      </ip>
    </Loopback>
  </interface>
</native>
</config>
</edit-config>
</rpc>
2019-12-19 17:24:10,741 - ydk - INFO - ============= Received RPC from device =============
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="2">
  <rpc-error>
    <error-type>application</error-type>
    <error-tag>data-missing</error-tag>
    <error-severity>error</error-severity>
    <error-path xmlns:ios="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
    /rpc/edit-config/config/ios:native/ios:interface/ios:GigabitEthernet[ios:name='2']/ios:ip/ios:address/ios:secondary[ios:address='192.168.20.3']/ios:secondary
  </error-path>
    <error-message xml:lang="en">/native/interface/GigabitEthernet[name='2']/ip/address/secondary[address='192.168.20.3']/secondary is not configured</error-message>
    <error-info>
      <bad-element>secondary</bad-element>
    </error-info>
  </rpc-error>
</rpc-reply>

2019-12-19 17:24:10,742 - ydk - ERROR - RPC error occurred:
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="2">
  <rpc-error>
    <error-type>application</error-type>
    <error-tag>data-missing</error-tag>
    <error-severity>error</error-severity>
    <error-path xmlns:ios="http://cisco.com/ns/yang/Cisco-IOS-XE-native">
    /rpc/edit-config/config/ios:native/ios:interface/ios:GigabitEthernet[ios:name='2']/ios:ip/ios:address/ios:secondary[ios:address='192.168.20.3']/ios:secondary
  </error-path>
    <error-message xml:lang="en">/native/interface/GigabitEthernet[name='2']/ip/address/secondary[address='192.168.20.3']/secondary is not configured</error-message>
    <error-info>
      <bad-element>secondary</bad-element>
    </error-info>
  </rpc-error>
</rpc-reply>

Traceback (most recent call last):
  File "xe_config_native_json.py", line 38, in <module>
    crud.create(provider, binding)
  File "/usr/local/lib/python3.6/dist-packages/ydk/errors/error_handler.py", line 112, in helper
    return func(self, provider, entity, *args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/ydk/services/crud_service.py", line 49, in create
    return _crud_update(provider, entity, self._crud.create)
  File "/usr/local/lib/python3.6/dist-packages/ydk/services/crud_service.py", line 70, in _crud_update
    return crud_call(provider, entity)
  File "/usr/lib/python3.6/contextlib.py", line 99, in __exit__
    self.gen.throw(type, value, traceback)
  File "/usr/local/lib/python3.6/dist-packages/ydk/errors/error_handler.py", line 82, in handle_runtime_error
    _raise(_exc)
  File "/usr/local/lib/python3.6/dist-packages/ydk/errors/error_handler.py", line 54, in _raise
    exec("raise exc from None")
  File "<string>", line 1, in <module>
ydk.errors.YServiceProviderError:  /native/interface/GigabitEthernet[name='2']/ip/address/secondary[address='192.168.20.3']/secondary is not configured
2019-12-19 17:24:11,290 - ydk - INFO - Disconnected from device

Have you tried to set 'secondary' to empty string?
Yan Gorelik
YDK Solutions

Yes. After YAML traqnsformed in JSON it looks like this:

{
  "Cisco-IOS-XE-native:native": {
    "interface": {
      "GigabitEthernet": [
        {
          "name": "2",
          "description": "Configured via ydk",
          "ip": {
            "address": {
              "primary": {
                "address": "172.16.2.0",
                "mask": "255.255.255.254"
              },
              "secondary": [
                {
                  "address": "192.168.20.1",
                  "mask": "255.255.255.254",
                  "secondary": null
                },
                {
                  "address": "192.168.20.3",
                  "mask": "255.255.255.254",
                  "secondary": null
                }
              ]
            }
          }
        },

And it fails with:

2019-12-20 10:46:09,508 - ydk - ERROR - Data is invalid according to the yang model. Libyang error: Invalid JSON data (unexpected value). Path: '/Cisco-IOS-XE-native:native/interface/GigabitEthernet[name='2']/ip/address/secondary[address='192.168.20.1']/secondary'
2019-12-20 10:46:09,508 - ydk - ERROR - Parsing failed with message Invalid JSON data (unexpected value).
Traceback (most recent call last):
  File "xe_config_native_json.py", line 27, in <module>
    binding = codec.decode(codec_provider, json_str)
  File "/usr/local/lib/python3.6/dist-packages/ydk/errors/error_handler.py", line 112, in helper
    return func(self, provider, entity, *args, **kwargs)
  File "/usr/local/lib/python3.6/dist-packages/ydk/services/codec_service.py", line 140, in decode
    return self._decode(provider, payload_holder, subtree)
  File "/usr/local/lib/python3.6/dist-packages/ydk/services/codec_service.py", line 174, in _decode
    root_data_node = codec_service.decode(root_schema, payload, provider.encoding)
RuntimeError: YCoreError: YCodecError:Invalid JSON data (unexpected value).. Path: /Cisco-IOS-XE-native:native/interface/GigabitEthernet[name='2']/ip/address/secondary[address='192.168.20.1']/secondary

Here is my script after debugging:

import yaml
import json

from ydk.services import CodecService
from ydk.providers import CodecServiceProvider

if __name__ == '__main__':
    with open("test_yaml_config.yml", 'r') as yaml_config:
        yaml_object = yaml.safe_load(yaml_config)

    json_config = json.dumps(yaml_object['config'], indent=2).replace('null', '[null]')
    print("\nGOT INITIAL JSON CONFIGURATION:\n%s" % json_config)

    codec = CodecService()
    codec_provider = CodecServiceProvider(type='json')
    config_entity = codec.decode(codec_provider, json_config)

    # Back to json
    json_payload = codec.encode(codec_provider, config_entity)
    print("\nJSON CONFIGURATION AFTER ENTITY DECODE:\n%s" % json_payload)

    # Back to yaml  , default_flow_style=False
    yaml_payload = yaml.dump(json.loads(json_payload)).replace('null', '')
    print("\nYAML CONFIGURATION AFTER JSON CONVERSION:\n%s" % yaml_payload)

The output:

(venv) Yakovs-Air:tests ygorelik$ python test_yaml_codec.py 

GOT INITIAL JSON CONFIGURATION:

{

  "Cisco-IOS-XE-native:native": {

    "interface": {

      "GigabitEthernet": [

        {

          "name": "2",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.2.0",

                "mask": "255.255.255.254"

              },

              "secondary": [

                {

                  "address": "192.168.20.1",

                  "mask": "255.255.255.254",

                  "secondary": [null]

                },

                {

                  "address": "192.168.20.3",

                  "mask": "255.255.255.254",

                  "secondary": [null]

                }

              ]

            }

          }

        },

        {

          "name": "3",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.3.0",

                "mask": "255.255.255.254"

              }

            }

          }

        },

        {

          "name": "4",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.4.0",

                "mask": "255.255.255.254"

              }

            }

          }

        }

      ],

      "Loopback": [

        {

          "name": 0,

          "description": "ROUTER ID",

          "ip": {

            "address": {

              "primary": {

                "address": "1.1.1.6",

                "mask": "255.255.255.255"

              }

            }

          }

        }

      ]

    }

  }

}

 

JSON CONFIGURATION AFTER ENTITY DECODE:

{

  "Cisco-IOS-XE-native:native": {

    "interface": {

      "GigabitEthernet": [

        {

          "name": "2",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "secondary": [

                {

                  "address": "192.168.20.1",

                  "mask": "255.255.255.254",

                  "secondary": [null]

                },

                {

                  "address": "192.168.20.3",

                  "mask": "255.255.255.254",

                  "secondary": [null]

                }

              ],

              "primary": {

                "address": "172.16.2.0",

                "mask": "255.255.255.254"

              }

            }

          }

        },

        {

          "name": "3",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.3.0",

                "mask": "255.255.255.254"

              }

            }

          }

        },

        {

          "name": "4",

          "description": "Configured via ydk",

          "ip": {

            "address": {

              "primary": {

                "address": "172.16.4.0",

                "mask": "255.255.255.254"

              }

            }

          }

        }

      ],

      "Loopback": [

        {

          "name": 0,

          "description": "ROUTER ID",

          "ip": {

            "address": {

              "primary": {

                "address": "1.1.1.6",

                "mask": "255.255.255.255"

              }

            }

          }

        }

      ]

    }

  }

}

 

YAML CONFIGURATION AFTER JSON CONVERSION:

Cisco-IOS-XE-native:native:

  interface:

    GigabitEthernet:

    - description: Configured via ydk

      ip:

        address:

          primary:

            address: 172.16.2.0

            mask: 255.255.255.254

          secondary:

          - address: 192.168.20.1

            mask: 255.255.255.254

            secondary:

            - 

          - address: 192.168.20.3

            mask: 255.255.255.254

            secondary:

            - 

      name: '2'

    - description: Configured via ydk

      ip:

        address:

          primary:

            address: 172.16.3.0

            mask: 255.255.255.254

      name: '3'

    - description: Configured via ydk

      ip:

        address:

          primary:

            address: 172.16.4.0

            mask: 255.255.255.254

      name: '4'

    Loopback:

    - description: ROUTER ID

      ip:

        address:

          primary:

            address: 1.1.1.6

            mask: 255.255.255.255

      name: 0

 

Now you have full cycle of encoding and decoding using YAML encoding.

 

Yan Gorelik
YDK Solutions

I didn't get why but this 

replace('null', '[null]')

transformation did something that CodecService was able to convert JSON to valid XML. 

Getting Started

Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community: