diff --git a/pkg/network/usm/istio_test.go b/pkg/network/usm/istio_test.go index 2fdcf4a52e7c40..9ba407ae266af0 100644 --- a/pkg/network/usm/istio_test.go +++ b/pkg/network/usm/istio_test.go @@ -8,7 +8,6 @@ package usm import ( - "fmt" "os" "os/exec" "path/filepath" @@ -17,19 +16,17 @@ import ( "github.com/DataDog/datadog-agent/pkg/network/config" "github.com/DataDog/datadog-agent/pkg/network/usm/utils" - "github.com/DataDog/datadog-agent/pkg/util/kernel" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGetEnvoyPath(t *testing.T) { - _ = createFakeProcFS(t) + _, pid := createFakeProc(t, "/bin") monitor := newIstioTestMonitor(t) t.Run("an actual envoy process", func(t *testing.T) { - path := monitor.getEnvoyPath(uint32(1)) - assert.True(t, strings.HasSuffix(path, "/usr/local/bin/envoy")) + path := monitor.getEnvoyPath(uint32(pid)) + assert.True(t, strings.HasSuffix(path, "/bin/envoy")) }) t.Run("something else", func(t *testing.T) { path := monitor.getEnvoyPath(uint32(2)) @@ -38,20 +35,21 @@ func TestGetEnvoyPath(t *testing.T) { } func TestGetEnvoyPathWithConfig(t *testing.T) { - t.Setenv("DD_SERVICE_MONITORING_CONFIG_TLS_ISTIO_ENVOY_PATH", "/envoy-conf") + t.Setenv("DD_SERVICE_MONITORING_CONFIG_TLS_ISTIO_ENVOY_PATH", "/test/envoy") - _ = createFakeProcFS(t) + _, pid := createFakeProc(t, "/test") monitor := newIstioTestMonitor(t) t.Run("an actual envoy process", func(t *testing.T) { - path := monitor.getEnvoyPath(uint32(4)) - assert.True(t, strings.HasSuffix(path, "/usr/local/bin/envoy-conf")) + path := monitor.getEnvoyPath(uint32(pid)) + assert.True(t, strings.HasSuffix(path, "/test/envoy")) }) } func TestIstioSync(t *testing.T) { t.Run("calling sync for the first time", func(t *testing.T) { - procRoot := createFakeProcFS(t) + procRoot1, _ := createFakeProc(t, filepath.Join("test1", "bin")) + procRoot2, _ := createFakeProc(t, filepath.Join("test2", "bin")) monitor := newIstioTestMonitor(t) registerRecorder := new(utils.CallbackRecorder) @@ -62,10 +60,10 @@ func TestIstioSync(t *testing.T) { // Calling sync should detect the two envoy processes monitor.sync() - pathID1, err := utils.NewPathIdentifier(filepath.Join(procRoot, "1", "root", "usr", "local", "bin", "envoy")) + pathID1, err := utils.NewPathIdentifier(filepath.Join(procRoot1, "test1", "bin", "envoy")) require.NoError(t, err) - pathID2, err := utils.NewPathIdentifier(filepath.Join(procRoot, "3", "root", "usr", "local", "bin", "envoy")) + pathID2, err := utils.NewPathIdentifier(filepath.Join(procRoot2, "test2", "bin", "envoy")) require.NoError(t, err) assert.Equal(t, 2, registerRecorder.TotalCalls()) @@ -74,7 +72,8 @@ func TestIstioSync(t *testing.T) { }) t.Run("calling sync multiple times", func(t *testing.T) { - procRoot := createFakeProcFS(t) + procRoot1, _ := createFakeProc(t, filepath.Join("test1", "bin")) + procRoot2, _ := createFakeProc(t, filepath.Join("test2", "bin")) monitor := newIstioTestMonitor(t) registerRecorder := new(utils.CallbackRecorder) @@ -89,10 +88,10 @@ func TestIstioSync(t *testing.T) { monitor.sync() monitor.sync() - pathID1, err := utils.NewPathIdentifier(filepath.Join(procRoot, "1/root/usr/local/bin/envoy")) + pathID1, err := utils.NewPathIdentifier(filepath.Join(procRoot1, "test1", "bin", "envoy")) require.NoError(t, err) - pathID2, err := utils.NewPathIdentifier(filepath.Join(procRoot, "3/root/usr/local/bin/envoy")) + pathID2, err := utils.NewPathIdentifier(filepath.Join(procRoot2, "test2", "bin", "envoy")) require.NoError(t, err) // Each PathID should have triggered a callback exactly once @@ -100,120 +99,29 @@ func TestIstioSync(t *testing.T) { assert.Equal(t, 1, registerRecorder.CallsForPathID(pathID1)) assert.Equal(t, 1, registerRecorder.CallsForPathID(pathID2)) }) - - t.Run("detecting a dangling process", func(t *testing.T) { - procRoot := createFakeProcFS(t) - monitor := newIstioTestMonitor(t) - registerRecorder := new(utils.CallbackRecorder) - unregisterRecorder := new(utils.CallbackRecorder) - - // Setup test callbacks - monitor.registerCB = registerRecorder.Callback() - monitor.unregisterCB = unregisterRecorder.Callback() - - monitor.sync() - - // The first call to sync() will start tracing PIDs 1 and 3, but not PID 2 - assert.Contains(t, monitor.registry.GetRegisteredProcesses(), uint32(1)) - assert.NotContains(t, monitor.registry.GetRegisteredProcesses(), uint32(2)) - assert.Contains(t, monitor.registry.GetRegisteredProcesses(), uint32(3)) - - // At this point we should have received: - // * 2 register calls - // * 0 unregister calls - assert.Equal(t, 2, registerRecorder.TotalCalls()) - assert.Equal(t, 0, unregisterRecorder.TotalCalls()) - - // Now we emulate a process termination for PID 3 by removing it from the fake - // procFS tree - require.NoError(t, os.RemoveAll(filepath.Join(procRoot, "3"))) - - // Once we call sync() again, PID 3 termination should be detected - // and the unregister callback should be executed - monitor.sync() - assert.Equal(t, 1, unregisterRecorder.TotalCalls()) - assert.NotContains(t, monitor.registry.GetRegisteredProcesses(), uint32(3)) - }) } -// This creates a bare-bones procFS with a structure that looks like -// the following: -// -// proc/ -// ├── 1 -// │   └── root -// │   └── usr -// │   └── local -// │   └── bin -// │   └── envoy -// ... -// -// This ProcFS contains 3 PIDs: -// -// PID 1 -> Envoy process -// PID 2 -> Bash process -// PID 3 -> Envoy process -func createFakeProcFS(t *testing.T) (procRoot string) { - _, err := os.Stat("/usr/bin/busybox") - if err != nil { - t.Skip("skip for the moment as some distro are not friendly with busybox package") - } - +// createFakeProc creates a fake envoy process in the given temporary directory. +// returns the temporary directory and the PID of the fake envoy process. +func createFakeProc(t *testing.T, path string) (procRoot string, pid int) { procRoot = t.TempDir() + binDir := filepath.Join(procRoot, path) + err := os.MkdirAll(binDir, os.ModePerm) + require.NoError(t, err) + fakeEnvoyPath := filepath.Join(binDir, "envoy") - // Inject fake ProcFS path - previousFn := kernel.ProcFSRoot - kernel.ProcFSRoot = func() string { return procRoot } - t.Cleanup(func() { - kernel.ProcFSRoot = previousFn - }) - - createFile(t, - filepath.Join(procRoot, "1", "root", "usr", "local", "bin", "envoy"), - "", - ) - createFile(t, - filepath.Join(procRoot, "3", "root", "usr", "local", "bin", "envoy"), - "", - ) - createFile(t, - filepath.Join(procRoot, "4", "root", "usr", "bin", "envoy-conf"), - "", - ) - - require.NoError(t, exec.Command("cp", "/usr/bin/busybox", filepath.Join(procRoot, "ash")).Run()) - require.NoError(t, exec.Command("cp", "/usr/bin/busybox", filepath.Join(procRoot, "envoy")).Run()) - - runFakeProcess(t, procRoot, "1", "/usr/local/bin/envoy") - runFakeProcess(t, procRoot, "3", "/usr/local/bin/envoy") - runFakeProcess(t, procRoot, "4", "/usr/local/bin/envoy-conf") - - return -} + // we are using the `yes` command as a fake envoy binary. + require.NoError(t, exec.Command("cp", "/usr/bin/yes", fakeEnvoyPath).Run()) -func runFakeProcess(t *testing.T, root, pid, binPath string) { - pidPath := filepath.Join(root, pid) - require.NoError(t, os.MkdirAll(pidPath, 0755)) - - cmd := exec.Command("unshare", "--fork", "--pid", "-R", root, "/ash", "-c", fmt.Sprintf("sleep 10 > %s", binPath)) + cmd := exec.Command(fakeEnvoyPath) require.NoError(t, cmd.Start()) - createSymlink(t, - binPath, - filepath.Join(pidPath, "exe"), - ) -} - -func createFile(t *testing.T, path, data string) { - dir := filepath.Dir(path) - require.NoError(t, os.MkdirAll(dir, 0775)) - require.NoError(t, os.WriteFile(path, []byte(data), 0775)) -} + // Schedule process termination after the test + t.Cleanup(func() { + _ = cmd.Process.Kill() + }) -func createSymlink(t *testing.T, target, link string) { - dir := filepath.Dir(link) - require.NoError(t, os.MkdirAll(dir, 0775)) - require.NoError(t, os.Symlink(target, link)) + return procRoot, cmd.Process.Pid } func newIstioTestMonitor(t *testing.T) *istioMonitor {