Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added utility methods to parse ORCA response headers from backends. #35422

Merged
merged 10 commits into from
Aug 8, 2024
25 changes: 25 additions & 0 deletions source/common/orca/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_library",
"envoy_package",
)

licenses(["notice"]) # Apache 2

envoy_package()

envoy_cc_library(
name = "orca_parser",
srcs = ["orca_parser.cc"],
hdrs = ["orca_parser.h"],
external_deps = [
"abseil_strings",
"abseil_statusor",
"fmtlib",
],
deps = [
"//envoy/http:header_map_interface",
"//source/common/common:base64_lib",
"@com_github_cncf_xds//xds/data/orca/v3:pkg_cc_proto",
],
)
45 changes: 45 additions & 0 deletions source/common/orca/orca_parser.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#include "source/common/orca/orca_parser.h"

#include <string>

#include "envoy/http/header_map.h"

#include "source/common/common/base64.h"
#include "source/common/common/fmt.h"

#include "absl/strings/string_view.h"

using ::Envoy::Http::HeaderMap;
using xds::data::orca::v3::OrcaLoadReport;

namespace Envoy {
namespace Orca {

namespace {

const Http::LowerCaseString& endpointLoadMetricsHeaderBin() {
CONSTRUCT_ON_FIRST_USE(Http::LowerCaseString, kEndpointLoadMetricsHeaderBin);
}

} // namespace

absl::StatusOr<OrcaLoadReport> parseOrcaLoadReportHeaders(const HeaderMap& headers) {
OrcaLoadReport load_report;

// Binary protobuf format.
if (const auto header_bin = headers.get(endpointLoadMetricsHeaderBin()); !header_bin.empty()) {
const auto header_value = header_bin[0]->value().getStringView();
const std::string decoded_value = Envoy::Base64::decode(header_value);
if (!load_report.ParseFromString(decoded_value)) {
return absl::InvalidArgumentError(
fmt::format("unable to parse binaryheader to OrcaLoadReport: {}", header_value));
}
} else {
return absl::NotFoundError("no ORCA data sent from the backend");
}

return load_report;
}

} // namespace Orca
} // namespace Envoy
19 changes: 19 additions & 0 deletions source/common/orca/orca_parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include "envoy/http/header_map.h"

#include "absl/status/statusor.h"
#include "xds/data/orca/v3/orca_load_report.pb.h"

namespace Envoy {
namespace Orca {

// Header used to send ORCA load metrics from the backend.
static constexpr absl::string_view kEndpointLoadMetricsHeaderBin = "endpoint-load-metrics-bin";

// Parses ORCA load metrics from a header map into an OrcaLoadReport proto.
// Supports serialized binary formats.
absl::StatusOr<xds::data::orca::v3::OrcaLoadReport>
parseOrcaLoadReportHeaders(const Envoy::Http::HeaderMap& headers);
} // namespace Orca
} // namespace Envoy
26 changes: 26 additions & 0 deletions test/common/orca/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_test",
"envoy_package",
)

licenses(["notice"]) # Apache 2

envoy_package()

envoy_cc_test(
name = "orca_parser_test",
srcs = ["orca_parser_test.cc"],
external_deps = [
"abseil_status",
"abseil_strings",
"fmtlib",
],
deps = [
"//source/common/common:base64_lib",
"//source/common/orca:orca_parser",
"//test/test_common:status_utility_lib",
"//test/test_common:utility_lib",
"@com_github_cncf_xds//xds/data/orca/v3:pkg_cc_proto",
],
)
72 changes: 72 additions & 0 deletions test/common/orca/orca_parser_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include <string>

#include "source/common/common/base64.h"
#include "source/common/orca/orca_parser.h"

#include "test/test_common/status_utility.h"
#include "test/test_common/utility.h"

#include "absl/status/status.h"
#include "xds/data/orca/v3/orca_load_report.pb.h"

namespace Envoy {
namespace Orca {
namespace {

// Returns an example OrcaLoadReport proto with all fields populated.
static xds::data::orca::v3::OrcaLoadReport exampleOrcaLoadReport() {
xds::data::orca::v3::OrcaLoadReport orca_load_report;
orca_load_report.set_cpu_utilization(0.7);
orca_load_report.set_application_utilization(0.8);
orca_load_report.set_mem_utilization(0.9);
orca_load_report.set_eps(2);
orca_load_report.set_rps_fractional(1000);
orca_load_report.mutable_named_metrics()->insert({"foo", 123});
orca_load_report.mutable_named_metrics()->insert({"bar", 0.2});
return orca_load_report;
}

TEST(OrcaParserUtilTest, NoHeaders) {
Http::TestRequestHeaderMapImpl headers{};
// parseOrcaLoadReport returns error when no ORCA data is sent from
// the backend.
EXPECT_THAT(parseOrcaLoadReportHeaders(headers),
StatusHelpers::HasStatus(absl::NotFoundError("no ORCA data sent from the backend")));
}

TEST(OrcaParserUtilTest, MissingOrcaHeaders) {
Http::TestRequestHeaderMapImpl headers{{"wrong-header", "wrong-value"}};
// parseOrcaLoadReport returns error when no ORCA data is sent from
// the backend.
EXPECT_THAT(parseOrcaLoadReportHeaders(headers),
StatusHelpers::HasStatus(absl::NotFoundError("no ORCA data sent from the backend")));
}

TEST(OrcaParserUtilTest, BinaryHeader) {
const std::string proto_string =
TestUtility::getProtobufBinaryStringFromMessage(exampleOrcaLoadReport());
const auto orca_load_report_header_bin =
Envoy::Base64::encode(proto_string.c_str(), proto_string.length());
Http::TestRequestHeaderMapImpl headers{
{std::string(kEndpointLoadMetricsHeaderBin), orca_load_report_header_bin}};
EXPECT_THAT(parseOrcaLoadReportHeaders(headers),
StatusHelpers::IsOkAndHolds(ProtoEq(exampleOrcaLoadReport())));
}

TEST(OrcaParserUtilTest, InvalidBinaryHeader) {
const std::string proto_string =
TestUtility::getProtobufBinaryStringFromMessage(exampleOrcaLoadReport());
// Force a bad base64 encoding by shortening the length of the output.
const auto orca_load_report_header_bin =
Envoy::Base64::encode(proto_string.c_str(), proto_string.length() / 2);
Http::TestRequestHeaderMapImpl headers{
{std::string(kEndpointLoadMetricsHeaderBin), orca_load_report_header_bin}};
EXPECT_THAT(parseOrcaLoadReportHeaders(headers),
StatusHelpers::HasStatus(
absl::StatusCode::kInvalidArgument,
testing::HasSubstr("unable to parse binaryheader to OrcaLoadReport")));
}

} // namespace
} // namespace Orca
} // namespace Envoy