gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+incompatible/executors/docker/internal/volumes/cache_container_test.go (about)

     1  package volumes
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/container"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/mock"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestNewCacheContainerManager(t *testing.T) {
    17  	logger := newDebugLoggerMock()
    18  
    19  	m := NewCacheContainerManager(context.Background(), logger, nil, nil, true)
    20  	assert.IsType(t, &cacheContainerManager{}, m)
    21  }
    22  
    23  func getCacheContainerManager() (*cacheContainerManager, *mockContainerClient) {
    24  	cClient := new(mockContainerClient)
    25  
    26  	m := &cacheContainerManager{
    27  		logger:              newDebugLoggerMock(),
    28  		containerClient:     cClient,
    29  		failedContainerIDs:  make([]string, 0),
    30  		helperImage:         &types.ImageInspect{ID: "helper-image"},
    31  		outdatedHelperImage: false,
    32  	}
    33  
    34  	return m, cClient
    35  }
    36  
    37  func TestCacheContainerManager_FindExistingCacheContainer(t *testing.T) {
    38  	containerName := "container-name"
    39  	containerPath := "container-path"
    40  
    41  	testCases := map[string]struct {
    42  		inspectResult       types.ContainerJSON
    43  		inspectError        error
    44  		expectedContainerID string
    45  		expectedRemoveID    string
    46  	}{
    47  		"error on container inspection": {
    48  			inspectError:        errors.New("test error"),
    49  			expectedContainerID: "",
    50  		},
    51  		"container with valid cache exists": {
    52  			inspectResult: types.ContainerJSON{
    53  				ContainerJSONBase: &types.ContainerJSONBase{
    54  					ID: "existingWithValidCacheID",
    55  				},
    56  				Config: &container.Config{
    57  					Volumes: map[string]struct{}{
    58  						containerPath: {},
    59  					},
    60  				},
    61  			},
    62  			inspectError:        nil,
    63  			expectedContainerID: "existingWithValidCacheID",
    64  		},
    65  		"container without valid cache exists": {
    66  			inspectResult: types.ContainerJSON{
    67  				ContainerJSONBase: &types.ContainerJSONBase{
    68  					ID: "existingWithInvalidCacheID",
    69  				},
    70  				Config: &container.Config{
    71  					Volumes: map[string]struct{}{
    72  						"different-path": {},
    73  					},
    74  				},
    75  			},
    76  			inspectError:        nil,
    77  			expectedContainerID: "",
    78  			expectedRemoveID:    "existingWithInvalidCacheID",
    79  		},
    80  	}
    81  
    82  	for testName, testCase := range testCases {
    83  		t.Run(testName, func(t *testing.T) {
    84  			m, cClient := getCacheContainerManager()
    85  			defer cClient.AssertExpectations(t)
    86  
    87  			cClient.On("ContainerInspect", mock.Anything, containerName).
    88  				Return(testCase.inspectResult, testCase.inspectError).
    89  				Once()
    90  
    91  			if testCase.expectedRemoveID != "" {
    92  				cClient.On("RemoveContainer", mock.Anything, testCase.expectedRemoveID).
    93  					Return(nil).
    94  					Once()
    95  			}
    96  
    97  			containerID := m.FindOrCleanExisting(containerName, containerPath)
    98  			assert.Equal(t, testCase.expectedContainerID, containerID)
    99  		})
   100  	}
   101  }
   102  
   103  func TestCacheContainerManager_CreateCacheContainer(t *testing.T) {
   104  	containerName := "container-name"
   105  	containerPath := "container-path"
   106  
   107  	testCases := map[string]struct {
   108  		expectedContainerID       string
   109  		createResult              container.ContainerCreateCreatedBody
   110  		createError               error
   111  		containerID               string
   112  		startError                error
   113  		waitForContainerError     error
   114  		expectedFailedContainerID string
   115  		expectedError             error
   116  	}{
   117  		"error on container create": {
   118  			createError:   errors.New("test error"),
   119  			expectedError: errors.New("test error"),
   120  		},
   121  		"error on container create with returnedID": {
   122  			createResult: container.ContainerCreateCreatedBody{
   123  				ID: "containerID",
   124  			},
   125  			createError:               errors.New("test error"),
   126  			expectedFailedContainerID: "containerID",
   127  			expectedError:             errors.New("test error"),
   128  		},
   129  		"error on container start": {
   130  			createResult: container.ContainerCreateCreatedBody{
   131  				ID: "containerID",
   132  			},
   133  			containerID:               "containerID",
   134  			startError:                errors.New("test error"),
   135  			expectedFailedContainerID: "containerID",
   136  			expectedError:             errors.New("test error"),
   137  		},
   138  		"error on wait for container": {
   139  			createResult: container.ContainerCreateCreatedBody{
   140  				ID: "containerID",
   141  			},
   142  			containerID:               "containerID",
   143  			waitForContainerError:     errors.New("test error"),
   144  			expectedFailedContainerID: "containerID",
   145  			expectedError:             errors.New("test error"),
   146  		},
   147  		"success": {
   148  			createResult: container.ContainerCreateCreatedBody{
   149  				ID: "containerID",
   150  			},
   151  			containerID:         "containerID",
   152  			expectedContainerID: "containerID",
   153  			expectedError:       nil,
   154  		},
   155  	}
   156  
   157  	// TODO: Remove in 12.0
   158  	outdatedHelperImageValues := map[bool][]string{
   159  		true:  {"gitlab-runner-cache", "container-path"},
   160  		false: {"gitlab-runner-helper", "cache-init", "container-path"},
   161  	}
   162  
   163  	for testName, testCase := range testCases {
   164  		for outdatedHelperImage, expectedCommand := range outdatedHelperImageValues {
   165  			t.Run(fmt.Sprintf("%s-outdated-helper-image-is-%v", testName, outdatedHelperImage), func(t *testing.T) {
   166  				m, cClient := getCacheContainerManager()
   167  				m.outdatedHelperImage = outdatedHelperImage
   168  
   169  				defer cClient.AssertExpectations(t)
   170  
   171  				configMatcher := mock.MatchedBy(func(config *container.Config) bool {
   172  					if config.Image != "helper-image" {
   173  						return false
   174  					}
   175  
   176  					if len(config.Cmd) != len(expectedCommand) {
   177  						return false
   178  					}
   179  
   180  					return config.Cmd[0] == expectedCommand[0]
   181  				})
   182  
   183  				cClient.On("LabelContainer", configMatcher, "cache", fmt.Sprintf("cache.dir=%s", containerPath)).
   184  					Once()
   185  
   186  				cClient.On("ContainerCreate", mock.Anything, configMatcher, mock.Anything, mock.Anything, containerName).
   187  					Return(testCase.createResult, testCase.createError).
   188  					Once()
   189  
   190  				if testCase.createError == nil {
   191  					cClient.On("ContainerStart", mock.Anything, testCase.containerID, mock.Anything).
   192  						Return(testCase.startError).
   193  						Once()
   194  
   195  					if testCase.startError == nil {
   196  						cClient.On("WaitForContainer", testCase.containerID).
   197  							Return(testCase.waitForContainerError).
   198  							Once()
   199  					}
   200  				}
   201  
   202  				require.Empty(t, m.failedContainerIDs, "Initial list of failed containers should be empty")
   203  
   204  				containerID, err := m.Create(containerName, containerPath)
   205  				assert.Equal(t, err, testCase.expectedError)
   206  				assert.Equal(t, testCase.expectedContainerID, containerID)
   207  
   208  				if testCase.expectedFailedContainerID != "" {
   209  					assert.Len(t, m.failedContainerIDs, 1)
   210  					assert.Contains(
   211  						t, m.failedContainerIDs, testCase.expectedFailedContainerID,
   212  						"List of failed container should be updated with %s", testCase.expectedContainerID,
   213  					)
   214  				} else {
   215  					assert.Empty(t, m.failedContainerIDs, "List of failed containers should not be updated")
   216  				}
   217  			})
   218  		}
   219  	}
   220  }
   221  
   222  func TestCacheContainerManager_Cleanup(t *testing.T) {
   223  	ctx := context.Background()
   224  
   225  	containerClientMock := new(mockContainerClient)
   226  	defer containerClientMock.AssertExpectations(t)
   227  
   228  	loggerMock := new(mockDebugLogger)
   229  	defer loggerMock.AssertExpectations(t)
   230  
   231  	containerClientMock.On("RemoveContainer", ctx, "failed-container-1").
   232  		Return(nil).
   233  		Once()
   234  	containerClientMock.On("RemoveContainer", ctx, "container-1-with-remove-error").
   235  		Return(errors.New("test-error")).
   236  		Once()
   237  	containerClientMock.On("RemoveContainer", ctx, "container-1").
   238  		Return(nil).
   239  		Once()
   240  
   241  	loggerMock.On("Debugln", "Error while removing the container: test-error").
   242  		Once()
   243  
   244  	m := &cacheContainerManager{
   245  		containerClient:    containerClientMock,
   246  		logger:             loggerMock,
   247  		failedContainerIDs: []string{"failed-container-1", "container-1-with-remove-error"},
   248  	}
   249  
   250  	done := m.Cleanup(ctx, []string{"container-1"})
   251  
   252  	<-done
   253  }