From 98b8400f7fa347ee2b1dad79c7d3d15b682d3c2f Mon Sep 17 00:00:00 2001 From: Konstantina Gramatova Date: Tue, 20 Sep 2022 15:32:19 +0300 Subject: [PATCH] Unit test the resources clean up implementation (#54) [#53] Unit test the resources clean up implementation Covered the containerd client internal logic. Covered the resources monitoring mechanism. Optimized watched resources disposal. Improved input parameters readability for the containerd internal client initialization. Aligned the containerd SPI API. Signed-off-by: Konstantina Gramatova --- containerm/ctr/ctrd_client_init.go | 2 +- containerm/ctr/ctrd_client_internal_test.go | 168 ++++++++++++- containerm/ctr/ctrd_resource_watcher.go | 34 +-- containerm/ctr/ctrd_resource_watcher_test.go | 226 ++++++++++++++++++ containerm/ctr/ctrd_spi.go | 4 +- containerm/ctr/ctrd_spi_images.go | 4 +- containerm/ctr/ctrd_spi_images_test.go | 121 +++++++++- containerm/ctr/ctrd_spi_snapshots_test.go | 108 +++++++-- .../matchers/image_delete_opts_matcher.go | 65 +++++ .../mocks/containerd/mock_image_store.go | 137 +++++++++++ .../pkg/testutil/mocks/ctrd/mock_ctrd_spi.go | 13 +- 11 files changed, 822 insertions(+), 60 deletions(-) create mode 100644 containerm/ctr/ctrd_resource_watcher_test.go create mode 100644 containerm/pkg/testutil/matchers/image_delete_opts_matcher.go create mode 100644 containerm/pkg/testutil/mocks/containerd/mock_image_store.go 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..8eb87d7 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_no_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..dc75554 --- /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, expectedCtxCancel := 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(expectedCtxCancel).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.go b/containerm/ctr/ctrd_spi.go index d4dcd0a..28d0608 100644 --- a/containerm/ctr/ctrd_spi.go +++ b/containerm/ctr/ctrd_spi.go @@ -61,8 +61,8 @@ type containerdSpi interface { UnpackImage(ctx context.Context, image containerd.Image, opts ...containerd.UnpackOpt) error // DeleteImage removes the contents of the provided image from the disk DeleteImage(ctx context.Context, imageRef string) error - // ListImages returns all locally existing images - ListImages(ctx context.Context) ([]containerd.Image, error) + // ListImages returns all locally existing images matching the provided filters or all if no filters are provided + ListImages(ctx context.Context, filters ...string) ([]containerd.Image, error) // Wrapper section for managing the file system of the container and its snapshots // GetSnapshotID generates a new ID for the snapshot to be used for this container diff --git a/containerm/ctr/ctrd_spi_images.go b/containerm/ctr/ctrd_spi_images.go index aafe64d..54e091d 100644 --- a/containerm/ctr/ctrd_spi_images.go +++ b/containerm/ctr/ctrd_spi_images.go @@ -38,9 +38,9 @@ func (spi *ctrdSpi) UnpackImage(ctx context.Context, image containerd.Image, opt } // ListImages returns all locally existing images -func (spi *ctrdSpi) ListImages(ctx context.Context) ([]containerd.Image, error) { +func (spi *ctrdSpi) ListImages(ctx context.Context, filters ...string) ([]containerd.Image, error) { ctx = spi.setContext(ctx, false) - return spi.client.ListImages(ctx) + return spi.client.ListImages(ctx, filters...) } // DeleteImage removes the contents of the provided image from the disk diff --git a/containerm/ctr/ctrd_spi_images_test.go b/containerm/ctr/ctrd_spi_images_test.go index 53c4c31..fb9e461 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,107 @@ func TestUnpackImage(t *testing.T) { }) } } + +func TestListImages(t *testing.T) { + const ( + testNamespace = "test-ns" + testFilter = "name=test.img/ref:latest" + ) + + 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, testFilter).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, testFilter).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, testFilter) + 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...) +} diff --git a/containerm/pkg/testutil/mocks/ctrd/mock_ctrd_spi.go b/containerm/pkg/testutil/mocks/ctrd/mock_ctrd_spi.go index 59aeb24..7c2d986 100644 --- a/containerm/pkg/testutil/mocks/ctrd/mock_ctrd_spi.go +++ b/containerm/pkg/testutil/mocks/ctrd/mock_ctrd_spi.go @@ -354,18 +354,23 @@ func (mr *MockcontainerdSpiMockRecorder) GetSnapshotID(containerID interface{}) } // ListImages mocks base method. -func (m *MockcontainerdSpi) ListImages(ctx context.Context) ([]containerd.Image, error) { +func (m *MockcontainerdSpi) ListImages(ctx context.Context, filters ...string) ([]containerd.Image, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ListImages", ctx) + varargs := []interface{}{ctx} + for _, a := range filters { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "ListImages", varargs...) ret0, _ := ret[0].([]containerd.Image) ret1, _ := ret[1].(error) return ret0, ret1 } // ListImages indicates an expected call of ListImages. -func (mr *MockcontainerdSpiMockRecorder) ListImages(ctx interface{}) *gomock.Call { +func (mr *MockcontainerdSpiMockRecorder) ListImages(ctx interface{}, filters ...interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListImages", reflect.TypeOf((*MockcontainerdSpi)(nil).ListImages), ctx) + varargs := append([]interface{}{ctx}, filters...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListImages", reflect.TypeOf((*MockcontainerdSpi)(nil).ListImages), varargs...) } // ListSnapshots mocks base method.