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

test: validate explicit routing #9385

Merged
merged 1 commit into from
Jun 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions google/cloud/bigtable/internal/legacy_row_reader_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,15 @@ TEST_F(LegacyRowReaderTest, ReadOneRowAppProfileId) {
auto parser = absl::make_unique<ReadRowsParserMock>();
parser->SetRows({"r1"});
EXPECT_CALL(*parser, HandleEndOfStreamHook).Times(1);
std::string expected_id = "test-id";
EXPECT_CALL(*client_, ReadRows)
.WillOnce([expected_id](grpc::ClientContext* context,
ReadRowsRequest const& req) {
.WillOnce([](grpc::ClientContext* context, ReadRowsRequest const& req) {
EXPECT_EQ("test-app-profile-id", req.app_profile_id());
ValidateMetadataFixture fixture;
fixture.IsContextMDValid(*context,
"google.bigtable.v2.Bigtable.ReadRows", req,
google::cloud::internal::ApiClientHeader());
EXPECT_EQ(expected_id, req.app_profile_id());
// TODO(#9123): Use `req` when explicit routing is implemented for the
// legacy Bigtable Data API.
fixture.IsContextMDValid(
*context, "google.bigtable.v2.Bigtable.ReadRows", ReadRowsRequest{},
google::cloud::internal::ApiClientHeader());
auto stream = absl::make_unique<MockReadRowsReader>(
"google.bigtable.v2.Bigtable.ReadRows");
::testing::InSequence s;
Expand All @@ -208,7 +208,7 @@ TEST_F(LegacyRowReaderTest, ReadOneRowAppProfileId) {

parser_factory_->AddParser(std::move(parser));
auto impl = std::make_shared<LegacyRowReader>(
client_, "test-id", "", bigtable::RowSet(),
client_, "test-app-profile-id", "table-name", bigtable::RowSet(),
bigtable::RowReader::NO_ROWS_LIMIT, bigtable::Filter::PassAllFilter(),
std::move(retry_policy_), std::move(backoff_policy_),
metadata_update_policy_, std::move(parser_factory_));
Expand Down
1 change: 1 addition & 0 deletions google/cloud/testing_util/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ cc_library(
"@com_github_grpc_grpc//:grpc++",
"@com_google_googleapis//:googleapis_system_includes",
"@com_google_googleapis//google/api:annotations_cc_proto",
"@com_google_googleapis//google/api:routing_cc_proto",
"@com_google_googletest//:gtest_main",
"@com_google_protobuf//:protobuf",
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,11 @@ add_library(
validate_metadata.h)
target_link_libraries(
google_cloud_cpp_testing_grpc
PUBLIC google-cloud-cpp::grpc_utils google-cloud-cpp::common
google-cloud-cpp::api_annotations_protos protobuf::libprotobuf
PUBLIC google-cloud-cpp::grpc_utils
google-cloud-cpp::common
google-cloud-cpp::api_annotations_protos
google-cloud-cpp::api_routing_protos
protobuf::libprotobuf
GTest::gmock)
google_cloud_cpp_add_common_options(google_cloud_cpp_testing_grpc)

Expand Down
58 changes: 56 additions & 2 deletions google/cloud/testing_util/validate_metadata.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
// limitations under the License.

#include "google/cloud/testing_util/validate_metadata.h"
#include "google/cloud/internal/absl_str_cat_quiet.h"
#include "google/cloud/internal/absl_str_replace_quiet.h"
#include "google/cloud/log.h"
#include "google/cloud/status_or.h"
#include <google/api/annotations.pb.h>
#include <google/api/routing.pb.h>
#include <google/protobuf/descriptor.h>
#include <gmock/gmock.h>
#include <grpcpp/completion_queue.h>
Expand Down Expand Up @@ -80,6 +83,53 @@ MATCHER_P(MatchesGlob, glob, "matches the glob: \"" + glob + "\"") {
return std::regex_match(arg, regex);
}

/**
* Parse the `RoutingRule` proto as described in the proto comments:
* https://github.com/googleapis/googleapis/blob/master/google/api/routing.proto
*
* We loop over the repeated `routing_parameters` field. For each one we attempt
* to match and extract a routing key-value pair.
*
* We may end up matching the same key multiple times. If this happens, we
* overwrite the current value in the map, because the "last match wins".
*/
RoutingHeaders FromRoutingRule(google::api::RoutingRule const& routing,
google::protobuf::Descriptor const* input_type,
google::protobuf::Message const& request) {
RoutingHeaders headers;
for (auto const& rp : routing.routing_parameters()) {
auto const& path_template = rp.path_template();
auto const* fd = input_type->FindFieldByName(rp.field());
auto const& field = request.GetReflection()->GetString(request, fd);
// We skip empty fields.
if (field.empty()) continue;
// If the path_template is empty, we use the field's name as the routing
// param key, and we match the entire value of the field.
if (path_template.empty()) {
headers[rp.field()] = field;
continue;
}
// First we parse the path_template field to extract the routing param key
static std::regex const kPatternRegex(R"((.*)\{(.*)=(.*)\}(.*))");
std::smatch match;
if (!std::regex_match(path_template, match, kPatternRegex)) {
GCP_LOG(FATAL) << __FILE__ << ":" << __LINE__ << ": "
<< "RoutingParameters path template is malformed: "
<< path_template;
}
auto pattern =
absl::StrCat(match[1].str(), "(", match[3].str(), ")", match[4].str());
pattern = absl::StrReplaceAll(pattern, {{"**", ".*"}, {"*", "[^/]+"}});
auto param = match[2].str();
// Then we parse the field in the given request to see if it matches the
// pattern we expect.
if (std::regex_match(field, match, std::regex{pattern})) {
headers[std::move(param)] = match[1].str();
}
}
return headers;
}

/**
* Given a `method`, extract its `google.api.http` option and parse it.
*
Expand Down Expand Up @@ -124,15 +174,19 @@ RoutingHeaders FromHttpRule(google::api::HttpRule const& http,
}

RoutingHeaders ExtractRoutingHeaders(
std::string const& method, google::protobuf::Message const&,
std::string const& method, google::protobuf::Message const& request,
absl::optional<std::string> const& resource_name) {
auto const* method_desc =
google::protobuf::DescriptorPool::generated_pool()->FindMethodByName(
method);
EXPECT_THAT(method_desc, NotNull()) << "Method " + method + " is unknown.";
if (!method_desc) return {};
auto options = method_desc->options();
// TODO(#9373): Handle `google::api::routing` extension.
if (options.HasExtension(google::api::routing)) {
auto const& routing = options.GetExtension(google::api::routing);
auto const* input_type = method_desc->input_type();
return FromRoutingRule(routing, input_type, request);
}
if (options.HasExtension(google::api::http)) {
auto const& http = options.GetExtension(google::api::http);
return FromHttpRule(http, resource_name);
Expand Down