mirror of
https://github.com/moby/moby.git
synced 2026-01-11 10:41:43 +00:00
246 lines
6.4 KiB
Go
246 lines
6.4 KiB
Go
package containerd
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
containerd "github.com/containerd/containerd/v2/client"
|
|
"github.com/containerd/containerd/v2/core/content"
|
|
"github.com/containerd/containerd/v2/core/leases"
|
|
"github.com/containerd/containerd/v2/core/metadata"
|
|
"github.com/containerd/containerd/v2/core/snapshots"
|
|
"github.com/containerd/containerd/v2/defaults"
|
|
cerrdefs "github.com/containerd/errdefs"
|
|
"github.com/moby/moby/v2/daemon/container"
|
|
daemonevents "github.com/moby/moby/v2/daemon/events"
|
|
"github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"gotest.tools/v3/assert"
|
|
)
|
|
|
|
func fakeImageService(t testing.TB, ctx context.Context, cs content.Store) *ImageService {
|
|
snapshotter := &testSnapshotterService{}
|
|
|
|
mdb := newTestDB(ctx, t)
|
|
|
|
snapshotters := map[string]snapshots.Snapshotter{
|
|
defaults.DefaultSnapshotter: snapshotter,
|
|
}
|
|
|
|
service := &ImageService{
|
|
images: metadata.NewImageStore(mdb),
|
|
containers: container.NewMemoryStore(),
|
|
content: cs,
|
|
eventsService: daemonevents.New(),
|
|
snapshotterServices: snapshotters,
|
|
snapshotter: defaults.DefaultSnapshotter,
|
|
}
|
|
|
|
// containerd.Image gets the services directly from containerd.Client
|
|
// so we need to create a "fake" containerd.Client with the test services.
|
|
c8dCli, err := containerd.New("", containerd.WithServices(
|
|
containerd.WithImageStore(service.images),
|
|
containerd.WithContentStore(cs),
|
|
containerd.WithSnapshotters(snapshotters),
|
|
containerd.WithLeasesService(noopLeasesManager{}),
|
|
))
|
|
assert.NilError(t, err)
|
|
|
|
service.client = c8dCli
|
|
return service
|
|
}
|
|
|
|
type noopLeasesManager struct{}
|
|
|
|
func (noopLeasesManager) Create(context.Context, ...leases.Opt) (leases.Lease, error) {
|
|
return leases.Lease{}, nil
|
|
}
|
|
|
|
func (noopLeasesManager) Delete(context.Context, leases.Lease, ...leases.DeleteOpt) error {
|
|
return nil
|
|
}
|
|
|
|
func (noopLeasesManager) List(context.Context, ...string) ([]leases.Lease, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (noopLeasesManager) AddResource(context.Context, leases.Lease, leases.Resource) error {
|
|
return nil
|
|
}
|
|
|
|
func (noopLeasesManager) DeleteResource(context.Context, leases.Lease, leases.Resource) error {
|
|
return nil
|
|
}
|
|
|
|
func (noopLeasesManager) ListResources(context.Context, leases.Lease) ([]leases.Resource, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
type blobsDirContentStore struct {
|
|
blobs string
|
|
}
|
|
|
|
type fileReaderAt struct {
|
|
*os.File
|
|
}
|
|
|
|
func (f *fileReaderAt) Size() int64 {
|
|
fi, err := f.Stat()
|
|
if err != nil {
|
|
return -1
|
|
}
|
|
return fi.Size()
|
|
}
|
|
|
|
func (s *blobsDirContentStore) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
|
|
p := filepath.Join(s.blobs, desc.Digest.Encoded())
|
|
r, err := os.Open(p)
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return nil, fmt.Errorf("%w: %s", cerrdefs.ErrNotFound, desc.Digest)
|
|
}
|
|
return nil, err
|
|
}
|
|
return &fileReaderAt{r}, nil
|
|
}
|
|
|
|
func (s *blobsDirContentStore) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
|
|
return nil, errors.New("read-only")
|
|
}
|
|
|
|
func (s *blobsDirContentStore) Status(ctx context.Context, _ string) (content.Status, error) {
|
|
return content.Status{}, errors.New("not implemented")
|
|
}
|
|
|
|
func (s *blobsDirContentStore) Delete(ctx context.Context, dgst digest.Digest) error {
|
|
p := filepath.Join(s.blobs, dgst.Encoded())
|
|
return os.Remove(p)
|
|
}
|
|
|
|
func (s *blobsDirContentStore) ListStatuses(ctx context.Context, filters ...string) ([]content.Status, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (s *blobsDirContentStore) Abort(ctx context.Context, ref string) error {
|
|
return errors.New("not implemented")
|
|
}
|
|
|
|
func (s *blobsDirContentStore) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
|
|
entries, err := os.ReadDir(s.blobs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, e := range entries {
|
|
if e.IsDir() {
|
|
continue
|
|
}
|
|
|
|
d := digest.FromString(e.Name())
|
|
if d == "" {
|
|
continue
|
|
}
|
|
|
|
stat, err := e.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := fn(content.Info{Digest: d, Size: stat.Size()}); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *blobsDirContentStore) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
|
|
f, err := os.Open(filepath.Join(s.blobs, dgst.Encoded()))
|
|
if err != nil {
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
return content.Info{}, fmt.Errorf("%w: %s", cerrdefs.ErrNotFound, dgst)
|
|
}
|
|
return content.Info{}, err
|
|
}
|
|
defer f.Close()
|
|
|
|
stat, err := f.Stat()
|
|
if err != nil {
|
|
return content.Info{}, err
|
|
}
|
|
|
|
return content.Info{
|
|
Digest: dgst,
|
|
Size: stat.Size(),
|
|
}, nil
|
|
}
|
|
|
|
func (s *blobsDirContentStore) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
|
|
return content.Info{}, errors.New("read-only")
|
|
}
|
|
|
|
// delayedStore is a content store wrapper that adds a constant delay to all
|
|
// operations in order to imitate gRPC overhead.
|
|
//
|
|
// The delay is constant to make the benchmark results more reproducible
|
|
// Since content store may be accessed concurrently random delay would be
|
|
// order-dependent.
|
|
type delayedStore struct {
|
|
store content.Store
|
|
overhead time.Duration
|
|
}
|
|
|
|
func (s *delayedStore) delay() {
|
|
time.Sleep(s.overhead)
|
|
}
|
|
|
|
func (s *delayedStore) ReaderAt(ctx context.Context, desc ocispec.Descriptor) (content.ReaderAt, error) {
|
|
s.delay()
|
|
return s.store.ReaderAt(ctx, desc)
|
|
}
|
|
|
|
func (s *delayedStore) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) {
|
|
s.delay()
|
|
return s.store.Writer(ctx, opts...)
|
|
}
|
|
|
|
func (s *delayedStore) Status(ctx context.Context, st string) (content.Status, error) {
|
|
s.delay()
|
|
return s.store.Status(ctx, st)
|
|
}
|
|
|
|
func (s *delayedStore) Delete(ctx context.Context, dgst digest.Digest) error {
|
|
s.delay()
|
|
return s.store.Delete(ctx, dgst)
|
|
}
|
|
|
|
func (s *delayedStore) ListStatuses(ctx context.Context, filters ...string) ([]content.Status, error) {
|
|
s.delay()
|
|
return s.store.ListStatuses(ctx, filters...)
|
|
}
|
|
|
|
func (s *delayedStore) Abort(ctx context.Context, ref string) error {
|
|
s.delay()
|
|
return s.store.Abort(ctx, ref)
|
|
}
|
|
|
|
func (s *delayedStore) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error {
|
|
s.delay()
|
|
return s.store.Walk(ctx, fn, filters...)
|
|
}
|
|
|
|
func (s *delayedStore) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) {
|
|
s.delay()
|
|
return s.store.Info(ctx, dgst)
|
|
}
|
|
|
|
func (s *delayedStore) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) {
|
|
s.delay()
|
|
return s.store.Update(ctx, info, fieldpaths...)
|
|
}
|