Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async bind feature #625

Merged
merged 26 commits into from
Jan 11, 2018
Merged

Async bind feature #625

merged 26 commits into from
Jan 11, 2018

Conversation

jmrodri
Copy link
Contributor

@jmrodri jmrodri commented Jan 10, 2018

The async bind feature is used to allow binds that need more time to execute than the 60 seconds response time allotted in the Open Service Broker API spec. Async bind will spawn a binding job and return the job token immediately. The catalog will use the last_operation to monitor the state of the running job until either successful completion or a failure.

There are no new dependencies for this feature.

There is a new binding_subscriber to monitor the JobMsgs sent from the new binding_job. The Bind method in the broker was updated to take in the async flag and determine if it should spawn an APB for bind or simply return what was already stored like it did before.

Another set of critical additions was the new getserviceinstance and getbind calls added to the handler. These are to implement the required getters for async bind, particularly the getbind. Prior to async bind, when bind was called we would return the credentials at the end of the request. Since it is now asynchronous, we return the job token as the operation, but we will need a way to get the bind that was created once the job is done.

Aysnc bind is gated by the LaunchApbOnBind broker configuration setting. This MUST be set to True for the async bind to even be an option. Also, accepts_incomplete=true must accompany all PUT requests on the bind endpoint for async to be used. Both of those conditions need to be satisfied before using the async feature.

@openshift-ci-robot openshift-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Jan 10, 2018
@jmrodri jmrodri added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. 3.9 | release-1.1 Kubernetes 1.9 | Openshift 3.9 | Broker release-1.1 needs-review and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jan 10, 2018
@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 10, 2018

I will remove or cleanup some of the debug logging statements. They're still needed as I continue testing.

@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 6db4ec3 on async-bind into ** on master**.

// Run - run the binding job.
func (p *BindingJob) Run(token string, msgBuffer chan<- JobMsg) {
metrics.BindingJobStarted()
var podName string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably don't need to declare extCreds, podName, and err ahead of time because if apb.Bind(p.serviceInstance, p.params) fails in anyway, these variables will still get set.

podName, extCreds, err := ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totally, I copied it from the broker.go where there was a conditional hence the variables being defined before hand. Will fix.

var extCreds *apb.ExtractedCredentials
var err error

log.Debug("BJ: binding job started, calling apb.Bind")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you spell out Bind Job:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will for the statements I plan to keep. Thanks.

@@ -769,8 +770,38 @@ func (a AnsibleBroker) isJobInProgress(instance *apb.ServiceInstance,
return len(methodJobs) > 0, token, nil
}

func (a AnsibleBroker) GetBind(instance apb.ServiceInstance, bindingUUID uuid.UUID) (*BindResponse, error) {

log.Debug("XXX entered GetBind")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is 'XXX' suppose to be substituted for something? Or is this to log that "we're running GetBind"?


log.Debug("XXX entered GetBind")

// this might not be required
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like your honesty. Why might this not be required? Is this to verify that a provision has completed?

var async bool

// ignore the error, if async can't be parsed it will be false
async, _ = strconv.ParseBool(r.FormValue("accepts_incomplete"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we turn on accepts_incomplete if LaunchApbOnBind is enabled and accepts_incomplete is not explicitly set to false? Also, should we have a warning if we have LaunchApbOnBind=true and accepts_incomplete=false ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I'll add a warning.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, accepts_incomplete will be given to us from the service-catalog. We can not change its value once we get it since the caller needs to know how to monitor the last_operation endpoint.

I will add a warning when LaunchApbOnBind=true and accepts_incomplete=false condition occurs. Worst case it means that during synchronous binds we WILL launch the apb, which COULD timeout.

@rthallisey
Copy link
Contributor

@jmrodri nice work. Mostly nits.

@openshift-ci-robot openshift-ci-robot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Jan 10, 2018
Copy link
Contributor

@shawn-hurley shawn-hurley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 change that I think needs to happen

si, err := h.broker.GetServiceInstance(instanceUUID)
if err != nil {
// return 404 or 422
log.Debugf("service instance %v", si)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this needs to return from here with the status code of 404 or 422

@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 06af68d on async-bind into ** on master**.

@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 1a385b0 on async-bind into ** on master**.

@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 11, 2018

[jesusr@speed3 ansible-service-broker{async-bind}]$ INSTANCE_ID=`oc get serviceinstances --all-namespaces -o jsonpath='{.items[*].spec.externalID}{"\n"}'`; echo $INSTANCE_ID                                      
86db03b8-4664-4f29-89a9-2a6870aaef50                
[jesusr@speed3 ansible-service-broker{async-bind}]$ BIND_ID=`uuidgen`; echo $BIND_ID                     
b644b678-da77-41ec-9bd6-fa5780f019b6                
[jesusr@speed3 ansible-service-broker{async-bind}]$ ./bind.sh $INSTANCE_ID $BIND_ID                      
{                                                   
  "operation": "16bf0e98-38d5-4619-a986-51b1c6ca1e0a"                                                    
}                                                   
[jesusr@speed3 ansible-service-broker{async-bind}]$ ./last_operation.sh $INSTANCE_ID 16bf0e98-38d5-4619-a986-51b1c6ca1e0a                                                                                          
{                                                   
  "state": "in progress"                            
}                                                   
[jesusr@speed3 ansible-service-broker{async-bind}]$ ./last_operation.sh $INSTANCE_ID 16bf0e98-38d5-4619-a986-51b1c6ca1e0a                                                                                          
{                                                   
  "state": "succeeded"                              
}                                                   
[jesusr@speed3 ansible-service-broker{async-bind}]$ ./getbind.sh $INSTANCE_ID $BIND_ID                   
{                                                   
  "credentials": {                                  
    "DB_HOST": "postgresql",                        
    "DB_NAME": "ktzdkptahabencn",                   
    "DB_PASSWORD": "RHs1ftbUI7NYGrhne8EZ",          
    "DB_PORT": "5432",                              
    "DB_TYPE": "postgres",                          
    "DB_USER": "ktzdkptahabencn"                    
  }                                                 
}                                                   
[jesusr@speed3 ansible-service-broker{async-bind}]$ ./getinstance.sh $INSTANCE_ID                        
{                                                   
  "service_id": "86db03b8-4664-4f29-89a9-2a6870aaef50",                                                  
  "plan_id": "dev",                                 
  "parameters": {                                   
    "_apb_plan_id": "dev",                          
    "_apb_service_class_id": "1dda1477cace09730bd8ed7a6505607e",                                         
    "_apb_service_instance_id": "86db03b8-4664-4f29-89a9-2a6870aaef50",                                  
    "postgresql_database": "admin",                 
    "postgresql_password": "password",              
    "postgresql_user": "admin",                     
    "postgresql_version": "9.6"                     
  }                                                 
}  

@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 11, 2018

bind.sh used in testing

$ cat bind.sh
INSTANCE_ID=$1
BINDING_ID=$2
PLAN_UUID="7f4a5e35e4af2beb70076e72fab0b7ff"
SERVICE_UUID="dh-postgresql-apb-s964m"

req="{
  \"plan_id\": \"$PLAN_UUID\",
  \"service_id\": \"$SERVICE_UUID\",
  \"context\": \"blog-project\",
  \"app_guid\":\"\",
  \"bind_resource\":{},
  \"parameters\": {}
}"

curl \
    -k \
    -X PUT \
    -H "Authorization: bearer $(oc whoami -t)" \
    -H "Content-type: application/json" \
    -H "Accept: application/json" \
    -H "X-Broker-API-Originating-Identity: " \
    -d "$req" \
    "https://asb-1338-ansible-service-broker.172.17.0.1.nip.io/ansible-service-broker/v2/service_instances/$INSTANCE_ID/service_bindings/$BINDING_ID?accepts_incomplete=true"

@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 11, 2018

last_operation.sh used in testing

$ cat last_operation.sh 
INSTANCE_ID=$1
OPERATION=$2
PLAN_UUID="7f4a5e35e4af2beb70076e72fab0b7ff"
SERVICE_UUID="dh-postgresql-apb-s964m"

curl \
    -k \
    -X GET \
    -H "Authorization: bearer $(oc whoami -t)" \
    -H "Content-type: application/json" \
    -H "Accept: application/json" \
    -H "X-Broker-API-Originating-Identity: " \
    "https://asb-1338-ansible-service-broker.172.17.0.1.nip.io/ansible-service-broker/v2/service_instances/$INSTANCE_ID/last_operation?operation=$OPERATION&service_id=$SERVICE_UUID&plan_id=$PLAN_UUID"

@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 11, 2018

getbind.sh used in testing

$ cat getbind.sh 
INSTANCE_ID=$1
BINDING_ID=$2
PLAN_UUID="7f4a5e35e4af2beb70076e72fab0b7ff"
SERVICE_UUID="dh-postgresql-apb-s964m"

req="{
  \"plan_id\": \"$PLAN_UUID\",
  \"service_id\": \"$SERVICE_UUID\",
  \"context\": \"blog-project\",
  \"app_guid\":\"\",
  \"bind_resource\":{},
  \"parameters\": {}
}"

curl \
    -k \
    -X GET \
    -H "Authorization: bearer $(oc whoami -t)" \
    -H "Content-type: application/json" \
    -H "Accept: application/json" \
    -H "X-Broker-API-Originating-Identity: " \
    "https://asb-1338-ansible-service-broker.172.17.0.1.nip.io/ansible-service-broker/v2/service_instances/$INSTANCE_ID/service_bindings/$BINDING_ID"

@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 11, 2018

getinstance.sh used in testing

$ cat getinstance.sh 
INSTANCE_ID=$1

curl \
    -k \
    -X GET \
    -H "Authorization: bearer $(oc whoami -t)" \
    -H "Content-type: application/json" \
    -H "Accept: application/json" \
    -H "X-Broker-API-Originating-Identity: " \
    "https://asb-1338-ansible-service-broker.172.17.0.1.nip.io/ansible-service-broker/v2/service_instances/$INSTANCE_ID"

@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 0590138 on async-bind into ** on master**.

@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 0590138 on async-bind into ** on master**.

@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 11, 2018

Test setup

I modified catasb my_vars to use my image for the broker:

dockerhub_org: jmrodri
asb_template_url: file:///home/jesusr/dev/src/github.com/openshift/ansible-service-broker/templates/deploy-ansible-service-broker.template.yaml
origin_image_tag: v3.9.0-alpha.0
broker_launch_apb_on_bind: true
broker_auto_escalate: true
broker_image_name: docker.io/jmrodri/origin-ansible-service-broker
broker_tag: "async"
local_oc_client: true

I built my image as follows:

make build-image ORG=jmrodri TAG=async
docker push docker.io/jmrodri/origin-ansible-service-broker:async

You should probably use your OWN organization or just use the image I already pushed up.

@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 11, 2018

TEST

  • bring up broker with async code in it (see above)
  • ensure you have an apb with bind in it: I'm using docker.io/mhrivnak/postgresql-apb. I actually pushed a copy to my own org and configured catasb and broker to use it.

Deploy PostgreSQL APB
Here we will create the service instance we need to create the binding.

  1. Launch openshift web console
  2. create a project i.e. blog-project
  3. click the PostgreSQL (APB)
  4. select Development plan, click Next
  5. select the blog-project
  6. enter password for database, click Next
  7. DO NOT CREATE A BINDING!
  8. Click Create
  9. Click Close
    10 Monitor the deployment to ensure the database has come up.

Create async bind
Here we will create an async bind using the CLI to talk directly to the broker's API. The commands will use scripts that were defined in previous comments.

  1. Get instance uuid from deployed PostgreSQL service
INSTANCE_ID=`oc get serviceinstances --all-namespaces -o jsonpath='{.items[*].spec.externalID}{"\n"}'`; echo $INSTANCE_ID
  1. Generate a new binding uuid
BIND_ID=`uuidgen`; echo $BIND_ID
  1. Kick off an async bind, NOTE: the job id will be returned by this call, it is required for the NEXT call
./bind.sh $INSTANCE_ID $BIND_ID
  1. Check the binding job status. Depending on how long it took you to run this command you may see "in progress" or "succeeded". If you see "in progress" run the command again until you see "succeeded". If you see "failed" I will be hiding in a corner.
./last_operation.sh $INSTANCE_ID 16bf0e98-38d5-replacewithidfrompreviouscall-51b1c6ca1e0a
  1. Call get bind to see the binding created by the async bind job:
./getbind.sh $INSTANCE_ID $BIND_ID
  1. See the service instance as well
./getinstance.sh $INSTANCE_ID

@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 11, 2018

Also consider testing the getters with different ids. Ones that don't exist, invalid ones to see how the broker responds. We are trying to avoid PANICs :)

Copy link
Contributor

@shawn-hurley shawn-hurley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nits but overall LGTM

Token: bmsg.JobToken,
State: apb.StateFailed,
Podname: bmsg.PodName,
Method: apb.JobMethodBind,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we might need a follow-on PR to pass errors back when the last operation is called.

I don't think this needs to be solved in the PR, but want to make a note of it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

})
} else {
json.Unmarshal([]byte(bmsg.Msg), &extCreds)
b.dao.SetState(bmsg.InstanceUUID, apb.JobState{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might want to handle the errors here

Podname: bmsg.PodName,
Method: apb.JobMethodBind,
})
b.dao.SetExtractedCredentials(bmsg.BindingUUID, extCreds)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Especially want to handle the error if the setting of the extracted credentials fail

@@ -219,6 +222,42 @@ func (h handler) catalog(w http.ResponseWriter, r *http.Request, params map[stri
writeDefaultResponse(w, http.StatusOK, resp, err)
}

func (h handler) getinstance(w http.ResponseWriter, r *http.Request, params map[string]string) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: getinstance -> getInstance

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but it's ugly and isn't all lower like the others in handler :) I really wanted to use get_instance :)

switch err {
case broker.ErrorNotFound:
log.Debug("handler: bind not found")
writeResponse(w, http.StatusNotFound, broker.ErrorResponse{Description: err.Error()})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to return after the writeResponse otherwise, you will get multiple writes to the response writer.

log.Debug("handler: something went bad during the getbind")
writeResponse(w, http.StatusBadRequest, broker.ErrorResponse{Description: err.Error()})
}
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this should not be in an else block

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs to be in the else block otherwise we'll write TWO responses out. It's consistent with some of the other calls. Probably a better alternative would be to do a return after the writeResponse in the if block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll add a return and remove the else, I like that better.

@jmrodri
Copy link
Contributor Author

jmrodri commented Jan 11, 2018

unbind.sh script

INSTANCE_ID=$1
BINDING_ID=$2
PLAN_UUID="7f4a5e35e4af2beb70076e72fab0b7ff"

curl \
    -k \
    -X DELETE \
    -H "Authorization: bearer $(oc whoami -t)" \
    -H "Content-type: application/json" \
    -H "Accept: application/json" \
    -H "X-Broker-API-Originating-Identity: " \
     "https://asb-1338-ansible-service-broker.172.17.0.1.nip.io/ansible-service-broker/v2/service_instances/$INSTANCE_ID/service_bindings/$BINDING_ID?accepts_incomplete=true&plan_id=$PLAN_UUID"

@coveralls
Copy link

Coverage Status

Changes Unknown when pulling b2429ef on async-bind into ** on master**.

@coveralls
Copy link

coveralls commented Jan 11, 2018

Coverage Status

Changes Unknown when pulling efd42b3 on async-bind into ** on master**.

@jmrodri jmrodri removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jan 11, 2018
@jmrodri jmrodri removed the request for review from jianzhangbjz January 11, 2018 19:26
@jmrodri jmrodri merged commit eb4db6e into master Jan 11, 2018
@coveralls
Copy link

Coverage Status

Changes Unknown when pulling 7e1592f on async-bind into ** on master**.

@jmrodri jmrodri deleted the async-bind branch January 11, 2018 20:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.9 | release-1.1 Kubernetes 1.9 | Openshift 3.9 | Broker release-1.1 needs-review size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants