diff --git a/contrib/endpoints/src/api_manager/service_control/BUILD b/contrib/endpoints/src/api_manager/service_control/BUILD index 73b722b480..c0e35adca0 100644 --- a/contrib/endpoints/src/api_manager/service_control/BUILD +++ b/contrib/endpoints/src/api_manager/service_control/BUILD @@ -122,3 +122,16 @@ cc_test( "//external:googletest_main", ], ) + +cc_test( + name = "allocate_quota_response_test", + size = "small", + srcs = [ + "allocate_quota_response_test.cc", + ], + linkstatic = 1, + deps = [ + ":service_control", + "//external:googletest_main", + ], +) diff --git a/contrib/endpoints/src/api_manager/service_control/aggregated_test.cc b/contrib/endpoints/src/api_manager/service_control/aggregated_test.cc index 5e9ca38a55..cc88308b8b 100644 --- a/contrib/endpoints/src/api_manager/service_control/aggregated_test.cc +++ b/contrib/endpoints/src/api_manager/service_control/aggregated_test.cc @@ -19,10 +19,13 @@ #include "contrib/endpoints/src/api_manager/mock_api_manager_environment.h" #include "contrib/endpoints/src/api_manager/service_control/proto.h" #include "gmock/gmock.h" +#include "google/protobuf/text_format.h" #include "gtest/gtest.h" using ::google::api::servicecontrol::v1::CheckRequest; using ::google::api::servicecontrol::v1::CheckResponse; +using ::google::api::servicecontrol::v1::AllocateQuotaRequest; +using ::google::api::servicecontrol::v1::AllocateQuotaResponse; using ::google::api::servicecontrol::v1::ReportRequest; using ::google::api::servicecontrol::v1::ReportResponse; using ::google::api_manager::utils::Status; @@ -39,6 +42,39 @@ namespace api_manager { namespace service_control { namespace { + +const char kAllocateQuotaResponse[] = R"( +operation_id: "test_service" +quota_metrics { + metric_name: "serviceruntime.googleapis.com/api/consumer/quota_used_count" + metric_values { + labels { + key: "/quota_name" + value: "metric_first" + } + int64_value: 2 + } + metric_values { + labels { + key: "/quota_name" + value: "metric" + } + int64_value: 1 + } +}service_config_id: "2017-02-08r9" + +)"; + +const char kAllocateQuotaResponseErrorExhausted[] = R"( +operation_id: "test_service" +allocate_errors { + code: RESOURCE_EXHAUSTED + description: "Insufficient tokens for quota group and limit \'apiWriteQpsPerProject_LOW\' of service \'jaebonginternal.sandbox.google.com\', using the limit by ID \'container:1002409420961\'." +} +service_config_id: "2017-02-08r9" + +)"; + void FillOperationInfo(OperationInfo* op) { op->operation_id = "operation_id"; op->operation_name = "operation_name"; @@ -195,6 +231,91 @@ TEST_F(AggregatedTestWithRealClient, CheckOKTest) { EXPECT_EQ(stat.send_report_operations, 0); } +class QuotaAllocationTestWithRealClient : public ::testing::Test { + public: + void SetUp() { + service_.set_name("test_service"); + service_.mutable_control()->set_environment( + "servicecontrol.googleapis.com"); + env_.reset(new ::testing::NiceMock); + sc_lib_.reset(Aggregated::Create(service_, nullptr, env_.get(), nullptr)); + ASSERT_TRUE((bool)(sc_lib_)); + // This is the call actually creating the client. + sc_lib_->Init(); + + metric_cost_vector_ = {{"metric_first", 1}, {"metric_second", 2}}; + } + + std::string getResponseBody(const char* response) { + AllocateQuotaResponse quota_response; + ::google::protobuf::TextFormat::ParseFromString(response, "a_response); + return quota_response.SerializeAsString(); + } + + void DoRunHTTPRequest(HTTPRequest* request) { + std::map headers; + + AllocateQuotaRequest quota_request; + + ASSERT_TRUE(quota_request.ParseFromString(request->body())); + ASSERT_EQ(quota_request.allocate_operation().quota_metrics_size(), 2); + + std::set> expected_costs = { + {"metric_first", 1}, {"metric_second", 2}}; + std::set> actual_costs; + + for (auto rule : quota_request.allocate_operation().quota_metrics()) { + actual_costs.insert(std::make_pair(rule.metric_name(), + rule.metric_values(0).int64_value())); + } + + ASSERT_EQ(actual_costs, expected_costs); + + request->OnComplete(Status::OK, std::move(headers), + std::move(getResponseBody(kAllocateQuotaResponse))); + } + + void DoRunHTTPRequestAllocationFailed(HTTPRequest* request) { + std::map headers; + + request->OnComplete( + Status::OK, std::move(headers), + std::move(getResponseBody(kAllocateQuotaResponseErrorExhausted))); + } + + ::google::api::Service service_; + std::unique_ptr env_; + std::unique_ptr sc_lib_; + std::vector> metric_cost_vector_; +}; + +TEST_F(QuotaAllocationTestWithRealClient, AllocateQuotaTest) { + EXPECT_CALL(*env_, DoRunHTTPRequest(_)) + .WillOnce( + Invoke(this, &QuotaAllocationTestWithRealClient::DoRunHTTPRequest)); + + QuotaRequestInfo info; + info.metric_cost_vector = &metric_cost_vector_; + + FillOperationInfo(&info); + sc_lib_->Quota(info, nullptr, + [](Status status) { ASSERT_TRUE(status.ok()); }); +} + +TEST_F(QuotaAllocationTestWithRealClient, AllocateQuotaFailedTest) { + EXPECT_CALL(*env_, DoRunHTTPRequest(_)) + .WillOnce(Invoke(this, &QuotaAllocationTestWithRealClient:: + DoRunHTTPRequestAllocationFailed)); + + QuotaRequestInfo info; + info.metric_cost_vector = &metric_cost_vector_; + + FillOperationInfo(&info); + sc_lib_->Quota(info, nullptr, [](Status status) { + ASSERT_TRUE(status.code() == Code::RESOURCE_EXHAUSTED); + }); +} + TEST(AggregatedServiceControlTest, Create) { // Verify that invalid service config yields nullptr. ::google::api::Service diff --git a/contrib/endpoints/src/api_manager/service_control/allocate_quota_response_test.cc b/contrib/endpoints/src/api_manager/service_control/allocate_quota_response_test.cc new file mode 100644 index 0000000000..729a730252 --- /dev/null +++ b/contrib/endpoints/src/api_manager/service_control/allocate_quota_response_test.cc @@ -0,0 +1,184 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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 +// +// http://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. +// +//////////////////////////////////////////////////////////////////////////////// +// +#include "contrib/endpoints/include/api_manager/utils/status.h" +#include "contrib/endpoints/src/api_manager/service_control/proto.h" +#include "gtest/gtest.h" + +namespace gasv1 = ::google::api::servicecontrol::v1; + +using ::google::api::servicecontrol::v1::QuotaError; +using ::google::api_manager::utils::Status; +using ::google::protobuf::util::error::Code; + +namespace google { +namespace api_manager { +namespace service_control { + +namespace { + +Status ConvertAllocateQuotaErrorToStatus(gasv1::QuotaError::Code code, + const char* error_detail, + const char* service_name) { + gasv1::AllocateQuotaResponse response; + gasv1::QuotaError* quota_error = response.add_allocate_errors(); + QuotaRequestInfo info; + quota_error->set_code(code); + quota_error->set_description(error_detail); + return Proto::ConvertAllocateQuotaResponse(response, service_name); +} + +Status ConvertAllocateQuotaErrorToStatus(gasv1::QuotaError::Code code) { + gasv1::AllocateQuotaResponse response; + std::string service_name; + response.add_allocate_errors()->set_code(code); + return Proto::ConvertAllocateQuotaResponse(response, service_name); +} + +} // namespace + +TEST(AllocateQuotaResponseTest, + AbortedWithInvalidArgumentWhenRespIsKeyInvalid) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::API_KEY_INVALID); + EXPECT_EQ(Code::INVALID_ARGUMENT, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithInvalidArgumentWhenRespIsKeyExpired) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::API_KEY_EXPIRED); + EXPECT_EQ(Code::INVALID_ARGUMENT, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithInvalidArgumentWhenRespIsBlockedWithResourceExausted) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::RESOURCE_EXHAUSTED); + EXPECT_EQ(Code::RESOURCE_EXHAUSTED, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithInvalidArgumentWhenRespIsBlockedWithProjectSuspended) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::PROJECT_SUSPENDED); + EXPECT_EQ(Code::PERMISSION_DENIED, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithPermissionDeniedWhenRespIsBlockedWithServiceNotEnabled) { + Status result = ConvertAllocateQuotaErrorToStatus( + QuotaError::SERVICE_NOT_ENABLED, + "API api_xxxx is not enabled for the project.", "api_xxxx"); + EXPECT_EQ(Code::PERMISSION_DENIED, result.code()); + EXPECT_EQ(result.message(), "API api_xxxx is not enabled for the project."); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithPermissionDeniedWhenRespIsBlockedWithBillingNotActivated) { + Status result = ConvertAllocateQuotaErrorToStatus( + QuotaError::BILLING_NOT_ACTIVE, + "API api_xxxx has billing disabled. Please enable it..", "api_xxxx"); + EXPECT_EQ(Code::PERMISSION_DENIED, result.code()); + EXPECT_EQ(result.message(), + "API api_xxxx has billing disabled. Please enable it."); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithPermissionDeniedWhenRespIsBlockedWithIpAddressBlocked) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::IP_ADDRESS_BLOCKED); + EXPECT_EQ(Code::PERMISSION_DENIED, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithPermissionDeniedWhenRespIsBlockedWithRefererBlocked) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::REFERER_BLOCKED); + EXPECT_EQ(Code::PERMISSION_DENIED, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithPermissionDeniedWhenRespIsBlockedWithClientAppBlocked) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::CLIENT_APP_BLOCKED); + EXPECT_EQ(Code::PERMISSION_DENIED, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithPermissionDeniedWhenResponseIsBlockedWithProjectInvalid) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::PROJECT_INVALID); + EXPECT_EQ(Code::INVALID_ARGUMENT, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithPermissionDeniedWhenRespIsBlockedWithProjectDeleted) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::PROJECT_DELETED); + EXPECT_EQ(Code::INVALID_ARGUMENT, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithPermissionDeniedWhenRespIsBlockedWithApiKeyInvalid) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::API_KEY_INVALID); + EXPECT_EQ(Code::INVALID_ARGUMENT, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AbortedWithPermissionDeniedWhenRespIsBlockedWithApiKeyExpiread) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::API_KEY_EXPIRED); + EXPECT_EQ(Code::INVALID_ARGUMENT, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AcceptOKWhenRespIsBlockedWithProjectStatusUnavailable) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::PROJECT_STATUS_UNVAILABLE); + EXPECT_EQ(Code::OK, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AcceptOKWhenRespIsBlockedWithServiceStatusUnavailable) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::SERVICE_STATUS_UNAVAILABLE); + EXPECT_EQ(Code::OK, result.code()); +} + +TEST(AllocateQuotaResponseTest, + AcceptOKWhenRespIsBlockedWithBillingStatusUnavailable) { + Status result = + ConvertAllocateQuotaErrorToStatus(QuotaError::BILLING_STATUS_UNAVAILABLE); + EXPECT_EQ(Code::OK, result.code()); +} + +TEST(AllocateQuotaResponseTest, FailOpenWhenResponseIsUnknownBillingStatus) { + EXPECT_TRUE( + ConvertAllocateQuotaErrorToStatus(QuotaError::BILLING_STATUS_UNAVAILABLE) + .ok()); +} + +TEST(AllocateQuotaResponseTest, FailOpenWhenResponseIsUnknownServiceStatus) { + EXPECT_TRUE( + ConvertAllocateQuotaErrorToStatus(QuotaError::SERVICE_STATUS_UNAVAILABLE) + .ok()); +} + +} // namespace service_control +} // namespace api_manager +} // namespace google diff --git a/contrib/endpoints/src/api_manager/service_control/proto_test.cc b/contrib/endpoints/src/api_manager/service_control/proto_test.cc index 609cb54abe..89799a10bf 100644 --- a/contrib/endpoints/src/api_manager/service_control/proto_test.cc +++ b/contrib/endpoints/src/api_manager/service_control/proto_test.cc @@ -76,6 +76,12 @@ void FillCheckRequestInfo(CheckRequestInfo* request) { request->referer = "referer"; } +void FillAllocateQuotaRequestInfo(QuotaRequestInfo* request) { + request->client_ip = "1.2.3.4"; + request->referer = "referer"; + request->method_name = "operation_name"; +} + void FillReportRequestInfo(ReportRequestInfo* request) { request->referer = "referer"; request->response_code = 200; @@ -122,6 +128,12 @@ std::string CheckRequestToString(gasv1::CheckRequest* request) { return text; } +std::string AllocateQuotaRequestToString(gasv1::AllocateQuotaRequest* request) { + std::string text; + google::protobuf::TextFormat::PrintToString(*request, &text); + return text; +} + std::string ReportRequestToString(gasv1::ReportRequest* request) { gasv1::Operation* op = request->mutable_operations(0); SetFixTimeStamps(op); @@ -161,6 +173,44 @@ TEST_F(ProtoTest, FillGoodCheckRequestTest) { ASSERT_EQ(expected_text, text); } +TEST_F(ProtoTest, FillGoodAllocateQuotaRequestTest) { + std::vector> metric_cost_vector = { + {"metric_first", 1}, {"metric_second", 2}}; + + google::api_manager::service_control::QuotaRequestInfo info; + info.metric_cost_vector = &metric_cost_vector; + + FillOperationInfo(&info); + FillAllocateQuotaRequestInfo(&info); + + gasv1::AllocateQuotaRequest request; + ASSERT_TRUE(scp_.FillAllocateQuotaRequest(info, &request).ok()); + + std::string text = AllocateQuotaRequestToString(&request); + std::string expected_text = ReadTestBaseline("allocate_quota_request.golden"); + ASSERT_EQ(expected_text, text); +} + +TEST_F(ProtoTest, FillAllocateQuotaRequestNoMethodNameTest) { + std::vector> metric_cost_vector = { + {"metric_first", 1}, {"metric_second", 2}}; + + google::api_manager::service_control::QuotaRequestInfo info; + FillOperationInfo(&info); + info.metric_cost_vector = &metric_cost_vector; + info.client_ip = "1.2.3.4"; + info.referer = "referer"; + info.method_name = ""; + + gasv1::AllocateQuotaRequest request; + ASSERT_TRUE(scp_.FillAllocateQuotaRequest(info, &request).ok()); + + std::string text = AllocateQuotaRequestToString(&request); + std::string expected_text = + ReadTestBaseline("allocate_quota_request_no_method_name.golden"); + ASSERT_EQ(expected_text, text); +} + TEST_F(ProtoTest, FillNoApiKeyCheckRequestTest) { CheckRequestInfo info; info.operation_id = "operation_id"; diff --git a/contrib/endpoints/src/api_manager/service_control/testdata/allocate_quota_request.golden b/contrib/endpoints/src/api_manager/service_control/testdata/allocate_quota_request.golden new file mode 100644 index 0000000000..73a2623923 --- /dev/null +++ b/contrib/endpoints/src/api_manager/service_control/testdata/allocate_quota_request.golden @@ -0,0 +1,36 @@ +service_name: "test_service" +allocate_operation { + operation_id: "operation_id" + method_name: "operation_name" + consumer_id: "api_key:api_key_x" + labels { + key: "servicecontrol.googleapis.com/caller_ip" + value: "1.2.3.4" + } + labels { + key: "servicecontrol.googleapis.com/referer" + value: "referer" + } + labels { + key: "servicecontrol.googleapis.com/service_agent" + value: "ESP/{{service_agent_version}}" + } + labels { + key: "servicecontrol.googleapis.com/user_agent" + value: "ESP" + } + quota_metrics { + metric_name: "metric_first" + metric_values { + int64_value: 1 + } + } + quota_metrics { + metric_name: "metric_second" + metric_values { + int64_value: 2 + } + } + quota_mode: NORMAL +} +service_config_id: "2016-09-19r0" diff --git a/contrib/endpoints/src/api_manager/service_control/testdata/allocate_quota_request_no_method_name.golden b/contrib/endpoints/src/api_manager/service_control/testdata/allocate_quota_request_no_method_name.golden new file mode 100644 index 0000000000..34a59f4765 --- /dev/null +++ b/contrib/endpoints/src/api_manager/service_control/testdata/allocate_quota_request_no_method_name.golden @@ -0,0 +1,35 @@ +service_name: "test_service" +allocate_operation { + operation_id: "operation_id" + consumer_id: "api_key:api_key_x" + labels { + key: "servicecontrol.googleapis.com/caller_ip" + value: "1.2.3.4" + } + labels { + key: "servicecontrol.googleapis.com/referer" + value: "referer" + } + labels { + key: "servicecontrol.googleapis.com/service_agent" + value: "ESP/{{service_agent_version}}" + } + labels { + key: "servicecontrol.googleapis.com/user_agent" + value: "ESP" + } + quota_metrics { + metric_name: "metric_first" + metric_values { + int64_value: 1 + } + } + quota_metrics { + metric_name: "metric_second" + metric_values { + int64_value: 2 + } + } + quota_mode: NORMAL +} +service_config_id: "2016-09-19r0" diff --git a/contrib/endpoints/src/api_manager/service_control/url_test.cc b/contrib/endpoints/src/api_manager/service_control/url_test.cc index afbf48bd15..8e0bb2d188 100644 --- a/contrib/endpoints/src/api_manager/service_control/url_test.cc +++ b/contrib/endpoints/src/api_manager/service_control/url_test.cc @@ -57,6 +57,10 @@ TEST(UrlTest, PrependHttps) { ASSERT_EQ( "https://servicecontrol.googleapis.com/v1/services/https-config:report", url.report_url()); + ASSERT_EQ( + "https://servicecontrol.googleapis.com/v1/services/" + "https-config:allocateQuota", + url.quota_url()); } TEST(UrlTest, ServerControlOverride) {