...

Source file src/github.com/redhat-developer/odo/pkg/remotecmd/kubeexec_test.go

Documentation: github.com/redhat-developer/odo/pkg/remotecmd

     1  package remotecmd
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/golang/mock/gomock"
    13  	"github.com/google/go-cmp/cmp"
    14  
    15  	"github.com/redhat-developer/odo/pkg/exec"
    16  	"github.com/redhat-developer/odo/pkg/kclient"
    17  )
    18  
    19  const (
    20  	_podName       = "my-pod"
    21  	_containerName = "my-container"
    22  	statFile       = `1 (tail) S 0 1 1 0 -1 1077952768 943 0 0 0 1 1 0 0 20 0 1 0 171838 5050368 338 18446744073709551615 94133334573056 94133335487553 140737112090992 0 0 0 0 0 0 0 0 0 17 1 0 0 0 0 0 94133335803888 94133335849476 94133343424512 140737112095206 140737112095282 140737112095282 140737112096746 0
    23  118 (bash) S 0 118 118 34816 128 4210944 1144 454 0 0 0 1 0 0 20 0 1 0 185395 4554752 926 18446744073709551615 93924054794240 93924055688405 140724979077904 0 0 0 65536 3686404 1266761467 0 0 0 17 1 0 0 0 0 0 93924055927824 93924055975568 93924085239808 140724979079714 140724979079719 140724979079719 140724979081194 0
    24  81 (sh) (param) S 0 81 81 0 -1 4210944 693 0 0 0 0 0 0 0 20 0 1 0 172021 4284416 760 18446744073709551615 94666717065216 94666717959381 140728008896192 0 0 0 65536 4 65538 0 0 0 17 0 0 0 0 0 0 94666718198800 94666718246544 94666730864640 140728008903100 140728008903254 140728008903254 140728008904688 0
    25  87 (main) S 81 81 81 0 -1 4210688 541 0 0 0 0 0 0 0 20 0 5 0 172022 1032048640 1892 18446744073709551615 4194304 6405776 140730311069152 0 0 0 0 0 2143420159 0 0 0 17 1 0 0 0 0 0 8507392 8757920 34906112 140730311072280 140730311072287 140730311072287 140730311073777 0
    26  128 (cat) R 118 128 118 34816 128 4210688 152 0 0 0 0 0 0 0 20 0 1 0 193754 5185536 625 18446744073709551615 94301628837888 94301629752385 140721174235312 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 94301630068720 94301630114308 94301634404352 140721174243694 140721174243865 140721174243865 140721174245355 0
    27  128 (cat) R 118 128 118 34816 128 4210688 152 0 0 0 0 0 0 0 20 0 1 0 193754 5185536 625 18446744073709551615 94301628837888 94301629752385 140721174235312 0 0 0 0 0 0 0 0 0 17 0 0 0 0 0 0 94301630068720 94301630114308 94301634404352 140721174243694 140721174243865 140721174243865 140721174245355 0
    28  222 (my-cmd) S 87 81 81 0 -1 4210688 541 0 0 0 0 0 0 0 20 0 5 0 172022 1032048640 1892 18446744073709551615 4194304 6405776 140730311069152 0 0 0 0 0 2143420159 0 0 0 17 1 0 0 0 0 0 8507392 8757920 34906112 140730311072280 140730311072287 140730311072287 140730311073777 0
    29  223 (my-cmd) S 87 81 81 0 -1 4210688 541 0 0 0 0 0 0 0 20 0 5 0 172022 1032048640 1892 18446744073709551615 4194304 6405776 140730311069152 0 0 0 0 0 2143420159 0 0 0 17 1 0 0 0 0 0 8507392 8757920 34906112 140730311072280 140730311072287 140730311072287 140730311073777 0
    30  333 (my-cmd) S 222 81 81 0 -1 4210688 541 0 0 0 0 0 0 0 20 0 5 0 172022 1032048640 1892 18446744073709551615 4194304 6405776 140730311069152 0 0 0 0 0 2143420159 0 0 0 17 1 0 0 0 0 0 8507392 8757920 34906112 140730311072280 140730311072287 140730311072287 140730311073777 0
    31  334 (my-cmd) S 222 81 81 0 -1 4210688 541 0 0 0 0 0 0 0 20 0 5 0 172022 1032048640 1892 18446744073709551615 4194304 6405776 140730311069152 0 0 0 0 0 2143420159 0 0 0 17 1 0 0 0 0 0 8507392 8757920 34906112 140730311072280 140730311072287 140730311072287 140730311073777 0`
    32  )
    33  
    34  func Test_kubeExecProcessHandler_GetProcessInfoForCommand(t *testing.T) {
    35  	cmdDef := CommandDefinition{Id: "my-run"}
    36  	kill0CmdProvider := func(p int) []string {
    37  		return []string{ShellExecutable, "-c", fmt.Sprintf("kill -0 %d; echo $?", p)}
    38  	}
    39  	for _, tt := range []struct {
    40  		name                 string
    41  		kubeClientCustomizer func(*kclient.MockClientInterface)
    42  		pid                  int
    43  		want                 RemoteProcessInfo
    44  		wantErr              bool
    45  	}{
    46  		{
    47  			name: "error returned when checking pid file",
    48  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
    49  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
    50  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
    51  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
    52  					Return(errors.New("an error"))
    53  			},
    54  			wantErr: true,
    55  		},
    56  		{
    57  			name: "stopped status if PID file missing",
    58  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
    59  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
    60  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
    61  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
    62  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
    63  						_, err := stderr.Write([]byte("no such file or directory"))
    64  						return err
    65  					})
    66  			},
    67  			want: RemoteProcessInfo{
    68  				Pid:    0,
    69  				Status: Stopped,
    70  			},
    71  		},
    72  		{
    73  			name: "unknown status if negative value stored in PID file",
    74  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
    75  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
    76  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
    77  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
    78  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
    79  						_, err := stdout.Write([]byte("-1"))
    80  						return err
    81  					})
    82  			},
    83  			want: RemoteProcessInfo{
    84  				Pid:    -1,
    85  				Status: Unknown,
    86  			},
    87  			wantErr: true,
    88  		},
    89  		{
    90  			name: "stopped status if kill -0 command exit status is non-zero",
    91  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
    92  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
    93  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
    94  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
    95  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
    96  						_, err := stdout.Write([]byte("123"))
    97  						return err
    98  					})
    99  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(kill0CmdProvider(123)),
   100  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   101  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   102  						_, err := stdout.Write([]byte("1"))
   103  						return err
   104  					})
   105  			},
   106  			want: RemoteProcessInfo{
   107  				Pid:    123,
   108  				Status: Stopped,
   109  			},
   110  		},
   111  		{
   112  			name: "error status if kill -0 command exit status is non-zero and process exit code recorded as failing",
   113  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   114  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   115  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
   116  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   117  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   118  						_, err := stdout.Write([]byte("123\n1"))
   119  						return err
   120  					})
   121  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(kill0CmdProvider(123)),
   122  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   123  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   124  						_, err := stdout.Write([]byte("1"))
   125  						return err
   126  					})
   127  			},
   128  			want: RemoteProcessInfo{
   129  				Pid:    123,
   130  				Status: Errored,
   131  			},
   132  		},
   133  		{
   134  			name: "running status if kill -0 command exit status is zero",
   135  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   136  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   137  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
   138  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   139  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   140  						_, err := stdout.Write([]byte("123"))
   141  						return err
   142  					})
   143  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(kill0CmdProvider(123)),
   144  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   145  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   146  						_, err := stdout.Write([]byte("0"))
   147  						return err
   148  					})
   149  			},
   150  			want: RemoteProcessInfo{
   151  				Pid:    123,
   152  				Status: Running,
   153  			},
   154  		},
   155  	} {
   156  		t.Run(tt.name, func(t *testing.T) {
   157  			ctrl := gomock.NewController(t)
   158  			kubeClient := kclient.NewMockClientInterface(ctrl)
   159  			if tt.kubeClientCustomizer != nil {
   160  				tt.kubeClientCustomizer(kubeClient)
   161  			}
   162  
   163  			execClient := exec.NewExecClient(kubeClient)
   164  			k := NewKubeExecProcessHandler(execClient)
   165  			got, err := k.GetProcessInfoForCommand(context.Background(), cmdDef, _podName, _containerName)
   166  
   167  			if tt.wantErr != (err != nil) {
   168  				t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
   169  			}
   170  			if diff := cmp.Diff(tt.want, got); diff != "" {
   171  				t.Errorf("kubeExecProcessHandler.GetProcessInfoForCommand() mismatch (-want +got):\n%s", diff)
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func Test_kubeExecProcessHandler_StartProcessForCommand(t *testing.T) {
   178  	kill0CmdProvider := func(p int) []string {
   179  		return []string{ShellExecutable, "-c", fmt.Sprintf("kill -0 %d; echo $?", p)}
   180  	}
   181  
   182  	execCmdWithoutWorkingDir := CommandDefinition{
   183  		Id:      "my-exec-cmd",
   184  		CmdLine: "echo Hello; sleep 300",
   185  	}
   186  	fullExecCmd := CommandDefinition{
   187  		Id:         "my-exec-cmd",
   188  		CmdLine:    "tail -f /path/to/a/file",
   189  		WorkingDir: "/path/to/working/dir",
   190  		EnvVars: []CommandEnvVar{
   191  			{
   192  				Key:   "ENV_VAR1",
   193  				Value: "value1",
   194  			},
   195  			{
   196  				Key:   "ENV_VAR2",
   197  				Value: "value2",
   198  			},
   199  		},
   200  	}
   201  	for _, tt := range []struct {
   202  		name                 string
   203  		cmdDef               CommandDefinition
   204  		kubeClientCustomizer func(*kclient.MockClientInterface)
   205  		isCmdExpectedToRun   bool
   206  		wantErr              bool
   207  		expectedStatuses     []RemoteProcessStatus
   208  	}{
   209  		{
   210  			name:   "command execution returned no error",
   211  			cmdDef: execCmdWithoutWorkingDir,
   212  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   213  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   214  					gomock.Eq([]string{ShellExecutable, "-c",
   215  						fmt.Sprintf("echo $$ > %[1]s &&   (%s) 1>>/proc/1/fd/1 2>>/proc/1/fd/2; echo $? >> %[1]s",
   216  							getPidFileForCommand(execCmdWithoutWorkingDir), execCmdWithoutWorkingDir.CmdLine)}),
   217  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   218  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   219  						_, err := stdout.Write([]byte("Hello"))
   220  						return err
   221  					})
   222  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   223  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(execCmdWithoutWorkingDir))}),
   224  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   225  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   226  						_, err := stdout.Write([]byte("123"))
   227  						return err
   228  					})
   229  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(kill0CmdProvider(123)),
   230  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   231  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   232  						_, err := stdout.Write([]byte("1"))
   233  						return err
   234  					})
   235  			},
   236  			isCmdExpectedToRun: true,
   237  			expectedStatuses:   []RemoteProcessStatus{Starting, Running, Stopped},
   238  		},
   239  		{
   240  			name:   "command with all fields returned an error",
   241  			cmdDef: fullExecCmd,
   242  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   243  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   244  					gomock.Eq([]string{ShellExecutable, "-c",
   245  						fmt.Sprintf("echo $$ > %[1]s && cd %s && export ENV_VAR1='value1' ENV_VAR2='value2' && (%s) 1>>/proc/1/fd/1 2>>/proc/1/fd/2; echo $? >> %[1]s",
   246  							getPidFileForCommand(fullExecCmd), fullExecCmd.WorkingDir, fullExecCmd.CmdLine)}),
   247  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   248  					Return(errors.New("error while running command"))
   249  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   250  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(fullExecCmd))}),
   251  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   252  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   253  						_, err := stdout.Write([]byte("123\n1"))
   254  						return err
   255  					})
   256  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(kill0CmdProvider(123)),
   257  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   258  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   259  						_, err := stdout.Write([]byte("1"))
   260  						return err
   261  					})
   262  			},
   263  			isCmdExpectedToRun: true,
   264  			expectedStatuses:   []RemoteProcessStatus{Starting, Running, Errored},
   265  		},
   266  	} {
   267  		t.Run(tt.name, func(t *testing.T) {
   268  			ctrl := gomock.NewController(t)
   269  			kubeClient := kclient.NewMockClientInterface(ctrl)
   270  			if tt.kubeClientCustomizer != nil {
   271  				tt.kubeClientCustomizer(kubeClient)
   272  			}
   273  
   274  			execClient := exec.NewExecClient(kubeClient)
   275  			k := NewKubeExecProcessHandler(execClient)
   276  
   277  			var wg sync.WaitGroup
   278  			wg.Add(len(tt.expectedStatuses)) //number of invocations of outputHandler
   279  			var statusesReported []RemoteProcessStatus
   280  			err := k.StartProcessForCommand(context.Background(), tt.cmdDef, _podName, _containerName, func(status RemoteProcessStatus, stdout []string, stderr []string, err error) {
   281  				defer wg.Done()
   282  				statusesReported = append(statusesReported, status)
   283  			})
   284  
   285  			if tt.wantErr != (err != nil) {
   286  				t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
   287  			}
   288  
   289  			if tt.isCmdExpectedToRun && waitTimeout(&wg, 10*time.Second) {
   290  				t.Errorf("timeout waiting for output handler to get called")
   291  				return
   292  			}
   293  
   294  			if diff := cmp.Diff(tt.expectedStatuses, statusesReported); diff != "" {
   295  				t.Errorf("kubeExecProcessHandler.StartProcessForCommand() expectedStatuses mismatch (-want +got):\n%s", diff)
   296  			}
   297  		})
   298  	}
   299  }
   300  
   301  func Test_kubeExecProcessHandler_StopProcessForCommand(t *testing.T) {
   302  	cmdDef := CommandDefinition{Id: "my-run"}
   303  	retrieveChildrenCmdProvider := func() []string {
   304  		return []string{ShellExecutable, "-c", "cat /proc/*/stat || true"}
   305  	}
   306  	killCmdProvider := func(p int) []string {
   307  		return []string{ShellExecutable, "-c", fmt.Sprintf("kill %d || true", p)}
   308  	}
   309  	kill0CmdProvider := func(p int) []string {
   310  		return []string{ShellExecutable, "-c", fmt.Sprintf("kill -0 %d; echo $?", p)}
   311  	}
   312  
   313  	for _, tt := range []struct {
   314  		name                 string
   315  		kubeClientCustomizer func(*kclient.MockClientInterface)
   316  		pid                  int
   317  		wantErr              bool
   318  	}{
   319  		{
   320  			name: "error returned when checking pid file",
   321  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   322  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   323  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
   324  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   325  					Return(errors.New("an error"))
   326  			},
   327  			wantErr: true,
   328  		},
   329  		{
   330  			name: "nothing to do if PID file missing",
   331  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   332  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   333  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
   334  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   335  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   336  						_, err := stderr.Write([]byte("no such file or directory"))
   337  						return err
   338  					})
   339  			},
   340  		},
   341  		{
   342  			name: "error while determining process children",
   343  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   344  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   345  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
   346  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   347  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   348  						_, err := stdout.Write([]byte("123"))
   349  						return err
   350  					})
   351  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(retrieveChildrenCmdProvider()),
   352  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   353  					Return(errors.New("an error"))
   354  				// parent process should still be killed.
   355  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(killCmdProvider(123)),
   356  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   357  					Return(nil)
   358  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(kill0CmdProvider(123)),
   359  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   360  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   361  						_, _ = stderr.Write([]byte("no such process"))
   362  						_, err := stdout.Write([]byte("1"))
   363  						return err
   364  					})
   365  			},
   366  			wantErr: true,
   367  		},
   368  		{
   369  			name: "no process children killed if no children file found",
   370  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   371  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   372  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("rm -f %s", getPidFileForCommand(cmdDef))}),
   373  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   374  					Return(errors.New("an error which should be ignored"))
   375  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   376  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
   377  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   378  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   379  						_, err := stdout.Write([]byte("123"))
   380  						return err
   381  					})
   382  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(retrieveChildrenCmdProvider()),
   383  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   384  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   385  						_, err := stderr.Write([]byte("no such file or directory"))
   386  						return err
   387  					})
   388  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(killCmdProvider(123)),
   389  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   390  					Return(nil)
   391  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(kill0CmdProvider(123)),
   392  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   393  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   394  						_, _ = stderr.Write([]byte("no such process"))
   395  						_, err := stdout.Write([]byte("1"))
   396  						return err
   397  					})
   398  			},
   399  		},
   400  		{
   401  			name: "process children should get killed",
   402  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   403  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   404  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("rm -f %s", getPidFileForCommand(cmdDef))}),
   405  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   406  					Return(errors.New("an error which should be ignored"))
   407  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   408  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
   409  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   410  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   411  						_, err := stdout.Write([]byte("81"))
   412  						return err
   413  					})
   414  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(retrieveChildrenCmdProvider()),
   415  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   416  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   417  						_, err := stdout.Write([]byte(statFile))
   418  						return err
   419  					})
   420  				for _, p := range []int{333, 334, 222, 223, 87, 81} {
   421  					kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(killCmdProvider(p)),
   422  						gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   423  						Return(nil)
   424  					kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(kill0CmdProvider(p)),
   425  						gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   426  						DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   427  							_, _ = stderr.Write([]byte("no such process"))
   428  							_, err := stdout.Write([]byte("1"))
   429  							return err
   430  						})
   431  				}
   432  			},
   433  		},
   434  		{
   435  			name: "error if any child process could not be killed",
   436  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   437  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   438  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("rm -f %s", getPidFileForCommand(cmdDef))}),
   439  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   440  					Return(errors.New("an error which should be ignored"))
   441  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   442  					gomock.Eq([]string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}),
   443  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   444  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   445  						_, err := stdout.Write([]byte("81"))
   446  						return err
   447  					})
   448  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(retrieveChildrenCmdProvider()),
   449  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   450  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   451  						_, err := stdout.Write([]byte(statFile))
   452  						return err
   453  					})
   454  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(killCmdProvider(333)),
   455  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   456  					Return(errors.New("error killing process 333"))
   457  				// parent process should be stopped in all cases
   458  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(killCmdProvider(81)),
   459  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   460  					Return(nil)
   461  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(kill0CmdProvider(81)),
   462  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   463  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   464  						_, _ = stderr.Write([]byte("no such process"))
   465  						_, err := stdout.Write([]byte("1"))
   466  						return err
   467  					})
   468  			},
   469  			wantErr: true,
   470  		},
   471  	} {
   472  		t.Run(tt.name, func(t *testing.T) {
   473  			ctrl := gomock.NewController(t)
   474  			kubeClient := kclient.NewMockClientInterface(ctrl)
   475  			if tt.kubeClientCustomizer != nil {
   476  				tt.kubeClientCustomizer(kubeClient)
   477  			}
   478  
   479  			execClient := exec.NewExecClient(kubeClient)
   480  			k := NewKubeExecProcessHandler(execClient)
   481  			err := k.StopProcessForCommand(context.Background(), cmdDef, _podName, _containerName)
   482  
   483  			if tt.wantErr != (err != nil) {
   484  				t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
   485  			}
   486  		})
   487  	}
   488  }
   489  
   490  func Test_kubeExecProcessHandler_getProcessInfoFromPid(t *testing.T) {
   491  	cmdProvider := func(p int) []string {
   492  		return []string{ShellExecutable, "-c", fmt.Sprintf("kill -0 %d; echo $?", p)}
   493  	}
   494  	for _, tt := range []struct {
   495  		name                 string
   496  		kubeClientCustomizer func(*kclient.MockClientInterface)
   497  		pid                  int
   498  		lastKnownExitStatus  int
   499  		want                 RemoteProcessInfo
   500  		wantErr              bool
   501  	}{
   502  		{
   503  			name:    "pid < 0",
   504  			pid:     -1,
   505  			wantErr: true,
   506  			want: RemoteProcessInfo{
   507  				Pid:    -1,
   508  				Status: Unknown,
   509  			},
   510  		},
   511  		{
   512  			name: "pid == 0",
   513  			want: RemoteProcessInfo{
   514  				Status: Stopped,
   515  			},
   516  		},
   517  		{
   518  			name: "error when checking process status",
   519  			pid:  123,
   520  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   521  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmdProvider(123)),
   522  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   523  					Return(errors.New("an error"))
   524  			},
   525  			wantErr: true,
   526  			want: RemoteProcessInfo{
   527  				Pid:    123,
   528  				Status: Unknown,
   529  			},
   530  		},
   531  		{
   532  			name: "non-integer content returned by kill command output",
   533  			pid:  123,
   534  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   535  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmdProvider(123)),
   536  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   537  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   538  						_, err := stdout.Write([]byte("should-not-happen"))
   539  						return err
   540  					})
   541  			},
   542  			wantErr: true,
   543  			want: RemoteProcessInfo{
   544  				Pid:    123,
   545  				Status: Unknown,
   546  			},
   547  		},
   548  		{
   549  			name: "kill command returned non-zero exit status code",
   550  			pid:  123,
   551  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   552  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmdProvider(123)),
   553  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   554  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   555  						_, err := stdout.Write([]byte("1"))
   556  						return err
   557  					})
   558  			},
   559  			want: RemoteProcessInfo{
   560  				Pid:    123,
   561  				Status: Stopped,
   562  			},
   563  		},
   564  		{
   565  			name: "kill command returned 0 as exit status code",
   566  			pid:  123,
   567  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   568  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmdProvider(123)),
   569  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   570  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   571  						_, err := stdout.Write([]byte("0"))
   572  						return err
   573  					})
   574  			},
   575  			want: RemoteProcessInfo{
   576  				Pid:    123,
   577  				Status: Running,
   578  			},
   579  		},
   580  	} {
   581  		t.Run(tt.name, func(t *testing.T) {
   582  			ctrl := gomock.NewController(t)
   583  			kubeClient := kclient.NewMockClientInterface(ctrl)
   584  			if tt.kubeClientCustomizer != nil {
   585  				tt.kubeClientCustomizer(kubeClient)
   586  			}
   587  
   588  			execClient := exec.NewExecClient(kubeClient)
   589  			k := NewKubeExecProcessHandler(execClient)
   590  			got, err := k.getProcessInfoFromPid(context.Background(), tt.pid, tt.lastKnownExitStatus, _podName, _containerName)
   591  
   592  			if tt.wantErr != (err != nil) {
   593  				t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
   594  			}
   595  			if diff := cmp.Diff(tt.want, got); diff != "" {
   596  				t.Errorf("kubeExecProcessHandler.getProcessInfoFromPid() mismatch (-want +got):\n%s", diff)
   597  			}
   598  		})
   599  	}
   600  }
   601  
   602  func Test_kubeExecProcessHandler_getRemoteProcessPID(t *testing.T) {
   603  	cmdDef := CommandDefinition{Id: "my-run"}
   604  	cmd := []string{ShellExecutable, "-c", fmt.Sprintf("cat %s || true", getPidFileForCommand(cmdDef))}
   605  	for _, tt := range []struct {
   606  		name                  string
   607  		kubeClientCustomizer  func(*kclient.MockClientInterface)
   608  		wantPid               int
   609  		wantLastKnownExitCode int
   610  		wantErr               bool
   611  	}{
   612  		{
   613  			name: "error returned at command execution",
   614  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   615  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   616  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   617  					Return(errors.New("an error"))
   618  			},
   619  			wantErr: true,
   620  		},
   621  		{
   622  			name: "missing pid file",
   623  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   624  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   625  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   626  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   627  						_, err := stderr.Write([]byte("no such file or directory"))
   628  						return err
   629  					})
   630  			},
   631  		},
   632  		{
   633  			name: "unexpected number of lines in pid file",
   634  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   635  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   636  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   637  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   638  						_, err := stdout.Write([]byte("123\n234\n345"))
   639  						return err
   640  					})
   641  			},
   642  			wantErr: true,
   643  		},
   644  		{
   645  			name: "invalid content in pid file",
   646  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   647  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   648  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   649  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   650  						_, err := stdout.Write([]byte("invalid-pid"))
   651  						return err
   652  					})
   653  			},
   654  			wantErr: true,
   655  		},
   656  		{
   657  			name: "valid content in pid file with trailing spaces",
   658  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   659  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   660  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   661  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   662  						_, err := stdout.Write([]byte(" 123 "))
   663  						return err
   664  					})
   665  			},
   666  			wantPid: 123,
   667  		},
   668  		{
   669  			name: "valid content in pid file",
   670  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   671  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   672  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   673  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   674  						_, err := stdout.Write([]byte("123"))
   675  						return err
   676  					})
   677  			},
   678  			wantPid: 123,
   679  		},
   680  		{
   681  			name: "negative value in pid file",
   682  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   683  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   684  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   685  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   686  						_, err := stdout.Write([]byte("-1"))
   687  						return err
   688  					})
   689  			},
   690  			wantPid: -1,
   691  		},
   692  		{
   693  			name: "valid content with zero exit status code in pid file",
   694  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   695  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   696  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   697  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   698  						_, err := stdout.Write([]byte("123\n0"))
   699  						return err
   700  					})
   701  			},
   702  			wantPid:               123,
   703  			wantLastKnownExitCode: 0,
   704  		},
   705  		{
   706  			name: "valid content with non-zero exit status code in pid file",
   707  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   708  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   709  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   710  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   711  						_, err := stdout.Write([]byte("123\n1"))
   712  						return err
   713  					})
   714  			},
   715  			wantPid:               123,
   716  			wantLastKnownExitCode: 1,
   717  		},
   718  		{
   719  			name: "error returned content if non-number recorded in pid file as process last-known exit code",
   720  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   721  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   722  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   723  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   724  						_, err := stdout.Write([]byte("123\nNAN"))
   725  						return err
   726  					})
   727  			},
   728  			wantErr: true,
   729  			wantPid: 123,
   730  		},
   731  	} {
   732  		t.Run(tt.name, func(t *testing.T) {
   733  			ctrl := gomock.NewController(t)
   734  			kubeClient := kclient.NewMockClientInterface(ctrl)
   735  			if tt.kubeClientCustomizer != nil {
   736  				tt.kubeClientCustomizer(kubeClient)
   737  			}
   738  
   739  			execClient := exec.NewExecClient(kubeClient)
   740  			kubeExecClient := NewKubeExecProcessHandler(execClient)
   741  			got, lastKnownExitStatus, err := kubeExecClient.getRemoteProcessPID(context.Background(), cmdDef, _podName, _containerName)
   742  			if tt.wantErr != (err != nil) {
   743  				t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
   744  			}
   745  			if diff := cmp.Diff(tt.wantPid, got); diff != "" {
   746  				t.Errorf("kubeExecProcessHandler.getRemoteProcessPID() wantPid mismatch (-want +got):\n%s", diff)
   747  			}
   748  			if diff := cmp.Diff(tt.wantLastKnownExitCode, lastKnownExitStatus); diff != "" {
   749  				t.Errorf("kubeExecProcessHandler.getRemoteProcessPID() wantLastKnownExitCode mismatch (-want +got):\n%s", diff)
   750  			}
   751  		})
   752  	}
   753  }
   754  
   755  func Test_kubeExecProcessHandler_getProcessChildren(t *testing.T) {
   756  	const ppid = 123
   757  	cmdProvider := func() []string {
   758  		return []string{ShellExecutable, "-c", "cat /proc/*/stat || true"}
   759  	}
   760  
   761  	for _, tt := range []struct {
   762  		name                 string
   763  		ppid                 int
   764  		kubeClientCustomizer func(*kclient.MockClientInterface)
   765  		want                 []int
   766  		wantErr              bool
   767  	}{
   768  		{
   769  			name:    "pid < 0",
   770  			ppid:    -1,
   771  			wantErr: true,
   772  		},
   773  		{
   774  			name:    "pid = 0",
   775  			wantErr: true,
   776  		},
   777  		{
   778  			name: "error returned at command execution",
   779  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   780  				cmd := cmdProvider()
   781  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   782  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   783  					Return(errors.New("an error"))
   784  			},
   785  			ppid:    ppid,
   786  			wantErr: true,
   787  		},
   788  		{
   789  			name: "missing stat file",
   790  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   791  				cmd := cmdProvider()
   792  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   793  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   794  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   795  						_, err := stderr.Write([]byte("no such file or directory"))
   796  						return err
   797  					})
   798  			},
   799  			ppid: ppid,
   800  			want: nil,
   801  		},
   802  		{
   803  			name: "empty stat file",
   804  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   805  				cmd := cmdProvider()
   806  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName), gomock.Eq(cmd),
   807  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   808  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   809  						_, err := stdout.Write([]byte(""))
   810  						return err
   811  					})
   812  			},
   813  			ppid: ppid,
   814  			want: nil,
   815  		},
   816  		{
   817  			name: "stat file with children at several levels",
   818  			kubeClientCustomizer: func(kclient *kclient.MockClientInterface) {
   819  				cmd := cmdProvider()
   820  				kclient.EXPECT().ExecCMDInContainer(gomock.Any(), gomock.Eq(_containerName), gomock.Eq(_podName),
   821  					gomock.Eq(cmd),
   822  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
   823  					DoAndReturn(func(ctx context.Context, containerName, podName string, cmd []string, stdout io.Writer, stderr io.Writer, stdin io.Reader, tty bool) error {
   824  						_, err := stdout.Write([]byte(statFile))
   825  						return err
   826  					})
   827  			},
   828  			ppid: 81,
   829  			want: []int{333, 334, 222, 223, 87},
   830  		},
   831  	} {
   832  		t.Run(tt.name, func(t *testing.T) {
   833  			ctrl := gomock.NewController(t)
   834  			kubeClient := kclient.NewMockClientInterface(ctrl)
   835  			if tt.kubeClientCustomizer != nil {
   836  				tt.kubeClientCustomizer(kubeClient)
   837  			}
   838  
   839  			execClient := exec.NewExecClient(kubeClient)
   840  			kubeExecClient := NewKubeExecProcessHandler(execClient)
   841  			got, err := kubeExecClient.getProcessChildren(context.Background(), tt.ppid, _podName, _containerName)
   842  			if tt.wantErr != (err != nil) {
   843  				t.Errorf("unexpected error %v, wantErr %v", err, tt.wantErr)
   844  			}
   845  			if diff := cmp.Diff(tt.want, got); diff != "" {
   846  				t.Errorf("kubeExecProcessHandler.getProcessChildren() mismatch (-want +got):\n%s", diff)
   847  			}
   848  		})
   849  	}
   850  }
   851  
   852  // waitTimeout waits for the waitgroup for the specified max timeout.
   853  // Returns true if waiting timed out.
   854  func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
   855  	c := make(chan struct{})
   856  	go func() {
   857  		defer close(c)
   858  		wg.Wait()
   859  	}()
   860  	select {
   861  	case <-c:
   862  		return false // completed normally
   863  	case <-time.After(timeout):
   864  		return true // timed out
   865  	}
   866  }
   867  

View as plain text