Skip to content

Commit

Permalink
Resolved issue CiscoDevNet#854 for C++ and Python
Browse files Browse the repository at this point in the history
  • Loading branch information
Yan Gorelik committed Jan 16, 2019
1 parent 06d5197 commit 81b3280
Show file tree
Hide file tree
Showing 9 changed files with 164 additions and 15 deletions.
20 changes: 20 additions & 0 deletions sdk/cpp/core/src/entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,25 @@ bool Entity::operator != (Entity & other) const
return false;
}

std::string
Entity::get_ylist_key() const
{
string key = ylist_key;
if (!key.empty() && ylist_key_names.size() == 0) {
try {
// This is keyless entry of a list. Expected value: 1000000 + key
auto index = std::stoi(ylist_key) % 1000000;
ostringstream os;
os << index;
key = os.str();
}
catch (const std::exception& ex) {
YLOG_ERROR("Failed to convert key '{}' to string", ylist_key);
}
}
return key;
}

std::ostream& operator<< (std::ostream& stream, Entity& entity)
{
stream<<get_entity_path(entity, entity.parent);
Expand Down Expand Up @@ -246,4 +265,5 @@ std::ostream& operator<< (std::ostream& stream, const EntityPath& path)
stream << " )";
return stream;
}

}
3 changes: 3 additions & 0 deletions sdk/cpp/core/src/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ class Entity {
bool is_top_level_class;
bool has_list_ancestor;
std::vector<std::string> ylist_key_names;
std::string ylist_key;

std::string get_ylist_key() const;
};

class Bits {
Expand Down
4 changes: 3 additions & 1 deletion sdk/cpp/core/src/value_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,8 @@ YList::build_key(shared_ptr<Entity> ep)
key = value_buffer.str();
if (key.length() == 0) {
// No key list or no matching key, use internal counter
value_buffer << counter++;
counter++;
value_buffer << counter;
key = value_buffer.str();
}
return key;
Expand All @@ -324,6 +325,7 @@ YList::append(shared_ptr<Entity> ep)
key_vector.push_back(key);
}
entity_map[key] = ep;
ep->ylist_key = key;
}

void
Expand Down
32 changes: 32 additions & 0 deletions sdk/cpp/core/tests/test_codec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,35 @@ TEST_CASE( "decode_multiple_json" )
auto json_str = s.encode(*dn, EncodingFormat::JSON, true);
REQUIRE(json_str == json_int_payload + json_bgp_payload);
}

TEST_CASE("test_no_key_list_path") {
std::string searchdir{TEST_HOME};
mock::MockSession sp{searchdir, test_openconfig};
auto & schema = sp.get_root_schema();
ydk::path::Codec codec{};

auto & runner = schema.create_datanode("ydktest-sanity:runner", "");
runner.create_datanode("no-key-list[1]/test", "t1");
runner.create_datanode("no-key-list[2]/test", "t2");

auto xml = codec.encode(runner, ydk::EncodingFormat::XML, true);
auto expected = R"(<runner xmlns="http://cisco.com/ns/yang/ydktest-sanity">
<no-key-list>
<test>t1</test>
</no-key-list>
<no-key-list>
<test>t2</test>
</no-key-list>
</runner>
)";
REQUIRE(xml== expected);

auto dn = codec.decode(schema, xml, ydk::EncodingFormat::XML);
REQUIRE(dn != nullptr);
auto real_dn = dn->get_children()[0];
REQUIRE(real_dn != nullptr);

auto xml_rt = codec.encode(*real_dn, ydk::EncodingFormat::XML, true);
REQUIRE(xml == xml_rt);
}

36 changes: 31 additions & 5 deletions sdk/cpp/tests/test_sanity_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
#include <sstream>
#include <string.h>

#include <ydk/codec_provider.hpp>
#include <ydk/codec_service.hpp>
#include "ydk/netconf_provider.hpp"
#include "ydk/crud_service.hpp"
#include "ydk_ydktest/ydktest_sanity.hpp"
Expand Down Expand Up @@ -767,19 +769,43 @@ TEST_CASE("test_ylist_two_keys") {
}

TEST_CASE("test_ylist_no_keys") {
auto runner = make_shared<ydktest_sanity::Runner>();
auto runner = ydktest_sanity::Runner();
auto t1 = make_shared<ydktest_sanity::Runner::NoKeyList> (); t1->test = "t1";
auto t2 = make_shared<ydktest_sanity::Runner::NoKeyList> (); t2->test = "t2";
auto t3 = make_shared<ydktest_sanity::Runner::NoKeyList> (); t3->test = "t3";
runner->no_key_list.extend({t1, t2, t3});
runner.no_key_list.extend({t1, t2, t3});

auto testKeys = runner->no_key_list.keys();
REQUIRE(vector_to_string(testKeys) == R"("1000000", "1000001", "1000002")");
auto testKeys = runner.no_key_list.keys();
REQUIRE(vector_to_string(testKeys) == R"("1000001", "1000002", "1000003")");

string count{};
for (auto ent : runner->no_key_list.entities()) {
for (auto ent : runner.no_key_list.entities()) {
auto elem = dynamic_cast<ydktest_sanity::Runner::NoKeyList*> (ent.get());
count += elem->test;
}
REQUIRE(count == "t1t2t3");

CodecServiceProvider codec_provider{EncodingFormat::JSON};
CodecService codec_service{};

string json = codec_service.encode(codec_provider, runner, true);
auto expected = R"({
"ydktest-sanity:runner": {
"no-key-list": [
{
"test": "t1"
},
{
"test": "t2"
},
{
"test": "t3"
}
]
}
}
)";

auto runner_decoded = codec_service.decode(codec_provider, json, std::make_unique<ydktest_sanity::Runner>());
CHECK(*runner_decoded == runner);
}
29 changes: 29 additions & 0 deletions sdk/go/core/tests/service_codec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,22 @@ const (
xmlOCPatternPayload = `<?xml version="1.0"?><oc-A xmlns="http://cisco.com/ns/yang/oc-pattern">
<a>Hello</a>
</oc-A>
`
jsonNoKeyList = `{
"ydktest-sanity:runner": {
"no-key-list": [
{
"test": "t1"
},
{
"test": "t2"
},
{
"test": "t3"
}
]
}
}
`
)

Expand Down Expand Up @@ -528,6 +544,19 @@ func (suite *CodecTestSuite) TestOneKeyList() {
}
}

func (suite *CodecTestSuite) TestListNoKeys() {
runner := ysanity.Runner{}
t1 := ysanity.Runner_NoKeyList{Test: "t1"}
t2 := ysanity.Runner_NoKeyList{Test: "t2"}
t3 := ysanity.Runner_NoKeyList{Test: "t3"}
runner.NoKeyList = []*ysanity.Runner_NoKeyList {&t1, &t2, &t3}

suite.Provider.Encoding = encoding.JSON
payload := suite.Codec.Encode(&suite.Provider, &runner)
fmt.Printf("\nGot payload:\n%s", payload)
//suite.Equal(jsonNoKeyList, payload)
}

func TestCodecTestSuite(t *testing.T) {
if testing.Verbose() {
ydk.EnableLogging(ydk.Debug)
Expand Down
23 changes: 23 additions & 0 deletions sdk/python/core/tests/test_ydk_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,29 @@ def test_ylist_runner_no_key_list(self):
for elem in runner.no_key_list:
count += elem.test
self.assertEqual(count, 't1t2t3')

from ydk.providers import CodecServiceProvider
from ydk.services import CodecService
provider = CodecServiceProvider(type='xml')
codec = CodecService()

payload = codec.encode(provider, runner)
expected = '''<runner xmlns="http://cisco.com/ns/yang/ydktest-sanity">
<no-key-list>
<test>t1</test>
</no-key-list>
<no-key-list>
<test>t2</test>
</no-key-list>
<no-key-list>
<test>t3</test>
</no-key-list>
</runner>
'''
self.assertEqual(payload, expected)

runner_decode = codec.decode(provider, payload)
self.assertEqual(runner_decode, runner)

if __name__ == '__main__':
suite = unittest.TestSuite()
Expand Down
11 changes: 10 additions & 1 deletion sdk/python/core/ydk/types/py_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ def __init__(self):
super(Entity, self).__init__()
self._is_frozen = False
self.parent = None
self.ylist_key = None
self.logger = logging.getLogger("ydk.types.EntityCollection")
self._local_refs = {}
self._children_name_map = OrderedDict()
Expand Down Expand Up @@ -311,6 +312,13 @@ def get_segment_path(self):
else:
# should never get here
return self._segment_path()
elif self.ylist_key is not None:
# the entity is member of keyless YList
try:
index = int(self.ylist_key) % 1000000
except:
index = self.ylist_key
path += '[%s]' % index
return path

def path(self):
Expand Down Expand Up @@ -578,8 +586,8 @@ def _key(self, entity):
break
key_list.append(attr)
if len(key_list) == 0:
key = format(self.counter)
self.counter += 1
key = format(self.counter)
elif len(key_list) == 1:
key = key_list[0]
if not isinstance(key, str):
Expand All @@ -600,6 +608,7 @@ def append(self, entities):
elif isinstance(entities, Entity):
key = self._key(entities)
self._cache_dict[key] = entities
entities.ylist_key = key
else:
msg = "Argument %s is not supported by YList class; data ignored"%type(entities)
self._log_error_and_raise_exception(msg, YInvalidArgumentError)
Expand Down
21 changes: 13 additions & 8 deletions ydkgen/printer/cpp/class_get_entity_path_printer.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"""
from ydkgen.api_model import Package
from ydkgen.common import has_list_ancestor, is_top_level_class
from ydkgen.common import has_list_ancestor, is_top_level_class, is_list_element


def get_leafs_children(clazz, leafs, children):
Expand Down Expand Up @@ -212,13 +212,18 @@ def _print_get_ydk_segment_path_body(self, clazz):
self.ctx.writeln('path_buffer << %s' % (path))

key_props = clazz.get_key_props()
for key_prop in key_props:
predicate = ''
if key_prop.stmt.i_module.arg != clazz.stmt.i_module.arg:
predicate += key_prop.stmt.i_module.arg
predicate += ':'
predicate += key_prop.stmt.arg
self.ctx.writeln('ADD_KEY_TOKEN(%s, "%s");' % (key_prop.name, predicate))
if len(key_props) > 0:
for key_prop in key_props:
predicate = ''
if key_prop.stmt.i_module.arg != clazz.stmt.i_module.arg:
predicate += key_prop.stmt.i_module.arg
predicate += ':'
predicate += key_prop.stmt.arg
self.ctx.writeln('ADD_KEY_TOKEN(%s, "%s");' % (key_prop.name, predicate))
elif is_list_element(clazz):
# list element with no keys
predicate = '"[" << get_ylist_key() << "]";'
self.ctx.writeln('path_buffer << %s' % (predicate))
self.ctx.writeln('return path_buffer.str();')

def _print_get_ydk_segment_path_trailer(self, clazz):
Expand Down

0 comments on commit 81b3280

Please sign in to comment.