cache_images.go 6.2 KB
Newer Older
M
Matt Rickard 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
Copyright 2016 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package machine

import (
20
	"fmt"
M
Matt Rickard 已提交
21
	"os"
22
	"path"
M
Matt Rickard 已提交
23
	"path/filepath"
24
	"sync"
M
Matt Rickard 已提交
25

26
	"github.com/docker/docker/client"
27
	"github.com/docker/machine/libmachine/state"
28 29
	"github.com/golang/glog"
	"github.com/pkg/errors"
K
kairen 已提交
30
	"golang.org/x/sync/errgroup"
M
Matt Rickard 已提交
31 32
	"k8s.io/minikube/pkg/minikube/assets"
	"k8s.io/minikube/pkg/minikube/bootstrapper"
33
	"k8s.io/minikube/pkg/minikube/cluster"
34
	"k8s.io/minikube/pkg/minikube/command"
P
Priya Wadhwa 已提交
35
	"k8s.io/minikube/pkg/minikube/config"
M
Matt Rickard 已提交
36
	"k8s.io/minikube/pkg/minikube/constants"
37
	"k8s.io/minikube/pkg/minikube/cruntime"
38 39
	"k8s.io/minikube/pkg/minikube/image"
	"k8s.io/minikube/pkg/minikube/localpath"
40
	"k8s.io/minikube/pkg/minikube/vmpath"
M
Matt Rickard 已提交
41 42
)

43
// loadRoot is where images should be loaded from within the guest VM
44
var loadRoot = path.Join(vmpath.GuestPersistentDir, "images")
M
Matt Rickard 已提交
45

46 47
// loadImageLock is used to serialize image loads to avoid overloading the guest VM
var loadImageLock sync.Mutex
48

49
// CacheImagesForBootstrapper will cache images for a bootstrapper
50
func CacheImagesForBootstrapper(imageRepository string, version string, clusterBootstrapper string) error {
51 52 53 54
	images, err := bootstrapper.GetCachedImageList(imageRepository, version, clusterBootstrapper)
	if err != nil {
		return errors.Wrap(err, "cached images list")
	}
M
Matt Rickard 已提交
55

M
Medya Gh 已提交
56
	if err := image.SaveToDir(images, constants.ImageCacheDir); err != nil {
M
Matt Rickard 已提交
57 58 59 60 61 62
		return errors.Wrapf(err, "Caching images for %s", clusterBootstrapper)
	}

	return nil
}

63
// LoadImages loads previously cached images into the container runtime
64
func LoadImages(cc *config.MachineConfig, runner command.Runner, images []string, cacheDir string) error {
65 66
	glog.Infof("LoadImages start: %s", images)
	defer glog.Infof("LoadImages end")
M
Matt Rickard 已提交
67
	var g errgroup.Group
M
Medya Gh 已提交
68 69 70
	cr, err := cruntime.New(cruntime.Config{Type: cc.ContainerRuntime, Runner: runner})
	if err != nil {
		return errors.Wrap(err, "runtime")
71
	}
M
Medya Gh 已提交
72

73 74 75 76 77 78
	imgClient, err := client.NewEnvClient() // image client
	if err != nil {
		glog.Infof("couldn't get a local image daemon which might be ok: %v", err)
		imgClient = nil
	}

M
Matt Rickard 已提交
79 80 81
	for _, image := range images {
		image := image
		g.Go(func() error {
82
			err := needsTransfer(imgClient, image, cr)
83
			if err == nil {
M
lint  
Medya Gh 已提交
84
				return nil
M
Medya Gh 已提交
85
			}
86 87
			glog.Infof("%q needs transfer: %v", image, err)
			return transferAndLoadImage(runner, cc.KubernetesConfig, image, cacheDir)
M
Matt Rickard 已提交
88 89 90 91 92
		})
	}
	if err := g.Wait(); err != nil {
		return errors.Wrap(err, "loading cached images")
	}
93 94 95 96 97
	glog.Infoln("Successfully loaded all cached images")
	return nil
}

// needsTransfer returns an error if an image needs to be retransfered
98
func needsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager) error {
99
	imgDgst := ""         // for instance sha256:7c92a2c6bbcb6b6beff92d0a940779769c2477b807c202954c537e2e0deb9bed
100
	if imgClient != nil { // if possible try to get img digest from Client lib which is 4s faster.
M
Medya Gh 已提交
101
		imgDgst = image.DigestByDockerLib(imgClient, imgName)
102 103 104
		if imgDgst != "" {
			if !cr.ImageExists(imgName, imgDgst) {
				return fmt.Errorf("%q does not exist at hash %q in container runtime", imgName, imgDgst)
105 106 107 108 109
			}
			return nil
		}
	}
	// if not found with method above try go-container lib (which is 4s slower)
M
Medya Gh 已提交
110
	imgDgst = image.DigestByGoLib(imgName)
111 112
	if imgDgst == "" {
		return fmt.Errorf("got empty img digest %q for %s", imgDgst, imgName)
113
	}
114 115
	if !cr.ImageExists(imgName, imgDgst) {
		return fmt.Errorf("%q does not exist at hash %q in container runtime", imgName, imgDgst)
116 117 118 119
	}
	return nil
}

120
// CacheAndLoadImages caches and loads images to all profiles
P
Priya Wadhwa 已提交
121
func CacheAndLoadImages(images []string) error {
M
Medya Gh 已提交
122
	if err := image.SaveToDir(images, constants.ImageCacheDir); err != nil {
P
Priya Wadhwa 已提交
123
		return err
P
Priya Wadhwa 已提交
124
	}
P
Priya Wadhwa 已提交
125 126 127 128 129
	api, err := NewAPIClient()
	if err != nil {
		return err
	}
	defer api.Close()
130
	profiles, _, err := config.ListProfiles() // need to load image to all profiles
131
	if err != nil {
132
		return errors.Wrap(err, "list profiles")
133
	}
M
Medya Gh 已提交
134
	for _, p := range profiles { // loading images to all running profiles
135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
		pName := p.Name // capture the loop variable
		status, err := cluster.GetHostStatus(api, pName)
		if err != nil {
			glog.Warningf("skipping loading cache for profile %s", pName)
			glog.Errorf("error getting status for %s: %v", pName, err)
			continue // try next machine
		}
		if status == state.Running.String() { // the not running hosts will load on next start
			h, err := api.Load(pName)
			if err != nil {
				return err
			}
			cr, err := CommandRunner(h)
			if err != nil {
				return err
			}
151 152 153 154 155
			c, err := config.Load(pName)
			if err != nil {
				return err
			}
			err = LoadImages(c, cr, images, constants.ImageCacheDir)
156 157 158 159
			if err != nil {
				glog.Warningf("Failed to load cached images for profile %s. make sure the profile is running. %v", pName, err)
			}
		}
P
Priya Wadhwa 已提交
160
	}
161
	return err
P
Priya Wadhwa 已提交
162 163
}

164
// transferAndLoadImage transfers and loads a single image from the cache
165 166 167 168 169 170
func transferAndLoadImage(cr command.Runner, k8s config.KubernetesConfig, imgName string, cacheDir string) error {
	r, err := cruntime.New(cruntime.Config{Type: k8s.ContainerRuntime, Runner: cr})
	if err != nil {
		return errors.Wrap(err, "runtime")
	}
	src := filepath.Join(cacheDir, imgName)
171
	src = localpath.SanitizeCacheDir(src)
172
	glog.Infof("Loading image from cache: %s", src)
M
Matt Rickard 已提交
173
	filename := filepath.Base(src)
174
	if _, err := os.Stat(src); err != nil {
175
		return err
M
Matt Rickard 已提交
176
	}
177 178
	dst := path.Join(loadRoot, filename)
	f, err := assets.NewFileAsset(src, loadRoot, filename, "0644")
M
Matt Rickard 已提交
179 180 181
	if err != nil {
		return errors.Wrapf(err, "creating copyable file asset: %s", filename)
	}
182
	if err := cr.Copy(f); err != nil {
M
Matt Rickard 已提交
183 184 185
		return errors.Wrap(err, "transferring cached image")
	}

186 187
	loadImageLock.Lock()
	defer loadImageLock.Unlock()
M
Matt Rickard 已提交
188

189 190 191
	err = r.LoadImage(dst)
	if err != nil {
		return errors.Wrapf(err, "%s load %s", r.Name(), dst)
192 193
	}

194
	glog.Infof("Transferred and loaded %s from cache", src)
M
Matt Rickard 已提交
195 196
	return nil
}