Add support for Envoy v3 API in sentinel-cluster-server-envoy-rls token server module (#2336)
* feat(envoy-rls): Update sentinel-cluster-server-envoy-rls to support envoy v2 and v3 api. * Add envoy v3 api implementation; Make it to be compatible with envoy v2 API. * Add k8s envoy v3 api demo. * docs(envoy-rls): update the README.md
This commit is contained in:
parent
20a0e4d7f0
commit
27514f0262
|
|
@ -91,6 +91,12 @@ After preparing the yaml template, you may deploy the Envoy instance:
|
|||
kubectl apply -f sample/k8s/envoy.yml
|
||||
```
|
||||
|
||||
for v3 api:
|
||||
|
||||
```bash
|
||||
kubectl apply -f sample/k8s/envoy-v3-api.yml
|
||||
```
|
||||
|
||||
## Test the rate limiting
|
||||
|
||||
Now it's show time! We could visit the URL `envoy-service:10000/json` in K8S pods.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: envoy-cm-17
|
||||
data:
|
||||
envoy-yml: |-
|
||||
admin:
|
||||
access_log_path: /tmp/admin_access.log
|
||||
address:
|
||||
socket_address:
|
||||
protocol: TCP
|
||||
address: 127.0.0.1
|
||||
port_value: 9901
|
||||
static_resources:
|
||||
listeners:
|
||||
- name: listener_0
|
||||
address:
|
||||
socket_address:
|
||||
protocol: TCP
|
||||
address: 0.0.0.0
|
||||
port_value: 10000
|
||||
filter_chains:
|
||||
- filters:
|
||||
- name: envoy.filters.network.http_connection_manager
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
|
||||
stat_prefix: ingress_http
|
||||
route_config:
|
||||
name: local_route
|
||||
virtual_hosts:
|
||||
- name: local_service
|
||||
domains: ["*"]
|
||||
routes:
|
||||
- match:
|
||||
prefix: "/"
|
||||
route:
|
||||
cluster: service_httpbin
|
||||
typed_per_filter_config:
|
||||
envoy.filters.http.dynamic_forward_proxy:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig
|
||||
host_rewrite_literal: httpbin.org
|
||||
rate_limits:
|
||||
- stage: 0
|
||||
actions:
|
||||
- {destination_cluster: {}}
|
||||
http_filters:
|
||||
- name: envoy.filters.http.ratelimit
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.ratelimit.v3.RateLimit
|
||||
domain: foo
|
||||
request_type: external
|
||||
failure_mode_deny: false
|
||||
stage: 0
|
||||
rate_limit_service:
|
||||
grpc_service:
|
||||
envoy_grpc:
|
||||
cluster_name: rate_limit_cluster
|
||||
timeout: 2s
|
||||
transport_api_version: V3
|
||||
- name: envoy.filters.http.router
|
||||
typed_config:
|
||||
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
|
||||
clusters:
|
||||
- name: service_httpbin
|
||||
connect_timeout: 0.5s
|
||||
type: LOGICAL_DNS
|
||||
# Comment out the following line to test on v6 networks
|
||||
dns_lookup_family: V4_ONLY
|
||||
lb_policy: ROUND_ROBIN
|
||||
load_assignment:
|
||||
cluster_name: service_httpbin
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: httpbin.org
|
||||
port_value: 80
|
||||
- name: rate_limit_cluster
|
||||
type: STRICT_DNS
|
||||
connect_timeout: 10s
|
||||
lb_policy: ROUND_ROBIN
|
||||
http2_protocol_options: {}
|
||||
load_assignment:
|
||||
cluster_name: rate_limit_cluster
|
||||
endpoints:
|
||||
- lb_endpoints:
|
||||
- endpoint:
|
||||
address:
|
||||
socket_address:
|
||||
address: sentinel-rls-service
|
||||
port_value: 10245
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: envoy-deployment-basic-17
|
||||
labels:
|
||||
app: envoy-17
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: envoy-17
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: envoy-17
|
||||
spec:
|
||||
containers:
|
||||
- name: envoy
|
||||
image: envoyproxy/envoy:v1.17.3
|
||||
ports:
|
||||
- containerPort: 10000
|
||||
command: ["envoy"]
|
||||
args: ["-c", "/tmp/envoy/envoy.yaml"]
|
||||
volumeMounts:
|
||||
- name: envoy-config
|
||||
mountPath: /tmp/envoy
|
||||
volumes:
|
||||
- name: envoy-config
|
||||
configMap:
|
||||
name: envoy-cm-17
|
||||
items:
|
||||
- key: envoy-yml
|
||||
path: envoy.yaml
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: envoy-service-17
|
||||
labels:
|
||||
name: envoy-service-17
|
||||
spec:
|
||||
type: NodePort
|
||||
ports:
|
||||
- port: 10000
|
||||
targetPort: 10000
|
||||
protocol: TCP
|
||||
selector:
|
||||
app: envoy-17
|
||||
|
|
@ -31,7 +31,9 @@ public class SentinelRlsGrpcServer {
|
|||
|
||||
public SentinelRlsGrpcServer(int port) {
|
||||
ServerBuilder<?> builder = ServerBuilder.forPort(port)
|
||||
.addService(new com.alibaba.csp.sentinel.cluster.server.envoy.rls.service.v3.SentinelEnvoyRlsServiceImpl())
|
||||
.addService(new SentinelEnvoyRlsServiceImpl());
|
||||
|
||||
server = builder.build();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,118 @@
|
|||
package com.alibaba.csp.sentinel.cluster.server.envoy.rls.service.v3;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
|
||||
import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager;
|
||||
import com.alibaba.csp.sentinel.cluster.server.envoy.rls.flow.SimpleClusterFlowChecker;
|
||||
import com.alibaba.csp.sentinel.cluster.server.envoy.rls.log.RlsAccessLogger;
|
||||
import com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.util.function.Tuple2;
|
||||
import com.google.protobuf.TextFormat;
|
||||
import io.envoyproxy.envoy.extensions.common.ratelimit.v3.RateLimitDescriptor;
|
||||
import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitRequest;
|
||||
import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse;
|
||||
import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse.Code;
|
||||
import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse.RateLimit;
|
||||
import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse.DescriptorStatus;
|
||||
import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitServiceGrpc;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.alibaba.csp.sentinel.cluster.server.envoy.rls.rule.EnvoySentinelRuleConverter.SEPARATOR;
|
||||
|
||||
/**
|
||||
* gRPC限流入口,实现envoy rls v3 api
|
||||
*
|
||||
* @author Winjay chan
|
||||
* @date 2021/8/4
|
||||
*/
|
||||
public class SentinelEnvoyRlsServiceImpl extends RateLimitServiceGrpc.RateLimitServiceImplBase {
|
||||
@Override
|
||||
public void shouldRateLimit(RateLimitRequest request, StreamObserver<RateLimitResponse> responseObserver) {
|
||||
int acquireCount = request.getHitsAddend();
|
||||
if (acquireCount < 0) {
|
||||
responseObserver.onError(new IllegalArgumentException(
|
||||
"acquireCount should be positive, but actual: " + acquireCount));
|
||||
return;
|
||||
}
|
||||
if (acquireCount == 0) {
|
||||
// Not present, use the default "1" by default.
|
||||
acquireCount = 1;
|
||||
}
|
||||
|
||||
String domain = request.getDomain();
|
||||
boolean blocked = false;
|
||||
List<DescriptorStatus> statusList = new ArrayList<>(request.getDescriptorsCount());
|
||||
for (RateLimitDescriptor descriptor : request.getDescriptorsList()) {
|
||||
Tuple2<FlowRule, TokenResult> t = checkToken(domain, descriptor, acquireCount);
|
||||
TokenResult r = t.r2;
|
||||
|
||||
printAccessLogIfNecessary(domain, descriptor, r);
|
||||
|
||||
if (r.getStatus() == TokenResultStatus.NO_RULE_EXISTS) {
|
||||
// If the rule of the descriptor is absent, the request will pass directly.
|
||||
r.setStatus(TokenResultStatus.OK);
|
||||
}
|
||||
|
||||
if (!blocked && r.getStatus() != TokenResultStatus.OK) {
|
||||
blocked = true;
|
||||
}
|
||||
|
||||
Code statusCode = r.getStatus() == TokenResultStatus.OK ? Code.OK : Code.OVER_LIMIT;
|
||||
DescriptorStatus.Builder descriptorStatusBuilder = DescriptorStatus.newBuilder()
|
||||
.setCode(statusCode);
|
||||
if (t.r1 != null) {
|
||||
descriptorStatusBuilder
|
||||
.setCurrentLimit(RateLimit.newBuilder().setUnit(RateLimit.Unit.SECOND)
|
||||
.setRequestsPerUnit((int)t.r1.getCount())
|
||||
.build())
|
||||
.setLimitRemaining(r.getRemaining());
|
||||
}
|
||||
statusList.add(descriptorStatusBuilder.build());
|
||||
}
|
||||
|
||||
Code overallStatus = blocked ? Code.OVER_LIMIT :Code.OK;
|
||||
RateLimitResponse response = RateLimitResponse.newBuilder()
|
||||
.setOverallCode(overallStatus)
|
||||
.addAllStatuses(statusList)
|
||||
.build();
|
||||
|
||||
responseObserver.onNext(response);
|
||||
responseObserver.onCompleted();
|
||||
}
|
||||
|
||||
private void printAccessLogIfNecessary(String domain, RateLimitDescriptor descriptor, TokenResult result) {
|
||||
if (!RlsAccessLogger.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
String message = new StringBuilder("[RlsAccessLog] domain=").append(domain)
|
||||
.append(", descriptor=").append(TextFormat.shortDebugString(descriptor))
|
||||
.append(", checkStatus=").append(result.getStatus())
|
||||
.append(", remaining=").append(result.getRemaining())
|
||||
.toString();
|
||||
RlsAccessLogger.log(message);
|
||||
}
|
||||
|
||||
protected Tuple2<FlowRule, TokenResult> checkToken(String domain, RateLimitDescriptor descriptor, int acquireCount) {
|
||||
long ruleId = EnvoySentinelRuleConverter.generateFlowId(generateKey(domain, descriptor));
|
||||
|
||||
FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(ruleId);
|
||||
if (rule == null) {
|
||||
// Pass if the target rule is absent.
|
||||
return Tuple2.of(null, new TokenResult(TokenResultStatus.NO_RULE_EXISTS));
|
||||
}
|
||||
// If the rule is present, it should be valid.
|
||||
return Tuple2.of(rule, SimpleClusterFlowChecker.acquireClusterToken(rule, acquireCount));
|
||||
}
|
||||
|
||||
private String generateKey(String domain, RateLimitDescriptor descriptor) {
|
||||
StringBuilder sb = new StringBuilder(domain);
|
||||
for (RateLimitDescriptor.Entry resource : descriptor.getEntriesList()) {
|
||||
sb.append(SEPARATOR).append(resource.getKey()).append(SEPARATOR).append(resource.getValue());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package envoy.config.core.v3;
|
||||
|
||||
import "udpa/annotations/status.proto";
|
||||
import "udpa/annotations/versioning.proto";
|
||||
import "validate/validate.proto";
|
||||
|
||||
option java_package = "io.envoyproxy.envoy.config.core.v3";
|
||||
option java_outer_classname = "BaseProto";
|
||||
option java_multiple_files = true;
|
||||
option (udpa.annotations.file_status).package_version_status = ACTIVE;
|
||||
|
||||
|
||||
|
||||
// Header name/value pair.
|
||||
message HeaderValue {
|
||||
option (udpa.annotations.versioning).previous_message_type = "envoy.api.v2.core.HeaderValue";
|
||||
|
||||
// Header name.
|
||||
string key = 1
|
||||
[(validate.rules).string =
|
||||
{min_len: 1 max_bytes: 16384 well_known_regex: HTTP_HEADER_NAME strict: false}];
|
||||
|
||||
// Header value.
|
||||
//
|
||||
// The same :ref:`format specifier <config_access_log_format>` as used for
|
||||
// :ref:`HTTP access logging <config_access_log>` applies here, however
|
||||
// unknown header values are replaced with the empty string instead of `-`.
|
||||
string value = 2 [
|
||||
(validate.rules).string = {max_bytes: 16384 well_known_regex: HTTP_HEADER_VALUE strict: false}
|
||||
];
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package envoy.extensions.common.ratelimit.v3;
|
||||
|
||||
import "envoy/type/v3/ratelimit_unit.proto";
|
||||
//
|
||||
import "udpa/annotations/status.proto";
|
||||
import "udpa/annotations/versioning.proto";
|
||||
import "validate/validate.proto";
|
||||
|
||||
option java_package = "io.envoyproxy.envoy.extensions.common.ratelimit.v3";
|
||||
option java_outer_classname = "RatelimitProto";
|
||||
option java_multiple_files = true;
|
||||
option (udpa.annotations.file_status).package_version_status = ACTIVE;
|
||||
|
||||
// [#protodoc-title: Common rate limit components]
|
||||
|
||||
// A RateLimitDescriptor is a list of hierarchical entries that are used by the service to
|
||||
// determine the final rate limit key and overall allowed limit. Here are some examples of how
|
||||
// they might be used for the domain "envoy".
|
||||
//
|
||||
// .. code-block:: cpp
|
||||
//
|
||||
// ["authenticated": "false"], ["remote_address": "10.0.0.1"]
|
||||
//
|
||||
// What it does: Limits all unauthenticated traffic for the IP address 10.0.0.1. The
|
||||
// configuration supplies a default limit for the *remote_address* key. If there is a desire to
|
||||
// raise the limit for 10.0.0.1 or block it entirely it can be specified directly in the
|
||||
// configuration.
|
||||
//
|
||||
// .. code-block:: cpp
|
||||
//
|
||||
// ["authenticated": "false"], ["path": "/foo/bar"]
|
||||
//
|
||||
// What it does: Limits all unauthenticated traffic globally for a specific path (or prefix if
|
||||
// configured that way in the service).
|
||||
//
|
||||
// .. code-block:: cpp
|
||||
//
|
||||
// ["authenticated": "false"], ["path": "/foo/bar"], ["remote_address": "10.0.0.1"]
|
||||
//
|
||||
// What it does: Limits unauthenticated traffic to a specific path for a specific IP address.
|
||||
// Like (1) we can raise/block specific IP addresses if we want with an override configuration.
|
||||
//
|
||||
// .. code-block:: cpp
|
||||
//
|
||||
// ["authenticated": "true"], ["client_id": "foo"]
|
||||
//
|
||||
// What it does: Limits all traffic for an authenticated client "foo"
|
||||
//
|
||||
// .. code-block:: cpp
|
||||
//
|
||||
// ["authenticated": "true"], ["client_id": "foo"], ["path": "/foo/bar"]
|
||||
//
|
||||
// What it does: Limits traffic to a specific path for an authenticated client "foo"
|
||||
//
|
||||
// The idea behind the API is that (1)/(2)/(3) and (4)/(5) can be sent in 1 request if desired.
|
||||
// This enables building complex application scenarios with a generic backend.
|
||||
//
|
||||
// Optionally the descriptor can contain a limit override under a "limit" key, that specifies
|
||||
// the number of requests per unit to use instead of the number configured in the
|
||||
// rate limiting service.
|
||||
message RateLimitDescriptor {
|
||||
option (udpa.annotations.versioning).previous_message_type =
|
||||
"envoy.api.v2.ratelimit.RateLimitDescriptor";
|
||||
|
||||
message Entry {
|
||||
option (udpa.annotations.versioning).previous_message_type =
|
||||
"envoy.api.v2.ratelimit.RateLimitDescriptor.Entry";
|
||||
|
||||
// Descriptor key.
|
||||
string key = 1 [(validate.rules).string = {min_len: 1}];
|
||||
|
||||
// Descriptor value.
|
||||
string value = 2 [(validate.rules).string = {min_len: 1}];
|
||||
}
|
||||
|
||||
// Override rate limit to apply to this descriptor instead of the limit
|
||||
// configured in the rate limit service. See :ref:`rate limit override
|
||||
// <config_http_filters_rate_limit_rate_limit_override>` for more information.
|
||||
message RateLimitOverride {
|
||||
// The number of requests per unit of time.
|
||||
uint32 requests_per_unit = 1;
|
||||
|
||||
// The unit of time.
|
||||
type.v3.RateLimitUnit unit = 2 [(validate.rules).enum = {defined_only: true}];
|
||||
}
|
||||
|
||||
// Descriptor entries.
|
||||
repeated Entry entries = 1 [(validate.rules).repeated = {min_items: 1}];
|
||||
|
||||
// Optional rate limit override to supply to the ratelimit service.
|
||||
RateLimitOverride limit = 2;
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package envoy.service.ratelimit.v3;
|
||||
|
||||
import "envoy/config/core/v3/base.proto";
|
||||
import "envoy/extensions/common/ratelimit/v3/ratelimit.proto";
|
||||
|
||||
import "google/protobuf/duration.proto";
|
||||
import "google/protobuf/struct.proto";
|
||||
import "google/protobuf/timestamp.proto";
|
||||
//
|
||||
import "udpa/annotations/status.proto";
|
||||
import "udpa/annotations/versioning.proto";
|
||||
import "validate/validate.proto";
|
||||
|
||||
option java_package = "io.envoyproxy.envoy.service.ratelimit.v3";
|
||||
option java_outer_classname = "RlsProto";
|
||||
option java_multiple_files = true;
|
||||
option java_generic_services = true;
|
||||
option (udpa.annotations.file_status).package_version_status = ACTIVE;
|
||||
|
||||
// [#protodoc-title: Rate Limit Service (RLS)]
|
||||
|
||||
service RateLimitService {
|
||||
// Determine whether rate limiting should take place.
|
||||
rpc ShouldRateLimit(RateLimitRequest) returns (RateLimitResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
// Main message for a rate limit request. The rate limit service is designed to be fully generic
|
||||
// in the sense that it can operate on arbitrary hierarchical key/value pairs. The loaded
|
||||
// configuration will parse the request and find the most specific limit to apply. In addition,
|
||||
// a RateLimitRequest can contain multiple "descriptors" to limit on. When multiple descriptors
|
||||
// are provided, the server will limit on *ALL* of them and return an OVER_LIMIT response if any
|
||||
// of them are over limit. This enables more complex application level rate limiting scenarios
|
||||
// if desired.
|
||||
message RateLimitRequest {
|
||||
option (udpa.annotations.versioning).previous_message_type =
|
||||
"envoy.service.ratelimit.v2.RateLimitRequest";
|
||||
|
||||
// All rate limit requests must specify a domain. This enables the configuration to be per
|
||||
// application without fear of overlap. E.g., "envoy".
|
||||
string domain = 1;
|
||||
|
||||
// All rate limit requests must specify at least one RateLimitDescriptor. Each descriptor is
|
||||
// processed by the service (see below). If any of the descriptors are over limit, the entire
|
||||
// request is considered to be over limit.
|
||||
repeated envoy.extensions.common.ratelimit.v3.RateLimitDescriptor descriptors = 2;
|
||||
|
||||
// Rate limit requests can optionally specify the number of hits a request adds to the matched
|
||||
// limit. If the value is not set in the message, a request increases the matched limit by 1.
|
||||
uint32 hits_addend = 3;
|
||||
}
|
||||
|
||||
// A response from a ShouldRateLimit call.
|
||||
// [#next-free-field: 7]
|
||||
message RateLimitResponse {
|
||||
option (udpa.annotations.versioning).previous_message_type =
|
||||
"envoy.service.ratelimit.v2.RateLimitResponse";
|
||||
|
||||
enum Code {
|
||||
// The response code is not known.
|
||||
UNKNOWN = 0;
|
||||
|
||||
// The response code to notify that the number of requests are under limit.
|
||||
OK = 1;
|
||||
|
||||
// The response code to notify that the number of requests are over limit.
|
||||
OVER_LIMIT = 2;
|
||||
}
|
||||
|
||||
// Defines an actual rate limit in terms of requests per unit of time and the unit itself.
|
||||
message RateLimit {
|
||||
option (udpa.annotations.versioning).previous_message_type =
|
||||
"envoy.service.ratelimit.v2.RateLimitResponse.RateLimit";
|
||||
|
||||
// Identifies the unit of of time for rate limit.
|
||||
// [#comment: replace by envoy/type/v3/ratelimit_unit.proto in v4]
|
||||
enum Unit {
|
||||
// The time unit is not known.
|
||||
UNKNOWN = 0;
|
||||
|
||||
// The time unit representing a second.
|
||||
SECOND = 1;
|
||||
|
||||
// The time unit representing a minute.
|
||||
MINUTE = 2;
|
||||
|
||||
// The time unit representing an hour.
|
||||
HOUR = 3;
|
||||
|
||||
// The time unit representing a day.
|
||||
DAY = 4;
|
||||
}
|
||||
|
||||
// A name or description of this limit.
|
||||
string name = 3;
|
||||
|
||||
// The number of requests per unit of time.
|
||||
uint32 requests_per_unit = 1;
|
||||
|
||||
// The unit of time.
|
||||
Unit unit = 2;
|
||||
}
|
||||
|
||||
// Cacheable quota for responses, see documentation for the :ref:`quota
|
||||
// <envoy_v3_api_field_service.ratelimit.v3.RateLimitResponse.DescriptorStatus.quota>` field.
|
||||
// [#not-implemented-hide:]
|
||||
message Quota {
|
||||
// Number of matching requests granted in quota. Must be 1 or more.
|
||||
uint32 requests = 1 [(validate.rules).uint32 = {gt: 0}];
|
||||
|
||||
oneof expiration_specifier {
|
||||
// Point in time at which the quota expires.
|
||||
google.protobuf.Timestamp valid_until = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// [#next-free-field: 6]
|
||||
message DescriptorStatus {
|
||||
option (udpa.annotations.versioning).previous_message_type =
|
||||
"envoy.service.ratelimit.v2.RateLimitResponse.DescriptorStatus";
|
||||
|
||||
// The response code for an individual descriptor.
|
||||
Code code = 1;
|
||||
|
||||
// The current limit as configured by the server. Useful for debugging, etc.
|
||||
RateLimit current_limit = 2;
|
||||
|
||||
// The limit remaining in the current time unit.
|
||||
uint32 limit_remaining = 3;
|
||||
|
||||
// Duration until reset of the current limit window.
|
||||
google.protobuf.Duration duration_until_reset = 4;
|
||||
|
||||
// Quota granted for the descriptor. This is a certain number of requests over a period of time.
|
||||
// The client may cache this result and apply the effective RateLimitResponse to future matching
|
||||
// requests containing a matching descriptor without querying rate limit service.
|
||||
//
|
||||
// Quota is available for a request if its descriptor set has cached quota available for all
|
||||
// descriptors.
|
||||
//
|
||||
// If quota is available, a RLS request will not be made and the quota will be reduced by 1 for
|
||||
// all matching descriptors.
|
||||
//
|
||||
// If there is not sufficient quota, there are three cases:
|
||||
// 1. A cached entry exists for a RLS descriptor that is out-of-quota, but not expired.
|
||||
// In this case, the request will be treated as OVER_LIMIT.
|
||||
// 2. Some RLS descriptors have a cached entry that has valid quota but some RLS descriptors
|
||||
// have no cached entry. This will trigger a new RLS request.
|
||||
// When the result is returned, a single unit will be consumed from the quota for all
|
||||
// matching descriptors.
|
||||
// If the server did not provide a quota, such as the quota message is empty for some of
|
||||
// the descriptors, then the request admission is determined by the
|
||||
// :ref:`overall_code <envoy_v3_api_field_service.ratelimit.v3.RateLimitResponse.overall_code>`.
|
||||
// 3. All RLS descriptors lack a cached entry, this will trigger a new RLS request,
|
||||
// When the result is returned, a single unit will be consumed from the quota for all
|
||||
// matching descriptors.
|
||||
// If the server did not provide a quota, such as the quota message is empty for some of
|
||||
// the descriptors, then the request admission is determined by the
|
||||
// :ref:`overall_code <envoy_v3_api_field_service.ratelimit.v3.RateLimitResponse.overall_code>`.
|
||||
//
|
||||
// When quota expires due to timeout, a new RLS request will also be made.
|
||||
// The implementation may choose to preemptively query the rate limit server for more quota on or
|
||||
// before expiration or before the available quota runs out.
|
||||
// [#not-implemented-hide:]
|
||||
Quota quota = 5;
|
||||
}
|
||||
|
||||
// The overall response code which takes into account all of the descriptors that were passed
|
||||
// in the RateLimitRequest message.
|
||||
Code overall_code = 1;
|
||||
|
||||
// A list of DescriptorStatus messages which matches the length of the descriptor list passed
|
||||
// in the RateLimitRequest. This can be used by the caller to determine which individual
|
||||
// descriptors failed and/or what the currently configured limits are for all of them.
|
||||
repeated DescriptorStatus statuses = 2;
|
||||
|
||||
// A list of headers to add to the response
|
||||
repeated config.core.v3.HeaderValue response_headers_to_add = 3;
|
||||
|
||||
// A list of headers to add to the request when forwarded
|
||||
repeated config.core.v3.HeaderValue request_headers_to_add = 4;
|
||||
|
||||
// A response body to send to the downstream client when the response code is not OK.
|
||||
bytes raw_body = 5;
|
||||
|
||||
// Optional response metadata that will be emitted as dynamic metadata to be consumed by the next
|
||||
// filter. This metadata lives in a namespace specified by the canonical name of extension filter
|
||||
// that requires it:
|
||||
//
|
||||
// - :ref:`envoy.filters.http.ratelimit <config_http_filters_ratelimit_dynamic_metadata>` for HTTP filter.
|
||||
// - :ref:`envoy.filters.network.ratelimit <config_network_filters_ratelimit_dynamic_metadata>` for network filter.
|
||||
// - :ref:`envoy.filters.thrift.rate_limit <config_thrift_filters_rate_limit_dynamic_metadata>` for Thrift filter.
|
||||
google.protobuf.Struct dynamic_metadata = 6;
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package envoy.type.v3;
|
||||
|
||||
import "udpa/annotations/status.proto";
|
||||
|
||||
option java_package = "io.envoyproxy.envoy.type.v3";
|
||||
option java_outer_classname = "RatelimitUnitProto";
|
||||
option java_multiple_files = true;
|
||||
option (udpa.annotations.file_status).package_version_status = ACTIVE;
|
||||
|
||||
// [#protodoc-title: Ratelimit Time Unit]
|
||||
|
||||
// Identifies the unit of of time for rate limit.
|
||||
enum RateLimitUnit {
|
||||
// The time unit is not known.
|
||||
UNKNOWN = 0;
|
||||
|
||||
// The time unit representing a second.
|
||||
SECOND = 1;
|
||||
|
||||
// The time unit representing a minute.
|
||||
MINUTE = 2;
|
||||
|
||||
// The time unit representing an hour.
|
||||
HOUR = 3;
|
||||
|
||||
// The time unit representing a day.
|
||||
DAY = 4;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
load("//bazel:api_build_system.bzl", "udpa_proto_package")
|
||||
|
||||
licenses(["notice"]) # Apache 2
|
||||
|
||||
udpa_proto_package()
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package udpa.annotations;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
// Magic number in this file derived from top 28bit of SHA256 digest of
|
||||
// "udpa.annotation.migrate".
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
MigrateAnnotation message_migrate = 171962766;
|
||||
}
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
FieldMigrateAnnotation field_migrate = 171962766;
|
||||
}
|
||||
|
||||
extend google.protobuf.EnumOptions {
|
||||
MigrateAnnotation enum_migrate = 171962766;
|
||||
}
|
||||
|
||||
extend google.protobuf.EnumValueOptions {
|
||||
MigrateAnnotation enum_value_migrate = 171962766;
|
||||
}
|
||||
|
||||
extend google.protobuf.FileOptions {
|
||||
FileMigrateAnnotation file_migrate = 171962766;
|
||||
}
|
||||
|
||||
message MigrateAnnotation {
|
||||
// Rename the message/enum/enum value in next version.
|
||||
string rename = 1;
|
||||
}
|
||||
|
||||
message FieldMigrateAnnotation {
|
||||
// Rename the field in next version.
|
||||
string rename = 1;
|
||||
|
||||
// Add the field to a named oneof in next version. If this already exists, the
|
||||
// field will join its siblings under the oneof, otherwise a new oneof will be
|
||||
// created with the given name.
|
||||
string oneof_promotion = 2;
|
||||
}
|
||||
|
||||
message FileMigrateAnnotation {
|
||||
// Move all types in the file to another package, this implies changing proto
|
||||
// file path.
|
||||
string move_to_package = 2;
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package udpa.annotations;
|
||||
|
||||
import "udpa/annotations/status.proto";
|
||||
|
||||
import "google/protobuf/any.proto";
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
import "validate/validate.proto";
|
||||
|
||||
// All annotations in this file are experimental and subject to change. Their
|
||||
// only consumer today is the Envoy APIs and SecuritAnnotationValidator protoc
|
||||
// plugin in this repository.
|
||||
option (udpa.annotations.file_status).work_in_progress = true;
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
// Magic number is the 28 most significant bits in the sha256sum of
|
||||
// "udpa.annotations.security".
|
||||
FieldSecurityAnnotation security = 11122993;
|
||||
}
|
||||
|
||||
// These annotations indicate metadata for the purpose of understanding the
|
||||
// security significance of fields.
|
||||
message FieldSecurityAnnotation {
|
||||
// Field should be set in the presence of untrusted downstreams.
|
||||
bool configure_for_untrusted_downstream = 1;
|
||||
|
||||
// Field should be set in the presence of untrusted upstreams.
|
||||
bool configure_for_untrusted_upstream = 2;
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package udpa.annotations;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
extend google.protobuf.FieldOptions {
|
||||
// Magic number is the 28 most significant bits in the sha256sum of "udpa.annotations.sensitive".
|
||||
// When set to true, `sensitive` indicates that this field contains sensitive data, such as
|
||||
// personally identifiable information, passwords, or private keys, and should be redacted for
|
||||
// display by tools aware of this annotation. Note that that this has no effect on standard
|
||||
// Protobuf functions such as `TextFormat::PrintToString`.
|
||||
bool sensitive = 76569463;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package udpa.annotations;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
// Magic number in this file derived from top 28bit of SHA256 digest of
|
||||
// "udpa.annotation.status".
|
||||
extend google.protobuf.FileOptions {
|
||||
StatusAnnotation file_status = 222707719;
|
||||
}
|
||||
|
||||
enum PackageVersionStatus {
|
||||
// Unknown package version status.
|
||||
UNKNOWN = 0;
|
||||
|
||||
// This version of the package is frozen.
|
||||
FROZEN = 1;
|
||||
|
||||
// This version of the package is the active development version.
|
||||
ACTIVE = 2;
|
||||
|
||||
// This version of the package is the candidate for the next major version. It
|
||||
// is typically machine generated from the active development version.
|
||||
NEXT_MAJOR_VERSION_CANDIDATE = 3;
|
||||
}
|
||||
|
||||
message StatusAnnotation {
|
||||
// The entity is work-in-progress and subject to breaking changes.
|
||||
bool work_in_progress = 1;
|
||||
|
||||
// The entity belongs to a package with the given version status.
|
||||
PackageVersionStatus package_version_status = 2;
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package udpa.annotations;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
extend google.protobuf.MessageOptions {
|
||||
// Magic number derived from 0x78 ('x') 0x44 ('D') 0x53 ('S')
|
||||
VersioningAnnotation versioning = 7881811;
|
||||
}
|
||||
|
||||
message VersioningAnnotation {
|
||||
// Track the previous message type. E.g. this message might be
|
||||
// udpa.foo.v3alpha.Foo and it was previously udpa.bar.v2.Bar. This
|
||||
// information is consumed by UDPA via proto descriptors.
|
||||
string previous_message_type = 1;
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
syntax = "proto2";
|
||||
package validate;
|
||||
|
||||
option go_package = "github.com/lyft/protoc-gen-validate/validate";
|
||||
option java_package = "com.lyft.pgv.validate";
|
||||
option go_package = "github.com/envoyproxy/protoc-gen-validate/validate";
|
||||
option java_package = "io.envoyproxy.pgv.validate";
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
import "google/protobuf/duration.proto";
|
||||
|
|
@ -12,26 +12,29 @@ import "google/protobuf/timestamp.proto";
|
|||
extend google.protobuf.MessageOptions {
|
||||
// Disabled nullifies any validation rules for this message, including any
|
||||
// message fields associated with it that do support validation.
|
||||
optional bool disabled = 919191;
|
||||
optional bool disabled = 1071;
|
||||
// Ignore skips generation of validation methods for this message.
|
||||
optional bool ignored = 1072;
|
||||
}
|
||||
|
||||
// Validation rules applied at the oneof level
|
||||
extend google.protobuf.OneofOptions {
|
||||
// Required ensures that exactly one the field options in a oneof is set;
|
||||
// validation fails if no fields in the oneof are set.
|
||||
optional bool required = 919191;
|
||||
optional bool required = 1071;
|
||||
}
|
||||
|
||||
// Validation rules applied at the field level
|
||||
extend google.protobuf.FieldOptions {
|
||||
// Rules specify the validations to be performed on this field. By default,
|
||||
// no validation is performed against a field.
|
||||
optional FieldRules rules = 919191;
|
||||
optional FieldRules rules = 1071;
|
||||
}
|
||||
|
||||
// FieldRules encapsulates the rules for each type of field. Depending on the
|
||||
// field, the correct set should be used to ensure proper validations.
|
||||
message FieldRules {
|
||||
optional MessageRules message = 17;
|
||||
oneof type {
|
||||
// Scalar Field Types
|
||||
FloatRules float = 1;
|
||||
|
|
@ -52,7 +55,6 @@ message FieldRules {
|
|||
|
||||
// Complex Field Types
|
||||
EnumRules enum = 16;
|
||||
MessageRules message = 17;
|
||||
RepeatedRules repeated = 18;
|
||||
MapRules map = 19;
|
||||
|
||||
|
|
@ -93,6 +95,10 @@ message FloatRules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated float not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// DoubleRules describes the constraints applied to `double` values
|
||||
|
|
@ -125,6 +131,10 @@ message DoubleRules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated double not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// Int32Rules describes the constraints applied to `int32` values
|
||||
|
|
@ -157,6 +167,10 @@ message Int32Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated int32 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// Int64Rules describes the constraints applied to `int64` values
|
||||
|
|
@ -189,6 +203,10 @@ message Int64Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated int64 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// UInt32Rules describes the constraints applied to `uint32` values
|
||||
|
|
@ -221,6 +239,10 @@ message UInt32Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated uint32 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// UInt64Rules describes the constraints applied to `uint64` values
|
||||
|
|
@ -253,6 +275,10 @@ message UInt64Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated uint64 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// SInt32Rules describes the constraints applied to `sint32` values
|
||||
|
|
@ -285,6 +311,10 @@ message SInt32Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated sint32 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// SInt64Rules describes the constraints applied to `sint64` values
|
||||
|
|
@ -317,6 +347,10 @@ message SInt64Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated sint64 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// Fixed32Rules describes the constraints applied to `fixed32` values
|
||||
|
|
@ -349,6 +383,10 @@ message Fixed32Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated fixed32 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// Fixed64Rules describes the constraints applied to `fixed64` values
|
||||
|
|
@ -381,6 +419,10 @@ message Fixed64Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated fixed64 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// SFixed32Rules describes the constraints applied to `sfixed32` values
|
||||
|
|
@ -413,6 +455,10 @@ message SFixed32Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated sfixed32 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// SFixed64Rules describes the constraints applied to `sfixed64` values
|
||||
|
|
@ -445,6 +491,10 @@ message SFixed64Rules {
|
|||
// NotIn specifies that this field cannot be equal to one of the specified
|
||||
// values
|
||||
repeated sfixed64 not_in = 7;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 8;
|
||||
}
|
||||
|
||||
// BoolRules describes the constraints applied to `bool` values
|
||||
|
|
@ -502,6 +552,10 @@ message StringRules {
|
|||
// anywhere in the string.
|
||||
optional string contains = 9;
|
||||
|
||||
// NotContains specifies that this field cannot have the specified substring
|
||||
// anywhere in the string.
|
||||
optional string not_contains = 23;
|
||||
|
||||
// In specifies that this field must be equal to one of the specified
|
||||
// values
|
||||
repeated string in = 10;
|
||||
|
|
@ -540,7 +594,41 @@ message StringRules {
|
|||
// UriRef specifies that the field must be a valid URI as defined by RFC
|
||||
// 3986 and may be relative or absolute.
|
||||
bool uri_ref = 18;
|
||||
|
||||
// Address specifies that the field must be either a valid hostname as
|
||||
// defined by RFC 1034 (which does not support internationalized domain
|
||||
// names or IDNs), or it can be a valid IP (v4 or v6).
|
||||
bool address = 21;
|
||||
|
||||
// Uuid specifies that the field must be a valid UUID as defined by
|
||||
// RFC 4122
|
||||
bool uuid = 22;
|
||||
|
||||
// WellKnownRegex specifies a common well known pattern defined as a regex.
|
||||
KnownRegex well_known_regex = 24;
|
||||
}
|
||||
|
||||
// This applies to regexes HTTP_HEADER_NAME and HTTP_HEADER_VALUE to enable
|
||||
// strict header validation.
|
||||
// By default, this is true, and HTTP header validations are RFC-compliant.
|
||||
// Setting to false will enable a looser validations that only disallows
|
||||
// \r\n\0 characters, which can be used to bypass header matching rules.
|
||||
optional bool strict = 25 [default = true];
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 26;
|
||||
}
|
||||
|
||||
// WellKnownRegex contain some well-known patterns.
|
||||
enum KnownRegex {
|
||||
UNKNOWN = 0;
|
||||
|
||||
// HTTP header name as defined by RFC 7230.
|
||||
HTTP_HEADER_NAME = 1;
|
||||
|
||||
// HTTP header value as defined by RFC 7230.
|
||||
HTTP_HEADER_VALUE = 2;
|
||||
}
|
||||
|
||||
// BytesRules describe the constraints applied to `bytes` values
|
||||
|
|
@ -599,6 +687,10 @@ message BytesRules {
|
|||
// format
|
||||
bool ipv6 = 12;
|
||||
}
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 14;
|
||||
}
|
||||
|
||||
// EnumRules describe the constraints applied to enum values
|
||||
|
|
@ -649,6 +741,10 @@ message RepeatedRules {
|
|||
// Repeated message fields will still execute validation against each item
|
||||
// unless skip is specified here.
|
||||
optional FieldRules items = 4;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 5;
|
||||
}
|
||||
|
||||
// MapRules describe the constraints applied to `map` values
|
||||
|
|
@ -672,6 +768,10 @@ message MapRules {
|
|||
// in the field. Message values will still have their validations evaluated
|
||||
// unless skip is specified here.
|
||||
optional FieldRules values = 5;
|
||||
|
||||
// IgnoreEmpty specifies that the validation rules of this field should be
|
||||
// evaluated only if the field is not empty
|
||||
optional bool ignore_empty = 6;
|
||||
}
|
||||
|
||||
// AnyRules describe constraints applied exclusively to the
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
package com.alibaba.csp.sentinel.cluster.server.envoy.rls.service.v3;
|
||||
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResult;
|
||||
import com.alibaba.csp.sentinel.cluster.TokenResultStatus;
|
||||
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
|
||||
import com.alibaba.csp.sentinel.util.function.Tuple2;
|
||||
|
||||
import io.envoyproxy.envoy.extensions.common.ratelimit.v3.RateLimitDescriptor;
|
||||
import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitRequest;
|
||||
import io.envoyproxy.envoy.service.ratelimit.v3.RateLimitResponse;
|
||||
import io.grpc.stub.StreamObserver;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Created by Winjay
|
||||
*
|
||||
* @author Winjay chan
|
||||
* @date 2021/8/13 16:31
|
||||
*/
|
||||
public class SentinelEnvoyRlsServiceImplTest {
|
||||
@Test
|
||||
public void testShouldRateLimitPass() {
|
||||
SentinelEnvoyRlsServiceImpl rlsService = mock(SentinelEnvoyRlsServiceImpl.class);
|
||||
StreamObserver<RateLimitResponse> streamObserver = mock(StreamObserver.class);
|
||||
String domain = "testShouldRateLimitPass";
|
||||
int acquireCount = 1;
|
||||
|
||||
RateLimitDescriptor descriptor1 = RateLimitDescriptor.newBuilder()
|
||||
.addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk1").setValue("rv1").build())
|
||||
.build();
|
||||
RateLimitDescriptor descriptor2 = RateLimitDescriptor.newBuilder()
|
||||
.addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk2").setValue("rv2").build())
|
||||
.addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk3").setValue("rv3").build())
|
||||
.build();
|
||||
|
||||
ArgumentCaptor<RateLimitResponse> responseCapture = ArgumentCaptor.forClass(RateLimitResponse.class);
|
||||
doNothing().when(streamObserver)
|
||||
.onNext(responseCapture.capture());
|
||||
|
||||
doCallRealMethod().when(rlsService).shouldRateLimit(any(), any());
|
||||
when(rlsService.checkToken(eq(domain), same(descriptor1), eq(acquireCount)))
|
||||
.thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK)));
|
||||
when(rlsService.checkToken(eq(domain), same(descriptor2), eq(acquireCount)))
|
||||
.thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK)));
|
||||
|
||||
RateLimitRequest rateLimitRequest = RateLimitRequest.newBuilder()
|
||||
.addDescriptors(descriptor1)
|
||||
.addDescriptors(descriptor2)
|
||||
.setDomain(domain)
|
||||
.setHitsAddend(acquireCount)
|
||||
.build();
|
||||
rlsService.shouldRateLimit(rateLimitRequest, streamObserver);
|
||||
|
||||
RateLimitResponse response = responseCapture.getValue();
|
||||
assertEquals(RateLimitResponse.Code.OK, response.getOverallCode());
|
||||
response.getStatusesList()
|
||||
.forEach(e -> assertEquals(RateLimitResponse.Code.OK, e.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShouldRatePartialBlock() {
|
||||
SentinelEnvoyRlsServiceImpl rlsService = mock(SentinelEnvoyRlsServiceImpl.class);
|
||||
StreamObserver<RateLimitResponse> streamObserver = mock(StreamObserver.class);
|
||||
String domain = "testShouldRatePartialBlock";
|
||||
int acquireCount = 1;
|
||||
|
||||
RateLimitDescriptor descriptor1 = RateLimitDescriptor.newBuilder()
|
||||
.addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk1").setValue("rv1").build())
|
||||
.build();
|
||||
RateLimitDescriptor descriptor2 = RateLimitDescriptor.newBuilder()
|
||||
.addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk2").setValue("rv2").build())
|
||||
.addEntries(RateLimitDescriptor.Entry.newBuilder().setKey("rk3").setValue("rv3").build())
|
||||
.build();
|
||||
|
||||
ArgumentCaptor<RateLimitResponse> responseCapture = ArgumentCaptor.forClass(RateLimitResponse.class);
|
||||
doNothing().when(streamObserver)
|
||||
.onNext(responseCapture.capture());
|
||||
|
||||
doCallRealMethod().when(rlsService).shouldRateLimit(any(), any());
|
||||
when(rlsService.checkToken(eq(domain), same(descriptor1), eq(acquireCount)))
|
||||
.thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.BLOCKED)));
|
||||
when(rlsService.checkToken(eq(domain), same(descriptor2), eq(acquireCount)))
|
||||
.thenReturn(Tuple2.of(new FlowRule(), new TokenResult(TokenResultStatus.OK)));
|
||||
|
||||
RateLimitRequest rateLimitRequest = RateLimitRequest.newBuilder()
|
||||
.addDescriptors(descriptor1)
|
||||
.addDescriptors(descriptor2)
|
||||
.setDomain(domain)
|
||||
.setHitsAddend(acquireCount)
|
||||
.build();
|
||||
rlsService.shouldRateLimit(rateLimitRequest, streamObserver);
|
||||
|
||||
RateLimitResponse response = responseCapture.getValue();
|
||||
assertEquals(RateLimitResponse.Code.OVER_LIMIT, response.getOverallCode());
|
||||
assertEquals(2, response.getStatusesCount());
|
||||
assertTrue(response.getStatusesList().stream()
|
||||
.anyMatch(e -> e.getCode().equals(RateLimitResponse.Code.OVER_LIMIT)));
|
||||
assertFalse(response.getStatusesList().stream()
|
||||
.allMatch(e -> e.getCode().equals(RateLimitResponse.Code.OVER_LIMIT)));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue