github.com/secure-build/gitlab-runner@v12.5.0+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)
    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  	}
    32  
    33  	return m, cClient
    34  }
    35  
    36  func TestCacheContainerManager_FindExistingCacheContainer(t *testing.T) {
    37  	containerName := "container-name"
    38  	containerPath := "container-path"
    39  
    40  	testCases := map[string]struct {
    41  		inspectResult       types.ContainerJSON
    42  		inspectError        error
    43  		expectedContainerID string
    44  		expectedRemoveID    string
    45  	}{
    46  		"error on container inspection": {
    47  			inspectError:        errors.New("test error"),
    48  			expectedContainerID: "",
    49  		},
    50  		"container with valid cache exists": {
    51  			inspectResult: types.ContainerJSON{
    52  				ContainerJSONBase: &types.ContainerJSONBase{
    53  					ID: "existingWithValidCacheID",
    54  				},
    55  				Config: &container.Config{
    56  					Volumes: map[string]struct{}{
    57  						containerPath: {},
    58  					},
    59  				},
    60  			},
    61  			inspectError:        nil,
    62  			expectedContainerID: "existingWithValidCacheID",
    63  		},
    64  		"container without valid cache exists": {
    65  			inspectResult: types.ContainerJSON{
    66  				ContainerJSONBase: &types.ContainerJSONBase{
    67  					ID: "existingWithInvalidCacheID",
    68  				},
    69  				Config: &container.Config{
    70  					Volumes: map[string]struct{}{
    71  						"different-path": {},
    72  					},
    73  				},
    74  			},
    75  			inspectError:        nil,
    76  			expectedContainerID: "",
    77  			expectedRemoveID:    "existingWithInvalidCacheID",
    78  		},
    79  	}
    80  
    81  	for testName, testCase := range testCases {
    82  		t.Run(testName, func(t *testing.T) {
    83  			m, cClient := getCacheContainerManager()
    84  			defer cClient.AssertExpectations(t)
    85  
    86  			cClient.On("ContainerInspect", mock.Anything, containerName).
    87  				Return(testCase.inspectResult, testCase.inspectError).
    88  				Once()
    89  
    90  			if testCase.expectedRemoveID != "" {
    91  				cClient.On("RemoveContainer", mock.Anything, testCase.expectedRemoveID).
    92  					Return(nil).
    93  					Once()
    94  			}
    95  
    96  			containerID := m.FindOrCleanExisting(containerName, containerPath)
    97  			assert.Equal(t, testCase.expectedContainerID, containerID)
    98  		})
    99  	}
   100  }
   101  
   102  func TestCacheContainerManager_CreateCacheContainer(t *testing.T) {
   103  	containerName := "container-name"
   104  	containerPath := "container-path"
   105  	expectedCacheCmd := []string{"gitlab-runner-helper", "cache-init", containerPath}
   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  	for testName, testCase := range testCases {
   158  		t.Run(testName, func(t *testing.T) {
   159  			m, cClient := getCacheContainerManager()
   160  
   161  			defer cClient.AssertExpectations(t)
   162  
   163  			configMatcher := mock.MatchedBy(func(config *container.Config) bool {
   164  				if config.Image != "helper-image" {
   165  					return false
   166  				}
   167  
   168  				if len(config.Cmd) != len(expectedCacheCmd) {
   169  					return false
   170  				}
   171  
   172  				return config.Cmd[0] == expectedCacheCmd[0]
   173  			})
   174  
   175  			cClient.On("LabelContainer", configMatcher, "cache", fmt.Sprintf("cache.dir=%s", containerPath)).
   176  				Once()
   177  
   178  			cClient.On("ContainerCreate", mock.Anything, configMatcher, mock.Anything, mock.Anything, containerName).
   179  				Return(testCase.createResult, testCase.createError).
   180  				Once()
   181  
   182  			if testCase.createError == nil {
   183  				cClient.On("ContainerStart", mock.Anything, testCase.containerID, mock.Anything).
   184  					Return(testCase.startError).
   185  					Once()
   186  
   187  				if testCase.startError == nil {
   188  					cClient.On("WaitForContainer", testCase.containerID).
   189  						Return(testCase.waitForContainerError).
   190  						Once()
   191  				}
   192  			}
   193  
   194  			require.Empty(t, m.failedContainerIDs, "Initial list of failed containers should be empty")
   195  
   196  			containerID, err := m.Create(containerName, containerPath)
   197  			assert.Equal(t, err, testCase.expectedError)
   198  			assert.Equal(t, testCase.expectedContainerID, containerID)
   199  
   200  			if testCase.expectedFailedContainerID != "" {
   201  				assert.Len(t, m.failedContainerIDs, 1)
   202  				assert.Contains(
   203  					t, m.failedContainerIDs, testCase.expectedFailedContainerID,
   204  					"List of failed container should be updated with %s", testCase.expectedContainerID,
   205  				)
   206  			} else {
   207  				assert.Empty(t, m.failedContainerIDs, "List of failed containers should not be updated")
   208  			}
   209  		})
   210  	}
   211  }
   212  
   213  func TestCacheContainerManager_Cleanup(t *testing.T) {
   214  	ctx := context.Background()
   215  
   216  	containerClientMock := new(mockContainerClient)
   217  	defer containerClientMock.AssertExpectations(t)
   218  
   219  	loggerMock := new(mockDebugLogger)
   220  	defer loggerMock.AssertExpectations(t)
   221  
   222  	containerClientMock.On("RemoveContainer", ctx, "failed-container-1").
   223  		Return(nil).
   224  		Once()
   225  	containerClientMock.On("RemoveContainer", ctx, "container-1-with-remove-error").
   226  		Return(errors.New("test-error")).
   227  		Once()
   228  	containerClientMock.On("RemoveContainer", ctx, "container-1").
   229  		Return(nil).
   230  		Once()
   231  
   232  	loggerMock.On("Debugln", "Error while removing the container: test-error").
   233  		Once()
   234  
   235  	m := &cacheContainerManager{
   236  		containerClient:    containerClientMock,
   237  		logger:             loggerMock,
   238  		failedContainerIDs: []string{"failed-container-1", "container-1-with-remove-error"},
   239  	}
   240  
   241  	done := m.Cleanup(ctx, []string{"container-1"})
   242  
   243  	<-done
   244  }