github.com/elopio/cli@v6.21.2-0.20160902224010-ea909d1fdb2f+incompatible/cf/api/logs/noaa_logs_repository_test.go (about)

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