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  }