• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-server / 1550767116

19 Nov 2024 11:58AM UTC coverage: 72.563% (-0.2%) from 72.771%
1550767116

Pull #202

gitlab-ci

alfrunes
test: Fix deviceauth.cache tests after changing client initialization

Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #202: MEN-7733: Rate limits for devices APIs

4174 of 6072 branches covered (68.74%)

Branch coverage included in aggregate %.

135 of 380 new or added lines in 8 files covered. (35.53%)

84 existing lines in 3 files now uncovered.

42593 of 58378 relevant lines covered (72.96%)

15.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/backend/pkg/redis/ratelimit.go
1
package redis
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "time"
8

9
        "github.com/redis/go-redis/v9"
10

11
        "github.com/mendersoftware/mender-server/pkg/rate"
12
)
13

14
func NewFixedWindowRateLimiter(
15
        client Client,
16
        paramsFromContext RatelimitParamsFunc,
NEW
17
) rate.Limiter {
×
NEW
18
        return &fixedWindowRatelimiter{
×
NEW
19
                client:     client,
×
NEW
20
                paramsFunc: paramsFromContext,
×
NEW
21
                nowFunc:    time.Now,
×
NEW
22
        }
×
NEW
23
}
×
24

25
type RatelimitParams struct {
26
        Burst     uint64
27
        Interval  time.Duration
28
        KeyPrefix string
29
}
30

31
type RatelimitParamsFunc func(context.Context) (*RatelimitParams, error)
32

NEW
33
func FixedRatelimitParams(params RatelimitParams) RatelimitParamsFunc {
×
NEW
34
        return func(ctx context.Context) (*RatelimitParams, error) {
×
NEW
35
                return &params, nil
×
NEW
36
        }
×
37
}
38

39
type fixedWindowRatelimiter struct {
40
        client     Client
41
        paramsFunc RatelimitParamsFunc
42
        nowFunc    func() time.Time
43
}
44

45
type simpleReservation struct {
46
        ok     bool
47
        tokens uint64
48
        delay  time.Duration
49
}
50

NEW
51
func (r *simpleReservation) OK() bool {
×
NEW
52
        return r.ok
×
NEW
53
}
×
54

NEW
55
func (r *simpleReservation) Delay() time.Duration {
×
NEW
56
        return r.delay
×
NEW
57
}
×
58

NEW
59
func (r *simpleReservation) Tokens() uint64 {
×
NEW
60
        return r.tokens
×
NEW
61
}
×
62

NEW
63
func epoch(t time.Time, interval time.Duration) int64 {
×
NEW
64
        return t.UnixMilli() / interval.Milliseconds()
×
NEW
65
}
×
66

NEW
67
func fixedWindowKey(prefix string, epoch int64) string {
×
NEW
68
        if prefix == "" {
×
NEW
69
                prefix = "ratelimit"
×
NEW
70
        }
×
NEW
71
        return fmt.Sprintf("%s:e:%d:c", prefix, epoch)
×
72
}
73

NEW
74
func (rl *fixedWindowRatelimiter) Reserve(ctx context.Context) (rate.Reservation, error) {
×
NEW
75
        now := rl.nowFunc()
×
NEW
76
        params, err := rl.paramsFunc(ctx)
×
NEW
77
        if err != nil {
×
NEW
78
                return nil, err
×
NEW
79
        }
×
NEW
80
        epoch := epoch(now, params.Interval)
×
NEW
81
        key := fixedWindowKey(params.KeyPrefix, epoch)
×
NEW
82
        count := uint64(1)
×
NEW
83

×
NEW
84
        err = rl.client.SetArgs(ctx, key, count, redis.SetArgs{
×
NEW
85
                TTL:  params.Interval,
×
NEW
86
                Mode: `NX`,
×
NEW
87
        }).Err()
×
NEW
88
        if errors.Is(err, redis.Nil) {
×
NEW
89
                count, err = rl.client.Incr(ctx, key).Uint64()
×
NEW
90
        }
×
NEW
91
        if err != nil {
×
NEW
92
                return nil, fmt.Errorf("redis: error computing rate limit: %w", err)
×
NEW
93
        }
×
NEW
94
        if count <= params.Burst {
×
NEW
95
                return &simpleReservation{
×
NEW
96
                        delay:  0,
×
NEW
97
                        ok:     true,
×
NEW
98
                        tokens: params.Burst - count,
×
NEW
99
                }, nil
×
NEW
100
        }
×
NEW
101
        return &simpleReservation{
×
NEW
102
                delay: now.Sub(time.UnixMilli((epoch + 1) *
×
NEW
103
                        params.Interval.Milliseconds())),
×
NEW
104
                ok:     false,
×
NEW
105
                tokens: 0,
×
NEW
106
        }, nil
×
107
}
108

NEW
109
func (rl *fixedWindowRatelimiter) Tokens(ctx context.Context) (uint64, error) {
×
NEW
110
        params, err := rl.paramsFunc(ctx)
×
NEW
111
        if err != nil {
×
NEW
112
                return 0, err
×
NEW
113
        }
×
NEW
114
        count, err := rl.client.Get(ctx,
×
NEW
115
                fixedWindowKey(params.KeyPrefix, epoch(rl.nowFunc(), params.Interval)),
×
NEW
116
        ).Uint64()
×
NEW
117
        if errors.Is(err, redis.Nil) {
×
NEW
118
                return params.Burst, nil
×
NEW
119
        } else if err != nil {
×
NEW
120
                return 0, fmt.Errorf("redis: error getting free tokens: %w", err)
×
NEW
121
        } else if count > params.Burst {
×
NEW
122
                return 0, nil
×
NEW
123
        }
×
NEW
124
        return params.Burst - count, nil
×
125
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc