map_linux_test.go
// Copyright 2018-2019 Authors of Cilium
//
// 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.
// +build privileged_tests
package bpf
import (
"fmt"
"os"
"strconv"
"strings"
"sync"
"testing"
"unsafe"
. "gopkg.in/check.v1"
"github.com/cilium/cilium/pkg/checker"
)
// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }
type BPFPrivilegedTestSuite struct{}
type TestKey struct {
Key uint32
}
type TestValue struct {
Value uint32
}
func (k *TestKey) String() string { return fmt.Sprintf("key=%d", k.Key) }
func (k *TestKey) GetKeyPtr() unsafe.Pointer { return unsafe.Pointer(k) }
func (k *TestKey) NewValue() MapValue { return &TestValue{} }
func (k *TestKey) DeepCopyMapKey() MapKey { return &TestKey{k.Key} }
func (v *TestValue) String() string { return fmt.Sprintf("value=%d", v.Value) }
func (v *TestValue) GetValuePtr() unsafe.Pointer { return unsafe.Pointer(v) }
func (v *TestValue) DeepCopyMapValue() MapValue { return &TestValue{v.Value} }
var _ = Suite(&BPFPrivilegedTestSuite{})
var (
maxEntries = 16
testMap = NewMap("cilium_test",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
maxEntries,
BPF_F_NO_PREALLOC,
0,
ConvertKeyValue,
).WithCache()
)
func runTests(m *testing.M) (int, error) {
CheckOrMountFS("", false)
if err := ConfigureResourceLimits(); err != nil {
return 1, fmt.Errorf("Failed to configure rlimit")
}
_, err := testMap.OpenOrCreate()
if err != nil {
return 1, fmt.Errorf("Failed to create map")
}
defer func() {
path, _ := testMap.Path()
os.Remove(path)
}()
defer testMap.Close()
return m.Run(), nil
}
func TestMain(m *testing.M) {
exitCode, err := runTests(m)
if err != nil {
log.Fatal(err)
}
os.Exit(exitCode)
}
func (s *BPFPrivilegedTestSuite) TestGetMapInfo(c *C) {
mi, err := GetMapInfo(os.Getpid(), testMap.GetFd())
c.Assert(err, IsNil)
// Check OpenMap warning section
testMap.MapKey = nil
testMap.MapValue = nil
defer func() {
testMap.MapKey = &TestKey{}
testMap.MapValue = &TestValue{}
}()
c.Assert(&testMap.MapInfo, checker.DeepEquals, mi)
}
func (s *BPFPrivilegedTestSuite) TestOpen(c *C) {
// Ensure that os.IsNotExist() can be used with Map.Open()
noSuchMap := NewMap("cilium_test_no_exist",
MapTypeHash, &TestKey{}, 4, &TestValue{}, 4, maxEntries, 0, 0, nil)
err := noSuchMap.Open()
c.Assert(os.IsNotExist(err), Equals, true)
c.Assert(err, ErrorMatches, ".*cilium_test_no_exist.*")
// existingMap is the same as testMap. Opening should succeed.
existingMap := NewMap("cilium_test",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
maxEntries,
BPF_F_NO_PREALLOC,
0,
ConvertKeyValue).WithCache()
err = existingMap.Open()
c.Check(err, IsNil) // Avoid assert to ensure Close() is called below.
err = existingMap.Open() // Reopen should be no-op.
c.Check(err, IsNil)
err = existingMap.Close()
c.Assert(err, IsNil)
}
func (s *BPFPrivilegedTestSuite) TestOpenMap(c *C) {
openedMap, err := OpenMap("cilium_test_no_exist")
c.Assert(err, Not(IsNil))
c.Assert(openedMap, IsNil)
openedMap, err = OpenMap("cilium_test")
c.Assert(err, IsNil)
// Check OpenMap warning section
testMap.MapKey = nil
testMap.MapValue = nil
defer func() {
testMap.MapKey = &TestKey{}
testMap.MapValue = &TestValue{}
}()
noDiff := openedMap.DeepEquals(testMap)
c.Assert(noDiff, Equals, true)
}
func (s *BPFPrivilegedTestSuite) TestOpenOrCreate(c *C) {
// existingMap is the same as testMap. OpenOrCreate should skip recreation.
existingMap := NewMap("cilium_test",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
maxEntries,
BPF_F_NO_PREALLOC,
0,
ConvertKeyValue).WithCache()
isNew, err := existingMap.OpenOrCreate()
c.Assert(err, IsNil)
c.Assert(isNew, Equals, false)
// preallocMap unsets BPF_F_NO_PREALLOC. OpenOrCreate should recreate map.
EnableMapPreAllocation() // prealloc on/off is controllable in HASH map case.
preallocMap := NewMap("cilium_test",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
maxEntries,
0,
0,
ConvertKeyValue).WithCache()
isNew, err = preallocMap.OpenOrCreate()
defer preallocMap.Close()
c.Assert(err, IsNil)
c.Assert(isNew, Equals, true)
DisableMapPreAllocation()
// preallocMap is already open. OpenOrCreate does nothing.
isNew, err = preallocMap.OpenOrCreate()
c.Assert(err, IsNil)
c.Assert(isNew, Equals, false)
}
func (s *BPFPrivilegedTestSuite) TestOpenParallel(c *C) {
parallelMap := NewMap("cilium_test",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
maxEntries,
BPF_F_NO_PREALLOC,
0,
ConvertKeyValue).WithCache()
isNew, err := parallelMap.OpenParallel()
defer parallelMap.Close()
c.Assert(err, IsNil)
c.Assert(isNew, Equals, true)
isNew, err = parallelMap.OpenParallel()
c.Assert(isNew, Equals, false)
c.Assert(err, Not(IsNil))
// Check OpenMap warning section
noDiff := parallelMap.DeepEquals(testMap)
c.Assert(noDiff, Equals, true)
key1 := &TestKey{Key: 101}
value1 := &TestValue{Value: 201}
key2 := &TestKey{Key: 102}
value2 := &TestValue{Value: 202}
err = testMap.Update(key1, value1)
c.Assert(err, IsNil)
err = parallelMap.Update(key2, value2)
c.Assert(err, IsNil)
value, err := testMap.Lookup(key1)
c.Assert(err, IsNil)
c.Assert(value, checker.DeepEquals, value1)
value, err = testMap.Lookup(key2)
c.Assert(err, Not(IsNil))
c.Assert(value, IsNil)
value, err = parallelMap.Lookup(key1)
c.Assert(err, Not(IsNil))
c.Assert(value, IsNil)
value, err = parallelMap.Lookup(key2)
c.Assert(err, IsNil)
c.Assert(value, checker.DeepEquals, value2)
parallelMap.EndParallelMode()
}
func (s *BPFPrivilegedTestSuite) TestBasicManipulation(c *C) {
// existingMap is the same as testMap. Opening should succeed.
existingMap := NewMap("cilium_test",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
maxEntries,
BPF_F_NO_PREALLOC,
0,
ConvertKeyValue).WithCache()
err := existingMap.Open()
defer existingMap.Close()
c.Assert(err, IsNil)
key1 := &TestKey{Key: 103}
value1 := &TestValue{Value: 203}
key2 := &TestKey{Key: 104}
value2 := &TestValue{Value: 204}
err = existingMap.Update(key1, value1)
c.Assert(err, IsNil)
// key val
// 103 203
value, err := existingMap.Lookup(key1)
c.Assert(err, IsNil)
c.Assert(value, checker.DeepEquals, value1)
value, err = existingMap.Lookup(key2)
c.Assert(err, Not(IsNil))
c.Assert(value, Equals, nil)
err = existingMap.Update(key1, value2)
c.Assert(err, IsNil)
// key val
// 103 204
value, err = existingMap.Lookup(key1)
c.Assert(err, IsNil)
c.Assert(value, checker.DeepEquals, value2)
err = existingMap.Update(key2, value2)
c.Assert(err, IsNil)
// key val
// 103 204
// 104 204
value, err = existingMap.Lookup(key1)
c.Assert(err, IsNil)
c.Assert(value, checker.DeepEquals, value2)
value, err = existingMap.Lookup(key2)
c.Assert(err, IsNil)
c.Assert(value, checker.DeepEquals, value2)
err = existingMap.Delete(key1)
c.Assert(err, IsNil)
// key val
// 104 204
value, err = existingMap.Lookup(key1)
c.Assert(err, Not(IsNil))
c.Assert(value, Equals, nil)
err = existingMap.DeleteAll()
c.Assert(err, IsNil)
value, err = existingMap.Lookup(key1)
c.Assert(err, Not(IsNil))
c.Assert(value, Equals, nil)
err = existingMap.DeleteAll()
c.Assert(err, IsNil)
}
func (s *BPFPrivilegedTestSuite) TestDump(c *C) {
key1 := &TestKey{Key: 105}
value1 := &TestValue{Value: 205}
key2 := &TestKey{Key: 106}
value2 := &TestValue{Value: 206}
err := testMap.Update(key1, value1)
c.Assert(err, IsNil)
err = testMap.Update(key2, value1)
c.Assert(err, IsNil)
err = testMap.Update(key2, value2)
c.Assert(err, IsNil)
dump1 := map[string][]string{}
testMap.Dump(dump1)
c.Assert(dump1, checker.DeepEquals, map[string][]string{
"key=105": {"value=205"},
"key=106": {"value=206"},
})
dump2 := map[string][]string{}
customCb := func(key MapKey, value MapValue) {
dump2[key.String()] = append(dump2[key.String()], "custom-"+value.String())
}
testMap.DumpWithCallback(customCb)
c.Assert(dump2, checker.DeepEquals, map[string][]string{
"key=105": {"custom-value=205"},
"key=106": {"custom-value=206"},
})
dump3 := map[string][]string{}
noSuchMap := NewMap("cilium_test_no_exist",
MapTypeHash, &TestKey{}, 4, &TestValue{}, 4, maxEntries, 0, 0, nil)
err = noSuchMap.DumpIfExists(dump3)
c.Assert(err, IsNil)
c.Assert(len(dump3), Equals, 0)
dump2 = map[string][]string{}
err = noSuchMap.DumpWithCallbackIfExists(customCb)
c.Assert(err, IsNil)
c.Assert(len(dump2), Equals, 0)
// Validate that if the key is zero, it shows up in dump output.
keyZero := &TestKey{Key: 0}
valueZero := &TestValue{Value: 0}
err = testMap.Update(keyZero, valueZero)
c.Assert(err, IsNil)
dump4 := map[string][]string{}
customCb = func(key MapKey, value MapValue) {
dump4[key.String()] = append(dump4[key.String()], "custom-"+value.String())
}
ds := NewDumpStats(testMap)
err = testMap.DumpReliablyWithCallback(customCb, ds)
c.Assert(err, IsNil)
c.Assert(dump4, checker.DeepEquals, map[string][]string{
"key=0": {"custom-value=0"},
"key=105": {"custom-value=205"},
"key=106": {"custom-value=206"},
})
dump5 := map[string][]string{}
err = testMap.Dump(dump5)
c.Assert(err, IsNil)
c.Assert(dump5, checker.DeepEquals, map[string][]string{
"key=0": {"value=0"},
"key=105": {"value=205"},
"key=106": {"value=206"},
})
}
func (s *BPFPrivilegedTestSuite) TestDumpReliablyWithCallback(c *C) {
maxEntries := uint32(256)
m := NewMap("cilium_dump_test",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
int(maxEntries),
BPF_F_NO_PREALLOC,
0,
ConvertKeyValue,
).WithCache()
_, err := m.OpenOrCreate()
c.Assert(err, IsNil)
defer func() {
path, _ := m.Path()
os.Remove(path)
}()
defer m.Close()
for i := uint32(4); i < maxEntries; i++ {
err := m.Update(&TestKey{Key: i}, &TestValue{Value: i + 100})
c.Check(err, IsNil) // we want to run the deferred calls
}
// start a goroutine that continuously updates the map
started := make(chan struct{}, 1)
done := make(chan struct{}, 1)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
started <- struct{}{}
for {
for i := uint32(0); i < 4; i++ {
if i < 3 {
err := m.Update(&TestKey{Key: i}, &TestValue{Value: i + 100})
// avoid assert to ensure we call wg.Done
c.Check(err, IsNil)
}
if i > 0 {
err := m.Delete(&TestKey{Key: i - 1})
// avoid assert to ensure we call wg.Done
c.Check(err, IsNil)
}
}
select {
case <-done:
return
default:
}
}
}()
<-started // wait until the routine has started to start the actual tests
wg.Add(1)
go func() {
defer wg.Done()
expect := map[string]string{}
for i := uint32(4); i < maxEntries; i++ {
expect[fmt.Sprintf("key=%d", i)] = fmt.Sprintf("custom-value=%d", i+100)
}
for i := 0; i < 100; i++ {
dump := map[string]string{}
customCb := func(key MapKey, value MapValue) {
k, err := strconv.ParseUint(strings.TrimPrefix(key.String(), "key="), 10, 32)
c.Check(err, IsNil)
if uint32(k) >= 4 {
dump[key.String()] = "custom-" + value.String()
}
}
ds := NewDumpStats(m)
if i == 0 {
// artificially trigger MaxLookupError as max lookup is based
// on ds.MaxEntries
ds.MaxEntries = 1
}
if err := m.DumpReliablyWithCallback(customCb, ds); err != nil {
// avoid Assert to ensure the done signal is sent
c.Check(err, Equals, ErrMaxLookup)
} else {
// avoid Assert to ensure the done signal is sent
c.Check(dump, checker.DeepEquals, expect)
}
}
done <- struct{}{}
}()
wg.Wait()
}
func (s *BPFPrivilegedTestSuite) TestDeleteAll(c *C) {
key1 := &TestKey{Key: 105}
value1 := &TestValue{Value: 205}
key2 := &TestKey{Key: 106}
value2 := &TestValue{Value: 206}
err := testMap.Update(key1, value1)
c.Assert(err, IsNil)
err = testMap.Update(key2, value1)
c.Assert(err, IsNil)
err = testMap.Update(key2, value2)
c.Assert(err, IsNil)
keyZero := &TestKey{Key: 0}
valueZero := &TestValue{Value: 0}
err = testMap.Update(keyZero, valueZero)
c.Assert(err, IsNil)
dump1 := map[string][]string{}
err = testMap.Dump(dump1)
c.Assert(err, IsNil)
c.Assert(dump1, checker.DeepEquals, map[string][]string{
"key=0": {"value=0"},
"key=105": {"value=205"},
"key=106": {"value=206"},
})
err = testMap.DeleteAll()
c.Assert(err, IsNil)
dump2 := map[string][]string{}
err = testMap.Dump(dump2)
c.Assert(err, IsNil)
}
func (s *BPFPrivilegedTestSuite) TestGetModel(c *C) {
model := testMap.GetModel()
c.Assert(model, Not(IsNil))
}
func (s *BPFPrivilegedTestSuite) TestCheckAndUpgrade(c *C) {
// CheckAndUpgrade removes map file if upgrade is needed
// so we setup and use another map.
upgradeMap := NewMap("cilium_test_upgrade",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
maxEntries,
BPF_F_NO_PREALLOC,
0,
ConvertKeyValue).WithCache()
_, err := upgradeMap.OpenOrCreate()
c.Assert(err, IsNil)
defer func() {
path, _ := upgradeMap.Path()
os.Remove(path)
}()
defer upgradeMap.Close()
// Exactly the same MapInfo so it won't be upgraded.
upgrade := upgradeMap.CheckAndUpgrade(&upgradeMap.MapInfo)
c.Assert(upgrade, Equals, false)
// preallocMap unsets BPF_F_NO_PREALLOC so upgrade is needed.
EnableMapPreAllocation()
preallocMap := NewMap("cilium_test_upgrade",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
maxEntries,
0,
0,
ConvertKeyValue).WithCache()
upgrade = upgradeMap.CheckAndUpgrade(&preallocMap.MapInfo)
c.Assert(upgrade, Equals, true)
DisableMapPreAllocation()
}
func (s *BPFPrivilegedTestSuite) TestUnpin(c *C) {
var exist bool
unpinMap := NewMap("cilium_test_unpin",
MapTypeHash,
&TestKey{},
int(unsafe.Sizeof(TestKey{})),
&TestValue{},
int(unsafe.Sizeof(TestValue{})),
maxEntries,
BPF_F_NO_PREALLOC,
0,
ConvertKeyValue).WithCache()
_, err := unpinMap.OpenOrCreate()
c.Assert(err, IsNil)
exist, err = unpinMap.exist()
c.Assert(err, IsNil)
c.Assert(exist, Equals, true)
err = unpinMap.Unpin()
c.Assert(err, IsNil)
exist, err = unpinMap.exist()
c.Assert(err, IsNil)
c.Assert(exist, Equals, false)
err = unpinMap.UnpinIfExists()
c.Assert(err, IsNil)
exist, err = unpinMap.exist()
c.Assert(err, IsNil)
c.Assert(exist, Equals, false)
err = UnpinMapIfExists("cilium_test_unpin")
c.Assert(err, IsNil)
_, err = unpinMap.OpenOrCreate()
c.Assert(err, IsNil)
err = UnpinMapIfExists("cilium_test_unpin")
c.Assert(err, IsNil)
exist, err = unpinMap.exist()
c.Assert(err, IsNil)
c.Assert(exist, Equals, false)
}