minikube/pkg/minikube/download/download_test.go

270 lines
8.8 KiB
Go

/*
Copyright 2020 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 download
import (
"fmt"
"io/fs"
"os"
"sync"
"testing"
"time"
"k8s.io/minikube/pkg/minikube/constants"
)
// Force download tests to run in serial.
func TestDownload(t *testing.T) {
t.Run("BinaryDownloadPreventsMultipleDownload", testBinaryDownloadPreventsMultipleDownload)
t.Run("PreloadDownloadPreventsMultipleDownload", testPreloadDownloadPreventsMultipleDownload)
t.Run("ImageToCache", testImageToCache)
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.
func mockSleepDownload(downloadsCounter *int) func(src, dst string) error {
return func(src, dst string) error {
// Sleep for 200ms to assure locking must have occurred.
time.Sleep(time.Millisecond * 200)
*downloadsCounter++
return CreateDstDownloadMock(src, dst)
}
}
func testBinaryDownloadPreventsMultipleDownload(t *testing.T) {
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
checkCache = func(_ string) (fs.FileInfo, error) {
if downloadNum == 0 {
return nil, fmt.Errorf("some error")
}
return nil, nil
}
var group sync.WaitGroup
group.Add(2)
dlCall := func() {
if _, err := Binary("kubectl", "v1.20.2", "linux", "amd64", ""); err != nil {
t.Errorf("Failed to download binary: %+v", err)
}
group.Done()
}
go dlCall()
go dlCall()
group.Wait()
if downloadNum != 1 {
t.Errorf("Expected only 1 download attempt but got %v!", downloadNum)
}
}
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(_ string) (fs.FileInfo, error) {
if downloadNum == 0 {
return nil, fmt.Errorf("some error")
}
return os.Stat(f.Name())
}
checkPreloadExists = func(_, _, _ string, _ ...bool) bool { return true }
getChecksumGCS = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
ensureChecksumValid = func(_, _, _ string, _ []byte) error { return nil }
var group sync.WaitGroup
group.Add(2)
dlCall := func() {
if err := Preload(constants.DefaultKubernetesVersion, constants.Docker, "docker"); err != nil {
t.Logf("Failed to download preload: %+v (may be ok)", err)
}
group.Done()
}
go dlCall()
go dlCall()
group.Wait()
if downloadNum != 1 {
t.Errorf("Expected only 1 download attempt but got %v!", downloadNum)
}
}
func testPreloadNotExists(t *testing.T) {
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
checkCache = func(_ string) (fs.FileInfo, error) { return nil, fmt.Errorf("cache not found") }
checkPreloadExists = func(_, _, _ string, _ ...bool) bool { return false }
getChecksumGCS = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
ensureChecksumValid = func(_, _, _ string, _ []byte) error { return nil }
err := Preload(constants.DefaultKubernetesVersion, constants.Docker, "docker")
if err != nil {
t.Errorf("Expected no error when preload exists")
}
if downloadNum != 0 {
t.Errorf("Expected no download attempt but got %v!", downloadNum)
}
}
func testPreloadChecksumMismatch(t *testing.T) {
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
checkCache = func(_ string) (fs.FileInfo, error) { return nil, fmt.Errorf("cache not found") }
checkPreloadExists = func(_, _, _ string, _ ...bool) bool { return true }
getChecksumGCS = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
ensureChecksumValid = func(_, _, _ string, _ []byte) error {
return fmt.Errorf("checksum mismatch")
}
err := Preload(constants.DefaultKubernetesVersion, constants.Docker, "docker")
expectedErrMsg := "checksum mismatch"
if err == nil {
t.Errorf("Expected error when checksum mismatches")
} else if err.Error() != expectedErrMsg {
t.Errorf("Expected error to be %s, got %s", expectedErrMsg, err.Error())
}
}
func testImageToCache(t *testing.T) {
downloadNum := 0
DownloadMock = mockSleepDownload(&downloadNum)
checkImageExistsInCache = func(_ string) bool { return downloadNum > 0 }
var group sync.WaitGroup
group.Add(2)
dlCall := func() {
if err := ImageToCache("testimg"); err != nil {
t.Errorf("Failed to download preload: %+v", err)
}
group.Done()
}
go dlCall()
go dlCall()
group.Wait()
if downloadNum != 1 {
t.Errorf("Expected only 1 download attempt but got %v!", downloadNum)
}
}
// Validates that preload existence checks correctly caches values retrieved by remote checks.
// testPreloadExistsCaching verifies the caching semantics of PreloadExists when
// the local cache is absent and remote existence checks are required.
// In summary, this test enforces that:
// - PreloadExists performs remote checks only on cache misses.
// - Negative and positive results are cached per (k8sVersion, containerVersion, runtime) key.
// - GitHub is only consulted when GCS reports the preload as not existing.
// - Global state is correctly restored after the test.
func testPreloadExistsCaching(t *testing.T) {
checkCache = func(_ string) (fs.FileInfo, error) {
return nil, fmt.Errorf("cache not found")
}
doesPreloadExist := false
gcsCheckCalls := 0
ghCheckCalls := 0
originalGCSCheck := checkRemotePreloadExistsGCS
originalGHCheck := checkRemotePreloadExistsGitHub
preloadStates = make(map[string]map[string]preloadState)
checkRemotePreloadExistsGCS = func(_, _ string) bool {
gcsCheckCalls++
return doesPreloadExist
}
checkRemotePreloadExistsGitHub = func(_, _ string) bool {
ghCheckCalls++
return false
}
t.Cleanup(func() {
checkRemotePreloadExistsGCS = originalGCSCheck
checkRemotePreloadExistsGitHub = originalGHCheck
preloadStates = make(map[string]map[string]preloadState)
})
existence := PreloadExists("v1", "c1", "docker", true)
if existence || gcsCheckCalls != 1 || ghCheckCalls != 1 {
t.Errorf("Expected preload not to exist and checks to be performed. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
gcsCheckCalls = 0
ghCheckCalls = 0
existence = PreloadExists("v1", "c1", "docker", true)
if existence || gcsCheckCalls != 0 || ghCheckCalls != 0 {
t.Errorf("Expected preload not to exist and no checks to be performed. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
doesPreloadExist = true
gcsCheckCalls = 0
ghCheckCalls = 0
existence = PreloadExists("v2", "c1", "docker", true)
if !existence || gcsCheckCalls != 1 || ghCheckCalls != 0 {
t.Errorf("Expected preload to exist via GCS. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
gcsCheckCalls = 0
ghCheckCalls = 0
existence = PreloadExists("v2", "c2", "docker", true)
if !existence || gcsCheckCalls != 1 || ghCheckCalls != 0 {
t.Errorf("Expected preload to exist via GCS for new runtime. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
gcsCheckCalls = 0
ghCheckCalls = 0
existence = PreloadExists("v2", "c2", "docker", true)
if !existence || gcsCheckCalls != 0 || ghCheckCalls != 0 {
t.Errorf("Expected preload to exist and no checks to be performed. Existence: %v, GCS Calls: %d, GH Calls: %d", existence, gcsCheckCalls, ghCheckCalls)
}
}
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(_ string) (fs.FileInfo, error) { return os.Stat(f.Name()) }
checkPreloadExists = func(_, _, _ string, _ ...bool) bool { return true }
getChecksumGCS = func(_, _ string) ([]byte, error) { return []byte("check"), nil }
ensureChecksumValid = func(_, _, _ string, _ []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)
}
}