-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Extract the daemon image cache to its own package
ImageCache is now independent of `Daemon` and is located in `image/cache` package. Signed-off-by: Vincent Demeester <vincent@sbr.pm>
- Loading branch information
1 parent
884a1c2
commit be1df1e
Showing
4 changed files
with
263 additions
and
237 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.