Skip to content

Commit

Permalink
gNMI Subscribe sanity test (sonic-net#334)
Browse files Browse the repository at this point in the history
Co-authored-by: Srikishen Pondicherry Shanmugam <[email protected]>
Co-authored-by: bhagatgit <[email protected]>
  • Loading branch information
3 people authored Jul 17, 2024
1 parent 7ae6ac1 commit d7c240a
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 0 deletions.
74 changes: 74 additions & 0 deletions lib/gnmi/gnmi_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "gutil/status_matchers.h"
#include "p4/v1/p4runtime.grpc.pb.h"
#include "p4_pdpi/p4_runtime_session.h"
#include "proto/gnmi/gnmi.pb.h"
#include "re2/re2.h"
#include "include/nlohmann/json.hpp"
#include "thinkit/ssh_client.h"
Expand Down Expand Up @@ -115,4 +116,77 @@ absl::StatusOr<std::string> ParseGnmiGetResponse(
return match_tag_json->dump();
}

absl::Status SetGnmiConfigPath(gnmi::gNMI::Stub* sut_gnmi_stub,
absl::string_view config_path,
GnmiSetType operation, absl::string_view value) {
ASSIGN_OR_RETURN(gnmi::SetRequest request,
BuildGnmiSetRequest(config_path, operation, value));
LOG(INFO) << "Sending SET request: " << request.ShortDebugString();
gnmi::SetResponse response;
grpc::ClientContext context;
auto status = sut_gnmi_stub->Set(&context, request, &response);
if (!status.ok()) {
LOG(INFO) << "SET request failed! Error code: " << status.error_code()
<< " , Error message: " << status.error_message();
return gutil::InternalErrorBuilder();
}
LOG(INFO) << "Received SET response: " << response.ShortDebugString();
return absl::OkStatus();
}

absl::StatusOr<std::string> GetGnmiStatePathInfo(
gnmi::gNMI::Stub* sut_gnmi_stub, absl::string_view state_path,
absl::string_view resp_parse_str) {
ASSIGN_OR_RETURN(gnmi::GetRequest request,
BuildGnmiGetRequest(state_path, gnmi::GetRequest::STATE));
LOG(INFO) << "Sending GET request: " << request.ShortDebugString();
gnmi::GetResponse response;
grpc::ClientContext context;
auto status = sut_gnmi_stub->Get(&context, request, &response);
if (!status.ok()) {
LOG(INFO) << "GET request failed! Error code: " << status.error_code()
<< " , Error message: " << status.error_message();
return gutil::InternalErrorBuilder();
}
LOG(INFO) << "Received GET response: " << response.ShortDebugString();
ASSIGN_OR_RETURN(std::string state_path_response,
ParseGnmiGetResponse(response, resp_parse_str));
return state_path_response;
}

void AddSubtreeToGnmiSubscription(absl::string_view subtree_root,
gnmi::SubscriptionList& subscription_list,
gnmi::SubscriptionMode mode,
bool suppress_redundant,
absl::Duration interval) {
gnmi::Subscription* subscription = subscription_list.add_subscription();
subscription->set_mode(mode);
if (mode == gnmi::SAMPLE) {
subscription->set_sample_interval(absl::ToInt64Nanoseconds(interval));
}
subscription->set_suppress_redundant(suppress_redundant);
*subscription->mutable_path() = ConvertOCStringToPath(subtree_root);
}

absl::StatusOr<std::vector<absl::string_view>>
GnmiGetElementFromTelemetryResponse(const gnmi::SubscribeResponse& response) {
if (response.update().update_size() <= 0)
return gutil::InternalErrorBuilder().LogError()
<< "Unexpected update size in response (should be > 0): "
<< response.update().update_size();
LOG(INFO) << "Update size in response: " << response.update().update_size();

std::vector<absl::string_view> elements;
for (const auto& u : response.update().update()) {
if (u.path().elem_size() <= 0)
return gutil::InternalErrorBuilder().LogError()
<< "Unexpected element size in response (should be > 0): "
<< u.path().elem_size();

for (const auto& e : u.path().elem()) {
elements.push_back(e.name());
}
}
return elements;
}
} // namespace pins_test
35 changes: 35 additions & 0 deletions lib/gnmi/gnmi_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "proto/gnmi/gnmi.grpc.pb.h"
#include "proto/gnmi/gnmi.pb.h"

namespace pins_test {

inline constexpr char kOpenconfigStr[] = "openconfig";
inline constexpr char kTarget[] = "target";

enum class GnmiSetType : char { kUpdate, kReplace, kDelete };

Expand All @@ -32,5 +34,38 @@ absl::StatusOr<gnmi::GetRequest> BuildGnmiGetRequest(
absl::StatusOr<std::string> ParseGnmiGetResponse(
const gnmi::GetResponse& response, absl::string_view match_tag);

absl::Status SetGnmiConfigPath(gnmi::gNMI::Stub* sut_gnmi_stub,
absl::string_view config_path,
GnmiSetType operation, absl::string_view value);

absl::StatusOr<std::string> GetGnmiStatePathInfo(
gnmi::gNMI::Stub* sut_gnmi_stub, absl::string_view state_path,
absl::string_view resp_parse_str);

template <class T>
std::string ConstructGnmiConfigSetString(std::string field, T value) {
std::string result_str;
if (std::is_integral<T>::value) {
// result: "{\"field\":value}"
result_str = absl::StrCat("{\"", field, "\":", value, "}");
} else if (std::is_same<T, std::string>::value) {
// result: "{\"field\":\"value\"};
result_str = absl::StrCat("{\"", field, "\":\"", value, "\"}");
}

return result_str;
}

// Adding subtree to gNMI Subscription list.
void AddSubtreeToGnmiSubscription(absl::string_view subtree_root,
gnmi::SubscriptionList& subscription_list,
gnmi::SubscriptionMode mode,
bool suppress_redundant,
absl::Duration interval);

// Returns vector of elements in subscriber response.
absl::StatusOr<std::vector<absl::string_view>>
GnmiGetElementFromTelemetryResponse(const gnmi::SubscribeResponse& response);

} // namespace pins_test
#endif // GOOGLE_LIB_GNMI_GNMI_HELPER_H_
29 changes: 29 additions & 0 deletions tests/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,32 @@ cc_library(
"@com_google_googletest//:gtest",
],
)

cc_library(
name = "thinkit_gnmi_subscribe_tests",
testonly = 1,
srcs = [
"thinkit_gnmi_subscribe_tests.cc",
],
hdrs = [
"thinkit_gnmi_subscribe_tests.h",
"thinkit_util.h",
],
deps = [
"//gutil:status_matchers",
"//lib/gnmi:gnmi_helper",
"//p4_pdpi:p4_runtime_session",
"//thinkit:ssh_client",
"//thinkit:switch",
"@com_github_gnmi//proto/gnmi:gnmi_cc_proto",
"@com_github_gnmi//proto/gnmi:gnmi_cc_grpc_proto",
"@com_github_google_glog//:glog",
"@com_github_nlohmann_json//:nlohmann_json",
"@com_github_p4lang_p4runtime//:p4runtime_cc_grpc",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/status:statusor",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
"@com_google_googletest//:gtest",
],
)
74 changes: 74 additions & 0 deletions tests/thinkit_gnmi_subscribe_tests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "tests/thinkit_gnmi_subscribe_tests.h"

#include "absl/container/flat_hash_set.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/time/time.h"
#include "glog/logging.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "gutil/status_matchers.h"
#include "lib/gnmi/gnmi_helper.h"
#include "p4/v1/p4runtime.grpc.pb.h"
#include "p4_pdpi/p4_runtime_session.h"
#include "proto/gnmi/gnmi.grpc.pb.h"
#include "proto/gnmi/gnmi.pb.h"
#include "include/nlohmann/json.hpp"
#include "tests/thinkit_util.h"
#include "thinkit/ssh_client.h"
#include "thinkit/switch.h"

namespace pins_test {

constexpr absl::Duration kSubscribeWaitTime = absl::Seconds(100);

// This test subscribes to interface and component subtree for updates.
void TestGnmiInterfaceAndComponentSubscribe(thinkit::Switch& sut) {
ASSERT_OK_AND_ASSIGN(auto sut_gnmi_stub, sut.CreateGnmiStub());
grpc::ClientContext context;
auto stream = sut_gnmi_stub->Subscribe(&context);
ASSERT_NE(stream, nullptr) << "cannot create a stream!.";
gnmi::SubscribeRequest subscribe_request;
gnmi::SubscriptionList* subscription_list =
subscribe_request.mutable_subscribe();
subscription_list->set_mode(gnmi::SubscriptionList::STREAM);
subscription_list->mutable_prefix()->set_origin(kOpenconfigStr);
subscription_list->mutable_prefix()->set_target(kTarget);
AddSubtreeToGnmiSubscription(kInterfaces, *subscription_list, gnmi::SAMPLE,
/*suppress_redundant=*/false, absl::Seconds(5));
AddSubtreeToGnmiSubscription(kComponents, *subscription_list, gnmi::SAMPLE,
/*suppress_redundant=*/false, absl::Seconds(5));
LOG(INFO) << "Sending subscription: " << subscribe_request.ShortDebugString();
ASSERT_TRUE(stream->Write(subscribe_request))
<< "Failed Write to forward request: " << subscribe_request.DebugString()
<< ". Possibly broken stream.";
LOG(INFO) << "Wrote subscribe request.";
gnmi::SubscribeResponse response;
bool intf_response_received = false;
bool component_response_received = false;
const auto start_time = absl::Now();
while (absl::Now() < (start_time + kSubscribeWaitTime) &&
stream->Read(&response)) {
LOG(INFO) << "Subscribe response received: " << response.DebugString();
ASSERT_OK_AND_ASSIGN(auto elem_name_vector,
GnmiGetElementFromTelemetryResponse(response));
for (auto elem_name : elem_name_vector) {
if (elem_name == kComponents) {
component_response_received = true;
}
if (elem_name == kInterfaces) {
intf_response_received = true;
}
}
if (component_response_received && intf_response_received) {
break;
}
}
EXPECT_TRUE(component_response_received);
EXPECT_TRUE(intf_response_received);
}

} // namespace pins_test
11 changes: 11 additions & 0 deletions tests/thinkit_gnmi_subscribe_tests.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#ifndef GOOGLE_TESTS_THINKIT_GNMI_SUBSCRIBE_TESTS_H_
#define GOOGLE_TESTS_THINKIT_GNMI_SUBSCRIBE_TESTS_H_

#include "thinkit/switch.h"

namespace pins_test {

void TestGnmiInterfaceAndComponentSubscribe(thinkit::Switch& sut);

}
#endif // GOOGLE_TESTS_THINKIT_GNMI_SUBSCRIBE_TESTS_H_
31 changes: 31 additions & 0 deletions tests/thinkit_util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2024 Google LLC
//
// 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
//
// https://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 GOOGLE_TESTS_THINKIT_UTIL_H_
#define GOOGLE_TESTS_THINKIT_UTIL_H_

namespace pins_test {

constexpr char kEnabledFalse[] = "{\"enabled\":false}";
constexpr char kEnabledTrue[] = "{\"enabled\":true}";
constexpr char kStateUp[] = "UP";
constexpr char kStateDown[] = "DOWN";
constexpr char kInterfaces[] = "interfaces";
constexpr char kComponents[] = "components";
constexpr char kPortSpeed[] = "openconfig-if-ethernet:port-speed";
constexpr char kPlatformJson[] = "platform.json";
constexpr char kGB[] = "GB";
} // namespace pins_test

#endif // GOOGLE_TESTS_THINKIT_UTIL_H_

0 comments on commit d7c240a

Please sign in to comment.