github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/pubsub/remoteserver_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package pubsub_test 5 6 import ( 7 "fmt" 8 "sync" 9 "time" 10 11 "github.com/juju/clock/testclock" 12 "github.com/juju/errors" 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/workertest" 20 21 "github.com/juju/juju/api" 22 "github.com/juju/juju/apiserver/params" 23 "github.com/juju/juju/pubsub/centralhub" 24 "github.com/juju/juju/pubsub/forwarder" 25 coretesting "github.com/juju/juju/testing" 26 psworker "github.com/juju/juju/worker/pubsub" 27 ) 28 29 type RemoteServerSuite struct { 30 testing.IsolationSuite 31 connectionOpener *fakeConnectionOpener 32 config psworker.RemoteServerConfig 33 clock *testclock.Clock 34 hub *pubsub.StructuredHub 35 origin string 36 } 37 38 var _ = gc.Suite(&RemoteServerSuite{}) 39 40 func (s *RemoteServerSuite) SetUpTest(c *gc.C) { 41 s.IsolationSuite.SetUpTest(c) 42 logger := loggo.GetLogger("juju.worker.pubsub") 43 logger.SetLogLevel(loggo.TRACE) 44 s.connectionOpener = &fakeConnectionOpener{} 45 tag := names.NewMachineTag("42") 46 s.clock = testclock.NewClock(time.Now()) 47 s.hub = centralhub.New(tag) 48 s.origin = tag.String() 49 s.config = psworker.RemoteServerConfig{ 50 Hub: s.hub, 51 Origin: s.origin, 52 Target: "target", 53 Clock: s.clock, 54 Logger: logger, 55 APIInfo: &api.Info{ 56 Addrs: []string{"localhost"}, 57 CACert: "fake as", 58 Tag: tag, 59 }, 60 NewWriter: s.connectionOpener.newWriter, 61 } 62 } 63 64 func (s *RemoteServerSuite) TestCleanShutdown(c *gc.C) { 65 server, err := psworker.NewRemoteServer(s.config) 66 c.Assert(err, jc.ErrorIsNil) 67 workertest.CleanKill(c, server) 68 } 69 70 func (s *RemoteServerSuite) TestConnectPublished(c *gc.C) { 71 done := make(chan struct{}) 72 unsub, err := s.config.Hub.Subscribe(forwarder.ConnectedTopic, func(_ string, data map[string]interface{}) { 73 c.Check(data["target"], gc.Equals, "target") 74 c.Check(data["origin"], gc.Equals, "machine-42") 75 close(done) 76 }) 77 c.Assert(err, jc.ErrorIsNil) 78 defer unsub() 79 server, err := psworker.NewRemoteServer(s.config) 80 c.Assert(err, jc.ErrorIsNil) 81 defer workertest.CleanKill(c, server) 82 83 select { 84 case <-done: 85 case <-time.After(coretesting.LongWait): 86 c.Fatal("no connect message published") 87 } 88 // Make sure that it is reported as started. 89 r, ok := server.(psworker.Reporter) 90 c.Assert(ok, jc.IsTrue) 91 // Since we are just testing the remote, the code that makes sure the 92 // published message is forwarded is the subscriber, so we will always 93 // show empty queue and none sent. 94 c.Check(r.IntrospectionReport(), gc.Equals, ""+ 95 " Status: connected\n"+ 96 " Addresses: [localhost]\n"+ 97 " Queue length: 0\n"+ 98 " Sent count: 0\n") 99 c.Check(r.Report(), jc.DeepEquals, map[string]interface{}{ 100 "status": "connected", 101 "addresses": []string{"localhost"}, 102 "queue-len": 0, 103 "sent": uint64(0), 104 }) 105 } 106 107 func (s *RemoteServerSuite) TestDisconnectPublishedOnWriteError(c *gc.C) { 108 done := make(chan struct{}) 109 unsub, err := s.config.Hub.Subscribe(forwarder.DisconnectedTopic, func(_ string, data map[string]interface{}) { 110 c.Check(data["target"], gc.Equals, "target") 111 c.Check(data["origin"], gc.Equals, "machine-42") 112 select { 113 case <-done: 114 c.Fatal("closed already") 115 default: 116 close(done) 117 } 118 }) 119 c.Assert(err, jc.ErrorIsNil) 120 defer unsub() 121 s.connectionOpener.forwardErr = errors.New("forward fail") 122 123 server := s.newConnectedServer(c) 124 server.Publish(¶ms.PubSubMessage{ 125 Topic: "some topic", 126 }) 127 128 select { 129 case <-done: 130 case <-time.After(coretesting.LongWait): 131 c.Fatal("no disconnect message published") 132 } 133 } 134 135 func (s *RemoteServerSuite) TestConnectErrorRetryDelay(c *gc.C) { 136 now := s.clock.Now() 137 delays := make([]string, 0) 138 s.connectionOpener.err = errors.New("oops") 139 s.connectionOpener.callback = func(_ *api.Info) { 140 delay := s.clock.Now().Sub(now) 141 now = s.clock.Now() 142 delays = append(delays, fmt.Sprint(delay)) 143 } 144 145 server, err := psworker.NewRemoteServer(s.config) 146 c.Assert(err, jc.ErrorIsNil) 147 defer workertest.CleanKill(c, server) 148 149 for i := 0; i < 1200; i++ { 150 s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 1) 151 } 152 // Starts immediately, with a one second delay doubling each failure 153 // up to a max wait time of 5 minutes. 154 c.Assert(delays, jc.DeepEquals, []string{ 155 "0s", "1s", "2s", "4s", "8s", "16s", "32s", 156 "1m4s", "2m8s", "4m16s", 157 "5m0s", "5m0s", 158 }) 159 } 160 161 func (s *RemoteServerSuite) TestConnectRetryInterruptedOnTargetConnection(c *gc.C) { 162 now := s.clock.Now() 163 delays := make([]string, 0) 164 s.connectionOpener.err = errors.New("oops") 165 s.connectionOpener.callback = func(_ *api.Info) { 166 delay := s.clock.Now().Sub(now) 167 now = s.clock.Now() 168 delays = append(delays, fmt.Sprint(delay)) 169 } 170 171 server, err := psworker.NewRemoteServer(s.config) 172 c.Assert(err, jc.ErrorIsNil) 173 defer workertest.CleanKill(c, server) 174 175 for i := 0; i < 35; i++ { 176 s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 1) 177 } 178 // This leaves us 4s into a 32s retry wait. 179 done, err := s.hub.Publish(forwarder.ConnectedTopic, forwarder.OriginTarget{ 180 Target: s.origin, 181 Origin: "target", 182 }) 183 c.Assert(err, jc.ErrorIsNil) 184 select { 185 case <-done: 186 case <-time.After(coretesting.LongWait): 187 c.Fatal("worker didn't consume the event") 188 } 189 190 // Now advance the clock some more 191 for i := 0; i < 10; i++ { 192 s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 2) 193 } 194 195 c.Assert(delays, jc.DeepEquals, []string{ 196 "0s", "1s", "2s", "4s", "8s", "16s", // standard fallback 197 "5s", // 4s due to interruption, 1s due to loop delay on failure 198 "1s", "2s", "4s", // standard fallback 199 }) 200 } 201 202 func (s *RemoteServerSuite) TestConnectRetryInterruptedWithNewAddresses(c *gc.C) { 203 now := s.clock.Now() 204 delays := make([]string, 0) 205 expected := []string{"localhost"} 206 s.connectionOpener.err = errors.New("oops") 207 s.connectionOpener.callback = func(info *api.Info) { 208 c.Check(info.Addrs, jc.DeepEquals, expected) 209 delay := s.clock.Now().Sub(now) 210 now = s.clock.Now() 211 delays = append(delays, fmt.Sprint(delay)) 212 } 213 214 server, err := psworker.NewRemoteServer(s.config) 215 c.Assert(err, jc.ErrorIsNil) 216 defer workertest.CleanKill(c, server) 217 218 for i := 0; i < 35; i++ { 219 s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 1) 220 } 221 // This leaves us 4s into a 32s retry wait. 222 223 expected = []string{"new addresses"} 224 server.UpdateAddresses(expected) 225 226 // Now advance the clock some more 227 for i := 0; i < 10; i++ { 228 s.clock.WaitAdvance(time.Second, coretesting.ShortWait, 2) 229 } 230 231 c.Assert(delays, jc.DeepEquals, []string{ 232 "0s", "1s", "2s", "4s", "8s", "16s", // standard fallback 233 "5s", // 4s due to interruption, 1s due to loop delay on failure 234 "1s", "2s", "4s", // standard fallback 235 }) 236 } 237 238 func (s *RemoteServerSuite) newConnectedServer(c *gc.C) psworker.RemoteServer { 239 connected := make(chan struct{}) 240 unsub, err := s.config.Hub.Subscribe(forwarder.ConnectedTopic, func(_ string, _ map[string]interface{}) { 241 close(connected) 242 }) 243 c.Assert(err, jc.ErrorIsNil) 244 defer unsub() 245 246 server, err := psworker.NewRemoteServer(s.config) 247 c.Assert(err, jc.ErrorIsNil) 248 s.AddCleanup(func(*gc.C) { workertest.CleanKill(c, server) }) 249 250 select { 251 case <-connected: 252 case <-time.After(coretesting.LongWait): 253 c.Fatal("no connect message published") 254 } 255 return server 256 } 257 258 func (s *RemoteServerSuite) TestSendsMessages(c *gc.C) { 259 numMessages := 10 260 done := make(chan struct{}) 261 // Close the done channel when the writer has received the 262 // appropriate number of messages 263 go func() { 264 defer close(done) 265 for { 266 if s.writer().count() == numMessages { 267 return 268 } 269 } 270 }() 271 272 server := s.newConnectedServer(c) 273 274 for i := 0; i < numMessages; i++ { 275 server.Publish(¶ms.PubSubMessage{ 276 Topic: fmt.Sprintf("topic.%d", i), 277 }) 278 } 279 280 select { 281 case <-done: 282 case <-time.After(coretesting.LongWait): 283 c.Fatalf("not all messages received, got %d", s.writer().count()) 284 } 285 286 for i := 0; i < numMessages; i++ { 287 c.Check(s.writer().messages[i].Topic, gc.Equals, fmt.Sprintf("topic.%d", i)) 288 } 289 } 290 291 func (s *RemoteServerSuite) writer() *messageWriter { 292 writer := s.connectionOpener.getWriter() 293 if writer == nil { 294 return &messageWriter{} 295 } 296 return writer 297 } 298 299 type fakeConnectionOpener struct { 300 mutex sync.Mutex 301 err error 302 callback func(*api.Info) 303 writer *messageWriter 304 forwardErr error 305 } 306 307 func (f *fakeConnectionOpener) getWriter() *messageWriter { 308 f.mutex.Lock() 309 defer f.mutex.Unlock() 310 return f.writer 311 } 312 313 func (f *fakeConnectionOpener) newWriter(info *api.Info) (psworker.MessageWriter, error) { 314 f.mutex.Lock() 315 defer f.mutex.Unlock() 316 if f.callback != nil { 317 f.callback(info) 318 } 319 if f.err != nil { 320 return nil, f.err 321 } 322 f.writer = &messageWriter{err: f.forwardErr} 323 return f.writer, nil 324 } 325 326 type messageWriter struct { 327 messages []*params.PubSubMessage 328 mutex sync.Mutex 329 err error 330 } 331 332 func (m *messageWriter) count() int { 333 m.mutex.Lock() 334 defer m.mutex.Unlock() 335 return len(m.messages) 336 } 337 338 func (m *messageWriter) ForwardMessage(message *params.PubSubMessage) error { 339 m.mutex.Lock() 340 defer m.mutex.Unlock() 341 if m.err != nil { 342 return m.err 343 } 344 m.messages = append(m.messages, message) 345 return nil 346 } 347 348 func (*messageWriter) Close() {}