Skip to content

Commit 2c36099

Browse files
committed
add support for rate limiting
Adds support for rate limiting for inbound and outbound data streams of firecracker. This also introduces a TokenBuilder which can be passed into the NewRateLimiter factory function to return a new rate limiter.
1 parent 1585d53 commit 2c36099

File tree

4 files changed

+186
-0
lines changed

4 files changed

+186
-0
lines changed

example_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"os"
7+
"time"
78

89
"github.com/firecracker-microvm/firecracker-go-sdk"
910
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
@@ -151,3 +152,63 @@ func ExampleDrivesBuilder_DriveOpt() {
151152
panic(err)
152153
}
153154
}
155+
156+
func ExampleNetworkInterfaces_RateLimiting() {
157+
// construct the limitations of the bandwidth for firecracker
158+
bandwidthBuilder := firecracker.TokenBucketBuilder{}.
159+
WithInitialSize(1024 * 1024). // Initial token amount
160+
WithBucketSize(1024 * 1024). // Max number of tokens
161+
WithRefillDuration(30 * time.Second) // Refill rate
162+
163+
// construct the limitations of the number of operations per duration for firecracker
164+
opsBuilder := firecracker.TokenBucketBuilder{}.
165+
WithInitialSize(5).
166+
WithBucketSize(5).
167+
WithRefillDuration(5 * time.Second)
168+
169+
// create the inbound rate limiter
170+
inbound := firecracker.NewRateLimiter(bandwidthBuilder.Build(), opsBuilder.Build())
171+
172+
bandwidthBuilder = bandwidthBuilder.WithBucketSize(1024 * 1024 * 10)
173+
opsBuilder = opsBuilder.
174+
WithBucketSize(100).
175+
WithInitialSize(100)
176+
// create the outbound rate limiter
177+
outbound := firecracker.NewRateLimiter(bandwidthBuilder.Build(), opsBuilder.Build())
178+
179+
networkIfaces := []firecracker.NetworkInterface{
180+
{
181+
MacAddress: "01-23-45-67-89-AB-CD-EF",
182+
HostDevName: "tap-name",
183+
InRateLimiter: inbound,
184+
OutRateLimiter: outbound,
185+
},
186+
}
187+
188+
cfg := firecracker.Config{
189+
SocketPath: "/path/to/socket",
190+
KernelImagePath: "/path/to/kernel",
191+
Drives: firecracker.NewDrivesBuilder("/path/to/rootfs").Build(),
192+
MachineCfg: models.MachineConfiguration{
193+
VcpuCount: 1,
194+
},
195+
NetworkInterfaces: networkIfaces,
196+
}
197+
198+
ctx := context.Background()
199+
m, err := firecracker.NewMachine(ctx, cfg)
200+
if err != nil {
201+
panic(fmt.Errorf("failed to create new machine: %v", err))
202+
}
203+
204+
defer os.Remove(cfg.SocketPath)
205+
206+
if err := m.Start(ctx); err != nil {
207+
panic(fmt.Errorf("failed to initialize machine: %v", err))
208+
}
209+
210+
// wait for VMM to execute
211+
if err := m.Wait(ctx); err != nil {
212+
panic(err)
213+
}
214+
}

machine.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ type NetworkInterface struct {
161161
HostDevName string
162162
// AllowMMDS makes the Firecracker MMDS available on this network interface.
163163
AllowMDDS bool
164+
165+
// InRateLimiter limits the incoming bytes.
166+
InRateLimiter *models.RateLimiter
167+
// OutRateLimiter limits the outgoing bytes.
168+
OutRateLimiter *models.RateLimiter
164169
}
165170

166171
// VsockDevice represents a vsock connection between the host and the guest
@@ -475,6 +480,14 @@ func (m *Machine) createNetworkInterface(ctx context.Context, iface NetworkInter
475480
AllowMmdsRequests: iface.AllowMDDS,
476481
}
477482

483+
if iface.InRateLimiter != nil {
484+
ifaceCfg.RxRateLimiter = iface.InRateLimiter
485+
}
486+
487+
if iface.OutRateLimiter != nil {
488+
ifaceCfg.TxRateLimiter = iface.OutRateLimiter
489+
}
490+
478491
resp, err := m.client.PutGuestNetworkInterfaceByID(ctx, ifaceID, &ifaceCfg)
479492
if err == nil {
480493
m.logger.Printf("PutGuestNetworkInterfaceByID: %s", resp.Error())

rate_limiter.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package firecracker
2+
3+
import (
4+
"time"
5+
6+
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
7+
)
8+
9+
// RateLimiterOpt represents a functional option for rate limiting construction
10+
type RateLimiterOpt func(*models.RateLimiter)
11+
12+
// NewRateLimiter will construct a new RateLimiter with given parameters.
13+
func NewRateLimiter(bandwidth, ops models.TokenBucket, opts ...RateLimiterOpt) *models.RateLimiter {
14+
limiter := &models.RateLimiter{
15+
Bandwidth: &bandwidth,
16+
Ops: &ops,
17+
}
18+
19+
for _, opt := range opts {
20+
opt(limiter)
21+
}
22+
23+
return limiter
24+
}
25+
26+
// TokenBucketBuilder is a builder that allows of building components of the
27+
// models.RateLimiter structure.
28+
type TokenBucketBuilder struct {
29+
bucket models.TokenBucket
30+
}
31+
32+
// WithBucketSize will set the bucket size for the given token bucket
33+
func (b TokenBucketBuilder) WithBucketSize(size int64) TokenBucketBuilder {
34+
b.bucket.Size = &size
35+
36+
return b
37+
}
38+
39+
// WithRefillDuration will set the given refill duration of the bucket fill rate.
40+
func (b TokenBucketBuilder) WithRefillDuration(dur time.Duration) TokenBucketBuilder {
41+
ms := dur.Nanoseconds()
42+
ms /= int64(time.Millisecond)
43+
b.bucket.RefillTime = &ms
44+
45+
return b
46+
}
47+
48+
// WithInitialSize will set the initial token bucket size
49+
func (b TokenBucketBuilder) WithInitialSize(size int64) TokenBucketBuilder {
50+
b.bucket.OneTimeBurst = &size
51+
52+
return b
53+
}
54+
55+
// Build will return a new token bucket
56+
func (b TokenBucketBuilder) Build() models.TokenBucket {
57+
return b.bucket
58+
}

rate_limiter_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package firecracker_test
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/firecracker-microvm/firecracker-go-sdk"
8+
models "github.com/firecracker-microvm/firecracker-go-sdk/client/models"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestRateLimiter(t *testing.T) {
13+
bucket := firecracker.TokenBucketBuilder{}.
14+
WithRefillDuration(1 * time.Hour).
15+
WithBucketSize(100).
16+
WithInitialSize(100).
17+
Build()
18+
19+
expectedBucket := models.TokenBucket{
20+
OneTimeBurst: firecracker.Int64(100),
21+
RefillTime: firecracker.Int64(3600000),
22+
Size: firecracker.Int64(100),
23+
}
24+
25+
assert.Equal(t, expectedBucket, bucket)
26+
}
27+
28+
func TestRateLimiter_RefillTime(t *testing.T) {
29+
cases := []struct {
30+
Name string
31+
Dur time.Duration
32+
ExpectedMilliseconds int64
33+
}{
34+
{
35+
Name: "one hour",
36+
Dur: 1 * time.Hour,
37+
ExpectedMilliseconds: 3600000,
38+
},
39+
{
40+
Name: "zero",
41+
ExpectedMilliseconds: 0,
42+
},
43+
}
44+
45+
for _, c := range cases {
46+
t.Run(c.Name, func(t *testing.T) {
47+
bucket := firecracker.TokenBucketBuilder{}.
48+
WithRefillDuration(c.Dur).
49+
Build()
50+
51+
assert.Equal(t, &c.ExpectedMilliseconds, bucket.RefillTime)
52+
})
53+
}
54+
}

0 commit comments

Comments
 (0)