moby/daemon/containerd/service.go
Paweł Gronowski 1b108bdfeb
daemon/c8d: Cache SnapshotService
Avoid fetching `SnapshotService` from client every time. Fetch it once
and then store when creating the image service.

This also allows to pass custom snapshotter implementation for unit
testing.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
2024-03-07 16:25:29 +01:00

203 lines
6.9 KiB
Go

package containerd
import (
"context"
"fmt"
"sync/atomic"
"github.com/containerd/containerd"
"github.com/containerd/containerd/content"
cerrdefs "github.com/containerd/containerd/errdefs"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/plugin"
"github.com/containerd/containerd/remotes/docker"
"github.com/containerd/containerd/snapshots"
"github.com/containerd/log"
"github.com/distribution/reference"
"github.com/docker/docker/container"
daemonevents "github.com/docker/docker/daemon/events"
dimages "github.com/docker/docker/daemon/images"
"github.com/docker/docker/daemon/snapshotter"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/layer"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
)
// ImageService implements daemon.ImageService
type ImageService struct {
client *containerd.Client
images images.Store
content content.Store
containers container.Store
snapshotterServices map[string]snapshots.Snapshotter
snapshotter string
registryHosts docker.RegistryHosts
registryService registryResolver
eventsService *daemonevents.Events
pruneRunning atomic.Bool
refCountMounter snapshotter.Mounter
idMapping idtools.IdentityMapping
}
type registryResolver interface {
IsInsecureRegistry(host string) bool
ResolveRepository(name reference.Named) (*registry.RepositoryInfo, error)
LookupPullEndpoints(hostname string) ([]registry.APIEndpoint, error)
LookupPushEndpoints(hostname string) ([]registry.APIEndpoint, error)
}
type ImageServiceConfig struct {
Client *containerd.Client
Containers container.Store
Snapshotter string
RegistryHosts docker.RegistryHosts
Registry registryResolver
EventsService *daemonevents.Events
RefCountMounter snapshotter.Mounter
IDMapping idtools.IdentityMapping
}
// NewService creates a new ImageService.
func NewService(config ImageServiceConfig) *ImageService {
return &ImageService{
client: config.Client,
images: config.Client.ImageService(),
content: config.Client.ContentStore(),
snapshotterServices: map[string]snapshots.Snapshotter{
config.Snapshotter: config.Client.SnapshotService(config.Snapshotter),
},
containers: config.Containers,
snapshotter: config.Snapshotter,
registryHosts: config.RegistryHosts,
registryService: config.Registry,
eventsService: config.EventsService,
refCountMounter: config.RefCountMounter,
idMapping: config.IDMapping,
}
}
func (i *ImageService) snapshotterService(snapshotter string) snapshots.Snapshotter {
s, ok := i.snapshotterServices[snapshotter]
if !ok {
s = i.client.SnapshotService(snapshotter)
i.snapshotterServices[snapshotter] = s
}
return s
}
// DistributionServices return services controlling daemon image storage.
func (i *ImageService) DistributionServices() dimages.DistributionServices {
return dimages.DistributionServices{}
}
// CountImages returns the number of images stored by ImageService
// called from info.go
func (i *ImageService) CountImages(ctx context.Context) int {
imgs, err := i.client.ListImages(ctx)
if err != nil {
return 0
}
return len(imgs)
}
// CreateLayer creates a filesystem layer for a container.
// called from create.go
// TODO: accept an opt struct instead of container?
func (i *ImageService) CreateLayer(container *container.Container, initFunc layer.MountInit) (layer.RWLayer, error) {
return nil, errdefs.NotImplemented(errdefs.NotImplemented(errors.New("not implemented")))
}
// LayerStoreStatus returns the status for each layer store
// called from info.go
func (i *ImageService) LayerStoreStatus() [][2]string {
// TODO(thaJeztah) do we want to add more details about the driver here?
return [][2]string{
{"driver-type", string(plugin.SnapshotPlugin)},
}
}
// GetLayerMountID returns the mount ID for a layer
// called from daemon.go Daemon.Shutdown(), and Daemon.Cleanup() (cleanup is actually continerCleanup)
// TODO: needs to be refactored to Unmount (see callers), or removed and replaced with GetLayerByID
func (i *ImageService) GetLayerMountID(cid string) (string, error) {
return "", errdefs.NotImplemented(errors.New("not implemented"))
}
// Cleanup resources before the process is shutdown.
// called from daemon.go Daemon.Shutdown()
func (i *ImageService) Cleanup() error {
return nil
}
// StorageDriver returns the name of the default storage-driver (snapshotter)
// used by the ImageService.
func (i *ImageService) StorageDriver() string {
return i.snapshotter
}
// ReleaseLayer releases a layer allowing it to be removed
// called from delete.go Daemon.cleanupContainer(), and Daemon.containerExport()
func (i *ImageService) ReleaseLayer(rwlayer layer.RWLayer) error {
return errdefs.NotImplemented(errors.New("not implemented"))
}
// LayerDiskUsage returns the number of bytes used by layer stores
// called from disk_usage.go
func (i *ImageService) LayerDiskUsage(ctx context.Context) (int64, error) {
var allLayersSize int64
// TODO(thaJeztah): do we need to take multiple snapshotters into account? See https://github.com/moby/moby/issues/45273
snapshotter := i.client.SnapshotService(i.snapshotter)
snapshotter.Walk(ctx, func(ctx context.Context, info snapshots.Info) error {
usage, err := snapshotter.Usage(ctx, info.Name)
if err != nil {
return err
}
allLayersSize += usage.Size
return nil
})
return allLayersSize, nil
}
// UpdateConfig values
//
// called from reload.go
func (i *ImageService) UpdateConfig(maxDownloads, maxUploads int) {
log.G(context.TODO()).Warn("max downloads and uploads is not yet implemented with the containerd store")
}
// GetContainerLayerSize returns the real size & virtual size of the container.
func (i *ImageService) GetContainerLayerSize(ctx context.Context, containerID string) (int64, int64, error) {
ctr := i.containers.Get(containerID)
if ctr == nil {
return 0, 0, nil
}
snapshotter := i.client.SnapshotService(ctr.Driver)
rwLayerUsage, err := snapshotter.Usage(ctx, containerID)
if err != nil {
if cerrdefs.IsNotFound(err) {
return 0, 0, errdefs.NotFound(fmt.Errorf("rw layer snapshot not found for container %s", containerID))
}
return 0, 0, errdefs.System(errors.Wrapf(err, "snapshotter.Usage failed for %s", containerID))
}
unpackedUsage, err := calculateSnapshotParentUsage(ctx, snapshotter, containerID)
if err != nil {
if cerrdefs.IsNotFound(err) {
log.G(ctx).WithField("ctr", containerID).Warn("parent of container snapshot no longer present")
} else {
log.G(ctx).WithError(err).WithField("ctr", containerID).Warn("unexpected error when calculating usage of the parent snapshots")
}
}
log.G(ctx).WithFields(log.Fields{
"rwLayerUsage": rwLayerUsage.Size,
"unpacked": unpackedUsage.Size,
}).Debug("GetContainerLayerSize")
// TODO(thaJeztah): include content-store size for the image (similar to "GET /images/json")
return rwLayerUsage.Size, rwLayerUsage.Size + unpackedUsage.Size, nil
}