diff --git a/errors/errors.go b/errors/errors.go new file mode 100644 index 000000000..1cbf01091 --- /dev/null +++ b/errors/errors.go @@ -0,0 +1,49 @@ +// Copyright © 2017 The virtual-kubelet authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import ( + "fmt" + "github.com/cpuguy83/strongerrors" +) + +// DeadlineWithMessage creates a "Deadline" error with the specified message and formatting arguments. +func DeadlineWithMessage(format string, args ...interface{}) error { + return strongerrors.Deadline(fmt.Errorf(format, args...)) +} + +// Exhausted creates an "Exhausted" error from the specified error. +func Exhausted(err error) error { + return strongerrors.Exhausted(err) +} + +// InvalidArgument creates an "InvalidArgument" error from the specified error. +func InvalidArgument(err error) error { + return strongerrors.InvalidArgument(err) +} + +// InvalidArgumentWithMessage creates an "InvalidArgument" error with the specified message and formatting arguments. +func InvalidArgumentWithMessage(format string, args ...interface{}) error { + return strongerrors.InvalidArgument(fmt.Errorf(format, args...)) +} + +// Unknown creates an "Unknown" error from the specified error and optional message and formatting arguments. +func Unknown(err error, args ...interface{}) error { + // If we've been given additional arguments, re-format the error + if len(args) >= 1 { + err = fmt.Errorf("%s: %s", fmt.Sprintf(args[0].(string), args[1:]...), err) + } + return strongerrors.Unknown(err) +} diff --git a/errors/errors_test.go b/errors/errors_test.go new file mode 100644 index 000000000..10e3a558c --- /dev/null +++ b/errors/errors_test.go @@ -0,0 +1,73 @@ +// Copyright © 2017 The virtual-kubelet authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package errors + +import ( + goerrors "errors" + "testing" + + "github.com/cpuguy83/strongerrors" +) + +func TestUnknownFromRoot(t *testing.T) { + // Create a root cause. + // This represents an error returned by a function invocation. + err1 := goerrors.New("root cause") + // Create an error of type "Unknown" with err1 as the cause and containing an additional message. + err2 := Unknown(err1) + + // Make sure that the resulting error is indeed of type "Unknown". + if !strongerrors.IsUnknown(err2) { + t.Fatal("expected err2 to be of type Unknown") + } + // Make sure that the error's message is equal to the root cause's message. + if err2.Error() != "root cause" { + t.Fatal("expected err2's error message to be equal to the root cause's message") + } +} + +func TestUnknownFromRootCauseWithMessage(t *testing.T) { + // Create a root cause. + // This represents an error returned by a function invocation. + err1 := goerrors.New("root cause") + // Create an error of type "Unknown" with err1 as the cause and containing an additional message. + err2 := Unknown(err1, "extra info") + + // Make sure that the resulting error is indeed of type "Unknown". + if !strongerrors.IsUnknown(err2) { + t.Fatal("expected err2 to be of type Unknown") + } + // Make sure that the message was adequately formatted. + if err2.Error() != "extra info: root cause" { + t.Fatal("expected err2 to be adequately formatted as a string") + } +} + +func TestUnknownFromRootCauseWithMessageAndFormattingParameters(t *testing.T) { + // Create a root cause. + // This represents an error returned by a function invocation. + err1 := goerrors.New("root cause") + // Create an error of type "Unknown" with err1 as the cause and containing an additional message. + err2 := Unknown(err1, "expected %d, got %d", 0, 1) + + // Make sure that the resulting error is indeed of type "Unknown". + if !strongerrors.IsUnknown(err2) { + t.Fatal("expected err2 to be of type Unknown") + } + // Make sure that the message was adequately formatted. + if err2.Error() != "expected 0, got 1: root cause" { + t.Fatal("expected err2 to be adequately formatted as a string") + } +} diff --git a/vkubelet/podcontroller.go b/vkubelet/podcontroller.go index 967ede7cc..4992aa41b 100644 --- a/vkubelet/podcontroller.go +++ b/vkubelet/podcontroller.go @@ -20,7 +20,6 @@ import ( "sync" "time" - pkgerrors "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/util/runtime" @@ -33,6 +32,7 @@ import ( "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" + vkerrors "github.com/virtual-kubelet/virtual-kubelet/errors" "github.com/virtual-kubelet/virtual-kubelet/log" ) @@ -112,7 +112,7 @@ func (pc *PodController) Run(ctx context.Context, threadiness int) error { // Wait for the caches to be synced before starting workers. if ok := cache.WaitForCacheSync(ctx.Done(), pc.podsInformer.Informer().HasSynced); !ok { - return pkgerrors.New("failed to wait for caches to sync") + return vkerrors.DeadlineWithMessage("failed to wait for caches to sync") } // Perform a reconciliation step that deletes any dangling pods from the provider. @@ -162,7 +162,7 @@ func (pc *PodController) processNextWorkItem() bool { if key, ok = obj.(string); !ok { // As the item in the work queue is actually invalid, we call Forget here else we'd go into a loop of attempting to process a work item that is invalid. pc.workqueue.Forget(obj) - runtime.HandleError(pkgerrors.Errorf("expected string in work queue but got %#v", obj)) + runtime.HandleError(vkerrors.InvalidArgumentWithMessage("expected string in work queue but got %#v", obj)) return nil } // Run the syncHandler, passing it the namespace/name string of the Pod resource to be synced. @@ -175,7 +175,7 @@ func (pc *PodController) processNextWorkItem() bool { } // We've exceeded the maximum retries, so we must forget the key. pc.workqueue.Forget(key) - return pkgerrors.Wrapf(err, "forgetting %q due to maximum retries reached", key) + return vkerrors.Exhausted(err) } // Finally, if no error occurs we Forget this item so it does not get queued again until another change happens. pc.workqueue.Forget(obj) @@ -196,7 +196,7 @@ func (pc *PodController) syncHandler(key string) error { namespace, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { // Log the error but do not requeue the key as it is invalid. - runtime.HandleError(pkgerrors.Wrapf(err, "invalid resource key: %q", key)) + runtime.HandleError(vkerrors.InvalidArgument(err)) return nil } @@ -209,7 +209,7 @@ func (pc *PodController) syncHandler(key string) error { if !errors.IsNotFound(err) { // We've failed to fetch the pod from the lister, but the error is not a 404. // Hence, we add the key back to the work queue so we can retry processing it later. - return pkgerrors.Wrapf(err, "failed to fetch pod with key %q from lister", key) + return vkerrors.Unknown(err, "failed to fetch pod with key %q from lister", key) } // At this point we know the Pod resource doesn't exist, which most probably means it was deleted. // Hence, we must delete it from the provider if it still exists there. @@ -229,7 +229,7 @@ func (pc *PodController) syncPodInProvider(ctx context.Context, pod *corev1.Pod) if pod.DeletionTimestamp != nil { // Delete the pod. if err := pc.server.deletePod(ctx, pod); err != nil { - return pkgerrors.Wrapf(err, "failed to delete pod %q in the provider", key) + return vkerrors.Unknown(err, "failed to delete pod %q in the provider", key) } return nil } @@ -250,7 +250,7 @@ func (pc *PodController) syncPodInProvider(ctx context.Context, pod *corev1.Pod) } // Create the pod in the provider. if err := pc.server.createPod(ctx, pod); err != nil { - return pkgerrors.Wrapf(err, "failed to create pod %q in the provider", key) + return vkerrors.Unknown(err, "failed to create pod %q in the provider", key) } return nil } @@ -273,7 +273,7 @@ func (pc *PodController) deletePodInProvider(ctx context.Context, namespace, nam // Delete the pod. if err := pc.server.deletePod(ctx, pod); err != nil { - return pkgerrors.Wrapf(err, "failed to delete pod %q in the provider", key) + return vkerrors.Unknown(err, "failed to delete pod %q in the provider", key) } return nil } @@ -283,7 +283,7 @@ func (pc *PodController) deleteDanglingPods(ctx context.Context) error { // Grab the list of pods known to the provider. pps, err := pc.server.provider.GetPods(ctx) if err != nil { - return pkgerrors.Wrap(err, "failed to fetch the list of pods from the provider") + return vkerrors.Unknown(err, "failed to fetch the list of pods from the provider") } // Create a slice to hold the pods we will be deleting from the provider. @@ -299,7 +299,7 @@ func (pc *PodController) deleteDanglingPods(ctx context.Context) error { continue } // For some reason we couldn't fetch the pod from the lister, so we propagate the error. - return pkgerrors.Wrap(err, "failed to fetch pod from the lister") + return vkerrors.Unknown(err, "failed to fetch pod from the lister") } }