github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/configuration/container/container_kill_test.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package container
    21  
    22  import (
    23  	"context"
    24  	"net"
    25  	"os"
    26  	"path/filepath"
    27  	"reflect"
    28  	"testing"
    29  	"time"
    30  
    31  	"github.com/docker/docker/api/types"
    32  	"github.com/golang/mock/gomock"
    33  	"github.com/stretchr/testify/require"
    34  	"go.uber.org/zap"
    35  	"golang.org/x/net/http2"
    36  	runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
    37  
    38  	"github.com/1aal/kubeblocks/pkg/configuration/container/mocks"
    39  	cfgcore "github.com/1aal/kubeblocks/pkg/configuration/core"
    40  )
    41  
    42  var zapLog, _ = zap.NewDevelopment()
    43  
    44  func TestNewContainerKiller(t *testing.T) {
    45  	zaplog, _ := zap.NewProduction()
    46  
    47  	type args struct {
    48  		criType    CRIType
    49  		socketPath string
    50  	}
    51  	tests := []struct {
    52  		name    string
    53  		args    args
    54  		want    ContainerKiller
    55  		wantErr bool
    56  	}{{
    57  		name: "test1",
    58  		args: args{
    59  			criType: "xxxx",
    60  		},
    61  		wantErr: true,
    62  	}, {
    63  		name: "test2",
    64  		args: args{
    65  			criType:    ContainerdType,
    66  			socketPath: "for_test",
    67  		},
    68  		wantErr: false,
    69  		want: &containerdContainer{
    70  			runtimeEndpoint: "for_test",
    71  			logger:          zaplog.Sugar(),
    72  		},
    73  	}, {
    74  		name: "test3",
    75  		args: args{
    76  			criType: DockerType,
    77  		},
    78  		wantErr: false,
    79  		want: &dockerContainer{
    80  			logger: zaplog.Sugar(),
    81  		},
    82  	}}
    83  
    84  	for _, tt := range tests {
    85  		t.Run(tt.name, func(t *testing.T) {
    86  			got, err := NewContainerKiller(tt.args.criType, tt.args.socketPath, zaplog.Sugar())
    87  			if (err != nil) != tt.wantErr {
    88  				t.Errorf("NewContainerKiller() error = %v, wantErr %v", err, tt.wantErr)
    89  				return
    90  			}
    91  			if !reflect.DeepEqual(got, tt.want) {
    92  				t.Errorf("NewContainerKiller() got = %v, want %v", got, tt.want)
    93  			}
    94  		})
    95  	}
    96  }
    97  
    98  func TestDockerContainerKill(t *testing.T) {
    99  	mockCtrl := gomock.NewController(t)
   100  	defer mockCtrl.Finish()
   101  	cli := mocks.NewMockContainerAPIClient(mockCtrl)
   102  
   103  	docker := &dockerContainer{
   104  		logger: zapLog.Sugar(),
   105  		dc:     cli,
   106  	}
   107  
   108  	// mock ContainerList failed
   109  	cli.EXPECT().ContainerList(gomock.Any(), gomock.Any()).
   110  		Return(nil, cfgcore.MakeError("docker service not ready!"))
   111  
   112  	// mock container is not exist
   113  	cli.EXPECT().ContainerList(gomock.Any(), gomock.Any()).
   114  		Return([]types.Container{
   115  			testContainer("docker", "e5a00fc1653e196287576abccb70bac7411f553d09096a16fc2e0d8a66e03a8e", ""),
   116  		}, nil)
   117  
   118  	// mock container is existed
   119  	cli.EXPECT().ContainerList(gomock.Any(), gomock.Any()).
   120  		Return([]types.Container{
   121  			testContainer("docker", "e5a00fc1653e196287576abccb70bac7411f553d09096a16fc2e0d8a66e03a8e", ""),
   122  			testContainer("docker", "76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55", "exited"),
   123  		}, nil)
   124  
   125  	// mock container is running
   126  	cli.EXPECT().ContainerList(gomock.Any(), gomock.Any()).
   127  		Return([]types.Container{
   128  			testContainer("docker", "76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55", "running"),
   129  			testContainer("docker", "754d7342de7feb16be79462bc9d72b9c37306ec08374e914ae0a8f8377d0d855", "running"),
   130  		}, nil).AnyTimes()
   131  
   132  	// mock ContainerKill failed
   133  	cli.EXPECT().ContainerKill(gomock.Any(), gomock.Any(), gomock.Any()).
   134  		Return(cfgcore.MakeError("failed to kill docker container!"))
   135  	// mock ContainerKill success
   136  	cli.EXPECT().ContainerKill(gomock.Any(), gomock.Any(), gomock.Any()).
   137  		Return(nil).AnyTimes()
   138  
   139  	require.ErrorContains(t,
   140  		docker.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil),
   141  		"docker service not ready!")
   142  	require.Nil(t, docker.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil))
   143  	require.Nil(t, docker.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil))
   144  	require.ErrorContains(t,
   145  		docker.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil),
   146  		"failed to kill docker container")
   147  	require.Nil(t, docker.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil))
   148  }
   149  
   150  func TestContainerdContainerKill(t *testing.T) {
   151  	mockCtrl := gomock.NewController(t)
   152  	defer mockCtrl.Finish()
   153  	criCli := mocks.NewMockRuntimeServiceClient(mockCtrl)
   154  
   155  	containerd := containerdContainer{
   156  		logger:         zapLog.Sugar(),
   157  		backendRuntime: criCli,
   158  	}
   159  
   160  	// ctx context.Context, in *ListContainersRequest, opts ...grpc.CallOption
   161  	// mock ListContainers failed
   162  	criCli.EXPECT().ListContainers(gomock.Any(), gomock.Any()).
   163  		Return(nil, cfgcore.MakeError("failed to list containers!"))
   164  
   165  	// mock not exist
   166  	criCli.EXPECT().ListContainers(gomock.Any(), gomock.Any()).
   167  		Return(nil, nil)
   168  
   169  	// mock exited
   170  	criCli.EXPECT().ListContainers(gomock.Any(), gomock.Any()).
   171  		Return(&runtimeapi.ListContainersResponse{Containers: []*runtimeapi.Container{{
   172  			Id:    "76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55",
   173  			State: runtimeapi.ContainerState_CONTAINER_EXITED,
   174  		}}}, nil)
   175  
   176  	// mock running
   177  	criCli.EXPECT().ListContainers(gomock.Any(), gomock.Any()).
   178  		Return(&runtimeapi.ListContainersResponse{Containers: []*runtimeapi.Container{{
   179  			Id:    "76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55",
   180  			State: runtimeapi.ContainerState_CONTAINER_RUNNING,
   181  		}}}, nil).AnyTimes()
   182  
   183  	// mock stop failed
   184  	criCli.EXPECT().StopContainer(gomock.Any(), gomock.Any()).
   185  		Return(nil, cfgcore.MakeError("failed to stop container!"))
   186  	criCli.EXPECT().StopContainer(gomock.Any(), gomock.Any()).
   187  		Return(&runtimeapi.StopContainerResponse{}, nil)
   188  
   189  	require.ErrorContains(t,
   190  		containerd.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil),
   191  		"failed to list containers!")
   192  
   193  	require.Nil(t, containerd.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil))
   194  	require.Nil(t, containerd.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil))
   195  
   196  	require.ErrorContains(t,
   197  		containerd.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil),
   198  		"failed to stop container!")
   199  	require.Nil(t, containerd.Kill(context.Background(), []string{"76f9c2ae8cf47bfa43b97626e3c95045cb3b82c50019ab759801ab52e3acff55"}, "", nil))
   200  
   201  }
   202  
   203  func TestAutoCheckCRIType(t *testing.T) {
   204  	tmpDir, err := os.MkdirTemp(os.TempDir(), "SocketFileTest-")
   205  	require.Nil(t, err)
   206  	defer os.RemoveAll(tmpDir)
   207  
   208  	var (
   209  		testFile1  = filepath.Join(tmpDir, "file1.sock")
   210  		testFile2  = filepath.Join(tmpDir, "file2.sock")
   211  		testFile3  = filepath.Join(tmpDir, "file3.sock")
   212  		dockerFile = filepath.Join(tmpDir, "docker.sock")
   213  	)
   214  
   215  	require.Equal(t, autoCheckCRIType([]string{testFile1, testFile2, testFile3}, dockerFile, zapLog.Sugar()), CRIType(""))
   216  
   217  	l, err := net.Listen("unix", dockerFile)
   218  	if err != nil {
   219  		t.Errorf("failed to  create socket file: %s", dockerFile)
   220  	}
   221  	defer l.Close()
   222  	require.Equal(t, autoCheckCRIType([]string{testFile1, testFile2, testFile3}, dockerFile, zapLog.Sugar()), DockerType)
   223  
   224  	// mock grpc
   225  	{
   226  		listen, err := net.Listen("unix", testFile3)
   227  		if err != nil {
   228  			t.Fatalf("Error while listening. Err: %v", err)
   229  		}
   230  		defer listen.Close()
   231  		listenDone := make(chan struct{})
   232  		dialDone := make(chan struct{})
   233  		// mock grpc connection
   234  		go func() {
   235  			defer close(listenDone)
   236  			conn, err := listen.Accept()
   237  			if err != nil {
   238  				t.Errorf("failed to accepting. Err: %v", err)
   239  				return
   240  			}
   241  			framer := http2.NewFramer(conn, conn)
   242  			if err := framer.WriteSettings(http2.Setting{}); err != nil {
   243  				t.Errorf("failed to writing settings. Err: %v", err)
   244  				return
   245  			}
   246  			<-dialDone // wait for dialDone before closing connection
   247  			conn.Close()
   248  		}()
   249  
   250  		// for test
   251  		require.Equal(t, autoCheckCRIType([]string{testFile1, testFile2, testFile3}, dockerFile, zapLog.Sugar()), ContainerdType)
   252  		// close grpc mock
   253  		close(dialDone)
   254  
   255  		// wait grpc listen close
   256  		timeout := time.After(1 * time.Second)
   257  		select {
   258  		case <-timeout:
   259  			t.Fatal("timed out waiting for server to finish")
   260  		case <-listenDone:
   261  		}
   262  	}
   263  }
   264  
   265  func TestContainerdInit(t *testing.T) {
   266  	mockCtrl := gomock.NewController(t)
   267  	defer mockCtrl.Finish()
   268  	criCli := mocks.NewMockRuntimeServiceClient(mockCtrl)
   269  
   270  	containerd := containerdContainer{
   271  		logger:          zapLog.Sugar(),
   272  		backendRuntime:  criCli,
   273  		runtimeEndpoint: "not_exist_point",
   274  	}
   275  	require.NotNil(t, containerd.Init(context.Background()))
   276  }
   277  
   278  func TestContainerdPingCRI(t *testing.T) {
   279  	mockCtrl := gomock.NewController(t)
   280  	defer mockCtrl.Finish()
   281  	criCli := mocks.NewMockRuntimeServiceClient(mockCtrl)
   282  
   283  	criCli.EXPECT().Status(gomock.Any(), gomock.Any()).
   284  		Return(nil, cfgcore.MakeError("failed to ping CRI!"))
   285  	criCli.EXPECT().Status(gomock.Any(), gomock.Any()).
   286  		Return(&runtimeapi.StatusResponse{
   287  			Status: &runtimeapi.RuntimeStatus{},
   288  		}, nil)
   289  
   290  	containerd := containerdContainer{
   291  		logger:         zapLog.Sugar(),
   292  		backendRuntime: criCli,
   293  	}
   294  
   295  	require.NotNil(t, containerd.pingCRI(context.Background(), criCli))
   296  	require.Nil(t, containerd.pingCRI(context.Background(), criCli))
   297  }
   298  
   299  func TestDockerInit(t *testing.T) {
   300  	tmpDir, err := os.MkdirTemp(os.TempDir(), "SocketFileTest-")
   301  	require.Nil(t, err)
   302  	defer os.RemoveAll(tmpDir)
   303  
   304  	docker := &dockerContainer{
   305  		logger:         zapLog.Sugar(),
   306  		dockerEndpoint: filepath.Join(tmpDir, "docker.sock"),
   307  	}
   308  	require.NotNil(t, docker.Init(context.Background()))
   309  }