Repository: kubernetes/minikube
Pull request #15256 (2022-10-31 21:30:28)

Ensure downloading preload images when size is zero
Fixes https://github.com/kubernetes/minikube/issues/15237

**Problem:**
We can get in the situation were the cached preload file exists, but the file is empty (size 0). Only the existence of the file is checked, not the size, which results in the preload not being downloaded when it should.

**Before (preload is not downloaded):**
```
$ minikube start --download-only
😄  minikube v1.27.1 on Darwin 12.6.1 (arm64)
✨  Automatically selected the docker driver. Other choices: ssh, qemu2 (experimental)
📌  Using Docker Desktop driver with root privileges
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
💾  Downloading Kubernetes v1.25.3 preload ...
    > preloaded-images-k8s-v18-v1...:  320.81 MiB / 320.81 MiB  100.00% 5.97 Mi
    > kubectl.sha256:  64 B / 64 B [-------------------------] 100.00% ? p/s 0s
    > kubectl:  47.38 MiB / 47.38 MiB [-------------] 100.00% 9.49 MiB p/s 5.2s
    > gcr.io/k8s-minikube/kicbase...:  347.52 MiB / 347.52 MiB  100.00% 5.31 Mi
✅  Download complete!
$ rm ~/.minikube/cache/preloaded-tarball/preloaded-images-k8s-v18-v1.25.3-docker-overlay2-arm64.tar.lz4
$ touch ~/.minikube/cache/preloaded-tarball/preloaded-images-k8s-v18-v1.25.3-docker-overlay2-arm64.tar.lz4
$ minikube start
😄  minikube v1.27.1 on Darwin 12.6.1 (arm64)
✨  Using the docker driver based on existing profile
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
    > gcr.io/k8s-minikube/kicbase...:  0 B [_____________________] ?% ? p/s 41s
🔥  Creating docker container (CPUs=2, Memory=1988MB) ...
🐳  Preparing Kubernetes v1.25.3 on Docker 20.10.20 ...
❌  Unable to load cached images: loading cached images: stat /Users/powellsteven/.minikube/cache/images/arm64/registry.k8s.io/pause_3.8: no such file or directory
    > kubelet.sha256:  64 B / 64 B [-------------------------] 100.00% ? p/s 0s
    > kubectl.sha256:  64 B / 64 B [-------------------------] 100.00% ? p/s 0s
    > kubeadm.sha256:  64 B / 64 B [-------------------------] 100.00% ? p/s 0s
    > kubeadm:  40.56 MiB / 40.56 MiB [--------------] 100.00% 3.52 MiB p/s 12s
    > kubectl:  41.56 MiB / 41.56 MiB [--------------] 100.00% 3.42 MiB p/s 12s
    > kubelet:  104.74 MiB / 104.74 MiB [------------] 100.00% 6.13 MiB p/s 17s
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: default-storageclass, storage-provisioner
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
```

**After (preload is downloaded):**
```
$ minikube start --download-only
😄  minikube v1.27.1 on Darwin 12.6.1 (arm64)
✨  Automatically selected the docker driver. Other choices: ssh, qemu2 (experimental)
📌  Using Docker Desktop driver with root privileges
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
💾  Downloading Kubernetes v1.25.3 preload ...
    > preloaded-images-k8s-v18-v1...:  320.81 MiB / 320.81 MiB  100.00% 5.97 Mi
    > kubectl.sha256:  64 B / 64 B [-------------------------] 100.00% ? p/s 0s
    > kubectl:  47.38 MiB / 47.38 MiB [-------------] 100.00% 9.49 MiB p/s 5.2s
    > gcr.io/k8s-minikube/kicbase...:  347.52 MiB / 347.52 MiB  100.00% 5.31 Mi
✅  Download complete!
$ rm ~/.minikube/cache/preloaded-tarball/preloaded-images-k8s-v18-v1.25.3-docker-overlay2-arm64.tar.lz4
$ touch ~/.minikube/cache/preloaded-tarball/preloaded-images-k8s-v18-v1.25.3-docker-overlay2-arm64.tar.lz4
$ minikube start
😄  minikube v1.27.1 on Darwin 12.6.1 (arm64)
✨  Using the docker driver based on existing profile
👍  Starting control plane node minikube in cluster minikube
🚜  Pulling base image ...
💾  Downloading Kubernetes v1.25.3 preload ...
    > preloaded-images-k8s-v18-v1...:  320.81 MiB / 320.81 MiB  100.00% 9.92 Mi
    > gcr.io/k8s-minikube/kicbase...:  0 B [_____________________] ?% ? p/s 42s
🔥  Creating docker container (CPUs=2, Memory=1988MB) ...
🐳  Preparing Kubernetes v1.25.3 on Docker 20.10.20 ...
    ▪ Generating certificates and keys ...
    ▪ Booting up control plane ...
    ▪ Configuring RBAC rules ...
🔎  Verifying Kubernetes components...
    ▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
🌟  Enabled addons: storage-provisioner, default-storageclass
🏄  Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default
```




Patch:
diff --git a/pkg/minikube/download/preload.go b/pkg/minikube/download/preload.go
index 1a09ab0bd..6a60e6e4a 100644
--- a/pkg/minikube/download/preload.go
+++ b/pkg/minikube/download/preload.go
@@ -144,7 +144,7 @@ func PreloadExists(k8sVersion, containerRuntime, driverName string, forcePreload
 
 	// Omit remote check if tarball exists locally
 	targetPath := TarballPath(k8sVersion, containerRuntime)
-	if _, err := checkCache(targetPath); err == nil {
+	if f, err := checkCache(targetPath); err == nil && f.Size() != 0 {
 		klog.Infof("Found local preload: %s", targetPath)
 		setPreloadState(k8sVersion, containerRuntime, true)
 		return true
@@ -170,7 +170,7 @@ func Preload(k8sVersion, containerRuntime, driverName string) error {
 		return err
 	}
 
-	if _, err := checkCache(targetPath); err == nil {
+	if f, err := checkCache(targetPath); err == nil && f.Size() != 0 {
 		klog.Infof("Found %s in cache, skipping download", targetPath)
 		return nil
 	}


Test patch:
diff --git a/pkg/minikube/download/download_test.go b/pkg/minikube/download/download_test.go
index e126b4532..3ae373155 100644
--- a/pkg/minikube/download/download_test.go
+++ b/pkg/minikube/download/download_test.go
@@ -19,6 +19,7 @@ package download
 import (
 	"fmt"
 	"io/fs"
+	"os"
 	"sync"
 	"testing"
 	"time"
@@ -35,6 +36,7 @@ func TestDownload(t *testing.T) {
 	t.Run("PreloadNotExists", testPreloadNotExists)
 	t.Run("PreloadChecksumMismatch", testPreloadChecksumMismatch)
 	t.Run("PreloadExistsCaching", testPreloadExistsCaching)
+	t.Run("PreloadWithCachedSizeZero", testPreloadWithCachedSizeZero)
 }
 
 // Returns a mock function that sleeps before incrementing `downloadsCounter` and creates the requested file.
@@ -80,12 +82,20 @@ func testBinaryDownloadPreventsMultipleDownload(t *testing.T) {
 func testPreloadDownloadPreventsMultipleDownload(t *testing.T) {
 	downloadNum := 0
 	DownloadMock = mockSleepDownload(&downloadNum)
+	f, err := os.CreateTemp("", "preload")
+	if err != nil {
+		t.Fatalf("failed to create temp file: %v", err)
+	}
+	defer os.Remove(f.Name())
+	if _, err := f.Write([]byte("data")); err != nil {
+		t.Fatalf("failed to write to temp file: %v", err)
+	}
 
 	checkCache = func(file string) (fs.FileInfo, error) {
 		if downloadNum == 0 {
 			return nil, fmt.Errorf("some error")
 		}
-		return nil, nil
+		return os.Stat(f.Name())
 	}
 	checkPreloadExists = func(k8sVersion, containerRuntime, driverName string, forcePreload ...bool) bool { return true }
 	getChecksum = func(k8sVersion, containerRuntime string) ([]byte, error) { return []byte("check"), nil }
@@ -236,3 +246,25 @@ func testPreloadExistsCaching(t *testing.T) {
 		t.Errorf("Expected preload to exist and no check to be performed. Existence: %v, Check: %v", existence, checkCalled)
 	}
 }
+
+func testPreloadWithCachedSizeZero(t *testing.T) {
+	downloadNum := 0
+	DownloadMock = mockSleepDownload(&downloadNum)
+	f, err := os.CreateTemp("", "preload")
+	if err != nil {
+		t.Fatalf("failed to create temp file: %v", err)
+	}
+
+	checkCache = func(file string) (fs.FileInfo, error) { return os.Stat(f.Name()) }
+	checkPreloadExists = func(k8sVersion, containerRuntime, driverName string, forcePreload ...bool) bool { return true }
+	getChecksum = func(k8sVersion, containerRuntime string) ([]byte, error) { return []byte("check"), nil }
+	ensureChecksumValid = func(k8sVersion, containerRuntime, path string, checksum []byte) error { return nil }
+
+	if err := Preload(constants.DefaultKubernetesVersion, constants.Docker, "docker"); err != nil {
+		t.Errorf("Expected no error with cached preload of size zero")
+	}
+
+	if downloadNum != 1 {
+		t.Errorf("Expected only 1 download attempt but got %v!", downloadNum)
+	}
+}