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 })