functional_test.go 37.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"
M
lint  
Medya Gh 已提交
43
	"k8s.io/minikube/pkg/util/retry"
44

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

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

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

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

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

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

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
		}{
82 83
			{"CopySyncFile", setupFileSync},                 // Set file for the file sync test case
			{"StartWithProxy", validateStartWithProxy},      // Set everything else up for success
M
Medya Gh 已提交
84
			{"SoftStart", validateSoftStart},                // do a soft start. ensure config didnt change.
85 86 87 88
			{"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
89 90 91 92
		}
		for _, tc := range tests {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
M
lint  
Medya Gh 已提交
93
				tc.validator(ctx, t, profile)
94
			})
M
Medya Gh 已提交
95
		}
96
	})
M
Medya Gh 已提交
97

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

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

M
Medya Gh 已提交
142
	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 已提交
143
	if err != nil {
M
Medya Gh 已提交
144
		t.Errorf("failed to 'kubectl get nodes' with args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
145 146 147
	}
	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 已提交
148
		if !strings.Contains(rr.Output(), el) {
M
Medya Gh 已提交
149
			t.Errorf("expected to have label %q in node labels but got : %s", el, rr.Output())
M
Medya Gh 已提交
150 151 152 153
		}
	}
}

154 155
// check functionality of minikube after evaling docker-env
func validateDockerEnv(ctx context.Context, t *testing.T, profile string) {
156
	defer PostMortemLogs(t, profile)
M
Medya Gh 已提交
157
	mctx, cancel := context.WithTimeout(ctx, Seconds(30))
158
	defer cancel()
M
Medya Gh 已提交
159 160
	var rr *RunResult
	var err error
M
Medya Gh 已提交
161
	if runtime.GOOS == "windows" {
M
Medya Gh 已提交
162 163
		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 已提交
164 165 166 167 168
	} 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 已提交
169 170
	if mctx.Err() == context.DeadlineExceeded {
		t.Errorf("failed to run the command by deadline. exceeded timeout. %s", rr.Command())
M
Medya Gh 已提交
171
	}
172
	if err != nil {
M
Medya Gh 已提交
173
		t.Fatalf("failed to do status after eval-ing docker-env. error: %v", err)
174 175
	}
	if !strings.Contains(rr.Output(), "Running") {
M
Medya Gh 已提交
176
		t.Fatalf("expected status output to include 'Running' after eval docker-env but got: *%s*", rr.Output())
177 178
	}

M
Medya Gh 已提交
179
	mctx, cancel = context.WithTimeout(ctx, Seconds(30))
180 181
	defer cancel()
	// do a eval $(minikube -p profile docker-env) and check if we are point to docker inside minikube
M
Medya Gh 已提交
182
	if runtime.GOOS == "windows" { // testing docker-env eval in powershell
M
Medya Gh 已提交
183 184
		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 已提交
185
	} else {
M
Medya Gh 已提交
186
		c := exec.CommandContext(mctx, "/bin/bash", "-c", "eval $("+Target()+" -p "+profile+" docker-env) && docker images")
M
Medya Gh 已提交
187 188 189
		rr, err = Run(t, c)
	}

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

194
	if err != nil {
M
Medya Gh 已提交
195
		t.Fatalf("failed to run minikube docker-env. args %q : %v ", rr.Command(), err)
196 197 198 199
	}

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

}

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

208 209
	srv, err := startHTTPProxy(t)
	if err != nil {
210
		t.Fatalf("failed to set up the test proxy: %s", err)
211
	}
212 213

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

	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 已提交
237
// validateSoftStart validates that after minikube already started, a "minikube start" should not change the configs.
M
lint  
Medya Gh 已提交
238
func validateSoftStart(ctx context.Context, t *testing.T, profile string) {
239 240
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
241
	start := time.Now()
M
Medya Gh 已提交
242
	// the test before this had been start with --apiserver-port=8441
M
lint  
Medya Gh 已提交
243 244 245 246
	beforeCfg, err := config.LoadProfile(profile)
	if err != nil {
		t.Errorf("error reading cluster config before soft start: %v", err)
	}
M
Medya Gh 已提交
247 248
	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 已提交
249 250
	}

M
lint  
Medya Gh 已提交
251
	softStartArgs := []string{"start", "-p", profile}
M
Medya Gh 已提交
252
	c := exec.CommandContext(ctx, Target(), softStartArgs...)
M
lint  
Medya Gh 已提交
253
	rr, err := Run(t, c)
M
Medya Gh 已提交
254 255 256
	if err != nil {
		t.Errorf("failed to soft start minikube. args %q: %v", rr.Command(), err)
	}
M
Medya Gh 已提交
257
	t.Logf("soft start took %s for %q cluster.", time.Since(start), profile)
M
Medya Gh 已提交
258

M
lint  
Medya Gh 已提交
259 260 261 262 263
	afterCfg, err := config.LoadProfile(profile)
	if err != nil {
		t.Errorf("error reading cluster config after soft start: %v", err)
	}

M
Medya Gh 已提交
264 265
	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 已提交
266
	}
M
Medya Gh 已提交
267

M
Medya Gh 已提交
268 269
}

270 271
// validateKubeContext asserts that kubectl is properly configured (race-condition prone!)
func validateKubeContext(ctx context.Context, t *testing.T, profile string) {
272 273
	defer PostMortemLogs(t, profile)

274 275
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "config", "current-context"))
	if err != nil {
M
Medya Gh 已提交
276
		t.Errorf("failed to get current-context. args %q : %v", rr.Command(), err)
277 278
	}
	if !strings.Contains(rr.Stdout.String(), profile) {
279
		t.Errorf("expected current-context = %q, but got *%q*", profile, rr.Stdout.String())
280 281 282
	}
}

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

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

299 300
// validateMinikubeKubectl validates that the `minikube kubectl` command returns content
func validateMinikubeKubectl(ctx context.Context, t *testing.T, profile string) {
301 302
	defer PostMortemLogs(t, profile)

303 304
	// 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 已提交
305
	rr, err := Run(t, exec.CommandContext(ctx, Target(), kubectlArgs...))
306
	if err != nil {
M
Medya Gh 已提交
307
		t.Fatalf("failed to get pods. args %q: %v", rr.Command(), err)
308 309 310
	}
}

311 312
// validateComponentHealth asserts that all Kubernetes components are healthy
func validateComponentHealth(ctx context.Context, t *testing.T, profile string) {
313 314
	defer PostMortemLogs(t, profile)

315 316
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "cs", "-o=json"))
	if err != nil {
M
Medya Gh 已提交
317
		t.Fatalf("failed to get components. args %q: %v", rr.Command(), err)
318 319 320 321
	}
	cs := api.ComponentStatusList{}
	d := json.NewDecoder(bytes.NewReader(rr.Stdout.Bytes()))
	if err := d.Decode(&cs); err != nil {
M
Medya Gh 已提交
322
		t.Fatalf("failed to decode kubectl json output: args %q : %v", rr.Command(), err)
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338
	}

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

J
Josh Woodcock 已提交
339
func validateStatusCmd(ctx context.Context, t *testing.T, profile string) {
340
	defer PostMortemLogs(t, profile)
341
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status"))
J
Josh Woodcock 已提交
342
	if err != nil {
M
Medya Gh 已提交
343
		t.Errorf("failed to run minikube status. args %q : %v", rr.Command(), err)
J
Josh Woodcock 已提交
344 345 346
	}

	// Custom format
347
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-f", "host:{{.Host}},kublet:{{.Kubelet}},apiserver:{{.APIServer}},kubeconfig:{{.Kubeconfig}}"))
J
Josh Woodcock 已提交
348
	if err != nil {
M
Medya Gh 已提交
349
		t.Errorf("failed to run minikube status with custom format: args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
350
	}
351 352
	re := `host:([A-z]+),kublet:([A-z]+),apiserver:([A-z]+),kubeconfig:([A-z]+)`
	match, _ := regexp.MatchString(re, rr.Stdout.String())
J
Josh Woodcock 已提交
353
	if !match {
M
Medya Gh 已提交
354
		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 已提交
355 356 357
	}

	// Json output
358
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "status", "-o", "json"))
J
Josh Woodcock 已提交
359
	if err != nil {
M
Medya Gh 已提交
360
		t.Errorf("failed to run minikube status with json output. args %q : %v", rr.Command(), err)
J
Josh Woodcock 已提交
361 362 363 364
	}
	var jsonObject map[string]interface{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
	if err != nil {
M
Medya Gh 已提交
365
		t.Errorf("failed to decode json from minikube status. args %q. %v", rr.Command(), err)
J
Josh Woodcock 已提交
366 367
	}
	if _, ok := jsonObject["Host"]; !ok {
M
Medya Gh 已提交
368
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Host")
J
Josh Woodcock 已提交
369 370
	}
	if _, ok := jsonObject["Kubelet"]; !ok {
M
Medya Gh 已提交
371
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Kubelet")
J
Josh Woodcock 已提交
372 373
	}
	if _, ok := jsonObject["APIServer"]; !ok {
M
Medya Gh 已提交
374
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "APIServer")
J
Josh Woodcock 已提交
375 376
	}
	if _, ok := jsonObject["Kubeconfig"]; !ok {
M
Medya Gh 已提交
377
		t.Errorf("%q failed: %v. Missing key %s in json object", rr.Command(), err, "Kubeconfig")
J
Josh Woodcock 已提交
378 379 380
	}
}

381 382
// validateDashboardCmd asserts that the dashboard command works
func validateDashboardCmd(ctx context.Context, t *testing.T, profile string) {
383 384
	defer PostMortemLogs(t, profile)

385 386 387
	args := []string{"dashboard", "--url", "-p", profile, "--alsologtostderr", "-v=1"}
	ss, err := Start(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
388
		t.Errorf("failed to run minikube dashboard. args %q : %v", args, err)
389 390 391 392 393 394
	}
	defer func() {
		ss.Stop(t)
	}()

	start := time.Now()
M
Medya Gh 已提交
395
	s, err := ReadLineWithTimeout(ss.Stdout, Seconds(300))
396
	if err != nil {
T
tstromberg 已提交
397 398 399 400
		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)
401 402 403 404 405 406 407 408 409
	}

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

413 414 415
	if resp.StatusCode != http.StatusOK {
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
416
			t.Errorf("failed to read http response body from dashboard %q: %v", u.String(), err)
417 418 419 420
		}
		t.Errorf("%s returned status code %d, expected %d.\nbody:\n%s", u, resp.StatusCode, http.StatusOK, body)
	}
}
M
Medya Gh 已提交
421

422 423
// validateDNS asserts that all Kubernetes DNS is healthy
func validateDNS(ctx context.Context, t *testing.T, profile string) {
424 425
	defer PostMortemLogs(t, profile)

426 427
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "replace", "--force", "-f", filepath.Join(*testdataDir, "busybox.yaml")))
	if err != nil {
M
Medya Gh 已提交
428
		t.Fatalf("failed to kubectl replace busybox : args %q: %v", rr.Command(), err)
429 430
	}

M
typo  
Medya Gh 已提交
431
	names, err := PodWait(ctx, t, profile, "default", "integration-test=busybox", Minutes(4))
432
	if err != nil {
433
		t.Fatalf("failed waiting for busybox pod : %v", err)
434 435
	}

436 437 438 439 440 441
	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.
442
	if err = retry.Expo(nslookup, 1*time.Second, Minutes(1)); err != nil {
443
		t.Errorf("failed to do nslookup on kubernetes.default: %v", err)
444 445 446 447
	}

	want := []byte("10.96.0.1")
	if !bytes.Contains(rr.Stdout.Bytes(), want) {
448
		t.Errorf("failed nslookup: got=%q, want=*%q*", rr.Stdout.Bytes(), want)
449 450 451
	}
}

T
Thomas Stromberg 已提交
452 453
// validateDryRun asserts that the dry-run mode quickly exits with the right code
func validateDryRun(ctx context.Context, t *testing.T, profile string) {
454
	// dry-run mode should always be able to finish quickly (<5s)
M
Medya Gh 已提交
455
	mctx, cancel := context.WithTimeout(ctx, Seconds(5))
T
Thomas Stromberg 已提交
456 457 458
	defer cancel()

	// Too little memory!
459
	startArgs := append([]string{"start", "-p", profile, "--dry-run", "--memory", "250MB", "--alsologtostderr"}, StartArgs()...)
T
Thomas Stromberg 已提交
460 461 462 463 464 465 466 467
	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)
	}

M
Medya Gh 已提交
468
	dctx, cancel := context.WithTimeout(ctx, Seconds(5))
T
Thomas Stromberg 已提交
469
	defer cancel()
470
	startArgs = append([]string{"start", "-p", profile, "--dry-run", "--alsologtostderr", "-v=1"}, StartArgs()...)
T
Thomas Stromberg 已提交
471 472 473 474 475 476 477
	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 已提交
478
// validateCacheCmd tests functionality of cache command (cache add, delete, list)
479
func validateCacheCmd(ctx context.Context, t *testing.T, profile string) {
480 481
	defer PostMortemLogs(t, profile)

482 483 484
	if NoneDriver() {
		t.Skipf("skipping: cache unsupported by none")
	}
M
Medya Gh 已提交
485 486
	t.Run("cache", func(t *testing.T) {
		t.Run("add", func(t *testing.T) {
487
			for _, img := range []string{"busybox:latest", "busybox:1.28.4-glibc", "k8s.gcr.io/pause:latest"} {
488
				rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "add", img))
M
Medya Gh 已提交
489
				if err != nil {
M
Medya Gh 已提交
490
					t.Errorf("failed to cache add image %q. args %q err %v", img, rr.Command(), err)
M
Medya Gh 已提交
491 492 493
				}
			}
		})
M
Medya Ghazizadeh 已提交
494
		t.Run("delete_busybox:1.28.4-glibc", func(t *testing.T) {
495
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "delete", "busybox:1.28.4-glibc"))
M
Medya Gh 已提交
496
			if err != nil {
M
Medya Gh 已提交
497
				t.Errorf("failed to delete image busybox:1.28.4-glibc from cache. args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
498 499
			}
		})
M
Medya Gh 已提交
500

M
Medya Gh 已提交
501 502 503
		t.Run("list", func(t *testing.T) {
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "cache", "list"))
			if err != nil {
M
Medya Gh 已提交
504
				t.Errorf("failed to do cache list. args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
505 506
			}
			if !strings.Contains(rr.Output(), "k8s.gcr.io/pause") {
M
Medya Gh 已提交
507
				t.Errorf("expected 'cache list' output to include 'k8s.gcr.io/pause' but got:\n ***%s***", rr.Output())
M
Medya Gh 已提交
508 509
			}
			if strings.Contains(rr.Output(), "busybox:1.28.4-glibc") {
M
Medya Gh 已提交
510
				t.Errorf("expected 'cache list' output not to include busybox:1.28.4-glibc but got:\n ***%s***", rr.Output())
M
Medya Gh 已提交
511 512
			}
		})
M
Medya Gh 已提交
513

M
Medya Ghazizadeh 已提交
514
		t.Run("verify_cache_inside_node", func(t *testing.T) {
M
Medya Gh 已提交
515
			rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "images"))
M
Medya Gh 已提交
516
			if err != nil {
M
Medya Gh 已提交
517
				t.Errorf("failed to get images by %q ssh %v", rr.Command(), err)
M
Medya Gh 已提交
518
			}
M
Medya Gh 已提交
519
			if !strings.Contains(rr.Output(), "1.28.4-glibc") {
M
Medya Gh 已提交
520
				t.Errorf("expected '1.28.4-glibc' to be in the output but got *%s*", rr.Output())
M
Medya Gh 已提交
521 522 523
			}

		})
M
Medya Gh 已提交
524

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

M
Medya Gh 已提交
530 531 532 533 534 535
			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 {
536
				t.Errorf("expected an error. because image should not exist. but got *nil error* ! cmd: %q", rr.Command())
M
Medya Gh 已提交
537
			}
M
Medya Gh 已提交
538
			// minikube cache reload.
M
Medya Gh 已提交
539 540
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "cache", "reload"))
			if err != nil {
541
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
542
			}
M
Medya Gh 已提交
543
			// make sure 'cache reload' brought back the manually deleted image.
M
Medya Gh 已提交
544 545
			rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", "sudo", "crictl", "inspecti", img))
			if err != nil {
546
				t.Errorf("expected %q to run successfully but got error: %v", rr.Command(), err)
M
Medya Gh 已提交
547 548 549
			}
		})

M
Medya Gh 已提交
550
	})
551 552 553 554 555 556 557 558 559 560 561
}

// 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 已提交
562
		{[]string{"set", "cpus", "2"}, "", "! These changes will take effect upon a minikube delete and then a minikube start"},
563 564 565 566 567 568 569 570 571
		{[]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 已提交
572
			t.Errorf("failed to config minikube. args %q : %v", rr.Command(), err)
573 574 575 576
		}

		got := strings.TrimSpace(rr.Stdout.String())
		if got != tc.wantOut {
577
			t.Errorf("expected config output for %q to be -%q- but got *%q*", rr.Command(), tc.wantOut, got)
578 579 580
		}
		got = strings.TrimSpace(rr.Stderr.String())
		if got != tc.wantErr {
581
			t.Errorf("expected config error for %q to be -%q- but got *%q*", rr.Command(), tc.wantErr, got)
582 583 584 585 586 587 588 589
		}
	}
}

// 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 已提交
590
		t.Errorf("%s failed: %v", rr.Command(), err)
591 592 593
	}
	for _, word := range []string{"Docker", "apiserver", "Linux", "kubelet"} {
		if !strings.Contains(rr.Stdout.String(), word) {
M
Medya Gh 已提交
594
			t.Errorf("excpeted minikube logs to include word: -%q- but got \n***%s***\n", word, rr.Output())
595 596 597 598
		}
	}
}

J
Josh Woodcock 已提交
599
// validateProfileCmd asserts "profile" command functionality
600
func validateProfileCmd(ctx context.Context, t *testing.T, profile string) {
M
Medya Gh 已提交
601 602 603 604 605
	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 已提交
606
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
607 608 609
		}
		rr, err = Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "--output", "json"))
		if err != nil {
M
Medya Gh 已提交
610
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
611 612 613 614
		}
		var profileJSON map[string][]map[string]interface{}
		err = json.Unmarshal(rr.Stdout.Bytes(), &profileJSON)
		if err != nil {
M
Medya Gh 已提交
615
			t.Errorf("%s failed: %v", rr.Command(), err)
M
Medya Gh 已提交
616 617 618 619 620 621 622
		}
		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)
				}
623
			}
624
		}
M
Medya Gh 已提交
625
	})
626

M
Medya Gh 已提交
627 628
	t.Run("profile_list", func(t *testing.T) {
		// List profiles
M
lint  
Medya Gh 已提交
629
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list"))
M
Medya Gh 已提交
630
		if err != nil {
M
Medya Gh 已提交
631
			t.Errorf("failed to list profiles: args %q : %v", rr.Command(), err)
M
Medya Gh 已提交
632
		}
J
Josh Woodcock 已提交
633

M
Medya Gh 已提交
634 635 636 637 638 639 640 641 642 643 644
		// 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 已提交
645
			t.Errorf("expected 'profile list' output to include %q but got *%q*. args: %q", profile, rr.Stdout.String(), rr.Command())
J
Josh Woodcock 已提交
646
		}
M
Medya Gh 已提交
647 648 649 650
	})

	t.Run("profile_json_output", func(t *testing.T) {
		// Json output
M
lint  
Medya Gh 已提交
651
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "--output", "json"))
M
Medya Gh 已提交
652
		if err != nil {
M
Medya Gh 已提交
653
			t.Errorf("failed to list profiles with json format. args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
654
		}
M
Medya Gh 已提交
655 656 657
		var jsonObject map[string][]map[string]interface{}
		err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
		if err != nil {
M
Medya Gh 已提交
658
			t.Errorf("failed to decode json from profile list: args %q: %v", rr.Command(), err)
M
Medya Gh 已提交
659 660
		}
		validProfiles := jsonObject["valid"]
M
lint  
Medya Gh 已提交
661
		profileExists := false
M
Medya Gh 已提交
662 663 664 665 666 667 668
		for _, profileObject := range validProfiles {
			if profileObject["Name"] == profile {
				profileExists = true
				break
			}
		}
		if !profileExists {
M
Medya Gh 已提交
669
			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 已提交
670 671 672
		}

	})
673 674 675
}

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

679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702
	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)
		}
	}()

703
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "create", "deployment", "hello-node", "--image=k8s.gcr.io/echoserver:1.4"))
704
	if err != nil {
M
Medya Gh 已提交
705
		t.Logf("%q failed: %v (may not be an error).", rr.Command(), err)
706
	}
707
	rr, err = Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "expose", "deployment", "hello-node", "--type=NodePort", "--port=8080"))
708
	if err != nil {
M
Medya Gh 已提交
709
		t.Logf("%q failed: %v (may not be an error)", rr.Command(), err)
710 711
	}

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

716
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "list"))
717
	if err != nil {
M
Medya Gh 已提交
718
		t.Errorf("failed to do service list. args %q : %v", rr.Command(), err)
719
	}
720
	if !strings.Contains(rr.Stdout.String(), "hello-node") {
721
		t.Errorf("expected 'service list' to contain *hello-node* but got -%q-", rr.Stdout.String())
722 723
	}

724 725 726 727
	if NeedsPortForward() {
		t.Skipf("test is broken for port-forwarded drivers: https://github.com/kubernetes/minikube/issues/7383")
	}

T
Thomas Stromberg 已提交
728
	// Test --https --url mode
729
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "--namespace=default", "--https", "--url", "hello-node"))
730
	if err != nil {
M
Medya Gh 已提交
731
		t.Fatalf("failed to get service url. args %q : %v", rr.Command(), err)
732 733
	}
	if rr.Stderr.String() != "" {
M
Medya Gh 已提交
734
		t.Errorf("expected stderr to be empty but got *%q* . args %q", rr.Stderr, rr.Command())
735
	}
736

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

740 741
	u, err := url.Parse(endpoint)
	if err != nil {
742
		t.Fatalf("failed to parse service url endpoint %q: %v", endpoint, err)
743 744
	}
	if u.Scheme != "https" {
745
		t.Errorf("expected scheme for %s to be 'https' but got %q", endpoint, u.Scheme)
746 747 748
	}

	// Test --format=IP
749
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "hello-node", "--url", "--format={{.IP}}"))
750
	if err != nil {
M
Medya Gh 已提交
751
		t.Errorf("failed to get service url with custom format. args %q: %v", rr.Command(), err)
752
	}
753
	if strings.TrimSpace(rr.Stdout.String()) != u.Hostname() {
M
Medya Gh 已提交
754
		t.Errorf("expected 'service --format={{.IP}}' output to be -%q- but got *%q* . args %q.", u.Hostname(), rr.Stdout.String(), rr.Command())
755 756
	}

757
	// Test a regular URL
758
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "service", "hello-node", "--url"))
759
	if err != nil {
M
Medya Gh 已提交
760
		t.Errorf("failed to get service url. args: %q: %v", rr.Command(), err)
761
	}
762 763

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

766 767 768 769
	u, err = url.Parse(endpoint)
	if err != nil {
		t.Fatalf("failed to parse %q: %v", endpoint, err)
	}
770

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

775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
	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
797
	}
798 799 800

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

804 805
// validateAddonsCmd asserts basic "addon" command functionality
func validateAddonsCmd(ctx context.Context, t *testing.T, profile string) {
806 807
	defer PostMortemLogs(t, profile)

M
Medya Gh 已提交
808
	// Table output
809 810
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "addons", "list"))
	if err != nil {
M
Medya Gh 已提交
811
		t.Errorf("failed to do addon list: args %q : %v", rr.Command(), err)
812
	}
M
Medya Gh 已提交
813 814
	for _, a := range []string{"dashboard", "ingress", "ingress-dns"} {
		if !strings.Contains(rr.Output(), a) {
M
Medya Gh 已提交
815
			t.Errorf("expected 'addon list' output to include -%q- but got *%s*", a, rr.Output())
816 817
		}
	}
J
Josh Woodcock 已提交
818 819 820 821

	// Json output
	rr, err = Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "addons", "list", "-o", "json"))
	if err != nil {
M
Medya Gh 已提交
822
		t.Errorf("failed to do addon list with json output. args %q: %v", rr.Command(), err)
J
Josh Woodcock 已提交
823 824 825 826
	}
	var jsonObject map[string]interface{}
	err = json.Unmarshal(rr.Stdout.Bytes(), &jsonObject)
	if err != nil {
827
		t.Errorf("failed to decode addon list json output : %v", err)
J
Josh Woodcock 已提交
828
	}
829 830
}

831 832
// validateSSHCmd asserts basic "ssh" command functionality
func validateSSHCmd(ctx context.Context, t *testing.T, profile string) {
833
	defer PostMortemLogs(t, profile)
834 835 836
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}
M
Medya Gh 已提交
837 838
	mctx, cancel := context.WithTimeout(ctx, Minutes(1))
	defer cancel()
M
Medya Gh 已提交
839

S
Sharif Elgamal 已提交
840
	want := "hello"
M
Medya Gh 已提交
841

M
Medya Gh 已提交
842 843
	rr, err := Run(t, exec.CommandContext(mctx, Target(), "-p", profile, "ssh", "echo hello"))
	if mctx.Err() == context.DeadlineExceeded {
M
try pwd  
Medya Gh 已提交
844 845 846 847 848
		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)
	}
849
	// trailing whitespace differs between native and external SSH clients, so let's trim it and call it a day
S
Sharif Elgamal 已提交
850
	if strings.TrimSpace(rr.Stdout.String()) != want {
M
try pwd  
Medya Gh 已提交
851 852 853
		t.Errorf("expected minikube ssh command output to be -%q- but got *%q*. args %q", want, rr.Stdout.String(), rr.Command())
	}

M
Medya Gh 已提交
854 855
	// 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 已提交
856
	// so better to test something inside minikube, that is meaningful per profile
M
Medya Gh 已提交
857
	// in this case /etc/hostname is same as the profile name
858
	want = profile
M
Medya Gh 已提交
859
	rr, err = Run(t, exec.CommandContext(mctx, Target(), "-p", profile, "ssh", "cat /etc/hostname"))
M
Medya Gh 已提交
860
	if mctx.Err() == context.DeadlineExceeded {
M
try pwd  
Medya Gh 已提交
861
		t.Errorf("failed to run command by deadline. exceeded timeout : %s", rr.Command())
M
Medya Gh 已提交
862 863
	}

864
	if err != nil {
M
Medya Gh 已提交
865
		t.Errorf("failed to run an ssh command. args %q : %v", rr.Command(), err)
866
	}
S
Sharif Elgamal 已提交
867
	// trailing whitespace differs between native and external SSH clients, so let's trim it and call it a day
S
Sharif Elgamal 已提交
868
	if strings.TrimSpace(rr.Stdout.String()) != want {
M
Medya Gh 已提交
869
		t.Errorf("expected minikube ssh command output to be -%q- but got *%q*. args %q", want, rr.Stdout.String(), rr.Command())
870 871 872
	}
}

873 874
// validateMySQL validates a minimalist MySQL deployment
func validateMySQL(ctx context.Context, t *testing.T, profile string) {
875 876
	defer PostMortemLogs(t, profile)

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

882
	names, err := PodWait(ctx, t, profile, "default", "app=mysql", Minutes(10))
883
	if err != nil {
884
		t.Fatalf("failed waiting for mysql pod: %v", err)
885 886
	}

887 888 889 890
	// 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
891
	}
M
Medya Gh 已提交
892
	if err = retry.Expo(mysql, 1*time.Second, Minutes(5)); err != nil {
893
		t.Errorf("failed to exec 'mysql -ppassword -e show databases;': %v", err)
894 895 896
	}
}

897 898 899 900 901 902 903 904 905 906
// 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())
}

907 908 909 910 911 912 913 914 915 916
// 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())
}

917 918 919 920 921
// 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()))
}

922 923
// Copy extra file into minikube home folder for file sync test
func setupFileSync(ctx context.Context, t *testing.T, profile string) {
924 925 926
	p := localSyncTestPath()
	t.Logf("local sync path: %s", p)
	err := copy.Copy("./testdata/sync.test", p)
927
	if err != nil {
928
		t.Fatalf("failed to copy ./testdata/sync.test: %v", err)
929
	}
930

931 932
	testPem := "./testdata/minikube_test.pem"

933 934 935
	// Write to a temp file for an atomic write
	tmpPem := localTestCertPath() + ".pem"
	if err := copy.Copy(testPem, tmpPem); err != nil {
936 937 938
		t.Fatalf("failed to copy %s: %v", testPem, err)
	}

939 940 941 942
	if err := os.Rename(tmpPem, localTestCertPath()); err != nil {
		t.Fatalf("failed to rename %s: %v", tmpPem, err)
	}

943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959
	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)
960
	}
961 962 963 964
}

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

967 968 969
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}
970 971 972

	vp := vmSyncTestPath()
	t.Logf("Checking for existence of %s within VM", vp)
973
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("sudo cat %s", vp)))
974
	if err != nil {
M
Medya Gh 已提交
975
		t.Errorf("%s failed: %v", rr.Command(), err)
976
	}
977 978
	got := rr.Stdout.String()
	t.Logf("file sync test content: %s", got)
979 980 981

	expected, err := ioutil.ReadFile("./testdata/sync.test")
	if err != nil {
982
		t.Errorf("failed to read test file '/testdata/sync.test' : %v", err)
983 984
	}

985
	if diff := cmp.Diff(string(expected), got); diff != "" {
986 987 988 989
		t.Errorf("/etc/sync.test content mismatch (-want +got):\n%s", diff)
	}
}

990 991
// validateCertSync to check existence of the test certificate
func validateCertSync(ctx context.Context, t *testing.T, profile string) {
992 993
	defer PostMortemLogs(t, profile)

994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011
	if NoneDriver() {
		t.Skipf("skipping: ssh unsupported by none")
	}

	want, err := ioutil.ReadFile("./testdata/minikube_test.pem")
	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)
1012
		rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "ssh", fmt.Sprintf("sudo cat %s", vp)))
1013
		if err != nil {
M
Medya Gh 已提交
1014
			t.Errorf("failed to check existence of %q inside minikube. args %q: %v", vp, rr.Command(), err)
1015 1016 1017 1018 1019
		}

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

1025 1026
// validateUpdateContextCmd asserts basic "update-context" command functionality
func validateUpdateContextCmd(ctx context.Context, t *testing.T, profile string) {
1027 1028
	defer PostMortemLogs(t, profile)

1029 1030
	rr, err := Run(t, exec.CommandContext(ctx, Target(), "-p", profile, "update-context", "--alsologtostderr", "-v=2"))
	if err != nil {
M
Medya Gh 已提交
1031
		t.Errorf("failed to run minikube update-context: args %q: %v", rr.Command(), err)
1032 1033
	}

T
Thomas Stromberg 已提交
1034
	want := []byte("No changes")
1035 1036 1037 1038 1039
	if !bytes.Contains(rr.Stdout.Bytes(), want) {
		t.Errorf("update-context: got=%q, want=*%q*", rr.Stdout.Bytes(), want)
	}
}

1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055
// 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
1056
}