github.com/cilium/cilium@v1.16.2/pkg/bpf/ops_linux_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package bpf 5 6 import ( 7 "context" 8 "encoding" 9 "errors" 10 "testing" 11 12 "github.com/cilium/ebpf" 13 "github.com/cilium/hive/cell" 14 "github.com/cilium/hive/hivetest" 15 "github.com/cilium/statedb" 16 "github.com/cilium/statedb/index" 17 "github.com/cilium/statedb/reconciler" 18 "github.com/sirupsen/logrus" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 22 "github.com/cilium/cilium/pkg/hive" 23 "github.com/cilium/cilium/pkg/logging" 24 "github.com/cilium/cilium/pkg/testutils" 25 "github.com/cilium/cilium/pkg/time" 26 ) 27 28 type TestObject struct { 29 Key TestKey 30 Value TestValue 31 Status reconciler.Status 32 } 33 34 func (o *TestObject) BinaryKey() encoding.BinaryMarshaler { 35 return StructBinaryMarshaler{&o.Key} 36 } 37 38 func (o *TestObject) BinaryValue() encoding.BinaryMarshaler { 39 return StructBinaryMarshaler{&o.Value} 40 } 41 42 type emptyIterator struct{} 43 44 func (*emptyIterator) Next() (*TestObject, uint64, bool) { 45 return nil, 0, false 46 } 47 48 var _ statedb.Iterator[*TestObject] = &emptyIterator{} 49 50 func Test_MapOps(t *testing.T) { 51 testutils.PrivilegedTest(t) 52 53 testMap := NewMap("cilium_ops_test", 54 ebpf.Hash, 55 &TestKey{}, 56 &TestValue{}, 57 maxEntries, 58 BPF_F_NO_PREALLOC, 59 ) 60 61 err := testMap.OpenOrCreate() 62 require.NoError(t, err, "OpenOrCreate") 63 defer testMap.Close() 64 65 ctx := context.TODO() 66 ops := NewMapOps[*TestObject](testMap) 67 obj := &TestObject{Key: TestKey{1}, Value: TestValue{2}} 68 69 // Test Update() and Delete() 70 err = ops.Update(ctx, nil, obj) 71 assert.NoError(t, err, "Update") 72 73 err = ops.Update(ctx, nil, obj) 74 assert.NoError(t, err, "Update") 75 76 v, err := testMap.Lookup(&TestKey{1}) 77 assert.NoError(t, err, "Lookup") 78 assert.Equal(t, v.(*TestValue).Value, obj.Value.Value) 79 80 err = ops.Delete(ctx, nil, obj) 81 assert.NoError(t, err, "Delete") 82 83 _, err = testMap.Lookup(&TestKey{1}) 84 assert.Error(t, err, "Lookup") 85 86 // Test Prune() 87 err = testMap.Update(&TestKey{2}, &TestValue{3}) 88 assert.NoError(t, err, "Update") 89 90 v, err = testMap.Lookup(&TestKey{2}) 91 if assert.NoError(t, err, "Lookup") { 92 assert.Equal(t, v.(*TestValue).Value, uint32(3)) 93 } 94 95 // Give Prune() an empty set of objects, which should cause it to 96 // remove everything. 97 err = ops.Prune(ctx, nil, &emptyIterator{}) 98 assert.NoError(t, err, "Prune") 99 100 data := map[string][]string{} 101 testMap.Dump(data) 102 assert.Len(t, data, 0) 103 } 104 105 func Test_MapOpsPrune(t *testing.T) { 106 testutils.PrivilegedTest(t) 107 108 // This tests pruning with an LPM trie. This ensures we do not regress, as 109 // previously we had issues with Prune concurrently iterating and deleting 110 // entries, which caused the iteration to skip entries 111 testMap := NewMap( 112 "cilium_ops_prune_test", 113 ebpf.LPMTrie, 114 &TestLPMKey{}, 115 &TestValue{}, 116 maxEntries, 117 BPF_F_NO_PREALLOC, 118 ) 119 err := testMap.OpenOrCreate() 120 require.NoError(t, err, "OpenOrCreate") 121 defer testMap.Close() 122 123 ctx := context.TODO() 124 ops := NewMapOps[*TestObject](testMap) 125 126 // Fill map with similarly prefixed entries 127 err = testMap.Update(&TestLPMKey{32, 0xFF00_00FF}, &TestValue{0}) 128 assert.NoError(t, err, "Update 0") 129 err = testMap.Update(&TestLPMKey{32, 0xFF01_01FF}, &TestValue{1}) 130 assert.NoError(t, err, "Update 1") 131 err = testMap.Update(&TestLPMKey{32, 0xFF02_02FF}, &TestValue{2}) 132 assert.NoError(t, err, "Update 2") 133 err = testMap.Update(&TestLPMKey{32, 0xFF03_03FF}, &TestValue{3}) 134 assert.NoError(t, err, "Update 3") 135 136 // Prune should now remove everything 137 err = ops.Prune(ctx, nil, &emptyIterator{}) 138 assert.NoError(t, err, "Prune") 139 140 data := map[string][]string{} 141 testMap.Dump(data) 142 assert.Len(t, data, 0) 143 } 144 145 // Test_MapOps_ReconcilerExample serves as a testable example for the map ops. 146 // This is not an "Example*" function as it can only run privileged. 147 func Test_MapOps_ReconcilerExample(t *testing.T) { 148 testutils.PrivilegedTest(t) 149 150 exampleMap := NewMap("example", 151 ebpf.Hash, 152 &TestKey{}, 153 &TestValue{}, 154 maxEntries, 155 BPF_F_NO_PREALLOC, 156 ) 157 err := exampleMap.OpenOrCreate() 158 require.NoError(t, err) 159 t.Cleanup(func() { exampleMap.Close() }) 160 161 // Create the table containing the desired state of the map. 162 keyIndex := statedb.Index[*TestObject, uint32]{ 163 Name: "example", 164 FromObject: func(obj *TestObject) index.KeySet { 165 return index.NewKeySet(index.Uint32(obj.Key.Key)) 166 }, 167 FromKey: index.Uint32, 168 Unique: true, 169 } 170 table, err := statedb.NewTable("example", keyIndex) 171 require.NoError(t, err, "NewTable") 172 173 // Create the map operations and the reconciler configuration. 174 ops := NewMapOps[*TestObject](exampleMap) 175 176 // Silence the hive log output. 177 oldLogLevel := logging.DefaultLogger.GetLevel() 178 logging.SetLogLevel(logrus.ErrorLevel) 179 t.Cleanup(func() { 180 logging.SetLogLevel(oldLogLevel) 181 }) 182 183 // Setup and start a hive to run the reconciler. 184 var db *statedb.DB 185 h := hive.New( 186 cell.Module( 187 "example", 188 "Example", 189 190 cell.Invoke( 191 func(db_ *statedb.DB) error { 192 db = db_ 193 return db.RegisterTable(table) 194 }, 195 ), 196 cell.Invoke( 197 func(params reconciler.Params) error { 198 _, err := reconciler.Register[*TestObject]( 199 params, 200 table, 201 func(obj *TestObject) *TestObject { 202 obj2 := *obj 203 return &obj2 204 }, 205 func(obj *TestObject, s reconciler.Status) *TestObject { 206 obj.Status = s 207 return obj 208 }, 209 func(obj *TestObject) reconciler.Status { 210 return obj.Status 211 }, 212 ops, 213 nil, 214 ) 215 return err 216 }), 217 ), 218 ) 219 220 tlog := hivetest.Logger(t) 221 err = h.Start(tlog, context.Background()) 222 require.NoError(t, err, "Start") 223 224 t.Cleanup(func() { 225 h.Stop(tlog, context.Background()) 226 }) 227 228 // Insert an object to the desired state and wait for it to reconcile. 229 txn := db.WriteTxn(table) 230 table.Insert(txn, &TestObject{ 231 Key: TestKey{1}, 232 Value: TestValue{2}, 233 234 // Mark the object to be pending for reconciliation. Without this 235 // the reconciler would ignore this object. 236 Status: reconciler.StatusPending(), 237 }) 238 txn.Commit() 239 240 for { 241 obj, _, watch, ok := table.GetWatch(db.ReadTxn(), keyIndex.Query(1)) 242 if ok { 243 if obj.Status.Kind == reconciler.StatusKindDone { 244 // The object has been reconciled. 245 break 246 } 247 t.Logf("Object not done yet: %#v", obj) 248 } 249 // Wait for the object to update 250 <-watch 251 } 252 253 v, err := exampleMap.Lookup(&TestKey{1}) 254 require.NoError(t, err, "Lookup") 255 require.Equal(t, uint32(2), v.(*TestValue).Value) 256 257 // Mark the object for deletion 258 txn = db.WriteTxn(table) 259 table.Delete(txn, &TestObject{ 260 Key: TestKey{1}, 261 Value: TestValue{2}, 262 }) 263 txn.Commit() 264 265 for { 266 obj, _, watch, ok := table.GetWatch(db.ReadTxn(), keyIndex.Query(1)) 267 if !ok { 268 // The object has been successfully deleted. 269 break 270 } 271 t.Logf("Object not deleted yet: %#v", obj) 272 // Wait for the object to update 273 <-watch 274 } 275 276 require.Eventually( 277 t, 278 func() bool { 279 _, err = exampleMap.Lookup(&TestKey{1}) 280 return errors.Is(err, ebpf.ErrKeyNotExist) 281 }, 282 time.Second, 283 100*time.Millisecond, 284 "Expected key to eventually be removed") 285 286 }