functional_test.go 40.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
// +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 (
22 23 24 25 26 27 28 29 30
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
31
	"path"
32
	"path/filepath"
33
	"regexp"
T
tstromberg 已提交
34
	"runtime"
35
	"strings"
36
	"testing"
37 38
	"time"

39 40
	"github.com/google/go-cmp/cmp"

M
Medya Gh 已提交
41
	"k8s.io/minikube/pkg/minikube/config"
42
	"k8s.io/minikube/pkg/minikube/localpath"
43
	"k8s.io/minikube/pkg/minikube/reason"
M
lint  
Medya Gh 已提交
44
	"k8s.io/minikube/pkg/util/retry"
45

46 47
	"github.com/elazarl/goproxy"
	"github.com/hashicorp/go-retryablehttp"
48
	"github.com/otiai10/copy"
49 50 51
	"github.com/phayes/freeport"
	"github.com/pkg/errors"
	"golang.org/x/build/kubernetes/api"
52 53
)

54 55 56
// validateFunc are for subtests that share a single setup
type validateFunc func(context.Context, *testing.T, string)

M
Medya Gh 已提交
57 58 59
// used in validateStartWithProxy and validateSoftStart
var apiPortTest = 8441

60
// TestFunctional are functionality tests which can safely share a profile in parallel
N
Nick Kubala 已提交
61
func TestFunctional(t *testing.T) {
62 63

	profile := UniqueProfileName("functional")
M
Medya Gh 已提交
64
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
65
	defer func() {
66 67 68
		if !*cleanup {
			return
		}
69 70
		p := localSyncTestPath()
		if err := os.Remove(p); err != nil {
71
			t.Logf("unable to remove %q: %v", p, err)
72
		}
73

74
		Cleanup(t, profile, cancel)
75
	}()
76 77 78 79 80 81 82

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
		}{
83 84
			{"CopySyncFile", setupFileSync},                 // Set file for the file sync test case
			{"StartWithProxy", validateStartWithProxy},      // Set everything else up for success
M
Medya Gh 已提交
85
			{"SoftStart", validateSoftStart},                // do a soft start. ensure config didnt change.
86 87 88 89
			{"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
P
Pablo Caderno 已提交
90
			{"MinikubeKubectlCmdDirectly", validateMinikubeKubectlDirectCall},
91 92 93
		}
		for _, tc := range tests {
			tc := tc
94 95 96
			if ctx.Err() == context.DeadlineExceeded {
				t.Fatalf("Unable to run more tests (deadline exceeded)")
			}
97
			t.Run(tc.name, func(t *testing.T) {
M
lint  
Medya Gh 已提交
98
				tc.validator(ctx, t, profile)
99
			})
M
Medya Gh 已提交
100
		}
101
	})
M
Medya Gh 已提交
102

103 104 105 106 107 108 109 110 111 112 113 114
	// 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},
T
Thomas Stromberg 已提交
115
			{"DryRun", validateDryRun},
J
Josh Woodcock 已提交
116
			{"StatusCmd", validateStatusCmd},
117 118 119
			{"LogsCmd", validateLogsCmd},
			{"MountCmd", validateMountCmd},
			{"ProfileCmd", validateProfileCmd},
120
			{"ServiceCmd", validateServiceCmd},
121
			{"AddonsCmd", validateAddonsCmd},
122 123 124
			{"PersistentVolumeClaim", validatePersistentVolumeClaim},
			{"TunnelCmd", validateTunnelCmd},
			{"SSHCmd", validateSSHCmd},
125
			{"MySQL", validateMySQL},
126
			{"FileSync", validateFileSync},
127
			{"CertSync", validateCertSync},
128
			{"UpdateContextCmd", validateUpdateContextCmd},
129
			{"DockerEnv", validateDockerEnv},
M
Medya Gh 已提交
130
			{"NodeLabels", validateNodeLabels},
131 132 133
		}
		for _, tc := range tests {
			tc := tc
134 135 136 137
			if ctx.Err() == context.DeadlineExceeded {
				t.Fatalf("Unable to run more tests (deadline exceeded)")
			}

138 139 140 141 142
			t.Run(tc.name, func(t *testing.T) {
				MaybeParallel(t)
				tc.validator(ctx, t, profile)
			})
		}
M
Medya Gh 已提交
143
	})
144 145
}

M
Medya Gh 已提交
146 147
// validateNodeLabels checks if minikube cluster is created with correct kubernetes's node label
func validateNodeLabels(ctx context.Context, t *testing.T, profile string) {
148 149
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
150
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "nodes", "--output=go-template", "--template='{{range $k, $v := (index .items 0).metadata.labels}}{{$k}} {{end}}'"))
M
Medya Gh 已提交
151
	if err != nil {
M
Medya Gh 已提交
152
		t.Errorf("failed to 'kubectl get nodes' with args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
153 154 155
	}
	expectedLabels := []string{"minikube.k8s.io/commit", "minikube.k8s.io/version", "minikube.k8s.io/updated_at", "minikube.k8s.io/name"}
	for _, el := range expectedLabels {
M
Medya Gh 已提交
156
		if !strings.Contains(rr.Output(), el) {
M
Medya Gh 已提交
157
			t.Errorf("expected to have label %q in node labels but got : %s", el, rr.Output())
M
Medya Gh 已提交
158 159 160 161
		}
	}
}

162 163
// check functionality of minikube after evaling docker-env
func validateDockerEnv(ctx context.Context, t *testing.T, profile string) {
164
	defer PostMortemLogs(t, profile)
M
Medya Gh 已提交
165
	mctx, cancel := context.WithTimeout(ctx, Seconds(30))
166
	defer cancel()
M
Medya Gh 已提交
167 168
	var rr *RunResult
	var err error
M
Medya Gh 已提交
169
	if runtime.GOOS == "windows" {
M
Medya Gh 已提交
170 171
		c := exec.CommandContext(mctx, "powershell.exe", "-NoProfile", "-NonInteractive", Target()+" -p "+profile+" docker-env | Invoke-Expression ;"+Target()+" status -p "+profile)
		rr, err = Run(t, c)
M
Medya Gh 已提交
172 173 174 175 176
	} else {
		c := exec.CommandContext(mctx, "/bin/bash", "-c", "eval $("+Target()+" -p "+profile+" docker-env) && "+Target()+" status -p "+profile)
		// we should be able to get minikube status with a bash which evaled docker-env
		rr, err = Run(t, c)
	}
M
Medya Gh 已提交
177 178
	if mctx.Err() == context.DeadlineExceeded {
		t.Errorf("failed to run the command by deadline. exceeded timeout. %s", rr.Command())
M
Medya Gh 已提交
179
	}
180
	if err != nil {
M
Medya Gh 已提交
181
		t.Fatalf("failed to do status after eval-ing docker-env. error: %v", err)
182 183
	}
	if !strings.Contains(rr.Output(), "Running") {
M
Medya Gh 已提交
184
		t.Fatalf("expected status output to include 'Running' after eval docker-env but got: *%s*", rr.Output())
185 186
	}

M
Medya Gh 已提交
187
	mctx, cancel = context.WithTimeout(ctx, Seconds(30))
188 189
	defer cancel()
	// do a eval $(minikube -p profile docker-env) and check if we are point to docker inside minikube
M
Medya Gh 已提交
190
	if runtime.GOOS == "windows" { // testing docker-env eval in powershell
M
Medya Gh 已提交
191 192
		c := exec.CommandContext(mctx, "powershell.exe", "-NoProfile", "-NonInteractive", Target(), "-p "+profile+" docker-env | Invoke-Expression ; docker images")
		rr, err = Run(t, c)
M
Medya Gh 已提交
193
	} else {
M
Medya Gh 已提交
194
		c := exec.CommandContext(mctx, "/bin/bash", "-c", "eval $("+Target()+" -p "+profile+" docker-env) && docker images")
M
Medya Gh 已提交
195 196 197
		rr, err = Run(t, c)
	}

M
Medya Gh 已提交
198
	if mctx.Err() == context.DeadlineExceeded {
M
try pwd  
Medya Gh 已提交
199
		t.Errorf("failed to run the command in 30 seconds. exceeded 30s timeout. %s", rr.Command())
M
Medya Gh 已提交
200 201
	}

202
	if err != nil {
M
Medya Gh 已提交
203
		t.Fatalf("failed to run minikube docker-env. args %q : %v ", rr.Command(), err)
204 205 206 207
	}

	expectedImgInside := "gcr.io/k8s-minikube/storage-provisioner"
	if !strings.Contains(rr.Output(), expectedImgInside) {
M
Medya Gh 已提交
208
		t.Fatalf("expected 'docker images' to have %q inside minikube. but the output is: *%s*", expectedImgInside, rr.Output())
209 210 211 212
	}

}

M
lint  
Medya Gh 已提交
213
func validateStartWithProxy(ctx context.Context, t *testing.T, profile string) {
214 215
	defer PostMortemLogs(t, profile)

216 217
	srv, err := startHTTPProxy(t)
	if err != nil {
218
		t.Fatalf("failed to set up the test proxy: %s", err)
219
	}
220 221

	// Use more memory so that we may reliably fit MySQL and nginx
M
Medya Gh 已提交
222
	// changing api server so later in soft start we verify it didn't change
M
Medya Gh 已提交
223
	startArgs := append([]string{"start", "-p", profile, "--memory=4000", fmt.Sprintf("--apiserver-port=%d", apiPortTest), "--wait=true"}, StartArgs()...)
224
	c := exec.CommandContext(ctx, Target(), startArgs...)
225 226 227 228 229 230
	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 {
M
Medya Gh 已提交
231
		t.Errorf("failed minikube start. args %q: %v", rr.Command(), err)
232 233 234 235 236 237 238 239 240 241 242 243 244
	}

	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)
	}
}

M
Medya Gh 已提交
245
// validateSoftStart validates that after minikube already started, a "minikube start" should not change the configs.
M
lint  
Medya Gh 已提交
246
func validateSoftStart(ctx context.Context, t *testing.T, profile string) {
247 248
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
249
	start := time.Now()
M
Medya Gh 已提交
250
	// the test before this had been start with --apiserver-port=8441
M
lint  
Medya Gh 已提交
251 252
	beforeCfg, err := config.LoadProfile(profile)
	if err != nil {
253
		t.Fatalf("error reading cluster config before soft start: %v", err)
M
lint  
Medya Gh 已提交
254
	}
M
Medya Gh 已提交
255 256
	if beforeCfg.Config.KubernetesConfig.NodePort != apiPortTest {
		t.Errorf("expected cluster config node port before soft start to be %d but got %d", apiPortTest, beforeCfg.Config.KubernetesConfig.NodePort)
M
Medya Gh 已提交
257 258
	}

M
typo  
Medya Gh 已提交
259
	softStartArgs := []string{"start", "-p", profile, "--alsologtostderr", "-v=8"}
M
Medya Gh 已提交
260
	c := exec.CommandContext(ctx, Target(), softStartArgs...)
M
lint  
Medya Gh 已提交
261
	rr, err := Run(t, c)
M
Medya Gh 已提交
262 263 264
	if err != nil {
		t.Errorf("failed to soft start minikube. args %q: %v", rr.Command(), err)
	}
M
Medya Gh 已提交
265
	t.Logf("soft start took %s for %q cluster.", time.Since(start), profile)
M
Medya Gh 已提交
266

M
lint  
Medya Gh 已提交
267 268 269 270 271
	afterCfg, err := config.LoadProfile(profile)
	if err != nil {
		t.Errorf("error reading cluster config after soft start: %v", err)
	}

M
Medya Gh 已提交
272 273
	if afterCfg.Config.KubernetesConfig.NodePort != apiPortTest {
		t.Errorf("expected node port in the config not change after soft start. exepceted node port to be %d but got %d.", apiPortTest, afterCfg.Config.KubernetesConfig.NodePort)
M
Medya Gh 已提交
274
	}
M
Medya Gh 已提交
275

M
Medya Gh 已提交
276 277
}

278 279
// validateKubeContext asserts that kubectl is properly configured (race-condition prone!)
func validateKubeContext(ctx context.Context, t *testing.T, profile string) {
280 281
	defer PostMortemLogs(t, profile)

282 283
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "config", "current-context"))
	if err != nil {
M
Medya Gh 已提交
284
		t.Errorf("failed to get current-context. args %q : %v", rr.Command(), err)
285 286
	}
	if !strings.Contains(rr.Stdout.String(), profile) {
287
		t.Errorf("expected current-context = %q, but got *%q*", profile, rr.Stdout.String())
288 289 290
	}
}

P
Priya Wadhwa 已提交
291 292
// validateKubectlGetPods asserts that `kubectl get pod -A` returns non-zero content
func validateKubectlGetPods(ctx context.Context, t *testing.T, profile string) {
293 294
	defer PostMortemLogs(t, profile)

295
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "po", "-A"))
P
Priya Wadhwa 已提交
296
	if err != nil {
M
Medya Gh 已提交
297
		t.Errorf("failed to get kubectl pods: args %q : %v", rr.Command(), err)
P
Priya Wadhwa 已提交
298
	}
299
	if rr.Stderr.String() != "" {
300
		t.Errorf("expected stderr to be empty but got *%q*: args %q", rr.Stderr, rr.Command())
301
	}
T
Thomas Stromberg 已提交
302
	if !strings.Contains(rr.Stdout.String(), "kube-system") {
303
		t.Errorf("expected stdout to include *kube-system* but got *%q*. args: %q", rr.Stdout, rr.Command())
P
Priya Wadhwa 已提交
304 305 306
	}
}

307 308
// validateMinikubeKubectl validates that the `minikube kubectl` command returns content
func validateMinikubeKubectl(ctx context.Context, t *testing.T, profile string) {
309 310
	defer PostMortemLogs(t, profile)

311 312
	// Must set the profile so that it knows what version of Kubernetes to use
	kubectlArgs := []string{"-p", profile, "kubectl", "--", "--context", profile, "get", "pods"}
P
Priya Wadhwa 已提交
313
	rr, err := Run(t, exec.CommandContext(ctx, Target(), kubectlArgs...))
314
	if err != nil {
M
Medya Gh 已提交
315
		t.Fatalf("failed to get pods. args %q: %v", rr.Command(), err)
316 317 318
	}
}

P
Pablo Caderno 已提交
319 320 321 322 323 324 325 326 327 328 329 330
// validateMinikubeKubectlDirectCall validates that calling minikube's kubectl
func validateMinikubeKubectlDirectCall(ctx context.Context, t *testing.T, profile string) {
	defer PostMortemLogs(t, profile)
	dir := filepath.Dir(Target())
	dstfn := filepath.Join(dir, "kubectl")
	err := os.Link(Target(), dstfn)

	if err != nil {
		t.Fatal(err)
	}
	defer os.Remove(dstfn) // clean up

P
Pablo Caderno 已提交
331
	kubectlArgs := []string{"get", "pods"}
P
Pablo Caderno 已提交
332 333 334 335 336 337 338
	rr, err := Run(t, exec.CommandContext(ctx, dstfn, kubectlArgs...))
	if err != nil {
		t.Fatalf("failed to run kubectl directl. args %q: %v", rr.Command(), err)
	}

}

339 340
// validateComponentHealth asserts that all Kubernetes components are healthy
func validateComponentHealth(ctx context.Context, t *testing.T, profile string) {
341 342
	defer PostMortemLogs(t, profile)

343 344 345 346 347 348 349 350 351
	// The ComponentStatus API is deprecated in v1.19, so do the next closest thing.
	found := map[string]bool{
		"etcd":                    false,
		"kube-apiserver":          false,
		"kube-controller-manager": false,
		"kube-scheduler":          false,
	}

	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "po", "-l", "tier=control-plane", "-n", "kube-system", "-o=json"))
352
	if err != nil {
M
Medya Gh 已提交
353
		t.Fatalf("failed to get components. args %q: %v", rr.Command(), err)
354
	}
355
	cs := api.PodList{}
356 357
	d := json.NewDecoder(bytes.NewReader(rr.Stdout.Bytes()))
	if err := d.Decode(&cs); err != nil {
M
Medya Gh 已提交
358
		t.Fatalf("failed to decode kubectl json output: args %q : %v", rr.Command(), err)
359 360 361
	}

	for _, i := range cs.Items {
362 363 364 365 366 367 368 369
		for _, l := range i.Labels {
			t.Logf("%s phase: %s", l, i.Status.Phase)
			_, ok := found[l]
			if ok {
				found[l] = true
				if i.Status.Phase != "Running" {
					t.Errorf("%s is not Running: %+v", l, i.Status)
				}
370 371
			}
		}
372 373 374 375 376
	}

	for k, v := range found {
		if !v {
			t.Errorf("expected component %q was not found", k)
377 378 379 380
		}
	}
}

J
Josh Woodcock 已提交
381
func validateStatusCmd(ctx context.Context, t *testing.T, profile string) {
382
	defer PostMortemLogs(t, profile)
383
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status"))
J
Josh Woodcock 已提交
384
	if err != nil {
M
Medya Gh 已提交
385
		t.Errorf("failed to run minikube status. args %q : %v", rr.Command(), err)
J
Josh Woodcock 已提交
386 387 388
	}

	// Custom format
389
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubeconfig:{{.Kubeconfig}}"))
J
Josh Woodcock 已提交
390
	if err != nil {
M
Medya Gh 已提交
391
		t.Errorf("failed to run minikube status with custom format: args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
392
	}
393 394
	re := `host:([A-z]+),kublet:([A-z]+),apiserver:([A-z]+),kubeconfig:([A-z]+)`
	match, _ := regexp.MatchString(re, rr.Stdout.String())
J
Josh Woodcock 已提交
395
	if !match {
M
Medya Gh 已提交
396
		t.Errorf("failed to match regex %q for minikube status with custom format. args %q. output: %s", re, rr.Command(), rr.Output())
J
Josh Woodcock 已提交
397 398 399
	}

	// Json output
400
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-o", "json"))
J
Josh Woodcock 已提交
401
	if err != nil {
M
Medya Gh 已提交
402
		t.Errorf("failed to run minikube status with json output. args %q : %v", rr.Command(), err)
J
Josh Woodcock 已提交
403 404 405 406
	}
	var jsonObject map[string]interface{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
	if err != nil {
M
Medya Gh 已提交
407
		t.Errorf("failed to decode json from minikube status. args %q. %v", rr.Command(), err)
J
Josh Woodcock 已提交
408 409
	}
	if _, ok := jsonObject["Host"]; !ok {
M
Medya Gh 已提交
410
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Host")
J
Josh Woodcock 已提交
411 412
	}
	if _, ok := jsonObject["Kubelet"]; !ok {
M
Medya Gh 已提交
413
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Kubelet")
J
Josh Woodcock 已提交
414 415
	}
	if _, ok := jsonObject["APIServer"]; !ok {
M
Medya Gh 已提交
416
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "APIServer")
J
Josh Woodcock 已提交
417 418
	}
	if _, ok := jsonObject["Kubeconfig"]; !ok {
M
Medya Gh 已提交
419
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Kubeconfig")
J
Josh Woodcock 已提交
420 421 422
	}
}

423 424
// validateDashboardCmd asserts that the dashboard command works
func validateDashboardCmd(ctx context.Context, t *testing.T, profile string) {
425 426
	defer PostMortemLogs(t, profile)

427 428 429
	args := []string{"dashboard", "--url", "-p", profile, "--alsologtostderr", "-v=1"}
	ss, err := Start(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
430
		t.Errorf("failed to run minikube dashboard. args %q : %v", args, err)
431 432 433 434 435 436
	}
	defer func() {
		ss.Stop(t)
	}()

	start := time.Now()
M
Medya Gh 已提交
437
	s, err := ReadLineWithTimeout(ss.Stdout, Seconds(300))
438
	if err != nil {
T
tstromberg 已提交
439 440 441 442
		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)
443 444 445 446 447 448 449 450 451
	}

	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 {
452
		t.Fatalf("failed to http get %q: %v\nresponse: %+v", u.String(), err, resp)
453
	}
454

455 456 457
	if resp.StatusCode != http.StatusOK {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
458
			t.Errorf("failed to read http response body from dashboard %q: %v", u.String(), err)
459 460 461 462
		}
		t.Errorf("%s returned status code %d, expected %d.\nbody:\n%s", u, resp.StatusCode, http.StatusOK, body)
	}
}
M
Medya Gh 已提交
463

T
Thomas Stromberg 已提交
464 465
// validateDryRun asserts that the dry-run mode quickly exits with the right code
func validateDryRun(ctx context.Context, t *testing.T, profile string) {
466
	// dry-run mode should always be able to finish quickly (<5s)
M
Medya Gh 已提交
467
	mctx, cancel := context.WithTimeout(ctx, Seconds(5))
T
Thomas Stromberg 已提交
468 469 470
	defer cancel()

	// Too little memory!
471
	startArgs := append([]string{"start", "-p", profile, "--dry-run", "--memory", "250MB", "--alsologtostderr"}, StartArgs()...)
T
Thomas Stromberg 已提交
472 473 474
	c := exec.CommandContext(mctx, Target(), startArgs...)
	rr, err := Run(t, c)

475
	wantCode := reason.ExInsufficientMemory
T
Thomas Stromberg 已提交
476 477 478 479
	if rr.ExitCode != wantCode {
		t.Errorf("dry-run(250MB) exit code = %d, wanted = %d: %v", rr.ExitCode, wantCode, err)
	}

M
Medya Gh 已提交
480
	dctx, cancel := context.WithTimeout(ctx, Seconds(5))
T
Thomas Stromberg 已提交
481
	defer cancel()
482
	startArgs = append([]string{"start", "-p", profile, "--dry-run", "--alsologtostderr", "-v=1"}, StartArgs()...)
T
Thomas Stromberg 已提交
483 484 485 486 487 488 489
	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)
	}
}

M
Medya Gh 已提交
490
// validateCacheCmd tests functionality of cache command (cache add, delete, list)
491
func validateCacheCmd(ctx context.Context, t *testing.T, profile string) {
492 493
	defer PostMortemLogs(t, profile)

494 495 496
	if NoneDriver() {
		t.Skipf("skipping: cache unsupported by none")
	}
497

M
Medya Gh 已提交
498 499
	t.Run("cache", func(t *testing.T) {
		t.Run("add", func(t *testing.T) {
500
			for _, img := range []string{"busybox:latest", "busybox:1.28.4-glibc", "k8s.gcr.io/pause:latest"} {
501
				rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "add", img))
M
Medya Gh 已提交
502
				if err != nil {
M
Medya Gh 已提交
503
					t.Errorf("failed to cache add image %q. args %q err %v", img, rr.Command(), err)
M
Medya Gh 已提交
504 505 506
				}
			}
		})
507 508 509 510 511 512 513 514 515 516 517 518 519 520

		t.Run("add_local", func(t *testing.T) {
			dname, err := ioutil.TempDir("", profile)
			if err != nil {
				t.Fatalf("Cannot create temp dir: %v", err)
			}

			message := []byte("FROM scratch\nADD Dockerfile /x")
			err = ioutil.WriteFile(filepath.Join(dname, "Dockerfile"), message, 0644)
			if err != nil {
				t.Fatalf("unable to writefile: %v", err)
			}

			img := "minikube-local-cache-test:" + profile
T
Thomas Stromberg 已提交
521
			_, err := Run(t, exec.CommandContext(ctx, "docker", "build", "-t", img, dname))
522 523 524 525 526 527 528 529 530 531
			if err != nil {
				t.Errorf("failed to build docker image: %v", err)
			}

			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "add", img))
			if err != nil {
				t.Errorf("failed to add local image %q. args %q err %v", img, rr.Command(), err)
			}
		})

M
Medya Ghazizadeh 已提交
532
		t.Run("delete_busybox:1.28.4-glibc", func(t *testing.T) {
533
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "delete", "busybox:1.28.4-glibc"))
M
Medya Gh 已提交
534
			if err != nil {
M
Medya Gh 已提交
535
				t.Errorf("failed to delete image busybox:1.28.4-glibc from cache. args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
536 537
			}
		})
M
Medya Gh 已提交
538

M
Medya Gh 已提交
539 540 541
		t.Run("list", func(t *testing.T) {
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "list"))
			if err != nil {
M
Medya Gh 已提交
542
				t.Errorf("failed to do cache list. args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
543 544
			}
			if !strings.Contains(rr.Output(), "k8s.gcr.io/pause") {
M
Medya Gh 已提交
545
				t.Errorf("expected 'cache list' output to include 'k8s.gcr.io/pause' but got:\n ***%s***", rr.Output())
M
Medya Gh 已提交
546 547
			}
			if strings.Contains(rr.Output(), "busybox:1.28.4-glibc") {
M
Medya Gh 已提交
548
				t.Errorf("expected 'cache list' output not to include busybox:1.28.4-glibc but got:\n ***%s***", rr.Output())
M
Medya Gh 已提交
549 550
			}
		})
M
Medya Gh 已提交
551

M
Medya Ghazizadeh 已提交
552
		t.Run("verify_cache_inside_node", func(t *testing.T) {
M
Medya Gh 已提交
553
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "images"))
M
Medya Gh 已提交
554
			if err != nil {
M
Medya Gh 已提交
555
				t.Errorf("failed to get images by %q ssh %v", rr.Command(), err)
M
Medya Gh 已提交
556
			}
M
Medya Gh 已提交
557
			if !strings.Contains(rr.Output(), "1.28.4-glibc") {
M
Medya Gh 已提交
558
				t.Errorf("expected '1.28.4-glibc' to be in the output but got *%s*", rr.Output())
M
Medya Gh 已提交
559 560 561
			}

		})
M
Medya Gh 已提交
562

M
Medya Ghazizadeh 已提交
563
		t.Run("cache_reload", func(t *testing.T) { // deleting image inside minikube node manually and expecting reload to bring it back
M
Medya Gh 已提交
564 565
			img := "busybox:latest"
			// deleting image inside minikube node manually
M
Medya Gh 已提交
566
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "docker", "rmi", img))
M
Medya Gh 已提交
567

M
Medya Gh 已提交
568 569 570 571 572 573
			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 {
574
				t.Errorf("expected an error. because image should not exist. but got *nil error* ! cmd: %q", rr.Command())
M
Medya Gh 已提交
575
			}
M
Medya Gh 已提交
576
			// minikube cache reload.
M
Medya Gh 已提交
577 578
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "reload"))
			if err != nil {
579
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
580
			}
M
Medya Gh 已提交
581
			// make sure 'cache reload' brought back the manually deleted image.
M
Medya Gh 已提交
582 583
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "inspecti", img))
			if err != nil {
584
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
585 586 587
			}
		})

M
Medya Gh 已提交
588 589 590 591 592 593 594 595 596
		// delete will clean up the cached images since they are global and all other tests will load it for no reason
		t.Run("delete", func(t *testing.T) {
			for _, img := range []string{"busybox:latest", "k8s.gcr.io/pause:latest"} {
				rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "delete", img))
				if err != nil {
					t.Errorf("failed to delete %s from cache. args %q: %v", img, rr.Command(), err)
				}
			}
		})
M
Medya Gh 已提交
597
	})
598 599 600 601 602 603 604 605 606 607 608
}

// 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"},
T
Thomas Stromberg 已提交
609
		{[]string{"set", "cpus", "2"}, "", "! These changes will take effect upon a minikube delete and then a minikube start"},
610 611 612 613 614 615 616 617 618
		{[]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 == "" {
M
Medya Gh 已提交
619
			t.Errorf("failed to config minikube. args %q : %v", rr.Command(), err)
620 621 622 623
		}

		got := strings.TrimSpace(rr.Stdout.String())
		if got != tc.wantOut {
624
			t.Errorf("expected config output for %q to be -%q- but got *%q*", rr.Command(), tc.wantOut, got)
625 626 627
		}
		got = strings.TrimSpace(rr.Stderr.String())
		if got != tc.wantErr {
628
			t.Errorf("expected config error for %q to be -%q- but got *%q*", rr.Command(), tc.wantErr, got)
629 630 631 632 633 634 635 636
		}
	}
}

// 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 {
M
Medya Gh 已提交
637
		t.Errorf("%s failed: %v", rr.Command(), err)
638 639 640
	}
	for _, word := range []string{"Docker", "apiserver", "Linux", "kubelet"} {
		if !strings.Contains(rr.Stdout.String(), word) {
M
Medya Gh 已提交
641
			t.Errorf("excpeted minikube logs to include word: -%q- but got \n***%s***\n", word, rr.Output())
642 643 644 645
		}
	}
}

J
Josh Woodcock 已提交
646
// validateProfileCmd asserts "profile" command functionality
647
func validateProfileCmd(ctx context.Context, t *testing.T, profile string) {
M
Medya Gh 已提交
648 649 650 651 652
	t.Run("profile_not_create", func(t *testing.T) {
		// Profile command should not create a nonexistent profile
		nonexistentProfile := "lis"
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", nonexistentProfile))
		if err != nil {
M
Medya Gh 已提交
653
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
654 655 656
		}
		rr, err = Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "--output", "json"))
		if err != nil {
M
Medya Gh 已提交
657
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
658 659 660 661
		}
		var profileJSON map[string][]map[string]interface{}
		err = json.Unmarshal(rr.Stdout.Bytes(), &profileJSON)
		if err != nil {
M
Medya Gh 已提交
662
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
663 664 665 666 667 668 669
		}
		for profileK := range profileJSON {
			for _, p := range profileJSON[profileK] {
				var name = p["Name"]
				if name == nonexistentProfile {
					t.Errorf("minikube profile %s should not exist", nonexistentProfile)
				}
670
			}
671
		}
M
Medya Gh 已提交
672
	})
673

M
Medya Gh 已提交
674 675
	t.Run("profile_list", func(t *testing.T) {
		// List profiles
M
lint  
Medya Gh 已提交
676
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list"))
M
Medya Gh 已提交
677
		if err != nil {
M
Medya Gh 已提交
678
			t.Errorf("failed to list profiles: args %q : %v", rr.Command(), err)
M
Medya Gh 已提交
679
		}
J
Josh Woodcock 已提交
680

M
Medya Gh 已提交
681 682 683 684 685 686 687 688 689 690 691
		// 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 {
M
Medya Gh 已提交
692
			t.Errorf("expected 'profile list' output to include %q but got *%q*. args: %q", profile, rr.Stdout.String(), rr.Command())
J
Josh Woodcock 已提交
693
		}
M
Medya Gh 已提交
694 695 696 697
	})

	t.Run("profile_json_output", func(t *testing.T) {
		// Json output
M
lint  
Medya Gh 已提交
698
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "--output", "json"))
M
Medya Gh 已提交
699
		if err != nil {
M
Medya Gh 已提交
700
			t.Errorf("failed to list profiles with json format. args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
701
		}
M
Medya Gh 已提交
702 703 704
		var jsonObject map[string][]map[string]interface{}
		err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
		if err != nil {
M
Medya Gh 已提交
705
			t.Errorf("failed to decode json from profile list: args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
706 707
		}
		validProfiles := jsonObject["valid"]
M
lint  
Medya Gh 已提交
708
		profileExists := false
M
Medya Gh 已提交
709 710 711 712 713 714 715
		for _, profileObject := range validProfiles {
			if profileObject["Name"] == profile {
				profileExists = true
				break
			}
		}
		if !profileExists {
M
Medya Gh 已提交
716
			t.Errorf("expected the json of 'profile list' to include %q but got *%q*. args: %q", profile, rr.Stdout.String(), rr.Command())
M
Medya Gh 已提交
717 718 719
		}

	})
720 721 722
}

// validateServiceCmd asserts basic "service" command functionality
723
func validateServiceCmd(ctx context.Context, t *testing.T, profile string) {
724 725
	defer PostMortemLogs(t, profile)

726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
	defer func() {
		if t.Failed() {
			t.Logf("service test failed - dumping debug information")

			rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "describe", "po", "hello-node"))
			if err != nil {
				t.Logf("%q failed: %v", rr.Command(), err)
			}
			t.Logf("hello-node pod describe:\n%s", rr.Stdout)

			rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "logs", "-l", "app=hello-node"))
			if err != nil {
				t.Logf("%q failed: %v", rr.Command(), err)
			}
			t.Logf("hello-node logs:\n%s", rr.Stdout)

			rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "describe", "svc", "hello-node"))
			if err != nil {
				t.Logf("%q failed: %v", rr.Command(), err)
			}
			t.Logf("hello-node svc describe:\n%s", rr.Stdout)
		}
	}()

750
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "create", "deployment", "hello-node", "--image=k8s.gcr.io/echoserver:1.4"))
751
	if err != nil {
M
Medya Gh 已提交
752
		t.Logf("%q failed: %v (may not be an error).", rr.Command(), err)
753
	}
754
	rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "expose", "deployment", "hello-node", "--type=NodePort", "--port=8080"))
755
	if err != nil {
M
Medya Gh 已提交
756
		t.Logf("%q failed: %v (may not be an error)", rr.Command(), err)
757 758
	}

759
	if _, err := PodWait(ctx, t, profile, "default", "app=hello-node", Minutes(10)); err != nil {
760
		t.Fatalf("failed waiting for hello-node pod: %v", err)
761 762
	}

763
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "list"))
764
	if err != nil {
M
Medya Gh 已提交
765
		t.Errorf("failed to do service list. args %q : %v", rr.Command(), err)
766
	}
767
	if !strings.Contains(rr.Stdout.String(), "hello-node") {
768
		t.Errorf("expected 'service list' to contain *hello-node* but got -%q-", rr.Stdout.String())
769 770
	}

771 772 773 774
	if NeedsPortForward() {
		t.Skipf("test is broken for port-forwarded drivers: https://github.com/kubernetes/minikube/issues/7383")
	}

T
Thomas Stromberg 已提交
775
	// Test --https --url mode
776
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "--namespace=default", "--https", "--url", "hello-node"))
777
	if err != nil {
M
Medya Gh 已提交
778
		t.Fatalf("failed to get service url. args %q : %v", rr.Command(), err)
779 780
	}
	if rr.Stderr.String() != "" {
M
Medya Gh 已提交
781
		t.Errorf("expected stderr to be empty but got *%q* . args %q", rr.Stderr, rr.Command())
782
	}
783

T
Thomas Stromberg 已提交
784
	endpoint := strings.TrimSpace(rr.Stdout.String())
785
	t.Logf("found endpoint: %s", endpoint)
T
Thomas Stromberg 已提交
786

787 788
	u, err := url.Parse(endpoint)
	if err != nil {
789
		t.Fatalf("failed to parse service url endpoint %q: %v", endpoint, err)
790 791
	}
	if u.Scheme != "https" {
792
		t.Errorf("expected scheme for %s to be 'https' but got %q", endpoint, u.Scheme)
793 794 795
	}

	// Test --format=IP
796
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "hello-node", "--url", "--format={{.IP}}"))
797
	if err != nil {
M
Medya Gh 已提交
798
		t.Errorf("failed to get service url with custom format. args %q: %v", rr.Command(), err)
799
	}
800
	if strings.TrimSpace(rr.Stdout.String()) != u.Hostname() {
M
Medya Gh 已提交
801
		t.Errorf("expected 'service --format={{.IP}}' output to be -%q- but got *%q* . args %q.", u.Hostname(), rr.Stdout.String(), rr.Command())
802 803
	}

804
	// Test a regular URL
805
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "hello-node", "--url"))
806
	if err != nil {
M
Medya Gh 已提交
807
		t.Errorf("failed to get service url. args: %q: %v", rr.Command(), err)
808
	}
809 810

	endpoint = strings.TrimSpace(rr.Stdout.String())
811 812
	t.Logf("found endpoint for hello-node: %s", endpoint)

813 814 815 816
	u, err = url.Parse(endpoint)
	if err != nil {
		t.Fatalf("failed to parse %q: %v", endpoint, err)
	}
817

818
	if u.Scheme != "http" {
M
lint  
Medya Gh 已提交
819
		t.Fatalf("expected scheme to be -%q- got scheme: *%q*", "http", u.Scheme)
820 821
	}

822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843
	t.Logf("Attempting to fetch %s ...", endpoint)

	fetch := func() error {
		resp, err := http.Get(endpoint)
		if err != nil {
			t.Logf("error fetching %s: %v", endpoint, err)
			return err
		}

		defer resp.Body.Close()

		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			t.Logf("error reading body from %s: %v", endpoint, err)
			return err
		}
		if resp.StatusCode != http.StatusOK {
			t.Logf("%s: unexpected status code %d - body:\n%s", endpoint, resp.StatusCode, body)
		} else {
			t.Logf("%s: success! body:\n%s", endpoint, body)
		}
		return nil
844
	}
845 846 847

	if err = retry.Expo(fetch, 1*time.Second, Seconds(30)); err != nil {
		t.Errorf("failed to fetch %s: %v", endpoint, err)
848 849 850
	}
}

851 852
// validateAddonsCmd asserts basic "addon" command functionality
func validateAddonsCmd(ctx context.Context, t *testing.T, profile string) {
853 854
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
855
	// Table output
856 857
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "addons", "list"))
	if err != nil {
M
Medya Gh 已提交
858
		t.Errorf("failed to do addon list: args %q : %v", rr.Command(), err)
859
	}
M
Medya Gh 已提交
860 861
	for _, a := range []string{"dashboard", "ingress", "ingress-dns"} {
		if !strings.Contains(rr.Output(), a) {
M
Medya Gh 已提交
862
			t.Errorf("expected 'addon list' output to include -%q- but got *%s*", a, rr.Output())
863 864
		}
	}
J
Josh Woodcock 已提交
865 866 867 868

	// Json output
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "addons", "list", "-o", "json"))
	if err != nil {
M
Medya Gh 已提交
869
		t.Errorf("failed to do addon list with json output. args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
870 871 872 873
	}
	var jsonObject map[string]interface{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
	if err != nil {
874
		t.Errorf("failed to decode addon list json output : %v", err)
J
Josh Woodcock 已提交
875
	}
876 877
}

878 879
// validateSSHCmd asserts basic "ssh" command functionality
func validateSSHCmd(ctx context.Context, t *testing.T, profile string) {
880
	defer PostMortemLogs(t, profile)
881 882 883
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}
M
Medya Gh 已提交
884 885
	mctx, cancel := context.WithTimeout(ctx, Minutes(1))
	defer cancel()
M
Medya Gh 已提交
886

S
Sharif Elgamal 已提交
887
	want := "hello"
M
Medya Gh 已提交
888

M
Medya Gh 已提交
889 890
	rr, err := Run(t, exec.CommandContext(mctx, Target(), "-p", profile, "ssh", "echo hello"))
	if mctx.Err() == context.DeadlineExceeded {
M
try pwd  
Medya Gh 已提交
891 892 893 894 895
		t.Errorf("failed to run command by deadline. exceeded timeout : %s", rr.Command())
	}
	if err != nil {
		t.Errorf("failed to run an ssh command. args %q : %v", rr.Command(), err)
	}
896
	// trailing whitespace differs between native and external SSH clients, so let's trim it and call it a day
S
Sharif Elgamal 已提交
897
	if strings.TrimSpace(rr.Stdout.String()) != want {
M
try pwd  
Medya Gh 已提交
898 899 900
		t.Errorf("expected minikube ssh command output to be -%q- but got *%q*. args %q", want, rr.Stdout.String(), rr.Command())
	}

M
Medya Gh 已提交
901 902
	// testing hostname as well because testing something like "minikube ssh echo" could be confusing
	// because it  is not clear if echo was run inside minikube on the powershell
M
Medya Gh 已提交
903
	// so better to test something inside minikube, that is meaningful per profile
M
Medya Gh 已提交
904
	// in this case /etc/hostname is same as the profile name
905
	want = profile
M
Medya Gh 已提交
906
	rr, err = Run(t, exec.CommandContext(mctx, Target(), "-p", profile, "ssh", "cat /etc/hostname"))
M
Medya Gh 已提交
907
	if mctx.Err() == context.DeadlineExceeded {
M
try pwd  
Medya Gh 已提交
908
		t.Errorf("failed to run command by deadline. exceeded timeout : %s", rr.Command())
M
Medya Gh 已提交
909 910
	}

911
	if err != nil {
M
Medya Gh 已提交
912
		t.Errorf("failed to run an ssh command. args %q : %v", rr.Command(), err)
913
	}
S
Sharif Elgamal 已提交
914
	// trailing whitespace differs between native and external SSH clients, so let's trim it and call it a day
S
Sharif Elgamal 已提交
915
	if strings.TrimSpace(rr.Stdout.String()) != want {
M
Medya Gh 已提交
916
		t.Errorf("expected minikube ssh command output to be -%q- but got *%q*. args %q", want, rr.Stdout.String(), rr.Command())
917 918 919
	}
}

920 921
// validateMySQL validates a minimalist MySQL deployment
func validateMySQL(ctx context.Context, t *testing.T, profile string) {
922 923
	defer PostMortemLogs(t, profile)

924 925
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "replace", "--force", "-f", filepath.Join(*testdataDir, "mysql.yaml")))
	if err != nil {
M
Medya Gh 已提交
926
		t.Fatalf("failed to kubectl replace mysql: args %q failed: %v", rr.Command(), err)
927 928
	}

929
	names, err := PodWait(ctx, t, profile, "default", "app=mysql", Minutes(10))
930
	if err != nil {
931
		t.Fatalf("failed waiting for mysql pod: %v", err)
932 933
	}

934 935 936 937
	// 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
938
	}
M
Medya Gh 已提交
939
	if err = retry.Expo(mysql, 1*time.Second, Minutes(5)); err != nil {
940
		t.Errorf("failed to exec 'mysql -ppassword -e show databases;': %v", err)
941 942 943
	}
}

944 945 946 947 948 949 950 951 952 953
// 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())
}

954 955 956 957 958 959 960 961 962 963
// testCert is name of the test certificate installed
func testCert() string {
	return fmt.Sprintf("%d.pem", os.Getpid())
}

// localTestCertPath is where the test file will be synced into the VM
func localTestCertPath() string {
	return filepath.Join(localpath.MiniPath(), "/certs", testCert())
}

964 965 966 967 968
// localEmptyCertPath is where the test file will be synced into the VM
func localEmptyCertPath() string {
	return filepath.Join(localpath.MiniPath(), "/certs", fmt.Sprintf("%d_empty.pem", os.Getpid()))
}

969 970
// Copy extra file into minikube home folder for file sync test
func setupFileSync(ctx context.Context, t *testing.T, profile string) {
971 972
	p := localSyncTestPath()
	t.Logf("local sync path: %s", p)
973 974
	syncFile := filepath.Join(*testdataDir, "sync.test")
	err := copy.Copy(syncFile, p)
975
	if err != nil {
976
		t.Fatalf("failed to copy testdata/sync.test: %v", err)
977
	}
978

979
	testPem := filepath.Join(*testdataDir, "minikube_test.pem")
980

981 982 983
	// Write to a temp file for an atomic write
	tmpPem := localTestCertPath() + ".pem"
	if err := copy.Copy(testPem, tmpPem); err != nil {
984 985 986
		t.Fatalf("failed to copy %s: %v", testPem, err)
	}

987 988 989 990
	if err := os.Rename(tmpPem, localTestCertPath()); err != nil {
		t.Fatalf("failed to rename %s: %v", tmpPem, err)
	}

991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007
	want, err := os.Stat(testPem)
	if err != nil {
		t.Fatalf("stat failed: %v", err)
	}

	got, err := os.Stat(localTestCertPath())
	if err != nil {
		t.Fatalf("stat failed: %v", err)
	}

	if want.Size() != got.Size() {
		t.Errorf("%s size=%d, want %d", localTestCertPath(), got.Size(), want.Size())
	}

	// Create an empty file just to mess with people
	if _, err := os.Create(localEmptyCertPath()); err != nil {
		t.Fatalf("create failed: %v", err)
1008
	}
1009 1010 1011 1012
}

// validateFileSync to check existence of the test file
func validateFileSync(ctx context.Context, t *testing.T, profile string) {
1013 1014
	defer PostMortemLogs(t, profile)

1015 1016 1017
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}
1018 1019 1020

	vp := vmSyncTestPath()
	t.Logf("Checking for existence of %s within VM", vp)
1021
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("sudo cat %s", vp)))
1022
	if err != nil {
M
Medya Gh 已提交
1023
		t.Errorf("%s failed: %v", rr.Command(), err)
1024
	}
1025 1026
	got := rr.Stdout.String()
	t.Logf("file sync test content: %s", got)
1027

1028 1029
	syncFile := filepath.Join(*testdataDir, "sync.test")
	expected, err := ioutil.ReadFile(syncFile)
1030
	if err != nil {
1031
		t.Errorf("failed to read test file 'testdata/sync.test' : %v", err)
1032 1033
	}

1034
	if diff := cmp.Diff(string(expected), got); diff != "" {
1035 1036 1037 1038
		t.Errorf("/etc/sync.test content mismatch (-want +got):\n%s", diff)
	}
}

1039 1040
// validateCertSync to check existence of the test certificate
func validateCertSync(ctx context.Context, t *testing.T, profile string) {
1041 1042
	defer PostMortemLogs(t, profile)

1043 1044 1045 1046
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}

1047 1048
	testPem := filepath.Join(*testdataDir, "minikube_test.pem")
	want, err := ioutil.ReadFile(testPem)
1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061
	if err != nil {
		t.Errorf("test file not found: %v", err)
	}

	// Check both the installed & reference certs (they should be symlinked)
	paths := []string{
		path.Join("/etc/ssl/certs", testCert()),
		path.Join("/usr/share/ca-certificates", testCert()),
		// hashed path generated by: 'openssl x509 -hash -noout -in testCert()'
		"/etc/ssl/certs/51391683.0",
	}
	for _, vp := range paths {
		t.Logf("Checking for existence of %s within VM", vp)
1062
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("sudo cat %s", vp)))
1063
		if err != nil {
M
Medya Gh 已提交
1064
			t.Errorf("failed to check existence of %q inside minikube. args %q: %v", vp, rr.Command(), err)
1065 1066 1067 1068 1069
		}

		// Strip carriage returned by ssh
		got := strings.Replace(rr.Stdout.String(), "\r", "", -1)
		if diff := cmp.Diff(string(want), got); diff != "" {
1070
			t.Errorf("failed verify pem file. minikube_test.pem -> %s mismatch (-want +got):\n%s", vp, diff)
1071 1072 1073 1074
		}
	}
}

1075 1076
// validateUpdateContextCmd asserts basic "update-context" command functionality
func validateUpdateContextCmd(ctx context.Context, t *testing.T, profile string) {
1077 1078
	defer PostMortemLogs(t, profile)

K
Kazuki Suda 已提交
1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125
	tests := []struct {
		name       string
		kubeconfig []byte
		want       []byte
	}{
		{
			name:       "no changes",
			kubeconfig: nil,
			want:       []byte("No changes"),
		},
		{
			name: "no minikube cluster",
			kubeconfig: []byte(`
apiVersion: v1
clusters:
- cluster:
    certificate-authority: /home/la-croix/apiserver.crt
    server: 192.168.1.1:8080
  name: la-croix
contexts:
- context:
    cluster: la-croix
    user: la-croix
  name: la-croix
current-context: la-croix
kind: Config
preferences: {}
users:
- name: la-croix
  user:
    client-certificate: /home/la-croix/apiserver.crt
    client-key: /home/la-croix/apiserver.key
`),
			want: []byte("context has been updated"),
		},
		{
			name: "no clusters",
			kubeconfig: []byte(`
apiVersion: v1
clusters:
contexts:
kind: Config
preferences: {}
users:
`),
			want: []byte("context has been updated"),
		},
1126 1127
	}

K
Kazuki Suda 已提交
1128 1129
	for _, tc := range tests {
		tc := tc
1130 1131 1132 1133 1134

		if ctx.Err() == context.DeadlineExceeded {
			t.Fatalf("Unable to run more tests (deadline exceeded)")
		}

K
Kazuki Suda 已提交
1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			c := exec.CommandContext(ctx, Target(), "-p", profile, "update-context", "--alsologtostderr", "-v=2")
			if tc.kubeconfig != nil {
				tf, err := ioutil.TempFile("", "kubeconfig")
				if err != nil {
					t.Fatal(err)
				}

				if err := ioutil.WriteFile(tf.Name(), tc.kubeconfig, 0644); err != nil {
					t.Fatal(err)
				}

				t.Cleanup(func() {
					os.Remove(tf.Name())
				})

				c.Env = append(os.Environ(), fmt.Sprintf("KUBECONFIG=%s", tf.Name()))
			}

			rr, err := Run(t, c)
			if err != nil {
				t.Errorf("failed to run minikube update-context: args %q: %v", rr.Command(), err)
			}

			if !bytes.Contains(rr.Stdout.Bytes(), tc.want) {
				t.Errorf("update-context: got=%q, want=*%q*", rr.Stdout.Bytes(), tc.want)
			}
		})
1164 1165 1166
	}
}

1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182
// 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
1183
}