github.com/LukasHeimann/cloudfoundrycli/v8@v8.4.4/integration/shared/isolated/logs_command_test.go (about)

     1  package isolated
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"net/http"
     7  	"runtime"
     8  	"strconv"
     9  	"time"
    10  
    11  	"github.com/LukasHeimann/cloudfoundrycli/v8/integration/helpers"
    12  	. "github.com/onsi/ginkgo"
    13  	. "github.com/onsi/gomega"
    14  	. "github.com/onsi/gomega/gbytes"
    15  	. "github.com/onsi/gomega/gexec"
    16  	"github.com/onsi/gomega/ghttp"
    17  )
    18  
    19  var _ = Describe("logs command", func() {
    20  	var server *ghttp.Server
    21  
    22  	BeforeEach(func() {
    23  		server = helpers.StartAndTargetMockServerWithAPIVersions(helpers.DefaultV2Version, helpers.DefaultV3Version)
    24  		helpers.AddLoginRoutes(server)
    25  
    26  		helpers.AddHandler(server,
    27  			http.MethodGet,
    28  			"/v3/organizations?order_by=name",
    29  			http.StatusOK,
    30  			[]byte(
    31  				`{
    32  				 "total_results": 1,
    33  				 "total_pages": 1,
    34  				 "resources": [
    35  					 {
    36  						 "guid": "f3ea75ba-ea6b-439f-8889-b07abf718e6a",
    37  						 "name": "some-fake-org"
    38  					 }
    39  				 ]}`),
    40  		)
    41  
    42  		// The v6 version of this command makes the below request when logging in.
    43  		// See below for comparison with v7 version.
    44  		helpers.AddHandler(server,
    45  			http.MethodGet,
    46  			"/v3/spaces?organization_guids=f3ea75ba-ea6b-439f-8889-b07abf718e6a",
    47  			http.StatusOK,
    48  			[]byte(
    49  				`{
    50  					 "total_results": 1,
    51  					 "total_pages": 1,
    52  					 "resources": [
    53  						 {
    54  							 "guid": "1704b4e7-14bb-4b7b-bc23-0b8d23a60238",
    55  							 "name": "some-fake-space"
    56  						 }
    57  					 ]}`),
    58  		)
    59  
    60  		// The v7 version of this command makes the below request when logging in,
    61  		// which is similar to the v6 version above except for the additional 'order_by'
    62  		// query parameter. Rather than split these tests across two files, we just add
    63  		// a handler for both routes (with and without 'order_by').
    64  		helpers.AddHandler(server,
    65  			http.MethodGet,
    66  			"/v3/spaces?order_by=name&organization_guids=f3ea75ba-ea6b-439f-8889-b07abf718e6a",
    67  			http.StatusOK,
    68  			[]byte(
    69  				`{
    70  					 "total_results": 1,
    71  					 "total_pages": 1,
    72  					 "resources": [
    73  						 {
    74  							 "guid": "1704b4e7-14bb-4b7b-bc23-0b8d23a60238",
    75  							 "name": "some-fake-space"
    76  						 }
    77  					 ]}`),
    78  		)
    79  
    80  		helpers.AddHandler(server,
    81  			http.MethodGet,
    82  			"/v2/apps?q=name%3Asome-fake-app&q=space_guid%3A1704b4e7-14bb-4b7b-bc23-0b8d23a60238",
    83  			http.StatusOK,
    84  			[]byte(
    85  				`{
    86  					 "total_results": 1,
    87  					 "total_pages": 1,
    88  					 "resources": [
    89  						 {
    90  							 "metadata": {
    91  									"guid": "d5d27772-315f-474b-8673-57e34ce2db2c"
    92  							 },
    93  							 "entity": {
    94  									"name": "some-fake-app"
    95  							 }
    96  						 }
    97  					 ]}`),
    98  		)
    99  
   100  		helpers.AddHandler(server,
   101  			http.MethodGet,
   102  			"/v3/apps?names=some-fake-app&space_guids=1704b4e7-14bb-4b7b-bc23-0b8d23a60238",
   103  			http.StatusOK,
   104  			[]byte(
   105  				`{
   106  					 "total_results": 1,
   107  					 "total_pages": 1,
   108  					 "resources": [
   109  						 {
   110  							 "guid": "d5d27772-315f-474b-8673-57e34ce2db2c",
   111  							 "name": "some-fake-app"
   112  						 }
   113  					 ]}`),
   114  		)
   115  
   116  		helpers.AddHandler(server,
   117  			http.MethodGet,
   118  			"/api/v1/info",
   119  			http.StatusOK,
   120  			[]byte(`{"version":"2.6.8"}`),
   121  		)
   122  	})
   123  
   124  	AfterEach(func() {
   125  		server.Close()
   126  	})
   127  
   128  	Describe("streaming logs", func() {
   129  
   130  		const logMessage = "hello from log-cache"
   131  		var returnEmptyEnvelope bool
   132  
   133  		onWindows := runtime.GOOS == "windows"
   134  
   135  		BeforeEach(func() {
   136  			latestEnvelopeTimestamp := "1581447006352020890"
   137  			latestEnvelopeTimestampMinusOneSecond := "1581447005352020890"
   138  			nextEnvelopeTimestamp := "1581447009352020890"
   139  			nextEnvelopeTimestampPlusOneNanosecond := "1581447009352020891"
   140  
   141  			server.RouteToHandler(
   142  				http.MethodGet,
   143  				"/api/v1/read/d5d27772-315f-474b-8673-57e34ce2db2c",
   144  				func(w http.ResponseWriter, r *http.Request) {
   145  					w.WriteHeader(http.StatusOK)
   146  					switch r.URL.RawQuery {
   147  					case fmt.Sprintf("descending=true&limit=1&start_time=%s", strconv.FormatInt(time.Time{}.UnixNano(), 10)):
   148  						if returnEmptyEnvelope {
   149  							_, err := w.Write([]byte(`{}`))
   150  							Expect(err).ToNot(HaveOccurred())
   151  							returnEmptyEnvelope = false // Allow the CLI to continue after receiving an empty envelope
   152  						} else {
   153  							_, err := w.Write([]byte(fmt.Sprintf(`
   154  						{
   155  							"envelopes": {
   156  								"batch": [
   157  									{
   158  										"timestamp": "%s",
   159  										"source_id": "d5d27772-315f-474b-8673-57e34ce2db2c"
   160  									}
   161  								]
   162  							}
   163  						}`, latestEnvelopeTimestamp)))
   164  							Expect(err).ToNot(HaveOccurred())
   165  						}
   166  					case fmt.Sprintf("envelope_types=LOG&start_time=%s", latestEnvelopeTimestampMinusOneSecond):
   167  						_, err := w.Write([]byte(fmt.Sprintf(`{
   168  							"envelopes": {
   169  								"batch": [
   170  									{
   171  										"timestamp": "%s",
   172  										"source_id": "d5d27772-315f-474b-8673-57e34ce2db2c",
   173  										"tags": {
   174  											"__v1_type": "LogMessage"
   175  										},
   176  										"log": {
   177  											"payload": "%s",
   178  											"type": "OUT"
   179  										}
   180  									}
   181  								]
   182  							}
   183  							}`, nextEnvelopeTimestamp, base64.StdEncoding.EncodeToString([]byte(logMessage)))))
   184  						Expect(err).ToNot(HaveOccurred())
   185  					case fmt.Sprintf("envelope_types=LOG&start_time=%s", nextEnvelopeTimestampPlusOneNanosecond):
   186  						_, err := w.Write([]byte("{}"))
   187  						Expect(err).ToNot(HaveOccurred())
   188  					default:
   189  						Fail(fmt.Sprintf("Unhandled log-cache api query string: %s", r.URL.RawQuery))
   190  					}
   191  				})
   192  		})
   193  
   194  		When("there already is an envelope in the log cache", func() {
   195  			JustBeforeEach(func() {
   196  				returnEmptyEnvelope = false
   197  			})
   198  
   199  			It("fetches logs with a timestamp just prior to the latest log envelope", func() {
   200  				username, password := helpers.GetCredentials()
   201  				session := helpers.CF("login", "-a", server.URL(), "-u", username, "-p", password, "--skip-ssl-validation")
   202  				Eventually(session).Should(Exit(0))
   203  
   204  				session = helpers.CF("logs", "some-fake-app")
   205  				Eventually(session).Should(Say(logMessage))
   206  				if onWindows {
   207  					session.Kill()
   208  					Eventually(session).Should(Exit())
   209  				} else {
   210  					session.Interrupt()
   211  					Eventually(session).Should(Exit(0), "Interrupt should be handled and fail gracefully")
   212  				}
   213  			})
   214  		})
   215  
   216  		When("there is not yet an envelope in the log cache", func() {
   217  			JustBeforeEach(func() {
   218  				returnEmptyEnvelope = true
   219  			})
   220  
   221  			// TODO: the case where log-cache has no envelopes yet may be "special": we may want to switch to "start from your oldest envelope" approach.
   222  			It("retries until there is an initial envelope, and then fetches logs with a timestamp just prior to the latest log envelope", func() {
   223  				username, password := helpers.GetCredentials()
   224  				session := helpers.CF("login", "-a", server.URL(), "-u", username, "-p", password, "--skip-ssl-validation")
   225  				Eventually(session).Should(Exit(0))
   226  
   227  				session = helpers.CF("logs", "some-fake-app")
   228  				Eventually(session).Should(Say(logMessage))
   229  				if onWindows {
   230  					session.Kill()
   231  					Eventually(session).Should(Exit())
   232  				} else {
   233  					session.Interrupt()
   234  					Eventually(session).Should(Exit(0))
   235  				}
   236  			})
   237  		})
   238  	})
   239  })