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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package raftforwarder_test
     5  
     6  import (
     7  	"sync/atomic"
     8  	"time"
     9  
    10  	"github.com/hashicorp/raft"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	"github.com/juju/pubsub"
    14  	"github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	gc "gopkg.in/check.v1"
    17  	"gopkg.in/juju/names.v2"
    18  	"gopkg.in/juju/worker.v1"
    19  	"gopkg.in/juju/worker.v1/workertest"
    20  
    21  	"github.com/juju/juju/core/lease"
    22  	"github.com/juju/juju/core/raftlease"
    23  	"github.com/juju/juju/pubsub/centralhub"
    24  	coretesting "github.com/juju/juju/testing"
    25  	"github.com/juju/juju/worker/raft/raftforwarder"
    26  )
    27  
    28  type workerFixture struct {
    29  	testing.IsolationSuite
    30  	raft     *mockRaft
    31  	response *mockResponse
    32  	target   *fakeTarget
    33  	hub      *pubsub.StructuredHub
    34  	config   raftforwarder.Config
    35  }
    36  
    37  func (s *workerFixture) SetUpTest(c *gc.C) {
    38  	s.IsolationSuite.SetUpTest(c)
    39  	err := loggo.ConfigureLoggers("TRACE")
    40  	c.Assert(err, jc.ErrorIsNil)
    41  
    42  	s.response = &mockResponse{}
    43  	s.raft = &mockRaft{af: &mockApplyFuture{
    44  		response: s.response,
    45  	}}
    46  	s.target = &fakeTarget{}
    47  	s.hub = centralhub.New(names.NewMachineTag("17"))
    48  	s.config = raftforwarder.Config{
    49  		Hub:    s.hub,
    50  		Raft:   s.raft,
    51  		Logger: loggo.GetLogger("raftforwarder_test"),
    52  		Topic:  "raftforwarder_test",
    53  		Target: s.target,
    54  	}
    55  }
    56  
    57  type workerValidationSuite struct {
    58  	workerFixture
    59  }
    60  
    61  var _ = gc.Suite(&workerValidationSuite{})
    62  
    63  func (s *workerValidationSuite) TestValidateErrors(c *gc.C) {
    64  	type test struct {
    65  		f      func(*raftforwarder.Config)
    66  		expect string
    67  	}
    68  	tests := []test{{
    69  		func(cfg *raftforwarder.Config) { cfg.Raft = nil },
    70  		"nil Raft not valid",
    71  	}, {
    72  		func(cfg *raftforwarder.Config) { cfg.Hub = nil },
    73  		"nil Hub not valid",
    74  	}, {
    75  		func(cfg *raftforwarder.Config) { cfg.Logger = nil },
    76  		"nil Logger not valid",
    77  	}, {
    78  		func(cfg *raftforwarder.Config) { cfg.Topic = "" },
    79  		"empty Topic not valid",
    80  	}, {
    81  		func(cfg *raftforwarder.Config) { cfg.Target = nil },
    82  		"nil Target not valid",
    83  	}}
    84  	for i, test := range tests {
    85  		c.Logf("test #%d (%s)", i, test.expect)
    86  		s.testValidateError(c, test.f, test.expect)
    87  	}
    88  }
    89  
    90  func (s *workerValidationSuite) testValidateError(c *gc.C, f func(*raftforwarder.Config), expect string) {
    91  	config := s.config
    92  	f(&config)
    93  	w, err := raftforwarder.NewWorker(config)
    94  	if !c.Check(err, gc.NotNil) {
    95  		workertest.DirtyKill(c, w)
    96  		return
    97  	}
    98  	c.Check(w, gc.IsNil)
    99  	c.Check(err, gc.ErrorMatches, expect)
   100  }
   101  
   102  type workerSuite struct {
   103  	workerFixture
   104  	worker worker.Worker
   105  	resps  chan raftlease.ForwardResponse
   106  }
   107  
   108  var _ = gc.Suite(&workerSuite{})
   109  
   110  func (s *workerSuite) SetUpTest(c *gc.C) {
   111  	s.workerFixture.SetUpTest(c)
   112  	s.resps = make(chan raftlease.ForwardResponse)
   113  
   114  	// Use a local variable to send to the channel in the callback, so
   115  	// we don't get races when a subsequent test overwrites s.resps
   116  	// with a new channel.
   117  	resps := s.resps
   118  	unsubscribe, err := s.hub.Subscribe(
   119  		"response",
   120  		func(_ string, resp raftlease.ForwardResponse, err error) {
   121  			c.Check(err, jc.ErrorIsNil)
   122  			resps <- resp
   123  		},
   124  	)
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	s.AddCleanup(func(c *gc.C) { unsubscribe() })
   127  
   128  	worker, err := raftforwarder.NewWorker(s.config)
   129  	c.Assert(err, jc.ErrorIsNil)
   130  	s.AddCleanup(func(c *gc.C) {
   131  		workertest.DirtyKill(c, worker)
   132  	})
   133  	s.worker = worker
   134  }
   135  
   136  func (s *workerSuite) TestCleanKill(c *gc.C) {
   137  	workertest.CleanKill(c, s.worker)
   138  }
   139  
   140  func (s *workerSuite) TestSuccess(c *gc.C) {
   141  	_, err := s.hub.Publish("raftforwarder_test", raftlease.ForwardRequest{
   142  		Command:       "myanmar",
   143  		ResponseTopic: "response",
   144  	})
   145  	c.Assert(err, jc.ErrorIsNil)
   146  
   147  	select {
   148  	case resp := <-s.resps:
   149  		c.Assert(resp, gc.DeepEquals, raftlease.ForwardResponse{})
   150  	case <-time.After(coretesting.LongWait):
   151  		c.Fatalf("timed out waiting for response")
   152  	}
   153  
   154  	s.raft.CheckCall(c, 0, "Apply", []byte("myanmar"), 5*time.Second)
   155  	s.response.CheckCall(c, 0, "Notify", s.target)
   156  }
   157  
   158  func (s *workerSuite) TestApplyError(c *gc.C) {
   159  	s.raft.af.SetErrors(errors.Errorf("boom"))
   160  	_, err := s.hub.Publish("raftforwarder_test", raftlease.ForwardRequest{
   161  		Command:       "france",
   162  		ResponseTopic: "response",
   163  	})
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	err = workertest.CheckKilled(c, s.worker)
   166  	c.Assert(err, gc.ErrorMatches, "applying command: boom")
   167  
   168  	select {
   169  	case <-s.resps:
   170  		c.Fatalf("unexpected response")
   171  	case <-time.After(coretesting.ShortWait):
   172  	}
   173  }
   174  
   175  func (s *workerSuite) TestBadResponseType(c *gc.C) {
   176  	s.raft.af.response = "23 skidoo!"
   177  	_, err := s.hub.Publish("raftforwarder_test", raftlease.ForwardRequest{
   178  		Command:       "france",
   179  		ResponseTopic: "response",
   180  	})
   181  	c.Assert(err, jc.ErrorIsNil)
   182  	err = workertest.CheckKilled(c, s.worker)
   183  	c.Assert(err, gc.ErrorMatches, `applying command: expected an FSMResponse, got string: "23 skidoo!"`)
   184  
   185  	select {
   186  	case <-s.resps:
   187  		c.Fatalf("unexpected response")
   188  	case <-time.After(coretesting.ShortWait):
   189  	}
   190  }
   191  
   192  func (s *workerSuite) TestResponseGenericError(c *gc.C) {
   193  	s.response.SetErrors(errors.Errorf("help!"))
   194  	_, err := s.hub.Publish("raftforwarder_test", raftlease.ForwardRequest{
   195  		Command:       "france",
   196  		ResponseTopic: "response",
   197  	})
   198  	c.Assert(err, jc.ErrorIsNil)
   199  
   200  	select {
   201  	case resp := <-s.resps:
   202  		c.Assert(resp, gc.DeepEquals, raftlease.ForwardResponse{
   203  			Error: &raftlease.ResponseError{"help!", "error"},
   204  		})
   205  	case <-time.After(coretesting.LongWait):
   206  		c.Fatalf("timed out waiting for response")
   207  	}
   208  }
   209  
   210  func (s *workerSuite) TestResponseSingletonError(c *gc.C) {
   211  	s.response.SetErrors(errors.Annotate(lease.ErrInvalid, "some context"))
   212  	_, err := s.hub.Publish("raftforwarder_test", raftlease.ForwardRequest{
   213  		Command:       "france",
   214  		ResponseTopic: "response",
   215  	})
   216  	c.Assert(err, jc.ErrorIsNil)
   217  
   218  	select {
   219  	case resp := <-s.resps:
   220  		c.Assert(resp, gc.DeepEquals, raftlease.ForwardResponse{
   221  			Error: &raftlease.ResponseError{"some context: invalid lease operation", "invalid"},
   222  		})
   223  	case <-time.After(coretesting.LongWait):
   224  		c.Fatalf("timed out waiting for response")
   225  	}
   226  }
   227  
   228  func (s *workerSuite) TestHandlesRequestsConcurrently(c *gc.C) {
   229  	resps2 := make(chan raftlease.ForwardResponse)
   230  	unsubscribe, err := s.hub.Subscribe(
   231  		"response2",
   232  		func(_ string, resp raftlease.ForwardResponse, err error) {
   233  			c.Check(err, jc.ErrorIsNil)
   234  			resps2 <- resp
   235  		},
   236  	)
   237  	c.Assert(err, jc.ErrorIsNil)
   238  	defer unsubscribe()
   239  
   240  	var calls int32
   241  	started := make(chan struct{})
   242  	finish := make(chan struct{})
   243  	s.raft.af.callback = func() {
   244  		call := atomic.AddInt32(&calls, 1)
   245  		// The first call blocks until we signal it.
   246  		if call == 1 {
   247  			close(started)
   248  			<-finish
   249  		}
   250  	}
   251  
   252  	// Send a request (response to come on s.resps) that blocks.
   253  	_, err = s.hub.Publish("raftforwarder_test", raftlease.ForwardRequest{
   254  		Command:       "myanmar",
   255  		ResponseTopic: "response",
   256  	})
   257  	c.Assert(err, jc.ErrorIsNil)
   258  
   259  	select {
   260  	case <-started:
   261  	case <-time.After(coretesting.LongWait):
   262  		c.Fatalf("timed out waiting for first request to start")
   263  	}
   264  
   265  	// Send a request (response to come on s.resps) that blocks.
   266  	_, err = s.hub.Publish("raftforwarder_test", raftlease.ForwardRequest{
   267  		Command:       "myanmar",
   268  		ResponseTopic: "response2",
   269  	})
   270  	c.Assert(err, jc.ErrorIsNil)
   271  
   272  	select {
   273  	case <-resps2:
   274  	case <-time.After(coretesting.LongWait):
   275  		c.Fatalf("timed out waiting for response from second request")
   276  	}
   277  
   278  	select {
   279  	case <-s.resps:
   280  		c.Fatalf("got response from first request too early")
   281  	case <-time.After(coretesting.ShortWait):
   282  	}
   283  
   284  	close(finish)
   285  
   286  	select {
   287  	case <-s.resps:
   288  	case <-time.After(coretesting.LongWait):
   289  		c.Fatalf("timed out waiting for response from first request")
   290  	}
   291  }
   292  
   293  type mockRaft struct {
   294  	testing.Stub
   295  	af *mockApplyFuture
   296  }
   297  
   298  func (r *mockRaft) Apply(cmd []byte, timeout time.Duration) raft.ApplyFuture {
   299  	r.AddCall("Apply", cmd, timeout)
   300  	return r.af
   301  }
   302  
   303  type mockApplyFuture struct {
   304  	raft.IndexFuture
   305  	testing.Stub
   306  	response interface{}
   307  	callback func()
   308  }
   309  
   310  func (f *mockApplyFuture) Error() error {
   311  	if f.callback != nil {
   312  		f.callback()
   313  	}
   314  	f.AddCall("Error")
   315  	return f.NextErr()
   316  }
   317  
   318  func (f *mockApplyFuture) Response() interface{} {
   319  	f.AddCall("Response")
   320  	return f.response
   321  }
   322  
   323  type mockResponse struct {
   324  	testing.Stub
   325  }
   326  
   327  func (r *mockResponse) Error() error {
   328  	return r.NextErr()
   329  }
   330  
   331  func (r *mockResponse) Notify(target raftlease.NotifyTarget) {
   332  	r.AddCall("Notify", target)
   333  }
   334  
   335  type fakeTarget struct {
   336  	raftlease.NotifyTarget
   337  }