From 43df091067566416dcc35a115c559b42be204a3d Mon Sep 17 00:00:00 2001 From: Shaun Crampton Date: Mon, 24 Oct 2016 14:10:37 +0100 Subject: [PATCH] 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. --- client/keys.go | 5 +++++ client/keys_test.go | 55 ++++++++++++++++++++++++--------------------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/client/keys.go b/client/keys.go index b74b9e0a5..4a6c41a78 100644 --- a/client/keys.go +++ b/client/keys.go @@ -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 } diff --git a/client/keys_test.go b/client/keys_test.go index 2a59f32a9..253fa9b8f 100644 --- a/client/keys_test.go +++ b/client/keys_test.go @@ -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)