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