Skip to content

Commit

Permalink
Extract the daemon image cache to its own package
Browse files Browse the repository at this point in the history
ImageCache is now independent of `Daemon` and is located in
`image/cache` package.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
  • Loading branch information
vdemeester committed Jan 3, 2017
1 parent 884a1c2 commit be1df1e
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 237 deletions.
235 changes: 4 additions & 231 deletions daemon/cache.go
Original file line number Diff line number Diff line change
@@ -1,254 +1,27 @@
package daemon

import (
"encoding/json"
"fmt"
"reflect"
"strings"

"github.com/Sirupsen/logrus"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/builder"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/image"
"github.com/docker/docker/layer"
"github.com/docker/docker/runconfig"
"github.com/pkg/errors"
"github.com/docker/docker/image/cache"
)

// getLocalCachedImage returns the most recent created image that is a child
// of the image with imgID, that had the same config when it was
// created. nil is returned if a child cannot be found. An error is
// returned if the parent image cannot be found.
func (daemon *Daemon) getLocalCachedImage(imgID image.ID, config *containertypes.Config) (*image.Image, error) {
// Loop on the children of the given image and check the config
getMatch := func(siblings []image.ID) (*image.Image, error) {
var match *image.Image
for _, id := range siblings {
img, err := daemon.imageStore.Get(id)
if err != nil {
return nil, fmt.Errorf("unable to find image %q", id)
}

if runconfig.Compare(&img.ContainerConfig, config) {
// check for the most up to date match
if match == nil || match.Created.Before(img.Created) {
match = img
}
}
}
return match, nil
}

// In this case, this is `FROM scratch`, which isn't an actual image.
if imgID == "" {
images := daemon.imageStore.Map()
var siblings []image.ID
for id, img := range images {
if img.Parent == imgID {
siblings = append(siblings, id)
}
}
return getMatch(siblings)
}

// find match from child images
siblings := daemon.imageStore.Children(imgID)
return getMatch(siblings)
}

// MakeImageCache creates a stateful image cache.
func (daemon *Daemon) MakeImageCache(sourceRefs []string) builder.ImageCache {
if len(sourceRefs) == 0 {
return &localImageCache{daemon}
return cache.NewLocal(daemon.imageStore)
}

cache := &imageCache{daemon: daemon, localImageCache: &localImageCache{daemon}}
cache := cache.New(daemon.imageStore)

for _, ref := range sourceRefs {
img, err := daemon.GetImage(ref)
if err != nil {
logrus.Warnf("Could not look up %s for cache resolution, skipping: %+v", ref, err)
continue
}
cache.sources = append(cache.sources, img)
cache.Populate(img)
}

return cache
}

// localImageCache is cache based on parent chain.
type localImageCache struct {
daemon *Daemon
}

func (lic *localImageCache) GetCache(imgID string, config *containertypes.Config) (string, error) {
return getImageIDAndError(lic.daemon.getLocalCachedImage(image.ID(imgID), config))
}

// imageCache is cache based on history objects. Requires initial set of images.
type imageCache struct {
sources []*image.Image
daemon *Daemon
localImageCache *localImageCache
}

func (ic *imageCache) restoreCachedImage(parent, target *image.Image, cfg *containertypes.Config) (image.ID, error) {
var history []image.History
rootFS := image.NewRootFS()
lenHistory := 0
if parent != nil {
history = parent.History
rootFS = parent.RootFS
lenHistory = len(parent.History)
}
history = append(history, target.History[lenHistory])
if layer := getLayerForHistoryIndex(target, lenHistory); layer != "" {
rootFS.Append(layer)
}

config, err := json.Marshal(&image.Image{
V1Image: image.V1Image{
DockerVersion: dockerversion.Version,
Config: cfg,
Architecture: target.Architecture,
OS: target.OS,
Author: target.Author,
Created: history[len(history)-1].Created,
},
RootFS: rootFS,
History: history,
OSFeatures: target.OSFeatures,
OSVersion: target.OSVersion,
})
if err != nil {
return "", errors.Wrap(err, "failed to marshal image config")
}

imgID, err := ic.daemon.imageStore.Create(config)
if err != nil {
return "", errors.Wrap(err, "failed to create cache image")
}

if parent != nil {
if err := ic.daemon.imageStore.SetParent(imgID, parent.ID()); err != nil {
return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID())
}
}
return imgID, nil
}

func (ic *imageCache) isParent(imgID, parentID image.ID) bool {
nextParent, err := ic.daemon.imageStore.GetParent(imgID)
if err != nil {
return false
}
if nextParent == parentID {
return true
}
return ic.isParent(nextParent, parentID)
}

func (ic *imageCache) GetCache(parentID string, cfg *containertypes.Config) (string, error) {
imgID, err := ic.localImageCache.GetCache(parentID, cfg)
if err != nil {
return "", err
}
if imgID != "" {
for _, s := range ic.sources {
if ic.isParent(s.ID(), image.ID(imgID)) {
return imgID, nil
}
}
}

var parent *image.Image
lenHistory := 0
if parentID != "" {
parent, err = ic.daemon.imageStore.Get(image.ID(parentID))
if err != nil {
return "", errors.Wrapf(err, "unable to find image %v", parentID)
}
lenHistory = len(parent.History)
}

for _, target := range ic.sources {
if !isValidParent(target, parent) || !isValidConfig(cfg, target.History[lenHistory]) {
continue
}

if len(target.History)-1 == lenHistory { // last
if parent != nil {
if err := ic.daemon.imageStore.SetParent(target.ID(), parent.ID()); err != nil {
return "", errors.Wrapf(err, "failed to set parent for %v to %v", target.ID(), parent.ID())
}
}
return target.ID().String(), nil
}

imgID, err := ic.restoreCachedImage(parent, target, cfg)
if err != nil {
return "", errors.Wrapf(err, "failed to restore cached image from %q to %v", parentID, target.ID())
}

ic.sources = []*image.Image{target} // avoid jumping to different target, tuned for safety atm
return imgID.String(), nil
}

return "", nil
}

func getImageIDAndError(img *image.Image, err error) (string, error) {
if img == nil || err != nil {
return "", err
}
return img.ID().String(), nil
}

func isValidParent(img, parent *image.Image) bool {
if len(img.History) == 0 {
return false
}
if parent == nil || len(parent.History) == 0 && len(parent.RootFS.DiffIDs) == 0 {
return true
}
if len(parent.History) >= len(img.History) {
return false
}
if len(parent.RootFS.DiffIDs) >= len(img.RootFS.DiffIDs) {
return false
}

for i, h := range parent.History {
if !reflect.DeepEqual(h, img.History[i]) {
return false
}
}
for i, d := range parent.RootFS.DiffIDs {
if d != img.RootFS.DiffIDs[i] {
return false
}
}
return true
}

func getLayerForHistoryIndex(image *image.Image, index int) layer.DiffID {
layerIndex := 0
for i, h := range image.History {
if i == index {
if h.EmptyLayer {
return ""
}
break
}
if !h.EmptyLayer {
layerIndex++
}
}
return image.RootFS.DiffIDs[layerIndex] // validate?
}

func isValidConfig(cfg *containertypes.Config, h image.History) bool {
// todo: make this format better than join that loses data
return strings.Join(cfg.Cmd, " ") == h.CreatedBy
}
Loading

0 comments on commit be1df1e

Please sign in to comment.