github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/api/logs/noaa_logs_repository_test.go (about) 1 package logs_test 2 3 import ( 4 "errors" 5 "time" 6 7 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 8 noaaerrors "github.com/cloudfoundry/noaa/errors" 9 "github.com/cloudfoundry/sonde-go/events" 10 "github.com/gogo/protobuf/proto" 11 12 "code.cloudfoundry.org/cli/cf/api/authentication/authenticationfakes" 13 testapi "code.cloudfoundry.org/cli/cf/api/logs/logsfakes" 14 testconfig "code.cloudfoundry.org/cli/cf/util/testhelpers/configuration" 15 16 "sync" 17 18 "code.cloudfoundry.org/cli/cf/api/logs" 19 . "github.com/onsi/ginkgo" 20 . "github.com/onsi/gomega" 21 ) 22 23 var _ = Describe("logs with noaa repository", func() { 24 var ( 25 fakeNoaaConsumer *testapi.FakeNoaaConsumer 26 config coreconfig.ReadWriter 27 fakeTokenRefresher *authenticationfakes.FakeRepository 28 retryTimeout time.Duration 29 repo *logs.NoaaLogsRepository 30 ) 31 32 BeforeEach(func() { 33 fakeNoaaConsumer = &testapi.FakeNoaaConsumer{} 34 config = testconfig.NewRepositoryWithDefaults() 35 config.SetDopplerEndpoint("doppler.test.com") 36 config.SetAccessToken("the-access-token") 37 fakeTokenRefresher = &authenticationfakes.FakeRepository{} 38 retryTimeout = time.Second + 500*time.Millisecond 39 repo = logs.NewNoaaLogsRepository(config, fakeNoaaConsumer, fakeTokenRefresher, retryTimeout) 40 }) 41 42 Describe("Authentication Token Refresh", func() { 43 It("sets the noaa token refresher", func() { 44 Expect(fakeNoaaConsumer.RefreshTokenFromCallCount()).To(Equal(1)) 45 Expect(fakeNoaaConsumer.RefreshTokenFromArgsForCall(0)).To(Equal(fakeTokenRefresher)) 46 }) 47 }) 48 49 Describe("RecentLogsFor", func() { 50 Context("when an error does not occur", func() { 51 var msg1, msg2, msg3 *events.LogMessage 52 53 BeforeEach(func() { 54 msg1 = makeNoaaLogMessage("message 1", 1000) 55 msg2 = makeNoaaLogMessage("message 2", 2000) 56 msg3 = makeNoaaLogMessage("message 3", 3000) 57 58 fakeNoaaConsumer.RecentLogsReturns([]*events.LogMessage{ 59 msg3, 60 msg2, 61 msg1, 62 }, nil) 63 }) 64 65 It("gets the logs for the requested app", func() { 66 repo.RecentLogsFor("app-guid-1") 67 arg, _ := fakeNoaaConsumer.RecentLogsArgsForCall(0) 68 Expect(arg).To(Equal("app-guid-1")) 69 }) 70 71 It("returns the sorted log messages", func() { 72 messages, err := repo.RecentLogsFor("app-guid") 73 Expect(err).NotTo(HaveOccurred()) 74 75 Expect(messages).To(Equal([]logs.Loggable{ 76 logs.NewNoaaLogMessage(msg1), 77 logs.NewNoaaLogMessage(msg2), 78 logs.NewNoaaLogMessage(msg3), 79 })) 80 }) 81 }) 82 }) 83 84 Describe("TailLogsFor", func() { 85 var errChan chan error 86 var logChan chan logs.Loggable 87 88 AfterEach(func() { 89 Eventually(errChan).Should(BeClosed()) 90 Eventually(logChan).Should(BeClosed()) 91 }) 92 93 Context("when an error occurs", func() { 94 var ( 95 e chan error 96 c chan *events.LogMessage 97 closeWg *sync.WaitGroup 98 ) 99 100 BeforeEach(func() { 101 closeWg = new(sync.WaitGroup) 102 errChan = make(chan error) 103 logChan = make(chan logs.Loggable) 104 105 e = make(chan error, 1) 106 c = make(chan *events.LogMessage) 107 108 closeWg.Add(1) 109 fakeNoaaConsumer.CloseStub = func() error { 110 defer closeWg.Done() 111 close(e) 112 close(c) 113 return nil 114 } 115 }) 116 117 AfterEach(func() { 118 closeWg.Wait() 119 }) 120 121 It("returns an error when it occurs", func(done Done) { 122 defer repo.Close() 123 err := errors.New("oops") 124 125 fakeNoaaConsumer.TailingLogsStub = func(appGuid string, authToken string) (<-chan *events.LogMessage, <-chan error) { 126 e <- err 127 return c, e 128 } 129 130 var wg sync.WaitGroup 131 wg.Add(1) 132 defer wg.Wait() 133 go func() { 134 defer wg.Done() 135 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 136 }() 137 138 Eventually(errChan).Should(Receive(&err)) 139 140 close(done) 141 }) 142 143 It("does not return a RetryError before RetryTimeout", func(done Done) { 144 defer repo.Close() 145 err := noaaerrors.NewRetryError(errors.New("oops")) 146 147 fakeNoaaConsumer.TailingLogsStub = func(appGuid string, authToken string) (<-chan *events.LogMessage, <-chan error) { 148 e <- err 149 return c, e 150 } 151 152 var wg sync.WaitGroup 153 wg.Add(1) 154 defer wg.Wait() 155 go func() { 156 defer wg.Done() 157 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 158 }() 159 160 Consistently(errChan).ShouldNot(Receive()) 161 close(done) 162 }) 163 164 It("returns a RetryError if no data is received before RetryTimeout", func() { 165 defer repo.Close() 166 err := noaaerrors.NewRetryError(errors.New("oops")) 167 168 fakeNoaaConsumer.TailingLogsStub = func(appGuid string, authToken string) (<-chan *events.LogMessage, <-chan error) { 169 e <- err 170 return c, e 171 } 172 173 var wg sync.WaitGroup 174 wg.Add(1) 175 defer wg.Wait() 176 go func() { 177 defer wg.Done() 178 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 179 }() 180 181 Consistently(errChan, time.Second).ShouldNot(Receive()) 182 expectedErr := errors.New("Timed out waiting for connection to Loggregator (doppler.test.com).") 183 Eventually(errChan, time.Second).Should(Receive(Equal(expectedErr))) 184 }) 185 186 It("Resets the retry timeout after a successful reconnection", func() { 187 defer repo.Close() 188 err := noaaerrors.NewRetryError(errors.New("oops")) 189 190 fakeNoaaConsumer.TailingLogsStub = func(appGuid string, authToken string) (<-chan *events.LogMessage, <-chan error) { 191 e <- err 192 return c, e 193 } 194 195 var wg sync.WaitGroup 196 wg.Add(1) 197 defer wg.Wait() 198 go func() { 199 defer wg.Done() 200 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 201 }() 202 203 Consistently(errChan, time.Second).ShouldNot(Receive()) 204 fakeNoaaConsumer.SetOnConnectCallbackArgsForCall(0)() 205 206 c <- makeNoaaLogMessage("foo", 100) 207 Eventually(logChan).Should(Receive()) 208 Consistently(errChan, time.Second).ShouldNot(Receive()) 209 210 e <- err 211 expectedErr := errors.New("Timed out waiting for connection to Loggregator (doppler.test.com).") 212 Eventually(errChan, 2*time.Second).Should(Receive(Equal(expectedErr))) 213 }) 214 }) 215 216 Context("when no error occurs", func() { 217 var e chan error 218 var c chan *events.LogMessage 219 220 BeforeEach(func() { 221 errChan = make(chan error) 222 logChan = make(chan logs.Loggable) 223 224 e = make(chan error) 225 c = make(chan *events.LogMessage) 226 227 fakeNoaaConsumer.CloseStub = func() error { 228 close(e) 229 close(c) 230 return nil 231 } 232 }) 233 234 It("asks for the logs for the given app", func(done Done) { 235 defer repo.Close() 236 237 fakeNoaaConsumer.TailingLogsReturns(c, e) 238 239 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 240 241 Eventually(fakeNoaaConsumer.TailingLogsCallCount).Should(Equal(1)) 242 appGuid, token := fakeNoaaConsumer.TailingLogsArgsForCall(0) 243 Expect(appGuid).To(Equal("app-guid")) 244 Expect(token).To(Equal("the-access-token")) 245 246 close(done) 247 }, 2) 248 249 It("sets the on connect callback", func() { 250 defer repo.Close() 251 252 fakeNoaaConsumer.TailingLogsReturns(c, e) 253 254 callbackCalled := make(chan struct{}) 255 var cb = func() { 256 close(callbackCalled) 257 return 258 } 259 repo.TailLogsFor("app-guid", cb, logChan, errChan) 260 261 Expect(fakeNoaaConsumer.SetOnConnectCallbackCallCount()).To(Equal(1)) 262 arg := fakeNoaaConsumer.SetOnConnectCallbackArgsForCall(0) 263 arg() 264 Expect(callbackCalled).To(BeClosed()) 265 }) 266 }) 267 268 Context("and the buffer time is sufficient for sorting", func() { 269 var msg1, msg2, msg3 *events.LogMessage 270 var ec chan error 271 var lc chan *events.LogMessage 272 var syncMu sync.Mutex 273 274 BeforeEach(func() { 275 msg1 = makeNoaaLogMessage("hello1", 100) 276 msg2 = makeNoaaLogMessage("hello2", 200) 277 msg3 = makeNoaaLogMessage("hello3", 300) 278 279 errChan = make(chan error) 280 logChan = make(chan logs.Loggable) 281 ec = make(chan error) 282 283 syncMu.Lock() 284 lc = make(chan *events.LogMessage) 285 syncMu.Unlock() 286 287 fakeNoaaConsumer.TailingLogsStub = func(string, string) (<-chan *events.LogMessage, <-chan error) { 288 go func() { 289 syncMu.Lock() 290 lc <- msg3 291 lc <- msg2 292 lc <- msg1 293 syncMu.Unlock() 294 }() 295 296 return lc, ec 297 } 298 }) 299 300 JustBeforeEach(func() { 301 repo = logs.NewNoaaLogsRepository(config, fakeNoaaConsumer, fakeTokenRefresher, retryTimeout) 302 303 fakeNoaaConsumer.CloseStub = func() error { 304 syncMu.Lock() 305 close(lc) 306 syncMu.Unlock() 307 close(ec) 308 309 return nil 310 } 311 }) 312 313 Context("when the channels are closed before reading", func() { 314 It("sorts the messages before yielding them", func(done Done) { 315 receivedMessages := []logs.Loggable{} 316 317 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 318 Consistently(errChan).ShouldNot(Receive()) 319 320 m := <-logChan 321 receivedMessages = append(receivedMessages, m) 322 m = <-logChan 323 receivedMessages = append(receivedMessages, m) 324 m = <-logChan 325 receivedMessages = append(receivedMessages, m) 326 repo.Close() 327 328 Expect(receivedMessages).To(Equal([]logs.Loggable{ 329 logs.NewNoaaLogMessage(msg1), 330 logs.NewNoaaLogMessage(msg2), 331 logs.NewNoaaLogMessage(msg3), 332 })) 333 close(done) 334 }) 335 }) 336 337 Context("when the channels are read while being written to", func() { 338 It("sorts the messages before yielding them", func(done Done) { 339 receivedMessages := []logs.Loggable{} 340 341 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 342 Consistently(errChan).ShouldNot(Receive()) 343 344 m := <-logChan 345 receivedMessages = append(receivedMessages, m) 346 m = <-logChan 347 receivedMessages = append(receivedMessages, m) 348 m = <-logChan 349 receivedMessages = append(receivedMessages, m) 350 351 repo.Close() 352 353 Expect(receivedMessages).To(Equal([]logs.Loggable{ 354 logs.NewNoaaLogMessage(msg1), 355 logs.NewNoaaLogMessage(msg2), 356 logs.NewNoaaLogMessage(msg3), 357 })) 358 359 close(done) 360 }) 361 362 It("flushes remaining log messages when Close is called", func() { 363 repo.BufferTime = 10 * time.Second 364 365 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 366 Consistently(errChan).ShouldNot(Receive()) 367 Consistently(logChan).ShouldNot(Receive()) 368 369 repo.Close() 370 371 Eventually(logChan).Should(Receive(Equal(logs.NewNoaaLogMessage(msg1)))) 372 Eventually(logChan).Should(Receive(Equal(logs.NewNoaaLogMessage(msg2)))) 373 Eventually(logChan).Should(Receive(Equal(logs.NewNoaaLogMessage(msg3)))) 374 }) 375 }) 376 }) 377 }) 378 }) 379 380 func makeNoaaLogMessage(message string, timestamp int64) *events.LogMessage { 381 messageType := events.LogMessage_OUT 382 sourceName := "DEA" 383 return &events.LogMessage{ 384 Message: []byte(message), 385 AppId: proto.String("app-guid"), 386 MessageType: &messageType, 387 SourceType: &sourceName, 388 Timestamp: proto.Int64(timestamp), 389 } 390 }