Export remote snapshotter label handler

Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
This commit is contained in:
Kohei Tokunaga 2023-01-31 22:26:27 +09:00
parent 753bfd6575
commit dbf384a5a8
4 changed files with 173 additions and 120 deletions

View File

@ -42,10 +42,10 @@ import (
"github.com/containerd/containerd" "github.com/containerd/containerd"
"github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/errdefs"
containerdimages "github.com/containerd/containerd/images" containerdimages "github.com/containerd/containerd/images"
"github.com/containerd/containerd/labels"
"github.com/containerd/containerd/log" "github.com/containerd/containerd/log"
"github.com/containerd/containerd/pkg/cri/annotations" "github.com/containerd/containerd/pkg/cri/annotations"
criconfig "github.com/containerd/containerd/pkg/cri/config" criconfig "github.com/containerd/containerd/pkg/cri/config"
snpkg "github.com/containerd/containerd/pkg/snapshotters"
distribution "github.com/containerd/containerd/reference/docker" distribution "github.com/containerd/containerd/reference/docker"
"github.com/containerd/containerd/remotes/docker" "github.com/containerd/containerd/remotes/docker"
"github.com/containerd/containerd/remotes/docker/config" "github.com/containerd/containerd/remotes/docker/config"
@ -170,7 +170,7 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
pullOpts = append(pullOpts, c.encryptedImagesPullOpts()...) pullOpts = append(pullOpts, c.encryptedImagesPullOpts()...)
if !c.config.ContainerdConfig.DisableSnapshotAnnotations { if !c.config.ContainerdConfig.DisableSnapshotAnnotations {
pullOpts = append(pullOpts, pullOpts = append(pullOpts,
containerd.WithImageHandlerWrapper(appendInfoHandlerWrapper(ref))) containerd.WithImageHandlerWrapper(snpkg.AppendInfoHandlerWrapper(ref)))
} }
if c.config.ContainerdConfig.DiscardUnpackedLayers { if c.config.ContainerdConfig.DiscardUnpackedLayers {
@ -552,76 +552,6 @@ func (c *criService) encryptedImagesPullOpts() []containerd.RemoteOpt {
return nil return nil
} }
const (
// targetRefLabel is a label which contains image reference and will be passed
// to snapshotters.
targetRefLabel = "containerd.io/snapshot/cri.image-ref"
// targetManifestDigestLabel is a label which contains manifest digest and will be passed
// to snapshotters.
targetManifestDigestLabel = "containerd.io/snapshot/cri.manifest-digest"
// targetLayerDigestLabel is a label which contains layer digest and will be passed
// to snapshotters.
targetLayerDigestLabel = "containerd.io/snapshot/cri.layer-digest"
// targetImageLayersLabel is a label which contains layer digests contained in
// the target image and will be passed to snapshotters for preparing layers in
// parallel. Skipping some layers is allowed and only affects performance.
targetImageLayersLabel = "containerd.io/snapshot/cri.image-layers"
)
// appendInfoHandlerWrapper makes a handler which appends some basic information
// of images like digests for manifest and their child layers as annotations during unpack.
// These annotations will be passed to snapshotters as labels. These labels will be
// used mainly by stargz-based snapshotters for querying image contents from the
// registry.
func appendInfoHandlerWrapper(ref string) func(f containerdimages.Handler) containerdimages.Handler {
return func(f containerdimages.Handler) containerdimages.Handler {
return containerdimages.HandlerFunc(func(ctx context.Context, desc imagespec.Descriptor) ([]imagespec.Descriptor, error) {
children, err := f.Handle(ctx, desc)
if err != nil {
return nil, err
}
switch desc.MediaType {
case imagespec.MediaTypeImageManifest, containerdimages.MediaTypeDockerSchema2Manifest:
for i := range children {
c := &children[i]
if containerdimages.IsLayerType(c.MediaType) {
if c.Annotations == nil {
c.Annotations = make(map[string]string)
}
c.Annotations[targetRefLabel] = ref
c.Annotations[targetLayerDigestLabel] = c.Digest.String()
c.Annotations[targetImageLayersLabel] = getLayers(ctx, targetImageLayersLabel, children[i:], labels.Validate)
c.Annotations[targetManifestDigestLabel] = desc.Digest.String()
}
}
}
return children, nil
})
}
}
// getLayers returns comma-separated digests based on the passed list of
// descriptors. The returned list contains as many digests as possible as well
// as meets the label validation.
func getLayers(ctx context.Context, key string, descs []imagespec.Descriptor, validate func(k, v string) error) (layers string) {
var item string
for _, l := range descs {
if containerdimages.IsLayerType(l.MediaType) {
item = l.Digest.String()
if layers != "" {
item = "," + item
}
// This avoids the label hits the size limitation.
if err := validate(key, layers+item); err != nil {
log.G(ctx).WithError(err).WithField("label", key).Debugf("%q is omitted in the layers list", l.Digest.String())
break
}
layers += item
}
}
return
}
const ( const (
// minPullProgressReportInternal is used to prevent the reporter from // minPullProgressReportInternal is used to prevent the reporter from
// eating more CPU resources // eating more CPU resources

View File

@ -20,11 +20,8 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"strings"
"testing" "testing"
digest "github.com/opencontainers/go-digest"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
runtime "k8s.io/cri-api/pkg/apis/runtime/v1" runtime "k8s.io/cri-api/pkg/apis/runtime/v1"
@ -338,51 +335,6 @@ func TestEncryptedImagePullOpts(t *testing.T) {
} }
} }
func TestImageLayersLabel(t *testing.T) {
sampleKey := "sampleKey"
sampleDigest, err := digest.Parse("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
assert.NoError(t, err)
sampleMaxSize := 300
sampleValidate := func(k, v string) error {
if (len(k) + len(v)) > sampleMaxSize {
return fmt.Errorf("invalid: %q: %q", k, v)
}
return nil
}
tests := []struct {
name string
layersNum int
wantNum int
}{
{
name: "valid number of layers",
layersNum: 2,
wantNum: 2,
},
{
name: "many layers",
layersNum: 5, // hits sampleMaxSize (300 chars).
wantNum: 4, // layers should be omitted for avoiding invalid label.
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var sampleLayers []imagespec.Descriptor
for i := 0; i < tt.layersNum; i++ {
sampleLayers = append(sampleLayers, imagespec.Descriptor{
MediaType: imagespec.MediaTypeImageLayerGzip,
Digest: sampleDigest,
})
}
gotS := getLayers(context.Background(), sampleKey, sampleLayers, sampleValidate)
got := len(strings.Split(gotS, ","))
assert.Equal(t, tt.wantNum, got)
})
}
}
func TestSnapshotterFromPodSandboxConfig(t *testing.T) { func TestSnapshotterFromPodSandboxConfig(t *testing.T) {
defaultSnashotter := "native" defaultSnashotter := "native"
runtimeSnapshotter := "devmapper" runtimeSnapshotter := "devmapper"

View File

@ -0,0 +1,97 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package snapshotters
import (
"context"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/labels"
"github.com/containerd/containerd/log"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// NOTE: The following labels contain "cri" prefix but they are not specific to CRI and
// can be used by non-CRI clients as well for enabling remote snapshotters. We need to
// retain that string for keeping compatibility with snapshotter implementations.
const (
// TargetRefLabel is a label which contains image reference and will be passed
// to snapshotters.
TargetRefLabel = "containerd.io/snapshot/cri.image-ref"
// TargetManifestDigestLabel is a label which contains manifest digest and will be passed
// to snapshotters.
TargetManifestDigestLabel = "containerd.io/snapshot/cri.manifest-digest"
// TargetLayerDigestLabel is a label which contains layer digest and will be passed
// to snapshotters.
TargetLayerDigestLabel = "containerd.io/snapshot/cri.layer-digest"
// TargetImageLayersLabel is a label which contains layer digests contained in
// the target image and will be passed to snapshotters for preparing layers in
// parallel. Skipping some layers is allowed and only affects performance.
TargetImageLayersLabel = "containerd.io/snapshot/cri.image-layers"
)
// AppendInfoHandlerWrapper makes a handler which appends some basic information
// of images like digests for manifest and their child layers as annotations during unpack.
// These annotations will be passed to snapshotters as labels. These labels will be
// used mainly by remote snapshotters for querying image contents from the remote location.
func AppendInfoHandlerWrapper(ref string) func(f images.Handler) images.Handler {
return func(f images.Handler) images.Handler {
return images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
children, err := f.Handle(ctx, desc)
if err != nil {
return nil, err
}
switch desc.MediaType {
case ocispec.MediaTypeImageManifest, images.MediaTypeDockerSchema2Manifest:
for i := range children {
c := &children[i]
if images.IsLayerType(c.MediaType) {
if c.Annotations == nil {
c.Annotations = make(map[string]string)
}
c.Annotations[TargetRefLabel] = ref
c.Annotations[TargetLayerDigestLabel] = c.Digest.String()
c.Annotations[TargetImageLayersLabel] = getLayers(ctx, TargetImageLayersLabel, children[i:], labels.Validate)
c.Annotations[TargetManifestDigestLabel] = desc.Digest.String()
}
}
}
return children, nil
})
}
}
// getLayers returns comma-separated digests based on the passed list of
// descriptors. The returned list contains as many digests as possible as well
// as meets the label validation.
func getLayers(ctx context.Context, key string, descs []ocispec.Descriptor, validate func(k, v string) error) (layers string) {
for _, l := range descs {
if images.IsLayerType(l.MediaType) {
item := l.Digest.String()
if layers != "" {
item = "," + item
}
// This avoids the label hits the size limitation.
if err := validate(key, layers+item); err != nil {
log.G(ctx).WithError(err).WithField("label", key).WithField("digest", l.Digest.String()).Debug("omitting digest in the layers list")
break
}
layers += item
}
}
return
}

View File

@ -0,0 +1,74 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package snapshotters
import (
"context"
"fmt"
"strings"
"testing"
digest "github.com/opencontainers/go-digest"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/assert"
)
func TestImageLayersLabel(t *testing.T) {
sampleKey := "sampleKey"
sampleDigest, err := digest.Parse("sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
assert.NoError(t, err)
sampleMaxSize := 300
sampleValidate := func(k, v string) error {
if (len(k) + len(v)) > sampleMaxSize {
return fmt.Errorf("invalid: %q: %q", k, v)
}
return nil
}
tests := []struct {
name string
layersNum int
wantNum int
}{
{
name: "valid number of layers",
layersNum: 2,
wantNum: 2,
},
{
name: "many layers",
layersNum: 5, // hits sampleMaxSize (300 chars).
wantNum: 4, // layers should be omitted for avoiding invalid label.
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
sampleLayers := make([]imagespec.Descriptor, 0, tt.layersNum)
for i := 0; i < tt.layersNum; i++ {
sampleLayers = append(sampleLayers, imagespec.Descriptor{
MediaType: imagespec.MediaTypeImageLayerGzip,
Digest: sampleDigest,
})
}
gotS := getLayers(context.Background(), sampleKey, sampleLayers, sampleValidate)
got := len(strings.Split(gotS, ","))
assert.Equal(t, tt.wantNum, got)
})
}
}