github.com/ablease/cli@v6.37.1-0.20180613014814-3adbb7d7fb19+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  }