Skip to content

Commit 1c27b47

Browse files
committed
Add tests
Signed-off-by: Amory Hoste <[email protected]>
1 parent 2e54a0a commit 1c27b47

File tree

8 files changed

+351
-19
lines changed

8 files changed

+351
-19
lines changed

ctriface/image/Makefile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# MIT License
2+
#
3+
# Copyright (c) 2020 Dmitrii Ustiugov, Plamen Petrov and EASE lab
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
EXTRAGOARGS:=-v -race -cover
24+
25+
test:
26+
# Need to pass GOROOT because GitHub-hosted runners may have several
27+
# go versions installed so that calling go from root may fail
28+
sudo env "PATH=$(PATH)" "GOROOT=$(GOROOT)" go test ./ $(EXTRAGOARGS)
29+
30+
test-man:
31+
echo "Nothing to test manually"
32+
33+
.PHONY: test test-man

ctrimages/imageManager.go renamed to ctriface/image/manager.go

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// SOFTWARE.
2222

2323
// Package ctrimages provides an image manager that manages and caches container images.
24-
package ctrimages
24+
package image
2525

2626
import (
2727
"context"
@@ -35,29 +35,29 @@ import (
3535
"sync"
3636
)
3737

38-
// ImageState is used to synchronize image pulling to avoid pulling the same image multiple times concurrently.
38+
// ImageState is used for synchronization to avoid pulling the same image multiple times concurrently.
3939
type ImageState struct {
4040
sync.Mutex
41-
pulled bool
41+
isCached bool
4242
}
4343

44-
// NewImageState creates a new ImageState object that can be used to synchronize image pulling.
44+
// NewImageState creates a new ImageState object that can be used to synchronize pulling a single image
4545
func NewImageState() *ImageState {
4646
state := new(ImageState)
47-
state.pulled = false
47+
state.isCached = false
4848
return state
4949
}
5050

5151
// ImageManager manages the images that have been pulled to the node.
5252
type ImageManager struct {
5353
sync.Mutex
54-
snapshotter string // image snapshotter
54+
snapshotter string // image snapshotter
5555
cachedImages map[string]containerd.Image // Cached container images
5656
imageStates map[string]*ImageState
5757
client *containerd.Client
5858
}
5959

60-
// NewImageManager creates a new imagemanager that can be used to fetch container images.
60+
// NewImageManager creates a new image manager that can be used to fetch container images.
6161
func NewImageManager(client *containerd.Client, snapshotter string) *ImageManager {
6262
log.Info("Creating image manager")
6363
manager := new(ImageManager)
@@ -104,8 +104,10 @@ func (mgr *ImageManager) pullImage(ctx context.Context, imageName string) error
104104
return nil
105105
}
106106

107-
// GetImage fetches an image that can be used to create a container using containerd
107+
// GetImage fetches an image that can be used to create a container using containerd. Synchronization is implemented
108+
// on a per image level to keep waiting to a minimum.
108109
func (mgr *ImageManager) GetImage(ctx context.Context, imageName string) (*containerd.Image, error) {
110+
// Get reference to synchronization object for image
109111
mgr.Lock()
110112
imgState, found := mgr.imageStates[imageName]
111113
if !found {
@@ -114,14 +116,14 @@ func (mgr *ImageManager) GetImage(ctx context.Context, imageName string) (*conta
114116
}
115117
mgr.Unlock()
116118

117-
// Pull image if necessary
119+
// Pull image if necessary. The image will only be pulled by the first thread to take the lock.
118120
imgState.Lock()
119-
if !imgState.pulled {
121+
if !imgState.isCached {
120122
if err := mgr.pullImage(ctx, imageName); err != nil {
121123
imgState.Unlock()
122124
return nil, err
123125
}
124-
imgState.pulled = true
126+
imgState.isCached = true
125127
}
126128
imgState.Unlock()
127129

ctriface/image/manager_test.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
// MIT License
2+
//
3+
// Copyright (c) 2020 Plamen Petrov, Amory Hoste and EASE lab
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files (the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions:
11+
//
12+
// The above copyright notice and this permission notice shall be included in all
13+
// copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
// SOFTWARE.
22+
23+
package image
24+
25+
import (
26+
"context"
27+
"fmt"
28+
"github.com/containerd/containerd"
29+
"github.com/containerd/containerd/namespaces"
30+
"os"
31+
"sync"
32+
"testing"
33+
"time"
34+
35+
ctrdlog "github.com/containerd/containerd/log"
36+
log "github.com/sirupsen/logrus"
37+
"github.com/stretchr/testify/require"
38+
)
39+
40+
const (
41+
TestImageName = "ghcr.io/ease-lab/helloworld:var_workload"
42+
containerdAddress = "/run/firecracker-containerd/containerd.sock"
43+
NamespaceName = "firecracker-containerd"
44+
)
45+
46+
func getAllImages() map[string]string {
47+
return map[string]string{
48+
"helloworld": "ghcr.io/ease-lab/helloworld:var_workload",
49+
"chameleon": "ghcr.io/ease-lab/chameleon:var_workload",
50+
"pyaes": "ghcr.io/ease-lab/pyaes:var_workload",
51+
"image_rotate": "ghcr.io/ease-lab/image_rotate:var_workload",
52+
"lr_training": "ghcr.io/ease-lab/lr_training:var_workload",
53+
}
54+
}
55+
56+
func TestMain(m *testing.M) {
57+
// call flag.Parse() here if TestMain uses flags
58+
59+
log.SetFormatter(&log.TextFormatter{
60+
TimestampFormat: ctrdlog.RFC3339NanoFixed,
61+
FullTimestamp: true,
62+
})
63+
//log.SetReportCaller(true) // FIXME: make sure it's false unless debugging
64+
65+
log.SetOutput(os.Stdout)
66+
67+
log.SetLevel(log.InfoLevel)
68+
69+
os.Exit(m.Run())
70+
}
71+
72+
func TestSingleConcurrent(t *testing.T) {
73+
// Create client
74+
client, err := containerd.New(containerdAddress)
75+
defer client.Close()
76+
require.NoError(t, err, "Containerd client creation returned error")
77+
78+
// Create image manager
79+
mgr := NewImageManager(client, "devmapper")
80+
81+
testTimeout := 120 * time.Second
82+
ctx, cancel := context.WithTimeout(namespaces.WithNamespace(context.Background(), NamespaceName), testTimeout)
83+
defer cancel()
84+
85+
// Pull image
86+
var wg sync.WaitGroup
87+
concurrentPulls := 100
88+
wg.Add(concurrentPulls)
89+
90+
for i := 0; i < concurrentPulls; i++ {
91+
go func(i int) {
92+
defer wg.Done()
93+
_, err := mgr.GetImage(ctx, TestImageName)
94+
require.NoError(t, err, fmt.Sprintf("Failed to pull image %s", TestImageName))
95+
}(i)
96+
}
97+
wg.Wait()
98+
}
99+
100+
func TestMultipleConcurrent(t *testing.T) {
101+
// Create client
102+
client, err := containerd.New(containerdAddress)
103+
defer client.Close()
104+
require.NoError(t, err, "Containerd client creation returned error")
105+
106+
// Create image manager
107+
mgr := NewImageManager(client, "devmapper")
108+
109+
testTimeout := 120 * time.Second
110+
ctx, cancel := context.WithTimeout(namespaces.WithNamespace(context.Background(), NamespaceName), testTimeout)
111+
defer cancel()
112+
113+
// Pull image
114+
var wg sync.WaitGroup
115+
concurrentPulls := 100
116+
wg.Add(len(getAllImages()))
117+
118+
for _, imgName := range getAllImages() {
119+
go func(imgName string) {
120+
var imgWg sync.WaitGroup
121+
imgWg.Add(concurrentPulls)
122+
for i := 0; i < concurrentPulls; i++ {
123+
go func(i int) {
124+
defer wg.Done()
125+
_, err := mgr.GetImage(ctx, imgName)
126+
require.NoError(t, err, fmt.Sprintf("Failed to pull image %s", imgName))
127+
}(i)
128+
}
129+
imgWg.Wait()
130+
wg.Done()
131+
}(imgName)
132+
}
133+
134+
wg.Wait()
135+
}

ctriface/orch.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
package ctriface
2424

2525
import (
26-
"github.com/ease-lab/vhive/ctrimages"
26+
"github.com/ease-lab/vhive/ctriface/image"
2727
"github.com/ease-lab/vhive/devmapper"
2828
"github.com/ease-lab/vhive/memory/manager"
2929
"os"
@@ -81,7 +81,7 @@ type Orchestrator struct {
8181
client *containerd.Client
8282
fcClient *fcclient.Client
8383
devMapper *devmapper.DeviceMapper
84-
imageManager *ctrimages.ImageManager
84+
imageManager *image.ImageManager
8585
// store *skv.KVStore
8686
snapshotsEnabled bool
8787
isUPFEnabled bool
@@ -140,7 +140,7 @@ func NewOrchestrator(snapshotter, hostIface, poolName, metadataDev string, netPo
140140

141141
o.devMapper = devmapper.NewDeviceMapper(o.client, poolName, metadataDev)
142142

143-
o.imageManager = ctrimages.NewImageManager(o.client, o.snapshotter)
143+
o.imageManager = image.NewImageManager(o.client, o.snapshotter)
144144

145145
return o
146146
}

networking/Makefile

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# MIT License
2+
#
3+
# Copyright (c) 2020 Dmitrii Ustiugov, Plamen Petrov and EASE lab
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining a copy
6+
# of this software and associated documentation files (the "Software"), to deal
7+
# in the Software without restriction, including without limitation the rights
8+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
# copies of the Software, and to permit persons to whom the Software is
10+
# furnished to do so, subject to the following conditions:
11+
#
12+
# The above copyright notice and this permission notice shall be included in all
13+
# copies or substantial portions of the Software.
14+
#
15+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
# SOFTWARE.
22+
23+
EXTRAGOARGS:=-v -race -cover
24+
25+
test:
26+
# Need to pass GOROOT because GitHub-hosted runners may have several
27+
# go versions installed so that calling go from root may fail
28+
sudo env "PATH=$(PATH)" "GOROOT=$(GOROOT)" go test ./ $(EXTRAGOARGS)
29+
30+
test-man:
31+
echo "Nothing to test manually"
32+
33+
.PHONY: test test-man

networking/networkManager.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ type NetworkManager struct {
4242

4343
// Mapping of function instance IDs to their network config
4444
netConfigs map[string]*NetworkConfig
45+
46+
// Network configs that are being created
47+
inCreation sync.WaitGroup
4548
}
4649

4750
// NewNetworkManager creates and returns a new network manager that connects function instances to the network
@@ -98,6 +101,7 @@ func (mgr *NetworkManager) addNetConfig() {
98101
mgr.Lock()
99102
id := mgr.nextID
100103
mgr.nextID += 1
104+
mgr.inCreation.Add(1)
101105
mgr.Unlock()
102106

103107
netCfg := NewNetworkConfig(id, mgr.hostIfaceName)
@@ -110,6 +114,7 @@ func (mgr *NetworkManager) addNetConfig() {
110114
// Signal in case someone is waiting for a new config to become available in the pool
111115
mgr.poolCond.Signal()
112116
mgr.poolCond.L.Unlock()
117+
mgr.inCreation.Done()
113118
}
114119

115120
// allocNetConfig allocates a new network config from the pool to a function instance identified by funcID
@@ -171,16 +176,30 @@ func (mgr *NetworkManager) RemoveNetwork(funcID string) error {
171176
return nil
172177
}
173178

174-
// Cleanup removes and deallocates all network configurations that are in use or in the network pool.
179+
// Cleanup removes and deallocates all network configurations that are in use or in the network pool. Make sure to first
180+
// clean up all running functions before removing their network configs.
175181
func (mgr *NetworkManager) Cleanup() error {
176182
log.Info("Cleaning up network manager")
177183
mgr.Lock()
178184
defer mgr.Unlock()
179185

186+
// Wait till all network configs still in creation are added
187+
mgr.inCreation.Wait()
188+
180189
// Release network configs still in use
190+
var wgu sync.WaitGroup
191+
wgu.Add(len(mgr.netConfigs))
181192
for funcID := range mgr.netConfigs {
182-
mgr.releaseNetConfig(funcID)
193+
config := mgr.netConfigs[funcID]
194+
go func(config *NetworkConfig) {
195+
if err := config.RemoveNetwork(); err != nil {
196+
log.Errorf("failed to remove network %s:", err)
197+
}
198+
wgu.Done()
199+
}(config)
183200
}
201+
wgu.Wait()
202+
mgr.netConfigs = make(map[string]*NetworkConfig)
184203

185204
// Cleanup network pool
186205
mgr.poolCond.L.Lock()

networking/networkconfig.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ const (
3939
)
4040

4141
// NetworkConfig represents the network devices, IPs, namespaces, routes and filter rules to connect a uVM
42-
// to the network. Note that due to the current allocation of IPs at most 2^14 VMs can be simultaneously be
43-
// available on a single host.
42+
// to the network. The network config ID is deterministically mapped to IP addresses to be used for the uVM.
43+
// Note that due to the current allocation of IPs at most 2^14 VMs can be simultaneously be available on a single host.
4444
type NetworkConfig struct {
4545
id int
4646
containerCIDR string // Container IP address (CIDR notation)
47-
gatewayCIDR string // Container gateway IP address
47+
gatewayCIDR string // Container gateway IP address (CIDR notation)
4848
containerTap string // Container tap name
4949
containerMac string // Container Mac address
5050
hostIfaceName string // Host network interface name

0 commit comments

Comments
 (0)