client: Return the server's cluster ID as part of the Response

This allows the client to spot if the cluster ID changes, which
would indicate that the cluster has been rebuilt and watches may be
out of sync.

Helps work around #6652.
This commit is contained in:
Shaun Crampton 2016-10-24 14:10:37 +01:00
parent 92c987f75d
commit 43df091067
2 changed files with 34 additions and 26 deletions

View File

@ -272,6 +272,10 @@ type Response struct {
// Index holds the cluster-level index at the time the Response was generated.
// This index is not tied to the Node(s) contained in this Response.
Index uint64 `json:"-"`
// ClusterID holds the cluster-level ID reported by the server. This
// should be different for different etcd clusters.
ClusterID string `json:"-"`
}
type Node struct {
@ -665,6 +669,7 @@ func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response
return nil, err
}
}
res.ClusterID = header.Get("X-Etcd-Cluster-ID")
return &res, nil
}

View File

@ -673,23 +673,24 @@ func TestUnmarshalSuccessfulResponse(t *testing.T) {
expiration.UnmarshalText([]byte("2015-04-07T04:40:23.044979686Z"))
tests := []struct {
hdr string
body string
wantRes *Response
wantErr bool
indexHdr string
clusterIDHdr string
body string
wantRes *Response
wantErr bool
}{
// Neither PrevNode or Node
{
hdr: "1",
body: `{"action":"delete"}`,
wantRes: &Response{Action: "delete", Index: 1},
wantErr: false,
indexHdr: "1",
body: `{"action":"delete"}`,
wantRes: &Response{Action: "delete", Index: 1},
wantErr: false,
},
// PrevNode
{
hdr: "15",
body: `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
indexHdr: "15",
body: `{"action":"delete", "prevNode": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
wantRes: &Response{
Action: "delete",
Index: 15,
@ -706,8 +707,8 @@ func TestUnmarshalSuccessfulResponse(t *testing.T) {
// Node
{
hdr: "15",
body: `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10, "ttl": 10, "expiration": "2015-04-07T04:40:23.044979686Z"}}`,
indexHdr: "15",
body: `{"action":"get", "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10, "ttl": 10, "expiration": "2015-04-07T04:40:23.044979686Z"}}`,
wantRes: &Response{
Action: "get",
Index: 15,
@ -726,8 +727,9 @@ func TestUnmarshalSuccessfulResponse(t *testing.T) {
// Node Dir
{
hdr: "15",
body: `{"action":"get", "node": {"key": "/foo", "dir": true, "modifiedIndex": 12, "createdIndex": 10}}`,
indexHdr: "15",
clusterIDHdr: "abcdef",
body: `{"action":"get", "node": {"key": "/foo", "dir": true, "modifiedIndex": 12, "createdIndex": 10}}`,
wantRes: &Response{
Action: "get",
Index: 15,
@ -737,15 +739,16 @@ func TestUnmarshalSuccessfulResponse(t *testing.T) {
ModifiedIndex: 12,
CreatedIndex: 10,
},
PrevNode: nil,
PrevNode: nil,
ClusterID: "abcdef",
},
wantErr: false,
},
// PrevNode and Node
{
hdr: "15",
body: `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
indexHdr: "15",
body: `{"action":"update", "prevNode": {"key": "/foo", "value": "baz", "modifiedIndex": 10, "createdIndex": 10}, "node": {"key": "/foo", "value": "bar", "modifiedIndex": 12, "createdIndex": 10}}`,
wantRes: &Response{
Action: "update",
Index: 15,
@ -767,24 +770,24 @@ func TestUnmarshalSuccessfulResponse(t *testing.T) {
// Garbage in body
{
hdr: "",
body: `garbage`,
wantRes: nil,
wantErr: true,
indexHdr: "",
body: `garbage`,
wantRes: nil,
wantErr: true,
},
// non-integer index
{
hdr: "poo",
body: `{}`,
wantRes: nil,
wantErr: true,
indexHdr: "poo",
body: `{}`,
wantRes: nil,
wantErr: true,
},
}
for i, tt := range tests {
h := make(http.Header)
h.Add("X-Etcd-Index", tt.hdr)
h.Add("X-Etcd-Index", tt.indexHdr)
res, err := unmarshalSuccessfulKeysResponse(h, []byte(tt.body))
if tt.wantErr != (err != nil) {
t.Errorf("#%d: wantErr=%t, err=%v", i, tt.wantErr, err)