...

Source file src/github.com/go-kit/kit/sd/etcd/client_test.go

Documentation: github.com/go-kit/kit/sd/etcd

     1  package etcd
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"reflect"
     7  	"testing"
     8  	"time"
     9  
    10  	etcd "go.etcd.io/etcd/client/v2"
    11  )
    12  
    13  func TestNewClient(t *testing.T) {
    14  	client, err := NewClient(
    15  		context.Background(),
    16  		[]string{"http://irrelevant:12345"},
    17  		ClientOptions{
    18  			DialTimeout:             2 * time.Second,
    19  			DialKeepAlive:           2 * time.Second,
    20  			HeaderTimeoutPerRequest: 2 * time.Second,
    21  		},
    22  	)
    23  	if err != nil {
    24  		t.Fatalf("unexpected error creating client: %v", err)
    25  	}
    26  	if client == nil {
    27  		t.Fatal("expected new Client, got nil")
    28  	}
    29  }
    30  
    31  // NewClient should fail when providing invalid or missing endpoints.
    32  func TestOptions(t *testing.T) {
    33  	a, err := NewClient(
    34  		context.Background(),
    35  		[]string{},
    36  		ClientOptions{
    37  			Cert:                    "",
    38  			Key:                     "",
    39  			CACert:                  "",
    40  			DialTimeout:             2 * time.Second,
    41  			DialKeepAlive:           2 * time.Second,
    42  			HeaderTimeoutPerRequest: 2 * time.Second,
    43  		},
    44  	)
    45  	if err == nil {
    46  		t.Errorf("expected error: %v", err)
    47  	}
    48  	if a != nil {
    49  		t.Fatalf("expected client to be nil on failure")
    50  	}
    51  
    52  	_, err = NewClient(
    53  		context.Background(),
    54  		[]string{"http://irrelevant:12345"},
    55  		ClientOptions{
    56  			Cert:                    "blank.crt",
    57  			Key:                     "blank.key",
    58  			CACert:                  "blank.CACert",
    59  			DialTimeout:             2 * time.Second,
    60  			DialKeepAlive:           2 * time.Second,
    61  			HeaderTimeoutPerRequest: 2 * time.Second,
    62  		},
    63  	)
    64  	if err == nil {
    65  		t.Errorf("expected error: %v", err)
    66  	}
    67  }
    68  
    69  // Mocks of the underlying etcd.KeysAPI interface that is called by the methods we want to test
    70  
    71  // fakeKeysAPI implements etcd.KeysAPI, event and err are channels used to emulate
    72  // an etcd event or error, getres will be returned when etcd.KeysAPI.Get is called.
    73  type fakeKeysAPI struct {
    74  	event  chan bool
    75  	err    chan bool
    76  	getres *getResult
    77  }
    78  
    79  type getResult struct {
    80  	resp *etcd.Response
    81  	err  error
    82  }
    83  
    84  // Get return the content of getres or nil, nil
    85  func (fka *fakeKeysAPI) Get(ctx context.Context, key string, opts *etcd.GetOptions) (*etcd.Response, error) {
    86  	if fka.getres == nil {
    87  		return nil, nil
    88  	}
    89  	return fka.getres.resp, fka.getres.err
    90  }
    91  
    92  // Set is not used in the tests
    93  func (fka *fakeKeysAPI) Set(ctx context.Context, key, value string, opts *etcd.SetOptions) (*etcd.Response, error) {
    94  	return nil, nil
    95  }
    96  
    97  // Delete is not used in the tests
    98  func (fka *fakeKeysAPI) Delete(ctx context.Context, key string, opts *etcd.DeleteOptions) (*etcd.Response, error) {
    99  	return nil, nil
   100  }
   101  
   102  // Create is not used in the tests
   103  func (fka *fakeKeysAPI) Create(ctx context.Context, key, value string) (*etcd.Response, error) {
   104  	return nil, nil
   105  }
   106  
   107  // CreateInOrder is not used in the tests
   108  func (fka *fakeKeysAPI) CreateInOrder(ctx context.Context, dir, value string, opts *etcd.CreateInOrderOptions) (*etcd.Response, error) {
   109  	return nil, nil
   110  }
   111  
   112  // Update is not used in the tests
   113  func (fka *fakeKeysAPI) Update(ctx context.Context, key, value string) (*etcd.Response, error) {
   114  	return nil, nil
   115  }
   116  
   117  // Watcher return a fakeWatcher that will forward event and error received on the channels
   118  func (fka *fakeKeysAPI) Watcher(key string, opts *etcd.WatcherOptions) etcd.Watcher {
   119  	return &fakeWatcher{fka.event, fka.err}
   120  }
   121  
   122  // fakeWatcher implements etcd.Watcher
   123  type fakeWatcher struct {
   124  	event chan bool
   125  	err   chan bool
   126  }
   127  
   128  // Next blocks until an etcd event or error is emulated.
   129  // When an event occurs it just return nil response and error.
   130  // When an error occur it return a non nil error.
   131  func (fw *fakeWatcher) Next(context.Context) (*etcd.Response, error) {
   132  	select {
   133  	case <-fw.event:
   134  		return nil, nil
   135  	case <-fw.err:
   136  		return nil, errors.New("error from underlying etcd watcher")
   137  
   138  	}
   139  }
   140  
   141  // newFakeClient return a new etcd.Client built on top of the mocked interfaces
   142  func newFakeClient(event, err chan bool, getres *getResult) Client {
   143  	return &client{
   144  		keysAPI: &fakeKeysAPI{event, err, getres},
   145  		ctx:     context.Background(),
   146  	}
   147  }
   148  
   149  // Register should fail when the provided service has an empty key or value
   150  func TestRegisterClient(t *testing.T) {
   151  	client := newFakeClient(nil, nil, nil)
   152  
   153  	err := client.Register(Service{Key: "", Value: "value", DeleteOptions: nil})
   154  	if want, have := ErrNoKey, err; want != have {
   155  		t.Fatalf("want %v, have %v", want, have)
   156  	}
   157  
   158  	err = client.Register(Service{Key: "key", Value: "", DeleteOptions: nil})
   159  	if want, have := ErrNoValue, err; want != have {
   160  		t.Fatalf("want %v, have %v", want, have)
   161  	}
   162  
   163  	err = client.Register(Service{Key: "key", Value: "value", DeleteOptions: nil})
   164  	if err != nil {
   165  		t.Fatal(err)
   166  	}
   167  }
   168  
   169  // Deregister should fail if the input service has an empty key
   170  func TestDeregisterClient(t *testing.T) {
   171  	client := newFakeClient(nil, nil, nil)
   172  
   173  	err := client.Deregister(Service{Key: "", Value: "value", DeleteOptions: nil})
   174  	if want, have := ErrNoKey, err; want != have {
   175  		t.Fatalf("want %v, have %v", want, have)
   176  	}
   177  
   178  	err = client.Deregister(Service{Key: "key", Value: "", DeleteOptions: nil})
   179  	if err != nil {
   180  		t.Fatal(err)
   181  	}
   182  }
   183  
   184  // WatchPrefix notify the caller by writing on the channel if an etcd event occurs
   185  // or return in case of an underlying error
   186  func TestWatchPrefix(t *testing.T) {
   187  	err := make(chan bool)
   188  	event := make(chan bool)
   189  	watchPrefixReturned := make(chan bool, 1)
   190  	client := newFakeClient(event, err, nil)
   191  
   192  	ch := make(chan struct{})
   193  	go func() {
   194  		client.WatchPrefix("prefix", ch) // block until an etcd event or error occurs
   195  		watchPrefixReturned <- true
   196  	}()
   197  
   198  	// WatchPrefix force the caller to read once from the channel before actually
   199  	// sending notification, emulate that first read.
   200  	<-ch
   201  
   202  	// Emulate an etcd event
   203  	event <- true
   204  	if want, have := struct{}{}, <-ch; want != have {
   205  		t.Fatalf("want %v, have %v", want, have)
   206  	}
   207  
   208  	// Emulate an error, WatchPrefix should return
   209  	err <- true
   210  	select {
   211  	case <-watchPrefixReturned:
   212  		break
   213  	case <-time.After(1 * time.Second):
   214  		t.Fatal("WatchPrefix not returning on errors")
   215  	}
   216  }
   217  
   218  var errKeyAPI = errors.New("emulate error returned by KeysAPI.Get")
   219  
   220  // table of test cases for method GetEntries
   221  var getEntriesTestTable = []struct {
   222  	input getResult // value returned by the underlying etcd.KeysAPI.Get
   223  	resp  []string  // response expected in output of GetEntries
   224  	err   error     //error expected in output of GetEntries
   225  
   226  }{
   227  	// test case: an error is returned by etcd.KeysAPI.Get
   228  	{getResult{nil, errKeyAPI}, nil, errKeyAPI},
   229  	// test case: return a single leaf node, with an empty value
   230  	{getResult{&etcd.Response{
   231  		Action: "get",
   232  		Node: &etcd.Node{
   233  			Key:           "nodekey",
   234  			Dir:           false,
   235  			Value:         "",
   236  			Nodes:         nil,
   237  			CreatedIndex:  0,
   238  			ModifiedIndex: 0,
   239  			Expiration:    nil,
   240  			TTL:           0,
   241  		},
   242  		PrevNode: nil,
   243  		Index:    0,
   244  	}, nil}, []string{}, nil},
   245  	// test case: return a single leaf node, with a value
   246  	{getResult{&etcd.Response{
   247  		Action: "get",
   248  		Node: &etcd.Node{
   249  			Key:           "nodekey",
   250  			Dir:           false,
   251  			Value:         "nodevalue",
   252  			Nodes:         nil,
   253  			CreatedIndex:  0,
   254  			ModifiedIndex: 0,
   255  			Expiration:    nil,
   256  			TTL:           0,
   257  		},
   258  		PrevNode: nil,
   259  		Index:    0,
   260  	}, nil}, []string{"nodevalue"}, nil},
   261  	// test case: return a node with two childs
   262  	{getResult{&etcd.Response{
   263  		Action: "get",
   264  		Node: &etcd.Node{
   265  			Key:   "nodekey",
   266  			Dir:   true,
   267  			Value: "nodevalue",
   268  			Nodes: []*etcd.Node{
   269  				{
   270  					Key:           "childnode1",
   271  					Dir:           false,
   272  					Value:         "childvalue1",
   273  					Nodes:         nil,
   274  					CreatedIndex:  0,
   275  					ModifiedIndex: 0,
   276  					Expiration:    nil,
   277  					TTL:           0,
   278  				},
   279  				{
   280  					Key:           "childnode2",
   281  					Dir:           false,
   282  					Value:         "childvalue2",
   283  					Nodes:         nil,
   284  					CreatedIndex:  0,
   285  					ModifiedIndex: 0,
   286  					Expiration:    nil,
   287  					TTL:           0,
   288  				},
   289  			},
   290  			CreatedIndex:  0,
   291  			ModifiedIndex: 0,
   292  			Expiration:    nil,
   293  			TTL:           0,
   294  		},
   295  		PrevNode: nil,
   296  		Index:    0,
   297  	}, nil}, []string{"childvalue1", "childvalue2"}, nil},
   298  }
   299  
   300  func TestGetEntries(t *testing.T) {
   301  	for _, et := range getEntriesTestTable {
   302  		client := newFakeClient(nil, nil, &et.input)
   303  		resp, err := client.GetEntries("prefix")
   304  		if want, have := et.resp, resp; !reflect.DeepEqual(want, have) {
   305  			t.Fatalf("want %v, have %v", want, have)
   306  		}
   307  		if want, have := et.err, err; want != have {
   308  			t.Fatalf("want %v, have %v", want, have)
   309  		}
   310  	}
   311  }
   312  

View as plain text