/
lcow_test.go
304 lines (252 loc) · 9.95 KB
/
lcow_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
//go:build windows && functional
// +build windows,functional
package functional
import (
"bytes"
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"testing"
"time"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/Microsoft/hcsshim/internal/cmd"
"github.com/Microsoft/hcsshim/internal/cow"
"github.com/Microsoft/hcsshim/internal/hcsoci"
"github.com/Microsoft/hcsshim/internal/lcow"
"github.com/Microsoft/hcsshim/internal/resources"
"github.com/Microsoft/hcsshim/internal/uvm"
"github.com/Microsoft/hcsshim/internal/uvm/scsi"
"github.com/Microsoft/hcsshim/osversion"
testutilities "github.com/Microsoft/hcsshim/test/internal"
testcmd "github.com/Microsoft/hcsshim/test/internal/cmd"
"github.com/Microsoft/hcsshim/test/internal/util"
"github.com/Microsoft/hcsshim/test/pkg/require"
testuvm "github.com/Microsoft/hcsshim/test/pkg/uvm"
)
// test if closing a waiting (but not starting) uVM succeeds.
func TestLCOW_UVMCreateClose(t *testing.T) {
requireFeatures(t, featureLCOW, featureUVM)
require.Build(t, osversion.RS5)
ctx := util.Context(context.Background(), t)
vm, cleanup := testuvm.CreateLCOW(ctx, t, defaultLCOWOptions(ctx, t))
testuvm.Close(ctx, t, vm)
// also run cleanup to make sure that works fine too
cleanup(ctx)
}
// test if waiting after creating (but not starting) an LCOW uVM returns.
func TestLCOW_UVMCreateWait(t *testing.T) {
requireFeatures(t, featureLCOW, featureUVM)
require.Build(t, osversion.RS5)
pCtx := util.Context(context.Background(), t)
vm, cleanup := testuvm.CreateLCOW(pCtx, t, defaultLCOWOptions(pCtx, t))
t.Cleanup(func() { cleanup(pCtx) })
ctx, cancel := context.WithTimeout(pCtx, 3*time.Second)
t.Cleanup(cancel)
switch err := vm.WaitCtx(ctx); {
case err == nil:
t.Fatal("wait did not error")
case !errors.Is(err, context.DeadlineExceeded):
t.Fatalf("wait should have errored with '%v'; got '%v'", context.DeadlineExceeded, err)
}
}
// TestLCOW_UVMNoSCSINoVPMemInitrd starts an LCOW utility VM without a SCSI controller and
// no VPMem device. Uses initrd.
func TestLCOW_UVMNoSCSINoVPMemInitrd(t *testing.T) {
requireFeatures(t, featureLCOW, featureUVM)
ctx := util.Context(context.Background(), t)
opts := defaultLCOWOptions(ctx, t)
opts.SCSIControllerCount = 0
opts.VPMemDeviceCount = 0
opts.PreferredRootFSType = uvm.PreferredRootFSTypeInitRd
opts.RootFSFile = uvm.InitrdFile
opts.KernelDirect = false
opts.KernelFile = uvm.KernelFile
testLCOWUVMNoSCSISingleVPMem(t, opts, fmt.Sprintf("Command line: initrd=/%s", opts.RootFSFile))
}
// TestLCOW_UVMNoSCSISingleVPMemVHD starts an LCOW utility VM without a SCSI controller and
// only a single VPMem device. Uses VPMEM VHD.
func TestLCOW_UVMNoSCSISingleVPMemVHD(t *testing.T) {
requireFeatures(t, featureLCOW, featureUVM)
ctx := util.Context(context.Background(), t)
opts := defaultLCOWOptions(ctx, t)
opts.SCSIControllerCount = 0
opts.VPMemDeviceCount = 1
opts.PreferredRootFSType = uvm.PreferredRootFSTypeVHD
opts.RootFSFile = uvm.VhdFile
testLCOWUVMNoSCSISingleVPMem(t, opts, `Command line: root=/dev/pmem0`, `init=/init`)
}
func testLCOWUVMNoSCSISingleVPMem(t *testing.T, opts *uvm.OptionsLCOW, expected ...string) {
t.Helper()
require.Build(t, osversion.RS5)
requireFeatures(t, featureLCOW, featureUVM)
ctx := util.Context(context.Background(), t)
lcowUVM := testuvm.CreateAndStartLCOWFromOpts(ctx, t, opts)
io := testcmd.NewBufferedIO()
// c := cmd.Command(lcowUVM, "dmesg")
c := testcmd.Create(ctx, t, lcowUVM, &specs.Process{Args: []string{"dmesg"}}, io)
testcmd.Start(ctx, t, c)
testcmd.WaitExitCode(ctx, t, c, 0)
out, err := io.Output()
if err != nil {
t.Helper()
t.Fatalf("uvm exec failed with: %s", err)
}
for _, s := range expected {
if !strings.Contains(out, s) {
t.Helper()
t.Fatalf("Expected dmesg output to have %q: %s", s, out)
}
}
}
// TestLCOW_TimeUVMStartVHD starts/terminates a utility VM booting from VPMem-
// attached root filesystem a number of times.
func TestLCOW_TimeUVMStartVHD(t *testing.T) {
require.Build(t, osversion.RS5)
requireFeatures(t, featureLCOW, featureUVM)
testLCOWTimeUVMStart(t, false, uvm.PreferredRootFSTypeVHD)
}
// TestLCOWUVMStart_KernelDirect_VHD starts/terminates a utility VM booting from
// VPMem- attached root filesystem a number of times starting from the Linux
// Kernel directly and skipping EFI.
func TestLCOW_UVMStart_KernelDirect_VHD(t *testing.T) {
require.Build(t, 18286)
requireFeatures(t, featureLCOW, featureUVM)
testLCOWTimeUVMStart(t, true, uvm.PreferredRootFSTypeVHD)
}
// TestLCOWTimeUVMStartInitRD starts/terminates a utility VM booting from initrd-
// attached root file system a number of times.
func TestLCOW_TimeUVMStartInitRD(t *testing.T) {
require.Build(t, osversion.RS5)
requireFeatures(t, featureLCOW, featureUVM)
testLCOWTimeUVMStart(t, false, uvm.PreferredRootFSTypeInitRd)
}
// TestLCOWUVMStart_KernelDirect_InitRd starts/terminates a utility VM booting
// from initrd- attached root file system a number of times starting from the
// Linux Kernel directly and skipping EFI.
func TestLCOW_UVMStart_KernelDirect_InitRd(t *testing.T) {
require.Build(t, 18286)
requireFeatures(t, featureLCOW, featureUVM)
testLCOWTimeUVMStart(t, true, uvm.PreferredRootFSTypeInitRd)
}
func testLCOWTimeUVMStart(t *testing.T, kernelDirect bool, rfsType uvm.PreferredRootFSType) {
t.Helper()
requireFeatures(t, featureLCOW, featureUVM)
ctx := util.Context(context.Background(), t)
for i := 0; i < 3; i++ {
opts := defaultLCOWOptions(ctx, t)
opts.KernelDirect = kernelDirect
if !kernelDirect {
// can only use the uncompressed kernel with direct boot
opts.KernelFile = uvm.KernelFile
}
opts.VPMemDeviceCount = 32
opts.PreferredRootFSType = rfsType
switch opts.PreferredRootFSType {
case uvm.PreferredRootFSTypeInitRd:
opts.RootFSFile = uvm.InitrdFile
case uvm.PreferredRootFSTypeVHD:
opts.RootFSFile = uvm.VhdFile
}
lcowUVM := testuvm.CreateAndStartLCOWFromOpts(context.Background(), t, opts)
testuvm.Close(ctx, t, lcowUVM)
}
}
func TestLCOWSimplePodScenario(t *testing.T) {
t.Skip("Doesn't work quite yet")
require.Build(t, osversion.RS5)
requireFeatures(t, featureLCOW, featureUVM, featureContainer)
layers := linuxImageLayers(context.Background(), t)
cacheDir := t.TempDir()
cacheFile := filepath.Join(cacheDir, "cache.vhdx")
// This is what gets mounted for UVM scratch
uvmScratchDir := t.TempDir()
uvmScratchFile := filepath.Join(uvmScratchDir, "uvmscratch.vhdx")
// Scratch for the first container
c1ScratchDir := t.TempDir()
c1ScratchFile := filepath.Join(c1ScratchDir, "sandbox.vhdx")
// Scratch for the second container
c2ScratchDir := t.TempDir()
c2ScratchFile := filepath.Join(c2ScratchDir, "sandbox.vhdx")
lcowUVM := testuvm.CreateAndStartLCOW(context.Background(), t, "uvm")
defer lcowUVM.Close()
// Populate the cache and generate the scratch file
if err := lcow.CreateScratch(context.Background(), lcowUVM, uvmScratchFile, lcow.DefaultScratchSizeGB, cacheFile); err != nil {
t.Fatal(err)
}
_, err := lcowUVM.SCSIManager.AddVirtualDisk(context.Background(), uvmScratchFile, false, lcowUVM.ID(), &scsi.MountConfig{})
if err != nil {
t.Fatal(err)
}
// Now create the first containers sandbox, populate a spec
if err := lcow.CreateScratch(context.Background(), lcowUVM, c1ScratchFile, lcow.DefaultScratchSizeGB, cacheFile); err != nil {
t.Fatal(err)
}
c1Spec := testutilities.GetDefaultLinuxSpec(t)
c1Folders := append(layers, c1ScratchDir)
c1Spec.Windows.LayerFolders = c1Folders
c1Spec.Process.Args = []string{"echo", "hello", "lcow", "container", "one"}
c1Opts := &hcsoci.CreateOptions{
Spec: c1Spec,
HostingSystem: lcowUVM,
}
// Now create the second containers sandbox, populate a spec
if err := lcow.CreateScratch(context.Background(), lcowUVM, c2ScratchFile, lcow.DefaultScratchSizeGB, cacheFile); err != nil {
t.Fatal(err)
}
c2Spec := testutilities.GetDefaultLinuxSpec(t)
c2Folders := append(layers, c2ScratchDir)
c2Spec.Windows.LayerFolders = c2Folders
c2Spec.Process.Args = []string{"echo", "hello", "lcow", "container", "two"}
c2Opts := &hcsoci.CreateOptions{
Spec: c2Spec,
HostingSystem: lcowUVM,
}
// Create the two containers
c1hcsSystem, c1Resources, err := hcsoci.CreateContainer(context.Background(), c1Opts)
if err != nil {
t.Fatal(err)
}
c2hcsSystem, c2Resources, err := hcsoci.CreateContainer(context.Background(), c2Opts)
if err != nil {
t.Fatal(err)
}
// Start them. In the UVM, they'll be in the created state from runc's perspective after this.eg
/// # runc list
//ID PID STATUS BUNDLE CREATED OWNER
//3a724c2b-f389-5c71-0555-ebc6f5379b30 138 running /run/gcs/c/1 2018-06-04T21:23:39.1253911Z root
//7a8229a0-eb60-b515-55e7-d2dd63ffae75 158 created /run/gcs/c/2 2018-06-04T21:23:39.4249048Z root
if err := c1hcsSystem.Start(context.Background()); err != nil {
t.Fatal(err)
}
defer resources.ReleaseResources(context.Background(), c1Resources, lcowUVM, true) //nolint:errcheck
if err := c2hcsSystem.Start(context.Background()); err != nil {
t.Fatal(err)
}
defer resources.ReleaseResources(context.Background(), c2Resources, lcowUVM, true) //nolint:errcheck
// Start the init process in each container and grab it's stdout comparing to expected
runInitProcess(t, c1hcsSystem, "hello lcow container one")
runInitProcess(t, c2hcsSystem, "hello lcow container two")
}
// Helper to run the init process in an LCOW container; verify it exits with exit
// code 0; verify stderr is empty; check output is as expected.
func runInitProcess(t *testing.T, s cow.Container, expected string) {
t.Helper()
var errB bytes.Buffer
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
cmd := &cmd.Cmd{
Host: s,
Stderr: &errB,
Context: ctx,
}
outb, err := cmd.Output()
if err != nil {
t.Fatalf("stderr: %s", err)
}
out := string(outb)
if strings.TrimSpace(out) != expected {
t.Fatalf("got %q expecting %q", string(out), expected)
}
}