diff --git a/containerm/ctr/ctrd_client_init.go b/containerm/ctr/ctrd_client_init.go index 5a3a0f3..4b4e9ce 100644 --- a/containerm/ctr/ctrd_client_init.go +++ b/containerm/ctr/ctrd_client_init.go @@ -24,7 +24,7 @@ import ( "github.com/eclipse-kanto/container-management/containerm/util" ) -func newContainerdClient(namespace, socket, rootExec, metaPath string, registryConfigs map[string]*RegistryConfig, imageDecKeys, imageDecRecipients []string, runcRuntime types.Runtime, imageExpiry time.Duration, imageExpiryDisable bool, leaseID string) (ContainerAPIClient, error) { +func newContainerdClient(namespace string, socket string, rootExec string, metaPath string, registryConfigs map[string]*RegistryConfig, imageDecKeys, imageDecRecipients []string, runcRuntime types.Runtime, imageExpiry time.Duration, imageExpiryDisable bool, leaseID string) (ContainerAPIClient, error) { //ensure storage err := util.MkDir(rootExec) diff --git a/containerm/ctr/ctrd_client_internal_test.go b/containerm/ctr/ctrd_client_internal_test.go index 872ba4d..08cdbb4 100644 --- a/containerm/ctr/ctrd_client_internal_test.go +++ b/containerm/ctr/ctrd_client_internal_test.go @@ -1144,7 +1144,7 @@ func TestClientInternalProcessEvents(t *testing.T) { } func TestClientInternalIsImageUsed(t *testing.T) { - testImgRef := "test.image/ref:latest" + const testImgRef = "test.image/ref:latest" entryDigest := digest.NewDigest(digest.SHA256, sha256.New()) testCases := map[string]struct { @@ -1200,7 +1200,7 @@ func TestClientInternalIsImageUsed(t *testing.T) { } func TestClientInternalRemoveUnusedImage(t *testing.T) { - testImgRef := "test.image/ref:latest" + const testImgRef = "test.image/ref:latest" entryDigest := digest.NewDigest(digest.SHA256, sha256.New()) testCases := map[string]struct { @@ -1222,6 +1222,15 @@ func TestClientInternalRemoveUnusedImage(t *testing.T) { return errImageIsInUse }, }, + "test_not_used_delete_not_found_error": { + mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, imageMock *mocksContainerd.MockImage) error { + imageMock.EXPECT().Name().Return(testImgRef).Times(2) + imageMock.EXPECT().RootFS(ctx).Return([]digest.Digest{entryDigest}, nil) + spiMock.EXPECT().ListSnapshots(ctx, fmt.Sprintf(snapshotsWalkFilterFormat, entryDigest.String())).Return(nil, nil) + spiMock.EXPECT().DeleteImage(ctx, testImgRef).Return(errdefs.ErrNotFound) + return nil + }, + }, "test_not_used_delete_error": { mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, imageMock *mocksContainerd.MockImage) error { imageMock.EXPECT().Name().Return(testImgRef).Times(2) @@ -1266,7 +1275,7 @@ func TestClientInternalRemoveUnusedImage(t *testing.T) { } func TestClientInternalHandleImageExpired(t *testing.T) { - testImgRef := "test.image/ref:latest" + const testImgRef = "test.image/ref:latest" entryDigest := digest.NewDigest(digest.SHA256, sha256.New()) testCases := map[string]struct { @@ -1294,6 +1303,15 @@ func TestClientInternalHandleImageExpired(t *testing.T) { return err }, }, + "test_used_error": { + mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, imageMock *mocksContainerd.MockImage) error { + spiMock.EXPECT().GetImage(ctx, testImgRef).Return(imageMock, nil) + imageMock.EXPECT().Name().Return(testImgRef).Times(2) + imageMock.EXPECT().RootFS(ctx).Return([]digest.Digest{entryDigest}, nil) + spiMock.EXPECT().ListSnapshots(ctx, fmt.Sprintf(snapshotsWalkFilterFormat, entryDigest.String())).Return([]snapshots.Info{{}}, nil) + return nil + }, + }, "test_no_error": { mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, imageMock *mocksContainerd.MockImage) error { spiMock.EXPECT().GetImage(ctx, testImgRef).Return(imageMock, nil) @@ -1329,7 +1347,7 @@ func TestClientInternalHandleImageExpired(t *testing.T) { } func TestClientInternalManageImageExpiry(t *testing.T) { - testImgRef := "test.image/ref:latest" + const testImgRef = "test.image/ref:latest" entryDigest := digest.NewDigest(digest.SHA256, sha256.New()) testCases := map[string]struct { @@ -1421,3 +1439,145 @@ func TestClientInternalManageImageExpiry(t *testing.T) { }) } } +func TestClientInternalHandleImageExpiryOnRemove(t *testing.T) { + const ( + testImgRef = "test.image/ref:latest" + imagesExpiry = 2 * time.Hour + ) + + testCases := map[string]struct { + imagesExpiryDisabled bool + mockExec func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, watcherMock *MockresourcesWatcher, imageMock *mocksContainerd.MockImage) error + }{ + "test_get_image_error": { + imagesExpiryDisabled: false, + mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, _ *MockresourcesWatcher, _ *mocksContainerd.MockImage) error { + err := log.NewError("test error") + spiMock.EXPECT().GetImage(ctx, testImgRef).Return(nil, err) + return err + }, + }, + "test_manage_expiry_error": { + imagesExpiryDisabled: false, + mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, watcherMock *MockresourcesWatcher, imageMock *mocksContainerd.MockImage) error { + spiMock.EXPECT().GetImage(ctx, testImgRef).Return(imageMock, nil) + imageMock.EXPECT().Name().Return(testImgRef) + imageMock.EXPECT().Metadata().Return(images.Image{CreatedAt: time.Now().Add(-5 * time.Minute)}) + err := log.NewError("test error") + watcherMock.EXPECT().Watch(testImgRef, gomock.Any(), gomock.Any()).Return(err) + return err + }, + }, + "test_manage_expiry_no_error": { + imagesExpiryDisabled: false, + mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, watcherMock *MockresourcesWatcher, imageMock *mocksContainerd.MockImage) error { + spiMock.EXPECT().GetImage(ctx, testImgRef).Return(imageMock, nil) + imageMock.EXPECT().Name().Return(testImgRef) + imageMock.EXPECT().Metadata().Return(images.Image{CreatedAt: time.Now().Add(-5 * time.Minute)}) + watcherMock.EXPECT().Watch(testImgRef, gomock.Any(), gomock.Any()).Return(nil) + return nil + }, + }, + "test_disabled": { + imagesExpiryDisabled: true, + mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, watcherMock *MockresourcesWatcher, imageMock *mocksContainerd.MockImage) error { + spiMock.EXPECT().GetImage(ctx, testImgRef).Return(imageMock, nil).Times(0) + imageMock.EXPECT().Name().Return(testImgRef).Times(0) + imageMock.EXPECT().Metadata().Return(images.Image{CreatedAt: time.Now().Add(-5 * time.Minute)}).Times(0) + watcherMock.EXPECT().Watch(testImgRef, gomock.Any(), gomock.Any()).Return(nil).Times(0) + return nil + }, + }, + } + + for testCaseName, testCaseData := range testCases { + t.Run(testCaseName, func(t *testing.T) { + t.Log(testCaseName) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + // init mocks + spiMock := mocksCtrd.NewMockcontainerdSpi(ctrl) + watcherMock := NewMockresourcesWatcher(ctrl) + imageMock := mocksContainerd.NewMockImage(ctrl) + + ctx := context.Background() + ctrdClient := &containerdClient{ + spi: spiMock, + imagesWatcher: watcherMock, + imageExpiry: imagesExpiry, + imageExpiryDisable: testCaseData.imagesExpiryDisabled, + } + // mock exec + expectedErr := testCaseData.mockExec(ctx, spiMock, watcherMock, imageMock) + + err := ctrdClient.handleImageExpiryOnRemove(ctx, testImgRef) + testutil.AssertError(t, expectedErr, err) + }) + } +} + +func TestClientInternalInitImagesExpiryManagement(t *testing.T) { + const ( + testImgRef = "test.image/ref:latest" + imagesExpiry = 2 * time.Hour + ) + + testCases := map[string]struct { + mockExec func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, watcherMock *MockresourcesWatcher, imageMock *mocksContainerd.MockImage) error + }{ + "test_list_images_error": { + mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, _ *MockresourcesWatcher, _ *mocksContainerd.MockImage) error { + err := log.NewError("test error") + spiMock.EXPECT().ListImages(ctx).Return(nil, err) + return err + }, + }, + "test_watch_image_error": { + mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, watcherMock *MockresourcesWatcher, imageMock *mocksContainerd.MockImage) error { + spiMock.EXPECT().ListImages(ctx).Return([]containerd.Image{imageMock, imageMock}, nil) + imageMock.EXPECT().Name().Return(testImgRef).Times(4) + imageMock.EXPECT().Metadata().Return(images.Image{CreatedAt: time.Now().Add(-5 * time.Minute)}).Times(2) // not expired + err := log.NewError("test error") + gomock.InOrder( + watcherMock.EXPECT().Watch(testImgRef, gomock.Any(), gomock.Any()).Return(err), // only first one fails + watcherMock.EXPECT().Watch(testImgRef, gomock.Any(), gomock.Any()).Return(nil), + ) + return nil + }, + }, + "test_no_error": { + mockExec: func(ctx context.Context, spiMock *mocksCtrd.MockcontainerdSpi, watcherMock *MockresourcesWatcher, imageMock *mocksContainerd.MockImage) error { + spiMock.EXPECT().ListImages(ctx).Return([]containerd.Image{imageMock, imageMock}, nil) + imageMock.EXPECT().Name().Return(testImgRef).Times(4) + imageMock.EXPECT().Metadata().Return(images.Image{CreatedAt: time.Now().Add(-5 * time.Minute)}).Times(2) // not expired + watcherMock.EXPECT().Watch(testImgRef, gomock.Any(), gomock.Any()).Return(nil).Times(2) + return nil + }, + }, + } + for testCaseName, testCaseData := range testCases { + t.Run(testCaseName, func(t *testing.T) { + t.Log(testCaseName) + + ctrl := gomock.NewController(t) + defer ctrl.Finish() + // init mocks + spiMock := mocksCtrd.NewMockcontainerdSpi(ctrl) + watcherMock := NewMockresourcesWatcher(ctrl) + imageMock := mocksContainerd.NewMockImage(ctrl) + + ctx := context.Background() + ctrdClient := &containerdClient{ + spi: spiMock, + imagesWatcher: watcherMock, + imageExpiry: imagesExpiry, + } + // mock exec + expectedErr := testCaseData.mockExec(ctx, spiMock, watcherMock, imageMock) + + err := ctrdClient.initImagesExpiryManagement(ctx) + testutil.AssertError(t, expectedErr, err) + }) + } +} diff --git a/containerm/ctr/ctrd_resource_watcher.go b/containerm/ctr/ctrd_resource_watcher.go index 509aeea..01e9f45 100644 --- a/containerm/ctr/ctrd_resource_watcher.go +++ b/containerm/ctr/ctrd_resource_watcher.go @@ -36,16 +36,18 @@ type watchInfo struct { type resWatcher struct { sync.Mutex - watchCache map[string]watchInfo - watchCacheLock sync.RWMutex - watcherCtx context.Context - watcherCtxCancel context.CancelFunc + watchCache map[string]watchInfo + watchCacheLock sync.RWMutex + watcherCtx context.Context + watcherCtxCancel context.CancelFunc + watchCacheWaitGroup *sync.WaitGroup } func newResourcesWatcher(ctx context.Context) resourcesWatcher { watcher := &resWatcher{ - watchCache: make(map[string]watchInfo), - watchCacheLock: sync.RWMutex{}, + watchCache: make(map[string]watchInfo), + watchCacheLock: sync.RWMutex{}, + watchCacheWaitGroup: &sync.WaitGroup{}, } watcher.watcherCtx, watcher.watcherCtxCancel = context.WithCancel(ctx) return watcher @@ -72,7 +74,9 @@ func (watcher *resWatcher) Watch(resourceID string, duration time.Duration, expi expiredHandler: expiredHandler, } watcher.watchCache[info.resourceID] = info + watcher.watchCacheWaitGroup.Add(1) go func(ctx context.Context, info watchInfo) { + defer watcher.watchCacheWaitGroup.Done() select { case <-info.timer.C: if info.expiredHandler != nil { @@ -80,9 +84,10 @@ func (watcher *resWatcher) Watch(resourceID string, duration time.Duration, expi log.WarnErr(err, "error while handling monitoring expiry for resource %s", info.resourceID) } } - watcher.cleanCache(info.resourceID) + watcher.cleanCache(info.resourceID, false) log.Debug("successfully processed expired resource %s", info.resourceID) case <-ctx.Done(): + watcher.cleanCache(info.resourceID, true) log.Debug("cancelled monitoring for resource %s", info.resourceID) } log.Debug("finished watch process for resource %s", info.resourceID) @@ -97,23 +102,22 @@ func (watcher *resWatcher) Dispose() { log.Debug("resource watcher is disposing") watcher.watcherCtxCancel() - watcher.watchCacheLock.RLock() - defer watcher.watchCacheLock.RUnlock() + log.Debug("waiting for monitoring routines to finish") + watcher.watchCacheWaitGroup.Wait() - for infoKey, info := range watcher.watchCache { - log.Debug("stopping monitoring for resource %s", infoKey) - info.timer.Stop() - } log.Debug("resource watcher disposed") } -func (watcher *resWatcher) cleanCache(id string) { +func (watcher *resWatcher) cleanCache(id string, withStop bool) { watcher.watchCacheLock.Lock() defer watcher.watchCacheLock.Unlock() info, ok := watcher.watchCache[id] if ok { + if withStop && info.timer.Stop() { + log.Debug("stopped monitoring timer for resource %s", info.resourceID) + } delete(watcher.watchCache, id) log.Debug("removed watch cache for resource %s", info.resourceID) } else { - log.Debug("no watch cache to remove for resource %s", info.resourceID) + log.Warn("no watch cache found for resource %s", info.resourceID) } } diff --git a/containerm/ctr/ctrd_resource_watcher_test.go b/containerm/ctr/ctrd_resource_watcher_test.go new file mode 100644 index 0000000..4f16934 --- /dev/null +++ b/containerm/ctr/ctrd_resource_watcher_test.go @@ -0,0 +1,226 @@ +// Copyright (c) 2022 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + +package ctr + +import ( + "context" + "github.com/eclipse-kanto/container-management/containerm/log" + "github.com/eclipse-kanto/container-management/containerm/pkg/testutil" + "reflect" + "sync" + "testing" + "time" +) + +func TestNewResourceWatcher(t *testing.T) { + ctx := context.Background() + + expectedCtx, expextedCtxCancel := context.WithCancel(ctx) + + testResourceWatcher := newResourcesWatcher(ctx) + testResourceWatcherInternal, ok := testResourceWatcher.(*resWatcher) + testutil.AssertTrue(t, ok) + testutil.AssertNotNil(t, testResourceWatcherInternal.watchCache) + testutil.AssertEqual(t, 0, len(testResourceWatcherInternal.watchCache)) + testutil.AssertEqual(t, expectedCtx, testResourceWatcherInternal.watcherCtx) + testutil.AssertEqual(t, reflect.ValueOf(expextedCtxCancel).Pointer(), reflect.ValueOf(testResourceWatcherInternal.watcherCtxCancel).Pointer()) + testutil.AssertNotNil(t, testResourceWatcherInternal.watchCacheWaitGroup) +} + +func TestResourceWatcherWatch(t *testing.T) { + const ( + testResourceID = "test-res-id" + testExpiryDuration = 5 * time.Hour + testTimeoutDuration = 5 * time.Second + ) + + testCases := map[string]struct { + watchCache map[string]watchInfo + expectedWatchCache map[string]watchInfo + prepareTestCtx func() (context.Context, context.CancelFunc) + expectedError error + }{ + "test_ctx_cancelled": { + watchCache: map[string]watchInfo{}, + expectedWatchCache: map[string]watchInfo{}, + prepareTestCtx: func() (context.Context, context.CancelFunc) { + ctx, cancel := context.WithCancel(context.Background()) + cancel() + return ctx, cancel + }, + expectedError: context.Canceled, + }, + "test_already_watched": { + watchCache: map[string]watchInfo{ + testResourceID: { + resourceID: testResourceID, + timer: time.NewTimer(testExpiryDuration), + }, + }, + expectedWatchCache: map[string]watchInfo{ + testResourceID: { + resourceID: testResourceID, + timer: time.NewTimer(testExpiryDuration), + }, + }, + prepareTestCtx: func() (context.Context, context.CancelFunc) { + return context.WithCancel(context.Background()) + }, + expectedError: errAlreadyWatched, + }, + "test_added_to_watch_cache_successfully": { + watchCache: map[string]watchInfo{}, + expectedWatchCache: map[string]watchInfo{ + testResourceID: { + resourceID: testResourceID, + }, + }, + prepareTestCtx: func() (context.Context, context.CancelFunc) { + return context.WithCancel(context.Background()) + }, + expectedError: nil, + }, + } + + for testName, testData := range testCases { + t.Run(testName, func(t *testing.T) { + t.Log(testName) + + testResourceWatch := &resWatcher{ + watchCache: testData.watchCache, + watchCacheLock: sync.RWMutex{}, + watchCacheWaitGroup: &sync.WaitGroup{}, + } + testResourceWatch.watcherCtx, testResourceWatch.watcherCtxCancel = testData.prepareTestCtx() + + defer func() { + testResourceWatch.watcherCtxCancel() + testutil.AssertWithTimeout(t, testResourceWatch.watchCacheWaitGroup, testTimeoutDuration) + }() + + actualErr := testResourceWatch.Watch(testResourceID, testExpiryDuration, nil) + + testutil.AssertError(t, testData.expectedError, actualErr) + testutil.AssertEqual(t, len(testData.expectedWatchCache), len(testResourceWatch.watchCache)) + + if len(testData.expectedWatchCache) > 0 { + for resID, resInfo := range testData.expectedWatchCache { + actualWatchInfo, ok := testResourceWatch.watchCache[resID] + testutil.AssertTrue(t, ok) + testutil.AssertNotNil(t, actualWatchInfo) + testutil.AssertEqual(t, resInfo.resourceID, actualWatchInfo.resourceID) + testutil.AssertNotNil(t, actualWatchInfo.timer) + } + } + }) + } +} + +func TestResourceWatcherWatchHandling(t *testing.T) { + const ( + testResourceID = "test-res-id" + testsExecutionTimeout = 10 * time.Second + ) + + testCases := map[string]struct { + doCancel bool + expiryDuration time.Duration + testExpiryHandler watchExpired + }{ + "test_added_to_watch_cache_timer_signal_no_handling_error": { + doCancel: false, + expiryDuration: 250 * time.Millisecond, + testExpiryHandler: func(ctx context.Context, id string) error { + testutil.AssertEqual(t, testResourceID, id) + return nil + }, + }, + "test_added_to_watch_cache_timer_signal_handling_error": { + doCancel: false, + expiryDuration: 250 * time.Millisecond, + testExpiryHandler: func(ctx context.Context, id string) error { + testutil.AssertEqual(t, testResourceID, id) + return log.NewError("test error") + }, + }, + "test_added_to_watch_cache_timer_signal_handling_cancelled": { + doCancel: true, + expiryDuration: 24 * time.Hour, + testExpiryHandler: nil, + }, + } + + for testName, testData := range testCases { + t.Run(testName, func(t *testing.T) { + t.Log(testName) + + testResourceWatch := &resWatcher{ + watchCache: make(map[string]watchInfo), + watchCacheLock: sync.RWMutex{}, + watchCacheWaitGroup: &sync.WaitGroup{}, + } + testResourceWatch.watcherCtx, testResourceWatch.watcherCtxCancel = context.WithCancel(context.Background()) + + defer func() { + testResourceWatch.watcherCtxCancel() + testutil.AssertWithTimeout(t, testResourceWatch.watchCacheWaitGroup, testsExecutionTimeout) + }() + + actualErr := testResourceWatch.Watch(testResourceID, testData.expiryDuration, testData.testExpiryHandler) + testutil.AssertNil(t, actualErr) + if testData.doCancel { + testResourceWatch.watcherCtxCancel() + } + + testutil.AssertWithTimeout(t, testResourceWatch.watchCacheWaitGroup, testsExecutionTimeout) + _, ok := testResourceWatch.watchCache[testResourceID] + testutil.AssertFalse(t, ok) + }) + } +} + +func TestResourceWatcherDispose(t *testing.T) { + const testsExecutionTimeout = 5 * time.Second + + testResources := []struct { + resourceID string + expiryDuration time.Duration + }{ + { + resourceID: "expired-res-id", + expiryDuration: 1 * time.Millisecond, + }, + { + resourceID: "active-res-id", + expiryDuration: 24 * time.Hour, + }, + } + + testResourceWatch := &resWatcher{ + watchCache: make(map[string]watchInfo), + watchCacheLock: sync.RWMutex{}, + watchCacheWaitGroup: &sync.WaitGroup{}, + } + testResourceWatch.watcherCtx, testResourceWatch.watcherCtxCancel = context.WithCancel(context.Background()) + + for _, testData := range testResources { + actualErr := testResourceWatch.Watch(testData.resourceID, testData.expiryDuration, nil) + testutil.AssertNil(t, actualErr) + _, isAdded := testResourceWatch.watchCache[testData.resourceID] + testutil.AssertTrue(t, isAdded) + } + go testResourceWatch.Dispose() + testutil.AssertWithTimeout(t, testResourceWatch.watchCacheWaitGroup, testsExecutionTimeout) + + testutil.AssertEqual(t, 0, len(testResourceWatch.watchCache)) +} diff --git a/containerm/ctr/ctrd_spi_images_test.go b/containerm/ctr/ctrd_spi_images_test.go index 53c4c31..a138280 100644 --- a/containerm/ctr/ctrd_spi_images_test.go +++ b/containerm/ctr/ctrd_spi_images_test.go @@ -14,6 +14,7 @@ package ctr import ( "context" + "github.com/containerd/containerd/images" "github.com/containerd/containerd/namespaces" "testing" @@ -81,10 +82,10 @@ func TestPullImage(t *testing.T) { ) testCases := map[string]struct { - mapExec func(context.Context, *ctrdMocks.MockcontainerClientWrapper, *containerdMocks.MockImage) (containerd.Image, error) + mockExec func(context.Context, *ctrdMocks.MockcontainerClientWrapper, *containerdMocks.MockImage) (containerd.Image, error) }{ "test_no_err": { - mapExec: func(ctx context.Context, ctrdWrapper *ctrdMocks.MockcontainerClientWrapper, image *containerdMocks.MockImage) (containerd.Image, error) { + mockExec: func(ctx context.Context, ctrdWrapper *ctrdMocks.MockcontainerClientWrapper, image *containerdMocks.MockImage) (containerd.Image, error) { ctrdWrapper.EXPECT().Pull(ctx, testImageRef, matchers.MatchesResolverOpts( containerd.WithSchema1Conversion, containerd.WithPullSnapshotter(testSnapshotterType), @@ -93,7 +94,7 @@ func TestPullImage(t *testing.T) { }, }, "test_pull_err": { - mapExec: func(ctx context.Context, ctrdWrapper *ctrdMocks.MockcontainerClientWrapper, _ *containerdMocks.MockImage) (containerd.Image, error) { + mockExec: func(ctx context.Context, ctrdWrapper *ctrdMocks.MockcontainerClientWrapper, _ *containerdMocks.MockImage) (containerd.Image, error) { err := log.NewError("test pull image error") ctrdWrapper.EXPECT().Pull(ctx, testImageRef, matchers.MatchesResolverOpts( containerd.WithSchema1Conversion, @@ -122,7 +123,7 @@ func TestPullImage(t *testing.T) { ctx := context.Background() // mock exec - expectedImage, expectedErr := testData.mapExec(namespaces.WithNamespace(ctx, testNamespace), mockCtrdWrapper, mockImage) + expectedImage, expectedErr := testData.mockExec(namespaces.WithNamespace(ctx, testNamespace), mockCtrdWrapper, mockImage) // test actualImage, actualErr := testSpi.PullImage(ctx, testImageRef, @@ -141,17 +142,17 @@ func TestUnpackImage(t *testing.T) { ) testCases := map[string]struct { - mapExec func(context.Context, *containerdMocks.MockImage) error + mockExec func(context.Context, *containerdMocks.MockImage) error }{ "test_no_err": { - mapExec: func(ctx context.Context, imageMock *containerdMocks.MockImage) error { + mockExec: func(ctx context.Context, imageMock *containerdMocks.MockImage) error { imageMock.EXPECT().Unpack(ctx, testSnapshotterType, matchers.MatchesUnpackOpts( containerd.WithSnapshotterPlatformCheck())).Times(1).Return(nil) return nil }, }, "test_err": { - mapExec: func(ctx context.Context, imageMock *containerdMocks.MockImage) error { + mockExec: func(ctx context.Context, imageMock *containerdMocks.MockImage) error { err := log.NewError("test pull image error") imageMock.EXPECT().Unpack(ctx, testSnapshotterType, matchers.MatchesUnpackOpts( containerd.WithSnapshotterPlatformCheck())).Times(1).Return(err) @@ -176,7 +177,7 @@ func TestUnpackImage(t *testing.T) { ctx := context.Background() // mock exec - expectedErr := testData.mapExec(namespaces.WithNamespace(ctx, testNamespace), mockImage) + expectedErr := testData.mockExec(namespaces.WithNamespace(ctx, testNamespace), mockImage) // test actualErr := testSpi.UnpackImage(ctx, mockImage, containerd.WithSnapshotterPlatformCheck()) @@ -184,3 +185,106 @@ func TestUnpackImage(t *testing.T) { }) } } + +func TestListImages(t *testing.T) { + const ( + testNamespace = "test-ns" + ) + + testCases := map[string]struct { + mockExec func(context.Context, *ctrdMocks.MockcontainerClientWrapper, *containerdMocks.MockImage) ([]containerd.Image, error) + }{ + "test_no_err": { + mockExec: func(ctx context.Context, ctrdWrapperMock *ctrdMocks.MockcontainerClientWrapper, imageMock *containerdMocks.MockImage) ([]containerd.Image, error) { + res := []containerd.Image{imageMock} + ctrdWrapperMock.EXPECT().ListImages(ctx).Return(res, nil) + return res, nil + }, + }, + "test_err": { + mockExec: func(ctx context.Context, ctrdWrapperMock *ctrdMocks.MockcontainerClientWrapper, imageMock *containerdMocks.MockImage) ([]containerd.Image, error) { + err := log.NewError("test pull image error") + ctrdWrapperMock.EXPECT().ListImages(ctx).Return(nil, err) + return nil, err + }, + }, + } + + for testName, testData := range testCases { + t.Run(testName, func(t *testing.T) { + // init mock ctrl + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + // init mocks + mockCtrdWrapper := ctrdMocks.NewMockcontainerClientWrapper(mockCtrl) + mockImg := containerdMocks.NewMockImage(mockCtrl) + + // init spi under test + testSpi := &ctrdSpi{ + client: mockCtrdWrapper, + namespace: testNamespace, + } + ctx := context.Background() + + // mock exec + expectedImgs, expectedErr := testData.mockExec(namespaces.WithNamespace(ctx, testNamespace), mockCtrdWrapper, mockImg) + + // test + imgs, actualErr := testSpi.ListImages(ctx) + testutil.AssertError(t, expectedErr, actualErr) + testutil.AssertEqual(t, expectedImgs, imgs) + }) + } +} + +func TestDeleteImage(t *testing.T) { + const ( + testNamespace = "test-ns" + testImgRef = "test.img/ref:latest" + ) + + testCases := map[string]struct { + mockExec func(context.Context, *ctrdMocks.MockcontainerClientWrapper, *containerdMocks.MockImageStore) error + }{ + "test_error": { + mockExec: func(ctx context.Context, ctrdWrapperMock *ctrdMocks.MockcontainerClientWrapper, imgStoreMock *containerdMocks.MockImageStore) error { + ctrdWrapperMock.EXPECT().ImageService().Return(imgStoreMock) + err := log.NewError("test error") + imgStoreMock.EXPECT().Delete(ctx, testImgRef, matchers.MatchesImageDeleteOpts(images.SynchronousDelete())).Return(err) + return err + }, + }, + "test_no_error": { + mockExec: func(ctx context.Context, ctrdWrapperMock *ctrdMocks.MockcontainerClientWrapper, imgStoreMock *containerdMocks.MockImageStore) error { + ctrdWrapperMock.EXPECT().ImageService().Return(imgStoreMock) + imgStoreMock.EXPECT().Delete(ctx, testImgRef, matchers.MatchesImageDeleteOpts(images.SynchronousDelete())).Return(nil) + return nil + }, + }, + } + + for testName, testData := range testCases { + t.Run(testName, func(t *testing.T) { + // init mock ctrl + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + // init mocks + mockCtrdWrapper := ctrdMocks.NewMockcontainerClientWrapper(mockCtrl) + mockImgStore := containerdMocks.NewMockImageStore(mockCtrl) + + // init spi under test + testSpi := &ctrdSpi{ + client: mockCtrdWrapper, + namespace: testNamespace, + } + ctx := context.Background() + + // mock exec + expectedErr := testData.mockExec(namespaces.WithNamespace(ctx, testNamespace), mockCtrdWrapper, mockImgStore) + + // test + actualErr := testSpi.DeleteImage(ctx, testImgRef) + testutil.AssertError(t, expectedErr, actualErr) + }) + } +} diff --git a/containerm/ctr/ctrd_spi_snapshots_test.go b/containerm/ctr/ctrd_spi_snapshots_test.go index 523695f..844dc46 100644 --- a/containerm/ctr/ctrd_spi_snapshots_test.go +++ b/containerm/ctr/ctrd_spi_snapshots_test.go @@ -18,6 +18,7 @@ import ( "fmt" "github.com/containerd/containerd/leases" "github.com/containerd/containerd/namespaces" + "github.com/eclipse-kanto/container-management/containerm/log" "testing" "github.com/containerd/containerd/errdefs" @@ -37,17 +38,17 @@ func TestGetSnapshot(t *testing.T) { testSnapshotID := fmt.Sprintf(snapshotIDTemplate, testCtrID) testCases := map[string]struct { - mapExec func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) (snapshots.Info, error) + mockExec func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) (snapshots.Info, error) }{ "test_no_err": { - mapExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) (snapshots.Info, error) { + mockExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) (snapshots.Info, error) { info := snapshots.Info{Name: "testSnapshotName"} mockSnapshotter.EXPECT().Stat(ctx, testSnapshotID).Return(info, nil) return info, nil }, }, "test_err": { - mapExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) (snapshots.Info, error) { + mockExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) (snapshots.Info, error) { info := snapshots.Info{} err := errors.New("test error") mockSnapshotter.EXPECT().Stat(ctx, testSnapshotID).Return(info, err) @@ -76,7 +77,7 @@ func TestGetSnapshot(t *testing.T) { namespace: testNamespace, } // mock exec - expectedInfo, expectedErr := testData.mapExec(prepareContext(ctx), mockSnapshotter) + expectedInfo, expectedErr := testData.mockExec(prepareContext(ctx), mockSnapshotter) // test actualInfo, actualErr := testSpi.GetSnapshot(ctx, testCtrID) @@ -112,11 +113,11 @@ func TestPrepareSnapshot(t *testing.T) { } testCases := map[string]struct { - ctx context.Context - mapExec func(context.Context, *containerdMocks.MockImage, *containerdMocks.MockSnapshotter) error + ctx context.Context + mockExec func(context.Context, *containerdMocks.MockImage, *containerdMocks.MockSnapshotter) error }{ "test_image_error_rootfs": { - mapExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { testCtx := prepareContext(ctx, testNamespace, testLeaseID) err := errors.New("test image RootFS error") mockImage.EXPECT().RootFS(testCtx).Return(nil, err) @@ -124,7 +125,7 @@ func TestPrepareSnapshot(t *testing.T) { }, }, "test_error_prepare": { - mapExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { err := errors.New("test prepare error") testCtx := prepareContext(ctx, testNamespace, testLeaseID) mockImage.EXPECT().RootFS(testCtx).Return(nil, nil) @@ -133,7 +134,7 @@ func TestPrepareSnapshot(t *testing.T) { }, }, "test_no_error_prepare": { - mapExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { testCtx := prepareContext(ctx, testNamespace, testLeaseID) mockImage.EXPECT().RootFS(testCtx).Return(nil, nil) mockSnapshotter.EXPECT().Prepare(testCtx, testSnapshotID, gomock.Any()).Return(make([]mount.Mount, 0), nil) @@ -141,7 +142,7 @@ func TestPrepareSnapshot(t *testing.T) { }, }, "test_error_is_unpacked": { - mapExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { err := errors.New("test isUnpacked error") testCtx := prepareContext(ctx, testNamespace, testLeaseID) @@ -153,7 +154,7 @@ func TestPrepareSnapshot(t *testing.T) { }, }, "test_error_unpack": { - mapExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { err := errors.New("test unpack error") testCtx := prepareContext(ctx, testNamespace, testLeaseID) testCtxNoLease := prepareContext(prepareContext(ctx, testNamespace, ""), testNamespace, "") @@ -167,7 +168,7 @@ func TestPrepareSnapshot(t *testing.T) { }, }, "test_unpack_success_prepare_fail": { - mapExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { err := errors.New("test prepare after unpack error") testCtx := prepareContext(ctx, testNamespace, testLeaseID) testCtxNoLease := prepareContext(prepareContext(ctx, testNamespace, ""), testNamespace, "") @@ -182,7 +183,7 @@ func TestPrepareSnapshot(t *testing.T) { }, }, "test_unpack_prepare_success": { - mapExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { testCtx := prepareContext(ctx, testNamespace, testLeaseID) testCtxNoLease := prepareContext(prepareContext(ctx, testNamespace, ""), testNamespace, "") @@ -196,7 +197,7 @@ func TestPrepareSnapshot(t *testing.T) { }, }, "test_is_packed": { - mapExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockImage *containerdMocks.MockImage, mockSnapshotter *containerdMocks.MockSnapshotter) error { testCtx := prepareContext(ctx, testNamespace, testLeaseID) mockImage.EXPECT().RootFS(testCtx).Return(nil, nil) @@ -223,7 +224,7 @@ func TestPrepareSnapshot(t *testing.T) { namespace: testNamespace, } ctx := context.Background() - expectedErr := testData.mapExec(ctx, mockImage, mockSnapshotter) + expectedErr := testData.mockExec(ctx, mockImage, mockSnapshotter) actualErr := testSpi.PrepareSnapshot(ctx, testCtrID, mockImage) testutil.AssertError(t, expectedErr, actualErr) @@ -242,17 +243,17 @@ func TestMountSnapshot(t *testing.T) { testSnapshotID := fmt.Sprintf(snapshotIDTemplate, testCtrID) testCases := map[string]struct { - mapExec func(context.Context, *containerdMocks.MockSnapshotter) error + mockExec func(context.Context, *containerdMocks.MockSnapshotter) error }{ "test_error_mounts": { - mapExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { err := errors.New("test mounts error") mockSnapshotter.EXPECT().Mounts(ctx, testSnapshotID).Return(nil, err) return err }, }, "test_mounts_size_error": { - mapExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { err := errors.New("failed to get mounts for snapshot for container test-container-id: not equals 1") mockSnapshotter.EXPECT().Mounts(ctx, testSnapshotID).Return(make([]mount.Mount, 2), nil) return err @@ -279,7 +280,7 @@ func TestMountSnapshot(t *testing.T) { } ctx := context.Background() - expectedErr := testData.mapExec(prepareContext(ctx), mockSnapshotter) + expectedErr := testData.mockExec(prepareContext(ctx), mockSnapshotter) actualErr := testSpi.MountSnapshot(ctx, testCtrID, testRootFs) testutil.AssertError(t, expectedErr, actualErr) @@ -298,23 +299,23 @@ func TestRemoveSnapshot(t *testing.T) { testSnapshotID := fmt.Sprintf(snapshotIDTemplate, testCtrID) testCases := map[string]struct { - mapExec func(context.Context, *containerdMocks.MockSnapshotter) error + mockExec func(context.Context, *containerdMocks.MockSnapshotter) error }{ "test_error_remove": { - mapExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { err := errors.New("test error remove") mockSnapshotter.EXPECT().Remove(ctx, testSnapshotID).Return(err) return err }, }, "test_error_not_found_remove": { - mapExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { mockSnapshotter.EXPECT().Remove(ctx, testSnapshotID).Return(errdefs.ErrNotFound) return nil }, }, "test_remove_success": { - mapExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { + mockExec: func(ctx context.Context, mockSnapshotter *containerdMocks.MockSnapshotter) error { mockSnapshotter.EXPECT().Remove(ctx, testSnapshotID).Return(nil) return nil }, @@ -341,7 +342,7 @@ func TestRemoveSnapshot(t *testing.T) { } ctx := context.Background() - expectedErr := testData.mapExec(prepareContext(ctx), mockSnapshotter) + expectedErr := testData.mockExec(prepareContext(ctx), mockSnapshotter) actualErr := testSpi.RemoveSnapshot(ctx, testCtrID) testutil.AssertError(t, expectedErr, actualErr) @@ -349,3 +350,62 @@ func TestRemoveSnapshot(t *testing.T) { }) } } + +func TestListSnapshots(t *testing.T) { + const ( + testType = "test_type" + testFilter = "name=test-snapshot" + testLeaseID = "test.lease" + testNamespace = "test-ns" + ) + + testCases := map[string]struct { + mockExec func(context.Context, *containerdMocks.MockSnapshotter) ([]snapshots.Info, error) + }{ + "test_walk_error": { + mockExec: func(ctx context.Context, snapshotter *containerdMocks.MockSnapshotter) ([]snapshots.Info, error) { + err := log.NewError("test error") + snapshotter.EXPECT().Walk(ctx, gomock.Any(), testFilter).Return(err) + return nil, err + }, + }, + "test_no_error": { + mockExec: func(ctx context.Context, snapshotter *containerdMocks.MockSnapshotter) ([]snapshots.Info, error) { + testSnapshotInfo := snapshots.Info{ + Name: "test-snapshot", + } + snapshotter.EXPECT().Walk(ctx, gomock.Any(), testFilter).Do( + func(ctx context.Context, fn snapshots.WalkFunc, filters ...string) error { + _ = fn(ctx, testSnapshotInfo) + return nil + }, + ) + return []snapshots.Info{testSnapshotInfo}, nil + }, + }, + } + + for testName, testData := range testCases { + t.Run(testName, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockSnapshotter := containerdMocks.NewMockSnapshotter(mockCtrl) + + testSpi := &ctrdSpi{ + snapshotService: mockSnapshotter, + snapshotterType: testType, + lease: &leases.Lease{ID: testLeaseID}, + namespace: testNamespace, + } + ctx := context.Background() + + expectedSnapshots, expectedErr := testData.mockExec(namespaces.WithNamespace(ctx, testNamespace), mockSnapshotter) + + actualSnapshots, actualErr := testSpi.ListSnapshots(ctx, testFilter) + testutil.AssertError(t, expectedErr, actualErr) + testutil.AssertEqual(t, expectedSnapshots, actualSnapshots) + + }) + } +} diff --git a/containerm/pkg/testutil/matchers/image_delete_opts_matcher.go b/containerm/pkg/testutil/matchers/image_delete_opts_matcher.go new file mode 100644 index 0000000..bb48949 --- /dev/null +++ b/containerm/pkg/testutil/matchers/image_delete_opts_matcher.go @@ -0,0 +1,65 @@ +// Copyright (c) 2022 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + +package matchers + +import ( + "context" + "fmt" + "github.com/containerd/containerd/images" + "github.com/golang/mock/gomock" + "reflect" +) + +type imageDeleteOptsMatcher struct { + opts []images.DeleteOpt + msg string +} + +// MatchesImageDeleteOpts returns a Matcher interface for images.DeleteOpt used in variadic functions +func MatchesImageDeleteOpts(opts ...images.DeleteOpt) gomock.Matcher { + return &imageDeleteOptsMatcher{opts, ""} +} + +func (matcher *imageDeleteOptsMatcher) Matches(x interface{}) bool { + switch x.(type) { + case []images.DeleteOpt: + opts := x.([]images.DeleteOpt) + if len(matcher.opts) != len(opts) { + matcher.msg = fmt.Sprintf("expected %d , got %d", len(matcher.opts), len(opts)) + return false + } + actualCtx := context.TODO() + actualDelOptions := &images.DeleteOptions{} + expectedCtx := context.TODO() + expectedDeleteOptions := &images.DeleteOptions{} + for i := range opts { + _ = opts[i](actualCtx, actualDelOptions) + _ = matcher.opts[i](expectedCtx, expectedDeleteOptions) + } + if !reflect.DeepEqual(expectedCtx, actualCtx) { + matcher.msg = fmt.Sprintf("expected %v , got %v", expectedCtx, actualCtx) + return false + } + if !reflect.DeepEqual(expectedDeleteOptions, actualDelOptions) { + matcher.msg = fmt.Sprintf("expected %v , got %v", expectedDeleteOptions, actualDelOptions) + return false + } + return true + default: + return false + } +} + +func (matcher *imageDeleteOptsMatcher) String() string { + return matcher.msg +} diff --git a/containerm/pkg/testutil/mocks/containerd/mock_image_store.go b/containerm/pkg/testutil/mocks/containerd/mock_image_store.go new file mode 100644 index 0000000..300520d --- /dev/null +++ b/containerm/pkg/testutil/mocks/containerd/mock_image_store.go @@ -0,0 +1,137 @@ +// Copyright (c) 2022 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 + +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/containerd/containerd/images (interfaces: Store) + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + images "github.com/containerd/containerd/images" + gomock "github.com/golang/mock/gomock" +) + +// MockImageStore is a mock of Store interface. +type MockImageStore struct { + ctrl *gomock.Controller + recorder *MockImageStoreMockRecorder +} + +// MockImageStoreMockRecorder is the mock recorder for MockImageStore. +type MockImageStoreMockRecorder struct { + mock *MockImageStore +} + +// NewMockImageStore creates a new mock instance. +func NewMockImageStore(ctrl *gomock.Controller) *MockImageStore { + mock := &MockImageStore{ctrl: ctrl} + mock.recorder = &MockImageStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockImageStore) EXPECT() *MockImageStoreMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockImageStore) Create(arg0 context.Context, arg1 images.Image) (images.Image, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", arg0, arg1) + ret0, _ := ret[0].(images.Image) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Create indicates an expected call of Create. +func (mr *MockImageStoreMockRecorder) Create(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockImageStore)(nil).Create), arg0, arg1) +} + +// Delete mocks base method. +func (m *MockImageStore) Delete(arg0 context.Context, arg1 string, arg2 ...images.DeleteOpt) error { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Delete", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// Delete indicates an expected call of Delete. +func (mr *MockImageStoreMockRecorder) Delete(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockImageStore)(nil).Delete), varargs...) +} + +// Get mocks base method. +func (m *MockImageStore) Get(arg0 context.Context, arg1 string) (images.Image, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", arg0, arg1) + ret0, _ := ret[0].(images.Image) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Get indicates an expected call of Get. +func (mr *MockImageStoreMockRecorder) Get(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockImageStore)(nil).Get), arg0, arg1) +} + +// List mocks base method. +func (m *MockImageStore) List(arg0 context.Context, arg1 ...string) ([]images.Image, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0} + for _, a := range arg1 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "List", varargs...) + ret0, _ := ret[0].([]images.Image) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// List indicates an expected call of List. +func (mr *MockImageStoreMockRecorder) List(arg0 interface{}, arg1 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0}, arg1...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockImageStore)(nil).List), varargs...) +} + +// Update mocks base method. +func (m *MockImageStore) Update(arg0 context.Context, arg1 images.Image, arg2 ...string) (images.Image, error) { + m.ctrl.T.Helper() + varargs := []interface{}{arg0, arg1} + for _, a := range arg2 { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "Update", varargs...) + ret0, _ := ret[0].(images.Image) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Update indicates an expected call of Update. +func (mr *MockImageStoreMockRecorder) Update(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{arg0, arg1}, arg2...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockImageStore)(nil).Update), varargs...) +}