Implement the new problem package
parent
b08af1947a
commit
37e259f7ea
|
@ -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("")
|
||||
}
|
||||
|
|
|
@ -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: "@ "},
|
||||
|
|
|
@ -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")
|
||||
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue