12-12-2019 06:52 AM - edited 12-12-2019 07:17 AM
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?
Solved! Go to Solution.
12-20-2019 01:15 PM
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.
12-12-2019 01:36 PM
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.
12-12-2019 11:56 PM
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.
12-13-2019 09:19 AM
Could you please share reference to solution that you have found. Maybe it is not too difficult to revive it.
12-14-2019 09:58 PM
12-17-2019 02:39 AM
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 =(
12-17-2019 02:13 PM
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.
12-18-2019 01:08 AM
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.
12-18-2019 04:46 AM - edited 12-18-2019 05:05 AM
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?
12-18-2019 09:49 AM
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.
12-19-2019 06:25 AM
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
12-19-2019 07:25 AM
12-19-2019 11:47 PM
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
12-20-2019 01:15 PM
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.
12-23-2019 01:45 AM
I didn't get why but this
replace('null', '[null]')
transformation did something that CodecService was able to convert JSON to valid XML.
Discover and save your favorite ideas. Come back to expert answers, step-by-step guides, recent topics, and more.
New here? Get started with these tips. How to use Community New member guide