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 }