提交 d4187cbd 编写于 作者: T Thomas Stromberg

Merge branch 'master' into v0.30.0.release

......@@ -282,10 +282,6 @@ install-hyperkit-driver: out/docker-machine-driver-hyperkit
check-release:
go test -v ./deploy/minikube/release_sanity_test.go -tags=release
.PHONY: update-releases
update-releases:
gsutil cp deploy/minikube/k8s_releases.json gs://minikube/k8s_releases.json
buildroot-image: $(ISO_BUILD_IMAGE) # convenient alias to build the docker container
$(ISO_BUILD_IMAGE): deploy/iso/minikube-iso/Dockerfile
docker build $(ISO_DOCKER_EXTRA_ARGS) -t $@ -f $< $(dir $<)
......
......@@ -17,68 +17,138 @@ limitations under the License.
package cmd
import (
"bufio"
"fmt"
"net/http"
"os"
"text/template"
"os/exec"
"regexp"
"time"
"github.com/golang/glog"
"github.com/pkg/browser"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/service"
commonutil "k8s.io/minikube/pkg/util"
"k8s.io/minikube/pkg/util"
)
var (
dashboardURLMode bool
// Matches: 127.0.0.1:8001
// TODO(tstromberg): Get kubectl to implement a stable supported output format.
hostPortRe = regexp.MustCompile(`127.0.0.1:\d{4,}`)
)
// dashboardCmd represents the dashboard command
var dashboardCmd = &cobra.Command{
Use: "dashboard",
Short: "Opens/displays the kubernetes dashboard URL for your local cluster",
Long: `Opens/displays the kubernetes dashboard URL for your local cluster`,
Short: "Access the kubernetes dashboard running within the minikube cluster",
Long: `Access the kubernetes dashboard running within the minikube cluster`,
Run: func(cmd *cobra.Command, args []string) {
api, err := machine.NewAPIClient()
defer func() {
err := api.Close()
if err != nil {
glog.Warningf("Failed to close API: %v", err)
}
}()
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting client: %s\n", err)
fmt.Fprintf(os.Stderr, "Error creating client: %v\n", err)
os.Exit(1)
}
defer api.Close()
cluster.EnsureMinikubeRunningOrExit(api, 1)
namespace := "kube-system"
svc := "kubernetes-dashboard"
if err = commonutil.RetryAfter(20, func() error { return service.CheckService(namespace, svc) }, 6*time.Second); err != nil {
fmt.Fprintf(os.Stderr, "Could not find finalized endpoint being pointed to by %s: %s\n", svc, err)
ns := "kube-system"
svc := "kubernetes-dashboard"
if err = util.RetryAfter(30, func() error { return service.CheckService(ns, svc) }, 1*time.Second); err != nil {
fmt.Fprintf(os.Stderr, "%s:%s is not running: %v\n", ns, svc, err)
os.Exit(1)
}
urls, err := service.GetServiceURLsForService(api, namespace, svc, template.Must(template.New("dashboardServiceFormat").Parse(defaultServiceFormatTemplate)))
p, hostPort, err := kubectlProxy()
if err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, "Check that minikube is running.")
os.Exit(1)
glog.Fatalf("kubectl proxy: %v", err)
}
if len(urls) == 0 {
errMsg := "There appears to be no url associated with dashboard, this is not expected, exiting"
glog.Infoln(errMsg)
url := dashboardURL(hostPort, ns, svc)
if err = util.RetryAfter(60, func() error { return checkURL(url) }, 1*time.Second); err != nil {
fmt.Fprintf(os.Stderr, "%s is not responding properly: %v\n", url, err)
os.Exit(1)
}
if dashboardURLMode {
fmt.Fprintln(os.Stdout, urls[0])
fmt.Fprintln(os.Stdout, url)
} else {
fmt.Fprintln(os.Stdout, "Opening kubernetes dashboard in default browser...")
browser.OpenURL(urls[0])
fmt.Fprintln(os.Stdout, fmt.Sprintf("Opening %s in your default browser...", url))
if err = browser.OpenURL(url); err != nil {
fmt.Fprintf(os.Stderr, fmt.Sprintf("failed to open browser: %v", err))
}
}
glog.Infof("Waiting forever for kubectl proxy to exit ...")
if err = p.Wait(); err != nil {
glog.Errorf("Wait: %v", err)
}
},
}
// kubectlProxy runs "kubectl proxy", returning host:port
func kubectlProxy() (*exec.Cmd, string, error) {
path, err := exec.LookPath("kubectl")
if err != nil {
return nil, "", errors.Wrap(err, "kubectl not found in PATH")
}
// port=0 picks a random system port
// config.GetMachineName() respects the -p (profile) flag
cmd := exec.Command(path, "--context", config.GetMachineName(), "proxy", "--port=0")
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return nil, "", errors.Wrap(err, "cmd stdout")
}
glog.Infof("Executing: %s %s", cmd.Path, cmd.Args)
if err := cmd.Start(); err != nil {
return nil, "", errors.Wrap(err, "proxy start")
}
reader := bufio.NewReader(stdoutPipe)
glog.Infof("proxy started, reading stdout pipe ...")
out, err := reader.ReadString('\n')
if err != nil {
return nil, "", errors.Wrap(err, "reading stdout pipe")
}
glog.Infof("proxy stdout: %s", out)
return cmd, hostPortRe.FindString(out), nil
}
// dashboardURL generates a URL for accessing the dashboard service
func dashboardURL(proxy string, ns string, svc string) string {
// Reference: https://github.com/kubernetes/dashboard/wiki/Accessing-Dashboard---1.7.X-and-above
return fmt.Sprintf("http://%s/api/v1/namespaces/%s/services/http:%s:/proxy/", proxy, ns, svc)
}
// checkURL checks if a URL returns 200 HTTP OK
func checkURL(url string) error {
resp, err := http.Get(url)
glog.Infof("%s response: %v %+v", url, err, resp)
if err != nil {
return errors.Wrap(err, "checkURL")
}
if resp.StatusCode != http.StatusOK {
return &util.RetriableError{
Err: fmt.Errorf("unexpected response code: %d", resp.StatusCode),
}
}
return nil
}
func init() {
dashboardCmd.Flags().BoolVar(&dashboardURLMode, "url", false, "Display the kubernetes dashboard in the CLI instead of opening it in the default browser")
dashboardCmd.Flags().BoolVar(&dashboardURLMode, "url", false, "Display dashboard URL instead of opening a browser")
RootCmd.AddCommand(dashboardCmd)
}
......@@ -24,10 +24,8 @@ metadata:
kubernetes.io/minikube-addons: dashboard
kubernetes.io/minikube-addons-endpoint: dashboard
spec:
type: NodePort
ports:
- port: 80
targetPort: 9090
nodePort: 30000
selector:
app: kubernetes-dashboard
[
{
"version": "v1.10.0"
},
{
"version": "v1.9.4"
},
{
"version": "v1.9.0"
},
{
"version": "v1.8.0"
},
{
"version": "v1.7.5"
},
{
"version": "v1.7.4"
},
{
"version": "v1.7.3"
},
{
"version": "v1.7.2"
},
{
"version": "v1.7.0"
},
{
"version": "v1.7.0-rc.1"
},
{
"version": "v1.7.0-alpha.2"
},
{
"version": "v1.6.4"
},
{
"version": "v1.6.3"
},
{
"version": "v1.6.0"
},
{
"version": "v1.6.0-rc.1"
},
{
"version": "v1.6.0-beta.4"
},
{
"version": "v1.6.0-beta.3"
},
{
"version": "v1.6.0-beta.2"
},
{
"version": "v1.6.0-alpha.1"
},
{
"version": "v1.6.0-alpha.0"
},
{
"version": "v1.5.3"
},
{
"version": "v1.5.2"
},
{
"version": "v1.5.1"
},
{
"version": "v1.4.5"
},
{
"version": "v1.4.3"
},
{
"version": "v1.4.2"
},
{
"version": "v1.4.1"
},
{
"version": "v1.4.0"
},
{
"version": "v1.3.7"
},
{
"version": "v1.3.6"
},
{
"version": "v1.3.5"
},
{
"version": "v1.3.4"
},
{
"version": "v1.3.3"
},
{
"version": "v1.3.0"
}
]
\ No newline at end of file
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "array",
"items": {
"type": "object",
"properties": {
"version": {
"type": "string"
}
},
"required": [
"version"
]
}
}
......@@ -26,7 +26,6 @@ import (
func main() {
validateSchema("deploy/minikube/schema.json", "deploy/minikube/releases.json")
validateSchema("deploy/minikube/k8s_schema.json", "deploy/minikube/k8s_releases.json")
os.Exit(0)
}
......
......@@ -110,7 +110,6 @@ const (
DefaultConfigViewFormat = "- {{.ConfigKey}}: {{.ConfigValue}}\n"
DefaultCacheListFormat = "{{.CacheImage}}\n"
GithubMinikubeReleasesURL = "https://storage.googleapis.com/minikube/releases.json"
KubernetesVersionGCSURL = "https://storage.googleapis.com/minikube/k8s_releases.json"
DefaultWait = 20
DefaultInterval = 6
DefaultClusterBootstrapper = "kubeadm"
......
......@@ -25,6 +25,8 @@ import (
"time"
"github.com/docker/machine/libmachine"
"github.com/golang/glog"
"github.com/pkg/browser"
"github.com/pkg/errors"
"k8s.io/api/core/v1"
......@@ -189,45 +191,23 @@ func printURLsForService(c corev1.CoreV1Interface, ip, service, namespace string
return urls, nil
}
// CheckService waits for the specified service to be ready by returning an error until the service is up
// The check is done by polling the endpoint associated with the service and when the endpoint exists, returning no error->service-online
// CheckService checks if a service is listening on a port.
func CheckService(namespace string, service string) error {
client, err := K8s.GetCoreClient()
if err != nil {
return errors.Wrap(err, "Error getting kubernetes client")
}
services := client.Services(namespace)
err = validateService(services, service)
if err != nil {
return errors.Wrap(err, "Error validating service")
}
endpoints := client.Endpoints(namespace)
return checkEndpointReady(endpoints, service)
}
func validateService(s corev1.ServiceInterface, service string) error {
if _, err := s.Get(service, metav1.GetOptions{}); err != nil {
return errors.Wrapf(err, "Error getting service %s", service)
}
return nil
}
func checkEndpointReady(endpoints corev1.EndpointsInterface, service string) error {
endpoint, err := endpoints.Get(service, metav1.GetOptions{})
svc, err := client.Services(namespace).Get(service, metav1.GetOptions{})
if err != nil {
return &util.RetriableError{Err: errors.Errorf("Error getting endpoints for service %s", service)}
}
const notReadyMsg = "Waiting, endpoint for service is not ready yet...\n"
if len(endpoint.Subsets) == 0 {
fmt.Fprintf(os.Stderr, notReadyMsg)
return &util.RetriableError{Err: errors.New("Endpoint for service is not ready yet")}
}
for _, subset := range endpoint.Subsets {
if len(subset.Addresses) == 0 {
fmt.Fprintf(os.Stderr, notReadyMsg)
return &util.RetriableError{Err: errors.New("No endpoints for service are ready yet")}
return &util.RetriableError{
Err: errors.Wrapf(err, "Error getting service %s", service),
}
}
if len(svc.Spec.Ports) == 0 {
return fmt.Errorf("%s:%s has no ports", namespace, service)
}
glog.Infof("Found service: %+v", svc)
return nil
}
......
......@@ -133,44 +133,6 @@ func (e MockEndpointsInterface) Get(name string, _ metav1.GetOptions) (*v1.Endpo
return endpoint, nil
}
func TestCheckEndpointReady(t *testing.T) {
var tests = []struct {
description string
service string
err bool
}{
{
description: "Endpoint with no subsets should return an error",
service: "no-subsets",
err: true,
},
{
description: "Endpoint with no ready endpoints should return an error",
service: "not-ready",
err: true,
},
{
description: "Endpoint with at least one ready endpoint should not return an error",
service: "one-ready",
err: false,
},
}
for _, test := range tests {
test := test
t.Run(test.description, func(t *testing.T) {
t.Parallel()
err := checkEndpointReady(&MockEndpointsInterface{}, test.service)
if err != nil && !test.err {
t.Errorf("Check endpoints returned an error: %+v", err)
}
if err == nil && test.err {
t.Errorf("Check endpoints should have returned an error but returned nil")
}
})
}
}
type MockServiceInterface struct {
fake.FakeServices
ServiceList *v1.ServiceList
......
......@@ -97,14 +97,17 @@ func Retry(attempts int, callback func() error) (err error) {
func RetryAfter(attempts int, callback func() error, d time.Duration) (err error) {
m := MultiError{}
for i := 0; i < attempts; i++ {
glog.V(1).Infof("retry loop %d", i)
err = callback()
if err == nil {
return nil
}
m.Collect(err)
if _, ok := err.(*RetriableError); !ok {
glog.Infof("non-retriable error: %v", err)
return m.ToError()
}
glog.V(2).Infof("sleeping %s", d)
time.Sleep(d)
}
return m.ToError()
......
......@@ -19,9 +19,10 @@ limitations under the License.
package integration
import (
"errors"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/url"
"path/filepath"
"strings"
......@@ -49,34 +50,45 @@ func testDashboard(t *testing.T) {
t.Parallel()
minikubeRunner := NewMinikubeRunner(t)
var u *url.URL
checkDashboard := func() error {
var err error
dashboardURL := minikubeRunner.RunCommand("dashboard --url", false)
if dashboardURL == "" {
return errors.New("error getting dashboard URL")
}
u, err = url.Parse(strings.TrimSpace(dashboardURL))
cmd, out := minikubeRunner.RunDaemon("dashboard --url")
defer func() {
err := cmd.Process.Kill()
if err != nil {
return err
t.Logf("Failed to kill mount command: %v", err)
}
return nil
}()
s, err := out.ReadString('\n')
if err != nil {
t.Fatalf("failed to read url: %v", err)
}
if err := util.Retry(t, checkDashboard, 2*time.Second, 60); err != nil {
t.Fatalf("error checking dashboard URL: %v", err)
u, err := url.Parse(strings.TrimSpace(s))
if err != nil {
t.Fatalf("failed to parse %q: %v", s, err)
}
if u.Scheme != "http" {
t.Fatalf("wrong scheme in dashboard URL, expected http, actual %s", u.Scheme)
t.Errorf("got Scheme %s, expected http", u.Scheme)
}
_, port, err := net.SplitHostPort(u.Host)
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
t.Fatalf("failed to split dashboard host %s: %v", u.Host, err)
t.Fatalf("failed SplitHostPort: %v", err)
}
if host != "127.0.0.1" {
t.Errorf("got host %s, expected 127.0.0.1", host)
}
if port != "30000" {
t.Fatalf("Dashboard is exposed on wrong port, expected 30000, actual %s", port)
resp, err := http.Get(u.String())
if err != nil {
t.Fatalf("failed get: %v", err)
}
if resp.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
t.Fatalf("Unable to read http response body: %v", err)
}
t.Errorf("%s returned status code %d, expected %d.\nbody:\n%s", u, resp.StatusCode, http.StatusOK, body)
}
}
......
......@@ -46,7 +46,7 @@ func testMounting(t *testing.T) {
defer os.RemoveAll(tempDir)
mountCmd := fmt.Sprintf("mount %s:/mount-9p", tempDir)
cmd := minikubeRunner.RunDaemon(mountCmd)
cmd, _ := minikubeRunner.RunDaemon(mountCmd)
defer func() {
err := cmd.Process.Kill()
if err != nil {
......
......@@ -17,6 +17,7 @@ limitations under the License.
package util
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
......@@ -81,15 +82,21 @@ func (m *MinikubeRunner) RunCommand(command string, checkError bool) string {
return string(stdout)
}
func (m *MinikubeRunner) RunDaemon(command string) *exec.Cmd {
func (m *MinikubeRunner) RunDaemon(command string) (*exec.Cmd, *bufio.Reader) {
commandArr := strings.Split(command, " ")
path, _ := filepath.Abs(m.BinaryPath)
cmd := exec.Command(path, commandArr...)
err := cmd.Start()
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
m.T.Fatalf("Error running command: %s %s", command, err)
m.T.Fatalf("stdout pipe failed: %v", err)
}
return cmd
err = cmd.Start()
if err != nil {
m.T.Fatalf("Error running command: %s %v", command, err)
}
return cmd, bufio.NewReader(stdoutPipe)
}
func (m *MinikubeRunner) SSH(command string) (string, error) {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册