Files
visor/pkg/lisafs/node_test.go
T
Ayush RanjanandgVisor bot ad40a1566c Introduce filesystem tree nodes to lisafs.
This is very similar to p9 in design and helps address all the discussed
security concerns around the old lisafs design.

In p9, the path tree grows unbounded. Once a path is walked, those path nodes
exist for the lifetime of the server. That hurts memory performance and usage.
Additionally, nodes are allocated separately from FidRefs which are allocated
separately from Files. Each path node also has 3 hashmaps tracking various
things. This leads to a LOT of allocations.

In lisafs, we cut down almost all the additional allocations. Nodes have a
bounded lifetime. Once all refs on node are dropped, the node is removed from
the filesystem tree. In the overwhelming common case, where the client is not
compromised and is behaving correctly (gofer client in sentry), the lifecycle
of the ControlFD and Node are equivalent. So lisafs allocates them together.

lisafs also only uses 1 hashmap in each node to track children. This too has
been optimized. Experimentation showed that majority of directories have at
most 3 children to track. So we only allocate the hashmap once we hit 4
children. Before that, we statically track 3 children using hardcoded pointers.

Tested: All 221 syscall tests pass with lisafs.
PiperOrigin-RevId: 424933787
2022-01-28 12:41:25 -08:00

87 lines
2.4 KiB
Go

// Copyright 2022 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 lisafs
import (
"fmt"
"testing"
)
func TestLookup(t *testing.T) {
for _, numChildren := range []int{
numStaticChildren, // For static children.
2 * numStaticChildren, // For dynamic children.
} {
t.Run(fmt.Sprintf("%dChildren", numChildren), func(t *testing.T) {
var root Node
root.InitLocked("", nil)
root.childrenMu.Lock()
defer root.childrenMu.Unlock()
// truth is the source of truth.
truth := make(map[string]*Node)
// Fill node with children.
for i := 0; i < numChildren; i++ {
name := fmt.Sprintf("%d", i)
var child Node
child.InitLocked(name, &root)
truth[name] = &child
}
// Test that lookup finds child correctly.
for i := 0; i < numChildren; i++ {
name := fmt.Sprintf("%d", i)
if got, want := root.LookupChildLocked(name), truth[name]; got != want {
t.Errorf("incorrect child returned by root: want %p, got %p", want, got)
}
}
})
}
}
func TestDelete(t *testing.T) {
for _, numChildren := range []int{
numStaticChildren, // For static children.
2 * numStaticChildren, // For dynamic children.
} {
t.Run(fmt.Sprintf("%dChildren", numChildren), func(t *testing.T) {
var root Node
root.InitLocked("", nil)
root.childrenMu.Lock()
defer root.childrenMu.Unlock()
// truth is the source of truth.
truth := make(map[string]*Node)
// Fill node with children.
for i := 0; i < numChildren; i++ {
name := fmt.Sprintf("%d", i)
var child Node
child.InitLocked(name, &root)
truth[name] = &child
}
// Now remove them and check if correct node is removed.
for i := 0; i < numChildren; i++ {
name := fmt.Sprintf("%d", i)
if got, want := root.removeChildLocked(name), truth[name]; got != want {
t.Errorf("root deleted incorrect ndoe: want %p, got %p", want, got)
}
}
})
}
}