github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/gadgets/test/gadgets_test.go (about) 1 //go:build linux 2 // +build linux 3 4 // Copyright 2022 The Inspektor Gadget authors 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package gadgets_test 19 20 import ( 21 "context" 22 "fmt" 23 "os/exec" 24 "testing" 25 "time" 26 27 "github.com/google/uuid" 28 "golang.org/x/sync/errgroup" 29 30 utilstest "github.com/inspektor-gadget/inspektor-gadget/internal/test" 31 containercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/container-collection" 32 execTracer "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/exec/tracer" 33 execTypes "github.com/inspektor-gadget/inspektor-gadget/pkg/gadgets/trace/exec/types" 34 tracercollection "github.com/inspektor-gadget/inspektor-gadget/pkg/tracer-collection" 35 "github.com/inspektor-gadget/inspektor-gadget/pkg/types" 36 ) 37 38 // Function to generate an event used most of the times. 39 // Returns pid of executed process. 40 func generateEvent(cmdName string) error { 41 cmd := exec.Command("/bin/"+cmdName, "/dev/null") 42 if err := cmd.Run(); err != nil { 43 return fmt.Errorf("running command: %w", err) 44 } 45 46 return nil 47 } 48 49 func createTestEnv( 50 t *testing.T, 51 traceName string, 52 containerName string, 53 eventCallback func(event *execTypes.Event), 54 ) *containercollection.ContainerCollection { 55 t.Helper() 56 57 cc := &containercollection.ContainerCollection{} 58 59 tc, err := tracercollection.NewTracerCollection(cc) 60 if err != nil { 61 t.Fatalf("failed to create tracer collection: %s", err) 62 } 63 t.Cleanup(tc.Close) 64 65 opts := []containercollection.ContainerCollectionOption{ 66 containercollection.WithTracerCollection(tc), 67 } 68 69 if err := cc.Initialize(opts...); err != nil { 70 t.Fatalf("failed to init container collection: %s", err) 71 } 72 t.Cleanup(cc.Close) 73 74 containerSelector := containercollection.ContainerSelector{ 75 K8s: containercollection.K8sSelector{ 76 BasicK8sMetadata: types.BasicK8sMetadata{ 77 ContainerName: containerName, 78 }, 79 }, 80 } 81 if err := tc.AddTracer(traceName, containerSelector); err != nil { 82 t.Fatalf("error adding tracer: %s", err) 83 } 84 t.Cleanup(func() { tc.RemoveTracer(traceName) }) 85 86 // Get mount namespace map to filter by containers 87 mountnsmap, err := tc.TracerMountNsMap(traceName) 88 if err != nil { 89 t.Fatalf("failed to get mountnsmap: %s", err) 90 } 91 92 // Create the tracer 93 tracer, err := execTracer.NewTracer(&execTracer.Config{MountnsMap: mountnsmap}, cc, eventCallback) 94 if err != nil { 95 t.Fatalf("failed to create tracer: %s", err) 96 } 97 t.Cleanup(tracer.Stop) 98 99 return cc 100 } 101 102 // TestContainerRemovalRaceCondition checks that a container is removed 103 // from the mount ns inode ids map fast enough to avoid capturing events 104 // from the wrong container. See 105 // https://github.com/inspektor-gadget/inspektor-gadget/issues/1001 106 func TestContainerRemovalRaceCondition(t *testing.T) { 107 t.Parallel() 108 109 utilstest.RequireRoot(t) 110 111 const ( 112 traceName = "trace_exec" 113 matchingContainer = "foo" 114 nonMatchingContainer = "bar" 115 matchingCommand = "cat" 116 nonMatchingCommand = "touch" 117 ) 118 119 eventCallback := func(event *execTypes.Event) { 120 // "nonMatchingCommand" is only executed in the 121 // "nonMatching" container that doesn't match with the 122 // filter 123 if event.Comm == nonMatchingCommand { 124 t.Fatalf("bad event captured") 125 } 126 } 127 128 cc := createTestEnv(t, traceName, matchingContainer, eventCallback) 129 130 runContainerTest := func( 131 cc *containercollection.ContainerCollection, 132 name string, 133 f func() error, 134 iterations int, 135 ) error { 136 for i := 0; i < iterations; i++ { 137 r, err := utilstest.NewRunner(&utilstest.RunnerConfig{}) 138 if err != nil { 139 return fmt.Errorf("creating runner: %w", err) 140 } 141 142 container := &containercollection.Container{ 143 Runtime: containercollection.RuntimeMetadata{ 144 BasicRuntimeMetadata: types.BasicRuntimeMetadata{ 145 ContainerID: uuid.New().String(), 146 }, 147 }, 148 Mntns: r.Info.MountNsID, 149 Pid: uint32(r.Info.Tid), 150 K8s: containercollection.K8sMetadata{ 151 BasicK8sMetadata: types.BasicK8sMetadata{ 152 ContainerName: name, 153 }, 154 }, 155 } 156 157 cc.AddContainer(container) 158 159 if err := r.Run(f); err != nil { 160 return fmt.Errorf("running command: %w", err) 161 } 162 163 r.Close() 164 165 // Use a delay to simulate the time it requires Inspektor Gadget to remove the 166 // container after it's notified. Notice that the container hooks are not blocking 167 // on the remove events, hence when we get the notification the container is already 168 // gone. 169 time.AfterFunc(1*time.Millisecond, func() { cc.RemoveContainer(container.Runtime.ContainerID) }) 170 } 171 172 return nil 173 } 174 175 const n = 1000 176 177 errs, _ := errgroup.WithContext(context.TODO()) 178 179 errs.Go(func() error { 180 catDevNull := func() error { return generateEvent(matchingCommand) } 181 return runContainerTest(cc, matchingContainer, catDevNull, n) 182 }) 183 errs.Go(func() error { 184 touchDevNull := func() error { return generateEvent(nonMatchingCommand) } 185 return runContainerTest(cc, nonMatchingContainer, touchDevNull, n) 186 }) 187 188 if err := errs.Wait(); err != nil { 189 t.Fatalf("failed generating events: %s", err) 190 } 191 } 192 193 // TestEventEnrichmentRaceCondition checks that an event is properly enriched when the generating 194 // container is removed soon after it generates the event. 195 // https://github.com/inspektor-gadget/inspektor-gadget/issues/1178 196 func TestEventEnrichmentRaceCondition(t *testing.T) { 197 t.Parallel() 198 199 utilstest.RequireRoot(t) 200 201 const ( 202 traceName = "trace_exec" 203 containerName = "foo" 204 command = "cat" 205 ) 206 207 eventCallback := func(event *execTypes.Event) { 208 if event.K8s.ContainerName == "" { 209 t.Fatal("event not enriched") 210 } 211 } 212 213 cc := createTestEnv(t, traceName, containerName, eventCallback) 214 215 const n = 1000 216 217 errs, _ := errgroup.WithContext(context.TODO()) 218 219 runContainerTest := func( 220 cc *containercollection.ContainerCollection, 221 name string, 222 f func() error, 223 iterations int, 224 ) error { 225 for i := 0; i < iterations; i++ { 226 r, err := utilstest.NewRunner(&utilstest.RunnerConfig{}) 227 if err != nil { 228 return fmt.Errorf("creating runner: %w", err) 229 } 230 231 container := &containercollection.Container{ 232 Runtime: containercollection.RuntimeMetadata{ 233 BasicRuntimeMetadata: types.BasicRuntimeMetadata{ 234 ContainerID: uuid.New().String(), 235 }, 236 }, 237 Mntns: r.Info.MountNsID, 238 Pid: uint32(r.Info.Tid), 239 K8s: containercollection.K8sMetadata{ 240 BasicK8sMetadata: types.BasicK8sMetadata{ 241 ContainerName: name, 242 }, 243 }, 244 } 245 246 cc.AddContainer(container) 247 // Remove the container right after it generates the command. Running this 248 // after r.Run() will be too late, so let's do in a different goroutine to 249 // have some time. 250 go func() { cc.RemoveContainer(container.Runtime.ContainerID) }() 251 252 if err := r.Run(f); err != nil { 253 return fmt.Errorf("running command: %w", err) 254 } 255 256 r.Close() 257 } 258 259 return nil 260 } 261 262 errs.Go(func() error { 263 catDevNull := func() error { return generateEvent(command) } 264 return runContainerTest(cc, containerName, catDevNull, n) 265 }) 266 267 if err := errs.Wait(); err != nil { 268 t.Fatalf("failed generating events: %s", err) 269 } 270 }