Merge pull request #533 from aaron-prindle/add-error-reporting

Added error reporting functionality and testing
pull/581/head
Aaron Prindle 2016-09-14 10:24:26 -04:00 committed by GitHub
commit 17db10a4f2
17 changed files with 190 additions and 43 deletions

View File

@ -18,8 +18,9 @@ package config
import (
"fmt"
"github.com/spf13/cobra"
"os"
"github.com/spf13/cobra"
)
var configGetCmd = &cobra.Command{

View File

@ -18,8 +18,9 @@ package config
import (
"fmt"
"github.com/spf13/cobra"
"os"
"github.com/spf13/cobra"
)
var configSetCmd = &cobra.Command{

View File

@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/util"
commonutil "k8s.io/minikube/pkg/util"
)
@ -48,15 +49,15 @@ var dashboardCmd = &cobra.Command{
service := "kubernetes-dashboard"
if err := commonutil.RetryAfter(20, func() error { return CheckService(namespace, service) }, 6*time.Second); err != nil {
fmt.Fprintf(os.Stderr, "Could not find finalized endpoint being pointed to by %s: %s\n", service, err)
os.Exit(1)
fmt.Fprintln(os.Stderr, "Could not find finalized endpoint being pointed to by %s: %s", service, err)
util.MaybeReportErrorAndExit(err)
}
url, err := cluster.GetServiceURL(api, namespace, service)
if err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, "Check that minikube is running.")
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
if dashboardURLMode {
fmt.Fprintln(os.Stdout, url)

View File

@ -18,12 +18,12 @@ package cmd
import (
"fmt"
"os"
"github.com/docker/machine/libmachine"
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/util"
)
// deleteCmd represents the delete command
@ -39,7 +39,7 @@ associated files.`,
if err := cluster.DeleteHost(api); err != nil {
fmt.Println("Errors occurred deleting machine: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
fmt.Println("Machine deleted.")
},

View File

@ -32,6 +32,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/util"
)
const (
@ -240,13 +241,13 @@ var dockerEnvCmd = &cobra.Command{
shellCfg, err = shellCfgUnset(api)
if err != nil {
glog.Errorln("Error setting machine env variable(s):", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
} else {
shellCfg, err = shellCfgSet(api)
if err != nil {
glog.Errorln("Error setting machine env variable(s):", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
}

View File

@ -18,13 +18,12 @@ package cmd
import (
"fmt"
"os"
"github.com/docker/machine/libmachine"
"github.com/golang/glog"
"k8s.io/minikube/pkg/minikube/constants"
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/util"
)
// ipCmd represents the ip command
@ -38,12 +37,12 @@ var ipCmd = &cobra.Command{
host, err := api.Load(constants.MachineName)
if err != nil {
glog.Errorln("Error getting IP: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
ip, err := host.Driver.GetIP()
if err != nil {
glog.Errorln("Error getting IP: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
fmt.Println(ip)
},

View File

@ -25,6 +25,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/util"
)
// logsCmd represents the logs command
@ -38,7 +39,7 @@ var logsCmd = &cobra.Command{
s, err := cluster.GetHostLogs(api)
if err != nil {
log.Println("Error getting machine logs:", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
fmt.Fprintln(os.Stdout, s)
},

View File

@ -138,5 +138,6 @@ func setupViper() {
viper.SetDefault(config.WantUpdateNotification, true)
viper.SetDefault(config.ReminderWaitPeriodInHours, 24)
viper.SetDefault(config.WantReportError, false)
setFlagsUsingViper()
}

View File

@ -29,8 +29,7 @@ import (
kubeApi "k8s.io/kubernetes/pkg/api"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/constants"
commonutil "k8s.io/minikube/pkg/util"
"k8s.io/minikube/pkg/util"
)
var (
@ -46,8 +45,9 @@ var serviceCmd = &cobra.Command{
Long: `Gets the kubernetes URL for the specified service in your local cluster`,
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 || len(args) > 1 {
fmt.Fprintln(os.Stderr, "Please specify a service name.")
os.Exit(1)
errText := "Please specify a service name."
fmt.Fprintln(os.Stderr, errText)
util.MaybeReportErrorAndExit(errors.New(errText))
}
service := args[0]
@ -55,16 +55,16 @@ var serviceCmd = &cobra.Command{
defer api.Close()
cluster.EnsureMinikubeRunningOrExit(api)
if err := commonutil.RetryAfter(20, func() error { return CheckService(namespace, service) }, 6*time.Second); err != nil {
fmt.Fprintf(os.Stderr, "Could not find finalized endpoint being pointed to by %s: %s\n", service, err)
os.Exit(1)
if err := util.RetryAfter(20, func() error { return CheckService(namespace, service) }, 6*time.Second); err != nil {
fmt.Fprintln(os.Stderr, "Could not find finalized endpoint being pointed to by %s: %s", service, err)
util.MaybeReportErrorAndExit(err)
}
url, err := cluster.GetServiceURL(api, namespace, service)
if err != nil {
fmt.Fprintln(os.Stderr, err)
fmt.Fprintln(os.Stderr, "Check that minikube is running and that you have specified the correct namespace (-n flag).")
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
if https {
url = strings.Replace(url, "http", "https", 1)

View File

@ -17,13 +17,12 @@ limitations under the License.
package cmd
import (
"os"
"github.com/docker/machine/libmachine"
"github.com/golang/glog"
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/util"
)
// sshCmd represents the docker-ssh command
@ -37,7 +36,7 @@ var sshCmd = &cobra.Command{
err := cluster.CreateSSHShell(api, args)
if err != nil {
glog.Errorln("Error attempting to ssh into machine: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
},
}

View File

@ -18,7 +18,6 @@ package cmd
import (
"fmt"
"os"
"strconv"
"strings"
@ -90,13 +89,13 @@ func runStart(cmd *cobra.Command, args []string) {
err := util.Retry(3, start)
if err != nil {
glog.Errorln("Error starting host: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
ip, err := host.Driver.GetIP()
if err != nil {
glog.Errorln("Error starting host: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
kubernetesConfig := cluster.KubernetesConfig{
KubernetesVersion: viper.GetString(kubernetesVersion),
@ -106,17 +105,17 @@ func runStart(cmd *cobra.Command, args []string) {
}
if err := cluster.UpdateCluster(host, host.Driver, kubernetesConfig); err != nil {
glog.Errorln("Error updating cluster: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
if err := cluster.SetupCerts(host.Driver); err != nil {
glog.Errorln("Error configuring authentication: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
if err := cluster.StartCluster(host, kubernetesConfig); err != nil {
glog.Errorln("Error starting cluster: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
kubeHost, err := host.Driver.GetURL()
@ -133,7 +132,7 @@ func runStart(cmd *cobra.Command, args []string) {
clientKey := constants.MakeMiniPath("apiserver.key")
if err := setupKubeconfig(name, kubeHost, certAuth, clientCert, clientKey); err != nil {
glog.Errorln("Error setting up kubeconfig: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
fmt.Println("Kubectl is now configured to use the cluster.")
}

View File

@ -26,6 +26,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/util"
)
var statusFormat string
@ -46,7 +47,7 @@ var statusCmd = &cobra.Command{
ms, err := cluster.GetHostStatus(api)
if err != nil {
glog.Errorln("Error getting machine status:", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
ls := "N/A"
if ms == state.Running.String() {
@ -54,19 +55,19 @@ var statusCmd = &cobra.Command{
}
if err != nil {
glog.Errorln("Error getting machine status:", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
status := Status{ms, ls}
tmpl, err := template.New("status").Parse(statusFormat)
if err != nil {
glog.Errorln("Error creating status template:", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
err = tmpl.Execute(os.Stdout, status)
if err != nil {
glog.Errorln("Error executing status template:", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
},
}

View File

@ -18,12 +18,12 @@ package cmd
import (
"fmt"
"os"
"github.com/docker/machine/libmachine"
"github.com/spf13/cobra"
"k8s.io/minikube/pkg/minikube/cluster"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/util"
)
// stopCmd represents the stop command
@ -39,7 +39,7 @@ itself, leaving all files intact. The cluster can be started again with the "sta
if err := cluster.StopHost(api); err != nil {
fmt.Println("Error stopping machine: ", err)
os.Exit(1)
util.MaybeReportErrorAndExit(err)
}
fmt.Println("Machine stopped.")
},

View File

@ -19,4 +19,5 @@ package config
const (
WantUpdateNotification = "WantUpdateNotification"
ReminderWaitPeriodInHours = "ReminderWaitPeriodInHours"
WantReportError = "WantReportError"
)

View File

@ -28,3 +28,5 @@ const (
func GetAlternateDNS(domain string) []string {
return []string{"kubernetes.default.svc." + domain, "kubernetes.default.svc", "kubernetes.default", "kubernetes"}
}
const reportingURL = "https://clouderrorreporting.googleapis.com/v1beta1/projects/k8s-minikube/events:report?key=AIzaSyACUwzG0dEPcl-eOgpDKnyKoUFgHdfoFuA"

View File

@ -17,15 +17,21 @@ limitations under the License.
package util
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/blang/semver"
"github.com/pkg/errors"
"github.com/spf13/viper"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/version"
)
@ -128,6 +134,14 @@ func (m MultiError) ToError() error {
return errors.New(strings.Join(errStrings, "\n"))
}
func IsDirectory(path string) (bool, error) {
fileInfo, err := os.Stat(path)
if err != nil {
return false, errors.Wrapf(err, "Error calling os.Stat on file %s", path)
}
return fileInfo.IsDir(), nil
}
type ServiceContext struct {
Service string `json:"service"`
Version string `json:"version"`
@ -138,10 +152,77 @@ type Message struct {
ServiceContext `json:"serviceContext"`
}
func IsDirectory(path string) (bool, error) {
fileInfo, err := os.Stat(path)
func ReportError(err error, url string) error {
errMsg, err := FormatError(err)
if err != nil {
return false, errors.Wrapf(err, "Error calling os.Stat on file %s", path)
return errors.Wrap(err, "")
}
return fileInfo.IsDir(), nil
jsonErrorMsg, err := MarshallError(errMsg, "default", version.GetVersion())
if err != nil {
return errors.Wrap(err, "")
}
if err != nil {
return errors.Wrap(err, "")
}
err = UploadError(jsonErrorMsg, url)
if err != nil {
return errors.Wrap(err, "")
}
return nil
}
func FormatError(err error) (string, error) {
if err == nil {
return "", errors.New("Error: ReportError was called with nil error value")
}
// Extract the stacktrace from the error messages in their orig format
errMsg := fmt.Sprintf("%+v\n", err)
errArray := strings.Split(errMsg, "\n")
errOutput := []string{}
//Error message must have at least 1 message w/ 1 stack trace(2 lines) -> 3 lines, 2 index
if len(errArray) <= 2 {
return "", errors.New("Error msg with no stack trace cannot be reported")
}
// This code is to format the error stacktraces so that StackDriver will accept them
errOutput = append(errOutput, errArray[0])
for i := 1; i < len(errArray)-1; i += 2 {
errOutput = append(errOutput, fmt.Sprintf("\tat %s (%s)", errArray[i],
filepath.Base(errArray[i+1])))
}
return strings.Join(errOutput, "\n") + "\n", nil
}
func MarshallError(errMsg, service, version string) ([]byte, error) {
m := Message{errMsg, ServiceContext{service, version}}
b, err := json.Marshal(m)
if err != nil {
return nil, errors.Wrap(err, "")
}
return b, nil
}
func UploadError(b []byte, url string) error {
req, err := http.NewRequest("POST", url, bytes.NewBuffer(b))
if err != nil {
return errors.Wrap(err, "")
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return errors.Wrap(err, "")
} else if resp.StatusCode != 200 {
return errors.Errorf("Error sending error report to %s, got response code %s", url, resp.StatusCode)
}
return nil
}
func MaybeReportErrorAndExit(err error) {
if viper.GetBool(config.WantReportError) {
ReportError(err, reportingURL)
}
os.Exit(1)
}

View File

@ -17,10 +17,14 @@ limitations under the License.
package util
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/version"
)
// Returns a function that will return n errors, then return successfully forever.
@ -108,3 +112,58 @@ Error 2`
t.Fatalf("Unexpected error: %s", err)
}
}
func TestFormatError(t *testing.T) {
var testErr error
if _, err := FormatError(testErr); err == nil {
t.Fatalf("FormatError should have errored with a nil error input")
}
testErr = fmt.Errorf("Not a valid error to format as there is no stacktrace")
if out, err := FormatError(testErr); err == nil {
t.Fatalf("FormatError should have errored with a non pkg/errors error (no stacktrace info): %s", out)
}
testErr = errors.New("TestFormatError 1")
errors.Wrap(testErr, "TestFormatError 2")
errors.Wrap(testErr, "TestFormatError 3")
_, err := FormatError(testErr)
if err != nil {
t.Fatalf("Unexpected error: %s", err)
}
}
func TestMarshallError(t *testing.T) {
testErr := errors.New("TestMarshallError 1")
errors.Wrap(testErr, "TestMarshallError 2")
errors.Wrap(testErr, "TestMarshallError 3")
errMsg, _ := FormatError(testErr)
if _, err := MarshallError(errMsg, "default", version.GetVersion()); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
}
func TestUploadError(t *testing.T) {
testErr := errors.New("TestUploadError 1")
errors.Wrap(testErr, "TestUploadError 2")
errors.Wrap(testErr, "TestUploadError 3")
errMsg, _ := FormatError(testErr)
jsonErrMsg, _ := MarshallError(errMsg, "default", version.GetVersion())
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, world!")
}))
if err := UploadError(jsonErrMsg, server.URL); err != nil {
t.Fatalf("Unexpected error: %s", err)
}
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "failed to write report", 400)
}))
if err := UploadError(jsonErrMsg, server.URL); err == nil {
t.Fatalf("UploadError should have errored from a 400 response")
}
}