diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 80090b0c..9b007810 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -6,7 +6,7 @@ on: pull_request: env: - GOLANGCI_LINT_VERSION: v1.54.2 + GOLANGCI_LINT_VERSION: v1.56.2 jobs: lint: diff --git a/.github/workflows/master.yaml b/.github/workflows/master.yaml index 0977635d..34450a84 100644 --- a/.github/workflows/master.yaml +++ b/.github/workflows/master.yaml @@ -12,9 +12,9 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 15 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: actions/setup-go@v4 + - uses: actions/setup-go@v5 with: go-version-file: './go.mod' cache-dependency-path: './go.sum' @@ -40,7 +40,7 @@ jobs: comment-on-alert: true - name: Save benchmark JSON to cache - uses: actions/cache/save@v3 + uses: actions/cache/save@v4 with: path: ./cache/benchmark-data.json # Save with commit hash to avoid "cache already exists" diff --git a/.github/workflows/on-pull-request.yml b/.github/workflows/on-pull-request.yml index 76da76b0..d89825f7 100644 --- a/.github/workflows/on-pull-request.yml +++ b/.github/workflows/on-pull-request.yml @@ -4,6 +4,9 @@ on: pull_request: branches: [ master ] +env: + GOLANGCI_LINT_VERSION: v1.56.2 + jobs: test: name: test @@ -25,12 +28,12 @@ jobs: run: ./contrib/check-version.sh - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - name: Cache deps - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} @@ -40,6 +43,12 @@ jobs: - name: Install deps run: go mod download + - name: golangci-lint + uses: golangci/golangci-lint-action@v4 + with: + version: ${{ env.GOLANGCI_LINT_VERSION }} + skip-cache: true + - name: Test run: go test -v -race -p=1 -count=1 go-bench: @@ -47,12 +56,12 @@ jobs: timeout-minutes: 30 steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 # to be able to retrieve the last commit in master branch - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version-file: './go.mod' cache-dependency-path: './go.sum' @@ -71,7 +80,7 @@ jobs: - name: Get benchmark JSON from Master branch id: cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 with: path: ./cache/benchmark-data.json key: ${{ steps.get-master-branch-sha.outputs.sha }}-${{ runner.os }}-go-benchmark diff --git a/.github/workflows/on-release.yml b/.github/workflows/on-release.yml index ff5bd161..c4a19c66 100644 --- a/.github/workflows/on-release.yml +++ b/.github/workflows/on-release.yml @@ -33,7 +33,7 @@ jobs: password: ${{ github.token }} - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v5 with: tags: | ghcr.io/mailgun/gubernator:${{ github.event.release.tag_name }} diff --git a/.gitignore b/.gitignore index 976cfb4f..1e85679c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,8 @@ .vscode/ __pycache__ *.pyc -gubernator.egg-info/ .DS_Store *.iml -googleapis/ coverage.out coverage.html /gubernator diff --git a/Makefile b/Makefile index 850102d1..5267e466 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION=$(shell cat version) LDFLAGS="-X main.Version=$(VERSION)" GOLANGCI_LINT = $(GOPATH)/bin/golangci-lint -GOLANGCI_LINT_VERSION = 1.54.2 +GOLANGCI_LINT_VERSION = 1.56.2 .PHONY: help help: diff --git a/README.md b/README.md index e60b5a9d..8c457727 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ rate_limits: limit: 100 # The duration of the rate limit in milliseconds duration: 1000 - # The algorithm used to calculate the rate limit + # The algorithm used to calculate the rate limit # 0 = Token Bucket # 1 = Leaky Bucket algorithm: 0 @@ -166,7 +166,7 @@ Given the following `Duration` values * 3 = Weeks * 4 = Months * 5 = Years - + Examples when using `Behavior = DURATION_IS_GREGORIAN` * If `Duration = 2` (Days) then the rate limit will reset to `Current = 0` at the end of the current day the rate limit was created. * If `Duration = 0` (Minutes) then the rate limit will reset to `Current = 0` at the end of the minute the rate limit was created. @@ -178,6 +178,31 @@ This will reset the rate limit as if created new on first use. When using Reset Remaining, the `Hits` field should be 0. +## Drain Over Limit Behavior +Users may add behavior `Behavior_DRAIN_OVER_LIMIT` to the rate check request. +A `GetRateLimits` call drains the remaining counter on first over limit event. +Then, successive `GetRateLimits` calls will return zero remaining counter and +not any residual value. This behavior works best with token bucket algorithm +because the `Remaining` counter will stay zero after an over limit until reset +time, whereas leaky bucket algorithm will immediately update `Remaining` to a +non-zero value. + +This facilitates scenarios that require an over limit event to stay over limit +until the rate limit resets. This approach is necessary if a process must make +two rate checks, before and after a process, and the `Hit` amount is not known +until after the process. + +- Before process: Call `GetRateLimits` with `Hits=0` to check the value of + `Remaining` counter. If `Remaining` is zero, it's known + that the rate limit is depleted and the process can be aborted. +- After process: Call `GetRateLimits` with a user specified `Hits` value. If + the call returns over limit, the process cannot be aborted because it had + already completed. Using `DRAIN_OVER_LIMIT` behavior, the `Remaining` count + will be drained to zero. + +Once an over limit occurs in the "After" step, successive processes will detect +the over limit state in the "Before" step. + ## Gubernator as a library If you are using golang, you can use Gubernator as a library. This is useful if you wish to implement a rate limit service with your own company specific model @@ -346,4 +371,4 @@ Gubernator publishes Prometheus metrics for realtime monitoring. See [prometheus.md](docs/prometheus.md) for details. ## OpenTelemetry Tracing (OTEL) -Gubernator supports OpenTelemetry. See [tracing.md](docs/tracing.md) for details. \ No newline at end of file +Gubernator supports OpenTelemetry. See [tracing.md](docs/tracing.md) for details. diff --git a/algorithms.go b/algorithms.go index c7e6315c..1fb8f9dd 100644 --- a/algorithms.go +++ b/algorithms.go @@ -181,6 +181,11 @@ func tokenBucket(ctx context.Context, s Store, c Cache, r *RateLimitReq) (resp * trace.SpanFromContext(ctx).AddEvent("Over the limit") metricOverLimitCounter.Add(1) rl.Status = Status_OVER_LIMIT + if HasBehavior(r.Behavior, Behavior_DRAIN_OVER_LIMIT) { + // DRAIN_OVER_LIMIT behavior drains the remaining counter. + t.Remaining = 0 + rl.Remaining = 0 + } return rl, nil } @@ -394,6 +399,11 @@ func leakyBucket(ctx context.Context, s Store, c Cache, r *RateLimitReq) (resp * if r.Hits > int64(b.Remaining) { metricOverLimitCounter.Add(1) rl.Status = Status_OVER_LIMIT + if HasBehavior(r.Behavior, Behavior_DRAIN_OVER_LIMIT) { + // DRAIN_OVER_LIMIT behavior drains the remaining counter. + b.Remaining = 0 + rl.Remaining = 0 + } return rl, nil } diff --git a/buf.gen.yaml b/buf.gen.yaml old mode 100644 new mode 100755 diff --git a/cmd/gubernator-cli/main.go b/cmd/gubernator-cli/main.go index 4e0a96b2..f75753bd 100644 --- a/cmd/gubernator-cli/main.go +++ b/cmd/gubernator-cli/main.go @@ -89,7 +89,11 @@ func main() { cmdLine := strings.Join(os.Args[1:], " ") logrus.WithContext(ctx).Info("Command line: " + cmdLine) - conf, err := guber.SetupDaemonConfig(log, configFile) + configFileReader, err := os.Open(configFile) + if err != nil { + return fmt.Errorf("while opening config file: %s", err) + } + conf, err := guber.SetupDaemonConfig(log, configFileReader) if err != nil { return err } diff --git a/cmd/gubernator/main.go b/cmd/gubernator/main.go index 2d3d6fe8..d556ba88 100644 --- a/cmd/gubernator/main.go +++ b/cmd/gubernator/main.go @@ -74,7 +74,11 @@ func main() { } // Read our config from the environment or optional environment config file - conf, err := gubernator.SetupDaemonConfig(logrus.StandardLogger(), configFile) + configFileReader, err := os.Open(configFile) + if err != nil { + log.WithError(err).Fatal("while opening config file") + } + conf, err := gubernator.SetupDaemonConfig(logrus.StandardLogger(), configFileReader) checkErr(err, "while getting config") ctx, cancel := context.WithTimeout(ctx, clock.Second*10) diff --git a/config.go b/config.go index c46e2fa3..122ffa22 100644 --- a/config.go +++ b/config.go @@ -62,7 +62,7 @@ type BehaviorConfig struct { GlobalTimeout time.Duration // The max number of global updates we can batch into a single peer request GlobalBatchLimit int - // ForceGlobal forces global mode on all rate limit checks. + // ForceGlobal forces global behavior on all rate limit checks. ForceGlobal bool // Number of concurrent requests that will be made to peers. Defaults to 100 @@ -263,9 +263,9 @@ func (d *DaemonConfig) ServerTLS() *tls.Config { return nil } -// SetupDaemonConfig returns a DaemonConfig object as configured by reading the provided config file -// and environment. -func SetupDaemonConfig(logger *logrus.Logger, configFile string) (DaemonConfig, error) { +// SetupDaemonConfig returns a DaemonConfig object that is the result of merging the lines +// in the provided configFile and the environment variables. See `example.conf` for all available config options and their descriptions. +func SetupDaemonConfig(logger *logrus.Logger, configFile io.Reader) (DaemonConfig, error) { log := logrus.NewEntry(logger) var conf DaemonConfig var logLevel string @@ -273,7 +273,7 @@ func SetupDaemonConfig(logger *logrus.Logger, configFile string) (DaemonConfig, var advAddr, advPort string var err error - if configFile != "" { + if configFile != nil { log.Infof("Loading env config: %s", configFile) if err := fromEnvFile(log, configFile); err != nil { return conf, err @@ -628,15 +628,10 @@ func getEnvSlice(name string) []string { return strings.Split(v, ",") } -// Take values from a file in the format `GUBER_CONF_ITEM=my-value` and put them into the environment -// lines that begin with `#` are ignored -func fromEnvFile(log logrus.FieldLogger, configFile string) error { - fd, err := os.Open(configFile) - if err != nil { - return fmt.Errorf("while opening config file: %s", err) - } - - contents, err := io.ReadAll(fd) +// Take values from a file in the format `GUBER_CONF_ITEM=my-value` and sets them as environment variables. +// Lines that begin with `#` are ignored +func fromEnvFile(log logrus.FieldLogger, configFile io.Reader) error { + contents, err := io.ReadAll(configFile) if err != nil { return fmt.Errorf("while reading config file '%s': %s", configFile, err) } diff --git a/config_test.go b/config_test.go new file mode 100644 index 00000000..290b329f --- /dev/null +++ b/config_test.go @@ -0,0 +1,40 @@ +package gubernator + +import ( + "fmt" + "os" + "strings" + "testing" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" +) + +func TestParsesGrpcAddress(t *testing.T) { + os.Clearenv() + s := ` +# a comment +GUBER_GRPC_ADDRESS=10.10.10.10:9000` + daemonConfig, err := SetupDaemonConfig(logrus.StandardLogger(), strings.NewReader(s)) + require.NoError(t, err) + require.Equal(t, "10.10.10.10:9000", daemonConfig.GRPCListenAddress) + require.NotEmpty(t, daemonConfig.InstanceID) +} + +func TestDefaultGrpcAddress(t *testing.T) { + os.Clearenv() + s := ` +# a comment` + daemonConfig, err := SetupDaemonConfig(logrus.StandardLogger(), strings.NewReader(s)) + require.NoError(t, err) + require.Equal(t, fmt.Sprintf("%s:81", LocalHost()), daemonConfig.GRPCListenAddress) + require.NotEmpty(t, daemonConfig.InstanceID) +} + +func TestDefaultInstanceId(t *testing.T) { + os.Clearenv() + s := `` + daemonConfig, err := SetupDaemonConfig(logrus.StandardLogger(), strings.NewReader(s)) + require.NoError(t, err) + require.NotEmpty(t, daemonConfig.InstanceID) +} diff --git a/contrib/charts/gubernator/Chart.yaml b/contrib/charts/gubernator/Chart.yaml index c1fbe823..27692748 100644 --- a/contrib/charts/gubernator/Chart.yaml +++ b/contrib/charts/gubernator/Chart.yaml @@ -4,6 +4,6 @@ description: A Helm Chart for gubernator type: application -version: 2.2.1 +version: 2.3.2 -appVersion: 2.2.1 +appVersion: 2.3.2 diff --git a/functional_test.go b/functional_test.go index 6225fc3e..4abd2e25 100644 --- a/functional_test.go +++ b/functional_test.go @@ -37,6 +37,14 @@ import ( json "google.golang.org/protobuf/encoding/protojson" ) +var algos = []struct { + Name string + Algorithm guber.Algorithm +}{ + {Name: "Token bucket", Algorithm: guber.Algorithm_TOKEN_BUCKET}, + {Name: "Leaky bucket", Algorithm: guber.Algorithm_LEAKY_BUCKET}, +} + // Setup and shutdown the mock gubernator cluster for the entire test suite func TestMain(m *testing.M) { if err := cluster.StartWith([]guber.PeerInfo{ @@ -363,6 +371,72 @@ func TestTokenBucketNegativeHits(t *testing.T) { } } +func TestDrainOverLimit(t *testing.T) { + defer clock.Freeze(clock.Now()).Unfreeze() + client, errs := guber.DialV1Server(cluster.PeerAt(0).GRPCAddress, nil) + require.Nil(t, errs) + + tests := []struct { + Name string + Hits int64 + Remaining int64 + Status guber.Status + }{ + { + Name: "check remaining before hit", + Hits: 0, + Remaining: 10, + Status: guber.Status_UNDER_LIMIT, + }, { + Name: "first hit", + Hits: 1, + Remaining: 9, + Status: guber.Status_UNDER_LIMIT, + }, { + Name: "over limit hit", + Hits: 100, + Remaining: 0, + Status: guber.Status_OVER_LIMIT, + }, { + Name: "check remaining", + Hits: 0, + Remaining: 0, + Status: guber.Status_UNDER_LIMIT, + }, + } + + for idx, algoCase := range algos { + t.Run(algoCase.Name, func(t *testing.T) { + for _, test := range tests { + ctx := context.Background() + t.Run(test.Name, func(t *testing.T) { + resp, err := client.GetRateLimits(ctx, &guber.GetRateLimitsReq{ + Requests: []*guber.RateLimitReq{ + { + Name: "test_drain_over_limit", + UniqueKey: fmt.Sprintf("account:1234:%d", idx), + Algorithm: algoCase.Algorithm, + Behavior: guber.Behavior_DRAIN_OVER_LIMIT, + Duration: guber.Second * 30, + Hits: test.Hits, + Limit: 10, + }, + }, + }) + require.NoError(t, err) + require.Len(t, resp.Responses, 1) + + rl := resp.Responses[0] + assert.Equal(t, test.Status, rl.Status) + assert.Equal(t, test.Remaining, rl.Remaining) + assert.Equal(t, int64(10), rl.Limit) + assert.NotZero(t, rl.ResetTime) + }) + } + }) + } +} + func TestLeakyBucket(t *testing.T) { defer clock.Freeze(clock.Now()).Unfreeze() diff --git a/go.mod b/go.mod index 0f5275c6..e32b4cd3 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 github.com/hashicorp/memberlist v0.5.0 github.com/mailgun/errors v0.1.5 - github.com/mailgun/holster/v4 v4.16.2-0.20231121154636-69040cb71a3b + github.com/mailgun/holster/v4 v4.16.3 github.com/miekg/dns v1.1.50 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.13.0 @@ -26,7 +26,7 @@ require ( golang.org/x/time v0.3.0 google.golang.org/genproto/googleapis/api v0.0.0-20231016165738-49dd2c1f3d0b google.golang.org/grpc v1.59.0 - google.golang.org/protobuf v1.31.0 + google.golang.org/protobuf v1.32.0 k8s.io/api v0.23.3 k8s.io/apimachinery v0.23.3 k8s.io/client-go v0.23.3 diff --git a/go.sum b/go.sum index 3ff2e93e..9b2e3287 100644 --- a/go.sum +++ b/go.sum @@ -291,8 +291,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailgun/errors v0.1.5 h1:riRpZqfUKTdc8saXvoEg2tYkbRyZESU1KvQ3UxPbdus= github.com/mailgun/errors v0.1.5/go.mod h1:lw+Nh4r/aoUTz6uK915FdfZJo3yq60gPiflFHNpK4NQ= -github.com/mailgun/holster/v4 v4.16.2-0.20231121154636-69040cb71a3b h1:ohMhrwmmA4JbXNukFpriztFWEVLlMuL90Cssg2Vl2TU= -github.com/mailgun/holster/v4 v4.16.2-0.20231121154636-69040cb71a3b/go.mod h1:phAg61z7LZ1PBfedyt2GXkGSlHhuVKK9AcVJO+Cm0/U= +github.com/mailgun/holster/v4 v4.16.3 h1:YMTkDoaFV83ViSaFuAfiyIvzrHJD1UNw7RjNv6J3Kfg= +github.com/mailgun/holster/v4 v4.16.3/go.mod h1:phAg61z7LZ1PBfedyt2GXkGSlHhuVKK9AcVJO+Cm0/U= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -839,8 +839,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/gubernator.pb.go b/gubernator.pb.go index 5756c5ce..310635f6 100644 --- a/gubernator.pb.go +++ b/gubernator.pb.go @@ -147,6 +147,10 @@ const ( // 'member-list' peer discovery. Also requires GUBER_DATA_CENTER to be set to different values on at // least 2 instances of Gubernator. Behavior_MULTI_REGION Behavior = 16 + // A GetRateLimits call drains the remaining counter on first over limit + // event. Then, successive GetRateLimits calls will return zero remaining + // counter and not any residual value. + Behavior_DRAIN_OVER_LIMIT Behavior = 32 ) // Enum value maps for Behavior. @@ -158,6 +162,7 @@ var ( 4: "DURATION_IS_GREGORIAN", 8: "RESET_REMAINING", 16: "MULTI_REGION", + 32: "DRAIN_OVER_LIMIT", } Behavior_value = map[string]int32{ "BATCHING": 0, @@ -166,6 +171,7 @@ var ( "DURATION_IS_GREGORIAN": 4, "RESET_REMAINING": 8, "MULTI_REGION": 16, + "DRAIN_OVER_LIMIT": 32, } ) @@ -474,7 +480,7 @@ type RateLimitResp struct { Status Status `protobuf:"varint,1,opt,name=status,proto3,enum=pb.gubernator.Status" json:"status,omitempty"` // The currently configured request limit (Identical to [[RateLimitReq.limit]]). Limit int64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` - // This is the number of requests remaining after the rate limiting is applied + // This is the number of requests remaining before the rate limit is hit but after subtracting the hits from the current request Remaining int64 `protobuf:"varint,3,opt,name=remaining,proto3" json:"remaining,omitempty"` // This is the time when the rate limit span will be reset, provided as a unix timestamp in milliseconds. ResetTime int64 `protobuf:"varint,4,opt,name=reset_time,json=resetTime,proto3" json:"reset_time,omitempty"` @@ -733,34 +739,35 @@ var file_gubernator_proto_rawDesc = []byte{ 0x75, 0x6e, 0x74, 0x2a, 0x2f, 0x0a, 0x09, 0x41, 0x6c, 0x67, 0x6f, 0x72, 0x69, 0x74, 0x68, 0x6d, 0x12, 0x10, 0x0a, 0x0c, 0x54, 0x4f, 0x4b, 0x45, 0x4e, 0x5f, 0x42, 0x55, 0x43, 0x4b, 0x45, 0x54, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x4c, 0x45, 0x41, 0x4b, 0x59, 0x5f, 0x42, 0x55, 0x43, 0x4b, - 0x45, 0x54, 0x10, 0x01, 0x2a, 0x77, 0x0a, 0x08, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, 0x72, - 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x41, 0x54, 0x43, 0x48, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, 0x0f, - 0x0a, 0x0b, 0x4e, 0x4f, 0x5f, 0x42, 0x41, 0x54, 0x43, 0x48, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, - 0x0a, 0x0a, 0x06, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, 0x44, - 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x53, 0x5f, 0x47, 0x52, 0x45, 0x47, 0x4f, - 0x52, 0x49, 0x41, 0x4e, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x45, 0x53, 0x45, 0x54, 0x5f, - 0x52, 0x45, 0x4d, 0x41, 0x49, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x08, 0x12, 0x10, 0x0a, 0x0c, 0x4d, - 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x52, 0x45, 0x47, 0x49, 0x4f, 0x4e, 0x10, 0x10, 0x2a, 0x29, 0x0a, - 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x44, 0x45, 0x52, - 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x4f, 0x56, 0x45, 0x52, - 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x10, 0x01, 0x32, 0xdd, 0x01, 0x0a, 0x02, 0x56, 0x31, 0x12, - 0x70, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, - 0x12, 0x1f, 0x2e, 0x70, 0x62, 0x2e, 0x67, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x6f, 0x72, - 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x1a, 0x20, 0x2e, 0x70, 0x62, 0x2e, 0x67, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x6f, - 0x72, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, - 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, - 0x73, 0x12, 0x65, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x12, 0x1d, 0x2e, 0x70, 0x62, 0x2e, 0x67, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x6f, 0x72, - 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x1a, - 0x1e, 0x2e, 0x70, 0x62, 0x2e, 0x67, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x2e, - 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x22, - 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, 0x2f, 0x76, 0x31, 0x2f, 0x48, 0x65, 0x61, - 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, 0x22, 0x5a, 0x1d, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x61, 0x69, 0x6c, 0x67, 0x75, 0x6e, 0x2f, 0x67, - 0x75, 0x62, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x80, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x45, 0x54, 0x10, 0x01, 0x2a, 0x8d, 0x01, 0x0a, 0x08, 0x42, 0x65, 0x68, 0x61, 0x76, 0x69, 0x6f, + 0x72, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x41, 0x54, 0x43, 0x48, 0x49, 0x4e, 0x47, 0x10, 0x00, 0x12, + 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x5f, 0x42, 0x41, 0x54, 0x43, 0x48, 0x49, 0x4e, 0x47, 0x10, 0x01, + 0x12, 0x0a, 0x0a, 0x06, 0x47, 0x4c, 0x4f, 0x42, 0x41, 0x4c, 0x10, 0x02, 0x12, 0x19, 0x0a, 0x15, + 0x44, 0x55, 0x52, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x49, 0x53, 0x5f, 0x47, 0x52, 0x45, 0x47, + 0x4f, 0x52, 0x49, 0x41, 0x4e, 0x10, 0x04, 0x12, 0x13, 0x0a, 0x0f, 0x52, 0x45, 0x53, 0x45, 0x54, + 0x5f, 0x52, 0x45, 0x4d, 0x41, 0x49, 0x4e, 0x49, 0x4e, 0x47, 0x10, 0x08, 0x12, 0x10, 0x0a, 0x0c, + 0x4d, 0x55, 0x4c, 0x54, 0x49, 0x5f, 0x52, 0x45, 0x47, 0x49, 0x4f, 0x4e, 0x10, 0x10, 0x12, 0x14, + 0x0a, 0x10, 0x44, 0x52, 0x41, 0x49, 0x4e, 0x5f, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x4c, 0x49, 0x4d, + 0x49, 0x54, 0x10, 0x20, 0x2a, 0x29, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0f, + 0x0a, 0x0b, 0x55, 0x4e, 0x44, 0x45, 0x52, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x10, 0x00, 0x12, + 0x0e, 0x0a, 0x0a, 0x4f, 0x56, 0x45, 0x52, 0x5f, 0x4c, 0x49, 0x4d, 0x49, 0x54, 0x10, 0x01, 0x32, + 0xdd, 0x01, 0x0a, 0x02, 0x56, 0x31, 0x12, 0x70, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x52, 0x61, 0x74, + 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x70, 0x62, 0x2e, 0x67, 0x75, 0x62, + 0x65, 0x72, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, 0x4c, + 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x71, 0x1a, 0x20, 0x2e, 0x70, 0x62, 0x2e, 0x67, 0x75, + 0x62, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x61, 0x74, 0x65, + 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x1c, 0x82, 0xd3, 0xe4, 0x93, + 0x02, 0x16, 0x3a, 0x01, 0x2a, 0x22, 0x11, 0x2f, 0x76, 0x31, 0x2f, 0x47, 0x65, 0x74, 0x52, 0x61, + 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x73, 0x12, 0x65, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x6c, + 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x1d, 0x2e, 0x70, 0x62, 0x2e, 0x67, 0x75, 0x62, + 0x65, 0x72, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x70, 0x62, 0x2e, 0x67, 0x75, 0x62, 0x65, + 0x72, 0x6e, 0x61, 0x74, 0x6f, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x22, 0x17, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x11, 0x12, 0x0f, + 0x2f, 0x76, 0x31, 0x2f, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x42, + 0x22, 0x5a, 0x1d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6d, 0x61, + 0x69, 0x6c, 0x67, 0x75, 0x6e, 0x2f, 0x67, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x61, 0x74, 0x6f, 0x72, + 0x80, 0x01, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/gubernator.proto b/gubernator.proto index 4b52e6ad..fea99a22 100644 --- a/gubernator.proto +++ b/gubernator.proto @@ -34,7 +34,6 @@ service V1 { }; } - // This method is for round trip benchmarking and can be used by // the client to determine connectivity to the server rpc HealthCheck (HealthCheckReq) returns (HealthCheckResp) { @@ -127,6 +126,11 @@ enum Behavior { // least 2 instances of Gubernator. MULTI_REGION = 16; + // A GetRateLimits call drains the remaining counter on first over limit + // event. Then, successive GetRateLimits calls will return zero remaining + // counter and not any residual value. + DRAIN_OVER_LIMIT = 32; + // TODO: Add support for LOCAL. Which would force the rate limit to be handled by the local instance } diff --git a/python/gubernator/gubernator_pb2.py b/python/gubernator/gubernator_pb2.py index 51749b92..17351bb6 100644 --- a/python/gubernator/gubernator_pb2.py +++ b/python/gubernator/gubernator_pb2.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: gubernator.proto -# Protobuf Python Version: 4.25.2 +# Protobuf Python Version: 4.25.3 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool @@ -15,7 +15,7 @@ from google.api import annotations_pb2 as google_dot_api_dot_annotations__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10gubernator.proto\x12\rpb.gubernator\x1a\x1cgoogle/api/annotations.proto\"K\n\x10GetRateLimitsReq\x12\x37\n\x08requests\x18\x01 \x03(\x0b\x32\x1b.pb.gubernator.RateLimitReqR\x08requests\"O\n\x11GetRateLimitsResp\x12:\n\tresponses\x18\x01 \x03(\x0b\x32\x1c.pb.gubernator.RateLimitRespR\tresponses\"\x8e\x03\n\x0cRateLimitReq\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n\nunique_key\x18\x02 \x01(\tR\tuniqueKey\x12\x12\n\x04hits\x18\x03 \x01(\x03R\x04hits\x12\x14\n\x05limit\x18\x04 \x01(\x03R\x05limit\x12\x1a\n\x08\x64uration\x18\x05 \x01(\x03R\x08\x64uration\x12\x36\n\talgorithm\x18\x06 \x01(\x0e\x32\x18.pb.gubernator.AlgorithmR\talgorithm\x12\x33\n\x08\x62\x65havior\x18\x07 \x01(\x0e\x32\x17.pb.gubernator.BehaviorR\x08\x62\x65havior\x12\x14\n\x05\x62urst\x18\x08 \x01(\x03R\x05\x62urst\x12\x45\n\x08metadata\x18\t \x03(\x0b\x32).pb.gubernator.RateLimitReq.MetadataEntryR\x08metadata\x1a;\n\rMetadataEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xac\x02\n\rRateLimitResp\x12-\n\x06status\x18\x01 \x01(\x0e\x32\x15.pb.gubernator.StatusR\x06status\x12\x14\n\x05limit\x18\x02 \x01(\x03R\x05limit\x12\x1c\n\tremaining\x18\x03 \x01(\x03R\tremaining\x12\x1d\n\nreset_time\x18\x04 \x01(\x03R\tresetTime\x12\x14\n\x05\x65rror\x18\x05 \x01(\tR\x05\x65rror\x12\x46\n\x08metadata\x18\x06 \x03(\x0b\x32*.pb.gubernator.RateLimitResp.MetadataEntryR\x08metadata\x1a;\n\rMetadataEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\x10\n\x0eHealthCheckReq\"b\n\x0fHealthCheckResp\x12\x16\n\x06status\x18\x01 \x01(\tR\x06status\x12\x18\n\x07message\x18\x02 \x01(\tR\x07message\x12\x1d\n\npeer_count\x18\x03 \x01(\x05R\tpeerCount*/\n\tAlgorithm\x12\x10\n\x0cTOKEN_BUCKET\x10\x00\x12\x10\n\x0cLEAKY_BUCKET\x10\x01*w\n\x08\x42\x65havior\x12\x0c\n\x08\x42\x41TCHING\x10\x00\x12\x0f\n\x0bNO_BATCHING\x10\x01\x12\n\n\x06GLOBAL\x10\x02\x12\x19\n\x15\x44URATION_IS_GREGORIAN\x10\x04\x12\x13\n\x0fRESET_REMAINING\x10\x08\x12\x10\n\x0cMULTI_REGION\x10\x10*)\n\x06Status\x12\x0f\n\x0bUNDER_LIMIT\x10\x00\x12\x0e\n\nOVER_LIMIT\x10\x01\x32\xdd\x01\n\x02V1\x12p\n\rGetRateLimits\x12\x1f.pb.gubernator.GetRateLimitsReq\x1a .pb.gubernator.GetRateLimitsResp\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/GetRateLimits:\x01*\x12\x65\n\x0bHealthCheck\x12\x1d.pb.gubernator.HealthCheckReq\x1a\x1e.pb.gubernator.HealthCheckResp\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/HealthCheckB\"Z\x1dgithub.com/mailgun/gubernator\x80\x01\x01\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x10gubernator.proto\x12\rpb.gubernator\x1a\x1cgoogle/api/annotations.proto\"K\n\x10GetRateLimitsReq\x12\x37\n\x08requests\x18\x01 \x03(\x0b\x32\x1b.pb.gubernator.RateLimitReqR\x08requests\"O\n\x11GetRateLimitsResp\x12:\n\tresponses\x18\x01 \x03(\x0b\x32\x1c.pb.gubernator.RateLimitRespR\tresponses\"\x8e\x03\n\x0cRateLimitReq\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12\x1d\n\nunique_key\x18\x02 \x01(\tR\tuniqueKey\x12\x12\n\x04hits\x18\x03 \x01(\x03R\x04hits\x12\x14\n\x05limit\x18\x04 \x01(\x03R\x05limit\x12\x1a\n\x08\x64uration\x18\x05 \x01(\x03R\x08\x64uration\x12\x36\n\talgorithm\x18\x06 \x01(\x0e\x32\x18.pb.gubernator.AlgorithmR\talgorithm\x12\x33\n\x08\x62\x65havior\x18\x07 \x01(\x0e\x32\x17.pb.gubernator.BehaviorR\x08\x62\x65havior\x12\x14\n\x05\x62urst\x18\x08 \x01(\x03R\x05\x62urst\x12\x45\n\x08metadata\x18\t \x03(\x0b\x32).pb.gubernator.RateLimitReq.MetadataEntryR\x08metadata\x1a;\n\rMetadataEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\xac\x02\n\rRateLimitResp\x12-\n\x06status\x18\x01 \x01(\x0e\x32\x15.pb.gubernator.StatusR\x06status\x12\x14\n\x05limit\x18\x02 \x01(\x03R\x05limit\x12\x1c\n\tremaining\x18\x03 \x01(\x03R\tremaining\x12\x1d\n\nreset_time\x18\x04 \x01(\x03R\tresetTime\x12\x14\n\x05\x65rror\x18\x05 \x01(\tR\x05\x65rror\x12\x46\n\x08metadata\x18\x06 \x03(\x0b\x32*.pb.gubernator.RateLimitResp.MetadataEntryR\x08metadata\x1a;\n\rMetadataEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"\x10\n\x0eHealthCheckReq\"b\n\x0fHealthCheckResp\x12\x16\n\x06status\x18\x01 \x01(\tR\x06status\x12\x18\n\x07message\x18\x02 \x01(\tR\x07message\x12\x1d\n\npeer_count\x18\x03 \x01(\x05R\tpeerCount*/\n\tAlgorithm\x12\x10\n\x0cTOKEN_BUCKET\x10\x00\x12\x10\n\x0cLEAKY_BUCKET\x10\x01*\x8d\x01\n\x08\x42\x65havior\x12\x0c\n\x08\x42\x41TCHING\x10\x00\x12\x0f\n\x0bNO_BATCHING\x10\x01\x12\n\n\x06GLOBAL\x10\x02\x12\x19\n\x15\x44URATION_IS_GREGORIAN\x10\x04\x12\x13\n\x0fRESET_REMAINING\x10\x08\x12\x10\n\x0cMULTI_REGION\x10\x10\x12\x14\n\x10\x44RAIN_OVER_LIMIT\x10 *)\n\x06Status\x12\x0f\n\x0bUNDER_LIMIT\x10\x00\x12\x0e\n\nOVER_LIMIT\x10\x01\x32\xdd\x01\n\x02V1\x12p\n\rGetRateLimits\x12\x1f.pb.gubernator.GetRateLimitsReq\x1a .pb.gubernator.GetRateLimitsResp\"\x1c\x82\xd3\xe4\x93\x02\x16\"\x11/v1/GetRateLimits:\x01*\x12\x65\n\x0bHealthCheck\x12\x1d.pb.gubernator.HealthCheckReq\x1a\x1e.pb.gubernator.HealthCheckResp\"\x17\x82\xd3\xe4\x93\x02\x11\x12\x0f/v1/HealthCheckB\"Z\x1dgithub.com/mailgun/gubernator\x80\x01\x01\x62\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -33,10 +33,10 @@ _globals['_V1'].methods_by_name['HealthCheck']._serialized_options = b'\202\323\344\223\002\021\022\017/v1/HealthCheck' _globals['_ALGORITHM']._serialized_start=1045 _globals['_ALGORITHM']._serialized_end=1092 - _globals['_BEHAVIOR']._serialized_start=1094 - _globals['_BEHAVIOR']._serialized_end=1213 - _globals['_STATUS']._serialized_start=1215 - _globals['_STATUS']._serialized_end=1256 + _globals['_BEHAVIOR']._serialized_start=1095 + _globals['_BEHAVIOR']._serialized_end=1236 + _globals['_STATUS']._serialized_start=1238 + _globals['_STATUS']._serialized_end=1279 _globals['_GETRATELIMITSREQ']._serialized_start=65 _globals['_GETRATELIMITSREQ']._serialized_end=140 _globals['_GETRATELIMITSRESP']._serialized_start=142 @@ -53,6 +53,6 @@ _globals['_HEALTHCHECKREQ']._serialized_end=943 _globals['_HEALTHCHECKRESP']._serialized_start=945 _globals['_HEALTHCHECKRESP']._serialized_end=1043 - _globals['_V1']._serialized_start=1259 - _globals['_V1']._serialized_end=1480 + _globals['_V1']._serialized_start=1282 + _globals['_V1']._serialized_end=1503 # @@protoc_insertion_point(module_scope) diff --git a/python/gubernator/peers_pb2.py b/python/gubernator/peers_pb2.py index 116ee6eb..b1451c7a 100644 --- a/python/gubernator/peers_pb2.py +++ b/python/gubernator/peers_pb2.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: peers.proto -# Protobuf Python Version: 4.25.2 +# Protobuf Python Version: 4.25.3 """Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool diff --git a/python/requirements-py2.txt b/python/requirements-py2.txt index 7beae473..bd761a72 100644 --- a/python/requirements-py2.txt +++ b/python/requirements-py2.txt @@ -4,7 +4,7 @@ enum34==1.1.6 funcsigs==1.0.2 futures==3.2.0 googleapis-common-protos==1.5.8 -grpcio==1.53.0 +grpcio==1.53.2 more-itertools==5.0.0 pathlib2==2.3.3 pluggy==0.8.1 diff --git a/python/requirements-py3.txt b/python/requirements-py3.txt index 03e84cbf..00d54ada 100644 --- a/python/requirements-py3.txt +++ b/python/requirements-py3.txt @@ -1,7 +1,7 @@ atomicwrites==1.3.0 attrs==18.2.0 googleapis-common-protos==1.5.8 -grpcio==1.53.0 +grpcio==1.53.2 grpcio-tools==1.19.0 more-itertools==6.0.0 pluggy==0.8.1 diff --git a/version b/version index 7fe52d36..f706a60d 100644 --- a/version +++ b/version @@ -1 +1 @@ -v2.2.1 +v2.3.2