Use narrowly scoped interfaces for client access

Use custom narrowly scoped interfaces for client access from the
RollingUpdater and Resizer. This allows for more flexible downstream
integration and unit testing without imposing a burden to implement
the entire client.Interface for just a handful of methods.
This commit is contained in:
Dan Mace 2015-04-15 14:28:59 -04:00
parent d912398d07
commit 312ccad3c1
7 changed files with 81 additions and 25 deletions

View File

@ -111,7 +111,7 @@ func RunRollingUpdate(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, arg
return err return err
} }
updater := kubectl.NewRollingUpdater(newRc.Namespace, client) updater := kubectl.NewRollingUpdater(newRc.Namespace, &kubectl.RealRollingUpdaterClient{client})
// fetch rc // fetch rc
oldRc, err := client.ReplicationControllers(newRc.Namespace).Get(oldName) oldRc, err := client.ReplicationControllers(newRc.Namespace).Get(oldName)

View File

@ -197,7 +197,7 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return kubectl.ResizerFor(mapping.Kind, client) return kubectl.ResizerFor(mapping.Kind, &kubectl.RealResizerClient{client})
}, },
Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { Reaper: func(mapping *meta.RESTMapping) (kubectl.Reaper, error) {
client, err := clients.ClientForVersion(mapping.APIVersion) client, err := clients.ClientForVersion(mapping.APIVersion)

View File

@ -89,7 +89,7 @@ type Resizer interface {
ResizeSimple(namespace, name string, preconditions *ResizePrecondition, newSize uint) (string, error) ResizeSimple(namespace, name string, preconditions *ResizePrecondition, newSize uint) (string, error)
} }
func ResizerFor(kind string, c client.Interface) (Resizer, error) { func ResizerFor(kind string, c ResizerClient) (Resizer, error) {
switch kind { switch kind {
case "ReplicationController": case "ReplicationController":
return &ReplicationControllerResizer{c}, nil return &ReplicationControllerResizer{c}, nil
@ -98,7 +98,7 @@ func ResizerFor(kind string, c client.Interface) (Resizer, error) {
} }
type ReplicationControllerResizer struct { type ReplicationControllerResizer struct {
client.Interface c ResizerClient
} }
type RetryParams struct { type RetryParams struct {
@ -122,8 +122,7 @@ func ResizeCondition(r Resizer, precondition *ResizePrecondition, namespace, nam
} }
func (resizer *ReplicationControllerResizer) ResizeSimple(namespace, name string, preconditions *ResizePrecondition, newSize uint) (string, error) { func (resizer *ReplicationControllerResizer) ResizeSimple(namespace, name string, preconditions *ResizePrecondition, newSize uint) (string, error) {
rc := resizer.ReplicationControllers(namespace) controller, err := resizer.c.GetReplicationController(namespace, name)
controller, err := rc.Get(name)
if err != nil { if err != nil {
return "", ControllerResizeError{ControllerResizeGetFailure, "Unknown", err} return "", ControllerResizeError{ControllerResizeGetFailure, "Unknown", err}
} }
@ -134,7 +133,7 @@ func (resizer *ReplicationControllerResizer) ResizeSimple(namespace, name string
} }
controller.Spec.Replicas = int(newSize) controller.Spec.Replicas = int(newSize)
// TODO: do retry on 409 errors here? // TODO: do retry on 409 errors here?
if _, err := rc.Update(controller); err != nil { if _, err := resizer.c.UpdateReplicationController(namespace, controller); err != nil {
return "", ControllerResizeError{ControllerResizeUpdateFailure, controller.ResourceVersion, err} return "", ControllerResizeError{ControllerResizeUpdateFailure, controller.ResourceVersion, err}
} }
// TODO: do a better job of printing objects here. // TODO: do a better job of printing objects here.
@ -159,9 +158,33 @@ func (resizer *ReplicationControllerResizer) Resize(namespace, name string, newS
if waitForReplicas != nil { if waitForReplicas != nil {
rc := &api.ReplicationController{ObjectMeta: api.ObjectMeta{Namespace: namespace, Name: name}} rc := &api.ReplicationController{ObjectMeta: api.ObjectMeta{Namespace: namespace, Name: name}}
if err := wait.Poll(waitForReplicas.interval, waitForReplicas.timeout, if err := wait.Poll(waitForReplicas.interval, waitForReplicas.timeout,
client.ControllerHasDesiredReplicas(resizer, rc)); err != nil { resizer.c.ControllerHasDesiredReplicas(rc)); err != nil {
return err return err
} }
} }
return nil return nil
} }
// ResizerClient abstracts access to ReplicationControllers.
type ResizerClient interface {
GetReplicationController(namespace, name string) (*api.ReplicationController, error)
UpdateReplicationController(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error)
ControllerHasDesiredReplicas(rc *api.ReplicationController) wait.ConditionFunc
}
// RealResizerClient is a ResizerClient which uses a Kube client.
type RealResizerClient struct {
Client client.Interface
}
func (c *RealResizerClient) GetReplicationController(namespace, name string) (*api.ReplicationController, error) {
return c.Client.ReplicationControllers(namespace).Get(name)
}
func (c *RealResizerClient) UpdateReplicationController(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) {
return c.Client.ReplicationControllers(namespace).Update(rc)
}
func (c *RealResizerClient) ControllerHasDesiredReplicas(rc *api.ReplicationController) wait.ConditionFunc {
return client.ControllerHasDesiredReplicas(c.Client, rc)
}

View File

@ -43,7 +43,7 @@ func (c *ErrorReplicationControllerClient) ReplicationControllers(namespace stri
func TestReplicationControllerResizeRetry(t *testing.T) { func TestReplicationControllerResizeRetry(t *testing.T) {
fake := &ErrorReplicationControllerClient{Fake: testclient.Fake{}} fake := &ErrorReplicationControllerClient{Fake: testclient.Fake{}}
resizer := ReplicationControllerResizer{fake} resizer := ReplicationControllerResizer{&RealResizerClient{fake}}
preconditions := ResizePrecondition{-1, ""} preconditions := ResizePrecondition{-1, ""}
count := uint(3) count := uint(3)
name := "foo" name := "foo"
@ -67,7 +67,7 @@ func TestReplicationControllerResizeRetry(t *testing.T) {
func TestReplicationControllerResize(t *testing.T) { func TestReplicationControllerResize(t *testing.T) {
fake := &testclient.Fake{} fake := &testclient.Fake{}
resizer := ReplicationControllerResizer{fake} resizer := ReplicationControllerResizer{&RealResizerClient{fake}}
preconditions := ResizePrecondition{-1, ""} preconditions := ResizePrecondition{-1, ""}
count := uint(3) count := uint(3)
name := "foo" name := "foo"
@ -90,7 +90,7 @@ func TestReplicationControllerResizeFailsPreconditions(t *testing.T) {
Replicas: 10, Replicas: 10,
}, },
}) })
resizer := ReplicationControllerResizer{fake} resizer := ReplicationControllerResizer{&RealResizerClient{fake}}
preconditions := ResizePrecondition{2, ""} preconditions := ResizePrecondition{2, ""}
count := uint(3) count := uint(3)
name := "foo" name := "foo"

View File

@ -31,13 +31,13 @@ import (
// fault-tolerant way. // fault-tolerant way.
type RollingUpdater struct { type RollingUpdater struct {
// Client interface for creating and updating controllers // Client interface for creating and updating controllers
c client.Interface c RollingUpdaterClient
// Namespace for resources // Namespace for resources
ns string ns string
} }
// NewRollingUpdater creates a RollingUpdater from a client // NewRollingUpdater creates a RollingUpdater from a client
func NewRollingUpdater(namespace string, c client.Interface) *RollingUpdater { func NewRollingUpdater(namespace string, c RollingUpdaterClient) *RollingUpdater {
return &RollingUpdater{ return &RollingUpdater{
c, c,
namespace, namespace,
@ -95,7 +95,7 @@ func (r *RollingUpdater) Update(out io.Writer, oldRc, newRc *api.ReplicationCont
newRc.ObjectMeta.Annotations[desiredReplicasAnnotation] = fmt.Sprintf("%d", desired) newRc.ObjectMeta.Annotations[desiredReplicasAnnotation] = fmt.Sprintf("%d", desired)
newRc.ObjectMeta.Annotations[sourceIdAnnotation] = sourceId newRc.ObjectMeta.Annotations[sourceIdAnnotation] = sourceId
newRc.Spec.Replicas = 0 newRc.Spec.Replicas = 0
newRc, err = r.c.ReplicationControllers(r.ns).Create(newRc) newRc, err = r.c.CreateReplicationController(r.ns, newRc)
if err != nil { if err != nil {
return err return err
} }
@ -147,7 +147,7 @@ func (r *RollingUpdater) Update(out io.Writer, oldRc, newRc *api.ReplicationCont
} }
} }
// Clean up annotations // Clean up annotations
if newRc, err = r.c.ReplicationControllers(r.ns).Get(newName); err != nil { if newRc, err = r.c.GetReplicationController(r.ns, newName); err != nil {
return err return err
} }
delete(newRc.ObjectMeta.Annotations, sourceIdAnnotation) delete(newRc.ObjectMeta.Annotations, sourceIdAnnotation)
@ -158,11 +158,11 @@ func (r *RollingUpdater) Update(out io.Writer, oldRc, newRc *api.ReplicationCont
} }
// delete old rc // delete old rc
fmt.Fprintf(out, "Update succeeded. Deleting %s\n", oldName) fmt.Fprintf(out, "Update succeeded. Deleting %s\n", oldName)
return r.c.ReplicationControllers(r.ns).Delete(oldName) return r.c.DeleteReplicationController(r.ns, oldName)
} }
func (r *RollingUpdater) getExistingNewRc(sourceId, name string) (rc *api.ReplicationController, existing bool, err error) { func (r *RollingUpdater) getExistingNewRc(sourceId, name string) (rc *api.ReplicationController, existing bool, err error) {
if rc, err = r.c.ReplicationControllers(r.ns).Get(name); err == nil { if rc, err = r.c.GetReplicationController(r.ns, name); err == nil {
existing = true existing = true
annotations := rc.ObjectMeta.Annotations annotations := rc.ObjectMeta.Annotations
source := annotations[sourceIdAnnotation] source := annotations[sourceIdAnnotation]
@ -184,17 +184,50 @@ func (r *RollingUpdater) resizeAndWait(rc *api.ReplicationController, retry *Ret
if err := resizer.Resize(rc.Namespace, rc.Name, uint(rc.Spec.Replicas), &ResizePrecondition{-1, ""}, retry, wait); err != nil { if err := resizer.Resize(rc.Namespace, rc.Name, uint(rc.Spec.Replicas), &ResizePrecondition{-1, ""}, retry, wait); err != nil {
return nil, err return nil, err
} }
return r.c.ReplicationControllers(r.ns).Get(rc.ObjectMeta.Name) return r.c.GetReplicationController(r.ns, rc.ObjectMeta.Name)
} }
func (r *RollingUpdater) updateAndWait(rc *api.ReplicationController, interval, timeout time.Duration) (*api.ReplicationController, error) { func (r *RollingUpdater) updateAndWait(rc *api.ReplicationController, interval, timeout time.Duration) (*api.ReplicationController, error) {
rc, err := r.c.ReplicationControllers(r.ns).Update(rc) rc, err := r.c.UpdateReplicationController(r.ns, rc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err = wait.Poll(interval, timeout, if err = wait.Poll(interval, timeout, r.c.ControllerHasDesiredReplicas(rc)); err != nil {
client.ControllerHasDesiredReplicas(r.c, rc)); err != nil {
return nil, err return nil, err
} }
return r.c.ReplicationControllers(r.ns).Get(rc.ObjectMeta.Name) return r.c.GetReplicationController(r.ns, rc.ObjectMeta.Name)
}
// RollingUpdaterClient abstracts access to ReplicationControllers.
type RollingUpdaterClient interface {
GetReplicationController(namespace, name string) (*api.ReplicationController, error)
UpdateReplicationController(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error)
CreateReplicationController(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error)
DeleteReplicationController(namespace, name string) error
ControllerHasDesiredReplicas(rc *api.ReplicationController) wait.ConditionFunc
}
// RealRollingUpdaterClient is a RollingUpdaterClient which uses a Kube client.
type RealRollingUpdaterClient struct {
Client client.Interface
}
func (c *RealRollingUpdaterClient) GetReplicationController(namespace, name string) (*api.ReplicationController, error) {
return c.Client.ReplicationControllers(namespace).Get(name)
}
func (c *RealRollingUpdaterClient) UpdateReplicationController(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) {
return c.Client.ReplicationControllers(namespace).Update(rc)
}
func (c *RealRollingUpdaterClient) CreateReplicationController(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) {
return c.Client.ReplicationControllers(namespace).Create(rc)
}
func (c *RealRollingUpdaterClient) DeleteReplicationController(namespace, name string) error {
return c.Client.ReplicationControllers(namespace).Delete(name)
}
func (c *RealRollingUpdaterClient) ControllerHasDesiredReplicas(rc *api.ReplicationController) wait.ConditionFunc {
return client.ControllerHasDesiredReplicas(c.Client, rc)
} }

View File

@ -253,7 +253,7 @@ Update succeeded. Deleting foo-v1
for _, test := range tests { for _, test := range tests {
updater := RollingUpdater{ updater := RollingUpdater{
fakeClientFor("default", test.responses), &RealRollingUpdaterClient{fakeClientFor("default", test.responses)},
"default", "default",
} }
var buffer bytes.Buffer var buffer bytes.Buffer
@ -296,7 +296,7 @@ Update succeeded. Deleting foo-v1
{newRc(3, 3), nil}, {newRc(3, 3), nil},
{newRc(3, 3), nil}, {newRc(3, 3), nil},
} }
updater := RollingUpdater{fakeClientFor("default", responses), "default"} updater := RollingUpdater{&RealRollingUpdaterClient{fakeClientFor("default", responses)}, "default"}
var buffer bytes.Buffer var buffer bytes.Buffer
if err := updater.Update(&buffer, rc, rcExisting, 0, time.Millisecond, time.Millisecond); err != nil { if err := updater.Update(&buffer, rc, rcExisting, 0, time.Millisecond, time.Millisecond); err != nil {

View File

@ -65,7 +65,7 @@ type objInterface interface {
func (reaper *ReplicationControllerReaper) Stop(namespace, name string) (string, error) { func (reaper *ReplicationControllerReaper) Stop(namespace, name string) (string, error) {
rc := reaper.ReplicationControllers(namespace) rc := reaper.ReplicationControllers(namespace)
resizer, err := ResizerFor("ReplicationController", *reaper) resizer, err := ResizerFor("ReplicationController", &RealResizerClient{*reaper})
if err != nil { if err != nil {
return "", err return "", err
} }