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 }