From 1b1eb0d115f5f3c047dc78862c17d39fb29beba5 Mon Sep 17 00:00:00 2001 From: Andrej Kozemcak Date: Thu, 21 Feb 2019 16:23:03 +0100 Subject: Init gNMI server. Change-Id: Ie898b5385096e735bf947775e0278c3c8f4797a8 Signed-off-by: Andrej Kozemcak --- Makefile | 87 ++++++-- src/cmake/FindJSONCPP.cmake | 56 +++++ src/cmake/FindPUGIXML.cmake | 52 +++++ src/gnmi/CMakeLists.txt | 73 +++++++ src/gnmi/eventreciverbase.h | 78 +++++++ src/gnmi/gnmidata.cpp | 112 ++++++++++ src/gnmi/gnmidata.h | 71 +++++++ src/gnmi/gnmiserver.cpp | 483 ++++++++++++++++++++++++++++++++++++++++++ src/gnmi/gnmiserver.h | 88 ++++++++ src/gnmi/log.cpp | 28 +++ src/gnmi/log.h | 35 +++ src/gnmi/main.cpp | 222 +++++++++++++++++++ src/gnmi/proto/CMakeLists.txt | 52 +++++ src/gnmi/proto/gnmi.proto | 457 +++++++++++++++++++++++++++++++++++++++ src/gnmi/proto/gnmi_ext.proto | 74 +++++++ src/gnmi/sysrepoapi.cpp | 419 ++++++++++++++++++++++++++++++++++++ src/gnmi/sysrepoapi.h | 109 ++++++++++ src/gnmi/sysrepoapipool.cpp | 42 ++++ src/gnmi/sysrepoapipool.h | 48 +++++ src/gnmi/xml2json.cpp | 326 ++++++++++++++++++++++++++++ src/gnmi/xml2json.h | 91 ++++++++ 21 files changed, 2990 insertions(+), 13 deletions(-) create mode 100644 src/cmake/FindJSONCPP.cmake create mode 100644 src/cmake/FindPUGIXML.cmake create mode 100644 src/gnmi/CMakeLists.txt create mode 100644 src/gnmi/eventreciverbase.h create mode 100644 src/gnmi/gnmidata.cpp create mode 100644 src/gnmi/gnmidata.h create mode 100644 src/gnmi/gnmiserver.cpp create mode 100644 src/gnmi/gnmiserver.h create mode 100644 src/gnmi/log.cpp create mode 100644 src/gnmi/log.h create mode 100644 src/gnmi/main.cpp create mode 100644 src/gnmi/proto/CMakeLists.txt create mode 100644 src/gnmi/proto/gnmi.proto create mode 100644 src/gnmi/proto/gnmi_ext.proto create mode 100644 src/gnmi/sysrepoapi.cpp create mode 100644 src/gnmi/sysrepoapi.h create mode 100644 src/gnmi/sysrepoapipool.cpp create mode 100644 src/gnmi/sysrepoapipool.h create mode 100644 src/gnmi/xml2json.cpp create mode 100644 src/gnmi/xml2json.h diff --git a/Makefile b/Makefile index 079ba4a..7933b38 100644 --- a/Makefile +++ b/Makefile @@ -40,10 +40,13 @@ endif DEB_DEPENDS = curl build-essential autoconf automake ccache git DEB_DEPENDS += bison flex libpcre3-dev libev-dev libavl-dev libprotobuf-c-dev protobuf-c-compiler libcmocka-dev -DEB_DEPENDS += cmake ninja-build python-pkgconfig python-dev libssl-dev indent wget +DEB_DEPENDS += cmake ninja-build python-pkgconfig python-dev libssl-dev indent wget zlib1g-dev + +DEB_GNMI_DEPENDS = libpugixml-dev libjsoncpp-dev libtool pkg-config golang libc-ares-dev libc-ares2 RPM_DEPENDS = curl autoconf automake ccache bison flex pcre-devel libev-devel protobuf-c-devel protobuf-c-compiler libcmocka-devel RPM_DEPENDS += cmake ninja-build python-pkgconfig python-devel openssl-devel graphviz wget gcc gcc-c++ indent git cmake3 +RPM_GNMI_DEPENDS = pugixml jsoncpp c-ares c-ares-devel libtool pugixml-devel jsoncpp-devel devtoolset-8-gcc-c++ ifeq ($(findstring y,$(UNATTENDED)),y) CONFIRM=-y @@ -63,18 +66,20 @@ endef help: @echo "Make Targets:" - @echo " install-dep - install software dependencies" - @echo " install-dep-extra - install software extra dependencips from source code" - @echo " install-vpp - install released vpp" - @echo " checkstyle - check coding style" - @echo " fixstyle - fix coding style" - @echo " build-scvpp - build scvpp" - @echo " build-plugins - build plugins" - @echo " build-package - build rpm or deb package" - @echo " docker - build sweetcomb in docker enviroment" - @echo " docker_test - run test in docker enviroment" - @echo " clean - clean all build" - @echo " distclean - remove all build directory" + @echo " install-dep - install software dependencies" + @echo " install-dep-extra - install software extra dependencips from source code" + @echo " install-vpp - install released vpp" + @echo " install-dep-gnmi-extra - install software extra dependencips from source code for gNMI" + @echo " checkstyle - check coding style" + @echo " fixstyle - fix coding style" + @echo " build-scvpp - build scvpp" + @echo " build-plugins - build plugins" + @echo " build-gnmi - build gNMIServer" + @echo " build-package - build rpm or deb package" + @echo " docker - build sweetcomb in docker enviroment" + @echo " docker_test - run test in docker enviroment" + @echo " clean - clean all build" + @echo " distclean - remove all build directory" $(BR)/.deps.ok: ifeq ($(findstring y,$(UNATTENDED)),y) make install-dep @@ -177,6 +182,52 @@ install-dep-extra: \ &&cd ../ && rm -rf $(BR)/downloads +install-dep-gnmi-extra: + @rm -rf $(BR)/downloads + @mkdir -p $(BR)/downloads/ +ifeq ($(filter ubuntu debian,$(OS_ID)),$(OS_ID)) + @sudo -E apt-get update + @sudo -E apt-get $(APT_ARGS) $(CONFIRM) $(FORCE) install $(DEB_GNMI_DEPENDS) + @sudo apt-get install -y software-properties-common + @sudo add-apt-repository ppa:ubuntu-toolchain-r/test + @sudo apt update + @sudo apt install g++-7 -y + @sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 60 \ + --slave /usr/bin/g++ g++ /usr/bin/g++-7 + @sudo update-alternatives --config gcc + +else ifeq ($(OS_ID),centos) + @sudo -E yum install $(CONFIRM) $(RPM_GNMI_DEPENDS) +else + $(error "This option currently works only on Ubuntu, Debian, Centos or openSUSE systems") +endif + + @cd $(BR)/downloads/\ + &&wget https://github.com/Kitware/CMake/releases/download/v3.14.0-rc2/cmake-3.14.0-rc2.tar.gz \ + &&tar xvf cmake-3.14.0-rc2.tar.gz &&cd cmake-3.14.0-rc2\ + &&./configure &&make &&make install\ + \ + &&cd $(BR)/downloads/\ + &&wget https://github.com/c-ares/c-ares/releases/download/cares-1_15_0/c-ares-1.15.0.tar.gz\ + &&tar xvf c-ares-1.15.0.tar.gz\ + &&mkdir -p c-ares-1.15.0/build && cd c-ares-1.15.0/build\ + &&cmake -DCMAKE_BUILD_TYPE=Release ../ &&make install\ + \ + &&cd $(BR)/downloads/\ + &&wget https://github.com/protocolbuffers/protobuf/archive/v3.7.0rc2.tar.gz\ + &&tar xvf v3.7.0rc2.tar.gz\ + &&cd protobuf-3.7.0rc2 &&./autogen.sh &&./configure --prefix=/usr\ + &&make &&make install &&ldconfig\ + \ + &&cd $(BR)/downloads/\ + &&wget https://github.com/grpc/grpc/archive/v1.18.0.tar.gz\ + &&tar xvf v1.18.0.tar.gz &&cd grpc-1.18.0 &&mkdir -p build && cd build\ + &&cmake -DgRPC_INSTALL:BOOL=ON -DgRPC_BUILD_TESTS:BOOL=OFF \ + -DgRPC_ZLIB_PROVIDER:STRING=package -DgRPC_CARES_PROVIDER:STRING=package \ + -DgRPC_SSL_PROVIDER:STRING=package -DgRPC_PROTOBUF_PROVIDER=package \ + -DCMAKE_BUILD_TYPE=Release ../\ + &&make ../ &&make install + install-vpp: @echo "please install vpp as vpp's guide from source if failed" ifeq ($(PKG),deb) @@ -207,6 +258,14 @@ docker: docker_test: @test/run_test.sh +build-gnmi: +ifeq ($(OS_ID),centos) + @source /opt/rh/devtoolset-8/enable \ + &&mkdir -p $(BR)/build-gnmi/;cd $(BR)/build-gnmi/;cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr $(WS_ROOT)/src/gnmi/;make; make install; +else + @mkdir -p $(BR)/build-gnmi/;cd $(BR)/build-gnmi/;cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr $(WS_ROOT)/src/gnmi/;make; make install; +endif + build-package: @mkdir -p $(BR)/build-scvpp/;cd $(BR)/build-scvpp;cmake -DCMAKE_INSTALL_PREFIX:PATH=/usr $(WS_ROOT)/src/scvpp/;make install; @mkdir -p $(BR)/build-package/;cd $(BR)/build-package/;$(cmake) $(WS_ROOT)/src/;make package;rm -rf $(BR)/build-package/_CPack_Packages; @@ -215,8 +274,10 @@ clean: @cd $(BR)/build-scvpp && make clean; @cd $(BR)/build-plugins && make clean; @cd $(BR)/build-package && make clean; + @cd $(BR)/build-gnmi && make clean; distclean: @rm -rf $(BR)/build-scvpp @rm -rf $(BR)/build-plugins @rm -rf $(BR)/build-package + @rm -rf $(BR)/build-gnmi diff --git a/src/cmake/FindJSONCPP.cmake b/src/cmake/FindJSONCPP.cmake new file mode 100644 index 0000000..a342f08 --- /dev/null +++ b/src/cmake/FindJSONCPP.cmake @@ -0,0 +1,56 @@ +# - Try to find LibJSONCPP +# Once done this will define +# +# LIBJSONCPP_FOUND - system has LibJSONCPP +# LIBJSONCPP_INCLUDE_DIRS - the LibJSONCPP include directory +# LIBJSONCPP_LIBRARIES - Link these to use LIBJSONCPP + + +if (LIBJSONCPP_LIBRARIES AND LIBJSONCPP_INCLUDE_DIRS) + # in cache already + set(LIBJSONCPP_FOUND TRUE) +else (LIBJSONCPP_LIBRARIES AND LIBJSONCPP_INCLUDE_DIRS) + + find_path(LIBJSONCPP_INCLUDE_DIR + NAMES + json/json.h + PATHS + /usr/include + /usr/local/include + /usr/include/jsoncpp + /opt/local/include + ${CMAKE_INCLUDE_PATH} + ${CMAKE_INSTALL_PREFIX}/include + ) + + find_library(LIBJSONCPP_LIBRARY + NAMES + jsoncpp + PATHS + /usr/lib + /usr/lib64 + /usr/lib/x86_64-linux-gnu + /usr/local/lib + /usr/local/lib64 + /opt/local/lib + ${CMAKE_LIBRARY_PATH} + ${CMAKE_INSTALL_PREFIX}/lib + ) + + if (LIBJSONCPP_INCLUDE_DIR AND LIBJSONCPP_LIBRARY) + set(LIBJSONCPP_FOUND TRUE) + else (LIBJSONCPP_INCLUDE_DIR AND LIBJSONCPP_LIBRARY) + set(LIBJSONCPP_FOUND FALSE) + endif (LIBJSONCPP_INCLUDE_DIR AND LIBJSONCPP_LIBRARY) + + set(LIBJSONCPP_INCLUDE_DIRS ${LIBJSONCPP_INCLUDE_DIR}) + set(LIBJSONCPP_LIBRARIES ${LIBJSONCPP_LIBRARY}) + + # show the LIBJSONCPP_INCLUDE_DIRS and LIBJSONCPP_LIBRARIES variables only in the advanced view + mark_as_advanced(LIBJSONCPP_INCLUDE_DIRS LIBJSONCPP_LIBRARIES) + +endif (LIBJSONCPP_LIBRARIES AND LIBJSONCPP_INCLUDE_DIRS) + + + + diff --git a/src/cmake/FindPUGIXML.cmake b/src/cmake/FindPUGIXML.cmake new file mode 100644 index 0000000..dc7c86b --- /dev/null +++ b/src/cmake/FindPUGIXML.cmake @@ -0,0 +1,52 @@ +# - Try to find LibPUGIXML +# Once done this will define +# +# LIBPUGIXML_FOUND - system has LibPUGIXML +# LIBPUGIXML_INCLUDE_DIRS - the LibPUGIXML include directory +# LIBPUGIXML_LIBRARIES - Link these to use LIBPUGIXML + +if (LIBPUGIXML_LIBRARIES AND LIBPUGIXML_INCLUDE_DIRS) + # in cache already + set(LIBPUGIXML_FOUND TRUE) +else (LIBPUGIXML_LIBRARIES AND LIBPUGIXML_INCLUDE_DIRS) + + find_path(LIBPUGIXML_INCLUDE_DIR + NAMES + pugixml.hpp + PATHS + /usr/include + /usr/local/include + /opt/local/include + ${CMAKE_INCLUDE_PATH} + ${CMAKE_INSTALL_PREFIX}/include + ) + + find_library(LIBPUGIXML_LIBRARY + NAMES + pugixml + PATHS + /usr/lib + /usr/lib64 + /usr/local/lib + /usr/local/lib64 + /opt/local/lib + ${CMAKE_LIBRARY_PATH} + ${CMAKE_INSTALL_PREFIX}/lib + ) + + if (LIBPUGIXML_INCLUDE_DIR AND LIBPUGIXML_LIBRARY) + set(LIBPUGIXML_FOUND TRUE) + else (LIBPUGIXML_INCLUDE_DIR AND LIBPUGIXML_LIBRARY) + set(LIBPUGIXML_FOUND FALSE) + endif (LIBPUGIXML_INCLUDE_DIR AND LIBPUGIXML_LIBRARY) + + set(LIBPUGIXML_INCLUDE_DIRS ${LIBPUGIXML_INCLUDE_DIR}) + set(LIBPUGIXML_LIBRARIES ${LIBPUGIXML_LIBRARY}) + + # show the LIBPUGIXML_INCLUDE_DIRS and LIBPUGIXML_LIBRARIES variables only in the advanced view + mark_as_advanced(LIBPUGIXML_INCLUDE_DIRS LIBPUGIXML_LIBRARIES) + +endif (LIBPUGIXML_LIBRARIES AND LIBPUGIXML_INCLUDE_DIRS) + + + diff --git a/src/gnmi/CMakeLists.txt b/src/gnmi/CMakeLists.txt new file mode 100644 index 0000000..e373827 --- /dev/null +++ b/src/gnmi/CMakeLists.txt @@ -0,0 +1,73 @@ +# +# Copyright (c) 2019 PANTHEON.tech. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +cmake_minimum_required(VERSION 2.8) +project(gNMI-server) + +set(CMAKE_CXX_STANDARD 17) +# set compiler option +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) +set(CMAKE_C_FLAGS "-Wall -std=c++17") +set(CMAKE_C_FLAGS_RELEASE "-DNDEBUG -O2") +set(CMAKE_C_FLAGS_DEBUG "-g -O0") +set(CMAKE_INSTALL_LIBDIR "/usr/lib") + +# Cmake find modules +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../cmake") + +find_package(PUGIXML REQUIRED) +find_package(JSONCPP REQUIRED) +find_package(PkgConfig) + + +pkg_check_modules(SYSREPO REQUIRED libsysrepo) + +add_subdirectory(proto) + +if(LIBPUGIXML_FOUND) + message ("PUGIXML found") +else() + message (FATAL_ERROR "Cannot find PUGIXML") +endif() + +if(LIBJSONCPP_FOUND) + message ("LIBJSONCPP found") +else() + message (FATAL_ERROR "Cannot find LIBJSONCPP") +endif() + +set(GNMI_SRC main.cpp + log.cpp + gnmiserver.cpp + sysrepoapi.cpp + sysrepoapipool.cpp + xml2json.cpp + gnmidata.cpp) + +add_executable(gnmi_server ${GNMI_SRC}) + +target_include_directories(gnmi_server PRIVATE ${PROTOBUF_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR}/proto + ${LIBPUGIXML_INCLUDE_DIR} + ${LIBJSONCPP_INCLUDE_DIR} + ${SYSREPO_INCLUDE_DIRS}) + +target_link_libraries(gnmi_server proto + ${LIBPUGIXML_LIBRARY} + ${LIBJSONCPP_LIBRARY} + ${SYSREPO_LIBRARIES}) + +install(TARGETS gnmi_server RUNTIME DESTINATION bin) diff --git a/src/gnmi/eventreciverbase.h b/src/gnmi/eventreciverbase.h new file mode 100644 index 0000000..13f79ff --- /dev/null +++ b/src/gnmi/eventreciverbase.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EVENTRECIVERBASE_H +#define EVENTRECIVERBASE_H + +#include +#include + +class EventReceiverBase +{ +public: + virtual ~EventReceiverBase() {} +}; + +template +class EventReceiver : public virtual EventReceiverBase +{ +public: + virtual void receiveWriteEvent(T *psender) = 0; +}; + +class EventSender +{ +public: + + template + void registerReciver(T *receiver) { + pReceiver = receiver; + } + + void registerStream(void *stream) { + this->stream = stream; + } + + void *getStream() { + return stream; + } + + template + void sendEvent(T* pSender) { + if (nullptr == pReceiver) { + throw std::runtime_error("Receiver is not register."); + } + + EventReceiver *pCastedReceiver = + dynamic_cast*>(pReceiver); + pCastedReceiver->receiveWriteEvent(pSender); + } + +private: + EventReceiverBase *pReceiver = nullptr; + void *stream = nullptr; +}; + +template +class BaseSender : public virtual EventSender +{ +public: + void send() { + sendEvent((T*) this); + } +}; + +#endif // EVENTRECIVERBASE_H diff --git a/src/gnmi/gnmidata.cpp b/src/gnmi/gnmidata.cpp new file mode 100644 index 0000000..5ee53c4 --- /dev/null +++ b/src/gnmi/gnmidata.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gnmidata.h" + +gNMIData::ValueType gNMIData::dataType() const +{ + return dtype; +} + +void gNMIData::clean() +{ + dtype = ValueType::UnknownVal; + value = {}; + xpath = ""; +} + +void gNMIData::setXPath(const std::string& str, xPathType type) +{ + std::size_t pos = 0; + + xpath = str; + + if (xPathType::gNMIPath == type) { + return; + } + + while (std::string::npos != (pos = xpath.find(":", pos))) { + xpath.replace(pos, std::string(":").length(), "/"); + } +} + +void gNMIData::setValue(const std::string& str) +{ + dtype = ValueType::dStringVal; + value = str; +} + +void gNMIData::setValue(int val) +{ + dtype = ValueType::dIntVal; + value = val; +} + +std::string gNMIData::getXPath(gNMIData::xPathType type) const +{ + switch (type) { + case xPathType::gNMIPath: + return xpath; + + case xPathType::sysrepoPath: + return convertToSyrepoPath(); + + default: + break; + } + + return xpath; +} + +std::string gNMIData::getStr() const +{ + switch (dtype) { + case ValueType::dIntVal: + return std::to_string(getInt()); + + case ValueType::dStringVal: + return std::get(value); + + case ValueType::UnknownVal: + default: + //TODO: I`m not sure with N/Al + return "N/A"; + } + + return "N/A"; +} + +int gNMIData::getInt() const +{ + return std::get(value); +} + +std::string gNMIData::convertToSyrepoPath() const +{ + std::string str = xpath; + std::size_t pos = 0; + int i = 0; + + while (std::string::npos != (pos = str.find("/", pos))) { + if (1 == i++) { + str.replace(pos, std::string("/").length(), ":"); + break; + } + pos++; + } + + return str; +} diff --git a/src/gnmi/gnmidata.h b/src/gnmi/gnmidata.h new file mode 100644 index 0000000..e4c15d2 --- /dev/null +++ b/src/gnmi/gnmidata.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GNMIDATA_H +#define GNMIDATA_H + +#include +#include + +/** + * @todo write docs + */ +class gNMIData +{ +public: + enum class ValueType { + dStringVal, + dIntVal, + UnknownVal + }; + + enum class xPathType { + gNMIPath, + sysrepoPath, + }; + +public: + /** + * Default constructor + */ + gNMIData() = default; + + /** + * Destructor + */ + ~gNMIData() = default; + + ValueType dataType() const; + void clean(); + + void setXPath(const std::string &str, xPathType type = xPathType::gNMIPath); + void setValue(const std::string &str); + void setValue(int val); + + std::string getXPath(xPathType type = xPathType::gNMIPath) const; + std::string getStr() const; + int getInt() const; + +private: + std::string convertToSyrepoPath() const; + +private: + ValueType dtype = ValueType::UnknownVal; + std::variant value; + std::string xpath; +}; + +#endif // GNMIDATA_H diff --git a/src/gnmi/gnmiserver.cpp b/src/gnmi/gnmiserver.cpp new file mode 100644 index 0000000..44ce6c5 --- /dev/null +++ b/src/gnmi/gnmiserver.cpp @@ -0,0 +1,483 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "gnmiserver.h" +#include "log.h" +#include "xml2json.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +gNMIServer::gNMIServer(SysrepoAPI& sysrepo) + : sysrepo(sysrepo) +{ +} + +grpc::Status gNMIServer::Capabilities(grpc::ServerContext* context, + const gnmi::CapabilityRequest* request, + gnmi::CapabilityResponse* reply) +{ + DEBUG("Capabilities message"); + + if (0 < request->extension_size()) { + //TODO; + } + + sysrepo.createSession(); + const auto &ss = sysrepo.getSchemas(); + + for (const auto schema : ss) { + auto model = reply->add_supported_models(); + model->set_name(schema.moduleName); + model->set_version(schema.revision); + } + + reply->add_supported_encodings(gnmi::Encoding::ASCII); + + //FIXME: I`m not sure, if this is a correct version of gnmi. + reply->set_gnmi_version(std::to_string(gnmi::kGnmiServiceFieldNumber)); + + sysrepo.closeSession(); + + return Status::OK; +} + +grpc::Status gNMIServer::Get(grpc::ServerContext* context, + const gnmi::GetRequest* request, + gnmi::GetResponse* reply) +{ + DEBUG("Get message"); + + std::cout << gnmi::GetRequest_DataType_Name(request->type()) << std::endl; + + try { + sysrepo.createSession(); + + auto &prefix = request->prefix(); + dataPrefix = convertToXPath(prefix); + DEBUG("Prefix: %s", dataPrefix.c_str()); + + DEBUG("Data type: %s", + request->DataType_Name(request->type()).c_str()); + DEBUG("Encoding: %s", gnmi::Encoding_Name(request->encoding()).c_str()); + + for (const auto &model : request->use_models()) { + DEBUG("Model, name: %s, organization: %s, version: %s", + model.name().c_str(), model.organization().c_str(), + model.version().c_str()); + } + +// for (const auto &extension : request->extension()) { +// DEBUG(ex); +// } + + if (0 < request->path().size()) { + for (const auto &path : request->path()) { + std::string strPath = convertToXPath(path); + vData.clean(); + vData.setXPath(dataPrefix + strPath); + sysrepo.addData(vData); + sysrepo.getItemMessage(); + + auto notification = reply->add_notification(); + + notification->set_timestamp(getTimeNanosec()); + auto elem = notification->mutable_prefix(); + xpathTogNMIEl(dataPrefix, *elem); + + const auto &oData = sysrepo.getOutputData(); + + for (const auto &data : oData) { + auto nupdate = notification->add_update(); + xpathTogNMIEl(data.getXPath(), *nupdate->mutable_path()); + auto uval = nupdate->mutable_val(); + uval->set_string_val(data.getStr()); + } + } + } + + sysrepo.cleanData(); + sysrepo.closeSession(); + } catch (...) { + sysrepo.cleanData(); + sysrepo.closeSession(); + return Status::CANCELLED; + } + + return Status::OK; +} + +//TODO: Need handle INVALID operation, somehow, but how???? +grpc::Status gNMIServer::Set(grpc::ServerContext* context, + const gnmi::SetRequest* request, + gnmi::SetResponse* reply) +{ + DEBUG("Set message"); + std::string path; + + reply->set_timestamp(getTimeNanosec()); + + auto prefix = reply->mutable_prefix(); + prefix->add_elem(); + + try { + sysrepo.createSession(SR_DS_RUNNING); + + dataPrefix = convertToXPath(request->prefix()); + + if (0 < request->delete__size()) { + for (const auto &del : request->delete_()) { + vData.clean(); + path = convertToXPath(del); + vData.setXPath(dataPrefix + path); + DEBUG("Delete: %s", vData.getXPath().c_str()); + sysrepo.addData(vData); + } + sysrepo.setItemMessage(); + + const auto &oData = sysrepo.getOutputData(); + for (const auto &data : oData) { + auto updateResult = reply->add_response(); + updateResult->set_op(gnmi::UpdateResult::DELETE); + xpathTogNMIEl(data.getXPath(), *updateResult->mutable_path()); + } + + sysrepo.cleanData(); + } + + if (0 < request->replace_size()) { + for (const auto &replace : request->replace()) { + vData.clean(); + handleUpdateMessage(replace); + DEBUG("Replace : %s", vData.getXPath().c_str()); + sysrepo.addData(vData); + } + sysrepo.setItemMessage(); + + const auto &oData = sysrepo.getOutputData(); + for (const auto &data : oData) { + auto updateResult = reply->add_response(); + updateResult->set_op(gnmi::UpdateResult::REPLACE); + xpathTogNMIEl(data.getXPath(), *updateResult->mutable_path()); + } + + sysrepo.cleanData(); + } + + if (0 < request->update_size()) { + for (const auto &update : request->update()) { + vData.clean(); + handleUpdateMessage(update); + DEBUG("Update: %s", vData.getXPath().c_str()); +// sysrepo.addData(vData); + XML2JSON json; + json.setData(vData.getStr()); + json.setPrefix(vData.getXPath()); + auto &gdatas = json.getgNMIData(); + for (const auto &gdata : gdatas) { + DEBUG("Data xpath: %s, data: %s", gdata.getXPath().c_str(), + gdata.getStr().c_str()); + sysrepo.addData(gdata); + } +// DEBUG("%s", json.getXML().c_str()); + } + sysrepo.setItemMessage(); + + const auto &oData = sysrepo.getOutputData(); + for (const auto &data : oData) { + auto updateResult = reply->add_response(); + updateResult->set_op(gnmi::UpdateResult::UPDATE); + xpathTogNMIEl(data.getXPath(), *updateResult->mutable_path()); + } + + sysrepo.cleanData(); + } + + //TODO: Need some special handling for sysrepo + sysrepo.commit(); + sysrepo.closeSession(); + } catch (...) { + sysrepo.cleanData(); + sysrepo.closeSession(); + return Status::CANCELLED; + } + + return Status::OK; +} + +grpc::Status gNMIServer::Subscribe(grpc::ServerContext* context, + ServerReaderWriter* stream) +{ + DEBUG("Subscribe message"); + + gnmi::SubscribeRequest request; + + try { + sysrepo.registerReciver(this); + sysrepo.registerStream(stream); + + sysrepo.createSession(SR_DS_RUNNING); + while (stream->Read(&request)) { + switch (request.request_case()) { + case gnmi::SubscribeRequest::kAliases: + break; + + case gnmi::SubscribeRequest::kPoll: + break; + + case gnmi::SubscribeRequest::kSubscribe: + subscibeList(request.subscribe()); + break; + + case gnmi::SubscribeRequest::REQUEST_NOT_SET: + break; + + default: + DEBUG("Unknown request case."); + break; + } + } + } catch (...) { + sysrepo.cleanData(); + sysrepo.closeSession(); + return Status::CANCELLED; + } + + sysrepo.cleanData(); + sysrepo.closeSession(); + DEBUG("Subscribe message end"); + + return Status::OK; +} + +void gNMIServer::subscibeList(const gnmi::SubscriptionList& slist) +{ + std::string prefix = convertToXPath(slist.prefix()); + + if (0 < slist.subscription_size()) { + for (const auto &sub : slist.subscription()) { + vData.clean(); + vData.setXPath(prefix + convertToXPath(sub.path())); + //TODO: Call sysrepo event + + DEBUG("Subs Mode: %s", + gnmi::SubscriptionMode_Name(sub.mode()).c_str()); + DEBUG("Subs xpath: %s", vData.getXPath().c_str()); + sysrepo.addData(vData); + sysrepo.eventSubscribeMessage(); + } + } + + DEBUG("Subscribe list mode: %s", slist.Mode_Name(slist.mode()).c_str()); + + if (0 < slist.use_models_size()) { + for (const auto &umod : slist.use_models()) { + DEBUG("Name: %s, Organization: %s, Version: %s", + umod.name().c_str(), umod.organization().c_str(), + umod.version().c_str()); + } + } + + DEBUG("Encoding: %s", gnmi::Encoding_Name(slist.encoding()).c_str()); + + DEBUG("Update only: %d", slist.updates_only()); +} + +void gNMIServer::receiveWriteEvent(SysrepoAPI* psender) +{ + gnmi::SubscribeResponse response; + ServerReaderWriter* stream; + + stream = (ServerReaderWriter*) psender->getStream(); + + auto sData = psender->getOutputData(); + auto update = response.mutable_update(); + + update->set_timestamp(getTimeNanosec()); + auto elem = update->mutable_prefix(); + + xpathTogNMIEl("/", *elem); + for (const auto &data : sData) { + DEBUG("XPath: %s, value: %s", + data.getXPath().c_str(), data.getStr().c_str()); + auto nupdate = update->add_update(); + xpathTogNMIEl(data.getXPath(), *nupdate->mutable_path()); + auto val = nupdate->mutable_val(); + val->set_string_val(data.getStr()); + } + + psender->cleanData(); + stream->Write(response); +} + +void gNMIServer::parsePathMsg(const gnmi::Path& path) +{ + if (0 >= path.elem_size()) { + DEBUG("Path is empty"); + return; + } + +} + +std::string gNMIServer::convertToXPath(const gnmi::Path& path) +{ + std::string str = "/"; + + if (0 < path.elem_size()) { + for (const auto &elm : path.elem()) { + str += elm.name(); + + for (const auto &el : elm.key()) { + str += "[" + el.first + "='" + el.second + "']"; + } + + str += "/"; + } + } + str.pop_back(); + + return str; +} + +void gNMIServer::printPath(const gnmi::Path& path) +{ + if (0 < path.element().size()) { + for (const auto &element : path.element()) { + DEBUG("Element: %s", element.c_str()); + } + } + + if (0 < path.elem_size()) { + for (const auto &elm : path.elem()) { + DEBUG("Elm name: %ss", elm.name().c_str()); + for (const auto &el : elm.key()) { + DEBUG("El key: %s, val: %s", el.first.c_str(), + el.second.c_str()); + } + } + } +} + +void gNMIServer::handleUpdateMessage(const gnmi::Update& msg) +{ + vData.setXPath(dataPrefix + convertToXPath(msg.path())); + + if (msg.has_val()) { + handleTypeValueMsg(msg.val()); + } +} + +void gNMIServer::handleTypeValueMsg(const gnmi::TypedValue& msg) +{ + DEBUG("Val case: %d", msg.value_case()); + + switch (msg.value_case()) { + case gnmi::TypedValue::ValueCase::kStringVal: + vData.setValue(msg.string_val()); + break; + + case gnmi::TypedValue::ValueCase::kIntVal: + break; + + case gnmi::TypedValue::ValueCase::kUintVal: + break; + + case gnmi::TypedValue::ValueCase::kBoolVal: + break; + + case gnmi::TypedValue::ValueCase::kBytesVal: + break; + + case gnmi::TypedValue::ValueCase::kFloatVal: + break; + + case gnmi::TypedValue::ValueCase::kDecimalVal: + break; + + case gnmi::TypedValue::ValueCase::kLeaflistVal: + break; + + case gnmi::TypedValue::ValueCase::kAnyVal: + break; + + case gnmi::TypedValue::ValueCase::kJsonVal: + break; + + case gnmi::TypedValue::ValueCase::kJsonIetfVal: + vData.setValue(msg.json_ietf_val()); + break; + + case gnmi::TypedValue::ValueCase::kAsciiVal: + break; + + case gnmi::TypedValue::ValueCase::kProtoBytes: + break; + + case gnmi::TypedValue::ValueCase::VALUE_NOT_SET: + break; + + default: + DEBUG("Unknown type."); + break; + } +} + +void gNMIServer::xpathTogNMIEl(const std::string& str, gnmi::Path& path) +{ + sr_xpath_ctx_t state; + std::string tmp(str); + const char *xpath = tmp.c_str(); + char *name; + + if ((name = sr_xpath_next_node((char *)xpath, &state)) == NULL) { + DEBUG("Empty XPATH, xpath: %s", xpath); + return; + } + + do { + auto pathEl = path.add_elem(); + pathEl->set_name(name); + + const char *key_name; + while ((key_name = sr_xpath_next_key_name(NULL, &state)) != NULL) { + std::string key(key_name); + const char *key_value = sr_xpath_next_key_value(NULL, &state); + (*pathEl->mutable_key())[key] = key_value; + } + } while ((name = sr_xpath_next_node(NULL, &state)) != NULL); + + sr_xpath_recover(&state); +} + + +uint64_t gNMIServer::getTimeNanosec() +{ + using namespace std::chrono; + + std::uint64_t tm = high_resolution_clock::now().time_since_epoch() / + nanoseconds(1); + + return tm; +} diff --git a/src/gnmi/gnmiserver.h b/src/gnmi/gnmiserver.h new file mode 100644 index 0000000..7dc1339 --- /dev/null +++ b/src/gnmi/gnmiserver.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GNMISERVER_H +#define GNMISERVER_H + +#include + +#include "gnmi.pb.h" +#include "gnmi.grpc.pb.h" +#include "sysrepoapi.h" +#include "gnmidata.h" +#include "eventreciverbase.h" + +#include + +using grpc::Server; +using grpc::ServerBuilder; +using grpc::ServerContext; +using grpc::ServerReaderWriter; +using grpc::Status; + +using gnmi::gNMI; +using gnmi::CapabilityRequest; +using gnmi::CapabilityResponse; +using gnmi::GetRequest; +using gnmi::GetResponse; +using gnmi::SetRequest; +using gnmi::SetResponse; +using gnmi::SubscribeRequest; +using gnmi::SubscribeResponse; + +/** + * @todo write docs + */ +class gNMIServer final : public gNMI::Service, + public virtual EventReceiver +{ +public: + gNMIServer(SysrepoAPI &sysrepo); + + Status Capabilities(ServerContext* context, + const CapabilityRequest* request, + CapabilityResponse* reply) override; + Status Get(ServerContext* context, const GetRequest* request, + GetResponse* reply) override; + Status Set(ServerContext* context, const SetRequest* request, + SetResponse* reply) override; + Status Subscribe(ServerContext* context, + ServerReaderWriter* stream) override; + + void receiveWriteEvent(SysrepoAPI * psender) override; + +private: + void parsePathMsg(const gnmi::Path &path); + void printPath(const gnmi::Path &path); + std::string convertToXPath(const gnmi::Path &path); + + void subscibeList(const gnmi::SubscriptionList &slist); + + void handleUpdateMessage(const gnmi::Update &msg); + void handleTypeValueMsg(const gnmi::TypedValue &msg); + + void xpathTogNMIEl(const std::string &str, gnmi::Path &path); + + uint64_t getTimeNanosec(); + +private: + SysrepoAPI &sysrepo; + gNMIData vData; + std::string dataPrefix; +}; + +#endif // GNMISERVER_H diff --git a/src/gnmi/log.cpp b/src/gnmi/log.cpp new file mode 100644 index 0000000..86d26b5 --- /dev/null +++ b/src/gnmi/log.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "log.h" + +void init_log(int option) +{ + openlog(NULL, option, LOG_DAEMON); +} + +void close_log() +{ + closelog(); +} + diff --git a/src/gnmi/log.h b/src/gnmi/log.h new file mode 100644 index 0000000..0bb01f0 --- /dev/null +++ b/src/gnmi/log.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __LOG_H__ +#define __LOG_H__ + +#include +#include + +#define ERROR(fmt, ...) \ + do { syslog(LOG_ERR, "%s:%d:%s():[ERROR]:" fmt "\n", __FILE__, \ + __LINE__, __func__, ##__VA_ARGS__); } while (0) + +#define DEBUG(fmt, ...) \ + do { syslog(LOG_DEBUG, "%s:%d:%s():[DEBUG]:" fmt "\n", __FILE__, \ + __LINE__, __func__, ##__VA_ARGS__); } while (0) + +void init_log(int option); + +void close_log(); + +#endif diff --git a/src/gnmi/main.cpp b/src/gnmi/main.cpp new file mode 100644 index 0000000..8115732 --- /dev/null +++ b/src/gnmi/main.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "log.h" +#include "gnmiserver.h" +#include "sysrepoapipool.h" + +#include +#include +#include +#include +#include +#include +#include + +const char *argp_program_version = "0.0.1"; +// const char *argp_program_bug_address = ""; +static char doc[] = "gNMI server implementation/integration for FD.io vpp "; +static char args_doc[] = ""; +static struct argp_option options[] = { + {"address", 'a', "x.x.x.x", 0, "Destination IPv4 address"}, + {"port", 'p', "port", 0, "Destination port"}, + {"sysrepo", 's', "name", 0, "Sysrepo name"}, + {"ssl/tls", 't', NULL, 0, "Enable ssl/tls"}, + {"serverKey", 'k', "file", 0, "Server Key"}, + {"serverCerts", 'c', "file", 0, "Server Certs"}, + {"rootCert", 'r', "file", 0, "Root Cert"}, + { 0 } +}; + +struct arguments { + std::string ip_address; + std::string port; + std::string sysrepo_name; + bool enable_ssltls; + std::string server_key; + std::string server_certs; + std::string root_cert; + + arguments() : ip_address{}, port{}, sysrepo_name{}, enable_ssltls{false}, + server_key{}, server_certs{}, root_cert{} {} +}; + +static error_t check_ip(const std::string &str) +{ + struct sockaddr_in sa; + int rc = inet_pton(AF_INET, str.c_str(), &(sa.sin_addr)); + if (1 != rc) { + ERROR("Wrong IPv4 address format."); + return ARGP_KEY_ERROR; + } + + return ARGP_KEY_ARG; +} + +static error_t parse_opt(int key, char *arg, struct argp_state *state) +{ + error_t rc = 0; + struct arguments *arguments = (struct arguments*) state->input; + + switch (key) { + case 'a': + arguments->ip_address = std::string(arg); + rc = check_ip(arguments->ip_address); + break; + + case 'p': + arguments->port = std::string(arg); + break; + + case 't': + arguments->enable_ssltls = true; + break; + + case 's': + arguments->sysrepo_name = std::string(arg); + break; + + case 'k': + arguments->server_key = std::string(arg); + break; + + case 'c': + arguments->server_certs = std::string(arg); + break; + + case 'r': + arguments->root_cert = std::string(arg); + break; + + case ARGP_KEY_ARG: + return rc; + + default: + return ARGP_ERR_UNKNOWN; + } + + return rc; +} + +static struct argp argp = { options, parse_opt, args_doc, doc, 0, 0, 0 }; +SysrepoApiPool sysrepoPoll; + +static std::string read_cert(const std::string &file) +{ + std::ifstream fo(file); + std::stringstream str; + + str << fo.rdbuf(); + + return str.str(); +} + +void RunServer(const struct arguments &arg) +{ + std::string server_address = std::string(arg.ip_address) + + std::string(":") + std::string(arg.port); + gNMIServer service(sysrepoPoll.get(arg.sysrepo_name)); + ServerBuilder builder; + + try { + if (arg.enable_ssltls) { + grpc::SslServerCredentialsOptions::PemKeyCertPair pkcp; + + pkcp.private_key = read_cert(arg.server_key); + pkcp.cert_chain = read_cert(arg.server_certs); + + grpc::SslServerCredentialsOptions ssl_opts; + ssl_opts.pem_root_certs = ""; + ssl_opts.pem_key_cert_pairs.push_back(pkcp); + ssl_opts.pem_root_certs = read_cert(arg.root_cert); + + auto creds = grpc::SslServerCredentials(ssl_opts); + + builder.AddListeningPort(server_address, creds); + } else { + + // Listen on the given address without any authentication mechanism. + builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); + } + // Register "service" as the instance through which we'll communicate with + // clients. In this case it corresponds to an *synchronous* service. + builder.RegisterService(&service); + // Finally assemble the server. + // builder. + std::unique_ptr server(builder.BuildAndStart()); + + if (server != nullptr) + { + std::cout << "Server listening on " << server_address << std::endl; + // Wait for the server to shutdown. Note that some other thread must be + // responsible for shutting down the server for this call to ever return. + server->Wait(); + } + else + { + throw std::logic_error("Failed to create Server."); + } + } catch(...) { + ERROR("Failed to create Server."); + exit(-1); + } + +} + +int main(int argc, char **argv) +{ + struct arguments arguments; + + init_log(LOG_PERROR); + + error_t rc = argp_parse(&argp, argc, argv, 0, 0, &arguments); + if (ARGP_KEY_ARG != rc) { + argp_help(&argp, stdout, ARGP_HELP_SEE, argv[0]); + exit(-1); + } + + if (arguments.ip_address.empty() || arguments.port.empty()) { + ERROR("IP address and port must be set."); + argp_help(&argp, stdout, ARGP_HELP_SEE, argv[0]); + exit(-1); + } + + if (arguments.enable_ssltls) { + if (arguments.server_key.empty()) { + ERROR("Server key must be set."); + argp_help(&argp, stdout, ARGP_HELP_SEE, argv[0]); + exit(-1); + } + + if (arguments.server_certs.empty()) { + ERROR("Server certificate must be set."); + argp_help(&argp, stdout, ARGP_HELP_SEE, argv[0]); + exit(-1); + } + } + + auto &sysrepo = sysrepoPoll.get(arguments.sysrepo_name); + sysrepo.connect(); + + std::string destination = std::string(arguments.ip_address) + + std::string(":") + std::string(arguments.port); + + RunServer(arguments); + + close_log(); + + return 0; +} diff --git a/src/gnmi/proto/CMakeLists.txt b/src/gnmi/proto/CMakeLists.txt new file mode 100644 index 0000000..8e70f77 --- /dev/null +++ b/src/gnmi/proto/CMakeLists.txt @@ -0,0 +1,52 @@ +set(protobuf_MODULE_COMPATIBLE TRUE) + +find_package(Protobuf REQUIRED) +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +message(STATUS "Using protobuf ${protobuf_VERSION}") + +set(_PROTOBUF_LIBPROTOBUF ${PROTOBUF_LIBRARIES}) +set(_PROTOBUF_PROTOC ${PROTOBUF_PROTOC_EXECUTABLE}) + +find_package(gRPC CONFIG REQUIRED) +set(_GRPC_GRPCPP gRPC::grpc++) +set(_GRPC_CPP_PLUGIN_EXECUTABLE $) + + +set(PROTOS + gnmi.proto + gnmi_ext.proto +) + +get_filename_component(hw_proto "gnmi.proto" ABSOLUTE) +get_filename_component(hw_proto_path "${hw_proto}" PATH) +get_filename_component(gnmi_ext_proto "gnmi_ext.proto" ABSOLUTE) +get_filename_component(gnmi_ext_proto_path "${gnmi_ext_proto}" PATH) + +# Generated sources +set(hw_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/gnmi.pb.cc") +set(hw_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/gnmi.pb.h") +set(hw_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/gnmi.grpc.pb.cc") +set(hw_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/gnmi.grpc.pb.h") +set(gnmi_ext_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/gnmi_ext.pb.cc") +set(gnmi_ext_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/gnmi_ext.pb.h") +set(gnmi_ext_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/gnmi_ext.grpc.pb.cc") +set(gnmi_ext_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/gnmi_ext.grpc.pb.h") + +add_custom_command( + OUTPUT "${hw_proto_srcs}" "${hw_proto_hdrs}" "${hw_grpc_srcs}" "${hw_grpc_hdrs}" "${gnmi_ext_proto_srcs}" "${gnmi_ext_proto_hdrs}" "${gnmi_ext_grpc_srcs}" "${gnmi_ext_grpc_hdrs}" + COMMAND ${_PROTOBUF_PROTOC} + ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" + --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" + -I "${hw_proto_path}" "${gnmi_ext_path}" + --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}" + "${hw_proto}" "${gnmi_ext_proto}" + DEPENDS "${hw_proto}" "${gnmi_ext_proto}") + +# Include generated *.pb.h files +include_directories("${CMAKE_CURRENT_BINARY_DIR}") + +add_library(proto ${hw_proto_srcs} ${hw_grpc_srcs} + ${gnmi_ext_proto_srcs} ${gnmi_ext_grpc_srcs}) +target_link_libraries(proto ${_GRPC_GRPCPP} ${_PROTOBUF_LIBPROTOBUF}) + diff --git a/src/gnmi/proto/gnmi.proto b/src/gnmi/proto/gnmi.proto new file mode 100644 index 0000000..99acce6 --- /dev/null +++ b/src/gnmi/proto/gnmi.proto @@ -0,0 +1,457 @@ +// +// Copyright 2016 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +syntax = "proto3"; + +import "google/protobuf/any.proto"; +import "google/protobuf/descriptor.proto"; +import "gnmi_ext.proto"; + +// Package gNMI defines a service specification for the gRPC Network Management +// Interface. This interface is defined to be a standard interface via which +// a network management system ("client") can subscribe to state values, +// retrieve snapshots of state information, and manipulate the state of a data +// tree supported by a device ("target"). +// +// This document references the gNMI Specification which can be found at +// http://github.com/openconfig/reference/blob/master/rpc/gnmi +package gnmi; + +// Define a protobuf FileOption that defines the gNMI service version. +extend google.protobuf.FileOptions { + // The gNMI service semantic version. + string gnmi_service = 1001; +} + +// gNMI_service is the current version of the gNMI service, returned through +// the Capabilities RPC. +option (gnmi_service) = "0.7.0"; + +service gNMI { + // Capabilities allows the client to retrieve the set of capabilities that + // is supported by the target. This allows the target to validate the + // service version that is implemented and retrieve the set of models that + // the target supports. The models can then be specified in subsequent RPCs + // to restrict the set of data that is utilized. + // Reference: gNMI Specification Section 3.2 + rpc Capabilities(CapabilityRequest) returns (CapabilityResponse); + // Retrieve a snapshot of data from the target. A Get RPC requests that the + // target snapshots a subset of the data tree as specified by the paths + // included in the message and serializes this to be returned to the + // client using the specified encoding. + // Reference: gNMI Specification Section 3.3 + rpc Get(GetRequest) returns (GetResponse); + // Set allows the client to modify the state of data on the target. The + // paths to modified along with the new values that the client wishes + // to set the value to. + // Reference: gNMI Specification Section 3.4 + rpc Set(SetRequest) returns (SetResponse); + // Subscribe allows a client to request the target to send it values + // of particular paths within the data tree. These values may be streamed + // at a particular cadence (STREAM), sent one off on a long-lived channel + // (POLL), or sent as a one-off retrieval (ONCE). + // Reference: gNMI Specification Section 3.5 + rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeResponse); +} + +// Notification is a re-usable message that is used to encode data from the +// target to the client. A Notification carries two types of changes to the data +// tree: +// - Deleted values (delete) - a set of paths that have been removed from the +// data tree. +// - Updated values (update) - a set of path-value pairs indicating the path +// whose value has changed in the data tree. +// Reference: gNMI Specification Section 2.1 +message Notification { + int64 timestamp = 1; // Timestamp in nanoseconds since Epoch. + Path prefix = 2; // Prefix used for paths in the message. + // An alias for the path specified in the prefix field. + // Reference: gNMI Specification Section 2.4.2 + string alias = 3; + repeated Update update = 4; // Data elements that have changed values. + repeated Path delete = 5; // Data elements that have been deleted. + // This notification contains a set of paths that are always updated together + // referenced by a globally unique prefix. + bool atomic = 6; +} + +// Update is a re-usable message that is used to store a particular Path, +// Value pair. +// Reference: gNMI Specification Section 2.1 +message Update { + Path path = 1; // The path (key) for the update. + Value value = 2 [deprecated=true]; // The value (value) for the update. + TypedValue val = 3; // The explicitly typed update value. + uint32 duplicates = 4; // Number of coalesced duplicates. +} + +// TypedValue is used to encode a value being sent between the client and +// target (originated by either entity). +message TypedValue { + // One of the fields within the val oneof is populated with the value + // of the update. The type of the value being included in the Update + // determines which field should be populated. In the case that the + // encoding is a particular form of the base protobuf type, a specific + // field is used to store the value (e.g., json_val). + oneof value { + string string_val = 1; // String value. + int64 int_val = 2; // Integer value. + uint64 uint_val = 3; // Unsigned integer value. + bool bool_val = 4; // Bool value. + bytes bytes_val = 5; // Arbitrary byte sequence value. + float float_val = 6; // Floating point value. + Decimal64 decimal_val = 7; // Decimal64 encoded value. + ScalarArray leaflist_val = 8; // Mixed type scalar array value. + google.protobuf.Any any_val = 9; // protobuf.Any encoded bytes. + bytes json_val = 10; // JSON-encoded text. + bytes json_ietf_val = 11; // JSON-encoded text per RFC7951. + string ascii_val = 12; // Arbitrary ASCII text. + // Protobuf binary encoded bytes. The message type is not included. + // See the specification at + // github.com/openconfig/reference/blob/master/rpc/gnmi/protobuf-vals.md + // for a complete specification. + bytes proto_bytes = 13; + } +} + +// Path encodes a data tree path as a series of repeated strings, with +// each element of the path representing a data tree node name and the +// associated attributes. +// Reference: gNMI Specification Section 2.2.2. +message Path { + // Elements of the path are no longer encoded as a string, but rather within + // the elem field as a PathElem message. + repeated string element = 1 [deprecated=true]; + string origin = 2; // Label to disambiguate path. + repeated PathElem elem = 3; // Elements of the path. + string target = 4; // The name of the target + // (Sec. 2.2.2.1) +} + +// PathElem encodes an element of a gNMI path, along ith any attributes (keys) +// that may be associated with it. +// Reference: gNMI Specification Section 2.2.2. +message PathElem { + string name = 1; // The name of the element in the path. + map key = 2; // Map of key (attribute) name to value. +} + +// Value encodes a data tree node's value - along with the way in which +// the value is encoded. This message is deprecated by gNMI 0.3.0. +// Reference: gNMI Specification Section 2.2.3. +message Value { + option deprecated = true; + bytes value = 1; // Value of the variable being transmitted. + Encoding type = 2; // Encoding used for the value field. +} + +// Encoding defines the value encoding formats that are supported by the gNMI +// protocol. These encodings are used by both the client (when sending Set +// messages to modify the state of the target) and the target when serializing +// data to be returned to the client (in both Subscribe and Get RPCs). +// Reference: gNMI Specification Section 2.3 +enum Encoding { + JSON = 0; // JSON encoded text. + BYTES = 1; // Arbitrarily encoded bytes. + PROTO = 2; // Encoded according to out-of-band agreed Protobuf. + ASCII = 3; // ASCII text of an out-of-band agreed format. + JSON_IETF = 4; // JSON encoded text as per RFC7951. +} + +// Error message previously utilised to return errors to the client. Deprecated +// in favour of using the google.golang.org/genproto/googleapis/rpc/status +// message in the RPC response. +// Reference: gNMI Specification Section 2.5 +message Error { + option deprecated = true; + uint32 code = 1; // Canonical gRPC error code. + string message = 2; // Human readable error. + google.protobuf.Any data = 3; // Optional additional information. +} + +// Decimal64 is used to encode a fixed precision decimal number. The value +// is expressed as a set of digits with the precision specifying the +// number of digits following the decimal point in the digit set. +message Decimal64 { + int64 digits = 1; // Set of digits. + uint32 precision = 2; // Number of digits following the decimal point. +} + +// ScalarArray is used to encode a mixed-type array of values. +message ScalarArray { + // The set of elements within the array. Each TypedValue message should + // specify only elements that have a field identifier of 1-7 (i.e., the + // values are scalar values). + repeated TypedValue element = 1; +} + +// SubscribeRequest is the message sent by the client to the target when +// initiating a subscription to a set of paths within the data tree. The +// request field must be populated and the initial message must specify a +// SubscriptionList to initiate a subscription. The message is subsequently +// used to define aliases or trigger polled data to be sent by the target. +// Reference: gNMI Specification Section 3.5.1.1 +message SubscribeRequest { + oneof request { + SubscriptionList subscribe = 1; // Specify the paths within a subscription. + Poll poll = 3; // Trigger a polled update. + AliasList aliases = 4; // Aliases to be created. + } + // Extension messages associated with the SubscribeRequest. See the + // gNMI extension specification for further definition. + repeated gnmi_ext.Extension extension = 5; +} + +// Poll is sent within a SubscribeRequest to trigger the device to +// send telemetry updates for the paths that are associated with the +// subscription. +// Reference: gNMI Specification Section Section 3.5.1.4 +message Poll { +} + +// SubscribeResponse is the message used by the target within a Subscribe RPC. +// The target includes a Notification message which is used to transmit values +// of the path(s) that are associated with the subscription. The same message +// is to indicate that the target has sent all data values once (is +// synchronized). +// Reference: gNMI Specification Section 3.5.1.4 +message SubscribeResponse { + oneof response { + Notification update = 1; // Changed or sampled value for a path. + // Indicate target has sent all values associated with the subscription + // at least once. + bool sync_response = 3; + // Deprecated in favour of google.golang.org/genproto/googleapis/rpc/status + Error error = 4 [deprecated=true]; + } + // Extension messages associated with the SubscribeResponse. See the + // gNMI extension specification for further definition. + repeated gnmi_ext.Extension extension = 5; +} + +// SubscriptionList is used within a Subscribe message to specify the list of +// paths that the client wishes to subscribe to. The message consists of a +// list of (possibly prefixed) paths, and options that relate to the +// subscription. +// Reference: gNMI Specification Section 3.5.1.2 +message SubscriptionList { + Path prefix = 1; // Prefix used for paths. + repeated Subscription subscription = 2; // Set of subscriptions to create. + // Whether target defined aliases are allowed within the subscription. + bool use_aliases = 3; + QOSMarking qos = 4; // DSCP marking to be used. + // Mode of the subscription. + enum Mode { + STREAM = 0; // Values streamed by the target (Sec. 3.5.1.5.2). + ONCE = 1; // Values sent once-off by the target (Sec. 3.5.1.5.1). + POLL = 2; // Values sent in response to a poll request (Sec. 3.5.1.5.3). + } + Mode mode = 5; + // Whether elements of the schema that are marked as eligible for aggregation + // should be aggregated or not. + bool allow_aggregation = 6; + // The set of schemas that define the elements of the data tree that should + // be sent by the target. + repeated ModelData use_models = 7; + // The encoding that the target should use within the Notifications generated + // corresponding to the SubscriptionList. + Encoding encoding = 8; + // An optional field to specify that only updates to current state should be + // sent to a client. If set, the initial state is not sent to the client but + // rather only the sync message followed by any subsequent updates to the + // current state. For ONCE and POLL modes, this causes the server to send only + // the sync message (Sec. 3.5.2.3). + bool updates_only = 9; +} + +// Subscription is a single request within a SubscriptionList. The path +// specified is interpreted (along with the prefix) as the elements of the data +// tree that the client is subscribing to. The mode determines how the target +// should trigger updates to be sent. +// Reference: gNMI Specification Section 3.5.1.3 +message Subscription { + Path path = 1; // The data tree path. + SubscriptionMode mode = 2; // Subscription mode to be used. + uint64 sample_interval = 3; // ns between samples in SAMPLE mode. + // Indicates whether values that not changed should be sent in a SAMPLE + // subscription. + bool suppress_redundant = 4; + // Specifies the maximum allowable silent period in nanoseconds when + // suppress_redundant is in use. The target should send a value at least once + // in the period specified. + uint64 heartbeat_interval = 5; +} + +// SubscriptionMode is the mode of the subscription, specifying how the +// target must return values in a subscription. +// Reference: gNMI Specification Section 3.5.1.3 +enum SubscriptionMode { + TARGET_DEFINED = 0; // The target selects the relevant mode for each element. + ON_CHANGE = 1; // The target sends an update on element value change. + SAMPLE = 2; // The target samples values according to the interval. +} + +// QOSMarking specifies the DSCP value to be set on transmitted telemetry +// updates from the target. +// Reference: gNMI Specification Section 3.5.1.2 +message QOSMarking { + uint32 marking = 1; +} + +// Alias specifies a data tree path, and an associated string which defines an +// alias which is to be used for this path in the context of the RPC. The alias +// is specified as a string which is prefixed with "#" to disambiguate it from +// data tree element paths. +// Reference: gNMI Specification Section 2.4.2 +message Alias { + Path path = 1; // The path to be aliased. + string alias = 2; // The alias value, a string prefixed by "#". +} + +// AliasList specifies a list of aliases. It is used in a SubscribeRequest for +// a client to create a set of aliases that the target is to utilize. +// Reference: gNMI Specification Section 3.5.1.6 +message AliasList { + repeated Alias alias = 1; // The set of aliases to be created. +} + +// SetRequest is sent from a client to the target to update values in the data +// tree. Paths are either deleted by the client, or modified by means of being +// updated, or replaced. Where a replace is used, unspecified values are +// considered to be replaced, whereas when update is used the changes are +// considered to be incremental. The set of changes that are specified within +// a single SetRequest are considered to be a transaction. +// Reference: gNMI Specification Section 3.4.1 +message SetRequest { + Path prefix = 1; // Prefix used for paths in the message. + repeated Path delete = 2; // Paths to be deleted from the data tree. + repeated Update replace = 3; // Updates specifying elements to be replaced. + repeated Update update = 4; // Updates specifying elements to updated. + // Extension messages associated with the SetRequest. See the + // gNMI extension specification for further definition. + repeated gnmi_ext.Extension extension = 5; +} + +// SetResponse is the response to a SetRequest, sent from the target to the +// client. It reports the result of the modifications to the data tree that were +// specified by the client. Errors for this RPC should be reported using the +// https://github.com/googleapis/googleapis/blob/master/google/rpc/status.proto +// message in the RPC return. The gnmi.Error message can be used to add additional +// details where required. +// Reference: gNMI Specification Section 3.4.2 +message SetResponse { + Path prefix = 1; // Prefix used for paths. + // A set of responses specifying the result of the operations specified in + // the SetRequest. + repeated UpdateResult response = 2; + Error message = 3 [deprecated=true]; // The overall status of the transaction. + int64 timestamp = 4; // Timestamp of transaction (ns since epoch). + // Extension messages associated with the SetResponse. See the + // gNMI extension specification for further definition. + repeated gnmi_ext.Extension extension = 5; +} + +// UpdateResult is used within the SetResponse message to communicate the +// result of an operation specified within a SetRequest message. +// Reference: gNMI Specification Section 3.4.2 +message UpdateResult { + // The operation that was associated with the Path specified. + enum Operation { + INVALID = 0; + DELETE = 1; // The result relates to a delete of Path. + REPLACE = 2; // The result relates to a replace of Path. + UPDATE = 3; // The result relates to an update of Path. + } + // Deprecated timestamp for the UpdateResult, this field has been + // replaced by the timestamp within the SetResponse message, since + // all mutations effected by a set should be applied as a single + // transaction. + int64 timestamp = 1 [deprecated=true]; + Path path = 2; // Path associated with the update. + Error message = 3 [deprecated=true]; // Status of the update operation. + Operation op = 4; // Update operation type. +} + +// GetRequest is sent when a client initiates a Get RPC. It is used to specify +// the set of data elements for which the target should return a snapshot of +// data. The use_models field specifies the set of schema modules that are to +// be used by the target - where use_models is not specified then the target +// must use all schema models that it has. +// Reference: gNMI Specification Section 3.3.1 +message GetRequest { + Path prefix = 1; // Prefix used for paths. + repeated Path path = 2; // Paths requested by the client. + // Type of elements within the data tree. + enum DataType { + ALL = 0; // All data elements. + CONFIG = 1; // Config (rw) only elements. + STATE = 2; // State (ro) only elements. + // Data elements marked in the schema as operational. This refers to data + // elements whose value relates to the state of processes or interactions + // running on the device. + OPERATIONAL = 3; + } + DataType type = 3; // The type of data being requested. + Encoding encoding = 5; // Encoding to be used. + repeated ModelData use_models = 6; // The schema models to be used. + // Extension messages associated with the GetRequest. See the + // gNMI extension specification for further definition. + repeated gnmi_ext.Extension extension = 7; +} + +// GetResponse is used by the target to respond to a GetRequest from a client. +// The set of Notifications corresponds to the data values that are requested +// by the client in the GetRequest. +// Reference: gNMI Specification Section 3.3.2 +message GetResponse { + repeated Notification notification = 1; // Data values. + Error error = 2 [deprecated=true]; // Errors that occurred in the Get. + // Extension messages associated with the GetResponse. See the + // gNMI extension specification for further definition. + repeated gnmi_ext.Extension extension = 3; +} + +// CapabilityRequest is sent by the client in the Capabilities RPC to request +// that the target reports its capabilities. +// Reference: gNMI Specification Section 3.2.1 +message CapabilityRequest { + // Extension messages associated with the CapabilityRequest. See the + // gNMI extension specification for further definition. + repeated gnmi_ext.Extension extension = 1; +} + +// CapabilityResponse is used by the target to report its capabilities to the +// client within the Capabilities RPC. +// Reference: gNMI Specification Section 3.2.2 +message CapabilityResponse { + repeated ModelData supported_models = 1; // Supported schema models. + repeated Encoding supported_encodings = 2; // Supported encodings. + string gNMI_version = 3; // Supported gNMI version. + // Extension messages associated with the CapabilityResponse. See the + // gNMI extension specification for further definition. + repeated gnmi_ext.Extension extension = 4; +} + +// ModelData is used to describe a set of schema modules. It can be used in a +// CapabilityResponse where a target reports the set of modules that it +// supports, and within the SubscribeRequest and GetRequest messages to specify +// the set of models from which data tree elements should be reported. +// Reference: gNMI Specification Section 3.2.3 +message ModelData { + string name = 1; // Name of the model. + string organization = 2; // Organization publishing the model. + string version = 3; // Semantic version of the model. +} + diff --git a/src/gnmi/proto/gnmi_ext.proto b/src/gnmi/proto/gnmi_ext.proto new file mode 100644 index 0000000..01b2864 --- /dev/null +++ b/src/gnmi/proto/gnmi_ext.proto @@ -0,0 +1,74 @@ +// +// Copyright 2018 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +syntax = "proto3"; + +// Package gnmi_ext defines a set of extensions messages which can be optionally +// included with the request and response messages of gNMI RPCs. A set of +// well-known extensions are defined within this file, along with a registry for +// extensions defined outside of this package. +package gnmi_ext; + +// The Extension message contains a single gNMI extension. +message Extension { + oneof ext { + RegisteredExtension registered_ext = 1; // A registered extension. + // Well known extensions. + MasterArbitration master_arbitration = 2; // Master arbitration extension. + } +} + +// The RegisteredExtension message defines an extension which is defined outside +// of this file. +message RegisteredExtension { + ExtensionID id = 1; // The unique ID assigned to this extension. + bytes msg = 2; // The binary-marshalled protobuf extension payload. +} + +// RegisteredExtension is an enumeration acting as a registry for extensions +// defined by external sources. +enum ExtensionID { + EID_UNSET = 0; + // New extensions are to be defined within this enumeration - their definition + // MUST link to a reference describing their implementation. + + // An experimental extension that may be used during prototyping of a new + // extension. + EID_EXPERIMENTAL = 999; +} + +// MasterArbitration is used to select the master among multiple gNMI clients +// with the same Roles. The client with the largest election_id is honored as +// the master. +// The document about gNMI master arbitration can be found at +// https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-master-arbitration.md +message MasterArbitration { + Role role = 1; + Uint128 election_id = 2; +} + +// Representation of unsigned 128-bit integer. +message Uint128 { + uint64 high = 1; + uint64 low = 2; +} + +// There can be one master for each role. The role is identified by its id. +message Role { + string id = 1; + // More fields can be added if needed, for example, to specify what paths the + // role can read/write. +} + diff --git a/src/gnmi/sysrepoapi.cpp b/src/gnmi/sysrepoapi.cpp new file mode 100644 index 0000000..dcf7668 --- /dev/null +++ b/src/gnmi/sysrepoapi.cpp @@ -0,0 +1,419 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sysrepoapi.h" +#include "log.h" + +#include +#include + +void event_notif_cb(const sr_ev_notif_type_t notif_type, + const char *xpath, const sr_val_t *values, + const size_t value_cnt, time_t timestamp, + void *private_ct) +{ + if (NULL == xpath || NULL == values || NULL == private_ct) { + ERROR("Error, NULL detect."); + return; + } + + SysrepoAPI *sapi = (SysrepoAPI*) private_ct; + + for (size_t i = 0; i < value_cnt; i++ ) { + sapi->setOutVal(*(values + i)); + } + + sapi->sendEvent(sapi); +} + +SysrepoAPI::SysrepoAPI() +{ + sr_log_stderr(SR_LL_DBG); +} + +SysrepoAPI::~SysrepoAPI() +{ + if (nullptr != subscription) { + sr_unsubscribe(sess, subscription); + } + + if (nullptr != sess) { + sr_session_stop(sess); + } + + if (nullptr != conn) { + sr_disconnect(conn); + } +} + +void SysrepoAPI::setSysrepoName(const std::string& sysrepo_name) +{ + this->sysrepo_name = sysrepo_name; +} + +void SysrepoAPI::connect() +{ + int rc = sr_connect(sysrepo_name.c_str(), SR_CONN_DEFAULT, &conn); + if (SR_ERR_OK != rc) { + ERROR("Failed connect to sysrepo."); + throw std::runtime_error("Failed connect to sysrepo."); + } +} + +void SysrepoAPI::createSession(sr_datastore_e sesType) +{ + int rc = SR_ERR_OK; + + if (nullptr == conn) { + ERROR("Error, nullptr detect."); + throw std::runtime_error("Error, nullptr detect."); + } + + closeSession(); + + rc = sr_session_start(conn, sesType, SR_SESS_DEFAULT, &sess); + if (SR_ERR_OK != rc) { + ERROR("Failed connect to sysrepo session."); + throw std::runtime_error("Failed connect to sysrepo session."); + } +} + +void SysrepoAPI::closeSession() +{ + if (nullptr != sess) { + sr_session_stop(sess); + sess = nullptr; + } +} + +void SysrepoAPI::commit() +{ + int rc = SR_ERR_OK; + + if (nullptr == sess) { + ERROR("Error, nullptr detect."); + throw std::runtime_error("Error, nullptr detect."); + } + + rc = sr_commit(sess); + if (SR_ERR_OK != rc) { + ERROR("Failed commit change to sysrepo."); + throw std::runtime_error("Failed commit change to sysrepo."); + } +} + +void SysrepoAPI::addData(const gNMIData& data) +{ + inputData.push_back(data); +} + +void SysrepoAPI::getItemMessage() +{ + if (nullptr == sess) { + ERROR("Error, nullptr detect."); + throw std::runtime_error("Error, nullptr detect."); + } + + for (const auto &data : inputData) { + sr_val_t *value = nullptr; + sr_val_iter_t *iter = nullptr; +// size_t count; + DEBUG("Path: %s", data.getXPath(gNMIData::xPathType::sysrepoPath).c_str()); + + int rc = sr_get_items_iter(sess, data.getXPath(gNMIData::xPathType::sysrepoPath).c_str(), + &iter); +// int rc = sr_get_item(sess, data.getXPath(gNMIData::xPathType::sysrepoPath).c_str(), +// &value); + if (SR_ERR_OK != rc) { + ERROR("Failed get item from sysrepo."); + throw std::runtime_error("Failed get item from sysrepo."); + } + + while (SR_ERR_OK == sr_get_item_next(sess, iter, &value)) { + sr_print_val(value); + +// sr_print_val(value); + + gNMIData oData; + + oData.setXPath(value->xpath, gNMIData::xPathType::sysrepoPath); + oData.setValue(sr_val_to_str(value)); + + outpuData.push_back(oData); + + sr_free_val(value); + } + } +} + +void SysrepoAPI::setItemMessage() +{ + int rc = SR_ERR_OK; + +// sr_xpath_ctx_t state = {0}; + + if (nullptr == sess) { + ERROR("Error, nullptr detect."); + throw std::runtime_error("Error, nullptr detect."); + } + + for (const auto &data : inputData) { + DEBUG("Set path: %s", data.getXPath(gNMIData::xPathType::sysrepoPath).c_str()); + DEBUG("Val: %s", data.getStr().c_str()); + +// if (gNMIData::ValueType::dStringVal == data.dataType()) { + rc = sr_set_item_str(sess, + data.getXPath(gNMIData::xPathType::sysrepoPath).c_str(), + data.getStr().c_str(), SR_EDIT_DEFAULT); +// } else { +// sr_val_t value = { 0 }; +// setSrVal(value, data); +// +// rc = sr_set_item(sess, data.getXPath(gNMIData::xPathType::sysrepoPath).c_str(), +// &value, SR_EDIT_DEFAULT); +// } + if (SR_ERR_OK != rc) { + ERROR("Failed set item to sysrepo."); + throw std::runtime_error("Failed set item to sysrepo."); + } + + //TODO: Hmm, this is duplication copy, I`m not sure with this solution. + outpuData.push_back(data); + } +} + +void SysrepoAPI::rpcSend(const std::string& xpath, sr_val_t &input) +{ + int rc = SR_ERR_OK; + sr_val_t *output = nullptr; + size_t output_cnt = 0; + + if (nullptr == sess) { + ERROR("Error, nullptr detect."); + throw std::runtime_error("Error, nullptr detect."); + } + + rc = sr_rpc_send(sess, xpath.c_str(), &input, 1, &output, &output_cnt); + if (SR_ERR_OK != rc) { + ERROR("RPC message failed: %s.", sr_strerror(rc)); + throw std::runtime_error("RPC message failed."); + } +} + +void SysrepoAPI::eventSubscribeMessage() +{ + int rc = SR_ERR_OK; + + if (nullptr == sess) { + ERROR("Error, nullptr detect."); + throw std::runtime_error("Error, nullptr detect."); + } + + for (const auto &data : inputData) { + DEBUG("Register subscribe path: %s", + data.getXPath(gNMIData::xPathType::sysrepoPath).c_str()); + rc = sr_event_notif_subscribe(sess, + data.getXPath(gNMIData::xPathType::sysrepoPath).c_str(), + event_notif_cb, this, SR_SUBSCR_DEFAULT, + &subscription); + if (SR_ERR_OK != rc) { + ERROR("Event notification subscribe failed: %s.", sr_strerror(rc)); + throw std::runtime_error("Event notification subscribe failed."); + } + } +} + +const std::list &SysrepoAPI::getSchemas() +{ + int rc = SR_ERR_OK; + sr_schema_t *schemas = nullptr; + size_t schema_cnt = 0; + + if (nullptr == sess) { + ERROR("Error, nullptr detect."); + throw std::runtime_error("Error, nullptr detect."); + } + + rc = sr_list_schemas(sess, &schemas, &schema_cnt); + if (SR_ERR_OK != rc) { + ERROR("List schemas failed: %s.", sr_strerror(rc)); + throw std::runtime_error("List schemas failed failed."); + } + + for (size_t i = 0; i < schema_cnt; i++) { +// DEBUG("Module: %s, xpath: %s, prefix: %s, r yang: %s, revison: %s", +// schemas[i].module_name, schemas[i].ns, schemas[i].prefix, +// schemas[i].revision.file_path_yang, schemas[i].revision.revision); + SysrepSchema schema; + + if (NULL != schemas[i].module_name) { + schema.moduleName = std::string(schemas[i].module_name); + } + + if (NULL != schemas[i].revision.revision) { + schema.revision = std::string(schemas[i].revision.revision); + } + schemaList.push_back(schema); + } + + sr_free_schemas(schemas, schema_cnt); + + return schemaList; +} + +std::list SysrepoAPI::getOutputData() const +{ + return outpuData; +} + +void SysrepoAPI::cleanData() +{ + inputData.clear(); + outpuData.clear(); + schemaList.clear(); +} + +enum sr_type_e SysrepoAPI::convergNMITypeToSysrepo(gNMIData::ValueType type) +{ + switch (type) { + case gNMIData::ValueType::dStringVal: + return SR_STRING_T; + + case gNMIData::ValueType::dIntVal: + return SR_INT32_T; + + case gNMIData::ValueType::UnknownVal: + default: + return SR_UNKNOWN_T; + } + + return SR_UNKNOWN_T; +} + +gNMIData::ValueType SysrepoAPI::convergSysrepoTypeTogNMI(sr_type_e type) +{ + switch (type) { + case SR_STRING_T: + return gNMIData::ValueType::dStringVal; + + case SR_INT8_T: + case SR_INT16_T: + case SR_INT32_T: + case SR_INT64_T: + case SR_UINT8_T: + case SR_UINT16_T: + case SR_UINT32_T: + case SR_UINT64_T: + return gNMIData::ValueType::dIntVal; + + case SR_ANYDATA_T: + case SR_ANYXML_T: + case SR_BINARY_T: + case SR_BITS_T: + case SR_BOOL_T: + case SR_CONTAINER_PRESENCE_T: + case SR_CONTAINER_T: + case SR_DECIMAL64_T: + case SR_ENUM_T: + case SR_IDENTITYREF_T: + case SR_INSTANCEID_T: + case SR_LEAF_EMPTY_T: + case SR_LIST_T: + case SR_TREE_ITERATOR_T: + case SR_UNKNOWN_T: + return gNMIData::ValueType::UnknownVal; + + default: + return gNMIData::ValueType::UnknownVal; + } + + return gNMIData::ValueType::UnknownVal; +} + +void SysrepoAPI::setSrVal(sr_val_t& value, const gNMIData& gData) +{ + value.type = convergNMITypeToSysrepo(gData.dataType()); + + switch (gData.dataType()) { + case gNMIData::ValueType::dStringVal: + value.data.string_val = (char *) gData.getStr().c_str(); + DEBUG("Val: %s", value.data.string_val); + break; + + case gNMIData::ValueType::dIntVal: + value.data.int32_val = gData.getInt(); + DEBUG("Val: %d", value.data.int32_val); + break; + + case gNMIData::ValueType::UnknownVal: + default: + DEBUG("Unknown value."); + //TODO: + break; + } +} + +void SysrepoAPI::setOutVal(const sr_val_t& value) +{ +// auto key = convergSysrepoTypeTogNMI(value.type); + std::string str; + + //TODO: Need rewrite, only for test + switch (value.type) { + case SR_STRING_T: + str = std::string(value.data.string_val); + break; + + case SR_INT8_T: + str = std::to_string(value.data.int8_val); + break; + + case SR_INT16_T: + str = std::to_string(value.data.int16_val); + break; + + case SR_INT32_T: + str = std::to_string(value.data.int32_val); + break; + + case SR_INT64_T: + str = std::to_string(value.data.int64_val); + break; + + case SR_UINT8_T: + str = std::to_string(value.data.uint8_val); + break; + case SR_UINT16_T: + str = std::to_string(value.data.uint16_val); + break; + case SR_UINT32_T: + str = std::to_string(value.data.uint32_val); + break; + case SR_UINT64_T: + str = std::to_string(value.data.uint64_val); + break; + + default: + //TODO: Need implement. + break; + } + + gNMIData sData; + sData.setXPath(value.xpath, gNMIData::xPathType::sysrepoPath); + sData.setValue(str); + + outpuData.push_back(sData); +} diff --git a/src/gnmi/sysrepoapi.h b/src/gnmi/sysrepoapi.h new file mode 100644 index 0000000..8ebcf95 --- /dev/null +++ b/src/gnmi/sysrepoapi.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYSREPOAPI_H +#define SYSREPOAPI_H + +#include "gnmidata.h" +#include "eventreciverbase.h" + +#include +#include +#include +#include + +#include + +struct SysrepoValue +{ + void setXPath(const std::string &str) { + std::string tmp = str; + std::size_t pos = 0; + + while (std::string::npos != (pos = tmp.find(":", pos))) { + tmp.replace(pos, std::string(":").length(), "/"); + } + + xpath = tmp; + } + + std::string xpath; + std::string value; +}; + +struct SysrepSchema +{ + std::string moduleName; + std::string revision; +}; + +/** + * @todo write docs + */ +class SysrepoAPI : public virtual BaseSender +{ +public: + /** + * Default constructor + */ + SysrepoAPI(); + + /** + * Destructor + */ + ~SysrepoAPI(); + + void setSysrepoName(const std::string &sysrepo_name); + + void connect(); + void createSession(sr_datastore_e sesType = SR_DS_RUNNING); + void closeSession(); + void commit(); + + void addData(const gNMIData &data); + void getItemMessage(); + void setItemMessage(); + void rpcSend(const std::string &xpath, sr_val_t &input); + void eventSubscribeMessage(); + const std::list &getSchemas(); +// void print_value(); + + std::list getOutputData() const; + void cleanData(); + +private: + friend void event_notif_cb(const sr_ev_notif_type_t notif_type, + const char *xpath, const sr_val_t *values, + const size_t value_cnt, time_t timestamp, + void *private_ct); + enum sr_type_e convergNMITypeToSysrepo(gNMIData::ValueType type); + gNMIData::ValueType convergSysrepoTypeTogNMI(sr_type_e type); + void setSrVal(sr_val_t &value, const gNMIData &gData); + void setOutVal(const sr_val_t &value); + +private: + std::string sysrepo_name = "app"; + sr_conn_ctx_t *conn = nullptr; + sr_session_ctx_t *sess = nullptr; + sr_subscription_ctx_t *subscription = nullptr; + + std::list inputData; + std::list outpuData; + std::list schemaList; + +}; + +#endif // SYSREPOAPI_H diff --git a/src/gnmi/sysrepoapipool.cpp b/src/gnmi/sysrepoapipool.cpp new file mode 100644 index 0000000..7e94a56 --- /dev/null +++ b/src/gnmi/sysrepoapipool.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "sysrepoapipool.h" + +SysrepoApiPool::SysrepoApiPool() + : pool{} +{ + +} + +SysrepoApiPool::~SysrepoApiPool() +{ + +} + +SysrepoAPI & SysrepoApiPool::get(const std::string &name) +{ + auto &sapi = pool[name]; + sapi.setSysrepoName(name); + + return sapi; +} + +void SysrepoApiPool::remove(const std::string &name) +{ + pool.erase(name); +} + diff --git a/src/gnmi/sysrepoapipool.h b/src/gnmi/sysrepoapipool.h new file mode 100644 index 0000000..ddc3260 --- /dev/null +++ b/src/gnmi/sysrepoapipool.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef SYSREPOAPIPOOL_H +#define SYSREPOAPIPOOL_H + +#include "sysrepoapi.h" + +#include +#include + +/** + * @todo write docs + */ +class SysrepoApiPool +{ +public: + /** + * Default constructor + */ + SysrepoApiPool(); + + /** + * Destructor + */ + ~SysrepoApiPool(); + + SysrepoAPI &get(const std::string &name); + void remove(const std::string &name); + +private: + std::map pool; +}; + +#endif // SYSREPOAPIPOOL_H diff --git a/src/gnmi/xml2json.cpp b/src/gnmi/xml2json.cpp new file mode 100644 index 0000000..15dac41 --- /dev/null +++ b/src/gnmi/xml2json.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "xml2json.h" +#include "log.h" + +#include +#include +#include + +XML2JSON::XML2JSON(const XML2JSON& other) +{ + +} + +XML2JSON::~XML2JSON() +{ + +} + +void XML2JSON::setData(const std::string& data, XML2JSON::string_type_t type) +{ + switch (type) { + case string_type_t::JSONSTRING: + { + Json::Reader read; + DEBUG("JSONSTRING: %s", data.c_str()); + + read.parse(data, jsonRoot); + DEBUG("Root size: %d", jsonRoot.size()); + } + break; + + case string_type_t::XMLSTRING: + { + int rc = 0; + rc = xmlDoc.load_string(data.c_str(), + pugi::parse_default | pugi::parse_comments); + if (0 != rc) { + ERROR("Error parse xml string."); + throw std::logic_error("Error parse xml string."); + } + } + break; + + default: + throw std::logic_error("Unknown type."); + break; + } +} + +void XML2JSON::setPrefix(const std::string& prefix) +{ + this->prefix = prefix; +} + +std::string XML2JSON::getJson() +{ + switch (type) { + case string_type_t::JSONSTRING: + //TODO: + return std::string(""); + + case string_type_t::XMLSTRING: + //TODO: + return std::string(""); + + default: + break; + } + + throw std::logic_error("Unknown type."); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wimplicit-fallthrough" +std::string XML2JSON::getXML() +{ + std::ostringstream stream; + + switch (type) { + case string_type_t::JSONSTRING: + createXML(); + + case string_type_t::XMLSTRING: + xmlDoc.print(stream); + DEBUG("XML doc: %s", xmlDoc.path().c_str()); + return stream.str(); + + default: + break; + } + + throw std::logic_error("Unknown type."); +} +#pragma GCC diagnostic pop + +const std::list & XML2JSON::getgNMIData() +{ + createGNMIData(); + + return gnmiData; +} + +void XML2JSON::parseJSON() +{ +// parseJSONValueType(jsonRoot); +} + +void XML2JSON::parseJSONValueType(const Json::Value& value, + pugi::xml_node &node) +{ + switch (value.type()) { + case Json::nullValue: + break; + + case Json::intValue: + DEBUG("Value: %d", value.asInt()); + node.append_child(pugi::node_pcdata).set_value( + std::to_string(value.asInt()).c_str()); + break; + + case Json::uintValue: + DEBUG("Value: %d", value.asUInt()); + node.append_child(pugi::node_pcdata).set_value( + std::to_string(value.asUInt()).c_str()); + break; + + case Json::realValue: + DEBUG("Value: %f", value.asFloat()); + node.append_child(pugi::node_pcdata).set_value( + std::to_string(value.asFloat()).c_str()); + break; + + case Json::stringValue: + DEBUG("Value: %s", value.asString().c_str()); + node.append_child(pugi::node_pcdata).set_value( + value.asString().c_str()); + break; + + case Json::booleanValue: + DEBUG("Value: %d", value.asBool()); + node.append_child(pugi::node_pcdata).set_value( + std::to_string(value.asBool()).c_str()); + break; + + case Json::arrayValue: + parseJSONArray(value, node); + break; + + case Json::objectValue: + parseJSONObject(value, node); + break; + + default: + DEBUG("Unknown value type."); + break; + } +} + +void XML2JSON::parseJSONValueType(const Json::Value& value, + const std::string& prefix) +{ + switch (value.type()) { + case Json::nullValue: + { + gNMIData data; + std::string tmp(prefix); + tmp.pop_back(); + data.setXPath(tmp); + gnmiData.push_back(data); + } + break; + + case Json::intValue: + // DEBUG("Value: %d", value.asInt()); + { + gNMIData data; + std::string tmp(prefix); + tmp.pop_back(); + data.setXPath(tmp); + data.setValue(value.asInt()); + gnmiData.push_back(data); + } + break; + + case Json::uintValue: + // DEBUG("Value: %d", value.asUInt()); + { + gNMIData data; + std::string tmp(prefix); + tmp.pop_back(); + data.setXPath(tmp); + data.setValue(value.asUInt()); + gnmiData.push_back(data); + } + break; + + case Json::realValue: + // DEBUG("Value: %f", value.asFloat()); + { + gNMIData data; + std::string tmp(prefix); + tmp.pop_back(); + data.setXPath(tmp); + data.setValue(value.asFloat()); + gnmiData.push_back(data); + } + break; + + case Json::stringValue: + // DEBUG("Value: %s", value.asString().c_str()); + { + gNMIData data; + std::string tmp(prefix); + tmp.pop_back(); + data.setXPath(tmp); + data.setValue(value.asString()); + gnmiData.push_back(data); + } + break; + + case Json::booleanValue: + // DEBUG("Value: %d", value.asBool()); + { + gNMIData data; + std::string tmp(prefix); + tmp.pop_back(); + data.setXPath(tmp); + data.setValue(value.asBool() ? "true" : "false"); + gnmiData.push_back(data); + } + break; + + case Json::arrayValue: + parseJSONArray(value, prefix); + break; + + case Json::objectValue: + parseJSONObject(value, prefix); + break; + + default: + DEBUG("Unknown value type."); + break; + } +} + +void XML2JSON::parseJSONArray(const Json::Value& value, pugi::xml_node &node) +{ + if (Json::arrayValue != value.type()) { + ERROR("Wrong JSON Value type"); + throw std::logic_error("Wrong JSON Value type"); + } + + for (const auto &elm : value) { + parseJSONValueType(elm, node); + } +} + +void XML2JSON::parseJSONArray(const Json::Value& value, + const std::string& prefix) +{ + if (Json::arrayValue != value.type()) { + ERROR("Wrong JSON Value type"); + throw std::logic_error("Wrong JSON Value type"); + } + + for (const auto &elm : value) { + parseJSONValueType(elm, prefix); + } +} + +void XML2JSON::parseJSONObject(const Json::Value& value, pugi::xml_node &node) +{ + if (Json::objectValue != value.type()) { + ERROR("Wrong JSON Value type"); + throw std::logic_error("Wrong JSON Value type"); + } + + for (const auto &name : value.getMemberNames()) { + DEBUG("Name: %s", name.c_str()); + auto child = node.append_child(name.c_str()); + parseJSONValueType(value.get(name, value), child); + } +} + +void XML2JSON::parseJSONObject(const Json::Value& value, + const std::string& prefix) +{ + if (Json::objectValue != value.type()) { + ERROR("Wrong JSON Value type"); + throw std::logic_error("Wrong JSON Value type"); + } + + for (const auto &name : value.getMemberNames()) { + DEBUG("Name: %s", name.c_str()); + std::string tmp = prefix + name + "/"; + parseJSONValueType(value.get(name, value), tmp); + } +} + +void XML2JSON::createXML() +{ +// auto elm = xmlDoc.append_child(pugi::xml_node_type::node_document); + parseJSONValueType(jsonRoot, xmlDoc); +} + +void XML2JSON::createGNMIData() +{ + std::string prefix = this->prefix + "/"; + parseJSONValueType(jsonRoot, prefix); +} diff --git a/src/gnmi/xml2json.h b/src/gnmi/xml2json.h new file mode 100644 index 0000000..fd99a2d --- /dev/null +++ b/src/gnmi/xml2json.h @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2019 PANTHEON.tech. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef XML2JSON_H +#define XML2JSON_H + +#include +#include + +#include +#include + +#include "gnmidata.h" + +/** + * @todo write docs + */ +class XML2JSON +{ +public: + enum class string_type_t { + XMLSTRING, + JSONSTRING + }; + +public: + /** + * Default constructor + */ + XML2JSON() = default; + + /** + * Copy Constructor + * + * @param other TODO + */ + XML2JSON(const XML2JSON& other); + + /** + * Destructor + */ + ~XML2JSON(); + + /** + * @todo write docs + * + * @param other TODO + * @return TODO + */ +// XML2JSON& operator=(const XML2JSON& other); + + void setData(const std::string &data, string_type_t type = string_type_t::JSONSTRING); + void setPrefix(const std::string &prefix); + std::string getJson(); + std::string getXML(); + const std::list &getgNMIData(); + +private: + void parseJSON(); + void parseJSONValueType(const Json::Value &value, pugi::xml_node &node); + void parseJSONValueType(const Json::Value &value, const std::string &prefix); + void parseJSONArray(const Json::Value &value, pugi::xml_node &node); + void parseJSONArray(const Json::Value &value, const std::string &prefix); + void parseJSONObject(const Json::Value &value, pugi::xml_node &node); + void parseJSONObject(const Json::Value &value, const std::string &prefix); + + void createXML(); + void createGNMIData(); + +private: + std::string prefix = "/"; + string_type_t type = string_type_t::JSONSTRING; + Json::Value jsonRoot; + pugi::xml_document xmlDoc; + std::list gnmiData; +}; + +#endif // XML2JSON_H -- cgit 1.2.3-korg