Skip to content

Commit cb1a697

Browse files
committed
feat: add demultiplexing snapshotter
This change adds a demultiplexing snapshotter application which allows for proxying remote snapshotter requests across the microVM threshold. The snapshotter includes a vsock lookup mechanism via namespace specified via configuration file. Also includes a cache mechanism for vsock lookup results. Signed-off-by: Austin Vazquez <[email protected]>
1 parent 004b92f commit cb1a697

23 files changed

+1986
-1
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# express or implied. See the License for the specific language governing
1212
# permissions and limitations under the License.
1313

14-
SUBDIRS:=agent runtime examples firecracker-control/cmd/containerd
14+
SUBDIRS:=agent runtime examples firecracker-control/cmd/containerd snapshotter
1515
TEST_SUBDIRS:=$(addprefix test-,$(SUBDIRS))
1616
INTEG_TEST_SUBDIRS:=$(addprefix integ-test-,$(SUBDIRS))
1717

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ require (
2323
github.com/opencontainers/image-spec v1.0.2-0.20211117181255-693428a734f5
2424
github.com/opencontainers/runc v1.1.0
2525
github.com/opencontainers/runtime-spec v1.0.3-0.20210910115017-0d6cc581aeea
26+
github.com/pelletier/go-toml v1.9.3
2627
github.com/pkg/errors v0.9.1
2728
github.com/shirou/gopsutil v2.18.12+incompatible
2829
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect

snapshotter/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
demux-snapshotter
2+
http-resolver

snapshotter/Makefile

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
# not use this file except in compliance with the License. A copy of the
5+
# License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is distributed
10+
# on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
# express or implied. See the License for the specific language governing
12+
# permissions and limitations under the License.
13+
14+
EXTRAGOARGS?=
15+
16+
SOURCES := $(shell find . -name '*.go')
17+
GOMOD := $(shell go env GOMOD)
18+
GOSUM := $(GOMOD:.mod=.sum)
19+
20+
REVISION := $(shell git rev-parse HEAD)
21+
22+
all: demux-snapshotter
23+
24+
demux-snapshotter: $(SOURCES) $(GOMOD) $(GOSUM)
25+
go build $(EXTRAGOARGS) -ldflags "-X main.revision=$(REVISION)" -o $@
26+
27+
http-resolver: $(SOURCES) $(GOMOD) $(GOSUM)
28+
go build $(EXTRAGOARGS) -ldflags "-X main.revision=$(REVISION)" -o $@ internal/http_address_resolver.go
29+
30+
test:
31+
go test ./... $(EXTRAGOARGS)
32+
33+
clean:
34+
- rm -f demux-snapshotter
35+
- rm -f http-resolver
36+
37+
distclean: clean
38+
39+
# Install is a noop here, for now.
40+
install:
41+
42+
.PHONY: all install test clean distclean

snapshotter/app/service.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package app
15+
16+
import (
17+
"context"
18+
"fmt"
19+
"net"
20+
"os"
21+
"os/signal"
22+
"strconv"
23+
"syscall"
24+
25+
snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
26+
"github.com/containerd/containerd/contrib/snapshotservice"
27+
"github.com/containerd/containerd/log"
28+
"github.com/containerd/containerd/snapshots"
29+
"github.com/firecracker-microvm/firecracker-go-sdk/vsock"
30+
"github.com/sirupsen/logrus"
31+
"golang.org/x/sync/errgroup"
32+
"google.golang.org/grpc"
33+
34+
"github.com/firecracker-microvm/firecracker-containerd/snapshotter/config"
35+
"github.com/firecracker-microvm/firecracker-containerd/snapshotter/demux"
36+
"github.com/firecracker-microvm/firecracker-containerd/snapshotter/demux/cache"
37+
"github.com/firecracker-microvm/firecracker-containerd/snapshotter/demux/proxy"
38+
proxyaddress "github.com/firecracker-microvm/firecracker-containerd/snapshotter/demux/proxy/address"
39+
)
40+
41+
// Run the demultiplexing snapshotter service.
42+
//
43+
// The snapshotter server will be running on the
44+
// network address and port specified in listener config.
45+
func Run(config config.Config) error {
46+
stop := make(chan os.Signal, 1)
47+
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM, syscall.SIGPIPE, syscall.SIGHUP, syscall.SIGQUIT)
48+
49+
ctx, cancel := context.WithCancel(context.Background())
50+
defer cancel()
51+
52+
group, ctx := errgroup.WithContext(ctx)
53+
54+
snapshotter, err := initSnapshotter(ctx, config)
55+
if err != nil {
56+
log.G(ctx).WithFields(
57+
logrus.Fields{"resolver": config.Snapshotter.Proxy.Address.Resolver.Type},
58+
).WithError(err).Fatal("failed creating socket resolver")
59+
return err
60+
}
61+
62+
grpcServer := grpc.NewServer()
63+
service := snapshotservice.FromSnapshotter(snapshotter)
64+
snapshotsapi.RegisterSnapshotsServer(grpcServer, service)
65+
66+
listenerConfig := config.Snapshotter.Listener
67+
listener, err := net.Listen(listenerConfig.Network, listenerConfig.Address)
68+
if err != nil {
69+
log.G(ctx).WithFields(
70+
logrus.Fields{
71+
"network": listenerConfig.Network,
72+
"address": listenerConfig.Address,
73+
},
74+
).WithError(err).Fatal("failed creating listener")
75+
return err
76+
}
77+
78+
group.Go(func() error {
79+
return grpcServer.Serve(listener)
80+
})
81+
82+
group.Go(func() error {
83+
defer func() {
84+
log.G(ctx).Info("stopping server")
85+
grpcServer.Stop()
86+
87+
if err := snapshotter.Close(); err != nil {
88+
log.G(ctx).WithError(err).Error("failed to close snapshotter")
89+
}
90+
}()
91+
92+
for {
93+
select {
94+
case <-stop:
95+
cancel()
96+
return nil
97+
case <-ctx.Done():
98+
return ctx.Err()
99+
}
100+
}
101+
})
102+
103+
if err := group.Wait(); err != nil {
104+
log.G(ctx).WithError(err).Error("demux snapshotter error")
105+
return err
106+
}
107+
108+
log.G(ctx).Info("done")
109+
return nil
110+
}
111+
112+
func initResolver(config config.Config) (proxyaddress.Resolver, error) {
113+
resolverConfig := config.Snapshotter.Proxy.Address.Resolver
114+
switch resolverConfig.Type {
115+
case "http":
116+
return proxyaddress.NewHTTPResolver(resolverConfig.Address), nil
117+
default:
118+
return nil, fmt.Errorf("invalid resolver type: %s", resolverConfig.Type)
119+
}
120+
}
121+
122+
const base10 = 10
123+
const bits32 = 32
124+
125+
func initSnapshotter(ctx context.Context, config config.Config) (snapshots.Snapshotter, error) {
126+
resolver, err := initResolver(config)
127+
if err != nil {
128+
return nil, err
129+
}
130+
131+
newProxySnapshotterFunc := func(ctx context.Context, namespace string) (snapshots.Snapshotter, error) {
132+
r := resolver
133+
response, err := r.Get(namespace)
134+
if err != nil {
135+
return nil, err
136+
}
137+
host, portstr, err := net.SplitHostPort(response.Address)
138+
if err != nil {
139+
return nil, err
140+
}
141+
port, err := strconv.ParseUint(portstr, base10, bits32)
142+
if err != nil {
143+
return nil, err
144+
}
145+
snapshotterDialer := func(ctx context.Context, namespace string) (net.Conn, error) {
146+
return vsock.DialContext(ctx, host, uint32(port), vsock.WithLogger(log.G(ctx)))
147+
}
148+
return proxy.NewProxySnapshotter(ctx, host, snapshotterDialer)
149+
}
150+
151+
return demux.NewSnapshotter(cache.NewSnapshotterCache(), newProxySnapshotterFunc), nil
152+
}

snapshotter/config/config.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"). You may
4+
// not use this file except in compliance with the License. A copy of the
5+
// License is located at
6+
//
7+
// http://aws.amazon.com/apache2.0/
8+
//
9+
// or in the "license" file accompanying this file. This file is distributed
10+
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
// express or implied. See the License for the specific language governing
12+
// permissions and limitations under the License.
13+
14+
package config
15+
16+
import (
17+
"io"
18+
"os"
19+
20+
"github.com/pelletier/go-toml"
21+
)
22+
23+
// Config contains metadata necessary to forward snapshotter requests
24+
// to the destined remote snapshotter.
25+
//
26+
// The default location for the configuration file is '/etc/demux-snapshotter/config.toml'
27+
type Config struct {
28+
Snapshotter snapshotter `toml:"snapshotter"`
29+
30+
Debug debug `toml:"debug"`
31+
}
32+
33+
type snapshotter struct {
34+
Listener listener `toml:"listener"`
35+
Proxy proxy `toml:"proxy"`
36+
}
37+
38+
type listener struct {
39+
Network string `toml:"network" default:"unix"`
40+
Address string `toml:"address" default:"/var/lib/demux-snapshotter/snapshotter.sock"`
41+
}
42+
43+
type proxy struct {
44+
Address address `toml:"address"`
45+
}
46+
47+
type address struct {
48+
Resolver resolver `toml:"resolver"`
49+
}
50+
51+
type resolver struct {
52+
Type string `toml:"type"`
53+
Address string `toml:"address"`
54+
}
55+
56+
type debug struct {
57+
LogLevel string `toml:"logLevel" default:"info"`
58+
}
59+
60+
// Load parses application configuration from a specified file path.
61+
func Load(filePath string) (Config, error) {
62+
file, err := os.Open(filePath)
63+
if err != nil {
64+
return Config{}, err
65+
}
66+
defer file.Close()
67+
68+
fileInfo, err := file.Stat()
69+
if err != nil {
70+
return Config{}, err
71+
}
72+
73+
return load(file, fileInfo.Size())
74+
}
75+
76+
func load(reader io.Reader, readSize int64) (Config, error) {
77+
buffer := make([]byte, readSize)
78+
readBytes, err := reader.Read(buffer)
79+
if int64(readBytes) != readSize || err != nil {
80+
return Config{}, err
81+
}
82+
83+
config := Config{}
84+
if err = toml.Unmarshal(buffer, &config); err != nil {
85+
return Config{}, err
86+
}
87+
return config, nil
88+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[snapshotter.listener]
2+
type = "unix"
3+
address = "/var/lib/demux-snapshotter/snapshotter.sock"
4+
5+
[snapshotter.proxy.address.resolver]
6+
type = "http"
7+
address = "127.0.0.1:10001"
8+
9+
[debug]
10+
logLevel = "info"

0 commit comments

Comments
 (0)