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
305
306
307
308
309
310
|
// Copyright 2018 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gofer
import (
"fmt"
"syscall"
"testing"
"time"
"gvisor.dev/gvisor/pkg/p9"
"gvisor.dev/gvisor/pkg/p9/p9test"
"gvisor.dev/gvisor/pkg/sentry/context"
"gvisor.dev/gvisor/pkg/sentry/context/contexttest"
"gvisor.dev/gvisor/pkg/sentry/fs"
)
// rootTest runs a test with a p9 mock and an fs.InodeOperations created from
// the attached root directory. The root file will be closed and client
// disconnected, but additional files must be closed manually.
func rootTest(t *testing.T, name string, cp cachePolicy, fn func(context.Context, *p9test.Harness, *p9test.Mock, *fs.Inode)) {
t.Run(name, func(t *testing.T) {
h, c := p9test.NewHarness(t)
defer h.Finish()
// Create a new root. Note that we pass an empty, but non-nil
// map here. This allows tests to extend the root children
// dynamically.
root := h.NewDirectory(map[string]p9test.Generator{})(nil)
// Return this as the root.
h.Attacher.EXPECT().Attach().Return(root, nil).Times(1)
// ... and open via the client.
rootFile, err := c.Attach("/")
if err != nil {
t.Fatalf("unable to attach: %v", err)
}
defer rootFile.Close()
// Wrap an a session.
s := &session{
mounter: fs.RootOwner,
cachePolicy: cp,
client: c,
}
// ... and an INode, with only the mode being explicitly valid for now.
ctx := contexttest.Context(t)
sattr, rootInodeOperations := newInodeOperations(ctx, s, contextFile{
file: rootFile,
}, root.QID, p9.AttrMaskAll(), root.Attr, false /* socket */)
m := fs.NewMountSource(ctx, s, &filesystem{}, fs.MountSourceFlags{})
rootInode := fs.NewInode(ctx, rootInodeOperations, m, sattr)
// Ensure that the cache is fully invalidated, so that any
// close actions actually take place before the full harness is
// torn down.
defer func() {
m.FlushDirentRefs()
// Wait for all resources to be released, otherwise the
// operations may fail after we close the rootFile.
fs.AsyncBarrier()
}()
// Execute the test.
fn(ctx, h, root, rootInode)
})
}
func TestLookup(t *testing.T) {
type lookupTest struct {
// Name of the test.
name string
// Expected return value.
want error
}
tests := []lookupTest{
{
name: "mock Walk passes (function succeeds)",
want: nil,
},
{
name: "mock Walk fails (function fails)",
want: syscall.ENOENT,
},
}
const file = "file" // The walked target file.
for _, test := range tests {
rootTest(t, test.name, cacheNone, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) {
// Setup the appropriate result.
rootFile.WalkCallback = func() error {
return test.want
}
if test.want == nil {
// Set the contents of the root. We expect a
// normal file generator for ppp above. This is
// overriden by setting WalkErr in the mock.
rootFile.AddChild(file, h.NewFile())
}
// Call function.
dirent, err := rootInode.Lookup(ctx, file)
// Unwrap the InodeOperations.
var newInodeOperations fs.InodeOperations
if dirent != nil {
if dirent.IsNegative() {
err = syscall.ENOENT
} else {
newInodeOperations = dirent.Inode.InodeOperations
}
}
// Check return values.
if err != test.want {
t.Errorf("Lookup got err %v, want %v", err, test.want)
}
if err == nil && newInodeOperations == nil {
t.Errorf("Lookup got non-nil err and non-nil node, wanted at least one non-nil")
}
})
}
}
func TestRevalidation(t *testing.T) {
type revalidationTest struct {
cachePolicy cachePolicy
// Whether dirent should be reloaded before any modifications.
preModificationWantReload bool
// Whether dirent should be reloaded after updating an unstable
// attribute on the remote fs.
postModificationWantReload bool
// Whether dirent unstable attributes should be updated after
// updating an attribute on the remote fs.
postModificationWantUpdatedAttrs bool
// Whether dirent should be reloaded after the remote has
// removed the file.
postRemovalWantReload bool
}
tests := []revalidationTest{
{
// Policy cacheNone causes Revalidate to always return
// true.
cachePolicy: cacheNone,
preModificationWantReload: true,
postModificationWantReload: true,
postModificationWantUpdatedAttrs: true,
postRemovalWantReload: true,
},
{
// Policy cacheAll causes Revalidate to always return
// false.
cachePolicy: cacheAll,
preModificationWantReload: false,
postModificationWantReload: false,
postModificationWantUpdatedAttrs: false,
postRemovalWantReload: false,
},
{
// Policy cacheAllWritethrough causes Revalidate to
// always return false.
cachePolicy: cacheAllWritethrough,
preModificationWantReload: false,
postModificationWantReload: false,
postModificationWantUpdatedAttrs: false,
postRemovalWantReload: false,
},
{
// Policy cacheRemoteRevalidating causes Revalidate to
// return update cached unstable attrs, and returns
// true only when the remote inode itself has been
// removed or replaced.
cachePolicy: cacheRemoteRevalidating,
preModificationWantReload: false,
postModificationWantReload: false,
postModificationWantUpdatedAttrs: true,
postRemovalWantReload: true,
},
}
const file = "file" // The file walked below.
for _, test := range tests {
name := fmt.Sprintf("cachepolicy=%s", test.cachePolicy)
rootTest(t, name, test.cachePolicy, func(ctx context.Context, h *p9test.Harness, rootFile *p9test.Mock, rootInode *fs.Inode) {
// Wrap in a dirent object.
rootDir := fs.NewDirent(ctx, rootInode, "root")
// Create a mock file a child of the root. We save when
// this is generated, so that when the time changed, we
// can update the original entry.
var origMocks []*p9test.Mock
rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock {
// Regular a regular file that has a consistent
// path number. This might be used by
// validation so we don't change it.
m := h.NewMock(parent, 0, p9.Attr{
Mode: p9.ModeRegular,
})
origMocks = append(origMocks, m)
return m
})
// Do the walk.
dirent, err := rootDir.Walk(ctx, rootDir, file)
if err != nil {
t.Fatalf("Lookup failed: %v", err)
}
// We must release the dirent, of the test will fail
// with a reference leak. This is tracked by p9test.
defer dirent.DecRef()
// Walk again. Depending on the cache policy, we may
// get a new dirent.
newDirent, err := rootDir.Walk(ctx, rootDir, file)
if err != nil {
t.Fatalf("Lookup failed: %v", err)
}
if test.preModificationWantReload && dirent == newDirent {
t.Errorf("Lookup with cachePolicy=%s got old dirent %+v, wanted a new dirent", test.cachePolicy, dirent)
}
if !test.preModificationWantReload && dirent != newDirent {
t.Errorf("Lookup with cachePolicy=%s got new dirent %+v, wanted old dirent %+v", test.cachePolicy, newDirent, dirent)
}
newDirent.DecRef() // See above.
// Modify the underlying mocked file's modification
// time for the next walk that occurs.
nowSeconds := time.Now().Unix()
rootFile.AddChild(file, func(parent *p9test.Mock) *p9test.Mock {
// Ensure that the path is the same as above,
// but we change only the modification time of
// the file.
return h.NewMock(parent, 0, p9.Attr{
Mode: p9.ModeRegular,
MTimeSeconds: uint64(nowSeconds),
})
})
// We also modify the original time, so that GetAttr
// behaves as expected for the caching case.
for _, m := range origMocks {
m.Attr.MTimeSeconds = uint64(nowSeconds)
}
// Walk again. Depending on the cache policy, we may
// get a new dirent.
newDirent, err = rootDir.Walk(ctx, rootDir, file)
if err != nil {
t.Fatalf("Lookup failed: %v", err)
}
if test.postModificationWantReload && dirent == newDirent {
t.Errorf("Lookup with cachePolicy=%s got old dirent, wanted a new dirent", test.cachePolicy)
}
if !test.postModificationWantReload && dirent != newDirent {
t.Errorf("Lookup with cachePolicy=%s got new dirent, wanted old dirent", test.cachePolicy)
}
uattrs, err := newDirent.Inode.UnstableAttr(ctx)
if err != nil {
t.Fatalf("Error getting unstable attrs: %v", err)
}
gotModTimeSeconds := uattrs.ModificationTime.Seconds()
if test.postModificationWantUpdatedAttrs && gotModTimeSeconds != nowSeconds {
t.Fatalf("Lookup with cachePolicy=%s got new modification time %v, wanted %v", test.cachePolicy, gotModTimeSeconds, nowSeconds)
}
newDirent.DecRef() // See above.
// Remove the file from the remote fs, subsequent walks
// should now fail to find anything.
rootFile.RemoveChild(file)
// Walk again. Depending on the cache policy, we may
// get ENOENT.
newDirent, err = rootDir.Walk(ctx, rootDir, file)
if test.postRemovalWantReload && err == nil {
t.Errorf("Lookup with cachePolicy=%s got nil error, wanted ENOENT", test.cachePolicy)
}
if !test.postRemovalWantReload && (err != nil || dirent != newDirent) {
t.Errorf("Lookup with cachePolicy=%s got new dirent and error %v, wanted old dirent and nil error", test.cachePolicy, err)
}
if err == nil {
newDirent.DecRef() // See above.
}
})
}
}
|