Skip to content

Commit

Permalink
test: validate explicit routing (#9385)
Browse files Browse the repository at this point in the history
  • Loading branch information
dbolduc authored Jun 30, 2022
1 parent 55f0da0 commit ec741cb
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 12 deletions.
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
7 changes: 5 additions & 2 deletions google/cloud/testing_util/google_cloud_cpp_testing_grpc.cmake
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

0 comments on commit ec741cb

Please sign in to comment.