Skip to content

Commit 9ed104e

Browse files
authored
Merge pull request #56 from liorokman/osb214-for-merge
Add missing pieces for supporting OSB 2.14
2 parents c7734df + d378e48 commit 9ed104e

9 files changed

Lines changed: 184 additions & 4 deletions

File tree

api.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const (
3232
deprovisionLogKey = "deprovision"
3333
bindLogKey = "bind"
3434
getBindLogKey = "getBinding"
35+
getInstanceLogKey = "getInstance"
3536
unbindLogKey = "unbind"
3637
updateLogKey = "update"
3738
lastOperationLogKey = "lastOperation"
@@ -60,6 +61,7 @@ const (
6061
planIdMissingKey = "plan-id-missing"
6162
invalidServiceID = "invalid-service-id"
6263
invalidPlanID = "invalid-plan-id"
64+
concurrentAccessKey = "get-instance-during-update"
6365
)
6466

6567
var (
@@ -84,6 +86,7 @@ func AttachRoutes(router *mux.Router, serviceBroker ServiceBroker, logger lager.
8486
handler := serviceBrokerHandler{serviceBroker: serviceBroker, logger: logger}
8587
router.HandleFunc("/v2/catalog", handler.catalog).Methods("GET")
8688

89+
router.HandleFunc("/v2/service_instances/{instance_id}", handler.getInstance).Methods("GET")
8790
router.HandleFunc("/v2/service_instances/{instance_id}", handler.provision).Methods("PUT")
8891
router.HandleFunc("/v2/service_instances/{instance_id}", handler.deprovision).Methods("DELETE")
8992
router.HandleFunc("/v2/service_instances/{instance_id}/last_operation", handler.lastOperation).Methods("GET")
@@ -292,7 +295,7 @@ func (h serviceBrokerHandler) update(w http.ResponseWriter, req *http.Request) {
292295
}
293296
h.respond(w, statusCode, UpdateResponse{
294297
OperationData: updateServiceSpec.OperationData,
295-
DashboardURL: updateServiceSpec.DashboardURL,
298+
DashboardURL: updateServiceSpec.DashboardURL,
296299
})
297300
}
298301

@@ -356,6 +359,54 @@ func (h serviceBrokerHandler) deprovision(w http.ResponseWriter, req *http.Reque
356359
}
357360
}
358361

362+
func (h serviceBrokerHandler) getInstance(w http.ResponseWriter, req *http.Request) {
363+
vars := mux.Vars(req)
364+
instanceID := vars["instance_id"]
365+
366+
logger := h.logger.Session(getInstanceLogKey, lager.Data{
367+
instanceIDLogKey: instanceID,
368+
})
369+
370+
versionCompatibility, err := checkBrokerAPIVersionHdr(req)
371+
if err != nil {
372+
h.respond(w, http.StatusPreconditionFailed, ErrorResponse{
373+
Description: err.Error(),
374+
})
375+
logger.Error(apiVersionInvalidKey, err)
376+
return
377+
}
378+
if versionCompatibility.Minor < 14 {
379+
err = errors.New("get instance endpoint only supported starting with OSB version 2.14")
380+
h.respond(w, http.StatusPreconditionFailed, ErrorResponse{
381+
Description: err.Error(),
382+
})
383+
logger.Error(apiVersionInvalidKey, err)
384+
return
385+
}
386+
387+
instanceDetails, err := h.serviceBroker.GetInstance(req.Context(), instanceID)
388+
if err != nil {
389+
switch err := err.(type) {
390+
case *FailureResponse:
391+
logger.Error(err.LoggerAction(), err)
392+
h.respond(w, err.ValidatedStatusCode(logger), err.ErrorResponse())
393+
default:
394+
logger.Error(unknownErrorKey, err)
395+
h.respond(w, http.StatusInternalServerError, ErrorResponse{
396+
Description: err.Error(),
397+
})
398+
}
399+
return
400+
}
401+
402+
h.respond(w, http.StatusOK, GetInstanceResponse{
403+
ServiceID: instanceDetails.ServiceID,
404+
PlanID: instanceDetails.PlanID,
405+
DashboardURL: instanceDetails.DashboardURL,
406+
Parameters: instanceDetails.Parameters,
407+
})
408+
}
409+
359410
func (h serviceBrokerHandler) getBinding(w http.ResponseWriter, req *http.Request) {
360411
vars := mux.Vars(req)
361412
instanceID := vars["instance_id"]

api_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,11 @@ var _ = Describe("Service Broker API", func() {
182182
makeRequest("GET", "/v2/service_instances/instance-id/last_operation", "{}")
183183
Expect(fakeServiceBroker.ReceivedContext).To(BeTrue())
184184
})
185+
186+
Specify("a get instance operation endpoint which passes the request context to the broker", func() {
187+
makeRequest("GET", "/v2/service_instances/instance-id", "{}")
188+
Expect(fakeServiceBroker.ReceivedContext).To(BeTrue())
189+
})
185190
})
186191

187192
Describe("authentication", func() {
@@ -297,6 +302,19 @@ var _ = Describe("Service Broker API", func() {
297302
})
298303

299304
Describe("instance lifecycle endpoint", func() {
305+
makeGetInstanceRequest := func(instanceID string) *testflight.Response {
306+
response := &testflight.Response{}
307+
testflight.WithServer(brokerAPI, func(r *testflight.Requester) {
308+
path := fmt.Sprintf("/v2/service_instances/%s", instanceID)
309+
request, err := http.NewRequest("GET", path, strings.NewReader(""))
310+
Expect(err).NotTo(HaveOccurred())
311+
request.Header.Add("X-Broker-API-Version", apiVersion)
312+
request.SetBasicAuth("username", "password")
313+
response = r.Do(request)
314+
})
315+
return response
316+
}
317+
300318
makeInstanceDeprovisioningRequestFull := func(instanceID, serviceID, planID, queryString string) *testflight.Response {
301319
response := &testflight.Response{}
302320
testflight.WithServer(brokerAPI, func(r *testflight.Requester) {
@@ -348,6 +366,15 @@ var _ = Describe("Service Broker API", func() {
348366
Expect(fakeServiceBroker.ProvisionedInstanceIDs).To(ContainElement(instanceID))
349367
})
350368

369+
It("calls GetInstance on the service broker with the instance id", func() {
370+
makeInstanceProvisioningRequest(instanceID, provisionDetails, "")
371+
Expect(fakeServiceBroker.ProvisionedInstanceIDs).To(ContainElement(instanceID))
372+
fakeServiceBroker.DashboardURL = "https://example.com/dashboard/some-instance"
373+
resp := makeGetInstanceRequest(instanceID)
374+
Expect(fakeServiceBroker.GetInstanceIDs).To(ContainElement(instanceID))
375+
Expect(resp.Body).To(MatchJSON(fixture("get_instance.json")))
376+
})
377+
351378
Context("when the broker returns some operation data", func() {
352379
BeforeEach(func() {
353380
fakeServiceBroker = &fakes.FakeServiceBroker{
@@ -1218,6 +1245,60 @@ var _ = Describe("Service Broker API", func() {
12181245
})
12191246
})
12201247
})
1248+
1249+
Describe("getting instance", func() {
1250+
It("returns the appropriate status code when it fails with a known error", func() {
1251+
fakeServiceBroker.GetInstanceError = brokerapi.NewFailureResponse(errors.New("some error"), http.StatusUnprocessableEntity, "fire")
1252+
1253+
response := makeGetInstanceRequest("instance-id")
1254+
1255+
Expect(response.StatusCode).To(Equal(http.StatusUnprocessableEntity))
1256+
Expect(lastLogLine().Message).To(ContainSubstring("broker-api.getInstance.fire"))
1257+
Expect(lastLogLine().Data["error"]).To(ContainSubstring("some error"))
1258+
})
1259+
1260+
It("returns 500 when it fails with an unknown error", func() {
1261+
fakeServiceBroker.GetInstanceError = errors.New("failed to get instance")
1262+
1263+
response := makeGetInstanceRequest("instance-id")
1264+
1265+
Expect(response.StatusCode).To(Equal(500))
1266+
Expect(lastLogLine().Message).To(ContainSubstring("broker-api.getInstance.unknown-error"))
1267+
Expect(lastLogLine().Data["error"]).To(ContainSubstring("failed to get instance"))
1268+
})
1269+
1270+
Context("the request is malformed", func() {
1271+
It("missing header X-Broker-API-Version", func() {
1272+
apiVersion = ""
1273+
response := makeGetInstanceRequest("instance-id")
1274+
Expect(response.StatusCode).To(Equal(412))
1275+
Expect(lastLogLine().Message).To(ContainSubstring(".getInstance.broker-api-version-invalid"))
1276+
Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header not set"))
1277+
})
1278+
1279+
It("has wrong version of API", func() {
1280+
apiVersion = "1.1"
1281+
response := makeGetInstanceRequest("instance-id")
1282+
Expect(response.StatusCode).To(Equal(412))
1283+
Expect(lastLogLine().Message).To(ContainSubstring(".getInstance.broker-api-version-invalid"))
1284+
Expect(lastLogLine().Data["error"]).To(ContainSubstring("X-Broker-API-Version Header must be 2.x"))
1285+
})
1286+
1287+
It("is using api version < 2.14", func() {
1288+
apiVersion = "2.13"
1289+
response := makeGetInstanceRequest("instance-id")
1290+
Expect(response.StatusCode).To(Equal(412))
1291+
1292+
Expect(lastLogLine().Message).To(ContainSubstring(".getInstance.broker-api-version-invalid"))
1293+
Expect(lastLogLine().Data["error"]).To(ContainSubstring("get instance endpoint only supported starting with OSB version 2.14"))
1294+
})
1295+
1296+
It("missing instance-id", func() {
1297+
response := makeGetInstanceRequest("")
1298+
Expect(response.StatusCode).To(Equal(404))
1299+
})
1300+
})
1301+
})
12211302
})
12221303

12231304
Describe("binding lifecycle endpoint", func() {

catalog_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ import (
2020
"reflect"
2121
"sync"
2222

23-
"github.com/pivotal-cf/brokerapi"
2423
. "github.com/onsi/ginkgo"
2524
. "github.com/onsi/gomega"
25+
"github.com/pivotal-cf/brokerapi"
2626
)
2727

2828
var _ = Describe("Catalog", func() {

failure_response_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ var _ = Describe("FailureResponse", func() {
6565
Expect(newError.Error()).To(Equal("my error message and some more details"))
6666
Expect(newError.ValidatedStatusCode(nil)).To(Equal(http.StatusForbidden))
6767
Expect(newError.LoggerAction()).To(Equal(failureResponse.LoggerAction()))
68-
68+
6969
errorResponse, typeCast := newError.ErrorResponse().(brokerapi.ErrorResponse)
7070
Expect(typeCast).To(BeTrue())
7171
Expect(errorResponse.Error).To(Equal("some-key"))

fakes/fake_service_broker.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ type FakeServiceBroker struct {
1515
ProvisionedInstanceIDs []string
1616
DeprovisionedInstanceIDs []string
1717
UpdatedInstanceIDs []string
18+
GetInstanceIDs []string
1819

1920
BoundInstanceIDs []string
2021
BoundBindingIDs []string
@@ -33,6 +34,7 @@ type FakeServiceBroker struct {
3334
DeprovisionError error
3435
LastOperationError error
3536
UpdateError error
37+
GetInstanceError error
3638

3739
BrokerCalled bool
3840
LastOperationState brokerapi.LastOperationState
@@ -232,6 +234,24 @@ func (fakeBroker *FakeServiceBroker) Update(context context.Context, instanceID
232234
return brokerapi.UpdateServiceSpec{IsAsync: fakeBroker.ShouldReturnAsync, OperationData: fakeBroker.OperationDataToReturn, DashboardURL: fakeBroker.DashboardURL}, nil
233235
}
234236

237+
func (fakeBroker *FakeServiceBroker) GetInstance(context context.Context, instanceID string) (brokerapi.GetInstanceDetailsSpec, error) {
238+
fakeBroker.BrokerCalled = true
239+
240+
if val, ok := context.Value("test_context").(bool); ok {
241+
fakeBroker.ReceivedContext = val
242+
}
243+
244+
fakeBroker.GetInstanceIDs = append(fakeBroker.GetInstanceIDs, instanceID)
245+
return brokerapi.GetInstanceDetailsSpec{
246+
ServiceID: fakeBroker.ServiceID,
247+
PlanID: fakeBroker.PlanID,
248+
DashboardURL: fakeBroker.DashboardURL,
249+
Parameters: map[string]interface{}{
250+
"param1": "value1",
251+
},
252+
}, fakeBroker.GetInstanceError
253+
}
254+
235255
func (fakeBroker *FakeServiceBroker) Deprovision(context context.Context, instanceID string, details brokerapi.DeprovisionDetails, asyncAllowed bool) (brokerapi.DeprovisionServiceSpec, error) {
236256
fakeBroker.BrokerCalled = true
237257

fixtures/get_instance.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"service_id" : "0A789746-596F-4CEA-BFAC-A0795DA056E3",
3+
"plan_id" : "plan-id",
4+
"dashboard_url" : "https://example.com/dashboard/some-instance",
5+
"parameters": {
6+
"param1" : "value1"
7+
}
8+
}

response.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ type ProvisioningResponse struct {
3131
OperationData string `json:"operation,omitempty"`
3232
}
3333

34+
type GetInstanceResponse struct {
35+
ServiceID string `json:"service_id"`
36+
PlanID string `json:"plan_id"`
37+
DashboardURL string `json:"dashboard_url,omitempty"`
38+
Parameters interface{} `json:"parameters,omitempty"`
39+
}
40+
3441
type UpdateResponse struct {
3542
DashboardURL string `json:"dashboard_url,omitempty"`
3643
OperationData string `json:"operation,omitempty"`

response_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ package brokerapi_test
1818
import (
1919
"encoding/json"
2020

21-
"github.com/pivotal-cf/brokerapi"
2221
. "github.com/onsi/ginkgo"
2322
. "github.com/onsi/gomega"
23+
"github.com/pivotal-cf/brokerapi"
2424
)
2525

2626
var _ = Describe("Catalog Response", func() {

service_broker.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type ServiceBroker interface {
2727

2828
Provision(ctx context.Context, instanceID string, details ProvisionDetails, asyncAllowed bool) (ProvisionedServiceSpec, error)
2929
Deprovision(ctx context.Context, instanceID string, details DeprovisionDetails, asyncAllowed bool) (DeprovisionServiceSpec, error)
30+
GetInstance(ctx context.Context, instanceID string) (GetInstanceDetailsSpec, error)
3031

3132
Bind(ctx context.Context, instanceID, bindingID string, details BindDetails, asyncAllowed bool) (Binding, error)
3233
Unbind(ctx context.Context, instanceID, bindingID string, details UnbindDetails, asyncAllowed bool) (UnbindSpec, error)
@@ -81,6 +82,13 @@ type ProvisionedServiceSpec struct {
8182
OperationData string
8283
}
8384

85+
type GetInstanceDetailsSpec struct {
86+
ServiceID string `json:"service_id"`
87+
PlanID string `json:"plan_id"`
88+
DashboardURL string `json:"dashboard_url"`
89+
Parameters interface{} `json:"parameters"`
90+
}
91+
8492
type UnbindSpec struct {
8593
IsAsync bool
8694
OperationData string
@@ -200,6 +208,7 @@ const (
200208
planChangeUnsupportedMsg = "The requested plan migration cannot be performed"
201209
rawInvalidParamsMsg = "The format of the parameters is not valid JSON"
202210
appGuidMissingMsg = "app_guid is a required field but was not provided"
211+
concurrentInstanceAccessMsg = "instance is being updated and cannot be retrieved"
203212
)
204213

205214
var (
@@ -245,4 +254,8 @@ var (
245254

246255
ErrPlanQuotaExceeded = errors.New(servicePlanQuotaExceededMsg)
247256
ErrServiceQuotaExceeded = errors.New(serviceQuotaExceededMsg)
257+
258+
ErrConcurrentInstanceAccess = NewFailureResponseBuilder(
259+
errors.New(concurrentInstanceAccessMsg), http.StatusUnprocessableEntity, concurrentAccessKey,
260+
).WithErrorKey("ConcurrencyError")
248261
)

0 commit comments

Comments
 (0)