Skip to content
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
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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using namespace?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't like to use namespace at mixer_client level. We should move these two variables into Attributes class, then use Attributes namespace.

Will do that later.

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