github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/actor/sharedaction/logging_test.go (about)

     1  package sharedaction_test
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/url"
     8  	"time"
     9  
    10  	"code.cloudfoundry.org/cli/actor/sharedaction"
    11  	"code.cloudfoundry.org/cli/actor/sharedaction/sharedactionfakes"
    12  	logcache "code.cloudfoundry.org/go-log-cache/v2"
    13  	"code.cloudfoundry.org/go-loggregator/v9/rpc/loggregator_v2"
    14  	. "github.com/onsi/ginkgo"
    15  	. "github.com/onsi/gomega"
    16  )
    17  
    18  var _ = Describe("Logging Actions", func() {
    19  	var (
    20  		fakeLogCacheClient *sharedactionfakes.FakeLogCacheClient
    21  	)
    22  
    23  	BeforeEach(func() {
    24  		fakeLogCacheClient = new(sharedactionfakes.FakeLogCacheClient)
    25  	})
    26  
    27  	Describe("LogMessage", func() {
    28  		Describe("Staging", func() {
    29  			When("the log is a staging log", func() {
    30  				It("returns true", func() {
    31  					message := *sharedaction.NewLogMessage(
    32  						"some-message",
    33  						"OUT",
    34  						time.Unix(0, 0),
    35  						"STG",
    36  						"some-source-instance",
    37  					)
    38  
    39  					Expect(message.Staging()).To(BeTrue())
    40  				})
    41  			})
    42  
    43  			When("the log is any other kind of log", func() {
    44  				It("returns false", func() {
    45  					message := *sharedaction.NewLogMessage(
    46  						"some-message",
    47  						"OUT",
    48  						time.Unix(0, 0),
    49  						"APP",
    50  						"some-source-instance",
    51  					)
    52  					Expect(message.Staging()).To(BeFalse())
    53  				})
    54  			})
    55  		})
    56  	})
    57  
    58  	Describe("GetStreamingLogs", func() {
    59  		var (
    60  			expectedAppGUID string
    61  
    62  			messages              <-chan sharedaction.LogMessage
    63  			errs                  <-chan error
    64  			stopStreaming         context.CancelFunc
    65  			mostRecentTime        time.Time
    66  			mostRecentEnvelope    loggregator_v2.Envelope
    67  			slightlyOlderEnvelope loggregator_v2.Envelope
    68  		)
    69  
    70  		BeforeEach(func() {
    71  			expectedAppGUID = "some-app-guid"
    72  			// 2 seconds in the past to get past Walk delay
    73  			// Walk delay context: https://github.com/cloudfoundry/cli/blob/283d5fcdefa1806b24f4242adea1fb85871b4c6b/vendor/code.cloudfoundry.org/go-log-cache/walk.go#L74
    74  			mostRecentTime = time.Now().Add(-2 * time.Second)
    75  			mostRecentTimestamp := mostRecentTime.UnixNano()
    76  			slightlyOlderTimestamp := mostRecentTime.Add(-500 * time.Millisecond).UnixNano()
    77  
    78  			mostRecentEnvelope = loggregator_v2.Envelope{
    79  				Timestamp:  mostRecentTimestamp,
    80  				SourceId:   "some-app-guid",
    81  				InstanceId: "some-source-instance",
    82  				Message: &loggregator_v2.Envelope_Log{
    83  					Log: &loggregator_v2.Log{
    84  						Payload: []byte("message-2"),
    85  						Type:    loggregator_v2.Log_OUT,
    86  					},
    87  				},
    88  				Tags: map[string]string{
    89  					"source_type": "some-source-type",
    90  				},
    91  			}
    92  
    93  			slightlyOlderEnvelope = loggregator_v2.Envelope{
    94  				Timestamp:  slightlyOlderTimestamp,
    95  				SourceId:   "some-app-guid",
    96  				InstanceId: "some-source-instance",
    97  				Message: &loggregator_v2.Envelope_Log{
    98  					Log: &loggregator_v2.Log{
    99  						Payload: []byte("message-1"),
   100  						Type:    loggregator_v2.Log_OUT,
   101  					},
   102  				},
   103  				Tags: map[string]string{
   104  					"source_type": "some-source-type",
   105  				},
   106  			}
   107  		})
   108  
   109  		AfterEach(func() {
   110  			Eventually(messages).Should(BeClosed())
   111  			Eventually(errs).Should(BeClosed())
   112  		})
   113  
   114  		JustBeforeEach(func() {
   115  			messages, errs, stopStreaming = sharedaction.GetStreamingLogs(expectedAppGUID, fakeLogCacheClient)
   116  		})
   117  
   118  		When("receiving logs", func() {
   119  			var walkStartTime time.Time
   120  
   121  			BeforeEach(func() {
   122  				fakeLogCacheClient.ReadStub = func(
   123  					ctx context.Context,
   124  					sourceID string,
   125  					start time.Time,
   126  					opts ...logcache.ReadOption,
   127  				) ([]*loggregator_v2.Envelope, error) {
   128  					if fakeLogCacheClient.ReadCallCount() > 2 {
   129  						stopStreaming()
   130  						return []*loggregator_v2.Envelope{}, ctx.Err()
   131  					}
   132  
   133  					if start.IsZero() {
   134  						return []*loggregator_v2.Envelope{&mostRecentEnvelope}, ctx.Err()
   135  					}
   136  
   137  					walkStartTime = start
   138  					return []*loggregator_v2.Envelope{&slightlyOlderEnvelope, &mostRecentEnvelope}, ctx.Err()
   139  				}
   140  			})
   141  
   142  			It("it starts walking at 1 second previous to the mostRecentEnvelope's time", func() {
   143  				Eventually(messages).Should(BeClosed())
   144  				Expect(walkStartTime).To(BeTemporally("~", mostRecentTime.Add(-1*time.Second), time.Millisecond))
   145  			})
   146  
   147  			It("converts them to log messages and passes them through the messages channel", func() {
   148  				Eventually(messages).Should(HaveLen(2))
   149  				var message sharedaction.LogMessage
   150  				Expect(messages).To(Receive(&message))
   151  				Expect(message.Message()).To(Equal("message-1"))
   152  				Expect(messages).To(Receive(&message))
   153  				Expect(message.Message()).To(Equal("message-2"))
   154  
   155  				Expect(errs).ToNot(Receive())
   156  			})
   157  		})
   158  
   159  		When("cancelling log streaming", func() {
   160  			BeforeEach(func() {
   161  				fakeLogCacheClient.ReadStub = func(
   162  					ctx context.Context,
   163  					sourceID string,
   164  					start time.Time,
   165  					opts ...logcache.ReadOption,
   166  				) ([]*loggregator_v2.Envelope, error) {
   167  					return []*loggregator_v2.Envelope{}, ctx.Err()
   168  				}
   169  			})
   170  
   171  			It("can be called multiple times", func() {
   172  				Expect(stopStreaming).ToNot(Panic())
   173  				Expect(stopStreaming).ToNot(Panic())
   174  			})
   175  		})
   176  
   177  		Describe("error handling", func() {
   178  			When("there is an error 'peeking' at log-cache to determine the latest log", func() {
   179  				BeforeEach(func() {
   180  					fakeLogCacheClient.ReadStub = func(
   181  						ctx context.Context,
   182  						sourceID string,
   183  						start time.Time,
   184  						opts ...logcache.ReadOption,
   185  					) ([]*loggregator_v2.Envelope, error) {
   186  						return nil, fmt.Errorf("error number %d", fakeLogCacheClient.ReadCallCount())
   187  					}
   188  				})
   189  
   190  				AfterEach(func() {
   191  					stopStreaming()
   192  				})
   193  
   194  				It("passes 5 errors through the errors channel", func() {
   195  					Eventually(errs, 2*time.Second).Should(HaveLen(5))
   196  					Eventually(errs).Should(Receive(MatchError("error number 1")))
   197  					Eventually(errs).Should(Receive(MatchError("error number 2")))
   198  					Eventually(errs).Should(Receive(MatchError("error number 3")))
   199  					Eventually(errs).Should(Receive(MatchError("error number 4")))
   200  					Eventually(errs).Should(Receive(MatchError("error number 5")))
   201  					Consistently(errs).ShouldNot(Receive())
   202  				})
   203  
   204  				It("tries exactly 5 times", func() {
   205  					Eventually(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(5))
   206  					Consistently(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(5))
   207  				})
   208  			})
   209  
   210  			When("there is an error walking log-cache to retrieve logs", func() {
   211  				BeforeEach(func() {
   212  					fakeLogCacheClient.ReadStub = func(
   213  						ctx context.Context,
   214  						sourceID string,
   215  						start time.Time,
   216  						opts ...logcache.ReadOption,
   217  					) ([]*loggregator_v2.Envelope, error) {
   218  						if start.IsZero() {
   219  							return []*loggregator_v2.Envelope{&mostRecentEnvelope}, ctx.Err()
   220  						}
   221  						return nil, fmt.Errorf("error number %d", fakeLogCacheClient.ReadCallCount()-1)
   222  					}
   223  				})
   224  
   225  				AfterEach(func() {
   226  					stopStreaming()
   227  				})
   228  
   229  				It("passes 5 errors through the errors channel", func() {
   230  					Eventually(errs, 2*time.Second).Should(HaveLen(5))
   231  					Eventually(errs).Should(Receive(MatchError("error number 1")))
   232  					Eventually(errs).Should(Receive(MatchError("error number 2")))
   233  					Eventually(errs).Should(Receive(MatchError("error number 3")))
   234  					Eventually(errs).Should(Receive(MatchError("error number 4")))
   235  					Eventually(errs).Should(Receive(MatchError("error number 5")))
   236  					Consistently(errs).ShouldNot(Receive())
   237  				})
   238  
   239  				It("tries exactly 5 times", func() {
   240  					initialPeekingRead := 1
   241  					walkRetries := 5
   242  					expectedReadCallCount := initialPeekingRead + walkRetries
   243  
   244  					Eventually(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(expectedReadCallCount))
   245  					Consistently(fakeLogCacheClient.ReadCallCount, 2*time.Second).Should(Equal(expectedReadCallCount))
   246  				})
   247  			})
   248  		})
   249  	})
   250  
   251  	Describe("GetRecentLogs", func() {
   252  		When("the application can be found", func() {
   253  			When("Log Cache returns logs", func() {
   254  				BeforeEach(func() {
   255  					messages := []*loggregator_v2.Envelope{
   256  						{
   257  							Timestamp:  int64(20),
   258  							SourceId:   "some-app-guid",
   259  							InstanceId: "some-source-instance",
   260  							Message: &loggregator_v2.Envelope_Log{
   261  								Log: &loggregator_v2.Log{
   262  									Payload: []byte("message-2"),
   263  									Type:    loggregator_v2.Log_OUT,
   264  								},
   265  							},
   266  							Tags: map[string]string{
   267  								"source_type": "some-source-type",
   268  							},
   269  						},
   270  						{
   271  							Timestamp:  int64(10),
   272  							SourceId:   "some-app-guid",
   273  							InstanceId: "some-source-instance",
   274  							Message: &loggregator_v2.Envelope_Log{
   275  								Log: &loggregator_v2.Log{
   276  									Payload: []byte("message-1"),
   277  									Type:    loggregator_v2.Log_OUT,
   278  								},
   279  							},
   280  							Tags: map[string]string{
   281  								"source_type": "some-source-type",
   282  							},
   283  						},
   284  					}
   285  
   286  					fakeLogCacheClient.ReadReturns(messages, nil)
   287  				})
   288  
   289  				It("returns all the recent logs and warnings", func() {
   290  					messages, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
   291  					Expect(err).ToNot(HaveOccurred())
   292  
   293  					Expect(messages[0].Message()).To(Equal("message-1"))
   294  					Expect(messages[0].Type()).To(Equal("OUT"))
   295  					Expect(messages[0].Timestamp()).To(Equal(time.Unix(0, 10)))
   296  					Expect(messages[0].SourceType()).To(Equal("some-source-type"))
   297  					Expect(messages[0].SourceInstance()).To(Equal("some-source-instance"))
   298  
   299  					Expect(messages[1].Message()).To(Equal("message-2"))
   300  					Expect(messages[1].Type()).To(Equal("OUT"))
   301  					Expect(messages[1].Timestamp()).To(Equal(time.Unix(0, 20)))
   302  					Expect(messages[1].SourceType()).To(Equal("some-source-type"))
   303  					Expect(messages[1].SourceInstance()).To(Equal("some-source-instance"))
   304  				})
   305  			})
   306  
   307  			When("Log Cache returns non-log envelopes", func() {
   308  				BeforeEach(func() {
   309  					messages := []*loggregator_v2.Envelope{
   310  						{
   311  							Timestamp:  int64(10),
   312  							SourceId:   "some-app-guid",
   313  							InstanceId: "some-source-instance",
   314  							Message:    &loggregator_v2.Envelope_Counter{},
   315  							Tags: map[string]string{
   316  								"source_type": "some-source-type",
   317  							},
   318  						},
   319  					}
   320  
   321  					fakeLogCacheClient.ReadReturns(messages, nil)
   322  				})
   323  
   324  				It("ignores them", func() {
   325  					messages, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
   326  					Expect(err).ToNot(HaveOccurred())
   327  					Expect(messages).To(BeEmpty())
   328  				})
   329  			})
   330  
   331  			When("Log Cache errors", func() {
   332  				BeforeEach(func() {
   333  					fakeLogCacheClient.ReadReturns(nil, errors.New("some-recent-logs-error"))
   334  				})
   335  
   336  				It("returns error and warnings", func() {
   337  					_, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
   338  					Expect(err).To(MatchError("Failed to retrieve logs from Log Cache: some-recent-logs-error"))
   339  				})
   340  			})
   341  
   342  			When("Log Cache returns a resource-exhausted error from grpc", func() {
   343  				resourceExhaustedErr := errors.New("unexpected status code 429")
   344  				u := new(url.URL)
   345  				v := make(url.Values)
   346  
   347  				BeforeEach(func() {
   348  					fakeLogCacheClient.ReadReturns([]*loggregator_v2.Envelope{}, resourceExhaustedErr)
   349  				})
   350  
   351  				It("attempts to halve numbber of requested logs, and eventually returns error and warnings", func() {
   352  					_, err := sharedaction.GetRecentLogs("some-app-guid", fakeLogCacheClient)
   353  
   354  					Expect(err).To(MatchError("Failed to retrieve logs from Log Cache: unexpected status code 429"))
   355  					Expect(fakeLogCacheClient.ReadCallCount()).To(Equal(10))
   356  
   357  					_, _, _, readOptions := fakeLogCacheClient.ReadArgsForCall(0)
   358  					readOptions[1](u, v)
   359  					Expect(v.Get("limit")).To(Equal("1000"))
   360  
   361  					_, _, _, readOptions = fakeLogCacheClient.ReadArgsForCall(1)
   362  					readOptions[1](u, v)
   363  					Expect(v.Get("limit")).To(Equal("500"))
   364  				})
   365  			})
   366  		})
   367  	})
   368  
   369  })