volume recycler: Don't start a new recycler pod if one already exists.

Recycling is a long duration process and when the recycler controller is
restarted in the meantime, it should not start a new recycler pod if there is
one already running.

This means that the recycler pod must have deterministic name based on name
of the recycled PV, we then get name conflicts when creating the pod.

Two things need to be changed:
- recycler controller and recycler plugins must pass the PV.Name to place,
  where the pod is created.

- create recycler pod with deterministic name and check "already exists" error.

When at it, remove useless 'resourceVersion' argument and make log messages
starting with lowercase.
This commit is contained in:
Jan Safranek
2016-05-19 12:58:25 +02:00
parent c63ac4e664
commit 0ee9160f88
10 changed files with 125 additions and 43 deletions

View File

@@ -22,6 +22,7 @@ import (
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/resource"
)
@@ -29,7 +30,6 @@ func TestRecyclerSuccess(t *testing.T) {
client := &mockRecyclerClient{}
recycler := &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "recycler-test",
Namespace: api.NamespaceDefault,
},
Status: api.PodStatus{
@@ -37,7 +37,7 @@ func TestRecyclerSuccess(t *testing.T) {
},
}
err := internalRecycleVolumeByWatchingPodUntilCompletion(recycler, client)
err := internalRecycleVolumeByWatchingPodUntilCompletion("pv-name", recycler, client)
if err != nil {
t.Errorf("Unexpected error watching recycler pod: %+v", err)
}
@@ -50,7 +50,6 @@ func TestRecyclerFailure(t *testing.T) {
client := &mockRecyclerClient{}
recycler := &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "recycler-test",
Namespace: api.NamespaceDefault,
},
Status: api.PodStatus{
@@ -59,7 +58,7 @@ func TestRecyclerFailure(t *testing.T) {
},
}
err := internalRecycleVolumeByWatchingPodUntilCompletion(recycler, client)
err := internalRecycleVolumeByWatchingPodUntilCompletion("pv-name", recycler, client)
if err == nil {
t.Fatalf("Expected pod failure but got nil error returned")
}
@@ -73,14 +72,67 @@ func TestRecyclerFailure(t *testing.T) {
}
}
func TestRecyclerAlreadyExists(t *testing.T) {
// Test that internalRecycleVolumeByWatchingPodUntilCompletion does not
// start a new recycler when an old one is already running.
// Old recycler is running and fails with "foo" error message
oldRecycler := &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "recycler-test",
Namespace: api.NamespaceDefault,
},
Status: api.PodStatus{
Phase: api.PodFailed,
Message: "foo",
},
}
// New recycler _would_ succeed if it was run
newRecycler := &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "recycler-test",
Namespace: api.NamespaceDefault,
},
Status: api.PodStatus{
Phase: api.PodSucceeded,
Message: "bar",
},
}
client := &mockRecyclerClient{
pod: oldRecycler,
}
err := internalRecycleVolumeByWatchingPodUntilCompletion("pv-name", newRecycler, client)
if err == nil {
t.Fatalf("Expected pod failure but got nil error returned")
}
// Check the recycler failed with "foo" error message, i.e. it was the
// old recycler that finished and not the new one.
if err != nil {
if !strings.Contains(err.Error(), "foo") {
t.Errorf("Expected pod.Status.Message %s but got %s", oldRecycler.Status.Message, err)
}
}
if !client.deletedCalled {
t.Errorf("Expected deferred client.Delete to be called on recycler pod")
}
}
type mockRecyclerClient struct {
pod *api.Pod
deletedCalled bool
}
func (c *mockRecyclerClient) CreatePod(pod *api.Pod) (*api.Pod, error) {
c.pod = pod
return c.pod, nil
if c.pod == nil {
c.pod = pod
return c.pod, nil
}
// Simulate "already exists" error
return nil, errors.NewAlreadyExists(api.Resource("pods"), pod.Name)
}
func (c *mockRecyclerClient) GetPod(name, namespace string) (*api.Pod, error) {
@@ -96,7 +148,7 @@ func (c *mockRecyclerClient) DeletePod(name, namespace string) error {
return nil
}
func (c *mockRecyclerClient) WatchPod(name, namespace, resourceVersion string, stopChannel chan struct{}) func() *api.Pod {
func (c *mockRecyclerClient) WatchPod(name, namespace string, stopChannel chan struct{}) func() *api.Pod {
return func() *api.Pod {
return c.pod
}