diff --git a/lib/gnmi/BUILD.bazel b/lib/gnmi/BUILD.bazel index d2b59aa04..98bc0f341 100644 --- a/lib/gnmi/BUILD.bazel +++ b/lib/gnmi/BUILD.bazel @@ -35,6 +35,7 @@ cc_library( "@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_map", "@com_google_absl//absl/numeric:int128", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", diff --git a/lib/gnmi/gnmi_helper.cc b/lib/gnmi/gnmi_helper.cc index 2da4add0e..a289f8068 100644 --- a/lib/gnmi/gnmi_helper.cc +++ b/lib/gnmi/gnmi_helper.cc @@ -223,8 +223,9 @@ absl::StatusOr GetAllInterfaceOverGnmi( return resp; } -absl::Status CheckAllInterfaceUpOverGnmi(gnmi::gNMI::Stub& stub, - absl::Duration timeout) { +absl::StatusOr> +GetInterfaceToOperStatusMapOverGnmi(gnmi::gNMI::StubInterface& stub, + absl::Duration timeout) { ASSIGN_OR_RETURN(auto req, BuildGnmiGetRequest("interfaces", gnmi::GetRequest::STATE)); gnmi::GetResponse resp; @@ -250,7 +251,7 @@ absl::Status CheckAllInterfaceUpOverGnmi(gnmi::gNMI::Stub& stub, absl::StrCat("'interface' not found: ", oc_intf_json->dump())); } - std::vector unavailable_interfaces; + absl::flat_hash_map interface_to_oper_status_map; for (auto const& element : oc_intf_list_json->items()) { const auto element_name_json = element.value().find("name"); if (element_name_json == element.value().end()) { @@ -275,8 +276,23 @@ absl::Status CheckAllInterfaceUpOverGnmi(gnmi::gNMI::Stub& stub, return absl::NotFoundError( absl::StrCat("'oper-status' not found: ", name)); } - if (!absl::StrContains(element_status_json->dump(), "UP")) { - unavailable_interfaces.push_back(name); + interface_to_oper_status_map[name] = + std::string(StripQuotes(element_status_json->dump())); + } + + return interface_to_oper_status_map; +} + +absl::Status CheckAllInterfaceOperStateOverGnmi( + gnmi::gNMI::StubInterface& stub, absl::string_view interface_oper_state, + absl::Duration timeout) { + ASSIGN_OR_RETURN(const auto interface_to_oper_status_map, + GetInterfaceToOperStatusMapOverGnmi(stub, timeout)); + + std::vector unavailable_interfaces; + for (const auto& [interface, oper_status] : interface_to_oper_status_map) { + if (oper_status != interface_oper_state) { + unavailable_interfaces.push_back(interface); } } if (!unavailable_interfaces.empty()) { @@ -343,19 +359,38 @@ GnmiGetElementFromTelemetryResponse(const gnmi::SubscribeResponse& response) { return elements; } +absl::StatusOr> GetUpInterfacesOverGnmi( + gnmi::gNMI::StubInterface& stub, absl::Duration timeout) { + ASSIGN_OR_RETURN(const auto interface_to_oper_status_map, + GetInterfaceToOperStatusMapOverGnmi(stub, timeout)); + + std::vector up_interfaces; + for (const auto& [interface, oper_status] : interface_to_oper_status_map) { + // Ignore the interfaces that is not EthernetXX. For example: bond0, + // Loopback0, etc. + if (!absl::StartsWith(interface, "Ethernet")) { + LOG(INFO) << "Ignoring interface: " << interface; + continue; + } + if (oper_status == "UP") { + up_interfaces.push_back(interface); + } + } + + return up_interfaces; +} + absl::StatusOr GetInterfaceOperStatusOverGnmi( gnmi::gNMI::Stub& stub, absl::string_view if_name) { std::string if_req = absl::StrCat("interfaces/interface[name=", if_name, "]/state/oper-status"); ASSIGN_OR_RETURN(auto request, BuildGnmiGetRequest(if_req, gnmi::GetRequest::STATE)); - LOG(INFO) << "Sending GET request: " << request.ShortDebugString(); gnmi::GetResponse response; grpc::ClientContext context; grpc::Status status = stub.Get(&context, request, &response); if (!status.ok()) return gutil::GrpcStatusToAbslStatus(status); - LOG(INFO) << "Received GET response: " << response.ShortDebugString(); if (response.notification_size() != 1 || response.notification(0).update_size() != 1) { @@ -365,7 +400,6 @@ absl::StatusOr GetInterfaceOperStatusOverGnmi( ASSIGN_OR_RETURN( std::string oper_status, ParseGnmiGetResponse(response, "openconfig-interfaces:oper-status")); - LOG(INFO) << "Got the operational status: " << oper_status << "."; if (absl::StrContains((oper_status), "UP")) { return OperStatus::kUp; diff --git a/lib/gnmi/gnmi_helper.h b/lib/gnmi/gnmi_helper.h index 8e051f89e..3ed1d0a8b 100644 --- a/lib/gnmi/gnmi_helper.h +++ b/lib/gnmi/gnmi_helper.h @@ -19,6 +19,7 @@ #include #include +#include "absl/container/flat_hash_map.h" #include "absl/numeric/int128.h" #include "absl/status/status.h" #include "absl/status/statusor.h" @@ -109,13 +110,24 @@ absl::Status CanGetAllInterfaceOverGnmi( absl::StatusOr GetAllInterfaceOverGnmi( gnmi::gNMI::Stub& stub, absl::Duration timeout = absl::Seconds(60)); -// Checks if all interfaces are up. -absl::Status CheckAllInterfaceUpOverGnmi( - gnmi::gNMI::Stub& stub, absl::Duration timeout = absl::Seconds(60)); +// Gets the interface to oper status map. +absl::StatusOr> +GetInterfaceToOperStatusMapOverGnmi(gnmi::gNMI::StubInterface& stub, + absl::Duration timeout); + +// Checks if all interfaces oper-status is up/down. +absl::Status CheckAllInterfaceOperStateOverGnmi( + gnmi::gNMI::StubInterface& stub, absl::string_view interface_oper_state, + absl::Duration timeout = absl::Seconds(60)); // Returns gNMI Path for OC strings. gnmi::Path ConvertOCStringToPath(absl::string_view oc_path); +// Gets all the EthernetXX interfaces whose operational status is UP. +absl::StatusOr> GetUpInterfacesOverGnmi( + gnmi::gNMI::StubInterface& stub, + absl::Duration timeout = absl::Seconds(60)); + // Gets the operational status of an interface. absl::StatusOr GetInterfaceOperStatusOverGnmi( gnmi::gNMI::Stub& stub, absl::string_view if_name); diff --git a/lib/gnmi/gnmi_helper_test.cc b/lib/gnmi/gnmi_helper_test.cc index 9c9ee6571..41e9bd15b 100644 --- a/lib/gnmi/gnmi_helper_test.cc +++ b/lib/gnmi/gnmi_helper_test.cc @@ -359,5 +359,252 @@ TEST(StripQuotes, VariousInputs) { EXPECT_EQ(StripQuotes(R"("test"")"), R"(test")"); } +TEST(GetInterfaceOperStatusMap, GnmiGetRpcFails) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce( + Return(grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, ""))); + EXPECT_THAT(GetInterfaceToOperStatusMapOverGnmi(stub, absl::Seconds(60)), + StatusIs(absl::StatusCode::kDeadlineExceeded)); +} + +TEST(GetInterfaceOperStatusMap, InvalidGnmiGetResponse) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(Return(grpc::Status::OK)); + EXPECT_THAT(GetInterfaceToOperStatusMapOverGnmi(stub, absl::Seconds(60)), + StatusIs(absl::StatusCode::kInternal, + testing::HasSubstr("Invalid response"))); +} + +TEST(GetInterfaceOperStatusMap, GnmiGetResponseWithoutOpenconfigInterface) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { json_ietf_val: "{\"openconfig-system:alarms\":{}}" } + } + })pb")), + Return(grpc::Status::OK))); + + EXPECT_THAT(GetInterfaceToOperStatusMapOverGnmi(stub, absl::Seconds(60)), + StatusIs(absl::StatusCode::kNotFound, + testing::HasSubstr( + "'openconfig-interfaces:interfaces' not found"))); +} + +TEST(GetInterfaceOperStatusMap, InterfaceNotFoundInGnmiGetResponse) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { + json_ietf_val: "{\"openconfig-interfaces:interfaces\":{}}" + } + } + })pb")), + Return(grpc::Status::OK))); + + EXPECT_THAT(GetInterfaceToOperStatusMapOverGnmi(stub, absl::Seconds(60)), + StatusIs(absl::StatusCode::kNotFound, + testing::HasSubstr("'interface' not found"))); +} + +TEST(GetInterfaceOperStatusMap, InterfaceNameNotFound) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { + json_ietf_val: "{\"openconfig-interfaces:interfaces\":{\"interface\":[{}]}}" + } + } + })pb")), + Return(grpc::Status::OK))); + + EXPECT_THAT(GetInterfaceToOperStatusMapOverGnmi(stub, absl::Seconds(60)), + StatusIs(absl::StatusCode::kNotFound, + testing::HasSubstr("'name' not found"))); +} + +TEST(GetInterfaceOperStatusMap, InterfaceStateNotFound) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { + json_ietf_val: "{\"openconfig-interfaces:interfaces\":{\"interface\":[{\"name\":\"Ethernet0\"}]}}" + } + } + })pb")), + Return(grpc::Status::OK))); + + EXPECT_THAT(GetInterfaceToOperStatusMapOverGnmi(stub, absl::Seconds(60)), + StatusIs(absl::StatusCode::kNotFound, + testing::HasSubstr("'state' not found"))); +} + +TEST(GetInterfaceOperStatusMap, OperStatusNotFoundInState) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { + json_ietf_val: "{\"openconfig-interfaces:interfaces\":{\"interface\":[{\"name\":\"Ethernet0\",\"state\":{\"name\":\"Ethernet0\"}}]}}" + } + } + })pb")), + Return(grpc::Status::OK))); + + EXPECT_THAT(GetInterfaceToOperStatusMapOverGnmi(stub, absl::Seconds(60)), + StatusIs(absl::StatusCode::kNotFound, + testing::HasSubstr("'oper-status' not found"))); +} + +TEST(GetInterfaceOperStatusMap, SuccessfullyReturnsInterfaceOperStatusMap) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { + json_ietf_val: "{\"openconfig-interfaces:interfaces\":{\"interface\":[{\"name\":\"Cpu0\"},{\"name\":\"Ethernet0\",\"state\":{\"oper-status\":\"DOWN\"}}]}}" + } + } + })pb")), + Return(grpc::Status::OK))); + + auto statusor = GetInterfaceToOperStatusMapOverGnmi(stub, absl::Seconds(60)); + ASSERT_OK(statusor); + const absl::flat_hash_map expected_map = { + {"Ethernet0", "DOWN"}}; + EXPECT_THAT(*statusor, + ::testing::UnorderedPointwise(::testing::Eq(), expected_map)); +} + +TEST(CheckAllInterfaceOperState, FailsToGetInterfaceOperStatusMap) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce( + Return(grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, ""))); + EXPECT_THAT( + CheckAllInterfaceOperStateOverGnmi(stub, /*interface_oper_state=*/"UP"), + StatusIs(absl::StatusCode::kDeadlineExceeded)); +} + +TEST(CheckAllInterfaceOperState, InterfaceNotUp) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { + json_ietf_val: "{\"openconfig-interfaces:interfaces\":{\"interface\":[{\"name\":\"Ethernet0\",\"state\":{\"oper-status\":\"DOWN\"}}]}}" + } + } + })pb")), + Return(grpc::Status::OK))); + + EXPECT_THAT( + CheckAllInterfaceOperStateOverGnmi(stub, /*interface_oper_state=*/"UP"), + StatusIs(absl::StatusCode::kUnavailable, + testing::HasSubstr("Interfaces are not ready"))); +} + +TEST(CheckAllInterfaceOperState, InterfaceNotDown) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { + json_ietf_val: "{\"openconfig-interfaces:interfaces\":{\"interface\":[{\"name\":\"Ethernet0\",\"state\":{\"oper-status\":\"TESTING\"}}]}}" + } + } + })pb")), + Return(grpc::Status::OK))); + + EXPECT_THAT( + CheckAllInterfaceOperStateOverGnmi(stub, /*interface_oper_state=*/"DOWN"), + StatusIs(absl::StatusCode::kUnavailable, + testing::HasSubstr("Interfaces are not ready"))); +} + +TEST(CheckAllInterfaceOperState, AllInterfacesUp) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { + json_ietf_val: "{\"openconfig-interfaces:interfaces\":{\"interface\":[{\"name\":\"Ethernet1\",\"state\":{\"oper-status\":\"UP\"}}]}}" + } + } + })pb")), + Return(grpc::Status::OK))); + + ASSERT_OK( + CheckAllInterfaceOperStateOverGnmi(stub, /*interface_oper_state=*/"UP")); +} + +TEST(GetUpInterfaces, FailsToGetInterfaceOperStatusMap) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce( + Return(grpc::Status(grpc::StatusCode::DEADLINE_EXCEEDED, ""))); + EXPECT_THAT(GetUpInterfacesOverGnmi(stub), + StatusIs(absl::StatusCode::kDeadlineExceeded)); +} + +TEST(GetUpInterfaces, SuccessfullyGetsUpInterface) { + gnmi::MockgNMIStub stub; + EXPECT_CALL(stub, Get).WillOnce(DoAll( + SetArgPointee<2>(gutil::ParseProtoOrDie( + R"pb(notification { + timestamp: 1620348032128305716 + prefix { origin: "openconfig" } + update { + path { elem { name: "interfaces" } } + val { + json_ietf_val: "{\"openconfig-interfaces:interfaces\":{\"interface\":[{\"name\":\"bond0\",\"state\":{\"oper-status\":\"UP\"}},{\"name\":\"Ethernet0\",\"state\":{\"oper-status\":\"UP\"}}]}}" + } + } + })pb")), + Return(grpc::Status::OK))); + + auto statusor = GetUpInterfacesOverGnmi(stub); + ASSERT_OK(statusor); + EXPECT_THAT(*statusor, + testing::ContainerEq(std::vector{"Ethernet0"})); +} + } // namespace } // namespace pins_test diff --git a/lib/validator/validator_lib.cc b/lib/validator/validator_lib.cc index 30223471a..76b69e682 100644 --- a/lib/validator/validator_lib.cc +++ b/lib/validator/validator_lib.cc @@ -115,7 +115,8 @@ absl::Status GnoiAble(thinkit::Switch& thinkit_switch, absl::Duration timeout) { absl::Status PortsUp(thinkit::Switch& thinkit_switch, absl::Duration timeout) { ASSIGN_OR_RETURN(std::unique_ptr gnmi_stub, thinkit_switch.CreateGnmiStub()); - return pins_test::CheckAllInterfaceUpOverGnmi(*gnmi_stub, timeout); + return pins_test::CheckAllInterfaceOperStateOverGnmi( + *gnmi_stub, /*interface_oper_state=*/"UP", timeout); } absl::Status NoAlarms(thinkit::Switch& thinkit_switch, absl::Duration timeout) {