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 {
|
if preexisting {
|
||||||
console.OutStyle("restarting", "Relaunching Kubernetes %s using %s ... ", kc.KubernetesVersion, bsName)
|
console.OutStyle("restarting", "Relaunching Kubernetes %s using %s ... ", kc.KubernetesVersion, bsName)
|
||||||
if err := bs.RestartCluster(kc); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.OutStyle("launch", "Launching Kubernetes %s using %s ... ", kc.KubernetesVersion, bsName)
|
console.OutStyle("launch", "Launching Kubernetes %s using %s ... ", kc.KubernetesVersion, bsName)
|
||||||
if err := bs.StartCluster(kc); err != nil {
|
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)
|
err := pkgutil.RetryAfter(20, k8sStat, 3*time.Second)
|
||||||
if err != nil {
|
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) {
|
aStat := func() (err error) {
|
||||||
st, err := bs.GetAPIServerStatus(net.ParseIP(ip))
|
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)
|
err = pkgutil.RetryAfter(30, aStat, 10*time.Second)
|
||||||
if err != nil {
|
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("")
|
console.OutLn("")
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,8 @@ var styles = map[string]style{
|
||||||
"log-entry": {Prefix: " "}, // Indent
|
"log-entry": {Prefix: " "}, // Indent
|
||||||
"crushed": {Prefix: "💔 "},
|
"crushed": {Prefix: "💔 "},
|
||||||
"url": {Prefix: "👉 "},
|
"url": {Prefix: "👉 "},
|
||||||
|
"solution": {Prefix: "> "},
|
||||||
|
"related-issue": {Prefix: " - "},
|
||||||
|
|
||||||
// Specialized purpose styles
|
// Specialized purpose styles
|
||||||
"iso-download": {Prefix: "💿 ", LowPrefix: "@ "},
|
"iso-download": {Prefix: "💿 ", LowPrefix: "@ "},
|
||||||
|
|
|
@ -20,11 +20,10 @@ package exit
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/glog"
|
"github.com/golang/glog"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"k8s.io/minikube/pkg/minikube/console"
|
"k8s.io/minikube/pkg/minikube/console"
|
||||||
|
"k8s.io/minikube/pkg/minikube/problem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Exit codes based on sysexits(3)
|
// Exit codes based on sysexits(3)
|
||||||
|
@ -40,8 +39,8 @@ const (
|
||||||
Config = 78 // Config represents an unconfigured or misconfigured state
|
Config = 78 // Config represents an unconfigured or misconfigured state
|
||||||
Permissions = 77 // Permissions represents a permissions error
|
Permissions = 77 // Permissions represents a permissions error
|
||||||
|
|
||||||
// MaxProblems controls the number of problems to show for each source
|
// MaxLogEntries controls the number of log entries to show for each source
|
||||||
MaxProblems = 3
|
MaxLogEntries = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
// Usage outputs a usage error and exits with error code 64
|
// Usage outputs a usage error and exits with error code 64
|
||||||
|
@ -67,14 +66,14 @@ func WithError(msg string, err error) {
|
||||||
os.Exit(Software)
|
os.Exit(Software)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithProblems outputs an error along with any autodetected problems, and exits.
|
// WithLogEntries outputs an error along with any important log entries, and exits.
|
||||||
func WithProblems(msg string, err error, problems map[string][]string) {
|
func WithLogEntries(msg string, err error, entries map[string][]string) {
|
||||||
displayError(msg, err)
|
displayError(msg, err)
|
||||||
|
|
||||||
for name, lines := range problems {
|
for name, lines := range entries {
|
||||||
console.OutStyle("failure", "Problems detected in %q:", name)
|
console.OutStyle("failure", "Problems detected in %q:", name)
|
||||||
if len(lines) > MaxProblems {
|
if len(lines) > MaxLogEntries {
|
||||||
lines = lines[:MaxProblems]
|
lines = lines[:MaxLogEntries]
|
||||||
}
|
}
|
||||||
for _, l := range lines {
|
for _, l := range lines {
|
||||||
console.OutStyle("log-entry", l)
|
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) {
|
func displayError(msg string, err error) {
|
||||||
// use Warning because Error will display a duplicate message to stderr
|
// use Warning because Error will display a duplicate message to stderr
|
||||||
glog.Warningf(fmt.Sprintf("%s: %v", msg, err))
|
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.Fatal(msg+": %v", err)
|
||||||
console.Err("\n")
|
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("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("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