...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/util/runtime/runtime_test.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/util/runtime

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package runtime
    18  
    19  import (
    20  	"net"
    21  	"os"
    22  	"reflect"
    23  	"runtime"
    24  	"testing"
    25  
    26  	"github.com/pkg/errors"
    27  
    28  	"k8s.io/utils/exec"
    29  	fakeexec "k8s.io/utils/exec/testing"
    30  
    31  	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
    32  )
    33  
    34  func TestNewContainerRuntime(t *testing.T) {
    35  	execLookPathOK := &fakeexec.FakeExec{
    36  		LookPathFunc: func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
    37  	}
    38  	execLookPathErr := &fakeexec.FakeExec{
    39  		LookPathFunc: func(cmd string) (string, error) { return "", errors.Errorf("%s not found", cmd) },
    40  	}
    41  	cases := []struct {
    42  		name    string
    43  		execer  *fakeexec.FakeExec
    44  		isError bool
    45  	}{
    46  		{"valid: crictl present", execLookPathOK, false},
    47  		{"invalid: no crictl", execLookPathErr, true},
    48  	}
    49  
    50  	for _, tc := range cases {
    51  		t.Run(tc.name, func(t *testing.T) {
    52  			_, err := NewContainerRuntime(tc.execer, "unix:///some/socket.sock")
    53  			if err != nil {
    54  				if !tc.isError {
    55  					t.Fatalf("unexpected NewContainerRuntime error. error: %v", err)
    56  				}
    57  				return // expected error occurs, impossible to test runtime further
    58  			}
    59  			if tc.isError && err == nil {
    60  				t.Fatal("unexpected NewContainerRuntime success")
    61  			}
    62  		})
    63  	}
    64  }
    65  
    66  func genFakeActions(fcmd *fakeexec.FakeCmd, num int) []fakeexec.FakeCommandAction {
    67  	var actions []fakeexec.FakeCommandAction
    68  	for i := 0; i < num; i++ {
    69  		actions = append(actions, func(cmd string, args ...string) exec.Cmd {
    70  			return fakeexec.InitFakeCmd(fcmd, cmd, args...)
    71  		})
    72  	}
    73  	return actions
    74  }
    75  
    76  func TestIsRunning(t *testing.T) {
    77  	fcmd := fakeexec.FakeCmd{
    78  		CombinedOutputScript: []fakeexec.FakeAction{
    79  			func() ([]byte, []byte, error) { return nil, nil, nil },
    80  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
    81  			func() ([]byte, []byte, error) { return nil, nil, nil },
    82  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
    83  		},
    84  	}
    85  
    86  	criExecer := &fakeexec.FakeExec{
    87  		CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
    88  		LookPathFunc:  func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
    89  	}
    90  
    91  	cases := []struct {
    92  		name      string
    93  		criSocket string
    94  		execer    *fakeexec.FakeExec
    95  		isError   bool
    96  	}{
    97  		{"valid: CRI-O is running", "unix:///var/run/crio/crio.sock", criExecer, false},
    98  		{"invalid: CRI-O is not running", "unix:///var/run/crio/crio.sock", criExecer, true},
    99  	}
   100  
   101  	for _, tc := range cases {
   102  		t.Run(tc.name, func(t *testing.T) {
   103  			runtime, err := NewContainerRuntime(tc.execer, tc.criSocket)
   104  			if err != nil {
   105  				t.Fatalf("unexpected NewContainerRuntime error: %v", err)
   106  			}
   107  			isRunning := runtime.IsRunning()
   108  			if tc.isError && isRunning == nil {
   109  				t.Error("unexpected IsRunning() success")
   110  			}
   111  			if !tc.isError && isRunning != nil {
   112  				t.Error("unexpected IsRunning() error")
   113  			}
   114  		})
   115  	}
   116  }
   117  
   118  func TestListKubeContainers(t *testing.T) {
   119  	fcmd := fakeexec.FakeCmd{
   120  		CombinedOutputScript: []fakeexec.FakeAction{
   121  			func() ([]byte, []byte, error) { return []byte("k8s_p1\nk8s_p2"), nil, nil },
   122  			func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
   123  			func() ([]byte, []byte, error) { return []byte("k8s_p1\nk8s_p2"), nil, nil },
   124  		},
   125  	}
   126  	execer := &fakeexec.FakeExec{
   127  		CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
   128  		LookPathFunc:  func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
   129  	}
   130  
   131  	cases := []struct {
   132  		name      string
   133  		criSocket string
   134  		isError   bool
   135  	}{
   136  		{"valid: list containers using CRI socket url", "unix:///var/run/crio/crio.sock", false},
   137  		{"invalid: list containers using CRI socket url", "unix:///var/run/crio/crio.sock", true},
   138  	}
   139  
   140  	for _, tc := range cases {
   141  		t.Run(tc.name, func(t *testing.T) {
   142  			runtime, err := NewContainerRuntime(execer, tc.criSocket)
   143  			if err != nil {
   144  				t.Fatalf("unexpected NewContainerRuntime error: %v", err)
   145  			}
   146  
   147  			containers, err := runtime.ListKubeContainers()
   148  			if tc.isError {
   149  				if err == nil {
   150  					t.Errorf("unexpected ListKubeContainers success")
   151  				}
   152  				return
   153  			} else if err != nil {
   154  				t.Errorf("unexpected ListKubeContainers error: %v", err)
   155  			}
   156  
   157  			if !reflect.DeepEqual(containers, []string{"k8s_p1", "k8s_p2"}) {
   158  				t.Errorf("unexpected ListKubeContainers output: %v", containers)
   159  			}
   160  		})
   161  	}
   162  }
   163  
   164  func TestSandboxImage(t *testing.T) {
   165  	fcmd := fakeexec.FakeCmd{
   166  		CombinedOutputScript: []fakeexec.FakeAction{
   167  			func() ([]byte, []byte, error) { return []byte("registry.k8s.io/pause:3.9"), nil, nil },
   168  			func() ([]byte, []byte, error) { return []byte("registry.k8s.io/pause:3.9\n"), nil, nil },
   169  			func() ([]byte, []byte, error) { return nil, nil, nil },
   170  			func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
   171  		},
   172  	}
   173  
   174  	execer := &fakeexec.FakeExec{
   175  		CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
   176  		LookPathFunc:  func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
   177  	}
   178  
   179  	cases := []struct {
   180  		name     string
   181  		expected string
   182  		isError  bool
   183  	}{
   184  		{"valid: read sandbox image normally", "registry.k8s.io/pause:3.9", false},
   185  		{"valid: read sandbox image with leading/trailing white spaces", "registry.k8s.io/pause:3.9", false},
   186  		{"invalid: read empty sandbox image", "", true},
   187  		{"invalid: failed to read sandbox image", "", true},
   188  	}
   189  
   190  	for _, tc := range cases {
   191  		t.Run(tc.name, func(t *testing.T) {
   192  			runtime, err := NewContainerRuntime(execer, "unix:///some/socket.sock")
   193  			if err != nil {
   194  				t.Fatalf("unexpected NewContainerRuntime error: %v", err)
   195  			}
   196  
   197  			sandboxImage, err := runtime.SandboxImage()
   198  			if tc.isError {
   199  				if err == nil {
   200  					t.Errorf("unexpected SandboxImage success")
   201  				}
   202  				return
   203  			} else if err != nil {
   204  				t.Errorf("unexpected SandboxImage error: %v", err)
   205  			}
   206  
   207  			if sandboxImage != tc.expected {
   208  				t.Errorf("expected sandbox image %v, but got %v", tc.expected, sandboxImage)
   209  			}
   210  		})
   211  	}
   212  }
   213  
   214  func TestRemoveContainers(t *testing.T) {
   215  	fakeOK := func() ([]byte, []byte, error) { return nil, nil, nil }
   216  	fakeErr := func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} }
   217  	fcmd := fakeexec.FakeCmd{
   218  		CombinedOutputScript: []fakeexec.FakeAction{
   219  			fakeOK, fakeOK, fakeOK, fakeOK, fakeOK, fakeOK, // Test case 1
   220  			fakeOK, fakeOK, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeErr, fakeOK, fakeOK, // Test case 2
   221  			fakeErr, fakeErr, fakeErr, fakeErr, fakeErr, fakeOK, fakeOK, fakeOK, fakeOK, // Test case 3
   222  		},
   223  	}
   224  	execer := &fakeexec.FakeExec{
   225  		CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
   226  		LookPathFunc:  func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
   227  	}
   228  
   229  	cases := []struct {
   230  		name       string
   231  		criSocket  string
   232  		containers []string
   233  		isError    bool
   234  	}{
   235  		{"valid: remove containers using CRI", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, false}, // Test case 1
   236  		{"invalid: CRI rmp failure", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, true},            // Test case 2
   237  		{"invalid: CRI stopp failure", "unix:///var/run/crio/crio.sock", []string{"k8s_p1", "k8s_p2", "k8s_p3"}, true},          // Test case 3
   238  	}
   239  
   240  	for _, tc := range cases {
   241  		t.Run(tc.name, func(t *testing.T) {
   242  			runtime, err := NewContainerRuntime(execer, tc.criSocket)
   243  			if err != nil {
   244  				t.Fatalf("unexpected NewContainerRuntime error: %v, criSocket: %s", err, tc.criSocket)
   245  			}
   246  
   247  			err = runtime.RemoveContainers(tc.containers)
   248  			if !tc.isError && err != nil {
   249  				t.Errorf("unexpected RemoveContainers errors: %v, criSocket: %s, containers: %v", err, tc.criSocket, tc.containers)
   250  			}
   251  			if tc.isError && err == nil {
   252  				t.Errorf("unexpected RemoveContainers success, criSocket: %s, containers: %v", tc.criSocket, tc.containers)
   253  			}
   254  		})
   255  	}
   256  }
   257  
   258  func TestPullImage(t *testing.T) {
   259  	fcmd := fakeexec.FakeCmd{
   260  		CombinedOutputScript: []fakeexec.FakeAction{
   261  			func() ([]byte, []byte, error) { return nil, nil, nil },
   262  			// If the pull fails, it will be retried 5 times (see PullImageRetry in constants/constants.go)
   263  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   264  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   265  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   266  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   267  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   268  			func() ([]byte, []byte, error) { return nil, nil, nil },
   269  			// If the pull fails, it will be retried 5 times (see PullImageRetry in constants/constants.go)
   270  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   271  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   272  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   273  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   274  			func() ([]byte, []byte, error) { return []byte("error"), nil, &fakeexec.FakeExitError{Status: 1} },
   275  		},
   276  	}
   277  	execer := &fakeexec.FakeExec{
   278  		CommandScript: genFakeActions(&fcmd, len(fcmd.CombinedOutputScript)),
   279  		LookPathFunc:  func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
   280  	}
   281  
   282  	cases := []struct {
   283  		name      string
   284  		criSocket string
   285  		image     string
   286  		isError   bool
   287  	}{
   288  		{"valid: pull image using CRI", "unix:///var/run/crio/crio.sock", "image1", false},
   289  		{"invalid: CRI pull error", "unix:///var/run/crio/crio.sock", "image2", true},
   290  	}
   291  
   292  	for _, tc := range cases {
   293  		t.Run(tc.name, func(t *testing.T) {
   294  			runtime, err := NewContainerRuntime(execer, tc.criSocket)
   295  			if err != nil {
   296  				t.Fatalf("unexpected NewContainerRuntime error: %v, criSocket: %s", err, tc.criSocket)
   297  			}
   298  
   299  			err = runtime.PullImage(tc.image)
   300  			if !tc.isError && err != nil {
   301  				t.Errorf("unexpected PullImage error: %v, criSocket: %s, image: %s", err, tc.criSocket, tc.image)
   302  			}
   303  			if tc.isError && err == nil {
   304  				t.Errorf("unexpected PullImage success, criSocket: %s, image: %s", tc.criSocket, tc.image)
   305  			}
   306  		})
   307  	}
   308  }
   309  
   310  func TestImageExists(t *testing.T) {
   311  	fcmd := fakeexec.FakeCmd{
   312  		RunScript: []fakeexec.FakeAction{
   313  			func() ([]byte, []byte, error) { return nil, nil, nil },
   314  			func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
   315  			func() ([]byte, []byte, error) { return nil, nil, nil },
   316  			func() ([]byte, []byte, error) { return nil, nil, &fakeexec.FakeExitError{Status: 1} },
   317  		},
   318  	}
   319  	execer := &fakeexec.FakeExec{
   320  		CommandScript: genFakeActions(&fcmd, len(fcmd.RunScript)),
   321  		LookPathFunc:  func(cmd string) (string, error) { return "/usr/bin/crictl", nil },
   322  	}
   323  
   324  	cases := []struct {
   325  		name      string
   326  		criSocket string
   327  		image     string
   328  		result    bool
   329  	}{
   330  		{"valid: test if image exists using CRI", "unix:///var/run/crio/crio.sock", "image1", false},
   331  		{"invalid: CRI inspect failure", "unix:///var/run/crio/crio.sock", "image2", true},
   332  	}
   333  
   334  	for _, tc := range cases {
   335  		t.Run(tc.name, func(t *testing.T) {
   336  			runtime, err := NewContainerRuntime(execer, tc.criSocket)
   337  			if err != nil {
   338  				t.Fatalf("unexpected NewContainerRuntime error: %v, criSocket: %s", err, tc.criSocket)
   339  			}
   340  
   341  			result, err := runtime.ImageExists(tc.image)
   342  			if !tc.result != result {
   343  				t.Errorf("unexpected ImageExists result: %t, criSocket: %s, image: %s, expected result: %t", err, tc.criSocket, tc.image, tc.result)
   344  			}
   345  		})
   346  	}
   347  }
   348  
   349  func TestIsExistingSocket(t *testing.T) {
   350  	// this test is not expected to work on Windows
   351  	if runtime.GOOS == "windows" {
   352  		return
   353  	}
   354  
   355  	const tempPrefix = "test.kubeadm.runtime.isExistingSocket."
   356  	tests := []struct {
   357  		name string
   358  		proc func(*testing.T)
   359  	}{
   360  		{
   361  			name: "Valid domain socket is detected as such",
   362  			proc: func(t *testing.T) {
   363  				tmpFile, err := os.CreateTemp("", tempPrefix)
   364  				if err != nil {
   365  					t.Fatalf("unexpected error by TempFile: %v", err)
   366  				}
   367  				theSocket := tmpFile.Name()
   368  				os.Remove(theSocket)
   369  				tmpFile.Close()
   370  
   371  				con, err := net.Listen("unix", theSocket)
   372  				if err != nil {
   373  					t.Fatalf("unexpected error while dialing a socket: %v", err)
   374  				}
   375  				defer con.Close()
   376  
   377  				if !isExistingSocket("unix://" + theSocket) {
   378  					t.Fatalf("isExistingSocket(%q) gave unexpected result. Should have been true, instead of false", theSocket)
   379  				}
   380  			},
   381  		},
   382  		{
   383  			name: "Regular file is not a domain socket",
   384  			proc: func(t *testing.T) {
   385  				tmpFile, err := os.CreateTemp("", tempPrefix)
   386  				if err != nil {
   387  					t.Fatalf("unexpected error by TempFile: %v", err)
   388  				}
   389  				theSocket := tmpFile.Name()
   390  				defer os.Remove(theSocket)
   391  				tmpFile.Close()
   392  
   393  				if isExistingSocket(theSocket) {
   394  					t.Fatalf("isExistingSocket(%q) gave unexpected result. Should have been false, instead of true", theSocket)
   395  				}
   396  			},
   397  		},
   398  		{
   399  			name: "Non existent socket is not a domain socket",
   400  			proc: func(t *testing.T) {
   401  				const theSocket = "/non/existent/socket"
   402  				if isExistingSocket(theSocket) {
   403  					t.Fatalf("isExistingSocket(%q) gave unexpected result. Should have been false, instead of true", theSocket)
   404  				}
   405  			},
   406  		},
   407  	}
   408  
   409  	for _, test := range tests {
   410  		t.Run(test.name, test.proc)
   411  	}
   412  }
   413  
   414  func TestDetectCRISocketImpl(t *testing.T) {
   415  	tests := []struct {
   416  		name            string
   417  		existingSockets []string
   418  		expectedError   bool
   419  		expectedSocket  string
   420  	}{
   421  		{
   422  			name:            "No existing sockets, use default",
   423  			existingSockets: []string{},
   424  			expectedError:   false,
   425  			expectedSocket:  constants.DefaultCRISocket,
   426  		},
   427  		{
   428  			name:            "One valid CRI socket leads to success",
   429  			existingSockets: []string{"unix:///foo/bar.sock"},
   430  			expectedError:   false,
   431  			expectedSocket:  "unix:///foo/bar.sock",
   432  		},
   433  		{
   434  			name: "Multiple CRI sockets lead to an error",
   435  			existingSockets: []string{
   436  				"unix:///foo/bar.sock",
   437  				"unix:///foo/baz.sock",
   438  			},
   439  			expectedError: true,
   440  		},
   441  	}
   442  
   443  	for _, test := range tests {
   444  		t.Run(test.name, func(t *testing.T) {
   445  			socket, err := detectCRISocketImpl(func(path string) bool {
   446  				for _, existing := range test.existingSockets {
   447  					if path == existing {
   448  						return true
   449  					}
   450  				}
   451  				return false
   452  			}, test.existingSockets)
   453  
   454  			if (err != nil) != test.expectedError {
   455  				t.Fatalf("detectCRISocketImpl returned unexpected result\n\tExpected error: %t\n\tGot error: %t", test.expectedError, err != nil)
   456  			}
   457  			if !test.expectedError && socket != test.expectedSocket {
   458  				t.Fatalf("detectCRISocketImpl returned unexpected CRI socket\n\tExpected socket: %s\n\tReturned socket: %s",
   459  					test.expectedSocket, socket)
   460  			}
   461  		})
   462  	}
   463  }
   464  
   465  func TestPullImagesInParallelImpl(t *testing.T) {
   466  	testError := errors.New("error")
   467  
   468  	tests := []struct {
   469  		name            string
   470  		images          []string
   471  		ifNotPresent    bool
   472  		imageExistsFunc func(string) (bool, error)
   473  		pullImageFunc   func(string) error
   474  		expectedErrors  int
   475  	}{
   476  		{
   477  			name:         "all images exist, no errors",
   478  			images:       []string{"foo", "bar", "baz"},
   479  			ifNotPresent: true,
   480  			imageExistsFunc: func(string) (bool, error) {
   481  				return true, nil
   482  			},
   483  			pullImageFunc:  nil,
   484  			expectedErrors: 0,
   485  		},
   486  		{
   487  			name:         "cannot check if one image exists due to error",
   488  			images:       []string{"foo", "bar", "baz"},
   489  			ifNotPresent: true,
   490  			imageExistsFunc: func(image string) (bool, error) {
   491  				if image == "baz" {
   492  					return false, testError
   493  				}
   494  				return true, nil
   495  			},
   496  			pullImageFunc:  nil,
   497  			expectedErrors: 1,
   498  		},
   499  		{
   500  			name:         "cannot pull two images",
   501  			images:       []string{"foo", "bar", "baz"},
   502  			ifNotPresent: true,
   503  			imageExistsFunc: func(string) (bool, error) {
   504  				return false, nil
   505  			},
   506  			pullImageFunc: func(image string) error {
   507  				if image == "foo" {
   508  					return nil
   509  				}
   510  				return testError
   511  			},
   512  			expectedErrors: 2,
   513  		},
   514  		{
   515  			name:         "pull all images",
   516  			images:       []string{"foo", "bar", "baz"},
   517  			ifNotPresent: true,
   518  			imageExistsFunc: func(string) (bool, error) {
   519  				return false, nil
   520  			},
   521  			pullImageFunc: func(string) error {
   522  				return nil
   523  			},
   524  			expectedErrors: 0,
   525  		},
   526  	}
   527  
   528  	for _, tc := range tests {
   529  		t.Run(tc.name, func(t *testing.T) {
   530  			actual := pullImagesInParallelImpl(tc.images, tc.ifNotPresent,
   531  				tc.imageExistsFunc, tc.pullImageFunc)
   532  			if len(actual) != tc.expectedErrors {
   533  				t.Fatalf("expected non-nil errors: %v, got: %v, full list of errors: %v",
   534  					tc.expectedErrors, len(actual), actual)
   535  			}
   536  		})
   537  	}
   538  }
   539  

View as plain text