go.etcd.io/etcd@v3.3.27+incompatible/clientv3/concurrency/election_test.go (about)

     1  // Copyright 2018 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 concurrency_test
    16  
    17  import (
    18  	"context"
    19  	"log"
    20  	"strings"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/coreos/etcd/clientv3"
    25  	"github.com/coreos/etcd/clientv3/concurrency"
    26  )
    27  
    28  func TestResumeElection(t *testing.T) {
    29  	const prefix = "/resume-election/"
    30  
    31  	cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})
    32  	if err != nil {
    33  		log.Fatal(err)
    34  	}
    35  	defer cli.Close()
    36  
    37  	s, err := concurrency.NewSession(cli)
    38  	if err != nil {
    39  		log.Fatal(err)
    40  	}
    41  	defer s.Close()
    42  
    43  	e := concurrency.NewElection(s, prefix)
    44  
    45  	// Entire test should never take more than 10 seconds
    46  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
    47  	defer cancel()
    48  
    49  	// Become leader
    50  	if err := e.Campaign(ctx, "candidate1"); err != nil {
    51  		t.Fatalf("Campaign() returned non nil err: %s", err)
    52  	}
    53  
    54  	// Get the leadership details of the current election
    55  	leader, err := e.Leader(ctx)
    56  	if err != nil {
    57  		t.Fatalf("Leader() returned non nil err: %s", err)
    58  	}
    59  
    60  	// Recreate the election
    61  	e = concurrency.ResumeElection(s, prefix,
    62  		string(leader.Kvs[0].Key), leader.Kvs[0].CreateRevision)
    63  
    64  	respChan := make(chan *clientv3.GetResponse)
    65  	go func() {
    66  		o := e.Observe(ctx)
    67  		respChan <- nil
    68  		for {
    69  			select {
    70  			case resp, ok := <-o:
    71  				if !ok {
    72  					t.Fatal("Observe() channel closed prematurely")
    73  				}
    74  				// Ignore any observations that candidate1 was elected
    75  				if string(resp.Kvs[0].Value) == "candidate1" {
    76  					continue
    77  				}
    78  				respChan <- &resp
    79  				return
    80  			}
    81  		}
    82  	}()
    83  
    84  	// Wait until observe goroutine is running
    85  	<-respChan
    86  
    87  	// Put some random data to generate a change event, this put should be
    88  	// ignored by Observe() because it is not under the election prefix.
    89  	_, err = cli.Put(ctx, "foo", "bar")
    90  	if err != nil {
    91  		t.Fatalf("Put('foo') returned non nil err: %s", err)
    92  	}
    93  
    94  	// Resign as leader
    95  	if err := e.Resign(ctx); err != nil {
    96  		t.Fatalf("Resign() returned non nil err: %s", err)
    97  	}
    98  
    99  	// Elect a different candidate
   100  	if err := e.Campaign(ctx, "candidate2"); err != nil {
   101  		t.Fatalf("Campaign() returned non nil err: %s", err)
   102  	}
   103  
   104  	// Wait for observed leader change
   105  	resp := <-respChan
   106  
   107  	kv := resp.Kvs[0]
   108  	if !strings.HasPrefix(string(kv.Key), prefix) {
   109  		t.Errorf("expected observed election to have prefix '%s' got '%s'", prefix, string(kv.Key))
   110  	}
   111  	if string(kv.Value) != "candidate2" {
   112  		t.Errorf("expected new leader to be 'candidate1' got '%s'", string(kv.Value))
   113  	}
   114  }