2016-12-05 22:49:52 +00:00
// +build integration
/ *
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 integration
import (
2019-09-11 16:59:38 +00:00
"bytes"
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
2019-10-11 14:43:00 +00:00
"regexp"
2019-11-06 22:14:56 +00:00
"runtime"
2019-09-11 16:59:38 +00:00
"strings"
2016-12-05 22:49:52 +00:00
"testing"
2019-09-11 16:59:38 +00:00
"time"
2019-11-07 01:20:52 +00:00
"github.com/google/go-cmp/cmp"
"k8s.io/minikube/pkg/minikube/localpath"
2019-09-11 16:59:38 +00:00
"github.com/elazarl/goproxy"
"github.com/hashicorp/go-retryablehttp"
2019-11-07 01:20:52 +00:00
"github.com/otiai10/copy"
2019-09-11 16:59:38 +00:00
"github.com/phayes/freeport"
"github.com/pkg/errors"
"golang.org/x/build/kubernetes/api"
2019-09-11 20:08:44 +00:00
"k8s.io/minikube/pkg/util/retry"
2016-12-05 22:49:52 +00:00
)
2019-09-11 16:59:38 +00:00
// validateFunc are for subtests that share a single setup
type validateFunc func ( context . Context , * testing . T , string )
// TestFunctional are functionality tests which can safely share a profile in parallel
2017-05-30 20:23:24 +00:00
func TestFunctional ( t * testing . T ) {
2019-09-11 16:59:38 +00:00
profile := UniqueProfileName ( "functional" )
2019-09-11 23:19:25 +00:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 40 * time . Minute )
2020-02-12 22:26:38 +00:00
defer func ( ) {
p := localSyncTestPath ( )
if err := os . Remove ( p ) ; err != nil {
t . Logf ( "unable to remove %s: %v" , p , err )
}
CleanupWithLogs ( t , profile , cancel )
} ( )
2019-09-11 16:59:38 +00:00
// Serial tests
t . Run ( "serial" , func ( t * testing . T ) {
tests := [ ] struct {
name string
validator validateFunc
} {
2020-02-12 17:54:17 +00:00
{ "CopySyncFile" , setupFileSync } , // Set file for the file sync test case
{ "StartWithProxy" , validateStartWithProxy } , // Set everything else up for success
{ "KubeContext" , validateKubeContext } , // Racy: must come immediately after "minikube start"
{ "KubectlGetPods" , validateKubectlGetPods } , // Make sure apiserver is up
{ "CacheCmd" , validateCacheCmd } , // Caches images needed for subsequent tests because of proxy
{ "MinikubeKubectlCmd" , validateMinikubeKubectl } , // Make sure `minikube kubectl` works
2019-09-11 16:59:38 +00:00
}
for _ , tc := range tests {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
tc . validator ( ctx , t , profile )
} )
2019-07-30 04:52:05 +00:00
}
2019-09-11 16:59:38 +00:00
} )
2019-07-30 04:52:05 +00:00
2019-09-11 16:59:38 +00:00
// Now that we are out of the woods, lets go.
MaybeParallel ( t )
// Parallelized tests
t . Run ( "parallel" , func ( t * testing . T ) {
tests := [ ] struct {
name string
validator validateFunc
} {
{ "ComponentHealth" , validateComponentHealth } ,
{ "ConfigCmd" , validateConfigCmd } ,
{ "DashboardCmd" , validateDashboardCmd } ,
{ "DNS" , validateDNS } ,
2020-01-09 19:10:23 +00:00
{ "DryRun" , validateDryRun } ,
2019-10-13 15:24:40 +00:00
{ "StatusCmd" , validateStatusCmd } ,
2019-09-11 16:59:38 +00:00
{ "LogsCmd" , validateLogsCmd } ,
{ "MountCmd" , validateMountCmd } ,
{ "ProfileCmd" , validateProfileCmd } ,
2019-10-30 17:44:54 +00:00
{ "ServiceCmd" , validateServiceCmd } ,
2019-10-11 14:43:00 +00:00
{ "AddonsCmd" , validateAddonsCmd } ,
2019-09-11 16:59:38 +00:00
{ "PersistentVolumeClaim" , validatePersistentVolumeClaim } ,
{ "TunnelCmd" , validateTunnelCmd } ,
{ "SSHCmd" , validateSSHCmd } ,
2019-10-29 05:31:57 +00:00
{ "MySQL" , validateMySQL } ,
2019-11-07 01:20:52 +00:00
{ "FileSync" , validateFileSync } ,
2019-12-16 15:58:45 +00:00
{ "UpdateContextCmd" , validateUpdateContextCmd } ,
2020-02-13 09:17:11 +00:00
{ "DockerEnv" , validateDockerEnv } ,
2019-09-11 16:59:38 +00:00
}
for _ , tc := range tests {
tc := tc
t . Run ( tc . name , func ( t * testing . T ) {
MaybeParallel ( t )
tc . validator ( ctx , t , profile )
} )
}
2019-07-30 04:52:05 +00:00
} )
2019-09-11 16:59:38 +00:00
}
2020-02-13 09:17:11 +00:00
// check functionality of minikube after evaling docker-env
func validateDockerEnv ( ctx context . Context , t * testing . T , profile string ) {
mctx , cancel := context . WithTimeout ( ctx , 13 * time . Second )
defer cancel ( )
// we should be able to get minikube status with a bash which evaled docker-env
c := exec . CommandContext ( mctx , "/bin/bash" , "-c" , "eval $(" + Target ( ) + " -p " + profile + " docker-env) && " + Target ( ) + " status -p " + profile )
rr , err := Run ( t , c )
if err != nil {
t . Fatalf ( "Failed to do minikube status after eval-ing docker-env %s" , err )
}
if ! strings . Contains ( rr . Output ( ) , "Running" ) {
t . Fatalf ( "Expected status output to include 'Running' after eval docker-env but got \n%s" , rr . Output ( ) )
}
mctx , cancel = context . WithTimeout ( ctx , 13 * time . Second )
defer cancel ( )
// do a eval $(minikube -p profile docker-env) and check if we are point to docker inside minikube
c = exec . CommandContext ( mctx , "/bin/bash" , "-c" , "eval $(" + Target ( ) + " -p " + profile + " docker-env) && docker images" )
rr , err = Run ( t , c )
if err != nil {
t . Fatalf ( "Failed to test eval docker-evn %s" , err )
}
expectedImgInside := "gcr.io/k8s-minikube/storage-provisioner"
if ! strings . Contains ( rr . Output ( ) , expectedImgInside ) {
t . Fatalf ( "Expected 'docker ps' to have %q from docker-daemon inside minikube. the docker ps output is:\n%q\n" , expectedImgInside , rr . Output ( ) )
}
}
2019-09-11 16:59:38 +00:00
func validateStartWithProxy ( ctx context . Context , t * testing . T , profile string ) {
srv , err := startHTTPProxy ( t )
if err != nil {
t . Fatalf ( "Failed to set up the test proxy: %s" , err )
}
2019-11-06 23:17:09 +00:00
// Use more memory so that we may reliably fit MySQL and nginx
2019-11-13 06:43:55 +00:00
startArgs := append ( [ ] string { "start" , "-p" , profile , "--wait=true" , "--memory" , "2500MB" } , StartArgs ( ) ... )
2019-09-11 20:08:44 +00:00
c := exec . CommandContext ( ctx , Target ( ) , startArgs ... )
2019-09-11 16:59:38 +00:00
env := os . Environ ( )
env = append ( env , fmt . Sprintf ( "HTTP_PROXY=%s" , srv . Addr ) )
env = append ( env , "NO_PROXY=" )
c . Env = env
rr , err := Run ( t , c )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
want := "Found network options:"
if ! strings . Contains ( rr . Stdout . String ( ) , want ) {
t . Errorf ( "start stdout=%s, want: *%s*" , rr . Stdout . String ( ) , want )
}
want = "You appear to be using a proxy"
if ! strings . Contains ( rr . Stderr . String ( ) , want ) {
t . Errorf ( "start stderr=%s, want: *%s*" , rr . Stderr . String ( ) , want )
}
}
// validateKubeContext asserts that kubectl is properly configured (race-condition prone!)
func validateKubeContext ( ctx context . Context , t * testing . T , profile string ) {
rr , err := Run ( t , exec . CommandContext ( ctx , "kubectl" , "config" , "current-context" ) )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
if ! strings . Contains ( rr . Stdout . String ( ) , profile ) {
t . Errorf ( "current-context = %q, want %q" , rr . Stdout . String ( ) , profile )
}
}
2019-10-28 23:50:01 +00:00
// validateKubectlGetPods asserts that `kubectl get pod -A` returns non-zero content
func validateKubectlGetPods ( ctx context . Context , t * testing . T , profile string ) {
2019-11-13 05:38:31 +00:00
rr , err := Run ( t , exec . CommandContext ( ctx , "kubectl" , "--context" , profile , "get" , "po" , "-A" ) )
2019-10-28 23:50:01 +00:00
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2019-11-13 05:38:31 +00:00
if rr . Stderr . String ( ) != "" {
t . Errorf ( "%s: got unexpected stderr: %s" , rr . Command ( ) , rr . Stderr )
}
2019-11-12 22:19:52 +00:00
if ! strings . Contains ( rr . Stdout . String ( ) , "kube-system" ) {
2019-11-13 05:38:31 +00:00
t . Errorf ( "%s = %q, want *kube-system*" , rr . Command ( ) , rr . Stdout )
2019-10-28 23:50:01 +00:00
}
}
2020-02-12 17:54:17 +00:00
// validateMinikubeKubectl validates that the `minikube kubectl` command returns content
func validateMinikubeKubectl ( ctx context . Context , t * testing . T , profile string ) {
kubectlArgs := [ ] string { "kubectl" , "--" , "get" , "pods" }
2020-02-13 21:34:05 +00:00
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , kubectlArgs ... ) )
2020-02-12 17:54:17 +00:00
if err != nil {
t . Fatalf ( "%s failed: %v" , rr . Args , err )
}
}
2019-09-11 16:59:38 +00:00
// validateComponentHealth asserts that all Kubernetes components are healthy
func validateComponentHealth ( ctx context . Context , t * testing . T , profile string ) {
rr , err := Run ( t , exec . CommandContext ( ctx , "kubectl" , "--context" , profile , "get" , "cs" , "-o=json" ) )
if err != nil {
t . Fatalf ( "%s failed: %v" , rr . Args , err )
}
cs := api . ComponentStatusList { }
d := json . NewDecoder ( bytes . NewReader ( rr . Stdout . Bytes ( ) ) )
if err := d . Decode ( & cs ) ; err != nil {
t . Fatalf ( "decode: %v" , err )
}
for _ , i := range cs . Items {
status := api . ConditionFalse
for _ , c := range i . Conditions {
if c . Type != api . ComponentHealthy {
continue
}
status = c . Status
}
if status != api . ConditionTrue {
t . Errorf ( "unexpected status: %v - item: %+v" , status , i )
}
}
}
2019-10-13 15:24:40 +00:00
func validateStatusCmd ( ctx context . Context , t * testing . T , profile string ) {
2019-10-21 18:00:01 +00:00
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "status" ) )
2019-10-13 15:24:40 +00:00
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
// Custom format
2019-10-21 18:00:01 +00:00
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "status" , "-f" , "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubeconfig:{{.Kubeconfig}}" ) )
2019-10-13 15:24:40 +00:00
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2019-10-17 21:10:25 +00:00
match , _ := regexp . MatchString ( ` host:([A-z]+),kublet:([A-z]+),apiserver:([A-z]+),kubeconfig:([A-z]+) ` , rr . Stdout . String ( ) )
2019-10-13 15:24:40 +00:00
if ! match {
t . Errorf ( "%s failed: %v. Output for custom format did not match" , rr . Args , err )
}
// Json output
2019-10-21 18:00:01 +00:00
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "status" , "-o" , "json" ) )
2019-10-13 15:24:40 +00:00
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
var jsonObject map [ string ] interface { }
err = json . Unmarshal ( rr . Stdout . Bytes ( ) , & jsonObject )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
if _ , ok := jsonObject [ "Host" ] ; ! ok {
t . Errorf ( "%s failed: %v. Missing key %s in json object" , rr . Args , err , "Host" )
}
if _ , ok := jsonObject [ "Kubelet" ] ; ! ok {
t . Errorf ( "%s failed: %v. Missing key %s in json object" , rr . Args , err , "Kubelet" )
}
if _ , ok := jsonObject [ "APIServer" ] ; ! ok {
t . Errorf ( "%s failed: %v. Missing key %s in json object" , rr . Args , err , "APIServer" )
}
if _ , ok := jsonObject [ "Kubeconfig" ] ; ! ok {
t . Errorf ( "%s failed: %v. Missing key %s in json object" , rr . Args , err , "Kubeconfig" )
}
}
2019-09-11 16:59:38 +00:00
// validateDashboardCmd asserts that the dashboard command works
func validateDashboardCmd ( ctx context . Context , t * testing . T , profile string ) {
args := [ ] string { "dashboard" , "--url" , "-p" , profile , "--alsologtostderr" , "-v=1" }
ss , err := Start ( t , exec . CommandContext ( ctx , Target ( ) , args ... ) )
if err != nil {
t . Errorf ( "%s failed: %v" , args , err )
}
defer func ( ) {
ss . Stop ( t )
} ( )
start := time . Now ( )
s , err := ReadLineWithTimeout ( ss . Stdout , 300 * time . Second )
if err != nil {
2019-11-06 22:14:56 +00:00
if runtime . GOOS == "windows" {
t . Skipf ( "failed to read url within %s: %v\noutput: %q\n" , time . Since ( start ) , err , s )
}
t . Fatalf ( "failed to read url within %s: %v\noutput: %q\n" , time . Since ( start ) , err , s )
2019-09-11 16:59:38 +00:00
}
u , err := url . Parse ( strings . TrimSpace ( s ) )
if err != nil {
t . Fatalf ( "failed to parse %q: %v" , s , err )
}
resp , err := retryablehttp . Get ( u . String ( ) )
if err != nil {
t . Errorf ( "failed get: %v" , err )
}
if resp . StatusCode != http . StatusOK {
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
t . Errorf ( "Unable to read http response body: %v" , err )
}
t . Errorf ( "%s returned status code %d, expected %d.\nbody:\n%s" , u , resp . StatusCode , http . StatusOK , body )
}
}
2019-07-30 04:52:05 +00:00
2019-09-11 16:59:38 +00:00
// validateDNS asserts that all Kubernetes DNS is healthy
func validateDNS ( ctx context . Context , t * testing . T , profile string ) {
rr , err := Run ( t , exec . CommandContext ( ctx , "kubectl" , "--context" , profile , "replace" , "--force" , "-f" , filepath . Join ( * testdataDir , "busybox.yaml" ) ) )
if err != nil {
t . Fatalf ( "%s failed: %v" , rr . Args , err )
}
2019-11-06 23:17:09 +00:00
names , err := PodWait ( ctx , t , profile , "default" , "integration-test=busybox" , 5 * time . Minute )
2019-09-11 16:59:38 +00:00
if err != nil {
t . Fatalf ( "wait: %v" , err )
}
2019-09-11 20:08:44 +00:00
nslookup := func ( ) error {
rr , err = Run ( t , exec . CommandContext ( ctx , "kubectl" , "--context" , profile , "exec" , names [ 0 ] , "nslookup" , "kubernetes.default" ) )
return err
}
// If the coredns process was stable, this retry wouldn't be necessary.
if err = retry . Expo ( nslookup , 1 * time . Second , 1 * time . Minute ) ; err != nil {
t . Errorf ( "nslookup failing: %v" , err )
2019-09-11 16:59:38 +00:00
}
want := [ ] byte ( "10.96.0.1" )
if ! bytes . Contains ( rr . Stdout . Bytes ( ) , want ) {
t . Errorf ( "nslookup: got=%q, want=*%q*" , rr . Stdout . Bytes ( ) , want )
}
}
2020-01-09 19:10:23 +00:00
// validateDryRun asserts that the dry-run mode quickly exits with the right code
func validateDryRun ( ctx context . Context , t * testing . T , profile string ) {
2020-01-22 20:14:05 +00:00
// dry-run mode should always be able to finish quickly (<5s)
2020-01-22 20:14:54 +00:00
mctx , cancel := context . WithTimeout ( ctx , 5 * time . Second )
2020-01-09 19:10:23 +00:00
defer cancel ( )
// Too little memory!
2020-01-22 20:14:05 +00:00
startArgs := append ( [ ] string { "start" , "-p" , profile , "--dry-run" , "--memory" , "250MB" , "--alsologtostderr" , "-v=1" } , StartArgs ( ) ... )
2020-01-09 19:10:23 +00:00
c := exec . CommandContext ( mctx , Target ( ) , startArgs ... )
rr , err := Run ( t , c )
wantCode := 78 // exit.Config
if rr . ExitCode != wantCode {
t . Errorf ( "dry-run(250MB) exit code = %d, wanted = %d: %v" , rr . ExitCode , wantCode , err )
}
2020-01-22 20:14:54 +00:00
dctx , cancel := context . WithTimeout ( ctx , 5 * time . Second )
2020-01-09 19:10:23 +00:00
defer cancel ( )
2020-01-22 20:14:05 +00:00
startArgs = append ( [ ] string { "start" , "-p" , profile , "--dry-run" , "--alsologtostderr" , "-v=1" } , StartArgs ( ) ... )
2020-01-09 19:10:23 +00:00
c = exec . CommandContext ( dctx , Target ( ) , startArgs ... )
rr , err = Run ( t , c )
if rr . ExitCode != 0 || err != nil {
t . Errorf ( "dry-run exit code = %d, wanted = %d: %v" , rr . ExitCode , 0 , err )
}
}
2019-11-27 00:08:07 +00:00
// validateCacheCmd tests functionality of cache command (cache add, delete, list)
2019-09-11 16:59:38 +00:00
func validateCacheCmd ( ctx context . Context , t * testing . T , profile string ) {
if NoneDriver ( ) {
t . Skipf ( "skipping: cache unsupported by none" )
}
2019-11-27 00:19:29 +00:00
t . Run ( "cache" , func ( t * testing . T ) {
t . Run ( "add" , func ( t * testing . T ) {
2020-02-13 09:20:32 +00:00
for _ , img := range [ ] string { "busybox:latest" , "busybox:1.28.4-glibc" , "k8s.gcr.io/pause:latest" } {
2019-11-27 00:19:29 +00:00
_ , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "cache" , "add" , img ) )
if err != nil {
t . Errorf ( "Failed to cache image %q" , img )
}
}
} )
2020-02-13 02:11:44 +00:00
t . Run ( "delete_busybox:1.28.4-glibc" , func ( t * testing . T ) {
2019-11-27 00:19:29 +00:00
_ , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "cache" , "delete" , "busybox:1.28.4-glibc" ) )
if err != nil {
t . Errorf ( "failed to delete image busybox:1.28.4-glibc from cache: %v" , err )
}
} )
2019-11-27 00:08:07 +00:00
2019-11-27 00:19:29 +00:00
t . Run ( "list" , func ( t * testing . T ) {
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "cache" , "list" ) )
if err != nil {
t . Errorf ( "cache list failed: %v" , err )
}
if ! strings . Contains ( rr . Output ( ) , "k8s.gcr.io/pause" ) {
t . Errorf ( "cache list did not include k8s.gcr.io/pause" )
}
if strings . Contains ( rr . Output ( ) , "busybox:1.28.4-glibc" ) {
t . Errorf ( "cache list should not include busybox:1.28.4-glibc" )
}
} )
2019-11-27 00:08:07 +00:00
2020-02-13 02:11:44 +00:00
t . Run ( "verify_cache_inside_node" , func ( t * testing . T ) {
2019-12-10 20:15:20 +00:00
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "ssh" , "sudo" , "crictl" , "images" ) )
2019-11-27 00:19:29 +00:00
if err != nil {
2019-12-11 05:01:12 +00:00
t . Errorf ( "failed to get images by %q ssh %v" , rr . Command ( ) , err )
2019-11-27 00:19:29 +00:00
}
2019-12-10 20:15:20 +00:00
if ! strings . Contains ( rr . Output ( ) , "1.28.4-glibc" ) {
t . Errorf ( "expected '1.28.4-glibc' to be in the output: %s" , rr . Output ( ) )
2019-11-27 00:19:29 +00:00
}
} )
2019-12-11 05:01:12 +00:00
2020-02-13 02:11:44 +00:00
t . Run ( "cache_reload" , func ( t * testing . T ) { // deleting image inside minikube node manually and expecting reload to bring it back
2019-12-11 05:01:12 +00:00
img := "busybox:latest"
// deleting image inside minikube node manually
2019-12-11 05:26:37 +00:00
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "ssh" , "sudo" , "docker" , "rmi" , img ) ) // for some reason crictl rmi doesn't work
2019-12-11 05:01:12 +00:00
if err != nil {
t . Errorf ( "failed to delete inside the node %q : %v" , rr . Command ( ) , err )
}
// make sure the image is deleted.
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "ssh" , "sudo" , "crictl" , "inspecti" , img ) )
if err == nil {
t . Errorf ( "expected the image be deleted and get error but got nil error ! cmd: %q" , rr . Command ( ) )
}
2019-12-11 05:06:17 +00:00
// minikube cache reload.
2019-12-11 05:01:12 +00:00
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "cache" , "reload" ) )
if err != nil {
2019-12-11 05:09:42 +00:00
t . Errorf ( "expected %q to run successfully but got error %v" , rr . Command ( ) , err )
2019-12-11 05:01:12 +00:00
}
2019-12-11 05:06:17 +00:00
// make sure 'cache reload' brought back the manually deleted image.
2019-12-11 05:01:12 +00:00
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "ssh" , "sudo" , "crictl" , "inspecti" , img ) )
if err != nil {
t . Errorf ( "expected to get no error for %q but got %v" , rr . Command ( ) , err )
}
} )
2019-11-27 00:19:29 +00:00
} )
2019-09-11 16:59:38 +00:00
}
// validateConfigCmd asserts basic "config" command functionality
func validateConfigCmd ( ctx context . Context , t * testing . T , profile string ) {
tests := [ ] struct {
args [ ] string
wantOut string
wantErr string
} {
{ [ ] string { "unset" , "cpus" } , "" , "" } ,
{ [ ] string { "get" , "cpus" } , "" , "Error: specified key could not be found in config" } ,
{ [ ] string { "set" , "cpus" , "2" } , "! These changes will take effect upon a minikube delete and then a minikube start" , "" } ,
{ [ ] string { "get" , "cpus" } , "2" , "" } ,
{ [ ] string { "unset" , "cpus" } , "" , "" } ,
{ [ ] string { "get" , "cpus" } , "" , "Error: specified key could not be found in config" } ,
}
for _ , tc := range tests {
args := append ( [ ] string { "-p" , profile , "config" } , tc . args ... )
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , args ... ) )
if err != nil && tc . wantErr == "" {
t . Errorf ( "unexpected failure: %s failed: %v" , rr . Args , err )
}
got := strings . TrimSpace ( rr . Stdout . String ( ) )
if got != tc . wantOut {
t . Errorf ( "%s stdout got: %q, want: %q" , rr . Command ( ) , got , tc . wantOut )
}
got = strings . TrimSpace ( rr . Stderr . String ( ) )
if got != tc . wantErr {
t . Errorf ( "%s stderr got: %q, want: %q" , rr . Command ( ) , got , tc . wantErr )
}
}
}
// validateLogsCmd asserts basic "logs" command functionality
func validateLogsCmd ( ctx context . Context , t * testing . T , profile string ) {
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "logs" ) )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
for _ , word := range [ ] string { "Docker" , "apiserver" , "Linux" , "kubelet" } {
if ! strings . Contains ( rr . Stdout . String ( ) , word ) {
t . Errorf ( "minikube logs missing expected word: %q" , word )
}
}
}
2019-10-11 17:16:26 +00:00
// validateProfileCmd asserts "profile" command functionality
2019-09-11 16:59:38 +00:00
func validateProfileCmd ( ctx context . Context , t * testing . T , profile string ) {
2020-02-18 12:10:27 +00:00
// Profile command should not create a nonexistent profile
nonexistentProfile := "lis"
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "profile" , nonexistentProfile ) )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2020-02-19 00:00:57 +00:00
for _ , line := range [ ] string { fmt . Sprintf ( "Created a new profile : %s" , nonexistentProfile ) , fmt . Sprintf ( "minikube profile was successfully set to %s" , nonexistentProfile ) } {
if strings . Contains ( rr . Stdout . String ( ) , line ) {
2020-02-18 12:10:27 +00:00
t . Errorf ( "minikube profile should not create a nonexistent profile" )
}
}
2020-02-19 00:00:57 +00:00
for _ , line := range [ ] string { fmt . Sprintf ( "profile \"%s\" not found" , nonexistentProfile ) , fmt . Sprintf ( "if you want to create a profile you can by this command: minikube start -p %s" , nonexistentProfile ) } {
if ! strings . Contains ( rr . Stderr . String ( ) , line ) {
2020-02-18 12:10:27 +00:00
t . Errorf ( "minikube profile should provide guidance on how to create a nonexistent profile" )
}
}
// List profiles
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "profile" , "list" ) )
2019-09-11 16:59:38 +00:00
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2019-10-11 17:16:26 +00:00
// Table output
listLines := strings . Split ( strings . TrimSpace ( rr . Stdout . String ( ) ) , "\n" )
profileExists := false
for i := 3 ; i < ( len ( listLines ) - 1 ) ; i ++ {
profileLine := listLines [ i ]
if strings . Contains ( profileLine , profile ) {
profileExists = true
break
}
}
if ! profileExists {
t . Errorf ( "%s failed: Missing profile '%s'. Got '\n%s\n'" , rr . Args , profile , rr . Stdout . String ( ) )
}
// Json output
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "profile" , "list" , "--output" , "json" ) )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
var jsonObject map [ string ] [ ] map [ string ] interface { }
err = json . Unmarshal ( rr . Stdout . Bytes ( ) , & jsonObject )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
validProfiles := jsonObject [ "valid" ]
profileExists = false
for _ , profileObject := range validProfiles {
if profileObject [ "Name" ] == profile {
profileExists = true
break
}
}
if ! profileExists {
t . Errorf ( "%s failed: Missing profile '%s'. Got '\n%s\n'" , rr . Args , profile , rr . Stdout . String ( ) )
}
2019-09-11 16:59:38 +00:00
}
// validateServiceCmd asserts basic "service" command functionality
2019-10-30 17:44:54 +00:00
func validateServiceCmd ( ctx context . Context , t * testing . T , profile string ) {
2019-10-30 20:57:08 +00:00
rr , err := Run ( t , exec . CommandContext ( ctx , "kubectl" , "--context" , profile , "create" , "deployment" , "hello-node" , "--image=gcr.io/hello-minikube-zero-install/hello-node" ) )
2019-10-30 17:44:54 +00:00
if err != nil {
t . Logf ( "%s failed: %v (may not be an error)" , rr . Args , err )
}
2019-10-30 20:57:08 +00:00
rr , err = Run ( t , exec . CommandContext ( ctx , "kubectl" , "--context" , profile , "expose" , "deployment" , "hello-node" , "--type=NodePort" , "--port=8080" ) )
2019-10-30 17:44:54 +00:00
if err != nil {
t . Logf ( "%s failed: %v (may not be an error)" , rr . Args , err )
}
2019-12-18 23:14:10 +00:00
if _ , err := PodWait ( ctx , t , profile , "default" , "app=hello-node" , 10 * time . Minute ) ; err != nil {
2019-10-30 20:57:08 +00:00
t . Fatalf ( "wait: %v" , err )
}
2019-10-30 17:44:54 +00:00
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "service" , "list" ) )
2019-09-11 16:59:38 +00:00
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2019-10-30 20:57:08 +00:00
if ! strings . Contains ( rr . Stdout . String ( ) , "hello-node" ) {
t . Errorf ( "service list got %q, wanted *hello-node*" , rr . Stdout . String ( ) )
2019-10-30 17:44:54 +00:00
}
// Test --https --url mode
2019-10-30 20:57:08 +00:00
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "service" , "--namespace=default" , "--https" , "--url" , "hello-node" ) )
2019-10-30 17:44:54 +00:00
if err != nil {
2019-10-30 20:57:08 +00:00
t . Fatalf ( "%s failed: %v" , rr . Args , err )
}
if rr . Stderr . String ( ) != "" {
t . Errorf ( "unexpected stderr output: %s" , rr . Stderr )
2019-10-30 17:44:54 +00:00
}
2019-10-30 20:57:08 +00:00
endpoint := strings . TrimSpace ( rr . Stdout . String ( ) )
2019-10-30 17:44:54 +00:00
u , err := url . Parse ( endpoint )
if err != nil {
t . Fatalf ( "failed to parse %q: %v" , endpoint , err )
}
if u . Scheme != "https" {
t . Errorf ( "got scheme: %q, expected: %q" , u . Scheme , "https" )
}
// Test --format=IP
2019-10-30 20:57:08 +00:00
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "service" , "hello-node" , "--url" , "--format={{.IP}}" ) )
2019-10-30 17:44:54 +00:00
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2019-10-30 20:57:08 +00:00
if strings . TrimSpace ( rr . Stdout . String ( ) ) != u . Hostname ( ) {
2019-10-30 17:44:54 +00:00
t . Errorf ( "%s = %q, wanted %q" , rr . Args , rr . Stdout . String ( ) , u . Hostname ( ) )
}
2019-10-30 20:57:08 +00:00
// Test a regular URLminikube
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "service" , "hello-node" , "--url" ) )
2019-10-30 17:44:54 +00:00
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2019-10-30 20:57:08 +00:00
endpoint = strings . TrimSpace ( rr . Stdout . String ( ) )
u , err = url . Parse ( endpoint )
if err != nil {
t . Fatalf ( "failed to parse %q: %v" , endpoint , err )
}
if u . Scheme != "http" {
t . Fatalf ( "got scheme: %q, expected: %q" , u . Scheme , "http" )
}
t . Logf ( "url: %s" , endpoint )
resp , err := retryablehttp . Get ( endpoint )
2019-10-30 17:44:54 +00:00
if err != nil {
2019-10-30 20:57:08 +00:00
t . Fatalf ( "get failed: %v\nresp: %v" , err , resp )
2019-10-30 17:44:54 +00:00
}
if resp . StatusCode != http . StatusOK {
2019-10-30 20:57:08 +00:00
t . Fatalf ( "%s = status code %d, want %d" , u , resp . StatusCode , http . StatusOK )
2019-09-11 16:59:38 +00:00
}
}
2019-10-11 14:43:00 +00:00
// validateAddonsCmd asserts basic "addon" command functionality
func validateAddonsCmd ( ctx context . Context , t * testing . T , profile string ) {
2020-01-11 00:39:17 +00:00
// Table output
2019-10-11 14:43:00 +00:00
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "addons" , "list" ) )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2020-01-11 00:39:17 +00:00
for _ , a := range [ ] string { "dashboard" , "ingress" , "ingress-dns" } {
if ! strings . Contains ( rr . Output ( ) , a ) {
t . Errorf ( "addon list expected to include %q but didn't output: %q" , a , rr . Output ( ) )
2019-10-11 14:43:00 +00:00
}
}
2019-10-11 18:34:50 +00:00
// Json output
rr , err = Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "addons" , "list" , "-o" , "json" ) )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
var jsonObject map [ string ] interface { }
err = json . Unmarshal ( rr . Stdout . Bytes ( ) , & jsonObject )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2019-10-11 14:43:00 +00:00
}
2019-09-11 16:59:38 +00:00
// validateSSHCmd asserts basic "ssh" command functionality
func validateSSHCmd ( ctx context . Context , t * testing . T , profile string ) {
if NoneDriver ( ) {
t . Skipf ( "skipping: ssh unsupported by none" )
}
want := "hello\r\n"
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "ssh" , fmt . Sprintf ( "echo hello" ) ) )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
if rr . Stdout . String ( ) != want {
t . Errorf ( "%v = %q, want = %q" , rr . Args , rr . Stdout . String ( ) , want )
}
}
2019-10-29 05:31:57 +00:00
// validateMySQL validates a minimalist MySQL deployment
func validateMySQL ( ctx context . Context , t * testing . T , profile string ) {
rr , err := Run ( t , exec . CommandContext ( ctx , "kubectl" , "--context" , profile , "replace" , "--force" , "-f" , filepath . Join ( * testdataDir , "mysql.yaml" ) ) )
if err != nil {
t . Fatalf ( "%s failed: %v" , rr . Args , err )
}
2019-12-18 23:14:10 +00:00
names , err := PodWait ( ctx , t , profile , "default" , "app=mysql" , 10 * time . Minute )
2019-11-06 23:17:09 +00:00
if err != nil {
t . Fatalf ( "podwait: %v" , err )
}
2019-10-29 20:13:33 +00:00
// Retry, as mysqld first comes up without users configured. Scan for names in case of a reschedule.
mysql := func ( ) error {
rr , err = Run ( t , exec . CommandContext ( ctx , "kubectl" , "--context" , profile , "exec" , names [ 0 ] , "--" , "mysql" , "-ppassword" , "-e" , "show databases;" ) )
return err
2019-10-29 05:31:57 +00:00
}
2019-11-11 16:18:01 +00:00
if err = retry . Expo ( mysql , 5 * time . Second , 180 * time . Second ) ; err != nil {
2019-10-29 20:13:33 +00:00
t . Errorf ( "mysql failing: %v" , err )
2019-10-29 05:31:57 +00:00
}
}
2020-02-12 22:26:38 +00:00
// vmSyncTestPath is where the test file will be synced into the VM
func vmSyncTestPath ( ) string {
return fmt . Sprintf ( "/etc/test/nested/copy/%d/hosts" , os . Getpid ( ) )
}
// localSyncTestPath is where the test file will be synced into the VM
func localSyncTestPath ( ) string {
return filepath . Join ( localpath . MiniPath ( ) , "/files" , vmSyncTestPath ( ) )
}
2019-11-07 01:20:52 +00:00
// Copy extra file into minikube home folder for file sync test
func setupFileSync ( ctx context . Context , t * testing . T , profile string ) {
2020-02-12 22:26:38 +00:00
p := localSyncTestPath ( )
t . Logf ( "local sync path: %s" , p )
err := copy . Copy ( "./testdata/sync.test" , p )
2019-11-07 01:20:52 +00:00
if err != nil {
t . Fatalf ( "copy: %v" , err )
}
}
// validateFileSync to check existence of the test file
func validateFileSync ( ctx context . Context , t * testing . T , profile string ) {
if NoneDriver ( ) {
t . Skipf ( "skipping: ssh unsupported by none" )
}
2020-02-12 22:26:38 +00:00
vp := vmSyncTestPath ( )
t . Logf ( "Checking for existence of %s within VM" , vp )
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "ssh" , fmt . Sprintf ( "cat %s" , vp ) ) )
2019-11-07 01:20:52 +00:00
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
2020-02-12 22:26:38 +00:00
got := rr . Stdout . String ( )
t . Logf ( "file sync test content: %s" , got )
2019-11-07 01:20:52 +00:00
expected , err := ioutil . ReadFile ( "./testdata/sync.test" )
if err != nil {
t . Errorf ( "test file not found: %v" , err )
}
2020-02-12 22:26:38 +00:00
if diff := cmp . Diff ( string ( expected ) , got ) ; diff != "" {
2019-11-07 01:20:52 +00:00
t . Errorf ( "/etc/sync.test content mismatch (-want +got):\n%s" , diff )
}
}
2019-12-16 15:58:45 +00:00
// validateUpdateContextCmd asserts basic "update-context" command functionality
func validateUpdateContextCmd ( ctx context . Context , t * testing . T , profile string ) {
rr , err := Run ( t , exec . CommandContext ( ctx , Target ( ) , "-p" , profile , "update-context" , "--alsologtostderr" , "-v=2" ) )
if err != nil {
t . Errorf ( "%s failed: %v" , rr . Args , err )
}
want := [ ] byte ( "IP was already correctly configured" )
if ! bytes . Contains ( rr . Stdout . Bytes ( ) , want ) {
t . Errorf ( "update-context: got=%q, want=*%q*" , rr . Stdout . Bytes ( ) , want )
}
}
2019-09-11 16:59:38 +00:00
// startHTTPProxy runs a local http proxy and sets the env vars for it.
func startHTTPProxy ( t * testing . T ) ( * http . Server , error ) {
port , err := freeport . GetFreePort ( )
if err != nil {
return nil , errors . Wrap ( err , "Failed to get an open port" )
}
addr := fmt . Sprintf ( "localhost:%d" , port )
proxy := goproxy . NewProxyHttpServer ( )
srv := & http . Server { Addr : addr , Handler : proxy }
go func ( s * http . Server , t * testing . T ) {
if err := s . ListenAndServe ( ) ; err != http . ErrServerClosed {
t . Errorf ( "Failed to start http server for proxy mock" )
}
} ( srv , t )
return srv , nil
2016-12-05 22:49:52 +00:00
}