Skip to content

Commit

Permalink
Properly support async instance creation
Browse files Browse the repository at this point in the history
  • Loading branch information
Xiwen Cheng authored and djvdorp committed Apr 12, 2016
1 parent 7902104 commit 5df1edc
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 2 deletions.
42 changes: 40 additions & 2 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,18 @@ import (
"encoding/json"
"io/ioutil"
"net/http"
"reflect"
)

type Response struct {
Description string `json:"description"`
}

type ErrorResponse struct {
Error string `json:"error"`
Description string `json:"description"`
}

type Operation struct {
State string
Description string
Expand Down Expand Up @@ -70,6 +76,19 @@ func CreateInstance(p martini.Params, req *http.Request, r render.Render, broker
// Get the correct database logic depending on the type of plan. (shared vs dedicated)
adapter, _ := s.InitializeAdapter(plan, brokerDb)

shared := reflect.TypeOf(adapter) != reflect.TypeOf(&(DedicatedDBAdapter{}))

acceptsIncomplete := false
if req.URL.Query().Get("accepts_incomplete") == "true" {
acceptsIncomplete = true
}

if !shared && !acceptsIncomplete {
// UNPROCESSABLE_ENTITY
r.JSON(422, ErrorResponse{Error: "AsyncRequired", Description: "This service plan requires client support for asynchronous service operations." })
return
}

err := instance.Init(
p["id"],
sr.OrganizationGuid,
Expand Down Expand Up @@ -108,7 +127,26 @@ func CreateInstance(p martini.Params, req *http.Request, r render.Render, broker
}
brokerDb.Save(&instance)

r.JSON(http.StatusCreated, Response{"The instance was created"})
if shared {
r.JSON(http.StatusCreated, Response{"The instance was created"})
} else {
r.JSON(http.StatusAccepted, Response{"The instance is being created asynchronously"})
}
}

func LastOperationInstance(p martini.Params, req *http.Request, r render.Render, brokerDb *gorm.DB, s *Settings, catalog *Catalog) {
instance := Instance{}

brokerDb.Where("uuid = ?", p["id"]).First(&instance)

if instance.Id <= 0 {
r.JSON(http.StatusNotFound, Response{"Requested instance id cannot be found"})
return
}
plan := catalog.fetchPlan(instance.ServiceId, instance.PlanId)
adapter, _ := s.InitializeAdapter(plan, brokerDb)
status, _ := adapter.GetDBStatus(&instance)
r.JSON(http.StatusOK, status)
}

// BindInstance
Expand All @@ -124,7 +162,7 @@ func BindInstance(p martini.Params, r render.Render, brokerDb *gorm.DB, s *Setti

brokerDb.Where("uuid = ?", p["instance_id"]).First(&instance)
if instance.Id == 0 {
r.JSON(404, Response{"Instance not found"})
r.JSON(http.StatusNotFound, Response{"Instance not found"})
return
}
password, err := instance.GetPassword(s.EncryptionKey)
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ func App(settings *Settings, DB *gorm.DB) *martini.ClassicMartini {

// Create the service instance (cf create-service-instance)
m.Put("/v2/service_instances/:id", CreateInstance)
// Last operation state used by async service instance creation
m.Get("/v2/service_instances/:id/last_operation", LastOperationInstance)

// Bind the service to app (cf bind-service)
m.Put("/v2/service_instances/:instance_id/service_bindings/:id", BindInstance)
Expand Down
51 changes: 51 additions & 0 deletions rds.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ const (
InstanceNotGone // 4
)

type DatabaseStatus struct {
State string `json:"state"`
Description string `json:"description"`
}

type DBAdapter interface {
CreateDB(i *Instance, password string) (DBInstanceState, error)
GetDBStatus(i *Instance) (DatabaseStatus, error)
BindDBToApp(i *Instance, password string) (map[string]string, error)
DeleteDB(i *Instance) (DBInstanceState, error)
}
Expand All @@ -40,6 +46,11 @@ func (d *MockDBAdapter) CreateDB(i *Instance, password string) (DBInstanceState,
return InstanceReady, nil
}

func (d *MockDBAdapter) GetDBStatus(i *Instance) (DatabaseStatus, error) {
// TODO
return DatabaseStatus{}, nil
}

func (d *MockDBAdapter) BindDBToApp(i *Instance, password string) (map[string]string, error) {
// TODO
return i.GetCredentials(password)
Expand Down Expand Up @@ -71,6 +82,19 @@ func (d *SharedDBAdapter) CreateDB(i *Instance, password string) (DBInstanceStat
return InstanceReady, nil
}

func (d *SharedDBAdapter) GetDBStatus(i *Instance) (DatabaseStatus, error) {
rows, err := d.SharedDbConn.DB().Query(fmt.Sprintf("SELECT datname FROM pg_database WHERE datname='%s';", i.Database))
result := DatabaseStatus{}
if rows.Next() {
result.State = "succeeded"
result.Description = "Creation completed"
} else {
result.State = "failed"
result.Description = "Unknown"
}
return result, err
}

func (d *SharedDBAdapter) BindDBToApp(i *Instance, password string) (map[string]string, error) {
return i.GetCredentials(password)
}
Expand Down Expand Up @@ -138,6 +162,33 @@ func (d *DedicatedDBAdapter) CreateDB(i *Instance, password string) (DBInstanceS
}
}

func (d *DedicatedDBAdapter) GetDBStatus(i *Instance) (DatabaseStatus, error) {
svc := rds.New(&aws.Config{Region: "eu-west-1"})
request := &rds.DescribeDBInstancesInput{
DBInstanceIdentifier: &i.Database,
}
result, err := svc.DescribeDBInstances(request)
status := DatabaseStatus{
State: "in progress",
Description: "Failed to retrieve state",
}
if yes := d.DidAwsCallSucceed(err); yes && len(result.DBInstances) > 0 {
databaseInstance := result.DBInstances[0]
switch *(databaseInstance.DBInstanceStatus) {
case "failed":
case "incompatible-parameters":
status.State = "failed"
status.Description = "Failed to create instance"
case "available":
status.State = "succeeded"
status.Description = "Instance is ready"
default:
status.Description = *(databaseInstance.DBInstanceStatus)
}
}
return status, err
}

func (d *DedicatedDBAdapter) BindDBToApp(i *Instance, password string) (map[string]string, error) {
// First, we need to check if the instance is up and available before binding.
// Only search for details if the instance was not indicated as ready.
Expand Down

0 comments on commit 5df1edc

Please sign in to comment.