mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-31 15:25:57 +00:00
Merge pull request #3824 from smarterclayton/allow_panic_and_error_reporting
Allow panics and unhandled errors to be sent elsewhere
This commit is contained in:
commit
358ace610d
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||||
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||||
|
|
||||||
"github.com/emicklei/go-restful"
|
"github.com/emicklei/go-restful"
|
||||||
@ -372,6 +373,7 @@ func errorJSON(err error, codec runtime.Codec, w http.ResponseWriter) {
|
|||||||
|
|
||||||
// errorJSONFatal renders an error to the response, and if codec fails will render plaintext
|
// errorJSONFatal renders an error to the response, and if codec fails will render plaintext
|
||||||
func errorJSONFatal(err error, codec runtime.Codec, w http.ResponseWriter) {
|
func errorJSONFatal(err error, codec runtime.Codec, w http.ResponseWriter) {
|
||||||
|
util.HandleError(fmt.Errorf("apiserver was unable to write a JSON response: %v", err))
|
||||||
status := errToAPIStatus(err)
|
status := errToAPIStatus(err)
|
||||||
output, err := codec.Encode(status)
|
output, err := codec.Encode(status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -386,7 +388,6 @@ func errorJSONFatal(err error, codec runtime.Codec, w http.ResponseWriter) {
|
|||||||
|
|
||||||
// writeRawJSON writes a non-API object in JSON.
|
// writeRawJSON writes a non-API object in JSON.
|
||||||
func writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
|
func writeRawJSON(statusCode int, object interface{}, w http.ResponseWriter) {
|
||||||
// PR #2243: Pretty-print JSON by default.
|
|
||||||
output, err := json.MarshalIndent(object, "", " ")
|
output, err := json.MarshalIndent(object, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
|
||||||
"github.com/golang/glog"
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// statusError is an object that can be converted into an api.Status
|
// statusError is an object that can be converted into an api.Status
|
||||||
@ -49,7 +49,7 @@ func errToAPIStatus(err error) *api.Status {
|
|||||||
// by REST storage - these typically indicate programmer
|
// by REST storage - these typically indicate programmer
|
||||||
// error by not using pkg/api/errors, or unexpected failure
|
// error by not using pkg/api/errors, or unexpected failure
|
||||||
// cases.
|
// cases.
|
||||||
glog.V(1).Infof("An unchecked error was received: %v", err)
|
util.HandleError(fmt.Errorf("apiserver received an error that is not an api.Status: %v", err))
|
||||||
return &api.Status{
|
return &api.Status{
|
||||||
Status: api.StatusFailure,
|
Status: api.StatusFailure,
|
||||||
Code: status,
|
Code: status,
|
||||||
|
@ -17,6 +17,7 @@ limitations under the License.
|
|||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -65,15 +66,15 @@ func (r RealPodControl) createReplica(namespace string, controller api.Replicati
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := api.Scheme.Convert(&controller.Spec.Template.Spec, &pod.Spec); err != nil {
|
if err := api.Scheme.Convert(&controller.Spec.Template.Spec, &pod.Spec); err != nil {
|
||||||
glog.Errorf("Unable to convert pod template: %v", err)
|
util.HandleError(fmt.Errorf("unable to convert pod template: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if labels.Set(pod.Labels).AsSelector().Empty() {
|
if labels.Set(pod.Labels).AsSelector().Empty() {
|
||||||
glog.Errorf("Unable to create pod replica, no labels")
|
util.HandleError(fmt.Errorf("unable to create pod replica, no labels"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, err := r.kubeClient.Pods(namespace).Create(pod); err != nil {
|
if _, err := r.kubeClient.Pods(namespace).Create(pod); err != nil {
|
||||||
glog.Errorf("Unable to create pod replica: %v", err)
|
util.HandleError(fmt.Errorf("unable to create pod replica: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +109,7 @@ func (rm *ReplicationManager) watchControllers(resourceVersion *string) {
|
|||||||
*resourceVersion,
|
*resourceVersion,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Unexpected failure to watch: %v", err)
|
util.HandleError(fmt.Errorf("unable to watch: %v", err))
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -125,13 +126,13 @@ func (rm *ReplicationManager) watchControllers(resourceVersion *string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if event.Type == watch.Error {
|
if event.Type == watch.Error {
|
||||||
glog.Errorf("error from watch during sync: %v", errors.FromObject(event.Object))
|
util.HandleError(fmt.Errorf("error from watch during sync: %v", errors.FromObject(event.Object)))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
glog.V(4).Infof("Got watch: %#v", event)
|
glog.V(4).Infof("Got watch: %#v", event)
|
||||||
rc, ok := event.Object.(*api.ReplicationController)
|
rc, ok := event.Object.(*api.ReplicationController)
|
||||||
if !ok {
|
if !ok {
|
||||||
glog.Errorf("unexpected object: %#v", event.Object)
|
util.HandleError(fmt.Errorf("unexpected object: %#v", event.Object))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// If we get disconnected, start where we left off.
|
// If we get disconnected, start where we left off.
|
||||||
@ -140,7 +141,7 @@ func (rm *ReplicationManager) watchControllers(resourceVersion *string) {
|
|||||||
// it in the desired state.
|
// it in the desired state.
|
||||||
glog.V(4).Infof("About to sync from watch: %v", rc.Name)
|
glog.V(4).Infof("About to sync from watch: %v", rc.Name)
|
||||||
if err := rm.syncHandler(*rc); err != nil {
|
if err := rm.syncHandler(*rc); err != nil {
|
||||||
glog.Errorf("unexpected sync. error: %v", err)
|
util.HandleError(fmt.Errorf("unexpected sync error: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,7 +200,7 @@ func (rm *ReplicationManager) synchronize() {
|
|||||||
var controllers []api.ReplicationController
|
var controllers []api.ReplicationController
|
||||||
list, err := rm.kubeClient.ReplicationControllers(api.NamespaceAll).List(labels.Everything())
|
list, err := rm.kubeClient.ReplicationControllers(api.NamespaceAll).List(labels.Everything())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Synchronization error: %v (%#v)", err, err)
|
util.HandleError(fmt.Errorf("synchronization error: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
controllers = list.Items
|
controllers = list.Items
|
||||||
@ -211,7 +212,7 @@ func (rm *ReplicationManager) synchronize() {
|
|||||||
glog.V(4).Infof("periodic sync of %v", controllers[ix].Name)
|
glog.V(4).Infof("periodic sync of %v", controllers[ix].Name)
|
||||||
err := rm.syncHandler(controllers[ix])
|
err := rm.syncHandler(controllers[ix])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Errorf("Error synchronizing: %v", err)
|
util.HandleError(fmt.Errorf("error synchronizing: %v", err))
|
||||||
}
|
}
|
||||||
}(ix)
|
}(ix)
|
||||||
}
|
}
|
||||||
|
@ -34,26 +34,53 @@ import (
|
|||||||
// For testing, bypass HandleCrash.
|
// For testing, bypass HandleCrash.
|
||||||
var ReallyCrash bool
|
var ReallyCrash bool
|
||||||
|
|
||||||
|
// PanicHandlers is a list of functions which will be invoked when a panic happens.
|
||||||
|
var PanicHandlers = []func(interface{}){logPanic}
|
||||||
|
|
||||||
// HandleCrash simply catches a crash and logs an error. Meant to be called via defer.
|
// HandleCrash simply catches a crash and logs an error. Meant to be called via defer.
|
||||||
func HandleCrash() {
|
func HandleCrash() {
|
||||||
if ReallyCrash {
|
if ReallyCrash {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if r := recover(); r != nil {
|
||||||
r := recover()
|
for _, fn := range PanicHandlers {
|
||||||
if r != nil {
|
fn(r)
|
||||||
callers := ""
|
|
||||||
for i := 0; true; i++ {
|
|
||||||
_, file, line, ok := runtime.Caller(i)
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
callers = callers + fmt.Sprintf("%v:%v\n", file, line)
|
|
||||||
}
|
}
|
||||||
glog.Infof("Recovered from panic: %#v (%v)\n%v", r, r, callers)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// logPanic logs the caller tree when a panic occurs.
|
||||||
|
func logPanic(r interface{}) {
|
||||||
|
callers := ""
|
||||||
|
for i := 0; true; i++ {
|
||||||
|
_, file, line, ok := runtime.Caller(i)
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
callers = callers + fmt.Sprintf("%v:%v\n", file, line)
|
||||||
|
}
|
||||||
|
glog.Infof("Recovered from panic: %#v (%v)\n%v", r, r, callers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorHandlers is a list of functions which will be invoked when an unreturnable
|
||||||
|
// error occurs.
|
||||||
|
var ErrorHandlers = []func(error){logError}
|
||||||
|
|
||||||
|
// HandlerError is a method to invoke when a non-user facing piece of code cannot
|
||||||
|
// return an error and needs to indicate it has been ignored. Invoking this method
|
||||||
|
// is preferable to logging the error - the default behavior is to log but the
|
||||||
|
// errors may be sent to a remote server for analysis.
|
||||||
|
func HandleError(err error) {
|
||||||
|
for _, fn := range ErrorHandlers {
|
||||||
|
fn(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logError prints an error with the call stack of the location it was reported
|
||||||
|
func logError(err error) {
|
||||||
|
glog.ErrorDepth(2, err)
|
||||||
|
}
|
||||||
|
|
||||||
// Forever loops forever running f every period. Catches any panics, and keeps going.
|
// Forever loops forever running f every period. Catches any panics, and keeps going.
|
||||||
func Forever(f func(), period time.Duration) {
|
func Forever(f func(), period time.Duration) {
|
||||||
Until(f, period, nil)
|
Until(f, period, nil)
|
||||||
|
@ -18,6 +18,7 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -59,6 +60,40 @@ func TestHandleCrash(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCustomHandleCrash(t *testing.T) {
|
||||||
|
old := PanicHandlers
|
||||||
|
defer func() { PanicHandlers = old }()
|
||||||
|
var result interface{}
|
||||||
|
PanicHandlers = []func(interface{}){
|
||||||
|
func(r interface{}) {
|
||||||
|
result = r
|
||||||
|
},
|
||||||
|
}
|
||||||
|
func() {
|
||||||
|
defer HandleCrash()
|
||||||
|
panic("test")
|
||||||
|
}()
|
||||||
|
if result != "test" {
|
||||||
|
t.Errorf("did not receive custom handler")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCustomHandleError(t *testing.T) {
|
||||||
|
old := ErrorHandlers
|
||||||
|
defer func() { ErrorHandlers = old }()
|
||||||
|
var result error
|
||||||
|
ErrorHandlers = []func(error){
|
||||||
|
func(err error) {
|
||||||
|
result = err
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := fmt.Errorf("test")
|
||||||
|
HandleError(err)
|
||||||
|
if result != err {
|
||||||
|
t.Errorf("did not receive custom handler")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewIntOrStringFromInt(t *testing.T) {
|
func TestNewIntOrStringFromInt(t *testing.T) {
|
||||||
i := NewIntOrStringFromInt(93)
|
i := NewIntOrStringFromInt(93)
|
||||||
if i.Kind != IntstrInt || i.IntVal != 93 {
|
if i.Kind != IntstrInt || i.IntVal != 93 {
|
||||||
|
Loading…
Reference in New Issue
Block a user