CiscoDevNet / ydk-gen

Generate model-driven APIs from YANG models
http://ciscodevnet.github.io/ydk-gen/
Apache License 2.0
136 stars 74 forks source link

Wrong RPC for reading entire list with CRUD Service #1079

Closed ygorelik closed 1 year ago

ygorelik commented 1 year ago

Issue Description

Let's say we have this YANG model (taken from YDK unit test model ydktest-sanity.yang):

container runner {
   container one_list {
       list ldata {
       key "number";
       leaf number {
           type int16;
       }
   }
}

When entire list is requested over the Netconf, the RPC should explicitly include the list component, like this:

<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <filter>
    <runner xmlns="http://cisco.com/ns/yang/ydktest-sanity">
      <one-list>
        <ldata/>
      </one-list>
    </runner>
</filter>
</get>
</rpc>

However the RPC in the below script run contains only top level container, which means the entire structure under the top level container will be retrieved:

<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
    <filter>
      <runner xmlns="http://cisco.com/ns/yang/ydktest-sanity"/>
    </filter>
  </get>
</rpc>

Test Script

The test script uses YDK unit test environment including ydktest model bundle and confd.

import sys
import unittest
import logging

from ydk.providers import NetconfServiceProvider
from ydk.services import CRUDService
from ydk.filters import YFilter

from ydk.models.ydktest import ydktest_sanity as ysanity

def test():

    crud = CRUDService()
    ncc = NetconfServiceProvider('127.0.0.1', 'admin', 'admin', 12022)

    # Create the list
    r1 = ysanity.Runner()
    l1, l2 = ysanity.Runner.OneList.Ldata(), ysanity.Runner.OneList.Ldata()
    l1.number, l2.number = 1, 2
    r1.one_list.ldata.extend([l1, l2])
    crud.create(ncc, r1)

    # Read the list
    r2 = ysanity.Runner()
    r2.one_list.ldata.yfilter = YFilter.read
    runner_read = crud.read(ncc, r2)

if __name__ == '__main__':
    log = logging.getLogger('ydk')
    log.setLevel(logging.INFO)
    handler = logging.StreamHandler()
    log.addHandler(handler)

    test()

Logs

Path where models are to be downloaded: /Users/ygorelik/.ydk/127.0.0.1
Connected to 127.0.0.1 on port 12022 using ssh with timeout of -1
Executing CRUD create operation on [ydktest-sanity:runner]
Executing 'edit-config' RPC on [ydktest-sanity:runner]
============= 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>
    <candidate/>
  </target>
  <config><runner xmlns="http://cisco.com/ns/yang/ydktest-sanity" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="merge">
  <one-list>
    <ldata>
      <number>1</number>
    </ldata>
    <ldata>
      <number>2</number>
    </ldata>
  </one-list>
</runner>
</config>
</edit-config>
</rpc>
============= 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">
  <ok/>
</rpc-reply>

Executing 'commit' RPC
============= Sending RPC to device =============
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
</rpc>
============= 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="3">
  <ok/>
</rpc-reply>

Operation succeeded
Executing CRUD read operation on [ydktest-sanity:runner]
Executing 'get' RPC on [ydktest-sanity:runner]
============= Sending RPC to device =============
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <filter><runner xmlns="http://cisco.com/ns/yang/ydktest-sanity"/></filter>
</get>
</rpc>
============= 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="4">
  <data>
    <runner xmlns="http://cisco.com/ns/yang/ydktest-sanity">
      <one-list>
        <ldata>
          <number>1</number>
        </ldata>
        <ldata>
          <number>2</number>
        </ldata>
      </one-list>
    </runner>
  </data>
</rpc-reply>

Disconnected from device

Note, the unit test passes only because only one-list model component is present under runner.

System Information

YDK-0.8.6.4

ygorelik commented 1 year ago

Actually the correct way of reading the list would be this:

    r2 = ysanity.Runner()
    data = ysanity.Runner.OneList.Ldata()
    data.number = YFilter.read  # Assign read filter to the key leaf 
    r2.one_list.ldata.append(data)
    runner_read = crud.read(ncc, r2)

With that approach applied to test script:

import sys
import unittest
import logging

from ydk.providers import NetconfServiceProvider
from ydk.services import CRUDService
from ydk.filters import YFilter

from ydk.models.ydktest import ydktest_sanity as ysanity

def test():

    crud = CRUDService()
    ncc = NetconfServiceProvider('127.0.0.1', 'admin', 'admin', 12022)

    # Create the list
    r1 = ysanity.Runner()
    l1, l2 = ysanity.Runner.OneList.Ldata(), ysanity.Runner.OneList.Ldata()
    l1.number, l2.number = 1, 2
    l1.name, l2.name = 'a', 'b'
    r1.one_list.ldata.extend([l1, l2])
    crud.create(ncc, r1)

    # Read the list ldata
    r2 = ysanity.Runner()
    data = ysanity.Runner.OneList.Ldata()
    data.number = YFilter.read
    r2.one_list.ldata.append(data)
    runner_read = crud.read(ncc, r2)
    assert len(runner_read.one_list.ldata) == 2, f"ldata list length is {len(runner_read.one_list.ldata)}"
    assert runner_read.one_list.ldata['1'].name is None, "ldata.name is expected to be None"

    # Read container one-list
    r3 = ysanity.Runner()
    r3.one_list.yfilter = YFilter.read
    runner_read = crud.read(ncc, r3)
    assert len(runner_read.one_list.ldata) == 2, \
        f"expected ldata length is 2, but it is {len(runner_read.one_list.ldata)}"
    assert runner_read.one_list.ldata['1'].name is not None, "ldata.name is expected to be not None"

if __name__ == '__main__':
    log = logging.getLogger('ydk')
    log.setLevel(logging.INFO)
    handler = logging.StreamHandler()
    log.addHandler(handler)

    test()

And the result is showing correct RPC's and device responses:

Path where models are to be downloaded: /Users/ygorelik/.ydk/127.0.0.1
Connected to 127.0.0.1 on port 12022 using ssh with timeout of -1
Executing CRUD create operation on [ydktest-sanity:runner]
Executing 'edit-config' RPC on [ydktest-sanity:runner]
============= 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>
    <candidate/>
  </target>
  <config><runner xmlns="http://cisco.com/ns/yang/ydktest-sanity" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" nc:operation="merge">
  <one-list>
    <ldata>
      <number>1</number>
      <name>a</name>
    </ldata>
    <ldata>
      <number>2</number>
      <name>b</name>
    </ldata>
  </one-list>
</runner>
</config>
</edit-config>
</rpc>
============= 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">
  <ok/>
</rpc-reply>

Executing 'commit' RPC
============= Sending RPC to device =============
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><commit xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"/>
</rpc>
============= 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="3">
  <ok/>
</rpc-reply>

Operation succeeded
Executing CRUD read operation on [ydktest-sanity:runner]
Executing 'get' RPC on [ydktest-sanity:runner]
============= Sending RPC to device =============
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <filter><runner xmlns="http://cisco.com/ns/yang/ydktest-sanity">
  <one-list>
    <ldata>
      <number/>
    </ldata>
  </one-list>
</runner></filter>
</get>
</rpc>
============= 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="4">
  <data>
    <runner xmlns="http://cisco.com/ns/yang/ydktest-sanity">
      <one-list>
        <ldata>
          <number>1</number>
        </ldata>
        <ldata>
          <number>2</number>
        </ldata>
      </one-list>
    </runner>
  </data>
</rpc-reply>

Executing CRUD read operation on [ydktest-sanity:runner]
Executing 'get' RPC on [ydktest-sanity:runner]
============= Sending RPC to device =============
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><get xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
  <filter><runner xmlns="http://cisco.com/ns/yang/ydktest-sanity">
  <one-list/>
</runner></filter>
</get>
</rpc>
============= 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="5">
  <data>
    <runner xmlns="http://cisco.com/ns/yang/ydktest-sanity">
      <one-list>
        <ldata>
          <number>1</number>
          <name>a</name>
        </ldata>
        <ldata>
          <number>2</number>
          <name>b</name>
        </ldata>
      </one-list>
    </runner>
  </data>
</rpc-reply>

Disconnected from device