syntax = "proto3"; package envoy.service.rate_limit_quota.v3; import "envoy/type/v3/ratelimit_strategy.proto"; import "google/protobuf/duration.proto"; import "xds/annotations/v3/status.proto"; import "udpa/annotations/status.proto"; import "validate/validate.proto"; option java_package = "io.envoyproxy.envoy.service.rate_limit_quota.v3"; option java_outer_classname = "RlqsProto"; option java_multiple_files = true; option go_package = "github.com/envoyproxy/go-control-plane/envoy/service/rate_limit_quota/v3;rate_limit_quotav3"; option (udpa.annotations.file_status).package_version_status = ACTIVE; option (xds.annotations.v3.file_status).work_in_progress = true; // [#protodoc-title: Rate Limit Quota Service (RLQS)] // The Rate Limit Quota Service (RLQS) is a Envoy global rate limiting service that allows to // delegate rate limit decisions to a remote service. The service will aggregate the usage reports // from multiple data plane instances, and distribute Rate Limit Assignments to each instance // based on its business logic. The logic is outside of the scope of the protocol API. // // The protocol is designed as a streaming-first API. It utilizes watch-like subscription model. // The data plane groups requests into Quota Buckets as directed by the filter config, // and periodically reports them to the RLQS server along with the Bucket identifier, :ref:`BucketId // `. Once RLQS server has collected enough // reports to make a decision, it'll send back the assignment with the rate limiting instructions. // // The first report sent by the data plane is interpreted by the RLQS server as a "watch" request, // indicating that the data plane instance is interested in receiving further updates for the // ``BucketId``. From then on, RLQS server may push assignments to this instance at will, even if // the instance is not sending usage reports. It's the responsibility of the RLQS server // to determine when the data plane instance didn't send ``BucketId`` reports for too long, // and to respond with the :ref:`AbandonAction // `, // indicating that the server has now stopped sending quota assignments for the ``BucketId`` bucket, // and the data plane instance should :ref:`abandon // ` // it. // // If for any reason the RLQS client doesn't receive the initial assignment for the reported bucket, // in order to prevent memory exhaustion, the data plane will limit the time such bucket // is retained. The exact time to wait for the initial assignment is chosen by the filter, // and may vary based on the implementation. // Once the duration ends, the data plane will stop reporting bucket usage, reject any enqueued // requests, and purge the bucket from the memory. Subsequent requests matched into the bucket // will re-initialize the bucket in the "no assignment" state, restarting the reports. // // Refer to Rate Limit Quota :ref:`configuration overview ` // for further details. // Defines the Rate Limit Quota Service (RLQS). service RateLimitQuotaService { // Main communication channel: the data plane sends usage reports to the RLQS server, // and the server asynchronously responding with the assignments. rpc StreamRateLimitQuotas(stream RateLimitQuotaUsageReports) returns (stream RateLimitQuotaResponse) { } } message RateLimitQuotaUsageReports { // The usage report for a bucket. // // .. note:: // Note that the first report sent for a ``BucketId`` indicates to the RLQS server that // the RLQS client is subscribing for the future assignments for this ``BucketId``. message BucketQuotaUsage { // ``BucketId`` for which request quota usage is reported. BucketId bucket_id = 1 [(validate.rules).message = {required: true}]; // Time elapsed since the last report. google.protobuf.Duration time_elapsed = 2 [(validate.rules).duration = { required: true gt {} }]; // Requests the data plane has allowed through. uint64 num_requests_allowed = 3; // Requests throttled. uint64 num_requests_denied = 4; } // All quota requests must specify the domain. This enables sharing the quota // server between different applications without fear of overlap. // E.g., "envoy". // // Should only be provided in the first report, all subsequent messages on the same // stream are considered to be in the same domain. In case the domain needs to be // changes, close the stream, and reopen a new one with the different domain. string domain = 1 [(validate.rules).string = {min_len: 1}]; // A list of quota usage reports. The list is processed by the RLQS server in the same order // it's provided by the client. repeated BucketQuotaUsage bucket_quota_usages = 2 [(validate.rules).repeated = {min_items: 1}]; } message RateLimitQuotaResponse { // Commands the data plane to apply one of the actions to the bucket with the // :ref:`bucket_id `. message BucketAction { // Quota assignment for the bucket. Configures the rate limiting strategy and the duration // for the given :ref:`bucket_id // `. // // **Applying the first assignment to the bucket** // // Once the data plane receives the ``QuotaAssignmentAction``, it must send the current usage // report for the bucket, and start rate limiting requests matched into the bucket // using the strategy configured in the :ref:`rate_limit_strategy // ` // field. The assignment becomes bucket's ``active`` assignment. // // **Expiring the assignment** // // The duration of the assignment defined in the :ref:`assignment_time_to_live // ` // field. When the duration runs off, the assignment is ``expired``, and no longer ``active``. // The data plane should stop applying the rate limiting strategy to the bucket, and transition // the bucket to the "expired assignment" state. This activates the behavior configured in the // :ref:`expired_assignment_behavior ` // field. // // **Replacing the assignment** // // * If the rate limiting strategy is different from bucket's ``active`` assignment, or // the current bucket assignment is ``expired``, the data plane must immediately // end the current assignment, report the bucket usage, and apply the new assignment. // The new assignment becomes bucket's ``active`` assignment. // * If the rate limiting strategy is the same as the bucket's ``active`` (not ``expired``) // assignment, the data plane should extend the duration of the ``active`` assignment // for the duration of the new assignment provided in the :ref:`assignment_time_to_live // ` // field. The ``active`` assignment is considered unchanged. message QuotaAssignmentAction { // A duration after which the assignment is be considered ``expired``. The process of the // expiration is described :ref:`above // `. // // * If unset, the assignment has no expiration date. // * If set to ``0``, the assignment expires immediately, forcing the client into the // :ref:`"expired assignment" // ` // state. This may be used by the RLQS server in cases when it needs clients to proactively // fall back to the pre-configured :ref:`ExpiredAssignmentBehavior // `, // f.e. before the server going into restart. // // .. attention:: // Note that :ref:`expiring // ` // the assignment is not the same as :ref:`abandoning // ` // the assignment. While expiring the assignment just transitions the bucket to // the "expired assignment" state; abandoning the assignment completely erases // the bucket from the data plane memory, and stops the usage reports. google.protobuf.Duration assignment_time_to_live = 2 [(validate.rules).duration = {gte {}}]; // Configures the local rate limiter for the request matched to the bucket. // If not set, allow all requests. type.v3.RateLimitStrategy rate_limit_strategy = 3; } // Abandon action for the bucket. Indicates that the RLQS server will no longer be // sending updates for the given :ref:`bucket_id // `. // // If no requests are reported for a bucket, after some time the server considers the bucket // inactive. The server stops tracking the bucket, and instructs the the data plane to abandon // the bucket via this message. // // **Abandoning the assignment** // // The data plane is to erase the bucket (including its usage data) from the memory. // It should stop tracking the bucket, and stop reporting its usage. This effectively resets // the data plane to the state prior to matching the first request into the bucket. // // **Restarting the subscription** // // If a new request is matched into a bucket previously abandoned, the data plane must behave // as if it has never tracked the bucket, and it's the first request matched into it: // // 1. The process of :ref:`subscription and reporting // ` // starts from the beginning. // // 2. The bucket transitions to the :ref:`"no assignment" // ` // state. // // 3. Once the new assignment is received, it's applied per // "Applying the first assignment to the bucket" section of the :ref:`QuotaAssignmentAction // `. message AbandonAction { } // ``BucketId`` for which request the action is applied. BucketId bucket_id = 1 [(validate.rules).message = {required: true}]; oneof bucket_action { option (validate.required) = true; // Apply the quota assignment to the bucket. // // Commands the data plane to apply a rate limiting strategy to the bucket. // The process of applying and expiring the rate limiting strategy is detailed in the // :ref:`QuotaAssignmentAction // ` // message. QuotaAssignmentAction quota_assignment_action = 2; // Abandon the bucket. // // Commands the data plane to abandon the bucket. // The process of abandoning the bucket is described in the :ref:`AbandonAction // ` // message. AbandonAction abandon_action = 3; } } // An ordered list of actions to be applied to the buckets. The actions are applied in the // given order, from top to bottom. repeated BucketAction bucket_action = 1 [(validate.rules).repeated = {min_items: 1}]; } // The identifier for the bucket. Used to match the bucket between the control plane (RLQS server), // and the data plane (RLQS client), f.e.: // // * the data plane sends a usage report for requests matched into the bucket with ``BucketId`` // to the control plane // * the control plane sends an assignment for the bucket with ``BucketId`` to the data plane // Bucket ID. // // Example: // // .. validated-code-block:: yaml // :type-name: envoy.service.rate_limit_quota.v3.BucketId // // bucket: // name: my_bucket // env: staging // // .. note:: // The order of ``BucketId`` keys do not matter. Buckets ``{ a: 'A', b: 'B' }`` and // ``{ b: 'B', a: 'A' }`` are identical. message BucketId { map bucket = 1 [(validate.rules).map = { min_pairs: 1 keys {string {min_len: 1}} values {string {min_len: 1}} }]; }