Skip to content

Commit

Permalink
Make quota call (#192)
Browse files Browse the repository at this point in the history
* hookup quota call

* Make quota call.

* Update indent.
  • Loading branch information
qiwzhang authored Mar 22, 2017
1 parent 57419f1 commit d2bc18a
Show file tree
Hide file tree
Showing 8 changed files with 87 additions and 18 deletions.
4 changes: 3 additions & 1 deletion src/envoy/mixer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ This filter will intercept all HTTP requests and call Mixer. Here is its config:
"mixer_server": "${MIXER_SERVER}",
"mixer_attributes" : {
"attribute_name1": "attribute_value1",
"attribute_name2": "attribute_value2"
"attribute_name2": "attribute_value2",
"quota.name": "RequestCount"
},
"forward_attributes" : {
"attribute_name1": "attribute_value1",
Expand All @@ -79,6 +80,7 @@ Notes:
* mixer_server is required
* mixer_attributes: these attributes will be send to the mixer
* forward_attributes: these attributes will be forwarded to the upstream istio/proxy.
* "quota.name" and "quota.amount" are used for quota call. "quota.amount" is default to 1 if missing.

By default, mixer filter forwards attributes and does not invoke mixer server. You can customize this behavior per HTTP route by supplying an opaque config:

Expand Down
3 changes: 2 additions & 1 deletion src/envoy/mixer/envoy.conf.template
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@
"mixer_server": "${MIXER_SERVER}",
"mixer_attributes": {
"target.uid": "POD222",
"target.namespace": "XYZ222"
"target.namespace": "XYZ222",
"quota.name": "RequestCount"
}
}
},
Expand Down
30 changes: 29 additions & 1 deletion src/envoy/mixer/http_control.cc
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,23 @@ HttpControl::HttpControl(const std::string& mixer_server,
::istio::mixer_client::MixerClientOptions options;
options.mixer_server = mixer_server;
mixer_client_ = ::istio::mixer_client::CreateMixerClient(options);

// Extract quota attributes
auto it = config_attributes_.find(::istio::mixer_client::kQuotaName);
if (it != config_attributes_.end()) {
quota_attributes_.attributes[ ::istio::mixer_client::kQuotaName] =
Attributes::StringValue(it->second);
config_attributes_.erase(it);

int64_t amount = 1; // default amount to 1.
it = config_attributes_.find(::istio::mixer_client::kQuotaAmount);
if (it != config_attributes_.end()) {
amount = std::stoi(it->second);
config_attributes_.erase(it);
}
quota_attributes_.attributes[ ::istio::mixer_client::kQuotaAmount] =
Attributes::Int64Value(amount);
}
}

void HttpControl::FillCheckAttributes(HeaderMap& header_map, Attributes* attr) {
Expand Down Expand Up @@ -141,7 +158,18 @@ void HttpControl::Check(HttpRequestDataPtr request_data, HeaderMap& headers,
FillCheckAttributes(headers, &request_data->attributes);
SetStringAttribute(kOriginUser, origin_user, &request_data->attributes);
log().debug("Send Check: {}", request_data->attributes.DebugString());
mixer_client_->Check(request_data->attributes, on_done);

auto check_on_done = [this, on_done](const Status& status) {
if (status.ok()) {
if (!quota_attributes_.attributes.empty()) {
log().debug("Send Quota: {}", quota_attributes_.DebugString());
mixer_client_->Quota(quota_attributes_, on_done);
return; // Not to call on_done again.
}
}
on_done(status);
};
mixer_client_->Check(request_data->attributes, check_on_done);
}

void HttpControl::Report(HttpRequestDataPtr request_data,
Expand Down
2 changes: 2 additions & 0 deletions src/envoy/mixer/http_control.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ class HttpControl final : public Logger::Loggable<Logger::Id::http> {
std::unique_ptr<::istio::mixer_client::MixerClient> mixer_client_;
// The attributes read from the config file.
std::map<std::string, std::string> config_attributes_;
// Quota attributes; extracted from envoy filter config.
::istio::mixer_client::Attributes quota_attributes_;
};

} // namespace Mixer
Expand Down
4 changes: 3 additions & 1 deletion src/envoy/mixer/integration_test/envoy.conf
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
"mixer_server": "localhost:29091",
"mixer_attributes": {
"target.uid": "POD222",
"target.namespace": "XYZ222"
"target.namespace": "XYZ222",
"quota.name": "RequestCount",
"quota.amount": "5"
}
}
},
Expand Down
8 changes: 5 additions & 3 deletions src/envoy/mixer/integration_test/mixer_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,10 @@ type MixerServer struct {
gp *pool.GoroutinePool
s mixerpb.MixerServer

check *Handler
report *Handler
quota *Handler
check *Handler
report *Handler
quota *Handler
quota_request *mixerpb.QuotaRequest
}

func (ts *MixerServer) Check(ctx context.Context, bag *attribute.MutableBag,
Expand All @@ -76,6 +77,7 @@ func (ts *MixerServer) Report(ctx context.Context, bag *attribute.MutableBag,
func (ts *MixerServer) Quota(ctx context.Context, bag *attribute.MutableBag,
request *mixerpb.QuotaRequest, response *mixerpb.QuotaResponse) {
response.RequestIndex = request.RequestIndex
ts.quota_request = request
response.Result = ts.quota.run(bag)
response.Amount = 0
}
Expand Down
52 changes: 42 additions & 10 deletions src/envoy/mixer/integration_test/mixer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import (
)

const (
mixerFailMessage = "Unauthenticated by mixer."
mixerAuthFailMessage = "Unauthenticated by mixer."
mixerQuotaFailMessage = "Not enough quota by mixer."
)

// Attributes verification rules
Expand Down Expand Up @@ -134,14 +135,14 @@ const reportAttributesOkPost = `
},
"request.size": 12,
"response.time": "*",
"response.size": 12,
"response.size": 45,
"response.latency": "*",
"response.http.code": 200,
"response.http.code": 429,
"response.headers": {
"date": "*",
"content-type": "text/plain",
"content-length": "12",
":status": "200",
"content-length": "45",
":status": "429",
"server": "envoy"
}
}
Expand Down Expand Up @@ -262,13 +263,26 @@ func verifyAttributes(
t.Fatalf("Failed to verify %s check: %v\n, Attributes: %+v",
tag, err, s.mixer.check.bag)
}

_ = <-s.mixer.report.ch
if err := Verify(s.mixer.report.bag, report); err != nil {
t.Fatalf("Failed to verify %s report: %v\n, Attributes: %+v",
tag, err, s.mixer.report.bag)
}
}

func verifyQuota(s *TestSetup, tag string, t *testing.T) {
_ = <-s.mixer.quota.ch
if s.mixer.quota_request.Quota != "RequestCount" {
t.Fatalf("Failed to verify %s quota name (=RequestCount): %v\n",
tag, s.mixer.quota_request.Quota)
}
if s.mixer.quota_request.Amount != 5 {
t.Fatalf("Failed to verify %s quota amount (=5): %v\n",
tag, s.mixer.quota_request.Amount)
}
}

func TestMixer(t *testing.T) {
s, err := SetUp()
if err != nil {
Expand All @@ -288,32 +302,49 @@ func TestMixer(t *testing.T) {
}
verifyAttributes(&s, "OkGet",
checkAttributesOkGet, reportAttributesOkGet, t)
verifyQuota(&s, "OkGet", t)

// Issues a POST echo request with
if _, _, err := HTTPPost(url, "text/plain", "Hello World!"); err != nil {
// Issues a failed POST request caused by Mixer Quota
s.mixer.quota.r_status = rpc.Status{
Code: int32(rpc.RESOURCE_EXHAUSTED),
Message: mixerQuotaFailMessage,
}
code, resp_body, err := HTTPPost(url, "text/plain", "Hello World!")
// Make sure to restore r_status for next request.
s.mixer.quota.r_status = rpc.Status{}
if err != nil {
t.Errorf("Failed in POST request: %v", err)
}
if code != 429 {
t.Errorf("Status code 429 is expected.")
}
if resp_body != "RESOURCE_EXHAUSTED:"+mixerQuotaFailMessage {
t.Errorf("Error response body is not expected.")
}
verifyAttributes(&s, "OkPost",
checkAttributesOkPost, reportAttributesOkPost, t)
verifyQuota(&s, "OkPost", t)

// Issues a failed request caused by mixer
s.mixer.check.r_status = rpc.Status{
Code: int32(rpc.UNAUTHENTICATED),
Message: mixerFailMessage,
Message: mixerAuthFailMessage,
}
code, resp_body, err := HTTPGet(url)
code, resp_body, err = HTTPGet(url)
// Make sure to restore r_status for next request.
s.mixer.check.r_status = rpc.Status{}
if err != nil {
t.Errorf("Failed in GET request: error: %v", err)
}
if code != 401 {
t.Errorf("Status code 401 is expected.")
}
if resp_body != "UNAUTHENTICATED:"+mixerFailMessage {
if resp_body != "UNAUTHENTICATED:"+mixerAuthFailMessage {
t.Errorf("Error response body is not expected.")
}
verifyAttributes(&s, "MixerFail",
checkAttributesMixerFail, reportAttributesMixerFail, t)
// Not quota call due to Mixer failure.

// Issues a failed request caused by backend
headers := map[string]string{}
Expand All @@ -330,4 +361,5 @@ func TestMixer(t *testing.T) {
}
verifyAttributes(&s, "BackendFail",
checkAttributesBackendFail, reportAttributesBackendFail, t)
verifyQuota(&s, "BackendFail", t)
}
2 changes: 1 addition & 1 deletion src/envoy/mixer/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
################################################################################
#

MIXER_CLIENT = "1d6b587755846fe1b3a44fb53e3ab9ce0534af2c"
MIXER_CLIENT = "b76b5131c4650cefff4af7e4267883a33d66bca1"

def mixer_client_repositories(bind=True):
native.git_repository(
Expand Down

0 comments on commit d2bc18a

Please sign in to comment.