...

Source file src/go.etcd.io/etcd/client/v2/members_test.go

Documentation: go.etcd.io/etcd/client/v2

     1  // Copyright 2015 The etcd Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package client
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"net/http"
    22  	"net/url"
    23  	"reflect"
    24  	"testing"
    25  
    26  	"go.etcd.io/etcd/client/pkg/v3/types"
    27  )
    28  
    29  func TestMembersAPIActionList(t *testing.T) {
    30  	ep := url.URL{Scheme: "http", Host: "example.com"}
    31  	act := &membersAPIActionList{}
    32  
    33  	wantURL := &url.URL{
    34  		Scheme: "http",
    35  		Host:   "example.com",
    36  		Path:   "/v2/members",
    37  	}
    38  
    39  	got := *act.HTTPRequest(ep)
    40  	err := assertRequest(got, "GET", wantURL, http.Header{}, nil)
    41  	if err != nil {
    42  		t.Error(err.Error())
    43  	}
    44  }
    45  
    46  func TestMembersAPIActionAdd(t *testing.T) {
    47  	ep := url.URL{Scheme: "http", Host: "example.com"}
    48  	act := &membersAPIActionAdd{
    49  		peerURLs: types.URLs([]url.URL{
    50  			{Scheme: "https", Host: "127.0.0.1:8081"},
    51  			{Scheme: "http", Host: "127.0.0.1:8080"},
    52  		}),
    53  	}
    54  
    55  	wantURL := &url.URL{
    56  		Scheme: "http",
    57  		Host:   "example.com",
    58  		Path:   "/v2/members",
    59  	}
    60  	wantHeader := http.Header{
    61  		"Content-Type": []string{"application/json"},
    62  	}
    63  	wantBody := []byte(`{"peerURLs":["https://127.0.0.1:8081","http://127.0.0.1:8080"]}`)
    64  
    65  	got := *act.HTTPRequest(ep)
    66  	err := assertRequest(got, "POST", wantURL, wantHeader, wantBody)
    67  	if err != nil {
    68  		t.Error(err.Error())
    69  	}
    70  }
    71  
    72  func TestMembersAPIActionUpdate(t *testing.T) {
    73  	ep := url.URL{Scheme: "http", Host: "example.com"}
    74  	act := &membersAPIActionUpdate{
    75  		memberID: "0xabcd",
    76  		peerURLs: types.URLs([]url.URL{
    77  			{Scheme: "https", Host: "127.0.0.1:8081"},
    78  			{Scheme: "http", Host: "127.0.0.1:8080"},
    79  		}),
    80  	}
    81  
    82  	wantURL := &url.URL{
    83  		Scheme: "http",
    84  		Host:   "example.com",
    85  		Path:   "/v2/members/0xabcd",
    86  	}
    87  	wantHeader := http.Header{
    88  		"Content-Type": []string{"application/json"},
    89  	}
    90  	wantBody := []byte(`{"peerURLs":["https://127.0.0.1:8081","http://127.0.0.1:8080"]}`)
    91  
    92  	got := *act.HTTPRequest(ep)
    93  	err := assertRequest(got, "PUT", wantURL, wantHeader, wantBody)
    94  	if err != nil {
    95  		t.Error(err.Error())
    96  	}
    97  }
    98  
    99  func TestMembersAPIActionRemove(t *testing.T) {
   100  	ep := url.URL{Scheme: "http", Host: "example.com"}
   101  	act := &membersAPIActionRemove{memberID: "XXX"}
   102  
   103  	wantURL := &url.URL{
   104  		Scheme: "http",
   105  		Host:   "example.com",
   106  		Path:   "/v2/members/XXX",
   107  	}
   108  
   109  	got := *act.HTTPRequest(ep)
   110  	err := assertRequest(got, "DELETE", wantURL, http.Header{}, nil)
   111  	if err != nil {
   112  		t.Error(err.Error())
   113  	}
   114  }
   115  
   116  func TestMembersAPIActionLeader(t *testing.T) {
   117  	ep := url.URL{Scheme: "http", Host: "example.com"}
   118  	act := &membersAPIActionLeader{}
   119  
   120  	wantURL := &url.URL{
   121  		Scheme: "http",
   122  		Host:   "example.com",
   123  		Path:   "/v2/members/leader",
   124  	}
   125  
   126  	got := *act.HTTPRequest(ep)
   127  	err := assertRequest(got, "GET", wantURL, http.Header{}, nil)
   128  	if err != nil {
   129  		t.Error(err.Error())
   130  	}
   131  }
   132  
   133  func TestAssertStatusCode(t *testing.T) {
   134  	if err := assertStatusCode(404, 400); err == nil {
   135  		t.Errorf("assertStatusCode failed to detect conflict in 400 vs 404")
   136  	}
   137  
   138  	if err := assertStatusCode(404, 400, 404); err != nil {
   139  		t.Errorf("assertStatusCode found conflict in (404,400) vs 400: %v", err)
   140  	}
   141  }
   142  
   143  func TestV2MembersURL(t *testing.T) {
   144  	got := v2MembersURL(url.URL{
   145  		Scheme: "http",
   146  		Host:   "foo.example.com:4002",
   147  		Path:   "/pants",
   148  	})
   149  	want := &url.URL{
   150  		Scheme: "http",
   151  		Host:   "foo.example.com:4002",
   152  		Path:   "/pants/v2/members",
   153  	}
   154  
   155  	if !reflect.DeepEqual(want, got) {
   156  		t.Fatalf("v2MembersURL got %#v, want %#v", got, want)
   157  	}
   158  }
   159  
   160  func TestMemberUnmarshal(t *testing.T) {
   161  	tests := []struct {
   162  		body       []byte
   163  		wantMember Member
   164  		wantError  bool
   165  	}{
   166  		// no URLs, just check ID & Name
   167  		{
   168  			body:       []byte(`{"id": "c", "name": "dungarees"}`),
   169  			wantMember: Member{ID: "c", Name: "dungarees", PeerURLs: nil, ClientURLs: nil},
   170  		},
   171  
   172  		// both client and peer URLs
   173  		{
   174  			body: []byte(`{"peerURLs": ["http://127.0.0.1:2379"], "clientURLs": ["http://127.0.0.1:2379"]}`),
   175  			wantMember: Member{
   176  				PeerURLs: []string{
   177  					"http://127.0.0.1:2379",
   178  				},
   179  				ClientURLs: []string{
   180  					"http://127.0.0.1:2379",
   181  				},
   182  			},
   183  		},
   184  
   185  		// multiple peer URLs
   186  		{
   187  			body: []byte(`{"peerURLs": ["http://127.0.0.1:2379", "https://example.com"]}`),
   188  			wantMember: Member{
   189  				PeerURLs: []string{
   190  					"http://127.0.0.1:2379",
   191  					"https://example.com",
   192  				},
   193  				ClientURLs: nil,
   194  			},
   195  		},
   196  
   197  		// multiple client URLs
   198  		{
   199  			body: []byte(`{"clientURLs": ["http://127.0.0.1:2379", "https://example.com"]}`),
   200  			wantMember: Member{
   201  				PeerURLs: nil,
   202  				ClientURLs: []string{
   203  					"http://127.0.0.1:2379",
   204  					"https://example.com",
   205  				},
   206  			},
   207  		},
   208  
   209  		// invalid JSON
   210  		{
   211  			body:      []byte(`{"peerU`),
   212  			wantError: true,
   213  		},
   214  	}
   215  
   216  	for i, tt := range tests {
   217  		got := Member{}
   218  		err := json.Unmarshal(tt.body, &got)
   219  		if tt.wantError != (err != nil) {
   220  			t.Errorf("#%d: want error %t, got %v", i, tt.wantError, err)
   221  			continue
   222  		}
   223  
   224  		if !reflect.DeepEqual(tt.wantMember, got) {
   225  			t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.wantMember, got)
   226  		}
   227  	}
   228  }
   229  
   230  func TestMemberCollectionUnmarshalFail(t *testing.T) {
   231  	mc := &memberCollection{}
   232  	if err := mc.UnmarshalJSON([]byte(`{`)); err == nil {
   233  		t.Errorf("got nil error")
   234  	}
   235  }
   236  
   237  func TestMemberCollectionUnmarshal(t *testing.T) {
   238  	tests := []struct {
   239  		body []byte
   240  		want memberCollection
   241  	}{
   242  		{
   243  			body: []byte(`{}`),
   244  			want: memberCollection([]Member{}),
   245  		},
   246  		{
   247  			body: []byte(`{"members":[]}`),
   248  			want: memberCollection([]Member{}),
   249  		},
   250  		{
   251  			body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`),
   252  			want: memberCollection(
   253  				[]Member{
   254  					{
   255  						ID:   "2745e2525fce8fe",
   256  						Name: "node3",
   257  						PeerURLs: []string{
   258  							"http://127.0.0.1:7003",
   259  						},
   260  						ClientURLs: []string{
   261  							"http://127.0.0.1:4003",
   262  						},
   263  					},
   264  					{
   265  						ID:   "42134f434382925",
   266  						Name: "node1",
   267  						PeerURLs: []string{
   268  							"http://127.0.0.1:2380",
   269  							"http://127.0.0.1:7001",
   270  						},
   271  						ClientURLs: []string{
   272  							"http://127.0.0.1:2379",
   273  							"http://127.0.0.1:4001",
   274  						},
   275  					},
   276  					{
   277  						ID:   "94088180e21eb87b",
   278  						Name: "node2",
   279  						PeerURLs: []string{
   280  							"http://127.0.0.1:7002",
   281  						},
   282  						ClientURLs: []string{
   283  							"http://127.0.0.1:4002",
   284  						},
   285  					},
   286  				},
   287  			),
   288  		},
   289  	}
   290  
   291  	for i, tt := range tests {
   292  		var got memberCollection
   293  		err := json.Unmarshal(tt.body, &got)
   294  		if err != nil {
   295  			t.Errorf("#%d: unexpected error: %v", i, err)
   296  			continue
   297  		}
   298  
   299  		if !reflect.DeepEqual(tt.want, got) {
   300  			t.Errorf("#%d: incorrect output: want=%#v, got=%#v", i, tt.want, got)
   301  		}
   302  	}
   303  }
   304  
   305  func TestMemberCreateRequestMarshal(t *testing.T) {
   306  	req := memberCreateOrUpdateRequest{
   307  		PeerURLs: types.URLs([]url.URL{
   308  			{Scheme: "http", Host: "127.0.0.1:8081"},
   309  			{Scheme: "https", Host: "127.0.0.1:8080"},
   310  		}),
   311  	}
   312  	want := []byte(`{"peerURLs":["http://127.0.0.1:8081","https://127.0.0.1:8080"]}`)
   313  
   314  	got, err := json.Marshal(&req)
   315  	if err != nil {
   316  		t.Fatalf("Marshal returned unexpected err=%v", err)
   317  	}
   318  
   319  	if !reflect.DeepEqual(want, got) {
   320  		t.Fatalf("Failed to marshal memberCreateRequest: want=%s, got=%s", want, got)
   321  	}
   322  }
   323  
   324  func TestHTTPMembersAPIAddSuccess(t *testing.T) {
   325  	wantAction := &membersAPIActionAdd{
   326  		peerURLs: types.URLs([]url.URL{
   327  			{Scheme: "http", Host: "127.0.0.1:7002"},
   328  		}),
   329  	}
   330  
   331  	mAPI := &httpMembersAPI{
   332  		client: &actionAssertingHTTPClient{
   333  			t:   t,
   334  			act: wantAction,
   335  			resp: http.Response{
   336  				StatusCode: http.StatusCreated,
   337  			},
   338  			body: []byte(`{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"]}`),
   339  		},
   340  	}
   341  
   342  	wantResponseMember := &Member{
   343  		ID:       "94088180e21eb87b",
   344  		PeerURLs: []string{"http://127.0.0.1:7002"},
   345  	}
   346  
   347  	m, err := mAPI.Add(context.Background(), "http://127.0.0.1:7002")
   348  	if err != nil {
   349  		t.Errorf("got non-nil err: %#v", err)
   350  	}
   351  	if !reflect.DeepEqual(wantResponseMember, m) {
   352  		t.Errorf("incorrect Member: want=%#v got=%#v", wantResponseMember, m)
   353  	}
   354  }
   355  
   356  func TestHTTPMembersAPIAddError(t *testing.T) {
   357  	okPeer := "http://example.com:2379"
   358  	tests := []struct {
   359  		peerURL string
   360  		client  httpClient
   361  
   362  		// if wantErr == nil, assert that the returned error is non-nil
   363  		// if wantErr != nil, assert that the returned error matches
   364  		wantErr error
   365  	}{
   366  		// malformed peer URL
   367  		{
   368  			peerURL: ":",
   369  		},
   370  
   371  		// generic httpClient failure
   372  		{
   373  			peerURL: okPeer,
   374  			client:  &staticHTTPClient{err: errors.New("fail!")},
   375  		},
   376  
   377  		// unrecognized HTTP status code
   378  		{
   379  			peerURL: okPeer,
   380  			client: &staticHTTPClient{
   381  				resp: http.Response{StatusCode: http.StatusTeapot},
   382  			},
   383  		},
   384  
   385  		// unmarshal body into membersError on StatusConflict
   386  		{
   387  			peerURL: okPeer,
   388  			client: &staticHTTPClient{
   389  				resp: http.Response{
   390  					StatusCode: http.StatusConflict,
   391  				},
   392  				body: []byte(`{"message":"fail!"}`),
   393  			},
   394  			wantErr: membersError{Message: "fail!"},
   395  		},
   396  
   397  		// fail to unmarshal body on StatusConflict
   398  		{
   399  			peerURL: okPeer,
   400  			client: &staticHTTPClient{
   401  				resp: http.Response{
   402  					StatusCode: http.StatusConflict,
   403  				},
   404  				body: []byte(`{"`),
   405  			},
   406  		},
   407  
   408  		// fail to unmarshal body on StatusCreated
   409  		{
   410  			peerURL: okPeer,
   411  			client: &staticHTTPClient{
   412  				resp: http.Response{
   413  					StatusCode: http.StatusCreated,
   414  				},
   415  				body: []byte(`{"id":"XX`),
   416  			},
   417  		},
   418  	}
   419  
   420  	for i, tt := range tests {
   421  		mAPI := &httpMembersAPI{client: tt.client}
   422  		m, err := mAPI.Add(context.Background(), tt.peerURL)
   423  		if err == nil {
   424  			t.Errorf("#%d: got nil err", i)
   425  		}
   426  		if tt.wantErr != nil && !reflect.DeepEqual(tt.wantErr, err) {
   427  			t.Errorf("#%d: incorrect error: want=%#v got=%#v", i, tt.wantErr, err)
   428  		}
   429  		if m != nil {
   430  			t.Errorf("#%d: got non-nil Member", i)
   431  		}
   432  	}
   433  }
   434  
   435  func TestHTTPMembersAPIRemoveSuccess(t *testing.T) {
   436  	wantAction := &membersAPIActionRemove{
   437  		memberID: "94088180e21eb87b",
   438  	}
   439  
   440  	mAPI := &httpMembersAPI{
   441  		client: &actionAssertingHTTPClient{
   442  			t:   t,
   443  			act: wantAction,
   444  			resp: http.Response{
   445  				StatusCode: http.StatusNoContent,
   446  			},
   447  		},
   448  	}
   449  
   450  	if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err != nil {
   451  		t.Errorf("got non-nil err: %#v", err)
   452  	}
   453  }
   454  
   455  func TestHTTPMembersAPIRemoveFail(t *testing.T) {
   456  	tests := []httpClient{
   457  		// generic error
   458  		&staticHTTPClient{
   459  			err: errors.New("fail!"),
   460  		},
   461  
   462  		// unexpected HTTP status code
   463  		&staticHTTPClient{
   464  			resp: http.Response{
   465  				StatusCode: http.StatusInternalServerError,
   466  			},
   467  		},
   468  	}
   469  
   470  	for i, tt := range tests {
   471  		mAPI := &httpMembersAPI{client: tt}
   472  		if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err == nil {
   473  			t.Errorf("#%d: got nil err", i)
   474  		}
   475  	}
   476  }
   477  
   478  func TestHTTPMembersAPIListSuccess(t *testing.T) {
   479  	wantAction := &membersAPIActionList{}
   480  	mAPI := &httpMembersAPI{
   481  		client: &actionAssertingHTTPClient{
   482  			t:   t,
   483  			act: wantAction,
   484  			resp: http.Response{
   485  				StatusCode: http.StatusOK,
   486  			},
   487  			body: []byte(`{"members":[{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}]}`),
   488  		},
   489  	}
   490  
   491  	wantResponseMembers := []Member{
   492  		{
   493  			ID:         "94088180e21eb87b",
   494  			Name:       "node2",
   495  			PeerURLs:   []string{"http://127.0.0.1:7002"},
   496  			ClientURLs: []string{"http://127.0.0.1:4002"},
   497  		},
   498  	}
   499  
   500  	m, err := mAPI.List(context.Background())
   501  	if err != nil {
   502  		t.Errorf("got non-nil err: %#v", err)
   503  	}
   504  	if !reflect.DeepEqual(wantResponseMembers, m) {
   505  		t.Errorf("incorrect Members: want=%#v got=%#v", wantResponseMembers, m)
   506  	}
   507  }
   508  
   509  func TestHTTPMembersAPIListError(t *testing.T) {
   510  	tests := []httpClient{
   511  		// generic httpClient failure
   512  		&staticHTTPClient{err: errors.New("fail!")},
   513  
   514  		// unrecognized HTTP status code
   515  		&staticHTTPClient{
   516  			resp: http.Response{StatusCode: http.StatusTeapot},
   517  		},
   518  
   519  		// fail to unmarshal body on StatusOK
   520  		&staticHTTPClient{
   521  			resp: http.Response{
   522  				StatusCode: http.StatusOK,
   523  			},
   524  			body: []byte(`[{"id":"XX`),
   525  		},
   526  	}
   527  
   528  	for i, tt := range tests {
   529  		mAPI := &httpMembersAPI{client: tt}
   530  		ms, err := mAPI.List(context.Background())
   531  		if err == nil {
   532  			t.Errorf("#%d: got nil err", i)
   533  		}
   534  		if ms != nil {
   535  			t.Errorf("#%d: got non-nil Member slice", i)
   536  		}
   537  	}
   538  }
   539  
   540  func TestHTTPMembersAPILeaderSuccess(t *testing.T) {
   541  	wantAction := &membersAPIActionLeader{}
   542  	mAPI := &httpMembersAPI{
   543  		client: &actionAssertingHTTPClient{
   544  			t:   t,
   545  			act: wantAction,
   546  			resp: http.Response{
   547  				StatusCode: http.StatusOK,
   548  			},
   549  			body: []byte(`{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}`),
   550  		},
   551  	}
   552  
   553  	wantResponseMember := &Member{
   554  		ID:         "94088180e21eb87b",
   555  		Name:       "node2",
   556  		PeerURLs:   []string{"http://127.0.0.1:7002"},
   557  		ClientURLs: []string{"http://127.0.0.1:4002"},
   558  	}
   559  
   560  	m, err := mAPI.Leader(context.Background())
   561  	if err != nil {
   562  		t.Errorf("err = %v, want %v", err, nil)
   563  	}
   564  	if !reflect.DeepEqual(wantResponseMember, m) {
   565  		t.Errorf("incorrect member: member = %v, want %v", wantResponseMember, m)
   566  	}
   567  }
   568  
   569  func TestHTTPMembersAPILeaderError(t *testing.T) {
   570  	tests := []httpClient{
   571  		// generic httpClient failure
   572  		&staticHTTPClient{err: errors.New("fail!")},
   573  
   574  		// unrecognized HTTP status code
   575  		&staticHTTPClient{
   576  			resp: http.Response{StatusCode: http.StatusTeapot},
   577  		},
   578  
   579  		// fail to unmarshal body on StatusOK
   580  		&staticHTTPClient{
   581  			resp: http.Response{
   582  				StatusCode: http.StatusOK,
   583  			},
   584  			body: []byte(`[{"id":"XX`),
   585  		},
   586  	}
   587  
   588  	for i, tt := range tests {
   589  		mAPI := &httpMembersAPI{client: tt}
   590  		m, err := mAPI.Leader(context.Background())
   591  		if err == nil {
   592  			t.Errorf("#%d: err = nil, want not nil", i)
   593  		}
   594  		if m != nil {
   595  			t.Errorf("member slice = %v, want nil", m)
   596  		}
   597  	}
   598  }
   599  

View as plain text