diff --git a/README.md b/README.md index 86387d43..295e808b 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ - [Example 1](#example-1) - [Example 2](#example-2) - [Example 3](#example-3) + - [Example 4](#example-4) - [Loading Configuration](#loading-configuration) - [Rate limit statistics](#rate-limit-statistics) - [Debug Port](#debug-port) @@ -61,9 +62,9 @@ The rate limit configuration file format is YAML (mainly so that comments are su ### Definitions -* **Domain:** A domain is a container for a set of rate limits. All domains known to the rate limit service must be +* **Domain:** A domain is a container for a set of rate limits. All domains known to the Ratelimit service must be globally unique. They serve as a way for different teams/projects to have rate limit configurations that don't conflict. -* **Descriptor:** A descriptor is a list of key/value pairs owned by a domain that the rate limit service uses to +* **Descriptor:** A descriptor is a list of key/value pairs owned by a domain that the Ratelimit service uses to select the correct rate limit to use when limiting. Descriptors are case-sensitive. Examples of descriptors are: * ("database", "users") * ("message_type", "marketing"),("to_number","2061234567") @@ -110,7 +111,7 @@ future based on customer demand. Let's start with a simple example: -``` +```yaml domain: mongo_cps descriptors: - key: database @@ -135,7 +136,7 @@ request per second rate limit. A slightly more complex example: -``` +```yaml domain: messaging descriptors: # Only allow 5 marketing messages a day @@ -178,13 +179,14 @@ RateLimitRequest: descriptor: ("to_number", "2061111111") ``` -And the service with rate limit against *all* matching rules and return an aggregate result. +And the service will rate limit against *all* matching rules and return an aggregate result; a logical OR of all +the individual rate limit decisions. #### Example 3 -One last example to illustrate matching order. +An example to illustrate matching order. -``` +```yaml domain: edge_proxy_per_ip descriptors: - key: ip_address @@ -205,8 +207,48 @@ be configured to make a rate limit service call with the descriptor ("ip_address get 10 requests per second as would any other IP. However, the configuration also contains a second configuration that explicitly defines a value along with the same key. If the descriptor ("ip_address", "50.0.0.5") is received, the service will -*attempt the most specific match possible*. This means both the most nested matching descriptor entry, as well as -the most specific at any descriptor list level. Thus, key/value is always attempted as a match before just key. +*attempt the most specific match possible*. This means +the most specific descriptor at the same level as your request. Thus, key/value is always attempted as a match before just key. + +#### Example 4 + +The Ratelimit service matches requests to configuration entries with the same level, i.e +same number of tuples in the request's descriptor as nested levels of descriptors +in the configuration file. For instance, the following request: + +``` +RateLimitRequest: + domain: example4 + descriptor: ("key", "value"),("subkey", "subvalue") +``` + +Would **not** match the following configuration. Even though the first descriptor in +the request matches the 1st level descriptor in the configuration, the request has +two tuples in the descriptor. + +```yaml +domain: example4 +descriptors: + - key: key + value: value + rate_limit: + - requests_per_unit: 300 + unit: second +``` + +However, it would match the following configuration: + +```yaml +domain: example4 +descriptors: + - key: key + value: value + descriptors: + - key: subkey + rate_limit: + - requests_per_unit: 300 + unit: second +``` ## Loading Configuration diff --git a/src/config/config_impl.go b/src/config/config_impl.go index a1fab23d..fff76ea9 100644 --- a/src/config/config_impl.go +++ b/src/config/config_impl.go @@ -251,7 +251,7 @@ func (this *rateLimitConfigImpl) GetLimit( } descriptorsMap := value.descriptors - for _, entry := range descriptor.Entries { + for i, entry := range descriptor.Entries { // First see if key_value is in the map. If that isn't in the map we look for just key // to check for a default value. finalKey := entry.Key + "_" + entry.Value @@ -265,7 +265,11 @@ func (this *rateLimitConfigImpl) GetLimit( if nextDescriptor != nil && nextDescriptor.limit != nil { logger.Debugf("found rate limit: %s", finalKey) - rateLimit = nextDescriptor.limit + if (i == len(descriptor.Entries) - 1) { + rateLimit = nextDescriptor.limit + } else { + logger.Debugf("request depth does not match config depth, there are more entries in the request's descriptor") + } } if nextDescriptor != nil && len(nextDescriptor.descriptors) > 0 { diff --git a/test/config/basic_config.yaml b/test/config/basic_config.yaml index 4dec3c07..29d3d56d 100644 --- a/test/config/basic_config.yaml +++ b/test/config/basic_config.yaml @@ -40,3 +40,15 @@ descriptors: rate_limit: unit: day requests_per_unit: 1 + + - key: key5 + value: value5 + rate_limit: + unit: day + requests_per_unit: 15 + descriptors: + - key: subkey5 + value: subvalue5 + rate_limit: + unit: day + requests_per_unit: 25 diff --git a/test/config/config_test.go b/test/config/config_test.go index a788c8a8..0443521e 100644 --- a/test/config/config_test.go +++ b/test/config/config_test.go @@ -37,6 +37,16 @@ func TestBasicConfig(t *testing.T) { &pb.RateLimitDescriptor{[]*pb.RateLimitDescriptor_Entry{{"key1", "value1"}}}) assert.Nil(rl) + rl = rlConfig.GetLimit( + nil, "test-domain", + &pb.RateLimitDescriptor{[]*pb.RateLimitDescriptor_Entry{{"key2", "value2"}, {"subkey", "subvalue"}}}) + assert.Nil(rl) + + rl = rlConfig.GetLimit( + nil, "test-domain", + &pb.RateLimitDescriptor{[]*pb.RateLimitDescriptor_Entry{{"key5", "value5"}, {"subkey5", "subvalue"}}}) + assert.Nil(rl) + rl = rlConfig.GetLimit( nil, "test-domain", &pb.RateLimitDescriptor{