cache_images.go 8.0 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
	"strings"
25
	"sync"
26
	"time"
M
Matt Rickard 已提交
27

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

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

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

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

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

	return nil
}

65
// LoadImages loads previously cached images into the container runtime
66
func LoadImages(cc *config.ClusterConfig, runner command.Runner, images []string, cacheDir string) error {
M
Medya Gh 已提交
67 68 69 70
	// Skip loading images if images already exist
	if cruntime.ImagesPreloaded(cc.KubernetesConfig.ContainerRuntime, runner, images) {
		glog.Infof("Images are preloaded, skipping loading")
		return nil
71
	}
72
	glog.Infof("LoadImages start: %s", images)
73 74 75 76 77 78
	start := time.Now()

	defer func() {
		glog.Infof("LoadImages completed in %s", time.Since(start))
	}()

M
Matt Rickard 已提交
79
	var g errgroup.Group
80

S
Sharif Elgamal 已提交
81
	cr, err := cruntime.New(cruntime.Config{Type: cc.KubernetesConfig.ContainerRuntime, Runner: runner})
M
Medya Gh 已提交
82 83
	if err != nil {
		return errors.Wrap(err, "runtime")
84
	}
M
Medya Gh 已提交
85

86
	imgClient, err := client.NewClientWithOpts(client.FromEnv) // image client
87 88 89 90 91
	if err != nil {
		glog.Infof("couldn't get a local image daemon which might be ok: %v", err)
		imgClient = nil
	}

M
Matt Rickard 已提交
92 93 94
	for _, image := range images {
		image := image
		g.Go(func() error {
95 96 97 98 99
			// Put a ten second limit on deciding if an image needs transfer
			// because it takes much less than that time to just transfer the image.
			// This is needed because if running in offline mode, we can spend minutes here
			// waiting for i/o timeout.
			err := timedNeedsTransfer(imgClient, image, cr, 10*time.Second)
100
			if err == nil {
M
lint  
Medya Gh 已提交
101
				return nil
M
Medya Gh 已提交
102
			}
103 104
			glog.Infof("%q needs transfer: %v", image, err)
			return transferAndLoadImage(runner, cc.KubernetesConfig, image, cacheDir)
M
Matt Rickard 已提交
105 106 107 108 109
		})
	}
	if err := g.Wait(); err != nil {
		return errors.Wrap(err, "loading cached images")
	}
110 111 112 113
	glog.Infoln("Successfully loaded all cached images")
	return nil
}

114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
func timedNeedsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager, t time.Duration) error {
	timeout := make(chan bool, 1)
	go func() {
		time.Sleep(t)
		timeout <- true
	}()

	transferFinished := make(chan bool, 1)
	var err error
	go func() {
		err = needsTransfer(imgClient, imgName, cr)
		transferFinished <- true
	}()

	select {
	case <-transferFinished:
		return err
	case <-timeout:
		return fmt.Errorf("needs transfer timed out in %f seconds", t.Seconds())
	}
}

136
// needsTransfer returns an error if an image needs to be retransfered
137
func needsTransfer(imgClient *client.Client, imgName string, cr cruntime.Manager) error {
138
	imgDgst := ""         // for instance sha256:7c92a2c6bbcb6b6beff92d0a940779769c2477b807c202954c537e2e0deb9bed
139
	if imgClient != nil { // if possible try to get img digest from Client lib which is 4s faster.
M
Medya Gh 已提交
140
		imgDgst = image.DigestByDockerLib(imgClient, imgName)
141 142 143
		if imgDgst != "" {
			if !cr.ImageExists(imgName, imgDgst) {
				return fmt.Errorf("%q does not exist at hash %q in container runtime", imgName, imgDgst)
144 145 146 147 148
			}
			return nil
		}
	}
	// if not found with method above try go-container lib (which is 4s slower)
M
Medya Gh 已提交
149
	imgDgst = image.DigestByGoLib(imgName)
150 151
	if imgDgst == "" {
		return fmt.Errorf("got empty img digest %q for %s", imgDgst, imgName)
152
	}
153 154
	if !cr.ImageExists(imgName, imgDgst) {
		return fmt.Errorf("%q does not exist at hash %q in container runtime", imgName, imgDgst)
155 156 157 158
	}
	return nil
}

159
// CacheAndLoadImages caches and loads images to all profiles
P
Priya Wadhwa 已提交
160
func CacheAndLoadImages(images []string) error {
161 162 163 164
	if len(images) == 0 {
		return nil
	}

165
	// This is the most important thing
M
Medya Gh 已提交
166
	if err := image.SaveToDir(images, constants.ImageCacheDir); err != nil {
167
		return errors.Wrap(err, "save to dir")
P
Priya Wadhwa 已提交
168
	}
169

P
Priya Wadhwa 已提交
170 171
	api, err := NewAPIClient()
	if err != nil {
172
		return errors.Wrap(err, "api")
P
Priya Wadhwa 已提交
173 174
	}
	defer api.Close()
175
	profiles, _, err := config.ListProfiles() // need to load image to all profiles
176
	if err != nil {
177
		return errors.Wrap(err, "list profiles")
178
	}
179 180 181 182

	succeeded := []string{}
	failed := []string{}

M
Medya Gh 已提交
183
	for _, p := range profiles { // loading images to all running profiles
184
		pName := p.Name // capture the loop variable
185

S
Sharif Elgamal 已提交
186
		c, err := config.Load(pName)
187
		if err != nil {
188 189 190 191
			// Non-fatal because it may race with profile deletion
			glog.Errorf("Failed to load profile %q: %v", pName, err)
			failed = append(failed, pName)
			continue
192
		}
193

S
Sharif Elgamal 已提交
194
		for _, n := range c.Nodes {
195
			m := driver.MachineName(*c, n)
196

197
			status, err := Status(api, m)
198
			if err != nil {
199
				glog.Warningf("error getting status for %s: %v", pName, err)
200 201
				failed = append(failed, pName)
				continue
202
			}
203

S
Sharif Elgamal 已提交
204 205 206
			if status == state.Running.String() { // the not running hosts will load on next start
				h, err := api.Load(m)
				if err != nil {
207
					glog.Warningf("Failed to load machine %q: %v", m, err)
208 209
					failed = append(failed, pName)
					continue
S
Sharif Elgamal 已提交
210 211 212 213 214 215 216
				}
				cr, err := CommandRunner(h)
				if err != nil {
					return err
				}
				err = LoadImages(c, cr, images, constants.ImageCacheDir)
				if err != nil {
217
					failed = append(failed, pName)
S
Sharif Elgamal 已提交
218 219
					glog.Warningf("Failed to load cached images for profile %s. make sure the profile is running. %v", pName, err)
				}
220
				succeeded = append(succeeded, pName)
221 222
			}
		}
P
Priya Wadhwa 已提交
223
	}
224

T
Thomas Stromberg 已提交
225 226
	glog.Infof("succeeded pushing to: %s", strings.Join(succeeded, " "))
	glog.Infof("failed pushing to: %s", strings.Join(failed, " "))
227 228
	// Live pushes are not considered a failure
	return nil
P
Priya Wadhwa 已提交
229 230
}

231
// transferAndLoadImage transfers and loads a single image from the cache
232 233 234 235 236 237
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)
238
	src = localpath.SanitizeCacheDir(src)
239
	glog.Infof("Loading image from cache: %s", src)
M
Matt Rickard 已提交
240
	filename := filepath.Base(src)
241
	if _, err := os.Stat(src); err != nil {
242
		return err
M
Matt Rickard 已提交
243
	}
244 245
	dst := path.Join(loadRoot, filename)
	f, err := assets.NewFileAsset(src, loadRoot, filename, "0644")
M
Matt Rickard 已提交
246 247 248
	if err != nil {
		return errors.Wrapf(err, "creating copyable file asset: %s", filename)
	}
249
	if err := cr.Copy(f); err != nil {
M
Matt Rickard 已提交
250 251 252
		return errors.Wrap(err, "transferring cached image")
	}

253 254
	loadImageLock.Lock()
	defer loadImageLock.Unlock()
M
Matt Rickard 已提交
255

256 257 258
	err = r.LoadImage(dst)
	if err != nil {
		return errors.Wrapf(err, "%s load %s", r.Name(), dst)
259 260
	}

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