github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/raft/raftbackstop/worker_test.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package raftbackstop_test
     5  
     6  import (
     7  	"bytes"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/hashicorp/go-msgpack/codec"
    12  	"github.com/hashicorp/raft"
    13  	"github.com/juju/loggo"
    14  	"github.com/juju/pubsub"
    15  	"github.com/juju/testing"
    16  	jc "github.com/juju/testing/checkers"
    17  	gc "gopkg.in/check.v1"
    18  	"gopkg.in/juju/names.v2"
    19  	"gopkg.in/juju/worker.v1"
    20  	"gopkg.in/juju/worker.v1/workertest"
    21  
    22  	"github.com/juju/juju/pubsub/apiserver"
    23  	"github.com/juju/juju/pubsub/centralhub"
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/worker/raft/raftbackstop"
    26  )
    27  
    28  type workerFixture struct {
    29  	testing.IsolationSuite
    30  	raft     *mockRaft
    31  	logStore *mockLogStore
    32  	hub      *pubsub.StructuredHub
    33  	config   raftbackstop.Config
    34  }
    35  
    36  func (s *workerFixture) SetUpTest(c *gc.C) {
    37  	s.IsolationSuite.SetUpTest(c)
    38  	tag := names.NewMachineTag("23")
    39  	s.raft = &mockRaft{}
    40  	s.logStore = &mockLogStore{}
    41  	s.hub = centralhub.New(tag)
    42  	s.config = raftbackstop.Config{
    43  		Raft:     s.raft,
    44  		LogStore: s.logStore,
    45  		Hub:      s.hub,
    46  		LocalID:  "23",
    47  		Logger:   loggo.GetLogger("raftbackstop_test"),
    48  	}
    49  }
    50  
    51  type WorkerValidationSuite struct {
    52  	workerFixture
    53  }
    54  
    55  var _ = gc.Suite(&WorkerValidationSuite{})
    56  
    57  func (s *WorkerValidationSuite) TestValidateErrors(c *gc.C) {
    58  	type test struct {
    59  		f      func(*raftbackstop.Config)
    60  		expect string
    61  	}
    62  	tests := []test{{
    63  		func(cfg *raftbackstop.Config) { cfg.Raft = nil },
    64  		"nil Raft not valid",
    65  	}, {
    66  		func(cfg *raftbackstop.Config) { cfg.Hub = nil },
    67  		"nil Hub not valid",
    68  	}, {
    69  		func(cfg *raftbackstop.Config) { cfg.LogStore = nil },
    70  		"nil LogStore not valid",
    71  	}, {
    72  		func(cfg *raftbackstop.Config) { cfg.LocalID = "" },
    73  		"empty LocalID not valid",
    74  	}, {
    75  		func(cfg *raftbackstop.Config) { cfg.Logger = nil },
    76  		"nil Logger not valid",
    77  	}}
    78  	for i, test := range tests {
    79  		c.Logf("test #%d (%s)", i, test.expect)
    80  		s.testValidateError(c, test.f, test.expect)
    81  	}
    82  }
    83  
    84  func (s *WorkerValidationSuite) testValidateError(c *gc.C, f func(*raftbackstop.Config), expect string) {
    85  	config := s.config
    86  	f(&config)
    87  	w, err := raftbackstop.NewWorker(config)
    88  	if !c.Check(err, gc.NotNil) {
    89  		workertest.DirtyKill(c, w)
    90  		return
    91  	}
    92  	c.Check(w, gc.IsNil)
    93  	c.Check(err, gc.ErrorMatches, expect)
    94  }
    95  
    96  type WorkerSuite struct {
    97  	workerFixture
    98  	worker worker.Worker
    99  	reqs   chan apiserver.DetailsRequest
   100  }
   101  
   102  var _ = gc.Suite(&WorkerSuite{})
   103  
   104  func (s *WorkerSuite) SetUpTest(c *gc.C) {
   105  	s.workerFixture.SetUpTest(c)
   106  	s.reqs = make(chan apiserver.DetailsRequest, 10)
   107  
   108  	// Use a local variable to send to the channel in the callback, so
   109  	// we don't get races when a subsequent test overwrites s.reqs
   110  	// with a new channel.
   111  	reqs := s.reqs
   112  	unsubscribe, err := s.hub.Subscribe(
   113  		apiserver.DetailsRequestTopic,
   114  		func(topic string, req apiserver.DetailsRequest, err error) {
   115  			c.Check(err, jc.ErrorIsNil)
   116  			reqs <- req
   117  		},
   118  	)
   119  	c.Assert(err, jc.ErrorIsNil)
   120  	s.AddCleanup(func(c *gc.C) { unsubscribe() })
   121  
   122  	worker, err := raftbackstop.NewWorker(s.config)
   123  	c.Assert(err, jc.ErrorIsNil)
   124  	s.AddCleanup(func(c *gc.C) {
   125  		workertest.DirtyKill(c, worker)
   126  	})
   127  	s.worker = worker
   128  }
   129  
   130  func (s *WorkerSuite) TestCleanKill(c *gc.C) {
   131  	workertest.CleanKill(c, s.worker)
   132  }
   133  
   134  func (s *WorkerSuite) TestRequestsDetails(c *gc.C) {
   135  	// The worker is started in SetUpTest.
   136  	select {
   137  	case req := <-s.reqs:
   138  		c.Assert(req, gc.Equals, apiserver.DetailsRequest{
   139  			Requester: "raft-backstop",
   140  			LocalOnly: true,
   141  		})
   142  	case <-time.After(coretesting.LongWait):
   143  		c.Fatalf("timed out waiting for details request")
   144  	}
   145  }
   146  
   147  func (s *WorkerSuite) findStoreLogCalls() []*raft.Log {
   148  	var results []*raft.Log
   149  	for _, call := range s.logStore.Calls() {
   150  		if call.FuncName != "StoreLog" {
   151  			continue
   152  		}
   153  		results = append(results, call.Args[0].(*raft.Log))
   154  	}
   155  	return results
   156  }
   157  
   158  func (s *WorkerSuite) assertRecovery(c *gc.C, index, term uint64, server raft.Server) {
   159  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   160  		if len(s.findStoreLogCalls()) != 0 {
   161  			break
   162  		}
   163  	}
   164  	storedLogs := s.findStoreLogCalls()
   165  	c.Assert(storedLogs, gc.HasLen, 1)
   166  	log := storedLogs[0]
   167  	c.Assert(log.Type, gc.Equals, raft.LogConfiguration)
   168  	c.Assert(log.Index, gc.Equals, index)
   169  	c.Assert(log.Term, gc.Equals, term)
   170  	c.Assert(decodeConfiguration(c, log.Data), gc.DeepEquals, raft.Configuration{
   171  		Servers: []raft.Server{server},
   172  	})
   173  }
   174  
   175  func (s *WorkerSuite) TestRecoversClusterOneNonvoter(c *gc.C) {
   176  	s.raft.setValues(raft.Follower, &mockConfigFuture{conf: raft.Configuration{
   177  		Servers: []raft.Server{{
   178  			ID:       "23",
   179  			Address:  "address",
   180  			Suffrage: raft.Nonvoter,
   181  		}},
   182  	}})
   183  	// We don't care about other fields in this case.
   184  	s.logStore.setLastLog(&raft.Log{
   185  		Index: 451,
   186  		Term:  66,
   187  	})
   188  	s.publishDetails(c, map[string]string{"23": "address"})
   189  	s.assertRecovery(c, 452, 66, raft.Server{
   190  		ID:       "23",
   191  		Address:  "address",
   192  		Suffrage: raft.Voter,
   193  	})
   194  }
   195  
   196  func (s *WorkerSuite) TestRecoversClusterTwoVoters(c *gc.C) {
   197  	s.raft.setValues(raft.Follower, &mockConfigFuture{conf: raft.Configuration{
   198  		Servers: []raft.Server{{
   199  			ID:       "23",
   200  			Address:  "address",
   201  			Suffrage: raft.Voter,
   202  		}, {
   203  			ID:       "100",
   204  			Address:  "otheraddress",
   205  			Suffrage: raft.Voter,
   206  		}},
   207  	}})
   208  	s.logStore.setLastLog(&raft.Log{
   209  		Index: 451,
   210  		Term:  66,
   211  	})
   212  	s.publishDetails(c, map[string]string{"23": "address"})
   213  	s.assertRecovery(c, 452, 66, raft.Server{
   214  		ID:       "23",
   215  		Address:  "address",
   216  		Suffrage: raft.Voter,
   217  	})
   218  }
   219  
   220  func (s *WorkerSuite) assertNoRecovery(c *gc.C) {
   221  	time.Sleep(coretesting.ShortWait)
   222  	c.Assert(s.findStoreLogCalls(), gc.HasLen, 0)
   223  }
   224  
   225  func (s *WorkerSuite) TestOnlyRecoversClusterOnce(c *gc.C) {
   226  	s.raft.setValues(raft.Follower, &mockConfigFuture{conf: raft.Configuration{
   227  		Servers: []raft.Server{{
   228  			ID:       "23",
   229  			Address:  "address",
   230  			Suffrage: raft.Nonvoter,
   231  		}},
   232  	}})
   233  	// We don't care about other fields in this case.
   234  	s.logStore.setLastLog(&raft.Log{
   235  		Index: 451,
   236  		Term:  66,
   237  	})
   238  	s.publishDetails(c, map[string]string{"23": "address"})
   239  	s.assertRecovery(c, 452, 66, raft.Server{
   240  		ID:       "23",
   241  		Address:  "address",
   242  		Suffrage: raft.Voter,
   243  	})
   244  	s.logStore.ResetCalls()
   245  	s.publishDetails(c, map[string]string{"23": "address"})
   246  	s.assertNoRecovery(c)
   247  }
   248  
   249  func (s *WorkerSuite) TestNoRecoveryIfMultipleMachines(c *gc.C) {
   250  	s.raft.setValues(raft.Follower, &mockConfigFuture{conf: raft.Configuration{
   251  		Servers: []raft.Server{{
   252  			ID:       "23",
   253  			Address:  "address",
   254  			Suffrage: raft.Voter,
   255  		}, {
   256  			ID:       "100",
   257  			Address:  "otheraddress",
   258  			Suffrage: raft.Voter,
   259  		}},
   260  	}})
   261  	s.logStore.setLastLog(&raft.Log{
   262  		Index: 451,
   263  		Term:  66,
   264  	})
   265  	s.publishDetails(c, map[string]string{
   266  		"23":  "address",
   267  		"100": "otheraddress",
   268  	})
   269  	s.assertNoRecovery(c)
   270  }
   271  
   272  func (s *WorkerSuite) TestNoRecoveryIfNotInServerDetails(c *gc.C) {
   273  	s.raft.setValues(raft.Follower, &mockConfigFuture{conf: raft.Configuration{
   274  		Servers: []raft.Server{{
   275  			ID:       "23",
   276  			Address:  "address",
   277  			Suffrage: raft.Voter,
   278  		}, {
   279  			ID:       "100",
   280  			Address:  "otheraddress",
   281  			Suffrage: raft.Voter,
   282  		}},
   283  	}})
   284  	s.logStore.setLastLog(&raft.Log{
   285  		Index: 451,
   286  		Term:  66,
   287  	})
   288  	s.publishDetails(c, map[string]string{"100": "otheraddress"})
   289  	s.assertNoRecovery(c)
   290  }
   291  
   292  func (s *WorkerSuite) TestNoRecoveryIfNotInRaftConfig(c *gc.C) {
   293  	s.raft.setValues(raft.Follower, &mockConfigFuture{conf: raft.Configuration{
   294  		Servers: []raft.Server{{
   295  			ID:       "100",
   296  			Address:  "otheraddress",
   297  			Suffrage: raft.Voter,
   298  		}},
   299  	}})
   300  	s.logStore.setLastLog(&raft.Log{
   301  		Index: 451,
   302  		Term:  66,
   303  	})
   304  	s.publishDetails(c, map[string]string{
   305  		"23": "address",
   306  	})
   307  	s.assertNoRecovery(c)
   308  }
   309  
   310  func (s *WorkerSuite) TestNoRecoveryIfOneRaftNodeAndVoter(c *gc.C) {
   311  	s.raft.setValues(raft.Follower, &mockConfigFuture{conf: raft.Configuration{
   312  		Servers: []raft.Server{{
   313  			ID:       "23",
   314  			Address:  "address",
   315  			Suffrage: raft.Voter,
   316  		}},
   317  	}})
   318  	s.logStore.setLastLog(&raft.Log{
   319  		Index: 451,
   320  		Term:  66,
   321  	})
   322  	s.publishDetails(c, map[string]string{"23": "address"})
   323  	s.assertNoRecovery(c)
   324  }
   325  
   326  func (s *WorkerSuite) publishDetails(c *gc.C, serverAddrs map[string]string) {
   327  	details := makeDetails(serverAddrs)
   328  	received, err := s.hub.Publish(apiserver.DetailsTopic, details)
   329  	c.Assert(err, jc.ErrorIsNil)
   330  	select {
   331  	case <-received:
   332  	case <-time.After(coretesting.LongWait):
   333  		c.Fatal("timed out waiting for details to be received")
   334  	}
   335  }
   336  
   337  func makeDetails(serverInfo map[string]string) apiserver.Details {
   338  	servers := make(map[string]apiserver.APIServer)
   339  	for id, address := range serverInfo {
   340  		servers[id] = apiserver.APIServer{
   341  			ID:              id,
   342  			InternalAddress: address,
   343  		}
   344  	}
   345  	return apiserver.Details{Servers: servers}
   346  }
   347  
   348  func decodeConfiguration(c *gc.C, data []byte) (out raft.Configuration) {
   349  	buf := bytes.NewReader(data)
   350  	hd := codec.MsgpackHandle{}
   351  	dec := codec.NewDecoder(buf, &hd)
   352  	err := dec.Decode(&out)
   353  	c.Assert(err, jc.ErrorIsNil)
   354  	return out
   355  }
   356  
   357  type mockRaft struct {
   358  	mu    sync.Mutex
   359  	state raft.RaftState
   360  	cf    *mockConfigFuture
   361  }
   362  
   363  func (r *mockRaft) State() raft.RaftState {
   364  	r.mu.Lock()
   365  	defer r.mu.Unlock()
   366  	return r.state
   367  }
   368  
   369  func (r *mockRaft) GetConfiguration() raft.ConfigurationFuture {
   370  	r.mu.Lock()
   371  	defer r.mu.Unlock()
   372  	return r.cf
   373  }
   374  
   375  func (r *mockRaft) setValues(state raft.RaftState, cf *mockConfigFuture) {
   376  	r.mu.Lock()
   377  	defer r.mu.Unlock()
   378  	r.state = state
   379  	r.cf = cf
   380  }
   381  
   382  type mockConfigFuture struct {
   383  	raft.IndexFuture
   384  	testing.Stub
   385  	conf raft.Configuration
   386  }
   387  
   388  func (f *mockConfigFuture) Error() error {
   389  	f.AddCall("Error")
   390  	return f.NextErr()
   391  }
   392  
   393  func (f *mockConfigFuture) Configuration() raft.Configuration {
   394  	f.AddCall("Configuration")
   395  	return f.conf
   396  }
   397  
   398  type mockLogStore struct {
   399  	raft.LogStore
   400  	testing.Stub
   401  	mu      sync.Mutex
   402  	lastLog raft.Log
   403  }
   404  
   405  func (s *mockLogStore) LastIndex() (uint64, error) {
   406  	s.mu.Lock()
   407  	defer s.mu.Unlock()
   408  	s.AddCall("LastIndex")
   409  	return s.lastLog.Index, s.NextErr()
   410  }
   411  
   412  func (s *mockLogStore) GetLog(index uint64, out *raft.Log) error {
   413  	s.AddCall("GetLog", index, out)
   414  	*out = s.lastLog
   415  	return s.NextErr()
   416  }
   417  
   418  func (s *mockLogStore) StoreLog(log *raft.Log) error {
   419  	s.AddCall("StoreLog", log)
   420  	return s.NextErr()
   421  }
   422  
   423  func (s *mockLogStore) setLastLog(lastLog *raft.Log) {
   424  	s.mu.Lock()
   425  	defer s.mu.Unlock()
   426  	s.lastLog = *lastLog
   427  }