Skip to content

Commit

Permalink
Add image removal lock to image server
Browse files Browse the repository at this point in the history
We now avoid removing images when a sandbox or container creation is in
progress. This should close the gap in the outlined corner case in:

kubernetes/kubernetes#123631

Refers to kubernetes/kubernetes#123711 as well.

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
  • Loading branch information
saschagrunert committed Mar 6, 2024
1 parent 0f7786a commit 3bd60e4
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 13 deletions.
17 changes: 17 additions & 0 deletions internal/storage/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ type imageService struct {
ctx context.Context
config *config.Config
regexForPinnedImages []*regexp.Regexp
imageRemovalLock sync.Map
}

// ImageBeingPulled map[string]bool to keep track of the images haven't done pulling.
Expand Down Expand Up @@ -151,6 +152,14 @@ type ImageServer interface {
// CandidatesForPotentiallyShortImageName resolves an image name into a set of fully-qualified image names (domain/repo/image:tag|@digest).
// It will only return an empty slice if err != nil.
CandidatesForPotentiallyShortImageName(systemContext *types.SystemContext, imageName string) ([]RegistryImageReference, error)
// ImageRemovalLock returns the removal lock for the specified image storage reference.
ImageRemovalLock(id StorageImageID) *sync.RWMutex
}

// ImageRemovalLock returns the removal lock for the specified image storage reference.
func (svc *imageService) ImageRemovalLock(id StorageImageID) *sync.RWMutex {
value, _ := svc.imageRemovalLock.LoadOrStore(id, &sync.RWMutex{})
return value.(*sync.RWMutex) // nolint:errcheck,forcetypeassert // type matches line above
}

func parseImageNames(image *storage.Image) (someName *RegistryImageReference, tags []reference.NamedTagged, digests []reference.Canonical, err error) {
Expand Down Expand Up @@ -726,6 +735,14 @@ func (svc *imageService) DeleteImage(systemContext *types.SystemContext, id Stor
return err
}

// Avoid removing images which are currently used for container creation
imageRemovalLock := svc.ImageRemovalLock(id)
imageRemovalLock.Lock()
defer imageRemovalLock.Unlock()

// Cleanup memory when the image got removed
defer svc.imageRemovalLock.Delete(id)

return ref.DeleteImage(svc.ctx, systemContext)
}

Expand Down
11 changes: 9 additions & 2 deletions internal/storage/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ type RuntimeServer interface {
// with the pod's infrastructure container having the same value for
// both its pod's ID and its container ID.
// Pointer arguments can be nil. All other arguments are required.
CreatePodSandbox(systemContext *types.SystemContext, podName, podID string, pauseImage RegistryImageReference, imageAuthFile, containerName, metadataName, uid, namespace string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged bool) (ContainerInfo, error)
CreatePodSandbox(systemContext *types.SystemContext, podName, podID string, pauseImage RegistryImageReference, imageAuthFile, containerName, metadataName, uid, namespace string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged bool, imageServer ImageServer) (ContainerInfo, error)

// GetContainerMetadata returns the metadata we've stored for a container.
GetContainerMetadata(idOrName string) (RuntimeContainerMetadata, error)
Expand Down Expand Up @@ -298,7 +298,7 @@ func (r *runtimeService) createContainerOrPodSandbox(systemContext *types.System
}, nil
}

func (r *runtimeService) CreatePodSandbox(systemContext *types.SystemContext, podName, podID string, pauseImage RegistryImageReference, imageAuthFile, containerName, metadataName, uid, namespace string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged bool) (ContainerInfo, error) {
func (r *runtimeService) CreatePodSandbox(systemContext *types.SystemContext, podName, podID string, pauseImage RegistryImageReference, imageAuthFile, containerName, metadataName, uid, namespace string, attempt uint32, idMappingsOptions *storage.IDMappingOptions, labelOptions []string, privileged bool, imageServer ImageServer) (ContainerInfo, error) {
// Check if we have the specified image.
var ref types.ImageReference
ref, err := istorage.Transport.NewStoreReference(r.storageImageServer.GetStore(), pauseImage.Raw(), "")
Expand Down Expand Up @@ -338,6 +338,13 @@ func (r *runtimeService) CreatePodSandbox(systemContext *types.SystemContext, po
// Resolve the image ID.
imageID := storageImageIDFromImage(img)

if imageServer != nil {
// Avoid image removal during sandbox creation
imageRemovalLock := imageServer.ImageRemovalLock(imageID)
imageRemovalLock.RLock()
defer imageRemovalLock.RUnlock()
}

return r.createContainerOrPodSandbox(systemContext, podID, &runtimeContainerMetadataTemplate{
podName: podName,
podID: podID,
Expand Down
10 changes: 5 additions & 5 deletions internal/storage/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,7 @@ var _ = t.Describe("Runtime", func() {
info, err = sut.CreatePodSandbox(&types.SystemContext{},
"podName", "podID", pauseImage, "",
"containerName", "metadataName",
"uid", "namespace", 0, nil, []string{"mountLabel"}, false,
"uid", "namespace", 0, nil, []string{"mountLabel"}, false, nil,
)
})

Expand Down Expand Up @@ -675,7 +675,7 @@ var _ = t.Describe("Runtime", func() {
_, err = sut.CreatePodSandbox(&types.SystemContext{},
"podName", "podID", pauseImage, "",
"containerName", "metadataName",
"uid", "namespace", 0, nil, []string{"mountLabel"}, false,
"uid", "namespace", 0, nil, []string{"mountLabel"}, false, nil,
)

// Then
Expand All @@ -697,7 +697,7 @@ var _ = t.Describe("Runtime", func() {
_, err = sut.CreatePodSandbox(&types.SystemContext{},
"podName", "podID", pauseImage, "",
"containerName", "metadataName",
"uid", "namespace", 0, nil, []string{"mountLabel"}, false,
"uid", "namespace", 0, nil, []string{"mountLabel"}, false, nil,
)

// Then
Expand Down Expand Up @@ -801,7 +801,7 @@ var _ = t.Describe("Runtime", func() {
info, err = sut.CreatePodSandbox(&types.SystemContext{},
"podName", "podID", pauseImage, "",
"containerName", "metadataName",
"uid", "namespace", 0, nil, []string{"mountLabel"}, false,
"uid", "namespace", 0, nil, []string{"mountLabel"}, false, nil,
)
})

Expand All @@ -820,7 +820,7 @@ var _ = t.Describe("Runtime", func() {
info, err = sut.CreatePodSandbox(&types.SystemContext{},
"podName", "podID", pauseImage, "/var/non-default/credentials.json",
"containerName", "metadataName",
"uid", "namespace", 0, nil, []string{"mountLabel"}, false,
"uid", "namespace", 0, nil, []string{"mountLabel"}, false, nil,
)
})

Expand Down
6 changes: 6 additions & 0 deletions server/container_create_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,12 @@ func (s *Server) createSandboxContainer(ctx context.Context, ctr ctrfactory.Cont

imageName := imgResult.SomeNameOfThisImage
imageID := imgResult.ID

// Avoid image removal during container creation
imageRemovalLock := s.StorageImageServer().ImageRemovalLock(imageID)
imageRemovalLock.RLock()
defer imageRemovalLock.RUnlock()

someRepoDigest := ""
if len(imgResult.RepoDigests) > 0 {
someRepoDigest = imgResult.RepoDigests[0]
Expand Down
1 change: 1 addition & 0 deletions server/sandbox_run_freebsd.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ func (s *Server) runPodSandbox(ctx context.Context, req *types.RunPodSandboxRequ
nil,
labelOptions,
privileged,
s.ContainerServer.StorageImageServer(),
)
if errors.Is(err, storage.ErrDuplicateName) {
return nil, fmt.Errorf("pod sandbox with name %q already exists", sbox.Name())
Expand Down
1 change: 1 addition & 0 deletions server/sandbox_run_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ func (s *Server) runPodSandbox(ctx context.Context, req *types.RunPodSandboxRequ
idMappingsOptions,
labelOptions,
privileged,
s.ContainerServer.StorageImageServer(),
)
if errors.Is(err, storage.ErrDuplicateName) {
return nil, fmt.Errorf("pod sandbox with name %q already exists", sbox.Name())
Expand Down
6 changes: 4 additions & 2 deletions server/sandbox_run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ var _ = t.Describe("RunPodSandbox", func() {
runtimeServerMock.EXPECT().CreatePodSandbox(gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any()).
Return(storage.ContainerInfo{
RunDir: "/tmp",
Config: &v1.Image{Config: v1.ImageConfig{}},
Expand Down Expand Up @@ -111,7 +112,8 @@ var _ = t.Describe("RunPodSandbox", func() {
runtimeServerMock.EXPECT().CreatePodSandbox(gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
gomock.Any()).
Return(storage.ContainerInfo{}, nil),
runtimeServerMock.EXPECT().DeleteContainer(gomock.Any(), gomock.Any()).
Return(nil),
Expand Down
23 changes: 19 additions & 4 deletions test/mocks/criostorage/criostorage.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 3bd60e4

Please sign in to comment.