Merge pull request #1192 from smarterclayton/standardize_etcd_errors

Return standard API errors from etcd registry by operation
pull/6/head
Daniel Smith 2014-09-08 15:57:08 -07:00
commit 41754f5bd4
4 changed files with 121 additions and 58 deletions

View File

@ -0,0 +1,18 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 etcd provides conversion of etcd errors to API errors.
package etcd

View File

@ -0,0 +1,66 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 etcd
import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/tools"
)
// InterpretGetError converts a generic etcd error on a retrieval
// operation into the appropriate API error.
func InterpretGetError(err error, kind, name string) error {
switch {
case tools.IsEtcdNotFound(err):
return errors.NewNotFound(kind, name)
default:
return err
}
}
// InterpretCreateError converts a generic etcd error on a create
// operation into the appropriate API error.
func InterpretCreateError(err error, kind, name string) error {
switch {
case tools.IsEtcdNodeExist(err):
return errors.NewAlreadyExists(kind, name)
default:
return err
}
}
// InterpretUpdateError converts a generic etcd error on a create
// operation into the appropriate API error.
func InterpretUpdateError(err error, kind, name string) error {
switch {
case tools.IsEtcdTestFailed(err), tools.IsEtcdNodeExist(err):
return errors.NewConflict(kind, name, err)
default:
return err
}
}
// InterpretDeleteError converts a generic etcd error on a create
// operation into the appropriate API error.
func InterpretDeleteError(err error, kind, name string) error {
switch {
case tools.IsEtcdNotFound(err):
return errors.NewNotFound(kind, name)
default:
return err
}
}

View File

@ -20,7 +20,7 @@ import (
"fmt" "fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" etcderr "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors/etcd"
"github.com/GoogleCloudPlatform/kubernetes/pkg/constraint" "github.com/GoogleCloudPlatform/kubernetes/pkg/constraint"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
@ -96,7 +96,7 @@ func (r *Registry) WatchPods(resourceVersion uint64, filter func(*api.Pod) bool)
func (r *Registry) GetPod(podID string) (*api.Pod, error) { func (r *Registry) GetPod(podID string) (*api.Pod, error) {
var pod api.Pod var pod api.Pod
if err := r.ExtractObj(makePodKey(podID), &pod, false); err != nil { if err := r.ExtractObj(makePodKey(podID), &pod, false); err != nil {
return nil, err return nil, etcderr.InterpretGetError(err, "pod", podID)
} }
// TODO: Currently nothing sets CurrentState.Host. We need a feedback loop that sets // TODO: Currently nothing sets CurrentState.Host. We need a feedback loop that sets
// the CurrentState.Host and Status fields. Here we pretend that reality perfectly // the CurrentState.Host and Status fields. Here we pretend that reality perfectly
@ -117,12 +117,13 @@ func (r *Registry) CreatePod(pod *api.Pod) error {
// DesiredState.Host == "" is a signal to the scheduler that this pod needs scheduling. // DesiredState.Host == "" is a signal to the scheduler that this pod needs scheduling.
pod.DesiredState.Status = api.PodRunning pod.DesiredState.Status = api.PodRunning
pod.DesiredState.Host = "" pod.DesiredState.Host = ""
return r.CreateObj(makePodKey(pod.ID), pod) err := r.CreateObj(makePodKey(pod.ID), pod)
return etcderr.InterpretCreateError(err, "pod", pod.ID)
} }
// ApplyBinding implements binding's registry // ApplyBinding implements binding's registry
func (r *Registry) ApplyBinding(binding *api.Binding) error { func (r *Registry) ApplyBinding(binding *api.Binding) error {
return r.assignPod(binding.PodID, binding.Host) return etcderr.InterpretCreateError(r.assignPod(binding.PodID, binding.Host), "binding", "")
} }
// setPodHostTo sets the given pod's host to 'machine' iff it was previously 'oldMachine'. // setPodHostTo sets the given pod's host to 'machine' iff it was previously 'oldMachine'.
@ -183,20 +184,14 @@ func (r *Registry) DeletePod(podID string) error {
var pod api.Pod var pod api.Pod
podKey := makePodKey(podID) podKey := makePodKey(podID)
err := r.ExtractObj(podKey, &pod, false) err := r.ExtractObj(podKey, &pod, false)
if tools.IsEtcdNotFound(err) {
return errors.NewNotFound("pod", podID)
}
if err != nil { if err != nil {
return err return etcderr.InterpretDeleteError(err, "pod", podID)
} }
// First delete the pod, so a scheduler doesn't notice it getting removed from the // First delete the pod, so a scheduler doesn't notice it getting removed from the
// machine and attempt to put it somewhere. // machine and attempt to put it somewhere.
err = r.Delete(podKey, true) err = r.Delete(podKey, true)
if tools.IsEtcdNotFound(err) {
return errors.NewNotFound("pod", podID)
}
if err != nil { if err != nil {
return err return etcderr.InterpretDeleteError(err, "pod", podID)
} }
machine := pod.DesiredState.Host machine := pod.DesiredState.Host
if machine == "" { if machine == "" {
@ -248,11 +243,8 @@ func (r *Registry) GetController(controllerID string) (*api.ReplicationControlle
var controller api.ReplicationController var controller api.ReplicationController
key := makeControllerKey(controllerID) key := makeControllerKey(controllerID)
err := r.ExtractObj(key, &controller, false) err := r.ExtractObj(key, &controller, false)
if tools.IsEtcdNotFound(err) {
return nil, errors.NewNotFound("replicationController", controllerID)
}
if err != nil { if err != nil {
return nil, err return nil, etcderr.InterpretGetError(err, "replicationController", controllerID)
} }
return &controller, nil return &controller, nil
} }
@ -260,25 +252,20 @@ func (r *Registry) GetController(controllerID string) (*api.ReplicationControlle
// CreateController creates a new ReplicationController. // CreateController creates a new ReplicationController.
func (r *Registry) CreateController(controller *api.ReplicationController) error { func (r *Registry) CreateController(controller *api.ReplicationController) error {
err := r.CreateObj(makeControllerKey(controller.ID), controller) err := r.CreateObj(makeControllerKey(controller.ID), controller)
if tools.IsEtcdNodeExist(err) { return etcderr.InterpretCreateError(err, "replicationController", controller.ID)
return errors.NewAlreadyExists("replicationController", controller.ID)
}
return err
} }
// UpdateController replaces an existing ReplicationController. // UpdateController replaces an existing ReplicationController.
func (r *Registry) UpdateController(controller *api.ReplicationController) error { func (r *Registry) UpdateController(controller *api.ReplicationController) error {
return r.SetObj(makeControllerKey(controller.ID), controller) err := r.SetObj(makeControllerKey(controller.ID), controller)
return etcderr.InterpretUpdateError(err, "replicationController", controller.ID)
} }
// DeleteController deletes a ReplicationController specified by its ID. // DeleteController deletes a ReplicationController specified by its ID.
func (r *Registry) DeleteController(controllerID string) error { func (r *Registry) DeleteController(controllerID string) error {
key := makeControllerKey(controllerID) key := makeControllerKey(controllerID)
err := r.Delete(key, false) err := r.Delete(key, false)
if tools.IsEtcdNotFound(err) { return etcderr.InterpretDeleteError(err, "replicationController", controllerID)
return errors.NewNotFound("replicationController", controllerID)
}
return err
} }
func makeServiceKey(name string) string { func makeServiceKey(name string) string {
@ -295,10 +282,7 @@ func (r *Registry) ListServices() (*api.ServiceList, error) {
// CreateService creates a new Service. // CreateService creates a new Service.
func (r *Registry) CreateService(svc *api.Service) error { func (r *Registry) CreateService(svc *api.Service) error {
err := r.CreateObj(makeServiceKey(svc.ID), svc) err := r.CreateObj(makeServiceKey(svc.ID), svc)
if tools.IsEtcdNodeExist(err) { return etcderr.InterpretCreateError(err, "service", svc.ID)
return errors.NewAlreadyExists("service", svc.ID)
}
return err
} }
// GetService obtains a Service specified by its name. // GetService obtains a Service specified by its name.
@ -306,11 +290,8 @@ func (r *Registry) GetService(name string) (*api.Service, error) {
key := makeServiceKey(name) key := makeServiceKey(name)
var svc api.Service var svc api.Service
err := r.ExtractObj(key, &svc, false) err := r.ExtractObj(key, &svc, false)
if tools.IsEtcdNotFound(err) {
return nil, errors.NewNotFound("service", name)
}
if err != nil { if err != nil {
return nil, err return nil, etcderr.InterpretGetError(err, "service", name)
} }
return &svc, nil return &svc, nil
} }
@ -320,11 +301,8 @@ func (r *Registry) GetEndpoints(name string) (*api.Endpoints, error) {
key := makeServiceEndpointsKey(name) key := makeServiceEndpointsKey(name)
var endpoints api.Endpoints var endpoints api.Endpoints
err := r.ExtractObj(key, &endpoints, false) err := r.ExtractObj(key, &endpoints, false)
if tools.IsEtcdNotFound(err) {
return nil, errors.NewNotFound("endpoints", name)
}
if err != nil { if err != nil {
return nil, err return nil, etcderr.InterpretGetError(err, "endpoints", name)
} }
return &endpoints, nil return &endpoints, nil
} }
@ -337,23 +315,23 @@ func makeServiceEndpointsKey(name string) string {
func (r *Registry) DeleteService(name string) error { func (r *Registry) DeleteService(name string) error {
key := makeServiceKey(name) key := makeServiceKey(name)
err := r.Delete(key, true) err := r.Delete(key, true)
if tools.IsEtcdNotFound(err) {
return errors.NewNotFound("service", name)
}
if err != nil { if err != nil {
return err return etcderr.InterpretDeleteError(err, "service", name)
} }
// TODO: can leave dangling endpoints, and potentially return incorrect
// endpoints if a new service is created with the same name
key = makeServiceEndpointsKey(name) key = makeServiceEndpointsKey(name)
err = r.Delete(key, true) if err := r.Delete(key, true); err != nil && !tools.IsEtcdNotFound(err) {
if !tools.IsEtcdNotFound(err) { return etcderr.InterpretDeleteError(err, "endpoints", name)
return err
} }
return nil return nil
} }
// UpdateService replaces an existing Service. // UpdateService replaces an existing Service.
func (r *Registry) UpdateService(svc *api.Service) error { func (r *Registry) UpdateService(svc *api.Service) error {
return r.SetObj(makeServiceKey(svc.ID), svc) err := r.SetObj(makeServiceKey(svc.ID), svc)
return etcderr.InterpretUpdateError(err, "service", svc.ID)
} }
// WatchServices begins watching for new, changed, or deleted service configurations. // WatchServices begins watching for new, changed, or deleted service configurations.
@ -380,11 +358,12 @@ func (r *Registry) ListEndpoints() (*api.EndpointsList, error) {
// UpdateEndpoints update Endpoints of a Service. // UpdateEndpoints update Endpoints of a Service.
func (r *Registry) UpdateEndpoints(e *api.Endpoints) error { func (r *Registry) UpdateEndpoints(e *api.Endpoints) error {
// TODO: this is a really bad misuse of AtomicUpdate, need to compute a diff inside the loop. // TODO: this is a really bad misuse of AtomicUpdate, need to compute a diff inside the loop.
return r.AtomicUpdate(makeServiceEndpointsKey(e.ID), &api.Endpoints{}, err := r.AtomicUpdate(makeServiceEndpointsKey(e.ID), &api.Endpoints{},
func(input runtime.Object) (runtime.Object, error) { func(input runtime.Object) (runtime.Object, error) {
// TODO: racy - label query is returning different results for two simultaneous updaters // TODO: racy - label query is returning different results for two simultaneous updaters
return e, nil return e, nil
}) })
return etcderr.InterpretUpdateError(err, "endpoints", e.ID)
} }
// WatchEndpoints begins watching for new, changed, or deleted endpoint configurations. // WatchEndpoints begins watching for new, changed, or deleted endpoint configurations.

View File

@ -63,8 +63,8 @@ func TestEtcdGetPodNotFound(t *testing.T) {
} }
registry := NewTestEtcdRegistry(fakeClient) registry := NewTestEtcdRegistry(fakeClient)
_, err := registry.GetPod("foo") _, err := registry.GetPod("foo")
if err == nil { if !errors.IsNotFound(err) {
t.Errorf("Unexpected non-error.") t.Errorf("Unexpected error returned: %#v", err)
} }
} }
@ -144,8 +144,8 @@ func TestEtcdCreatePodAlreadyExisting(t *testing.T) {
ID: "foo", ID: "foo",
}, },
}) })
if err == nil { if !errors.IsAlreadyExists(err) {
t.Error("Unexpected non-error") t.Errorf("Unexpected error returned: %#v", err)
} }
} }
@ -162,7 +162,7 @@ func TestEtcdCreatePodWithContainersError(t *testing.T) {
R: &etcd.Response{ R: &etcd.Response{
Node: nil, Node: nil,
}, },
E: tools.EtcdErrorValueRequired, E: tools.EtcdErrorNodeExist, // validate that ApplyBinding is translating Create errors
} }
registry := NewTestEtcdRegistry(fakeClient) registry := NewTestEtcdRegistry(fakeClient)
err := registry.CreatePod(&api.Pod{ err := registry.CreatePod(&api.Pod{
@ -176,8 +176,8 @@ func TestEtcdCreatePodWithContainersError(t *testing.T) {
// Suddenly, a wild scheduler appears: // Suddenly, a wild scheduler appears:
err = registry.ApplyBinding(&api.Binding{PodID: "foo", Host: "machine"}) err = registry.ApplyBinding(&api.Binding{PodID: "foo", Host: "machine"})
if err == nil { if !errors.IsAlreadyExists(err) {
t.Fatalf("Unexpected non error.") t.Fatalf("Unexpected error returned: %#v", err)
} }
existingPod, err := registry.GetPod("foo") existingPod, err := registry.GetPod("foo")
@ -185,7 +185,7 @@ func TestEtcdCreatePodWithContainersError(t *testing.T) {
t.Fatalf("Unexpected error: %v", err) t.Fatalf("Unexpected error: %v", err)
} }
if existingPod.DesiredState.Host == "machine" { if existingPod.DesiredState.Host == "machine" {
t.Fatal("Pod's host changed in response to an unappliable binding.") t.Fatal("Pod's host changed in response to an non-apply-able binding.")
} }
} }
@ -568,8 +568,8 @@ func TestEtcdGetControllerNotFound(t *testing.T) {
if ctrl != nil { if ctrl != nil {
t.Errorf("Unexpected non-nil controller: %#v", ctrl) t.Errorf("Unexpected non-nil controller: %#v", ctrl)
} }
if err == nil { if !errors.IsNotFound(err) {
t.Error("Unexpected non-error.") t.Errorf("Unexpected error returned: %#v", err)
} }
} }
@ -745,8 +745,8 @@ func TestEtcdGetServiceNotFound(t *testing.T) {
} }
registry := NewTestEtcdRegistry(fakeClient) registry := NewTestEtcdRegistry(fakeClient)
_, err := registry.GetService("foo") _, err := registry.GetService("foo")
if err == nil { if !errors.IsNotFound(err) {
t.Errorf("Unexpected non-error.") t.Errorf("Unexpected error returned: %#v", err)
} }
} }