From d30da9a8125c9aca51569a4b06ec1802cd1306e7 Mon Sep 17 00:00:00 2001 From: Deyuan Deng Date: Mon, 26 Jan 2015 17:03:48 -0500 Subject: [PATCH] List objects in deterministic order --- pkg/tools/etcd_tools.go | 2 +- pkg/tools/etcd_tools_test.go | 36 ++++++++++++++++++++++++++++------- pkg/tools/fake_etcd_client.go | 15 +++++++++++++++ 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/pkg/tools/etcd_tools.go b/pkg/tools/etcd_tools.go index 8e332a9c535..430f50ae8bb 100644 --- a/pkg/tools/etcd_tools.go +++ b/pkg/tools/etcd_tools.go @@ -147,7 +147,7 @@ func etcdErrorIndex(err error) (uint64, bool) { } func (h *EtcdHelper) listEtcdNode(key string) ([]*etcd.Node, uint64, error) { - result, err := h.Client.Get(key, false, true) + result, err := h.Client.Get(key, true, true) if err != nil { index, ok := etcdErrorIndex(err) if !ok { diff --git a/pkg/tools/etcd_tools_test.go b/pkg/tools/etcd_tools_test.go index 6a9e7fa0482..e151f002ddc 100644 --- a/pkg/tools/etcd_tools_test.go +++ b/pkg/tools/etcd_tools_test.go @@ -30,11 +30,6 @@ import ( "github.com/coreos/go-etcd/etcd" ) -type fakeClientGetSet struct { - get func(key string, sort, recursive bool) (*etcd.Response, error) - set func(key, value string, ttl uint64) (*etcd.Response, error) -} - type TestResource struct { api.TypeMeta `json:",inline"` api.ObjectMeta `json:"metadata"` @@ -72,17 +67,24 @@ func TestExtractToList(t *testing.T) { R: &etcd.Response{ EtcdIndex: 10, Node: &etcd.Node{ + Dir: true, Nodes: []*etcd.Node{ { + Key: "/foo", Value: `{"id":"foo","kind":"Pod","apiVersion":"v1beta1"}`, + Dir: false, ModifiedIndex: 1, }, { + Key: "/bar", Value: `{"id":"bar","kind":"Pod","apiVersion":"v1beta1"}`, + Dir: false, ModifiedIndex: 2, }, { + Key: "/baz", Value: `{"id":"baz","kind":"Pod","apiVersion":"v1beta1"}`, + Dir: false, ModifiedIndex: 3, }, }, @@ -92,9 +94,10 @@ func TestExtractToList(t *testing.T) { expect := api.PodList{ ListMeta: api.ListMeta{ResourceVersion: "10"}, Items: []api.Pod{ - {ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, + // We expect items to be sorted by its name. {ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}}, {ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}}, + {ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, }, } @@ -116,22 +119,34 @@ func TestExtractToListAcrossDirectories(t *testing.T) { R: &etcd.Response{ EtcdIndex: 10, Node: &etcd.Node{ + Dir: true, Nodes: []*etcd.Node{ { + Key: "/directory1", Value: `{"name": "directory1"}`, Dir: true, Nodes: []*etcd.Node{ { + Key: "/foo", Value: `{"id":"foo","kind":"Pod","apiVersion":"v1beta1"}`, + Dir: false, + ModifiedIndex: 1, + }, + { + Key: "/baz", + Value: `{"id":"baz","kind":"Pod","apiVersion":"v1beta1"}`, + Dir: false, ModifiedIndex: 1, }, }, }, { + Key: "/directory2", Value: `{"name": "directory2"}`, Dir: true, Nodes: []*etcd.Node{ { + Key: "/bar", Value: `{"id":"bar","kind":"Pod","apiVersion":"v1beta1"}`, ModifiedIndex: 2, }, @@ -144,6 +159,8 @@ func TestExtractToListAcrossDirectories(t *testing.T) { expect := api.PodList{ ListMeta: api.ListMeta{ResourceVersion: "10"}, Items: []api.Pod{ + // We expect list to be sorted by directory (e.g. namespace) first, then by name. + {ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "1"}}, {ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, {ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}}, }, @@ -166,20 +183,25 @@ func TestExtractToListExcludesDirectories(t *testing.T) { R: &etcd.Response{ EtcdIndex: 10, Node: &etcd.Node{ + Dir: true, Nodes: []*etcd.Node{ { + Key: "/foo", Value: `{"id":"foo","kind":"Pod","apiVersion":"v1beta1"}`, ModifiedIndex: 1, }, { + Key: "/bar", Value: `{"id":"bar","kind":"Pod","apiVersion":"v1beta1"}`, ModifiedIndex: 2, }, { + Key: "/baz", Value: `{"id":"baz","kind":"Pod","apiVersion":"v1beta1"}`, ModifiedIndex: 3, }, { + Key: "/directory", Value: `{"name": "directory"}`, Dir: true, }, @@ -190,9 +212,9 @@ func TestExtractToListExcludesDirectories(t *testing.T) { expect := api.PodList{ ListMeta: api.ListMeta{ResourceVersion: "10"}, Items: []api.Pod{ - {ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, {ObjectMeta: api.ObjectMeta{Name: "bar", ResourceVersion: "2"}}, {ObjectMeta: api.ObjectMeta{Name: "baz", ResourceVersion: "3"}}, + {ObjectMeta: api.ObjectMeta{Name: "foo", ResourceVersion: "1"}}, }, } diff --git a/pkg/tools/fake_etcd_client.go b/pkg/tools/fake_etcd_client.go index 1d8821e4643..5250187cc97 100644 --- a/pkg/tools/fake_etcd_client.go +++ b/pkg/tools/fake_etcd_client.go @@ -19,6 +19,7 @@ package tools import ( "errors" "fmt" + "sort" "sync" "github.com/coreos/go-etcd/etcd" @@ -132,9 +133,23 @@ func (f *FakeEtcdClient) Get(key string, sort, recursive bool) (*etcd.Response, return &etcd.Response{}, EtcdErrorNotFound } f.t.Logf("returning %v: %#v %#v", key, result.R, result.E) + + // Sort response, note this will alter resutl.R. + if result.R.Node != nil && result.R.Node.Nodes != nil && sort { + f.sortResponse(result.R.Node.Nodes) + } return result.R, result.E } +func (f *FakeEtcdClient) sortResponse(nodes etcd.Nodes) { + for i := range nodes { + if nodes[i].Dir { + f.sortResponse(nodes[i].Nodes) + } + } + sort.Sort(nodes) +} + func (f *FakeEtcdClient) nodeExists(key string) bool { result, ok := f.Data[key] return ok && result.R != nil && result.R.Node != nil && result.E == nil