From ab863390c381349b7589dadf8c686f2f8eeda236 Mon Sep 17 00:00:00 2001 From: Felisia Martini Date: Wed, 31 Mar 2021 12:32:34 +0100 Subject: [PATCH] Implement `X-Broker-API-Originating-Identity` (#148) * Add the request identity to the context. * Return X-Broker-API-Originating-Identity header on response when it is sent on request * Updated v7 folder Co-authored-by: Quentin Barrand --- README.md | 10 +- api.go | 1 + api_test.go | 527 +++++++++++++--------- handlers/api_handler.go | 5 +- handlers/bind.go | 23 +- handlers/catalog.go | 6 +- handlers/deprovision.go | 15 +- handlers/get_binding.go | 11 +- handlers/get_instance.go | 11 +- handlers/last_binding_operation.go | 12 +- handlers/last_operation.go | 10 +- handlers/provision.go | 23 +- handlers/unbind.go | 15 +- handlers/update.go | 13 +- middlewares/api_version_header.go | 8 + middlewares/request_identity_header.go | 16 + v7/api.go | 1 + v7/handlers/api_handler.go | 5 +- v7/handlers/bind.go | 23 +- v7/handlers/catalog.go | 6 +- v7/handlers/deprovision.go | 15 +- v7/handlers/get_binding.go | 11 +- v7/handlers/get_instance.go | 11 +- v7/handlers/last_binding_operation.go | 12 +- v7/handlers/last_operation.go | 10 +- v7/handlers/provision.go | 23 +- v7/handlers/unbind.go | 15 +- v7/handlers/update.go | 13 +- v7/middlewares/api_version_header.go | 8 + v7/middlewares/request_identity_header.go | 16 + 30 files changed, 541 insertions(+), 334 deletions(-) create mode 100644 middlewares/request_identity_header.go create mode 100644 v7/middlewares/request_identity_header.go diff --git a/README.md b/README.md index 428e68bb..9503c0af 100644 --- a/README.md +++ b/README.md @@ -75,10 +75,18 @@ func (sb *ServiceBrokerImplementation) Provision(ctx context.Context, The request context for every request contains the unparsed `X-Broker-API-Originating-Identity` header under the key -`originatingIdentityKey`. More details on how the Open Service Broker API +`originatingIdentity`. More details on how the Open Service Broker API manages request originating identity is available [here](https://github.com/openservicebrokerapi/servicebroker/blob/master/spec.md#originating-identity). +## Request Identity + +The request context for every request contains the unparsed +`X-Broker-API-Request-Identity` header under the key +`requestIdentity`. More details on how the Open Service Broker API +manages request originating identity is available +[here](https://github.com/openservicebrokerapi/servicebroker/blob/master/spec.md#request-identity). + ## Example Service Broker You can see the diff --git a/api.go b/api.go index f0573bc4..c4d19d4d 100644 --- a/api.go +++ b/api.go @@ -43,6 +43,7 @@ func New(serviceBroker ServiceBroker, logger lager.Logger, brokerCredentials Bro router.Use(middlewares.AddOriginatingIdentityToContext) router.Use(apiVersionMiddleware.ValidateAPIVersionHdr) router.Use(middlewares.AddInfoLocationToContext) + router.Use(middlewares.AddRequestIdentityToContext) return router } diff --git a/api_test.go b/api_test.go index 451a1d31..4326aa68 100644 --- a/api_test.go +++ b/api_test.go @@ -49,6 +49,8 @@ var _ = Describe("Service Broker API", func() { Password: "password", } + const requestIdentity = "Request Identity Name" + makeInstanceProvisioningRequest := func(instanceID string, details map[string]interface{}, queryString string) *testflight.Response { response := &testflight.Response{} @@ -60,11 +62,11 @@ var _ = Describe("Service Broker API", func() { request, err := http.NewRequest("PUT", path, buffer) Expect(err).NotTo(HaveOccurred()) request.Header.Add("Content-Type", "application/json") + request.Header.Add("X-Broker-API-Request-Identity", requestIdentity) if apiVersion != "" { request.Header.Add("X-Broker-API-Version", apiVersion) } request.SetBasicAuth(credentials.Username, credentials.Password) - response = r.Do(request) }) return response @@ -290,9 +292,9 @@ var _ = Describe("Service Broker API", func() { Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") ctx := fakeServiceBroker.ServicesArgsForCall(0) Expect(ctx.Value("originatingIdentity")).To(Equal(originatingIdentity)) - }) }) + When("X-Broker-API-Originating-Identity is not passed", func() { It("Adds empty originatingIdentity to the context", func() { _, err := http.DefaultClient.Do(req) @@ -305,7 +307,181 @@ var _ = Describe("Service Broker API", func() { }) }) + Describe("RequestIdentityHeader", func() { + var ( + fakeServiceBroker *fakes.AutoFakeServiceBroker + req *http.Request + testServer *httptest.Server + ) + + BeforeEach(func() { + fakeServiceBroker = new(fakes.AutoFakeServiceBroker) + brokerAPI = brokerapi.New(fakeServiceBroker, brokerLogger, credentials) + + testServer = httptest.NewServer(brokerAPI) + var err error + req, err = http.NewRequest("GET", testServer.URL+"/v2/catalog", nil) + Expect(err).NotTo(HaveOccurred()) + req.Header.Add("X-Broker-API-Version", "2.14") + req.SetBasicAuth(credentials.Username, credentials.Password) + }) + + AfterEach(func() { + testServer.Close() + }) + + When("X-Broker-API-Request-Identity is passed", func() { + It("adds it to the context and returns in response", func() { + const requestIdentity = "Request Identity Name" + req.Header.Add("X-Broker-API-Request-Identity", requestIdentity) + + response, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccurred()) + + Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") + ctx := fakeServiceBroker.ServicesArgsForCall(0) + Expect(ctx.Value("requestIdentity")).To(Equal(requestIdentity)) + + header := response.Header.Get("X-Broker-API-Request-Identity") + Expect(header).To(Equal(requestIdentity)) + }) + }) + + When("X-Broker-API-Request-Identity is not passed", func() { + It("adds empty requestIdentity to the context and does not return in response", func() { + response, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccurred()) + + Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") + ctx := fakeServiceBroker.ServicesArgsForCall(0) + Expect(ctx.Value("requestIdentity")).To(Equal("")) + + header := response.Header.Get("X-Broker-API-Request-Identity") + Expect(header).To(Equal("")) + }) + }) + }) + + Describe("InfoLocationHeader", func() { + + var ( + fakeServiceBroker *fakes.AutoFakeServiceBroker + req *http.Request + testServer *httptest.Server + ) + + BeforeEach(func() { + fakeServiceBroker = new(fakes.AutoFakeServiceBroker) + brokerAPI = brokerapi.New(fakeServiceBroker, brokerLogger, credentials) + + testServer = httptest.NewServer(brokerAPI) + var err error + req, err = http.NewRequest("GET", testServer.URL+"/v2/catalog", nil) + Expect(err).NotTo(HaveOccurred()) + req.Header.Add("X-Broker-API-Version", "2.14") + req.SetBasicAuth(credentials.Username, credentials.Password) + }) + + AfterEach(func() { + testServer.Close() + }) + + When("X-Api-Info-Location is passed", func() { + It("Adds it to the context", func() { + infoLocation := "API Info Location Value" + req.Header.Add("X-Api-Info-Location", infoLocation) + + _, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccurred()) + + Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") + ctx := fakeServiceBroker.ServicesArgsForCall(0) + Expect(ctx.Value("infoLocation")).To(Equal(infoLocation)) + + }) + }) + When("X-Api-Info-Location is not passed", func() { + It("Adds empty infoLocation to the context", func() { + _, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccurred()) + + Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") + ctx := fakeServiceBroker.ServicesArgsForCall(0) + Expect(ctx.Value("infoLocation")).To(Equal("")) + }) + }) + }) + + Describe("CorrelationIDHeader", func() { + const correlationID = "fake-correlation-id" + + type testCase struct { + correlationIDHeaderName string + } + + var ( + fakeServiceBroker *fakes.AutoFakeServiceBroker + req *http.Request + testServer *httptest.Server + ) + + BeforeEach(func() { + fakeServiceBroker = new(fakes.AutoFakeServiceBroker) + brokerAPI = brokerapi.New(fakeServiceBroker, brokerLogger, credentials) + + testServer = httptest.NewServer(brokerAPI) + var err error + req, err = http.NewRequest("GET", testServer.URL+"/v2/catalog", nil) + Expect(err).NotTo(HaveOccurred()) + req.Header.Add("X-Broker-API-Version", "2.14") + req.SetBasicAuth(credentials.Username, credentials.Password) + }) + + AfterEach(func() { + testServer.Close() + }) + + table.DescribeTable("Adds correlation id to the context", func(tc testCase) { + req.Header.Add(tc.correlationIDHeaderName, correlationID) + + _, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccurred()) + + Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") + ctx := fakeServiceBroker.ServicesArgsForCall(0) + Expect(ctx.Value(middlewares.CorrelationIDKey)).To(Equal(correlationID)) + }, + table.Entry("X-Correlation-ID", testCase{ + correlationIDHeaderName: "X-Correlation-ID", + }), + table.Entry("X-CorrelationID", testCase{ + correlationIDHeaderName: "X-CorrelationID", + }), + table.Entry("X-ForRequest-ID", testCase{ + correlationIDHeaderName: "X-ForRequest-ID", + }), + table.Entry("X-Request-ID", testCase{ + correlationIDHeaderName: "X-Request-ID", + }), + table.Entry("X-Vcap-Request-Id", testCase{ + correlationIDHeaderName: "X-Vcap-Request-Id", + }), + ) + + When("X-Correlation-ID is not passed", func() { + It("Generates correlation id and adds it to the context", func() { + _, err := http.DefaultClient.Do(req) + Expect(err).NotTo(HaveOccurred()) + + Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") + ctx := fakeServiceBroker.ServicesArgsForCall(0) + Expect(ctx.Value(middlewares.CorrelationIDKey)).To(Not(BeNil())) + }) + }) + }) + Describe("catalog endpoint", func() { + const requestIdentity = "Request Identity Name" makeCatalogRequest := func(apiVersion string, fail bool) *httptest.ResponseRecorder { recorder := httptest.NewRecorder() request, _ := http.NewRequest(http.MethodGet, "/v2/catalog", nil) @@ -313,6 +489,7 @@ var _ = Describe("Service Broker API", func() { request.Header.Add("X-Broker-API-Version", apiVersion) } request.SetBasicAuth(credentials.Username, credentials.Password) + request.Header.Add("X-Broker-API-Request-Identity", requestIdentity) ctx := context.Background() if fail { ctx = context.WithValue(ctx, "fails", true) @@ -324,31 +501,35 @@ var _ = Describe("Service Broker API", func() { It("returns a 200", func() { response := makeCatalogRequest("2.14", false) - Expect(response.Code).To(Equal(200)) - }) - It("returns valid catalog json", func() { - response := makeCatalogRequest("2.14", false) + Expect(response.Code).To(Equal(200)) + Expect(response.Header().Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(fixture("catalog.json"))) }) It("returns a 500", func() { response := makeCatalogRequest("2.14", true) + Expect(response.Code).To(Equal(500)) + Expect(response.Header().Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body.String()).To(MatchJSON(`{ "description": "something went wrong!" }`)) }) Context("the request is malformed", func() { It("missing header X-Broker-API-Version", func() { response := makeCatalogRequest("", false) + Expect(response.Code).To(Equal(412)) + Expect(response.Header().Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set")) }) It("has wrong version of API", func() { response := makeCatalogRequest("1.14", false) + Expect(response.Code).To(Equal(412)) + Expect(response.Header().Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x")) }) @@ -363,6 +544,7 @@ var _ = Describe("Service Broker API", func() { request, err := http.NewRequest("GET", path, strings.NewReader("")) Expect(err).NotTo(HaveOccurred()) request.Header.Add("X-Broker-API-Version", apiVersion) + request.Header.Add("X-Broker-API-Request-Identity", requestIdentity) request.SetBasicAuth("username", "password") response = r.Do(request) }) @@ -381,8 +563,8 @@ var _ = Describe("Service Broker API", func() { request.Header.Add("Content-Type", "application/json") request.SetBasicAuth("username", "password") request.Header.Add("X-Broker-API-Version", apiVersion) + request.Header.Add("X-Broker-API-Request-Identity", requestIdentity) response = r.Do(request) - }) return response } @@ -441,6 +623,7 @@ var _ = Describe("Service Broker API", func() { resp := makeGetInstanceRequest(instanceID) Expect(fakeServiceBroker.GetInstanceIDs).To(ContainElement(instanceID)) Expect(resp.Body).To(MatchJSON(fixture("get_instance.json"))) + Expect(resp.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) Context("when the broker returns some operation data", func() { @@ -518,14 +701,11 @@ var _ = Describe("Service Broker API", func() { }) Context("when the instance does not exist", func() { - It("returns a 201", func() { + It("returns a 201 with empty JSON", func() { response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.StatusCode).To(Equal(201)) - }) - - It("returns empty json", func() { - response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.Body).To(MatchJSON(fixture("provisioning.json"))) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) Context("when the broker returns a dashboard URL", func() { @@ -546,14 +726,12 @@ var _ = Describe("Service Broker API", func() { } }) - It("returns a 500", func() { + It("returns a 500 with error", func() { response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") - Expect(response.StatusCode).To(Equal(500)) - }) - It("returns json with a description field and a useful error message", func() { - response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") + Expect(response.StatusCode).To(Equal(500)) Expect(response.Body).To(MatchJSON(fixture("instance_limit_error.json"))) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) It("logs an appropriate error", func() { @@ -569,14 +747,11 @@ var _ = Describe("Service Broker API", func() { fakeServiceBroker.ProvisionError = errors.New("broker failed") }) - It("returns a 500", func() { + It("returns a 500 with error", func() { response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.StatusCode).To(Equal(500)) - }) - - It("returns json with a description field and a useful error message", func() { - response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.Body).To(MatchJSON(`{"description":"broker failed"}`)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) It("logs an appropriate error", func() { @@ -595,14 +770,11 @@ var _ = Describe("Service Broker API", func() { ) }) - It("returns status teapot", func() { + It("returns status teapot with error", func() { response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.StatusCode).To(Equal(http.StatusTeapot)) - }) - - It("returns json with a description field and a useful error message", func() { - response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.Body).To(MatchJSON(`{"description":"I failed in unique and interesting ways"}`)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) It("logs an appropriate error", func() { @@ -620,10 +792,7 @@ var _ = Describe("Service Broker API", func() { It("returns a 422", func() { response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.StatusCode).To(Equal(http.StatusUnprocessableEntity)) - }) - - It("returns json with a description field and a useful error message", func() { - response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(`{"description":"The format of the parameters is not valid JSON"}`)) }) @@ -645,11 +814,11 @@ var _ = Describe("Service Broker API", func() { request, err := http.NewRequest("PUT", path, body) Expect(err).NotTo(HaveOccurred()) request.Header.Add("Content-Type", "application/json") + request.Header.Add("X-Broker-API-Request-Identity", requestIdentity) if apiVersion != "" { request.Header.Add("X-Broker-Api-Version", apiVersion) } request.SetBasicAuth(credentials.Username, credentials.Password) - response = r.Do(request) }) @@ -659,6 +828,7 @@ var _ = Describe("Service Broker API", func() { It("returns a 422 bad request", func() { response := makeBadInstanceProvisioningRequest(instanceID) Expect(response.StatusCode).Should(Equal(http.StatusUnprocessableEntity)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) It("logs a message", func() { @@ -677,6 +847,7 @@ var _ = Describe("Service Broker API", func() { It("sync broker response", func() { response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.StatusCode).To(Equal(http.StatusOK)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) It("async broker response", func() { @@ -688,6 +859,7 @@ var _ = Describe("Service Broker API", func() { response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.StatusCode).To(Equal(http.StatusOK)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) }) @@ -700,6 +872,7 @@ var _ = Describe("Service Broker API", func() { }() response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") Expect(response.StatusCode).To(Equal(http.StatusConflict)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) It("returns an empty JSON object", func() { @@ -842,6 +1015,7 @@ var _ = Describe("Service Broker API", func() { acceptsIncomplete := false response := makeInstanceProvisioningRequestWithAcceptsIncomplete(instanceID, provisionDetails, acceptsIncomplete) Expect(response.StatusCode).To(Equal(http.StatusUnprocessableEntity)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(fixture("async_required.json"))) }) }) @@ -851,48 +1025,66 @@ var _ = Describe("Service Broker API", func() { Context("the request is malformed", func() { It("missing header X-Broker-API-Version", func() { apiVersion = "" + response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") + Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set")) }) It("has wrong version of API", func() { apiVersion = "1.14" + response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") + Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x")) }) It("missing service_id", func() { delete(provisionDetails, "service_id") + response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") + Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".provision.service-id-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("service_id missing")) }) It("missing plan_id", func() { delete(provisionDetails, "plan_id") + response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") + Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".provision.plan-id-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("plan_id missing")) }) It("service_id not in the catalog", func() { provisionDetails["service_id"] = "not-in-the-catalogue" + response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") + Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".provision.invalid-service-id")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("service-id not in the catalog")) }) It("plan_id not in the catalog", func() { provisionDetails["plan_id"] = "not-in-the-catalogue" + response := makeInstanceProvisioningRequest(instanceID, provisionDetails, "") + Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".provision.invalid-plan-id")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("plan-id not in the catalog")) }) @@ -906,6 +1098,7 @@ var _ = Describe("Service Broker API", func() { queryString string response *testflight.Response ) + const updateRequestIdentity = "Update Request Identity Name" makeInstanceUpdateRequest := func(instanceID string, details map[string]interface{}, queryString string, apiVersion string) *testflight.Response { response := &testflight.Response{} @@ -922,6 +1115,7 @@ var _ = Describe("Service Broker API", func() { } request.Header.Add("Content-Type", "application/json") request.SetBasicAuth(credentials.Username, credentials.Password) + request.Header.Add("X-Broker-API-Request-Identity", updateRequestIdentity) response = r.Do(request) }) @@ -963,7 +1157,9 @@ var _ = Describe("Service Broker API", func() { It("missing header X-Broker-API-Version", func() { instanceID := "instance-id" response := makeInstanceUpdateRequest(instanceID, details, queryString, "") + Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(updateRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set")) }) @@ -971,15 +1167,20 @@ var _ = Describe("Service Broker API", func() { It("has wrong version of API", func() { instanceID := "instance-id" response := makeInstanceUpdateRequest(instanceID, details, queryString, "1.14") + Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(updateRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x")) }) It("missing service-id", func() { delete(details, "service_id") + response := makeInstanceUpdateRequest("instance-id", details, queryString, "2.14") + Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(updateRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".update.service-id-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("service_id missing")) }) @@ -989,13 +1190,8 @@ var _ = Describe("Service Broker API", func() { Context("when the broker responds synchronously", func() { It("returns HTTP 200", func() { Expect(response.StatusCode).To(Equal(http.StatusOK)) - }) - - It("returns JSON content type", func() { Expect(response.RawResponse.Header.Get("Content-Type")).To(Equal("application/json")) - }) - - It("returns empty JSON body", func() { + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(updateRequestIdentity)) Expect(response.Body).To(Equal("{}\n")) }) @@ -1059,6 +1255,7 @@ var _ = Describe("Service Broker API", func() { It("returns HTTP 202", func() { Expect(response.StatusCode).To(Equal(http.StatusAccepted)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(updateRequestIdentity)) }) Context("when the broker responds with operation data", func() { @@ -1090,11 +1287,10 @@ var _ = Describe("Service Broker API", func() { fakeServiceBroker.UpdateError = brokerapi.ErrAsyncRequired }) - It("returns HTTP 422", func() { + It("returns HTTP 422 with error", func() { Expect(response.StatusCode).To(Equal(http.StatusUnprocessableEntity)) - }) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(updateRequestIdentity)) - It("returns a descriptive message", func() { var body map[string]string err := json.Unmarshal([]byte(response.Body), &body) Expect(err).ToNot(HaveOccurred()) @@ -1108,11 +1304,10 @@ var _ = Describe("Service Broker API", func() { fakeServiceBroker.UpdateError = brokerapi.ErrPlanChangeNotSupported }) - It("returns HTTP 422", func() { + It("returns HTTP 422 with error", func() { Expect(response.StatusCode).To(Equal(http.StatusUnprocessableEntity)) - }) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(updateRequestIdentity)) - It("returns a descriptive message", func() { var body map[string]string err := json.Unmarshal([]byte(response.Body), &body) Expect(err).ToNot(HaveOccurred()) @@ -1128,9 +1323,8 @@ var _ = Describe("Service Broker API", func() { It("returns HTTP 500", func() { Expect(response.StatusCode).To(Equal(500)) - }) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(updateRequestIdentity)) - It("returns a descriptive message", func() { var body map[string]string err := json.Unmarshal([]byte(response.Body), &body) Expect(err).ToNot(HaveOccurred()) @@ -1166,6 +1360,7 @@ var _ = Describe("Service Broker API", func() { It(fmt.Sprintf("returns HTTP %d", expectedStatus), func() { response := makeInstanceDeprovisioningRequest(instanceID, queryString) Expect(response.StatusCode).To(Equal(expectedStatus)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) }) } @@ -1280,11 +1475,9 @@ var _ = Describe("Service Broker API", func() { It("returns a 410", func() { response := makeInstanceDeprovisioningRequest(uniqueInstanceID(), "") - Expect(response.StatusCode).To(Equal(410)) - }) - It("returns an empty JSON object", func() { - response := makeInstanceDeprovisioningRequest(uniqueInstanceID(), "") + Expect(response.StatusCode).To(Equal(410)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(`{}`)) }) @@ -1315,13 +1508,11 @@ var _ = Describe("Service Broker API", func() { fakeServiceBroker.DeprovisionError = errors.New("broker failed") }) - It("returns a 500", func() { + It("returns a 500 with error", func() { response := makeInstanceDeprovisioningRequest(instanceID, "") - Expect(response.StatusCode).To(Equal(500)) - }) - It("returns json with a description field and a useful error message", func() { - response := makeInstanceDeprovisioningRequest(instanceID, "") + Expect(response.StatusCode).To(Equal(500)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(`{"description":"broker failed"}`)) }) @@ -1341,13 +1532,11 @@ var _ = Describe("Service Broker API", func() { ) }) - It("returns status teapot", func() { + It("returns status teapot with error", func() { response := makeInstanceDeprovisioningRequest(instanceID, "") - Expect(response.StatusCode).To(Equal(http.StatusTeapot)) - }) - It("returns json with a description field and a useful error message", func() { - response := makeInstanceDeprovisioningRequest(instanceID, "") + Expect(response.StatusCode).To(Equal(http.StatusTeapot)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(`{"description":"I failed in unique and interesting ways"}`)) }) @@ -1364,6 +1553,7 @@ var _ = Describe("Service Broker API", func() { apiVersion = "" response := makeInstanceDeprovisioningRequestFull("instance-id", "service-id", "plan-id", "") Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set")) }) @@ -1372,6 +1562,7 @@ var _ = Describe("Service Broker API", func() { apiVersion = "1.1" response := makeInstanceDeprovisioningRequestFull("instance-id", "service-id", "plan-id", "") Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x")) }) @@ -1379,6 +1570,7 @@ var _ = Describe("Service Broker API", func() { It("missing service-id", func() { response := makeInstanceDeprovisioningRequestFull("instance-id", "", "plan-id", "") Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".deprovision.service-id-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("service_id missing")) }) @@ -1386,6 +1578,7 @@ var _ = Describe("Service Broker API", func() { It("missing plan-id", func() { response := makeInstanceDeprovisioningRequestFull("instance-id", "service-id", "", "") Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".deprovision.plan-id-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("plan_id missing")) }) @@ -1399,6 +1592,7 @@ var _ = Describe("Service Broker API", func() { response := makeGetInstanceRequest("instance-id") Expect(response.StatusCode).To(Equal(http.StatusUnprocessableEntity)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("broker-api.getInstance.fire")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("some error")) }) @@ -1409,6 +1603,7 @@ var _ = Describe("Service Broker API", func() { response := makeGetInstanceRequest("instance-id") Expect(response.StatusCode).To(Equal(500)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("broker-api.getInstance.unknown-error")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("failed to get instance")) }) @@ -1418,6 +1613,7 @@ var _ = Describe("Service Broker API", func() { apiVersion = "" response := makeGetInstanceRequest("instance-id") Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set")) }) @@ -1426,6 +1622,7 @@ var _ = Describe("Service Broker API", func() { apiVersion = "1.1" response := makeGetInstanceRequest("instance-id") Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x")) }) @@ -1434,6 +1631,7 @@ var _ = Describe("Service Broker API", func() { apiVersion = "2.13" response := makeGetInstanceRequest("instance-id") Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("broker-api.getInstance.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("get instance endpoint only supported starting with OSB version 2.14")) @@ -1448,6 +1646,7 @@ var _ = Describe("Service Broker API", func() { }) Describe("binding lifecycle endpoint", func() { + const bindingRequestIdentity = "Bind Request Identity Name" makeLastBindingOperationRequest := func(instanceID, bindingID string) *testflight.Response { response := &testflight.Response{} @@ -1463,6 +1662,7 @@ var _ = Describe("Service Broker API", func() { request.Header.Add("X-Broker-Api-Version", "2.14") request.Header.Add("Content-Type", "application/json") request.SetBasicAuth("username", "password") + request.Header.Add("X-Broker-API-Request-Identity", bindingRequestIdentity) response = r.Do(request) }) @@ -1485,6 +1685,7 @@ var _ = Describe("Service Broker API", func() { } request.Header.Add("Content-Type", "application/json") request.SetBasicAuth("username", "password") + request.Header.Add("X-Broker-API-Request-Identity", bindingRequestIdentity) response = r.Do(request) }) @@ -1512,6 +1713,7 @@ var _ = Describe("Service Broker API", func() { } request.Header.Add("Content-Type", "application/json") request.SetBasicAuth("username", "password") + request.Header.Add("X-Broker-API-Request-Identity", bindingRequestIdentity) response = r.Do(request) }) @@ -1567,14 +1769,12 @@ var _ = Describe("Service Broker API", func() { Expect(rawParameters).To(Equal(json.RawMessage(`{"new-param":"new-param-value"}`))) }) - It("returns the credentials returned by Bind", func() { + It("returns a 201 with body", func() { response := makeBindingRequest(uniqueInstanceID(), uniqueBindingID(), details) - Expect(response.Body).To(MatchJSON(fixture("binding.json"))) - }) - It("returns a 201", func() { - response := makeBindingRequest(uniqueInstanceID(), uniqueBindingID(), details) Expect(response.StatusCode).To(Equal(201)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) + Expect(response.Body).To(MatchJSON(fixture("binding.json"))) }) Context("when syslog_drain_url is being passed", func() { @@ -1639,6 +1839,7 @@ var _ = Describe("Service Broker API", func() { It("returns a 422", func() { response := makeBindingRequest(uniqueInstanceID(), uniqueBindingID(), nil) Expect(response.StatusCode).To(Equal(http.StatusUnprocessableEntity)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) }) }) @@ -1744,6 +1945,7 @@ var _ = Describe("Service Broker API", func() { Expect(fakeServiceBroker.BoundBindings[bindingID].BindResource.BackupAgent).To(BeTrue()) Expect(response.StatusCode).To(Equal(http.StatusCreated)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(response.Body).To(MatchJSON(`{"backup_agent_url":"http://backup.example.com"}`)) }) }) @@ -1756,13 +1958,10 @@ var _ = Describe("Service Broker API", func() { fakeServiceBroker.BindError = brokerapi.ErrInstanceDoesNotExist }) - It("returns a 404", func() { + It("returns a 404 with error", func() { response := makeBindingRequest(uniqueInstanceID(), uniqueBindingID(), details) Expect(response.StatusCode).To(Equal(404)) - }) - - It("returns an error JSON object", func() { - response := makeBindingRequest(uniqueInstanceID(), uniqueBindingID(), details) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(response.Body).To(MatchJSON(`{"description":"instance does not exist"}`)) }) @@ -1794,6 +1993,7 @@ var _ = Describe("Service Broker API", func() { It("sync broker response", func() { response := makeBindingRequest(instanceID, bindingID, details) Expect(response.StatusCode).To(Equal(http.StatusOK)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) }) It("async broker response", func() { @@ -1804,12 +2004,14 @@ var _ = Describe("Service Broker API", func() { response := makeAsyncBindingRequest(instanceID, bindingID, details) Expect(response.StatusCode).To(Equal(http.StatusOK)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) }) }) It("returns a statusConflict", func() { response := makeBindingRequest(uniqueInstanceID(), uniqueBindingID(), details) Expect(response.StatusCode).To(Equal(http.StatusConflict)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) }) It("returns an error JSON object", func() { @@ -1835,6 +2037,7 @@ var _ = Describe("Service Broker API", func() { It("returns a generic 500 error response", func() { response := makeBindingRequest(uniqueInstanceID(), uniqueBindingID(), details) Expect(response.StatusCode).To(Equal(500)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(response.Body).To(MatchJSON(`{"description":"unknown error"}`)) }) @@ -1855,13 +2058,10 @@ var _ = Describe("Service Broker API", func() { ) }) - It("returns status teapot", func() { + It("returns status teapot and error", func() { response := makeBindingRequest(uniqueInstanceID(), uniqueBindingID(), details) Expect(response.StatusCode).To(Equal(http.StatusTeapot)) - }) - - It("returns json with a description field and a useful error message", func() { - response := makeBindingRequest(uniqueInstanceID(), uniqueBindingID(), details) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(response.Body).To(MatchJSON(`{"description":"I failed in unique and interesting ways"}`)) }) @@ -1888,14 +2088,14 @@ var _ = Describe("Service Broker API", func() { It("successfully returns a sync binding response", func() { response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, details, "2.13", true) Expect(response.StatusCode).To(Equal(http.StatusCreated)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(response.Body).To(MatchJSON(fixture("binding.json"))) }) It("fails for GetBinding request", func() { response := makeGetBindingRequestWithSpecificAPIVersion(instanceID, bindingID, "1.13") Expect(response.StatusCode).To(Equal(http.StatusPreconditionFailed)) - response = makeGetBindingRequestWithSpecificAPIVersion(instanceID, bindingID, "2.13") - Expect(response.StatusCode).To(Equal(http.StatusPreconditionFailed)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) }) }) @@ -1903,6 +2103,7 @@ var _ = Describe("Service Broker API", func() { It("returns an appropriate status code and operation data", func() { response := makeAsyncBindingRequest(instanceID, bindingID, details) Expect(response.StatusCode).To(Equal(http.StatusAccepted)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(response.Body).To(MatchJSON(fixture("async_bind_response.json"))) }) @@ -1911,12 +2112,14 @@ var _ = Describe("Service Broker API", func() { fakeAsyncServiceBroker.LastOperationDescription = "some description" response := makeLastBindingOperationRequest(instanceID, bindingID) Expect(response.StatusCode).To(Equal(http.StatusOK)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(response.Body).To(MatchJSON(fixture("last_operation_succeeded.json"))) }) It("returns the binding for the async request on getBinding", func() { response := makeGetBindingRequestWithSpecificAPIVersion(instanceID, bindingID, "2.14") Expect(response.StatusCode).To(Equal(http.StatusOK)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(response.Body).To(MatchJSON(fixture("binding.json"))) }) }) @@ -1930,6 +2133,7 @@ var _ = Describe("Service Broker API", func() { It("missing header X-Broker-API-Version", func() { response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{}, "", false) Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set")) }) @@ -1937,6 +2141,7 @@ var _ = Describe("Service Broker API", func() { It("has wrong version of API", func() { response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{}, "1.14", false) Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x")) }) @@ -1944,6 +2149,7 @@ var _ = Describe("Service Broker API", func() { It("missing service-id", func() { response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{"plan_id": "123"}, "2.14", false) Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".bind.service-id-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("service_id missing")) }) @@ -1951,6 +2157,7 @@ var _ = Describe("Service Broker API", func() { It("missing plan-id", func() { response := makeBindingRequestWithSpecificAPIVersion(instanceID, bindingID, map[string]interface{}{"service_id": "123"}, "2.14", false) Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".bind.plan-id-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("plan_id missing")) }) @@ -1959,6 +2166,8 @@ var _ = Describe("Service Broker API", func() { }) Describe("unbinding", func() { + const unbindRequestIdentity = "Unbind Request Identity Name" + makeUnbindingRequestWithServiceIDPlanID := func(instanceID, bindingID, serviceID, planID, apiVersion string) *testflight.Response { response := &testflight.Response{} testflight.WithServer(brokerAPI, func(r *testflight.Requester) { @@ -1968,6 +2177,7 @@ var _ = Describe("Service Broker API", func() { request.Header.Add("Content-Type", "application/json") request.Header.Add("X-Broker-API-Version", apiVersion) request.SetBasicAuth("username", "password") + request.Header.Add("X-Broker-API-Request-Identity", unbindRequestIdentity) response = r.Do(request) }) @@ -2004,6 +2214,7 @@ var _ = Describe("Service Broker API", func() { It("missing header X-Broker-API-Version", func() { response := makeUnbindingRequestWithServiceIDPlanID(instanceID, bindingID, "service-id", "plan-id", "") Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(unbindRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set")) }) @@ -2011,6 +2222,7 @@ var _ = Describe("Service Broker API", func() { It("has wrong version of API", func() { response := makeUnbindingRequestWithServiceIDPlanID(instanceID, bindingID, "service-id", "plan-id", "1.1") Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(unbindRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x")) }) @@ -2018,6 +2230,7 @@ var _ = Describe("Service Broker API", func() { It("missing service-id", func() { response := makeUnbindingRequestWithServiceIDPlanID(instanceID, bindingID, "", "plan-id", "2.13") Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(unbindRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".unbind.service-id-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("service_id missing")) }) @@ -2025,6 +2238,7 @@ var _ = Describe("Service Broker API", func() { It("missing plan-id", func() { response := makeUnbindingRequestWithServiceIDPlanID(instanceID, bindingID, "service-id", "", "2.13") Expect(response.StatusCode).To(Equal(400)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(unbindRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring(".unbind.plan-id-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("plan_id missing")) }) @@ -2043,10 +2257,7 @@ var _ = Describe("Service Broker API", func() { It("returns a 200", func() { response := makeUnbindingRequest(instanceID, bindingID) Expect(response.StatusCode).To(Equal(200)) - }) - - It("returns an empty JSON object", func() { - response := makeUnbindingRequest(instanceID, bindingID) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(unbindRequestIdentity)) Expect(response.Body).To(MatchJSON(`{}`)) }) @@ -2065,6 +2276,8 @@ var _ = Describe("Service Broker API", func() { It("returns a 410", func() { response := makeUnbindingRequest(instanceID, "does-not-exist") Expect(response.StatusCode).To(Equal(410)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(unbindRequestIdentity)) + Expect(response.Body).To(MatchJSON(`{}`)) }) It("logs an appropriate error message", func() { @@ -2073,11 +2286,6 @@ var _ = Describe("Service Broker API", func() { Expect(lastLogLine().Message).To(ContainSubstring(".unbind.binding-missing")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("binding does not exist")) }) - - It("returns an empty JSON object", func() { - response := makeUnbindingRequest(instanceID, "does-not-exist") - Expect(response.Body).To(MatchJSON(`{}`)) - }) }) }) @@ -2087,10 +2295,7 @@ var _ = Describe("Service Broker API", func() { It("returns a 410", func() { response := makeUnbindingRequest(uniqueInstanceID(), uniqueBindingID()) Expect(response.StatusCode).To(Equal(http.StatusGone)) - }) - - It("returns an empty JSON object", func() { - response := makeUnbindingRequest(uniqueInstanceID(), uniqueBindingID()) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(unbindRequestIdentity)) Expect(response.Body).To(MatchJSON(`{}`)) }) @@ -2111,6 +2316,7 @@ var _ = Describe("Service Broker API", func() { It("returns a generic 500 error response", func() { response := makeUnbindingRequest(uniqueInstanceID(), uniqueBindingID()) Expect(response.StatusCode).To(Equal(500)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(unbindRequestIdentity)) Expect(response.Body).To(MatchJSON(`{"description":"unknown error"}`)) }) @@ -2131,13 +2337,10 @@ var _ = Describe("Service Broker API", func() { ) }) - It("returns status teapot", func() { + It("returns status teapot with error", func() { response := makeUnbindingRequest(uniqueInstanceID(), uniqueBindingID()) Expect(response.StatusCode).To(Equal(http.StatusTeapot)) - }) - - It("returns json with a description field and a useful error message", func() { - response := makeUnbindingRequest(uniqueInstanceID(), uniqueBindingID()) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(unbindRequestIdentity)) Expect(response.Body).To(MatchJSON(`{"description":"I failed in unique and interesting ways"}`)) }) @@ -2164,7 +2367,7 @@ var _ = Describe("Service Broker API", func() { } request.Header.Add("Content-Type", "application/json") request.SetBasicAuth("username", "password") - + request.Header.Add("X-Broker-API-Request-Identity", requestIdentity) response = r.Do(request) }) return response @@ -2200,6 +2403,7 @@ var _ = Describe("Service Broker API", func() { Expect(logs[1].Data["state"]).To(ContainSubstring(string(fakeServiceBroker.LastOperationState))) Expect(response.StatusCode).To(Equal(200)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(fixture("last_operation_succeeded.json"))) }) @@ -2212,6 +2416,7 @@ var _ = Describe("Service Broker API", func() { Expect(lastLogLine().Data["error"]).To(ContainSubstring("instance does not exist")) Expect(response.StatusCode).To(Equal(410)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(`{}`)) }) @@ -2224,6 +2429,7 @@ var _ = Describe("Service Broker API", func() { response := makeLastOperationRequest("instanceID", "", "2.14") Expect(response.StatusCode).To(Equal(500)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(`{"description": "unknown error"}`)) }) @@ -2244,13 +2450,10 @@ var _ = Describe("Service Broker API", func() { ) }) - It("returns status teapot", func() { + It("returns status teapot with error", func() { response := makeLastOperationRequest("instanceID", "", "2.14") Expect(response.StatusCode).To(Equal(http.StatusTeapot)) - }) - - It("returns json with a description field and a useful error message", func() { - response := makeLastOperationRequest("instanceID", "", "2.14") + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(response.Body).To(MatchJSON(`{"description":"I failed in unique and interesting ways"}`)) }) @@ -2265,6 +2468,7 @@ var _ = Describe("Service Broker API", func() { It("missing header X-Broker-API-Version", func() { response := makeLastOperationRequest("instance-id", "", "") Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set")) }) @@ -2272,6 +2476,7 @@ var _ = Describe("Service Broker API", func() { It("has wrong version of API", func() { response := makeLastOperationRequest("instance-id", "", "1.2") Expect(response.StatusCode).To(Equal(412)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(requestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("version-header-check.broker-api-version-invalid")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x")) }) @@ -2284,6 +2489,7 @@ var _ = Describe("Service Broker API", func() { response := makeGetBindingRequestWithSpecificAPIVersion("some-instance", "some-binding", "2.14") Expect(response.StatusCode).To(Equal(http.StatusInternalServerError)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("broker-api.getBinding.unknown-error")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("something failed")) }) @@ -2293,127 +2499,10 @@ var _ = Describe("Service Broker API", func() { response := makeGetBindingRequestWithSpecificAPIVersion("some-instance", "some-binding", "2.14") Expect(response.StatusCode).To(Equal(http.StatusUnprocessableEntity)) + Expect(response.Header.Get("X-Broker-API-Request-Identity")).To(Equal(bindingRequestIdentity)) Expect(lastLogLine().Message).To(ContainSubstring("broker-api.getBinding.fire")) Expect(lastLogLine().Data["error"]).To(ContainSubstring("some error")) }) }) }) - - Describe("InfoLocationHeader", func() { - - var ( - fakeServiceBroker *fakes.AutoFakeServiceBroker - req *http.Request - testServer *httptest.Server - ) - - BeforeEach(func() { - fakeServiceBroker = new(fakes.AutoFakeServiceBroker) - brokerAPI = brokerapi.New(fakeServiceBroker, brokerLogger, credentials) - - testServer = httptest.NewServer(brokerAPI) - var err error - req, err = http.NewRequest("GET", testServer.URL+"/v2/catalog", nil) - Expect(err).NotTo(HaveOccurred()) - req.Header.Add("X-Broker-API-Version", "2.14") - req.SetBasicAuth(credentials.Username, credentials.Password) - }) - - AfterEach(func() { - testServer.Close() - }) - - When("X-Api-Info-Location is passed", func() { - It("Adds it to the context", func() { - infoLocation := "API Info Location Value" - req.Header.Add("X-Api-Info-Location", infoLocation) - - _, err := http.DefaultClient.Do(req) - Expect(err).NotTo(HaveOccurred()) - - Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") - ctx := fakeServiceBroker.ServicesArgsForCall(0) - Expect(ctx.Value("infoLocation")).To(Equal(infoLocation)) - - }) - }) - When("X-Api-Info-Location is not passed", func() { - It("Adds empty infoLocation to the context", func() { - _, err := http.DefaultClient.Do(req) - Expect(err).NotTo(HaveOccurred()) - - Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") - ctx := fakeServiceBroker.ServicesArgsForCall(0) - Expect(ctx.Value("infoLocation")).To(Equal("")) - }) - }) - }) - - Describe("CorrelationIDHeader", func() { - const correlationID = "fake-correlation-id" - - type testCase struct { - correlationIDHeaderName string - } - - var ( - fakeServiceBroker *fakes.AutoFakeServiceBroker - req *http.Request - testServer *httptest.Server - ) - - BeforeEach(func() { - fakeServiceBroker = new(fakes.AutoFakeServiceBroker) - brokerAPI = brokerapi.New(fakeServiceBroker, brokerLogger, credentials) - - testServer = httptest.NewServer(brokerAPI) - var err error - req, err = http.NewRequest("GET", testServer.URL+"/v2/catalog", nil) - Expect(err).NotTo(HaveOccurred()) - req.Header.Add("X-Broker-API-Version", "2.14") - req.SetBasicAuth(credentials.Username, credentials.Password) - }) - - AfterEach(func() { - testServer.Close() - }) - - table.DescribeTable("Adds correlation id to the context", func(tc testCase) { - req.Header.Add(tc.correlationIDHeaderName, correlationID) - - _, err := http.DefaultClient.Do(req) - Expect(err).NotTo(HaveOccurred()) - - Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") - ctx := fakeServiceBroker.ServicesArgsForCall(0) - Expect(ctx.Value(middlewares.CorrelationIDKey)).To(Equal(correlationID)) - }, - table.Entry("X-Correlation-ID", testCase{ - correlationIDHeaderName: "X-Correlation-ID", - }), - table.Entry("X-CorrelationID", testCase{ - correlationIDHeaderName: "X-CorrelationID", - }), - table.Entry("X-ForRequest-ID", testCase{ - correlationIDHeaderName: "X-ForRequest-ID", - }), - table.Entry("X-Request-ID", testCase{ - correlationIDHeaderName: "X-Request-ID", - }), - table.Entry("X-Vcap-Request-Id", testCase{ - correlationIDHeaderName: "X-Vcap-Request-Id", - }), - ) - - When("X-Correlation-ID is not passed", func() { - It("Generates correlation id and adds it to the context", func() { - _, err := http.DefaultClient.Do(req) - Expect(err).NotTo(HaveOccurred()) - - Expect(fakeServiceBroker.ServicesCallCount()).To(Equal(1), "Services was not called") - ctx := fakeServiceBroker.ServicesArgsForCall(0) - Expect(ctx.Value(middlewares.CorrelationIDKey)).To(Not(BeNil())) - }) - }) - }) }) diff --git a/handlers/api_handler.go b/handlers/api_handler.go index 91a011a7..8a9828b1 100644 --- a/handlers/api_handler.go +++ b/handlers/api_handler.go @@ -36,8 +36,11 @@ func NewApiHandler(broker domain.ServiceBroker, logger lager.Logger) APIHandler return APIHandler{broker, logger} } -func (h APIHandler) respond(w http.ResponseWriter, status int, response interface{}) { +func (h APIHandler) respond(w http.ResponseWriter, status int, requestIdentity string, response interface{}) { w.Header().Set("Content-Type", "application/json") + if requestIdentity != "" { + w.Header().Set("X-Broker-API-Request-Identity", requestIdentity) + } w.WriteHeader(status) encoder := json.NewEncoder(w) diff --git a/handlers/bind.go b/handlers/bind.go index f6f4aea7..eb4fb08c 100644 --- a/handlers/bind.go +++ b/handlers/bind.go @@ -2,6 +2,7 @@ package handlers import ( "encoding/json" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -33,10 +34,12 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { asyncAllowed = req.FormValue("accepts_incomplete") == "true" } + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + var details domain.BindDetails if err := json.NewDecoder(req.Body).Decode(&details); err != nil { logger.Error(invalidBindDetailsErrorKey, err) - h.respond(w, http.StatusUnprocessableEntity, apiresponses.ErrorResponse{ + h.respond(w, http.StatusUnprocessableEntity, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) return @@ -44,7 +47,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { if details.ServiceID == "" { logger.Error(serviceIdMissingKey, serviceIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) return @@ -52,7 +55,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { if details.PlanID == "" { logger.Error(planIdMissingKey, planIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) return @@ -72,10 +75,10 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { statusCode = http.StatusNotFound } logger.Error(err.LoggerAction(), err) - h.respond(w, statusCode, errorResponse) + h.respond(w, statusCode, requestId, errorResponse) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -83,7 +86,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { } if binding.AlreadyExists { - h.respond(w, http.StatusOK, apiresponses.BindingResponse{ + h.respond(w, http.StatusOK, requestId, apiresponses.BindingResponse{ Credentials: binding.Credentials, SyslogDrainURL: binding.SyslogDrainURL, RouteServiceURL: binding.RouteServiceURL, @@ -94,7 +97,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { } if binding.IsAsync { - h.respond(w, http.StatusAccepted, apiresponses.AsyncBindResponse{ + h.respond(w, http.StatusAccepted, requestId, apiresponses.AsyncBindResponse{ OperationData: binding.OperationData, }) return @@ -107,7 +110,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { experimentalConfig, err := json.Marshal(vol.Device.MountConfig) if err != nil { logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{Description: err.Error()}) + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{Description: err.Error()}) return } @@ -129,11 +132,11 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { VolumeMounts: experimentalVols, BackupAgentURL: binding.BackupAgentURL, } - h.respond(w, http.StatusCreated, experimentalBinding) + h.respond(w, http.StatusCreated, requestId, experimentalBinding) return } - h.respond(w, http.StatusCreated, apiresponses.BindingResponse{ + h.respond(w, http.StatusCreated, requestId, apiresponses.BindingResponse{ Credentials: binding.Credentials, SyslogDrainURL: binding.SyslogDrainURL, RouteServiceURL: binding.RouteServiceURL, diff --git a/handlers/catalog.go b/handlers/catalog.go index 1383aaa1..bc436ff8 100644 --- a/handlers/catalog.go +++ b/handlers/catalog.go @@ -1,16 +1,18 @@ package handlers import ( + "fmt" "net/http" "github.com/pivotal-cf/brokerapi/v7/domain/apiresponses" ) func (h *APIHandler) Catalog(w http.ResponseWriter, req *http.Request) { + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) services, err := h.serviceBroker.Services(req.Context()) if err != nil { - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) return @@ -20,5 +22,5 @@ func (h *APIHandler) Catalog(w http.ResponseWriter, req *http.Request) { Services: services, } - h.respond(w, http.StatusOK, catalog) + h.respond(w, http.StatusOK, requestId, catalog) } diff --git a/handlers/deprovision.go b/handlers/deprovision.go index 739bd51f..a4906e07 100644 --- a/handlers/deprovision.go +++ b/handlers/deprovision.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -27,8 +28,10 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { Force: req.FormValue("force") == "true", } + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + if details.ServiceID == "" { - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) logger.Error(serviceIdMissingKey, serviceIdError) @@ -36,7 +39,7 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { } if details.PlanID == "" { - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) logger.Error(planIdMissingKey, planIdError) @@ -50,10 +53,10 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -61,8 +64,8 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { } if deprovisionSpec.IsAsync { - h.respond(w, http.StatusAccepted, apiresponses.DeprovisionResponse{OperationData: deprovisionSpec.OperationData}) + h.respond(w, http.StatusAccepted, requestId, apiresponses.DeprovisionResponse{OperationData: deprovisionSpec.OperationData}) } else { - h.respond(w, http.StatusOK, apiresponses.EmptyResponse{}) + h.respond(w, http.StatusOK, requestId, apiresponses.EmptyResponse{}) } } diff --git a/handlers/get_binding.go b/handlers/get_binding.go index f53e0ff0..598b5816 100644 --- a/handlers/get_binding.go +++ b/handlers/get_binding.go @@ -2,6 +2,7 @@ package handlers import ( "errors" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -23,10 +24,12 @@ func (h APIHandler) GetBinding(w http.ResponseWriter, req *http.Request) { bindingIDLogKey: bindingID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + version := getAPIVersion(req) if version.Minor < 14 { err := errors.New("get binding endpoint only supported starting with OSB version 2.14") - h.respond(w, http.StatusPreconditionFailed, apiresponses.ErrorResponse{ + h.respond(w, http.StatusPreconditionFailed, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) logger.Error(middlewares.ApiVersionInvalidKey, err) @@ -38,17 +41,17 @@ func (h APIHandler) GetBinding(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } return } - h.respond(w, http.StatusOK, apiresponses.GetBindingResponse{ + h.respond(w, http.StatusOK, requestId, apiresponses.GetBindingResponse{ BindingResponse: apiresponses.BindingResponse{ Credentials: binding.Credentials, SyslogDrainURL: binding.SyslogDrainURL, diff --git a/handlers/get_instance.go b/handlers/get_instance.go index 93a4680a..6a479b7b 100644 --- a/handlers/get_instance.go +++ b/handlers/get_instance.go @@ -2,6 +2,7 @@ package handlers import ( "errors" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -21,10 +22,12 @@ func (h APIHandler) GetInstance(w http.ResponseWriter, req *http.Request) { instanceIDLogKey: instanceID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + version := getAPIVersion(req) if version.Minor < 14 { err := errors.New("get instance endpoint only supported starting with OSB version 2.14") - h.respond(w, http.StatusPreconditionFailed, apiresponses.ErrorResponse{ + h.respond(w, http.StatusPreconditionFailed, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) logger.Error(middlewares.ApiVersionInvalidKey, err) @@ -36,17 +39,17 @@ func (h APIHandler) GetInstance(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } return } - h.respond(w, http.StatusOK, apiresponses.GetInstanceResponse{ + h.respond(w, http.StatusOK, requestId, apiresponses.GetInstanceResponse{ ServiceID: instanceDetails.ServiceID, PlanID: instanceDetails.PlanID, DashboardURL: instanceDetails.DashboardURL, diff --git a/handlers/last_binding_operation.go b/handlers/last_binding_operation.go index 8ac150c7..27ed7d99 100644 --- a/handlers/last_binding_operation.go +++ b/handlers/last_binding_operation.go @@ -2,6 +2,7 @@ package handlers import ( "errors" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -28,10 +29,12 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques instanceIDLogKey: instanceID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + version := getAPIVersion(req) if version.Minor < 14 { err := errors.New("get binding endpoint only supported starting with OSB version 2.14") - h.respond(w, http.StatusPreconditionFailed, apiresponses.ErrorResponse{ + h.respond(w, http.StatusPreconditionFailed, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) logger.Error(middlewares.ApiVersionInvalidKey, err) @@ -41,15 +44,14 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques logger.Info("starting-check-for-binding-operation") lastOperation, err := h.serviceBroker.LastBindingOperation(req.Context(), instanceID, bindingID, pollDetails) - if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -62,5 +64,5 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques State: lastOperation.State, Description: lastOperation.Description, } - h.respond(w, http.StatusOK, lastOperationResponse) + h.respond(w, http.StatusOK, requestId, lastOperationResponse) } diff --git a/handlers/last_operation.go b/handlers/last_operation.go index 9aecd7d1..6a8b1af7 100644 --- a/handlers/last_operation.go +++ b/handlers/last_operation.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -28,16 +29,17 @@ func (h APIHandler) LastOperation(w http.ResponseWriter, req *http.Request) { logger.Info("starting-check-for-operation") - lastOperation, err := h.serviceBroker.LastOperation(req.Context(), instanceID, pollDetails) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + lastOperation, err := h.serviceBroker.LastOperation(req.Context(), instanceID, pollDetails) if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -51,5 +53,5 @@ func (h APIHandler) LastOperation(w http.ResponseWriter, req *http.Request) { Description: lastOperation.Description, } - h.respond(w, http.StatusOK, lastOperationResponse) + h.respond(w, http.StatusOK, requestId, lastOperationResponse) } diff --git a/handlers/provision.go b/handlers/provision.go index 6e5c0c1b..e4916eea 100644 --- a/handlers/provision.go +++ b/handlers/provision.go @@ -2,6 +2,7 @@ package handlers import ( "encoding/json" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -29,10 +30,12 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { instanceIDLogKey: instanceID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + var details domain.ProvisionDetails if err := json.NewDecoder(req.Body).Decode(&details); err != nil { logger.Error(invalidServiceDetailsErrorKey, err) - h.respond(w, http.StatusUnprocessableEntity, apiresponses.ErrorResponse{ + h.respond(w, http.StatusUnprocessableEntity, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) return @@ -40,7 +43,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { if details.ServiceID == "" { logger.Error(serviceIdMissingKey, serviceIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) return @@ -48,7 +51,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { if details.PlanID == "" { logger.Error(planIdMissingKey, planIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) return @@ -65,7 +68,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } if !valid { logger.Error(invalidServiceID, invalidServiceIDError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: invalidServiceIDError.Error(), }) return @@ -83,7 +86,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } if !valid { logger.Error(invalidPlanID, invalidPlanIDError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: invalidPlanIDError.Error(), }) return @@ -101,10 +104,10 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -117,18 +120,18 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } if provisionResponse.AlreadyExists { - h.respond(w, http.StatusOK, apiresponses.ProvisioningResponse{ + h.respond(w, http.StatusOK, requestId, apiresponses.ProvisioningResponse{ DashboardURL: provisionResponse.DashboardURL, Metadata: metadata, }) } else if provisionResponse.IsAsync { - h.respond(w, http.StatusAccepted, apiresponses.ProvisioningResponse{ + h.respond(w, http.StatusAccepted, requestId, apiresponses.ProvisioningResponse{ DashboardURL: provisionResponse.DashboardURL, OperationData: provisionResponse.OperationData, Metadata: metadata, }) } else { - h.respond(w, http.StatusCreated, apiresponses.ProvisioningResponse{ + h.respond(w, http.StatusCreated, requestId, apiresponses.ProvisioningResponse{ DashboardURL: provisionResponse.DashboardURL, Metadata: metadata, }) diff --git a/handlers/unbind.go b/handlers/unbind.go index 7a4ebe24..74d9da09 100644 --- a/handlers/unbind.go +++ b/handlers/unbind.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -23,13 +24,15 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { bindingIDLogKey: bindingID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + details := domain.UnbindDetails{ PlanID: req.FormValue("plan_id"), ServiceID: req.FormValue("service_id"), } if details.ServiceID == "" { - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) logger.Error(serviceIdMissingKey, serviceIdError) @@ -37,7 +40,7 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { } if details.PlanID == "" { - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) logger.Error(planIdMissingKey, planIdError) @@ -50,10 +53,10 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -61,11 +64,11 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { } if unbindResponse.IsAsync { - h.respond(w, http.StatusAccepted, apiresponses.UnbindResponse{ + h.respond(w, http.StatusAccepted, requestId, apiresponses.UnbindResponse{ OperationData: unbindResponse.OperationData, }) } else { - h.respond(w, http.StatusOK, apiresponses.EmptyResponse{}) + h.respond(w, http.StatusOK, requestId, apiresponses.EmptyResponse{}) } } diff --git a/handlers/update.go b/handlers/update.go index d8a7a95a..c64236bf 100644 --- a/handlers/update.go +++ b/handlers/update.go @@ -2,6 +2,7 @@ package handlers import ( "encoding/json" + "fmt" "net/http" "strconv" @@ -23,10 +24,12 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { instanceIDLogKey: instanceID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + var details domain.UpdateDetails if err := json.NewDecoder(req.Body).Decode(&details); err != nil { h.logger.Error(invalidServiceDetailsErrorKey, err) - h.respond(w, http.StatusUnprocessableEntity, apiresponses.ErrorResponse{ + h.respond(w, http.StatusUnprocessableEntity, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) return @@ -34,7 +37,7 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { if details.ServiceID == "" { logger.Error(serviceIdMissingKey, serviceIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) return @@ -47,10 +50,10 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: h.logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(h.logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(h.logger), requestId, err.ErrorResponse()) default: h.logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -61,7 +64,7 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { if updateServiceSpec.IsAsync { statusCode = http.StatusAccepted } - h.respond(w, statusCode, apiresponses.UpdateResponse{ + h.respond(w, statusCode, requestId, apiresponses.UpdateResponse{ OperationData: updateServiceSpec.OperationData, DashboardURL: updateServiceSpec.DashboardURL, }) diff --git a/middlewares/api_version_header.go b/middlewares/api_version_header.go index f1a38460..5e4369cb 100644 --- a/middlewares/api_version_header.go +++ b/middlewares/api_version_header.go @@ -43,6 +43,7 @@ func (m APIVersionMiddleware) ValidateAPIVersionHdr(next http.Handler) http.Hand logger.Error(ApiVersionInvalidKey, err) w.Header().Set("Content-type", "application/json") + setBrokerRequestIdentityHeader(req, w) statusResponse := http.StatusPreconditionFailed w.WriteHeader(statusResponse) @@ -61,6 +62,13 @@ func (m APIVersionMiddleware) ValidateAPIVersionHdr(next http.Handler) http.Hand }) } +func setBrokerRequestIdentityHeader(req *http.Request, w http.ResponseWriter) { + requestID := req.Header.Get("X-Broker-API-Request-Identity") + if requestID != "" { + w.Header().Set("X-Broker-API-Request-Identity", requestID) + } +} + func checkBrokerAPIVersionHdr(req *http.Request) error { var version struct { Major int diff --git a/middlewares/request_identity_header.go b/middlewares/request_identity_header.go new file mode 100644 index 00000000..0305a4c7 --- /dev/null +++ b/middlewares/request_identity_header.go @@ -0,0 +1,16 @@ +package middlewares + +import ( + "context" + "net/http" +) + +const RequestIdentityKey = "requestIdentity" + +func AddRequestIdentityToContext(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + requestIdentity := req.Header.Get("X-Broker-API-Request-Identity") + newCtx := context.WithValue(req.Context(), RequestIdentityKey, requestIdentity) + next.ServeHTTP(w, req.WithContext(newCtx)) + }) +} diff --git a/v7/api.go b/v7/api.go index f0573bc4..c4d19d4d 100644 --- a/v7/api.go +++ b/v7/api.go @@ -43,6 +43,7 @@ func New(serviceBroker ServiceBroker, logger lager.Logger, brokerCredentials Bro router.Use(middlewares.AddOriginatingIdentityToContext) router.Use(apiVersionMiddleware.ValidateAPIVersionHdr) router.Use(middlewares.AddInfoLocationToContext) + router.Use(middlewares.AddRequestIdentityToContext) return router } diff --git a/v7/handlers/api_handler.go b/v7/handlers/api_handler.go index 91a011a7..8a9828b1 100644 --- a/v7/handlers/api_handler.go +++ b/v7/handlers/api_handler.go @@ -36,8 +36,11 @@ func NewApiHandler(broker domain.ServiceBroker, logger lager.Logger) APIHandler return APIHandler{broker, logger} } -func (h APIHandler) respond(w http.ResponseWriter, status int, response interface{}) { +func (h APIHandler) respond(w http.ResponseWriter, status int, requestIdentity string, response interface{}) { w.Header().Set("Content-Type", "application/json") + if requestIdentity != "" { + w.Header().Set("X-Broker-API-Request-Identity", requestIdentity) + } w.WriteHeader(status) encoder := json.NewEncoder(w) diff --git a/v7/handlers/bind.go b/v7/handlers/bind.go index f6f4aea7..eb4fb08c 100644 --- a/v7/handlers/bind.go +++ b/v7/handlers/bind.go @@ -2,6 +2,7 @@ package handlers import ( "encoding/json" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -33,10 +34,12 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { asyncAllowed = req.FormValue("accepts_incomplete") == "true" } + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + var details domain.BindDetails if err := json.NewDecoder(req.Body).Decode(&details); err != nil { logger.Error(invalidBindDetailsErrorKey, err) - h.respond(w, http.StatusUnprocessableEntity, apiresponses.ErrorResponse{ + h.respond(w, http.StatusUnprocessableEntity, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) return @@ -44,7 +47,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { if details.ServiceID == "" { logger.Error(serviceIdMissingKey, serviceIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) return @@ -52,7 +55,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { if details.PlanID == "" { logger.Error(planIdMissingKey, planIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) return @@ -72,10 +75,10 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { statusCode = http.StatusNotFound } logger.Error(err.LoggerAction(), err) - h.respond(w, statusCode, errorResponse) + h.respond(w, statusCode, requestId, errorResponse) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -83,7 +86,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { } if binding.AlreadyExists { - h.respond(w, http.StatusOK, apiresponses.BindingResponse{ + h.respond(w, http.StatusOK, requestId, apiresponses.BindingResponse{ Credentials: binding.Credentials, SyslogDrainURL: binding.SyslogDrainURL, RouteServiceURL: binding.RouteServiceURL, @@ -94,7 +97,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { } if binding.IsAsync { - h.respond(w, http.StatusAccepted, apiresponses.AsyncBindResponse{ + h.respond(w, http.StatusAccepted, requestId, apiresponses.AsyncBindResponse{ OperationData: binding.OperationData, }) return @@ -107,7 +110,7 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { experimentalConfig, err := json.Marshal(vol.Device.MountConfig) if err != nil { logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{Description: err.Error()}) + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{Description: err.Error()}) return } @@ -129,11 +132,11 @@ func (h APIHandler) Bind(w http.ResponseWriter, req *http.Request) { VolumeMounts: experimentalVols, BackupAgentURL: binding.BackupAgentURL, } - h.respond(w, http.StatusCreated, experimentalBinding) + h.respond(w, http.StatusCreated, requestId, experimentalBinding) return } - h.respond(w, http.StatusCreated, apiresponses.BindingResponse{ + h.respond(w, http.StatusCreated, requestId, apiresponses.BindingResponse{ Credentials: binding.Credentials, SyslogDrainURL: binding.SyslogDrainURL, RouteServiceURL: binding.RouteServiceURL, diff --git a/v7/handlers/catalog.go b/v7/handlers/catalog.go index 1383aaa1..bc436ff8 100644 --- a/v7/handlers/catalog.go +++ b/v7/handlers/catalog.go @@ -1,16 +1,18 @@ package handlers import ( + "fmt" "net/http" "github.com/pivotal-cf/brokerapi/v7/domain/apiresponses" ) func (h *APIHandler) Catalog(w http.ResponseWriter, req *http.Request) { + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) services, err := h.serviceBroker.Services(req.Context()) if err != nil { - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) return @@ -20,5 +22,5 @@ func (h *APIHandler) Catalog(w http.ResponseWriter, req *http.Request) { Services: services, } - h.respond(w, http.StatusOK, catalog) + h.respond(w, http.StatusOK, requestId, catalog) } diff --git a/v7/handlers/deprovision.go b/v7/handlers/deprovision.go index 739bd51f..a4906e07 100644 --- a/v7/handlers/deprovision.go +++ b/v7/handlers/deprovision.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -27,8 +28,10 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { Force: req.FormValue("force") == "true", } + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + if details.ServiceID == "" { - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) logger.Error(serviceIdMissingKey, serviceIdError) @@ -36,7 +39,7 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { } if details.PlanID == "" { - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) logger.Error(planIdMissingKey, planIdError) @@ -50,10 +53,10 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -61,8 +64,8 @@ func (h APIHandler) Deprovision(w http.ResponseWriter, req *http.Request) { } if deprovisionSpec.IsAsync { - h.respond(w, http.StatusAccepted, apiresponses.DeprovisionResponse{OperationData: deprovisionSpec.OperationData}) + h.respond(w, http.StatusAccepted, requestId, apiresponses.DeprovisionResponse{OperationData: deprovisionSpec.OperationData}) } else { - h.respond(w, http.StatusOK, apiresponses.EmptyResponse{}) + h.respond(w, http.StatusOK, requestId, apiresponses.EmptyResponse{}) } } diff --git a/v7/handlers/get_binding.go b/v7/handlers/get_binding.go index f53e0ff0..598b5816 100644 --- a/v7/handlers/get_binding.go +++ b/v7/handlers/get_binding.go @@ -2,6 +2,7 @@ package handlers import ( "errors" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -23,10 +24,12 @@ func (h APIHandler) GetBinding(w http.ResponseWriter, req *http.Request) { bindingIDLogKey: bindingID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + version := getAPIVersion(req) if version.Minor < 14 { err := errors.New("get binding endpoint only supported starting with OSB version 2.14") - h.respond(w, http.StatusPreconditionFailed, apiresponses.ErrorResponse{ + h.respond(w, http.StatusPreconditionFailed, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) logger.Error(middlewares.ApiVersionInvalidKey, err) @@ -38,17 +41,17 @@ func (h APIHandler) GetBinding(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } return } - h.respond(w, http.StatusOK, apiresponses.GetBindingResponse{ + h.respond(w, http.StatusOK, requestId, apiresponses.GetBindingResponse{ BindingResponse: apiresponses.BindingResponse{ Credentials: binding.Credentials, SyslogDrainURL: binding.SyslogDrainURL, diff --git a/v7/handlers/get_instance.go b/v7/handlers/get_instance.go index 93a4680a..6a479b7b 100644 --- a/v7/handlers/get_instance.go +++ b/v7/handlers/get_instance.go @@ -2,6 +2,7 @@ package handlers import ( "errors" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -21,10 +22,12 @@ func (h APIHandler) GetInstance(w http.ResponseWriter, req *http.Request) { instanceIDLogKey: instanceID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + version := getAPIVersion(req) if version.Minor < 14 { err := errors.New("get instance endpoint only supported starting with OSB version 2.14") - h.respond(w, http.StatusPreconditionFailed, apiresponses.ErrorResponse{ + h.respond(w, http.StatusPreconditionFailed, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) logger.Error(middlewares.ApiVersionInvalidKey, err) @@ -36,17 +39,17 @@ func (h APIHandler) GetInstance(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } return } - h.respond(w, http.StatusOK, apiresponses.GetInstanceResponse{ + h.respond(w, http.StatusOK, requestId, apiresponses.GetInstanceResponse{ ServiceID: instanceDetails.ServiceID, PlanID: instanceDetails.PlanID, DashboardURL: instanceDetails.DashboardURL, diff --git a/v7/handlers/last_binding_operation.go b/v7/handlers/last_binding_operation.go index 8ac150c7..27ed7d99 100644 --- a/v7/handlers/last_binding_operation.go +++ b/v7/handlers/last_binding_operation.go @@ -2,6 +2,7 @@ package handlers import ( "errors" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -28,10 +29,12 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques instanceIDLogKey: instanceID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + version := getAPIVersion(req) if version.Minor < 14 { err := errors.New("get binding endpoint only supported starting with OSB version 2.14") - h.respond(w, http.StatusPreconditionFailed, apiresponses.ErrorResponse{ + h.respond(w, http.StatusPreconditionFailed, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) logger.Error(middlewares.ApiVersionInvalidKey, err) @@ -41,15 +44,14 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques logger.Info("starting-check-for-binding-operation") lastOperation, err := h.serviceBroker.LastBindingOperation(req.Context(), instanceID, bindingID, pollDetails) - if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -62,5 +64,5 @@ func (h APIHandler) LastBindingOperation(w http.ResponseWriter, req *http.Reques State: lastOperation.State, Description: lastOperation.Description, } - h.respond(w, http.StatusOK, lastOperationResponse) + h.respond(w, http.StatusOK, requestId, lastOperationResponse) } diff --git a/v7/handlers/last_operation.go b/v7/handlers/last_operation.go index 9aecd7d1..6a8b1af7 100644 --- a/v7/handlers/last_operation.go +++ b/v7/handlers/last_operation.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -28,16 +29,17 @@ func (h APIHandler) LastOperation(w http.ResponseWriter, req *http.Request) { logger.Info("starting-check-for-operation") - lastOperation, err := h.serviceBroker.LastOperation(req.Context(), instanceID, pollDetails) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + lastOperation, err := h.serviceBroker.LastOperation(req.Context(), instanceID, pollDetails) if err != nil { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -51,5 +53,5 @@ func (h APIHandler) LastOperation(w http.ResponseWriter, req *http.Request) { Description: lastOperation.Description, } - h.respond(w, http.StatusOK, lastOperationResponse) + h.respond(w, http.StatusOK, requestId, lastOperationResponse) } diff --git a/v7/handlers/provision.go b/v7/handlers/provision.go index 6e5c0c1b..e4916eea 100644 --- a/v7/handlers/provision.go +++ b/v7/handlers/provision.go @@ -2,6 +2,7 @@ package handlers import ( "encoding/json" + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -29,10 +30,12 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { instanceIDLogKey: instanceID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + var details domain.ProvisionDetails if err := json.NewDecoder(req.Body).Decode(&details); err != nil { logger.Error(invalidServiceDetailsErrorKey, err) - h.respond(w, http.StatusUnprocessableEntity, apiresponses.ErrorResponse{ + h.respond(w, http.StatusUnprocessableEntity, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) return @@ -40,7 +43,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { if details.ServiceID == "" { logger.Error(serviceIdMissingKey, serviceIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) return @@ -48,7 +51,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { if details.PlanID == "" { logger.Error(planIdMissingKey, planIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) return @@ -65,7 +68,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } if !valid { logger.Error(invalidServiceID, invalidServiceIDError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: invalidServiceIDError.Error(), }) return @@ -83,7 +86,7 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } if !valid { logger.Error(invalidPlanID, invalidPlanIDError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: invalidPlanIDError.Error(), }) return @@ -101,10 +104,10 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -117,18 +120,18 @@ func (h *APIHandler) Provision(w http.ResponseWriter, req *http.Request) { } if provisionResponse.AlreadyExists { - h.respond(w, http.StatusOK, apiresponses.ProvisioningResponse{ + h.respond(w, http.StatusOK, requestId, apiresponses.ProvisioningResponse{ DashboardURL: provisionResponse.DashboardURL, Metadata: metadata, }) } else if provisionResponse.IsAsync { - h.respond(w, http.StatusAccepted, apiresponses.ProvisioningResponse{ + h.respond(w, http.StatusAccepted, requestId, apiresponses.ProvisioningResponse{ DashboardURL: provisionResponse.DashboardURL, OperationData: provisionResponse.OperationData, Metadata: metadata, }) } else { - h.respond(w, http.StatusCreated, apiresponses.ProvisioningResponse{ + h.respond(w, http.StatusCreated, requestId, apiresponses.ProvisioningResponse{ DashboardURL: provisionResponse.DashboardURL, Metadata: metadata, }) diff --git a/v7/handlers/unbind.go b/v7/handlers/unbind.go index 7a4ebe24..74d9da09 100644 --- a/v7/handlers/unbind.go +++ b/v7/handlers/unbind.go @@ -1,6 +1,7 @@ package handlers import ( + "fmt" "net/http" "code.cloudfoundry.org/lager" @@ -23,13 +24,15 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { bindingIDLogKey: bindingID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + details := domain.UnbindDetails{ PlanID: req.FormValue("plan_id"), ServiceID: req.FormValue("service_id"), } if details.ServiceID == "" { - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) logger.Error(serviceIdMissingKey, serviceIdError) @@ -37,7 +40,7 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { } if details.PlanID == "" { - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: planIdError.Error(), }) logger.Error(planIdMissingKey, planIdError) @@ -50,10 +53,10 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(logger), requestId, err.ErrorResponse()) default: logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -61,11 +64,11 @@ func (h APIHandler) Unbind(w http.ResponseWriter, req *http.Request) { } if unbindResponse.IsAsync { - h.respond(w, http.StatusAccepted, apiresponses.UnbindResponse{ + h.respond(w, http.StatusAccepted, requestId, apiresponses.UnbindResponse{ OperationData: unbindResponse.OperationData, }) } else { - h.respond(w, http.StatusOK, apiresponses.EmptyResponse{}) + h.respond(w, http.StatusOK, requestId, apiresponses.EmptyResponse{}) } } diff --git a/v7/handlers/update.go b/v7/handlers/update.go index d8a7a95a..c64236bf 100644 --- a/v7/handlers/update.go +++ b/v7/handlers/update.go @@ -2,6 +2,7 @@ package handlers import ( "encoding/json" + "fmt" "net/http" "strconv" @@ -23,10 +24,12 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { instanceIDLogKey: instanceID, }, utils.DataForContext(req.Context(), middlewares.CorrelationIDKey)) + requestId := fmt.Sprintf("%v", req.Context().Value("requestIdentity")) + var details domain.UpdateDetails if err := json.NewDecoder(req.Body).Decode(&details); err != nil { h.logger.Error(invalidServiceDetailsErrorKey, err) - h.respond(w, http.StatusUnprocessableEntity, apiresponses.ErrorResponse{ + h.respond(w, http.StatusUnprocessableEntity, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) return @@ -34,7 +37,7 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { if details.ServiceID == "" { logger.Error(serviceIdMissingKey, serviceIdError) - h.respond(w, http.StatusBadRequest, apiresponses.ErrorResponse{ + h.respond(w, http.StatusBadRequest, requestId, apiresponses.ErrorResponse{ Description: serviceIdError.Error(), }) return @@ -47,10 +50,10 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { switch err := err.(type) { case *apiresponses.FailureResponse: h.logger.Error(err.LoggerAction(), err) - h.respond(w, err.ValidatedStatusCode(h.logger), err.ErrorResponse()) + h.respond(w, err.ValidatedStatusCode(h.logger), requestId, err.ErrorResponse()) default: h.logger.Error(unknownErrorKey, err) - h.respond(w, http.StatusInternalServerError, apiresponses.ErrorResponse{ + h.respond(w, http.StatusInternalServerError, requestId, apiresponses.ErrorResponse{ Description: err.Error(), }) } @@ -61,7 +64,7 @@ func (h APIHandler) Update(w http.ResponseWriter, req *http.Request) { if updateServiceSpec.IsAsync { statusCode = http.StatusAccepted } - h.respond(w, statusCode, apiresponses.UpdateResponse{ + h.respond(w, statusCode, requestId, apiresponses.UpdateResponse{ OperationData: updateServiceSpec.OperationData, DashboardURL: updateServiceSpec.DashboardURL, }) diff --git a/v7/middlewares/api_version_header.go b/v7/middlewares/api_version_header.go index f1a38460..5e4369cb 100644 --- a/v7/middlewares/api_version_header.go +++ b/v7/middlewares/api_version_header.go @@ -43,6 +43,7 @@ func (m APIVersionMiddleware) ValidateAPIVersionHdr(next http.Handler) http.Hand logger.Error(ApiVersionInvalidKey, err) w.Header().Set("Content-type", "application/json") + setBrokerRequestIdentityHeader(req, w) statusResponse := http.StatusPreconditionFailed w.WriteHeader(statusResponse) @@ -61,6 +62,13 @@ func (m APIVersionMiddleware) ValidateAPIVersionHdr(next http.Handler) http.Hand }) } +func setBrokerRequestIdentityHeader(req *http.Request, w http.ResponseWriter) { + requestID := req.Header.Get("X-Broker-API-Request-Identity") + if requestID != "" { + w.Header().Set("X-Broker-API-Request-Identity", requestID) + } +} + func checkBrokerAPIVersionHdr(req *http.Request) error { var version struct { Major int diff --git a/v7/middlewares/request_identity_header.go b/v7/middlewares/request_identity_header.go new file mode 100644 index 00000000..0305a4c7 --- /dev/null +++ b/v7/middlewares/request_identity_header.go @@ -0,0 +1,16 @@ +package middlewares + +import ( + "context" + "net/http" +) + +const RequestIdentityKey = "requestIdentity" + +func AddRequestIdentityToContext(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + requestIdentity := req.Header.Get("X-Broker-API-Request-Identity") + newCtx := context.WithValue(req.Context(), RequestIdentityKey, requestIdentity) + next.ServeHTTP(w, req.WithContext(newCtx)) + }) +}