From 8d0b509cee8da73981e0a98ee75107b5c7acef6c Mon Sep 17 00:00:00 2001 From: Yan Gorelik Date: Thu, 12 Jul 2018 10:22:41 -0700 Subject: [PATCH 1/8] Introduced Codec feature to decode multiple JSON payload --- sdk/cpp/core/src/path/path.cpp | 40 +++++++++++++++++++++++++ sdk/cpp/core/src/path_api.hpp | 1 + sdk/cpp/core/tests/test_codec.cpp | 46 ++++++++++++++++++++++------- sdk/cpp/tests/test_sanity_codec.cpp | 30 +++++++++---------- 4 files changed, 92 insertions(+), 25 deletions(-) diff --git a/sdk/cpp/core/src/path/path.cpp b/sdk/cpp/core/src/path/path.cpp index f48966deb..b4b26bd09 100644 --- a/sdk/cpp/core/src/path/path.cpp +++ b/sdk/cpp/core/src/path/path.cpp @@ -312,4 +312,44 @@ ydk::path::Codec::decode_rpc_output(RootSchemaNode & root_schema, const std::str return perform_decode(rs_impl, root); } +std::shared_ptr +ydk::path::Codec::decode_json_output(RootSchemaNode & root_schema, const std::vector & buffer_list) +{ + YLOG_DEBUG("ydk::path::Codec: Decoding JSON formatted list of payloads"); + + RootSchemaNodeImpl & rs_impl = get_root_schema_impl(root_schema); + + ydk::path::RootDataImpl* rd = new ydk::path::RootDataImpl{rs_impl, rs_impl.m_ctx, "/"}; + lyd_node* prev_sibling = nullptr; + lyd_node* first_sibling = nullptr; + + for (auto buffer : buffer_list) { + rs_impl.populate_new_schemas_from_payload(buffer, ydk::EncodingFormat::JSON); + + struct lyd_node *dnode = lyd_parse_mem(rs_impl.m_ctx, buffer.c_str(), LYD_JSON, LYD_OPT_TRUSTED | LYD_OPT_GET); + if (dnode == nullptr || ly_errno) { + YLOG_ERROR( "Parsing failed with message {}", ly_errmsg()); + throw(YCodecError{YCodecError::Error::XML_INVAL}); + } + + // Attach first node to the root + if (!rd->m_node) { + rd->m_node = dnode; + first_sibling = dnode; + } + + // Populate children map and connect siblings + rd->child_map.insert(std::make_pair(dnode, std::make_shared(rd, dnode, nullptr))); + + if (prev_sibling) { + prev_sibling->next = dnode; + dnode->prev = prev_sibling; + } + prev_sibling = dnode; + first_sibling->prev = dnode; + } + + return std::shared_ptr(rd); +} + #undef SLASH_CHAR diff --git a/sdk/cpp/core/src/path_api.hpp b/sdk/cpp/core/src/path_api.hpp index c8be72730..bebc49bb2 100644 --- a/sdk/cpp/core/src/path_api.hpp +++ b/sdk/cpp/core/src/path_api.hpp @@ -305,6 +305,7 @@ class Codec /// std::shared_ptr decode(RootSchemaNode & root_schema, const std::string& buffer, EncodingFormat format); std::shared_ptr decode_rpc_output(RootSchemaNode & root_schema, const std::string& buffer, const std:: string & rpc_path, EncodingFormat format); + std::shared_ptr decode_json_output(RootSchemaNode & root_schema, const std::vector & buffer_list); }; /// diff --git a/sdk/cpp/core/tests/test_codec.cpp b/sdk/cpp/core/tests/test_codec.cpp index ba19eb0fc..94c34e2a1 100644 --- a/sdk/cpp/core/tests/test_codec.cpp +++ b/sdk/cpp/core/tests/test_codec.cpp @@ -119,18 +119,44 @@ TEST_CASE( "test_submodule_feature" ) auto xml_rt = s.encode(*real_dn, ydk::EncodingFormat::XML, true); REQUIRE(xml == xml_rt); } -/* -TEST_CASE( "test_codec_ok" ) + +TEST_CASE( "test_decode_multiple_json" ) { - ydk::path::Codec s{}; std::string searchdir{TEST_HOME}; mock::MockSession sp{searchdir, test_openconfig}; - auto & schema = sp.get_root_schema(); - string pl2 = ""; - auto d2 = s.decode_rpc_output(schema, pl2, "/ietf-netconf:edit-config", EncodingFormat::XML); - REQUIRE(d2!=nullptr); - auto x2 = s.encode(*d2, EncodingFormat::XML, false); - REQUIRE(x2==""); -}*/ + std::string json_int_payload = R"({ + "openconfig-interfaces:interfaces": { + "interface": [ + { + "name": "Loopback10", + "config": { + "name": "Loopback10" + } + } + ] + } +} +)"; + std::string json_bgp_payload = R"({ + "openconfig-bgp:bgp": { + "global": { + "config": { + "as": 65172 + } + } + } +} +)"; + std::vector payload_list = {json_int_payload, json_bgp_payload}; + + ydk::path::Codec s{}; + + auto rdn = s.decode_json_output(schema, payload_list); + REQUIRE(rdn != nullptr); + + auto dn = dynamic_cast (rdn.get()); + auto json_str = s.encode(*dn, EncodingFormat::JSON, true); + REQUIRE(json_str == json_int_payload + json_bgp_payload); +} diff --git a/sdk/cpp/tests/test_sanity_codec.cpp b/sdk/cpp/tests/test_sanity_codec.cpp index e9d759a01..fd6f0e78b 100644 --- a/sdk/cpp/tests/test_sanity_codec.cpp +++ b/sdk/cpp/tests/test_sanity_codec.cpp @@ -310,26 +310,26 @@ TEST_CASE("multiple_encode") } // YCoreError: YCodecError:Unknown element "oc-A".. Path: -TEST_CASE("test_oc_pattern") -{ -//TODO +//TEST_CASE("test_oc_pattern") +//{ +////TODO // CodecServiceProvider codec_provider{EncodingFormat::JSON}; // CodecService codec_service{}; -// -// auto entity = codec_service.decode(codec_provider, "{\n" -// " \"oc-pattern:oc-A\": [\n" -// " {\n" -// " \"a\": \"Hello\",\n" -// " \"B\": {\n" -// " \"b\": \"Hello\"\n" -// " }\n" -// " }\n" -// " ]\n" -// "}", make_unique()); +// string payload = R"({ +// "oc-pattern:oc-A": [ +// { +// "a": "Hello", +// "B": { +// "b": "Hello" +// } +// } +// ] +//})"; +// auto entity = codec_service.decode(codec_provider, payload, make_unique()); // // oc_pattern::OcA * entity_ptr = dynamic_cast(entity.get()); // CHECK(entity_ptr->a.get() == "Hello"); -} +//} TEST_CASE("enum_2") { From bb0eadd5ac08d5b49e27fd88d04a77ecbe85e64d Mon Sep 17 00:00:00 2001 From: Yan Gorelik Date: Fri, 13 Jul 2018 09:49:51 -0700 Subject: [PATCH 2/8] Introduced Codec feature to decode multiple JSON payload --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 33437fe0d..eab7d3fcf 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ gen-api/ scripts sdk/cpp/core/tests/confd/ydktest/cli-history sdk/python/core/ydk/models/ydktest +test/gnmi* +yang/nxos* From 8c8096e48f5c2fbe88353df246b33e1323e40cf8 Mon Sep 17 00:00:00 2001 From: Yan Date: Fri, 20 Jul 2018 15:54:57 -0400 Subject: [PATCH 3/8] Enhancement #811 for C++ --- sdk/cpp/core/docsgen/api/ydk/types.rst | 24 +++++++++++++++---- sdk/cpp/core/src/types.hpp | 3 +++ sdk/cpp/core/src/value_list.cpp | 32 ++++++++++++++++++++++++++ sdk/cpp/core/tests/test_entity.cpp | 25 ++++++++++++++++++-- 4 files changed, 77 insertions(+), 7 deletions(-) diff --git a/sdk/cpp/core/docsgen/api/ydk/types.rst b/sdk/cpp/core/docsgen/api/ydk/types.rst index 5db5b2980..f632536e4 100644 --- a/sdk/cpp/core/docsgen/api/ydk/types.rst +++ b/sdk/cpp/core/docsgen/api/ydk/types.rst @@ -111,6 +111,21 @@ YANG leaf, list and leaf-list Returns size of the list. + .. cpp:function:: std::shared_ptr pop(const std::string& key) + + Removes from `YList` an entity, which has matching `key` value. + + :param key: ``strings``, which represent list entity key(s). + :return: ``std::shared_ptr`` for the removed entity, or nullptr matching `key` value is not found. + + .. cpp:function:: std::shared_ptr pop(const std::size_t item) + + Removes from `YList` an entity, which has sequence number `item`. The sequence number is defined by the order in which the entity was added to the list. + + :param key: ``std::size_t``, which represents entity sequential number in the list (starts from 0). + :return: ``std::shared_ptr`` for the found entity. + :raises: `YInvalidArgumentError` exception if `item` value is out of bounds. + .. cpp:class:: YLeafList Concrete class that represents a YANG leaf-list, to which multiple instances of data can be appended to. @@ -145,7 +160,7 @@ YANG type Example usage ~~~~~~~~~~~~~~~ -Examples of instantiating and using objects of :cpp:class:`Entity` type are shown below +Examples of how to instantiate and use objects of :cpp:class:`Entity` type: .. code-block:: c++ :linenos: @@ -160,7 +175,7 @@ Examples of instantiating and using objects of :cpp:class:`Entity` type // Append the afi-safi to the YList instance bgp->global->afi_safis->afi_safi.append(afi_safi); -Examples of assigning values to leafs are shown below +Examples of how to assign values to leafs: .. code-block:: c++ :linenos: @@ -182,7 +197,7 @@ Examples of assigning values to leafs are shown below node->bits_value["first-position"] = true // bits node->bits_value["second-position"] = false // bits -Examples of appending values to leaf-lists are shown below +Examples of how to append values to leaf-lists: .. code-block:: c++ :linenos: @@ -202,8 +217,7 @@ Examples of appending values to leaf-lists are shown below bits_value["first-position"] = false; // bits node->bits_values.append(bits_value); // bits - -Example of accessing YList entities +Examples of how to access YList entities .. code-block:: c++ :linenos: diff --git a/sdk/cpp/core/src/types.hpp b/sdk/cpp/core/src/types.hpp index 6f4b8b6c9..59e35fa3b 100644 --- a/sdk/cpp/core/src/types.hpp +++ b/sdk/cpp/core/src/types.hpp @@ -367,6 +367,9 @@ class YList void extend(std::initializer_list> ep_list); std::string build_key(std::shared_ptr ep); + std::shared_ptr pop(const std::string& key); + std::shared_ptr pop(const std::size_t item); + private: std::vector ylist_key_names; std::map> entity_map; diff --git a/sdk/cpp/core/src/value_list.cpp b/sdk/cpp/core/src/value_list.cpp index 74ae25eb3..617e6eaf2 100644 --- a/sdk/cpp/core/src/value_list.cpp +++ b/sdk/cpp/core/src/value_list.cpp @@ -376,4 +376,36 @@ YList::len() const return key_vector.size(); } +shared_ptr +YList::pop(const string& key) +{ + for (vector::iterator it = key_vector.begin() ; it != key_vector.end(); ++it) { + if (*it == key) { + shared_ptr found = entity_map[key]; + entity_map.erase(key); + key_vector.erase(it); + return found; + } + } + return nullptr; +} + +shared_ptr +YList::pop(const std::size_t item) +{ + if (item < key_vector.size()) { + vector::iterator it = key_vector.begin(); + for (size_t i = 0; i < item; i++) it++; + shared_ptr found = entity_map[*it]; + entity_map.erase(*it); + key_vector.erase(it); + return found; + } + else { + YLOG_ERROR("Index value {} is out of range [0,{}]", item, key_vector.size()); + throw(YInvalidArgumentError{"Index value is out of range"}); + } + return nullptr; +} + } diff --git a/sdk/cpp/core/tests/test_entity.cpp b/sdk/cpp/core/tests/test_entity.cpp index 4a621016b..9fa9fac01 100644 --- a/sdk/cpp/core/tests/test_entity.cpp +++ b/sdk/cpp/core/tests/test_entity.cpp @@ -463,21 +463,42 @@ TEST_CASE("test_ylist") auto keys = ylist_0.keys(); REQUIRE(vector_to_string(keys) == R"("1000000", "1000001")"); + auto ep = ylist_0.pop(1); + REQUIRE(ep != nullptr); + auto test_entity = dynamic_cast (ep.get()); + REQUIRE(test_entity->name == test2->name); + REQUIRE(ylist_0.len() == 1); + + ep = ylist_0.pop(0); + REQUIRE(ep != nullptr); + test_entity = dynamic_cast (ep.get()); + REQUIRE(test_entity->name == test1->name); + REQUIRE(ylist_0.len() == 0); + ylist_1.extend({test1, test2}); REQUIRE(ylist_1.len() == 2); keys = ylist_1.keys(); REQUIRE(vector_to_string(keys) == R"("test1", "test2")"); - auto ep = ylist_1[0]; + ep = ylist_1[0]; REQUIRE(ep != nullptr); - auto test_entity = dynamic_cast (ep.get()); + test_entity = dynamic_cast (ep.get()); REQUIRE(test_entity->name == test1->name); ep = ylist_1["test2"]; REQUIRE(ep != nullptr); test_entity = dynamic_cast (ep.get()); REQUIRE(test_entity->name == test2->name); + + ep = ylist_1.pop("test1"); + REQUIRE(ep != nullptr); + REQUIRE(ylist_1.len() == 1); + test_entity = dynamic_cast (ep.get()); + REQUIRE(test_entity->name == test1->name); + + ep = ylist_1.pop("test1"); + REQUIRE(ep == nullptr); } //TODO Test for issue #800 to be resolved From 9480c1f9dc7273aab874d96b806ec4360ec92c58 Mon Sep 17 00:00:00 2001 From: Yan Date: Fri, 20 Jul 2018 16:00:31 -0400 Subject: [PATCH 4/8] Added logic in CRUD and Netconf services to keep order of resulting entities as it is given in filter list --- .gitignore | 3 ++ sdk/cpp/core/src/crud_service.cpp | 53 +++++++++++--------------- sdk/cpp/core/src/netconf_service.cpp | 39 +++++++++++++------ sdk/cpp/tests/test_netconf_service.cpp | 6 ++- sdk/cpp/tests/test_path_api.cpp | 1 - 5 files changed, 58 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index eab7d3fcf..5c28bf41e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ sdk/cpp/core/tests/confd/ydktest/cli-history sdk/python/core/ydk/models/ydktest test/gnmi* yang/nxos* +grpc +protobuf-3.5.0 +sdk/cpp/gnmi diff --git a/sdk/cpp/core/src/crud_service.cpp b/sdk/cpp/core/src/crud_service.cpp index 0765f2881..44115491a 100644 --- a/sdk/cpp/core/src/crud_service.cpp +++ b/sdk/cpp/core/src/crud_service.cpp @@ -183,44 +183,37 @@ execute_rpc(ydk::ServiceProvider & provider, vector & filter_list, if (rnd == nullptr) return result_list; - // Build mapping of entity path to entity; needed to calculate top_entity - map path_to_entity{}; - for (Entity* entity : filter_list) { - string internal_key = "/" + entity->get_segment_path(); - path_to_entity[internal_key] = entity; + // Build mapping of DataNode path to DataNode pointer. + // Use this mapping to retain order of filter list in results. + map> path_to_datanode{}; + for (auto dn : rnd->get_children()) { + string internal_key = dn->get_path().substr(1); + path_to_datanode[internal_key] = dn; } - // Return all children of the root node - vector> data_nodes = rnd->get_children(); - for (auto dn : data_nodes) { - string internal_key = dn->get_path(); - Entity* entity; - if(path_to_entity.find(internal_key) == path_to_entity.end()) - { - YLOG_DEBUG("Searching for filter using yang name: {}", internal_key); - bool found = false; - for(auto e:path_to_entity) - { - if(internal_key.find(e.second->yang_name) != string::npos) - { - YLOG_DEBUG("Found filter entity: {}", e.second->yang_name); - entity = e.second; - found = true; + // Build resulting list of entities + for (Entity* entity : filter_list) { + string internal_key = entity->get_segment_path(); + shared_ptr datanode = path_to_datanode[internal_key]; + if (!datanode) { + YLOG_DEBUG("Searching for datanode using entity yang name '{}'", entity->yang_name); + path_to_datanode.erase(internal_key); + for (auto dn_entry : path_to_datanode) { + if (dn_entry.first.find(entity->yang_name) != string::npos && dn_entry.second) { + datanode = dn_entry.second; break; } } - if(!found) - { - YLOG_ERROR("Could not find filter entity: {}", internal_key); - throw(YServiceProviderError{"Could not find filter entity " + internal_key}); - } } - else - { - entity = path_to_entity[internal_key]; + if (datanode) { + result_list.push_back( read_datanode(*entity, datanode)); + } + else { + YLOG_DEBUG("CRUD read operation did not return data node on entity '{}'; returning nullptr.", internal_key); + result_list.push_back(nullptr); } - result_list.push_back( read_datanode(*entity, dn)); } + return result_list; } diff --git a/sdk/cpp/core/src/netconf_service.cpp b/sdk/cpp/core/src/netconf_service.cpp index 27a738479..9b38530d0 100644 --- a/sdk/cpp/core/src/netconf_service.cpp +++ b/sdk/cpp/core/src/netconf_service.cpp @@ -270,20 +270,37 @@ get_from_list(NetconfServiceProvider& provider, DataStore source, vector path_to_entity{}; - for (Entity* entity : filter_list) { - string internal_key = "/" + entity->get_segment_path(); - path_to_entity[internal_key] = entity; + // Build mapping of DataNode path to DataNode pointer. + // Use this mapping to retain order of filter list in results. + map> path_to_datanode{}; + for (auto dn : result_datanode->get_children()) { + string internal_key = dn->get_path().substr(1); + path_to_datanode[internal_key] = dn; } - // Return all children of the root node - vector> data_nodes = result_datanode->get_children(); - for (auto dn : data_nodes) { - string internal_key = dn->get_path(); - Entity* entity = path_to_entity[internal_key]; - result_list.push_back( read_datanode(*entity, dn)); + // Build resulting list of entities + for (Entity* entity : filter_list) { + string internal_key = entity->get_segment_path(); + shared_ptr datanode = path_to_datanode[internal_key]; + if (!datanode) { + YLOG_DEBUG("Searching for datanode using entity yang name '{}'", entity->yang_name); + path_to_datanode.erase(internal_key); + for (auto dn_entry : path_to_datanode) { + if (dn_entry.first.find(entity->yang_name) != string::npos && dn_entry.second) { + datanode = dn_entry.second; + break; + } + } + } + if (datanode) { + result_list.push_back( read_datanode(*entity, datanode)); + } + else { + YLOG_DEBUG("Netconf Service 'get' operation did not return data node on entity '{}'; returning nullptr.", internal_key); + result_list.push_back(nullptr); + } } + return result_list; } diff --git a/sdk/cpp/tests/test_netconf_service.cpp b/sdk/cpp/tests/test_netconf_service.cpp index ae7a2cddb..0de06833f 100644 --- a/sdk/cpp/tests/test_netconf_service.cpp +++ b/sdk/cpp/tests/test_netconf_service.cpp @@ -135,8 +135,10 @@ TEST_CASE("get_edit_copy_config") vector copy_config_list{}; for (auto ent : get_config_list) { - //print_entity(ent, provider.get_session().get_root_schema()); - copy_config_list.push_back(ent.get()); + if (ent) { + //print_entity(ent, provider.get_session().get_root_schema()); + copy_config_list.push_back(ent.get()); + } } // Copy config to candidate diff --git a/sdk/cpp/tests/test_path_api.cpp b/sdk/cpp/tests/test_path_api.cpp index c135dbe05..53084d54e 100644 --- a/sdk/cpp/tests/test_path_api.cpp +++ b/sdk/cpp/tests/test_path_api.cpp @@ -29,7 +29,6 @@ #include "catch.hpp" #include "entity_data_node_walker.hpp" -#include "ydk/crud_service.hpp" #include "ydk/netconf_provider.hpp" #include "ydk_ydktest/ydktest_sanity.hpp" From 96be146c887d1e07918e3f96f48c61ed85fcd476 Mon Sep 17 00:00:00 2001 From: Yan Date: Fri, 20 Jul 2018 16:04:29 -0400 Subject: [PATCH 5/8] Initial development of GNMI services in Python --- sdk/python/core/python.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk/python/core/python.cpp b/sdk/python/core/python.cpp index 1bb3a6556..325afeda7 100644 --- a/sdk/python/core/python.cpp +++ b/sdk/python/core/python.cpp @@ -534,7 +534,9 @@ PYBIND11_MODULE(ydk_, ydk) .def("encode", (std::string (ydk::path::Codec::*)(std::vector&, ydk::EncodingFormat, bool)) &ydk::path::Codec::encode, arg("data_node"), arg("encoding"), arg("pretty")) .def("decode", &ydk::path::Codec::decode, arg("root_schema_node"), arg("payload"), arg("encoding")) - .def("decode_rpc_output", &ydk::path::Codec::decode_rpc_output, arg("root_schema_node"), arg("payload"), arg("rpc_path"), arg("encoding")); + .def("decode_rpc_output", &ydk::path::Codec::decode_rpc_output, arg("root_schema_node"), arg("payload"), arg("rpc_path"), arg("encoding")) + .def("decode_json_output", (std::shared_ptr (ydk::path::Codec::*)(ydk::path::RootSchemaNode&, const std::vector&)) + &ydk::path::Codec::decode_json_output, arg("root_schema_node"), arg("buffer_list")); enum_(services, "Datastore") .value("candidate", ydk::DataStore::candidate) From d2225f2d5fd0ca6733a1c045d082f9ebc6bb8746 Mon Sep 17 00:00:00 2001 From: Yan Date: Fri, 20 Jul 2018 16:04:59 -0400 Subject: [PATCH 6/8] Initial development of GNMI services in Python --- .../core/tests/test_restconf_session.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/sdk/python/core/tests/test_restconf_session.py b/sdk/python/core/tests/test_restconf_session.py index cba543677..fdd00a4ae 100644 --- a/sdk/python/core/tests/test_restconf_session.py +++ b/sdk/python/core/tests/test_restconf_session.py @@ -76,6 +76,39 @@ def test_create_del_read(self): update_rpc(self.restconf_session) + def test_json_payload_list(self): + codec = Codec() + schema = self.restconf_session.get_root_schema() + + json_int_payload = '''{ + "openconfig-interfaces:interfaces": { + "interface": [ + { + "name": "Loopback10", + "config": { + "name": "Loopback10" + } + } + ] + } +} +''' + json_bgp_payload = '''{ + "openconfig-bgp:bgp": { + "global": { + "config": { + "as": 65172 + } + } + } +} +''' + payload_list = [json_int_payload, json_bgp_payload] + rdn = codec.decode_json_output(schema, payload_list) + + json_str = codec.encode(rdn, EncodingFormat.JSON, True) + self.assertEquals(json_str, json_int_payload + json_bgp_payload) + if __name__ == '__main__': import sys suite = unittest.TestLoader().loadTestsFromTestCase(SanityTest) From 02ca2ce5087f6f035f8a8d643484f36b01b1c151 Mon Sep 17 00:00:00 2001 From: Yan Date: Mon, 23 Jul 2018 14:34:12 -0400 Subject: [PATCH 7/8] Enhancement #811 for C++ --- sdk/cpp/core/src/value_list.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/cpp/core/src/value_list.cpp b/sdk/cpp/core/src/value_list.cpp index 617e6eaf2..34a3c444c 100644 --- a/sdk/cpp/core/src/value_list.cpp +++ b/sdk/cpp/core/src/value_list.cpp @@ -395,7 +395,7 @@ YList::pop(const std::size_t item) { if (item < key_vector.size()) { vector::iterator it = key_vector.begin(); - for (size_t i = 0; i < item; i++) it++; + for (size_t i = 0; i < item; i++) ++it; shared_ptr found = entity_map[*it]; entity_map.erase(*it); key_vector.erase(it); From 5e9daaed58a4c5c88743df6c2b93ecdb5abf5b3c Mon Sep 17 00:00:00 2001 From: Yan Date: Mon, 23 Jul 2018 18:36:27 -0400 Subject: [PATCH 8/8] Enhancement #811 for Go --- sdk/go/core/docsgen/api/types/ydk_types.rst | 6 +-- sdk/go/core/docsgen/guides/crud_guide.rst | 8 ++- sdk/go/core/tests/service_codec_test.go | 19 +++++-- sdk/go/core/tests/types_test.go | 60 +++++++++++++++++++-- sdk/go/core/ydk/types/ylist/ylist.go | 11 ++-- 5 files changed, 88 insertions(+), 16 deletions(-) diff --git a/sdk/go/core/docsgen/api/types/ydk_types.rst b/sdk/go/core/docsgen/api/types/ydk_types.rst index 9a6e34961..5f0a8110a 100644 --- a/sdk/go/core/docsgen/api/types/ydk_types.rst +++ b/sdk/go/core/docsgen/api/types/ydk_types.rst @@ -190,14 +190,14 @@ These are how YANG types are represented in Go. according to the YANG model the list may have one or more keys, which uniquely identify list element, or may have no keys. The slices can be created and list elements can be accessed using standard Go functions and methods. \ The YDK provides package `ylist` containing functions, which allow user access list elements by key or keys. - .. function:: Get(slice interface{}, keys ... interface{}) Entity + .. function:: Get(slice interface{}, keys ... interface{}) (int, Entity) Get list element (entity) by key or keys, if list element has more than one key :param slice: Slice variable, which is defined in the list holding entity :param keys: Comma separated list of key values - :return: Element of the list, which has matching key(s) value(s). - :rtype: :ref:`Entity ` + :return: Tuple of found element index and the element itself, which has matching key(s) value(s). If element is not found - (-1, nil). + :rtype: Tuple( `int`, :ref:`Entity `) .. function:: Keys(slice interface{}) []interface{} Entity diff --git a/sdk/go/core/docsgen/guides/crud_guide.rst b/sdk/go/core/docsgen/guides/crud_guide.rst index de9478ab2..29e1013e1 100644 --- a/sdk/go/core/docsgen/guides/crud_guide.rst +++ b/sdk/go/core/docsgen/guides/crud_guide.rst @@ -190,13 +190,19 @@ Lets continue previous example to demonstrate, how user can access `rib.Vrfs.Vrf // Iterate over the VRF names for _, name := range allVrfNames { - iVrf := ylist.Get(rib.Vrfs.Vrf, name) + _, iVrf := ylist.Get(rib.Vrfs.Vrf, name) if iVrf != nil { eVrf := iVrf.(*ip_rib_ipv4_oper.Rib_Vrfs_Vrf) fmt.Printf("VRF name: %v\n", eVrf.VrfName) } } + // Remove specific VRF from the configuration + i, rVrf = ylist.Get(rib.Vrfs.Vrf, "vrf-to-remove") + if rVrf != nil { + rib.Vrfs.Vrf = append(rib.Vrfs.Vrf[:i], rib.Vrfs.Vrf[i+1:] ...) + crud.Update(&provider, &rib) + } Reading a leaf -------------- diff --git a/sdk/go/core/tests/service_codec_test.go b/sdk/go/core/tests/service_codec_test.go index 536800e09..0474b65e7 100644 --- a/sdk/go/core/tests/service_codec_test.go +++ b/sdk/go/core/tests/service_codec_test.go @@ -493,17 +493,17 @@ func (suite *CodecTestSuite) TestOneKeyList() { configRunner(&runner) // Get first level list element - ldata := ylist.Get(runner.TwoList.Ldata, 22) + _, ldata := ylist.Get(runner.TwoList.Ldata, 22) suite.NotNil(ldata) suite.Equal(types.EntityToString(ldata), "Type: *sanity.Runner_TwoList_Ldata, Path: ldata[number='22']") // Try non-existant key - ldataNE := ylist.Get(runner.TwoList.Ldata, 222) + _, ldataNE := ylist.Get(runner.TwoList.Ldata, 222) suite.Nil(ldataNE) // Get second level list element sublist := ldata.(*ysanity.Runner_TwoList_Ldata).Subl1 - sublData:= ylist.Get(sublist, 221) + _, sublData:= ylist.Get(sublist, 221) suite.NotNil(sublData) suite.Equal(types.EntityToString(sublData), "Type: *sanity.Runner_TwoList_Ldata_Subl1, Path: subl1[number='221']") @@ -512,10 +512,21 @@ func (suite *CodecTestSuite) TestOneKeyList() { suite.Equal(len(sublistKeys), 2) suite.Equal(fmt.Sprintf("%v", sublistKeys), "[221 222]") for _, key := range sublistKeys { - entity := ylist.Get(sublist, key) + _, entity := ylist.Get(sublist, key) suite.NotNil(entity) ydk.YLogDebug(fmt.Sprintf("For key: %v, Found Entity: %v", key, types.EntityToString(entity))) } + + // Remove element from the ylist + i, rdata := ylist.Get(runner.TwoList.Ldata, 22) + suite.Equal(i, 1) + suite.NotNil(rdata) + if rdata != nil { + runner.TwoList.Ldata = append(runner.TwoList.Ldata[:i], runner.TwoList.Ldata[i+1:]...) + suite.Equal(len(runner.TwoList.Ldata), 1) + i, rdata = ylist.Get(runner.TwoList.Ldata, 22) + suite.Nil(rdata) + } } func TestCodecTestSuite(t *testing.T) { diff --git a/sdk/go/core/tests/types_test.go b/sdk/go/core/tests/types_test.go index 001f31ebb..26018233d 100644 --- a/sdk/go/core/tests/types_test.go +++ b/sdk/go/core/tests/types_test.go @@ -497,11 +497,65 @@ func (suite *SanityTypesTestSuite) TestListTwoKeys() { suite.Equal(fmt.Sprintf("%v", ldataKeys), "[[f1 11] [f2 22]]") for _, lkey := range ldataKeys { - ldata := ylist.Get(runner.TwoKeyList, lkey); + _, ldata := ylist.Get(runner.TwoKeyList, lkey); suite.NotNil(ldata) } - suite.Equal(types.EntityEqual(ylist.Get(runner.TwoKeyList, "f1", 11), &l1), true) - suite.Equal(types.EntityEqual(ylist.Get(runner.TwoKeyList, "f2", 22), &l2), true) + _, ldata := ylist.Get(runner.TwoKeyList, "f1", 11) + suite.Equal(types.EntityEqual(ldata, &l1), true) + _, ldata = ylist.Get(runner.TwoKeyList, "f2", 22) + suite.Equal(types.EntityEqual(ldata, &l2), true) + + i, ldata := ylist.Get(runner.TwoKeyList, "f1", 11) + suite.Equal(i, 0) + suite.NotNil(ldata) + suite.Equal(types.EntityEqual(ldata, &l1), true) + if ldata != nil { + runner.TwoKeyList = append(runner.TwoKeyList[:i], runner.TwoKeyList[i+1:]...) + i, ldata = ylist.Get(runner.TwoKeyList, "f1", 11) + suite.Nil(ldata) + } +} + +func (suite *SanityTypesTestSuite) TestIdentityList() { + runner := ysanity.Runner{} + i1 := ysanity.Runner_IdentityList{Name: "first"} + i2 := ysanity.Runner_IdentityList{Name: "second"} + i3 := ysanity.Runner_IdentityList{Name: "third"} + runner.IdentityList = []*ysanity.Runner_IdentityList {&i1, &i2, &i3} + + ldataKeys := ylist.Keys(runner.IdentityList) + suite.Equal(fmt.Sprintf("%v", ldataKeys), "[first second third]") + + var ldata types.Entity + for _, lkey := range ldataKeys { + _, ldata = ylist.Get(runner.IdentityList, lkey); + suite.NotNil(ldata) + } + _, ldata = ylist.Get(runner.IdentityList, "first") + suite.Equal(types.EntityEqual(ldata, &i1), true) + _, ldata = ylist.Get(runner.IdentityList, "third") + suite.Equal(types.EntityEqual(ldata, &i3), true) +} + +func (suite *SanityTypesTestSuite) TestEnumList() { + runner := ysanity.Runner{} + i1 := ysanity.Runner_EnumList{Name: ysanity.YdkEnumTest_none} + i2 := ysanity.Runner_EnumList{Name: ysanity.YdkEnumTest_local} + i3 := ysanity.Runner_EnumList{Name: ysanity.YdkEnumTest_remote} + runner.EnumList = []*ysanity.Runner_EnumList{&i1, &i2, &i3} + + ldataKeys := ylist.Keys(runner.EnumList) + suite.Equal(fmt.Sprintf("%v", ldataKeys), "[none local remote]") + + var ldata types.Entity + for _, lkey := range ldataKeys { + _, ldata = ylist.Get(runner.EnumList, lkey); + suite.NotNil(ldata) + } + _, ldata = ylist.Get(runner.EnumList, "none") + suite.Equal(types.EntityEqual(ldata, &i1), true) + _, ldata = ylist.Get(runner.EnumList, "remote") + suite.Equal(types.EntityEqual(ldata, &i3), true) } func (suite *SanityTypesTestSuite) TestListNoKeys() { diff --git a/sdk/go/core/ydk/types/ylist/ylist.go b/sdk/go/core/ydk/types/ylist/ylist.go index 908946705..541834a0f 100644 --- a/sdk/go/core/ydk/types/ylist/ylist.go +++ b/sdk/go/core/ydk/types/ylist/ylist.go @@ -82,10 +82,10 @@ func toEntitySlice(slice interface{}) []types.Entity { return ret } -func Get(iSlice interface{}, keys ... interface{}) types.Entity { +func Get(iSlice interface{}, keys ... interface{}) (int, types.Entity) { eSlice := toEntitySlice(iSlice) if len(keys) == 0 { - return nil + return -1, nil } var keyToCmp string if reflect.ValueOf(keys[0]).Kind() == reflect.Slice { @@ -93,16 +93,16 @@ func Get(iSlice interface{}, keys ... interface{}) types.Entity { } else { keyToCmp = keysToStr(keys) } - for _, elem := range eSlice { + for i, elem := range eSlice { keyList := buildKeyList(elem) if len(keyList) > 0 { key := keysToStr(keyList) if key == keyToCmp { - return elem + return i, elem } } } - return nil + return -1, nil } func Keys(iSlice interface{}) []interface{} { @@ -121,3 +121,4 @@ func Keys(iSlice interface{}) []interface{} { } return keyList } +