Implement the new problem package

pull/3931/head
Thomas Stromberg 2019-03-21 21:44:51 -07:00
parent b08af1947a
commit 37e259f7ea
5 changed files with 190 additions and 24 deletions

View File

@ -525,14 +525,14 @@ func bootstrapCluster(bs bootstrapper.Bootstrapper, r cruntime.Manager, runner b
if preexisting {
console.OutStyle("restarting", "Relaunching Kubernetes %s using %s ... ", kc.KubernetesVersion, bsName)
if err := bs.RestartCluster(kc); err != nil {
exit.WithProblems("Error restarting cluster", err, logs.FindProblems(r, bs, runner))
exit.WithLogEntries("Error restarting cluster", err, logs.FindProblems(r, bs, runner))
}
return
}
console.OutStyle("launch", "Launching Kubernetes %s using %s ... ", kc.KubernetesVersion, bsName)
if err := bs.StartCluster(kc); err != nil {
exit.WithProblems("Error starting cluster", err, logs.FindProblems(r, bs, runner))
exit.WithLogEntries("Error starting cluster", err, logs.FindProblems(r, bs, runner))
}
}
@ -549,7 +549,7 @@ func validateCluster(bs bootstrapper.Bootstrapper, r cruntime.Manager, runner bo
}
err := pkgutil.RetryAfter(20, k8sStat, 3*time.Second)
if err != nil {
exit.WithProblems("kubelet checks failed", err, logs.FindProblems(r, bs, runner))
exit.WithLogEntries("kubelet checks failed", err, logs.FindProblems(r, bs, runner))
}
aStat := func() (err error) {
st, err := bs.GetAPIServerStatus(net.ParseIP(ip))
@ -562,7 +562,7 @@ func validateCluster(bs bootstrapper.Bootstrapper, r cruntime.Manager, runner bo
err = pkgutil.RetryAfter(30, aStat, 10*time.Second)
if err != nil {
exit.WithProblems("apiserver checks failed", err, logs.FindProblems(r, bs, runner))
exit.WithLogEntries("apiserver checks failed", err, logs.FindProblems(r, bs, runner))
}
console.OutLn("")
}

View File

@ -66,6 +66,8 @@ var styles = map[string]style{
"log-entry": {Prefix: " "}, // Indent
"crushed": {Prefix: "💔 "},
"url": {Prefix: "👉 "},
"solution": {Prefix: "> "},
"related-issue": {Prefix: " - "},
// Specialized purpose styles
"iso-download": {Prefix: "💿 ", LowPrefix: "@ "},

View File

@ -20,11 +20,10 @@ package exit
import (
"fmt"
"os"
"strings"
"github.com/golang/glog"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/minikube/console"
"k8s.io/minikube/pkg/minikube/problem"
)
// Exit codes based on sysexits(3)
@ -40,8 +39,8 @@ const (
Config = 78 // Config represents an unconfigured or misconfigured state
Permissions = 77 // Permissions represents a permissions error
// MaxProblems controls the number of problems to show for each source
MaxProblems = 3
// MaxLogEntries controls the number of log entries to show for each source
MaxLogEntries = 3
)
// Usage outputs a usage error and exits with error code 64
@ -67,14 +66,14 @@ func WithError(msg string, err error) {
os.Exit(Software)
}
// WithProblems outputs an error along with any autodetected problems, and exits.
func WithProblems(msg string, err error, problems map[string][]string) {
// WithLogEntries outputs an error along with any important log entries, and exits.
func WithLogEntries(msg string, err error, entries map[string][]string) {
displayError(msg, err)
for name, lines := range problems {
for name, lines := range entries {
console.OutStyle("failure", "Problems detected in %q:", name)
if len(lines) > MaxProblems {
lines = lines[:MaxProblems]
if len(lines) > MaxLogEntries {
lines = lines[:MaxLogEntries]
}
for _, l := range lines {
console.OutStyle("log-entry", l)
@ -86,17 +85,22 @@ func WithProblems(msg string, err error, problems map[string][]string) {
func displayError(msg string, err error) {
// use Warning because Error will display a duplicate message to stderr
glog.Warningf(fmt.Sprintf("%s: %v", msg, err))
p := problem.FromError(err)
if p != nil {
console.Err("\n")
console.Fatal(msg)
p.Display()
console.Err("\n")
console.ErrStyle("sad", "If the solution does not help, please let us know:")
console.ErrStyle("url", "https://github.com/kubernetes/minikube/issues/new")
return
}
console.Err("\n")
console.Fatal(msg+": %v", err)
console.Err("\n")
// unfortunately Cause only supports one level of actual error wrapping
cause := errors.Cause(err)
text := cause.Error()
if strings.Contains(text, "VBoxManage not found. Make sure VirtualBox is installed and VBoxManage is in the path") ||
strings.Contains(text, "Driver \"kvm2\" not found. Do you have the plugin binary \"docker-machine-driver-kvm2\" accessible in your PATH?") {
console.ErrStyle("usage", "Make sure to install all necessary requirements, according to the documentation:")
console.ErrStyle("url", "https://kubernetes.io/docs/tasks/tools/install-minikube/")
} else {
console.ErrStyle("sad", "Sorry that minikube crashed. If this was unexpected, we would love to hear from you:")
console.ErrStyle("url", "https://github.com/kubernetes/minikube/issues/new")
}
console.ErrStyle("sad", "Sorry that minikube crashed. If this was unexpected, we would love to hear from you:")
console.ErrStyle("url", "https://github.com/kubernetes/minikube/issues/new")
}

View File

@ -0,0 +1,92 @@
package problem
import "regexp"
// r is a shortcut around regexp.MustCompile
func r(s string) *regexp.Regexp {
return regexp.MustCompile(s)
}
// vmProblems are VM related problems
var vmProblems = map[string]match{
"VBOX_NOT_FOUND": match{
Regexp: r(`VBoxManage not found. Make sure VirtualBox is installed and VBoxManage is in the path`),
Solution: "Install VirtualBox, ensure that VBoxManage is executable and in path, or select an alternative value for --vm-driver",
URL: "https://www.virtualbox.org/wiki/Downloads",
Issues: []int{3784, 3776},
},
"VBOX_VTX_DISABLED": match{
Regexp: r(`This computer doesn't have VT-X/AMD-v enabled`),
Solution: "In some environments, this message is incorrect. Try 'minikube start --no-vtx-check'",
Issues: []int{3900},
},
"KVM2_NOT_FOUND": match{
Regexp: r(`Driver "kvm2" not found. Do you have the plugin binary .* accessible in your PATH`),
Solution: "Please install the minikube kvm2 VM driver, or select an alternative --vm-driver",
URL: "https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#kvm2-driver",
},
"KVM2_NO_IP": match{
Regexp: r(`Error starting stopped host: Machine didn't return an IP after 120 seconds`),
Solution: "The KVM driver is unable to ressurect this old VM. Please run `minikube delete` to delete it and try again.",
Issues: []int{3901, 3566, 3434},
},
"VM_DOES_NOT_EXIST": match{
Regexp: r(`Error getting state for host: machine does not exist`),
Solution: "Your system no longer knows about the VM previously created by minikube. Run 'minikube delete' to reset your local state.",
Issues: []int{3864},
},
"VM_IP_NOT_FOUND": match{
Regexp: r(`Error getting ssh host name for driver: IP not found`),
Solution: "The minikube VM is offline. Please run 'minikube start' to start it again.",
Issues: []int{3849, 3648},
},
}
// proxyDoc is the URL to proxy documentation
const proxyDoc = "https://github.com/kubernetes/minikube/blob/master/docs/http_proxy.md"
// netProblems are network related problems.
var netProblems = map[string]match{
"GCR_UNAVAILABLE": match{
Regexp: r(`gcr.io.*443: connect: invalid argument`),
Solution: "minikube is unable to access the Google Container Registry. You may need to configure it to use a HTTP proxy.",
URL: proxyDoc,
Issues: []int{3860},
},
"DOWNLOAD_RESET_BY_PEER": match{
Regexp: r(`Error downloading .*connection reset by peer`),
Solution: "A firewall is likely blocking minikube from reaching the internet. You may need to configure minikube to use a proxy.",
URL: proxyDoc,
Issues: []int{3909},
},
"DOWNLOAD_IO_TIMEOUT": match{
Regexp: r(`Error downloading .*timeout`),
Solution: "A firewall is likely blocking minikube from reaching the internet. You may need to configure minikube to use a proxy.",
URL: proxyDoc,
Issues: []int{3846},
},
"DOWNLOAD_TLS_OVERSIZED": match{
Regexp: r(`failed to download.*tls: oversized record received with length`),
Solution: "A firewall is interfering with minikube's ability to make outgoing HTTPS requests. You may need to configure minikube to use a proxy.",
URL: proxyDoc,
Issues: []int{3857, 3759},
},
"ISO_DOWNLOAD_FAILED": match{
Regexp: r(`iso: failed to download`),
Solution: "A firewall is likely blocking minikube from reaching the internet. You may need to configure minikube to use a proxy.",
URL: proxyDoc,
Issues: []int{3922},
},
"PULL_TIMEOUT_EXCEEDED": match{
Regexp: r(`failed to pull image k8s.gcr.io.*Client.Timeout exceeded while awaiting headers`),
Solution: "A firewall is blocking Docker within the minikube VM from reaching the internet. You may need to configure it to use a proxy.",
URL: proxyDoc,
Issues: []int{3898},
},
}
// deployProblems are Kubernetes deployment problems.
var deployProblems = map[string]match{
// This space intentionally left empty.
}

View File

@ -0,0 +1,68 @@
// Package problem helps deliver actionable feedback to a user based on an error message.
package problem
import (
"regexp"
"k8s.io/minikube/pkg/minikube/console"
)
const issueBase = "https://github.com/kubernetes/minikube/issue"
// Problem represents a known problem in minikube.
type Problem struct {
ID string
Err error
Solution string
URL string
Issues []int
}
// match maps a regular expression to problem metadata.
type match struct {
Regexp *regexp.Regexp
Solution string
URL string
Issues []int
}
// Display problem metadata to the console
func (p *Problem) Display() {
console.ErrStyle("solution", "Error: [%s] %v", p.ID, p.Err)
console.ErrStyle("solution", "Solution: %s", p.Solution)
console.ErrStyle("solution", "Documentation: %s", p.URL)
if len(p.Issues) == 0 {
return
}
issues := p.Issues
if len(issues) > 3 {
issues = issues[0:3]
}
console.ErrStyle("solution", "Related issues:")
for _, i := range issues {
console.ErrStyle("related-issue", "%s/%d", issueBase, i)
}
}
// FromError returns a known problem from an error.
func FromError(err error) *Problem {
maps := []map[string]match{
vmProblems,
netProblems,
deployProblems,
}
for _, m := range maps {
for k, v := range m {
if v.Regexp.MatchString(err.Error()) {
return &Problem{
Err: err,
Solution: v.Solution,
URL: v.URL,
ID: k,
Issues: v.Issues,
}
}
}
}
return nil
}