Support recovery from in the middle of a rename.
This commit is contained in:
@@ -19,13 +19,21 @@ package kubectl
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/testapi"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/testclient"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
@@ -380,8 +388,430 @@ func TestRollingUpdater_preserveCleanup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRename(t *testing.T) {
|
||||
tests := []struct {
|
||||
namespace string
|
||||
newName string
|
||||
oldName string
|
||||
err error
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
namespace: "default",
|
||||
newName: "bar",
|
||||
oldName: "foo",
|
||||
},
|
||||
{
|
||||
namespace: "default",
|
||||
newName: "bar",
|
||||
oldName: "foo",
|
||||
err: fmt.Errorf("Test Error"),
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
fakeClient := &rollingUpdaterClientImpl{
|
||||
CreateReplicationControllerFn: func(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error) {
|
||||
if namespace != test.namespace {
|
||||
t.Errorf("unexepected namespace: %s, expected %s", namespace, test.namespace)
|
||||
}
|
||||
if rc.Name != test.newName {
|
||||
t.Errorf("unexepected name: %s, expected %s", rc.Name, test.newName)
|
||||
}
|
||||
return rc, test.err
|
||||
},
|
||||
DeleteReplicationControllerFn: func(namespace, name string) error {
|
||||
if namespace != test.namespace {
|
||||
t.Errorf("unexepected namespace: %s, expected %s", namespace, test.namespace)
|
||||
}
|
||||
if name != test.oldName {
|
||||
t.Errorf("unexepected name: %s, expected %s", name, test.oldName)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
err := Rename(fakeClient, &api.ReplicationController{ObjectMeta: api.ObjectMeta{Namespace: test.namespace, Name: test.oldName}}, test.newName)
|
||||
if err != nil && !test.expectError {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if err == nil && test.expectError {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindSourceController(t *testing.T) {
|
||||
ctrl1 := api.ReplicationController{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "foo",
|
||||
Annotations: map[string]string{
|
||||
sourceIdAnnotation: "bar:1234",
|
||||
},
|
||||
},
|
||||
}
|
||||
ctrl2 := api.ReplicationController{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: "bar",
|
||||
Annotations: map[string]string{
|
||||
sourceIdAnnotation: "foo:12345",
|
||||
},
|
||||
},
|
||||
}
|
||||
ctrl3 := api.ReplicationController{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
sourceIdAnnotation: "baz:45667",
|
||||
},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
list *api.ReplicationControllerList
|
||||
expectedController *api.ReplicationController
|
||||
err error
|
||||
name string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
list: &api.ReplicationControllerList{},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
list: &api.ReplicationControllerList{
|
||||
Items: []api.ReplicationController{ctrl1},
|
||||
},
|
||||
name: "foo",
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
list: &api.ReplicationControllerList{
|
||||
Items: []api.ReplicationController{ctrl1},
|
||||
},
|
||||
name: "bar",
|
||||
expectedController: &ctrl1,
|
||||
},
|
||||
{
|
||||
list: &api.ReplicationControllerList{
|
||||
Items: []api.ReplicationController{ctrl1, ctrl2},
|
||||
},
|
||||
name: "bar",
|
||||
expectedController: &ctrl1,
|
||||
},
|
||||
{
|
||||
list: &api.ReplicationControllerList{
|
||||
Items: []api.ReplicationController{ctrl1, ctrl2},
|
||||
},
|
||||
name: "foo",
|
||||
expectedController: &ctrl2,
|
||||
},
|
||||
{
|
||||
list: &api.ReplicationControllerList{
|
||||
Items: []api.ReplicationController{ctrl1, ctrl2, ctrl3},
|
||||
},
|
||||
name: "baz",
|
||||
expectedController: &ctrl3,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
fakeClient := rollingUpdaterClientImpl{
|
||||
ListReplicationControllersFn: func(namespace string, selector labels.Selector) (*api.ReplicationControllerList, error) {
|
||||
return test.list, test.err
|
||||
},
|
||||
}
|
||||
ctrl, err := FindSourceController(&fakeClient, "default", test.name)
|
||||
if test.expectError && err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
if !test.expectError && err != nil {
|
||||
t.Errorf("unexpected error")
|
||||
}
|
||||
if !reflect.DeepEqual(ctrl, test.expectedController) {
|
||||
t.Errorf("expected:\n%v\ngot:\n%v\n", test.expectedController, ctrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateExistingReplicationController(t *testing.T) {
|
||||
tests := []struct {
|
||||
rc *api.ReplicationController
|
||||
name string
|
||||
deploymentKey string
|
||||
deploymentValue string
|
||||
|
||||
expectedRc *api.ReplicationController
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
rc: &api.ReplicationController{
|
||||
Spec: api.ReplicationControllerSpec{
|
||||
Template: &api.PodTemplateSpec{},
|
||||
},
|
||||
},
|
||||
name: "foo",
|
||||
deploymentKey: "dk",
|
||||
deploymentValue: "some-hash",
|
||||
|
||||
expectedRc: &api.ReplicationController{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"kubectl.kubernetes.io/next-controller-id": "foo",
|
||||
},
|
||||
},
|
||||
Spec: api.ReplicationControllerSpec{
|
||||
Selector: map[string]string{
|
||||
"dk": "some-hash",
|
||||
},
|
||||
Template: &api.PodTemplateSpec{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"dk": "some-hash",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
rc: &api.ReplicationController{
|
||||
Spec: api.ReplicationControllerSpec{
|
||||
Template: &api.PodTemplateSpec{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"dk": "some-other-hash",
|
||||
},
|
||||
},
|
||||
},
|
||||
Selector: map[string]string{
|
||||
"dk": "some-other-hash",
|
||||
},
|
||||
},
|
||||
},
|
||||
name: "foo",
|
||||
deploymentKey: "dk",
|
||||
deploymentValue: "some-hash",
|
||||
|
||||
expectedRc: &api.ReplicationController{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"kubectl.kubernetes.io/next-controller-id": "foo",
|
||||
},
|
||||
},
|
||||
Spec: api.ReplicationControllerSpec{
|
||||
Selector: map[string]string{
|
||||
"dk": "some-other-hash",
|
||||
},
|
||||
Template: &api.PodTemplateSpec{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"dk": "some-other-hash",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
buffer := &bytes.Buffer{}
|
||||
fakeClient := fakeClientFor("default", []fakeResponse{})
|
||||
rc, err := UpdateExistingReplicationController(fakeClient, test.rc, "default", test.name, test.deploymentKey, test.deploymentValue, buffer)
|
||||
if !reflect.DeepEqual(rc, test.expectedRc) {
|
||||
t.Errorf("expected:\n%#v\ngot:\n%#v\n", test.expectedRc, rc)
|
||||
}
|
||||
if test.expectErr && err == nil {
|
||||
t.Errorf("unexpected non-error")
|
||||
}
|
||||
if !test.expectErr && err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateWithRetries(t *testing.T) {
|
||||
codec := testapi.Codec()
|
||||
rc := &api.ReplicationController{
|
||||
ObjectMeta: api.ObjectMeta{Name: "rc",
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Spec: api.ReplicationControllerSpec{
|
||||
Selector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Template: &api.PodTemplateSpec{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
RestartPolicy: api.RestartPolicyAlways,
|
||||
DNSPolicy: api.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test end to end updating of the rc with retries. Essentially make sure the update handler
|
||||
// sees the right updates, failures in update/get are handled properly, and that the updated
|
||||
// rc with new resource version is returned to the caller. Without any of these rollingupdate
|
||||
// will fail cryptically.
|
||||
newRc := *rc
|
||||
newRc.ResourceVersion = "2"
|
||||
newRc.Spec.Selector["baz"] = "foobar"
|
||||
updates := []*http.Response{
|
||||
{StatusCode: 500, Body: objBody(codec, &api.ReplicationController{})},
|
||||
{StatusCode: 500, Body: objBody(codec, &api.ReplicationController{})},
|
||||
{StatusCode: 200, Body: objBody(codec, &newRc)},
|
||||
}
|
||||
gets := []*http.Response{
|
||||
{StatusCode: 500, Body: objBody(codec, &api.ReplicationController{})},
|
||||
{StatusCode: 200, Body: objBody(codec, rc)},
|
||||
}
|
||||
fakeClient := &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/api/v1beta3/namespaces/default/replicationcontrollers/rc" && m == "PUT":
|
||||
update := updates[0]
|
||||
updates = updates[1:]
|
||||
// We should always get an update with a valid rc even when the get fails. The rc should always
|
||||
// contain the update.
|
||||
if c, ok := readOrDie(t, req, codec).(*api.ReplicationController); !ok || !reflect.DeepEqual(rc, c) {
|
||||
t.Errorf("Unexpected update body, got %+v expected %+v", c, rc)
|
||||
} else if sel, ok := c.Spec.Selector["baz"]; !ok || sel != "foobar" {
|
||||
t.Errorf("Expected selector label update, got %+v", c.Spec.Selector)
|
||||
} else {
|
||||
delete(c.Spec.Selector, "baz")
|
||||
}
|
||||
return update, nil
|
||||
case p == "/api/v1beta3/namespaces/default/replicationcontrollers/rc" && m == "GET":
|
||||
get := gets[0]
|
||||
gets = gets[1:]
|
||||
return get, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
clientConfig := &client.Config{Version: latest.Version}
|
||||
client := client.NewOrDie(clientConfig)
|
||||
client.Client = fakeClient.Client
|
||||
|
||||
if rc, err := updateWithRetries(
|
||||
client.ReplicationControllers("default"), rc, func(c *api.ReplicationController) {
|
||||
c.Spec.Selector["baz"] = "foobar"
|
||||
}); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
} else if sel, ok := rc.Spec.Selector["baz"]; !ok || sel != "foobar" || rc.ResourceVersion != "2" {
|
||||
t.Errorf("Expected updated rc, got %+v", rc)
|
||||
}
|
||||
if len(updates) != 0 || len(gets) != 0 {
|
||||
t.Errorf("Remaining updates %+v gets %+v", updates, gets)
|
||||
}
|
||||
}
|
||||
|
||||
func readOrDie(t *testing.T, req *http.Request, codec runtime.Codec) runtime.Object {
|
||||
data, err := ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Error reading: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
obj, err := codec.Decode(data)
|
||||
if err != nil {
|
||||
t.Errorf("error decoding: %v", err)
|
||||
t.FailNow()
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
|
||||
}
|
||||
|
||||
func TestAddDeploymentHash(t *testing.T) {
|
||||
buf := &bytes.Buffer{}
|
||||
codec := testapi.Codec()
|
||||
rc := &api.ReplicationController{
|
||||
ObjectMeta: api.ObjectMeta{Name: "rc"},
|
||||
Spec: api.ReplicationControllerSpec{
|
||||
Selector: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
Template: &api.PodTemplateSpec{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"foo": "bar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
podList := &api.PodList{
|
||||
Items: []api.Pod{
|
||||
{ObjectMeta: api.ObjectMeta{Name: "foo"}},
|
||||
{ObjectMeta: api.ObjectMeta{Name: "bar"}},
|
||||
{ObjectMeta: api.ObjectMeta{Name: "baz"}},
|
||||
},
|
||||
}
|
||||
|
||||
seen := util.StringSet{}
|
||||
updatedRc := false
|
||||
fakeClient := &client.FakeRESTClient{
|
||||
Codec: codec,
|
||||
Client: client.HTTPClientFunc(func(req *http.Request) (*http.Response, error) {
|
||||
switch p, m := req.URL.Path, req.Method; {
|
||||
case p == "/api/v1beta3/namespaces/default/pods" && m == "GET":
|
||||
if req.URL.RawQuery != "labelSelector=foo%3Dbar" {
|
||||
t.Errorf("Unexpected query string: %s", req.URL.RawQuery)
|
||||
}
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, podList)}, nil
|
||||
case p == "/api/v1beta3/namespaces/default/pods/foo" && m == "PUT":
|
||||
seen.Insert("foo")
|
||||
obj := readOrDie(t, req, codec)
|
||||
podList.Items[0] = *(obj.(*api.Pod))
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &podList.Items[0])}, nil
|
||||
case p == "/api/v1beta3/namespaces/default/pods/bar" && m == "PUT":
|
||||
seen.Insert("bar")
|
||||
obj := readOrDie(t, req, codec)
|
||||
podList.Items[1] = *(obj.(*api.Pod))
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &podList.Items[1])}, nil
|
||||
case p == "/api/v1beta3/namespaces/default/pods/baz" && m == "PUT":
|
||||
seen.Insert("baz")
|
||||
obj := readOrDie(t, req, codec)
|
||||
podList.Items[2] = *(obj.(*api.Pod))
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, &podList.Items[2])}, nil
|
||||
case p == "/api/v1beta3/namespaces/default/replicationcontrollers/rc" && m == "PUT":
|
||||
updatedRc = true
|
||||
return &http.Response{StatusCode: 200, Body: objBody(codec, rc)}, nil
|
||||
default:
|
||||
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
|
||||
return nil, nil
|
||||
}
|
||||
}),
|
||||
}
|
||||
clientConfig := &client.Config{Version: latest.Version}
|
||||
client := client.NewOrDie(clientConfig)
|
||||
client.Client = fakeClient.Client
|
||||
|
||||
if _, err := AddDeploymentKeyToReplicationController(rc, client, "dk", "hash", api.NamespaceDefault, buf); err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
for _, pod := range podList.Items {
|
||||
if !seen.Has(pod.Name) {
|
||||
t.Errorf("Missing update for pod: %s", pod.Name)
|
||||
}
|
||||
}
|
||||
if !updatedRc {
|
||||
t.Errorf("Failed to update replication controller with new labels")
|
||||
}
|
||||
}
|
||||
|
||||
// rollingUpdaterClientImpl is a dynamic RollingUpdaterClient.
|
||||
type rollingUpdaterClientImpl struct {
|
||||
ListReplicationControllersFn func(namespace string, selector labels.Selector) (*api.ReplicationControllerList, error)
|
||||
GetReplicationControllerFn func(namespace, name string) (*api.ReplicationController, error)
|
||||
UpdateReplicationControllerFn func(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error)
|
||||
CreateReplicationControllerFn func(namespace string, rc *api.ReplicationController) (*api.ReplicationController, error)
|
||||
@@ -389,6 +819,10 @@ type rollingUpdaterClientImpl struct {
|
||||
ControllerHasDesiredReplicasFn func(rc *api.ReplicationController) wait.ConditionFunc
|
||||
}
|
||||
|
||||
func (c *rollingUpdaterClientImpl) ListReplicationControllers(namespace string, selector labels.Selector) (*api.ReplicationControllerList, error) {
|
||||
return c.ListReplicationControllersFn(namespace, selector)
|
||||
}
|
||||
|
||||
func (c *rollingUpdaterClientImpl) GetReplicationController(namespace, name string) (*api.ReplicationController, error) {
|
||||
return c.GetReplicationControllerFn(namespace, name)
|
||||
}
|
||||
|
Reference in New Issue
Block a user