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

Update schema for instance-update #444

Merged
merged 37 commits into from
Oct 20, 2017
Merged

Update schema for instance-update #444

merged 37 commits into from
Oct 20, 2017

Conversation

jmontleon
Copy link
Contributor

No description provided.

@jmontleon jmontleon changed the base branch from instance-update to master September 26, 2017 18:39
@jmontleon jmontleon changed the title Update schema for instance-update [WIP][DO NOT MERGE]Update schema for instance-update Sep 26, 2017
@shawn-hurley shawn-hurley added do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. feature labels Sep 29, 2017
@jmontleon jmontleon changed the title [WIP][DO NOT MERGE]Update schema for instance-update Update schema for instance-update Oct 3, 2017
@jmontleon
Copy link
Contributor Author

Update requests can be performed with asbcli as well, but for sake of demonstrating bad input"

## rhscl-postgresql-apb - no plan specified, continues with current plan (dev)
$ curl -k -XPATCH -H "Authorization: Bearer $(oc whoami -t)" -H 'Content-Type: application/json'  https://asb-1338-ansible-service-broker.172.18.0.1.nip.io/ansible-service-broker/v2/service_instances/6119f273-5ef6-4899-89b0-bd7ad5c2e05a -d '{}'
{}

## rhscl-postgresql-apb - prod plan specified with current plan dev
$ curl -k -XPATCH -H "Authorization: Bearer $(oc whoami -t)" -H 'Content-Type: application/json'  https://asb-1338-ansible-service-broker.172.18.0.1.nip.io/ansible-service-broker/v2/service_instances/6119f273-5ef6-4899-89b0-bd7ad5c2e05a -d '{"plan_id": "prod"}'
{}

## rhscl-postgresql-apb - trying to update to a non existent plan
$ curl -k -XPATCH -H "Authorization: Bearer $(oc whoami -t)" -H 'Content-Type: application/json'  https://asb-1338-ansible-service-broker.172.18.0.1.nip.io/ansible-service-broker/v2/service_instances/6119f273-5ef6-4899-89b0-bd7ad5c2e05a -d '{"plan_id": "blablabla"}'
{
  "description": "plan not found"
}

## rhscl-postgresql-apb - trying to update from prod to dev (not possible)
$ curl -k -XPATCH -H "Authorization: Bearer $(oc whoami -t)" -H 'Content-Type: application/json'  https://asb-1338-ansible-service-broker.172.18.0.1.nip.io/ansible-service-broker/v2/service_instances/6119f273-5ef6-4899-89b0-bd7ad5c2e05a -d '{"plan_id": "dev"}'
{
  "description": "plan update not possible"
}

## mediawiki123-apb - new parameters specified:
$ curl -k -XPATCH -H "Authorization: Bearer $(oc whoami -t)" -H 'Content-Type: application/json'  https://asb-1338-ansible-service-broker.172.18.0.1.nip.io/ansible-service-broker/v2/service_instances/ef0efd9e-6982-44da-a600-1e05e2a1cca1 -d '{"parameters": {"mediawiki_site_name": "wackawacka"}}'
{}

## mediawiki123-apb - new plan_id and parameters specified:
$ curl -k -XPATCH -H "Authorization: Bearer $(oc whoami -t)" -H 'Content-Type: application/json'  https://asb-1338-ansible-service-broker.172.18.0.1.nip.io/ansible-service-broker/v2/service_instances/ef0efd9e-6982-44da-a600-1e05e2a1cca1 -d '{"plan_id": "default", "parameters": {"mediawiki_site_name": "wackawacka"}}'
{}

## mediawiki123-apb - bad plan specified
$ curl -k -XPATCH -H "Authorization: Bearer $(oc whoami -t)" -H 'Content-Type: application/json'  https://asb-1338-ansible-service-broker.172.18.0.1.nip.io/ansible-service-broker/v2/service_instances/ef0efd9e-6982-44da-a600-1e05e2a1cca1 -d '{"plan_id": "sdfgsdgfsdfgsdfg", "parameters": {"mediawiki_site_name": "wackawacka"}}'
{
  "description": "plan not found"
}

## mediawiki123-apb - trying to update a non-updatable parameter
$ curl -k -XPATCH -H "Authorization: Bearer $(oc whoami -t)" -H 'Content-Type: application/json'  https://asb-1338-ansible-service-broker.172.18.0.1.nip.io/ansible-service-broker/v2/service_instances/ef0efd9e-6982-44da-a600-1e05e2a1cca1 -d '{"parameters": {"mediawiki_site_lang": "foo"}}'
{
  "description": "parameter not updatable"
}

## mediawiki123-apb - trying to update a non-existent parameter
$ curl -k -XPATCH -H "Authorization: Bearer $(oc whoami -t)" -H 'Content-Type: application/json'  https://asb-1338-ansible-service-broker.172.18.0.1.nip.io/ansible-service-broker/v2/service_instances/ef0efd9e-6982-44da-a600-1e05e2a1cca1 -d '{"parameters": {"bar": "foo"}}'
{
  "description": "parameter not found"
}

@openshift-ci-robot openshift-ci-robot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Oct 5, 2017
@eriknelson eriknelson added needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. needs-review labels Oct 16, 2017
Copy link
Contributor

@eriknelson eriknelson left a comment

Choose a reason for hiding this comment

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

Overall, looks very good! I have a couple things I'd suggest for refactoring:

  • Update request validations
  • apb.Provision being used for provision and update

Since I'm requesting the change, I would be happy to submit a patch for both if you think the changes make sense @jmontleon .


if err != nil {
switch err {
case broker.ErrorAlreadyProvisioned:
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't a valid error case for Update: https://github.com/openservicebrokerapi/servicebroker/blob/master/spec.md#response-3

I think an httpStatusOK (200) should be returned in the case of a successful synchronous update

si, err := a.dao.GetServiceInstance(instanceUUID.String())
if err != nil {
a.log.Debug("Error retrieving instance")
return nil, ErrorNotFound
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 400 BadRequest results.

* normal async return UpdateResponse

handler returns the following
* synchronous update return 201 created
Copy link
Contributor

Choose a reason for hiding this comment

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

Sync Updates should return 200 httpOK


handler returns the following
* synchronous update return 201 created
* instance already exists with IDENTICAL parameters to existing instance, 200 OK
Copy link
Contributor

Choose a reason for hiding this comment

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

The spec is a little ambiguous with this one:

(200) MUST be returned if the request's changes have been applied.

What if no changes are required because the requested update is identical to the current state of the instance? Trying to get clarification with the SIG.


// If no plan was specified we'll continue with the current plan
if req.PlanID == "" {
req.PlanID = (*si.Parameters)["_apb_plan_id"].(string)
Copy link
Contributor

Choose a reason for hiding this comment

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

It's a good idea to check type assertions for success and deal with surprises explicitly:

var currentPlan string
if currentPlan, ok := (*si.Parameters)["_apb_plan_id"].(string); !ok {
    a.log.Error("No plan_id passed as part of update request, and error occurred while trying to retrieve current plan from service instance")
    return nil, fmt.Errorf("could not get service instance %s current plan", si.ID)
}

// Lock down current plan so nothing changes.
// Quoting spec: "If this field [plan_id] is not present in the request message, then the service broker
// MUST NOT change the plan of the instance as a result of this request.
req.PlanID = currentPlan

Copy link
Contributor

Choose a reason for hiding this comment

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

One other note now that I look at it again: There's a const that should be used instead of the string literal _apb_plan_id for this key: https://github.com/openshift/ansible-service-broker/blob/master/pkg/broker/types.go#L35

So any "_apb_plan_id" should get updated to be planParameterKey instead.

// is set and the job was already started. But I need the token.
a.dao.SetState(instanceUUID.String(), apb.JobState{Token: token, State: apb.StateInProgress})
} else {
// TODO: do we want to do synchronous updating?
Copy link
Contributor

Choose a reason for hiding this comment

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

We'll likely only see async requests for update from the catalog, but we should probably support sync updates. If not, we should explicitly 422.

}

// Run - run the provision job.
func (p *ProvisionJob) Run(token string, msgBuffer chan<- WorkMsg) {
podName, extCreds, err := apb.Provision(p.serviceInstance, p.clusterConfig, p.log)
podName, extCreds, err := apb.Provision(p.task, p.serviceInstance, p.clusterConfig, p.log)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not a huge fan of parameterizing Provision with a couple variants of behavior; it breaks the semantics of the function from a caller's perspective. One thought would be to have two distinct Provision and Update available on the public interface, and behind the scenes they actually do some specific behavior for their case, and call into some shared function.

SchemaRef: schema.SchemaURL,
Type: []schema.PrimitiveType{schema.ObjectType},
Properties: updatableProperties,
Required: updatableRequired,
Copy link
Contributor

Choose a reason for hiding this comment

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

Will this always be the set of required parameters that also are update-able?

@@ -291,6 +302,33 @@ func extractRequired(params []apb.ParameterDescriptor) []string {
return req
}

func extractUpdatable(params []apb.ParameterDescriptor) map[string]*schema.Schema {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 spec transforms look good.

} else if async {
writeDefaultResponse(w, http.StatusAccepted, resp, err)
} else {
writeDefaultResponse(w, http.StatusCreated, resp, err)
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be an http.StatusOK

@eriknelson eriknelson removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Oct 17, 2017
@eriknelson eriknelson removed the do-not-merge DEPRECATED. Indicates that a PR should not merge. Label can only be manually applied/removed. label Oct 17, 2017
@eriknelson
Copy link
Contributor

eriknelson commented Oct 17, 2017

Confirmed I'm able to transition from "dev" to "prod" plans as well as modify postgresql_version from 9.4 -> 9.5. 🎉

ns := instance.Context.Namespace
log.Info("Checking if project %s exists...", ns)
if !projectExists(ns) {
log.Error("Project %s does NOT exist! Cannot provision requested %s", ns, instance.Spec.FQName)
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 should be log.Errorf

}

func projectExists(project string) bool {
_, _, code := runtime.RunCommandWithExitCode("kubectl", "get", "project", project)
Copy link
Contributor

Choose a reason for hiding this comment

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

I would really prefer to not rely on kubectl and use the namespace client-go interface. This is going to help us remove places that we are running commands rather than using the client.

If you feel that we should let this pass through and deal with it during the other refactors I understand that just want to call it out here.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree, this code has been here for some time however and simply got moved over from Provision, so I'd rather address it in another PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

Existing code fix in another PR. 👍


writeDefaultResponse(w, http.StatusOK, resp, err)
// 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.

We must be checking for auto escalate and checking for users access to the namespace. see: https://github.com/openshift/ansible-service-broker/blob/master/pkg/handler/handler.go#L246

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll take a look at this, thanks for pointing it out @shawn-hurley

eriknelson and others added 4 commits October 18, 2017 20:12
* Move to proper Update job/subscribers instead of trying to use
Provision jobs
Full async update support and burst request guard
@openshift-ci-robot openshift-ci-robot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Oct 19, 2017
Copy link
Contributor

@jmrodri jmrodri left a comment

Choose a reason for hiding this comment

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

The OSB spec states:

To enable support for the update of the plan, a service broker MUST declare support per service by including plan_updateable: true in its catalog endpoint.

I think the apb needs to have this field set on its metadata. I expect the catalog to look at that before calling update.

We have PlanUpdatable on the struct, but we're not looking at it. I also think the broker should look at it before performing an update.

https://github.com/openshift/ansible-service-broker/blob/master/pkg/broker/types.go#L69

func Provision(
instance *ServiceInstance,
clusterConfig ClusterConfig, log *logging.Logger,
clusterConfig ClusterConfig,
log *logging.Logger,
Copy link
Contributor

Choose a reason for hiding this comment

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

😆

}

func projectExists(project string) bool {
_, _, code := runtime.RunCommandWithExitCode("kubectl", "get", "project", project)
Copy link
Contributor

Choose a reason for hiding this comment

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

Existing code fix in another PR. 👍

executionMethodUpdate executionMethod = "update"
)

func provisionOrUpdate(
Copy link
Contributor

Choose a reason for hiding this comment

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

Good refactoring. I also like the comment in provision and update which is helpful.

if err != nil {
app.log.Errorf("Failed to attach subscriber to WorkEngine: %s", err.Error())
os.Exit(1)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to the new subscriber

@@ -71,7 +81,7 @@ type Broker interface {
Bootstrap() (*BootstrapResponse, error)
Catalog() (*CatalogResponse, error)
Provision(uuid.UUID, *ProvisionRequest, bool) (*ProvisionResponse, error)
Update(uuid.UUID, *UpdateRequest) (*UpdateResponse, error)
Update(uuid.UUID, *UpdateRequest, bool) (*UpdateResponse, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

good catch for async

} else if rs.State.Method == apb.JobMethodUpdate {
job = NewUpdateJob(instance, a.clusterConfig, a.log)
topic = UpdateTopic
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

Logic seems correct.

"Attempted to recover job %s, but found an unrecognized "+
"MethodType: %s, skipping...",
rs.State.Token, rs.State.Method,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

You really want me to make the line length 80 characters :)

}

// Retrieve from/to plans by name, else respond with appropriate error
if fromPlan = spec.GetPlan(fromPlanName); fromPlan == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wasn't aware this syntax was legal :)

@spadgett
Copy link
Member

I think the apb needs to have this field set on its metadata. I expect the catalog to look at that before calling update.

The web console will only allow you to change plans if planUpdatable is true.

@jmrodri
Copy link
Contributor

jmrodri commented Oct 19, 2017

@spadgett thanks for the confirmation that's what I suspected based on the spec. So we'll fix it.

@jmontleon jmontleon merged commit 439ce3e into openshift:master Oct 20, 2017
jianzhangbjz pushed a commit to jianzhangbjz/ansible-service-broker that referenced this pull request May 17, 2018
* asbcli update to support PATCH request

* Add rudimentary plan update support, catch asbcli exception

* merge provision and update code

* add comments, clean up logic, add return errors

* Fix schema test

* fix style errors

* customize yaml serializer for style fix

* Fix test

* Instance update refactoring

* Abstract out validations
* Separate shared provision/update logic and wrap with public funcs

* Lookup plan names by ID for update

* asbcli update to support PATCH request

* Add rudimentary plan update support, catch asbcli exception

* merge provision and update code

* add comments, clean up logic, add return errors

* Fix schema test

* fix style errors

* customize yaml serializer for style fix

* Fix test

* Instance update refactoring

* Abstract out validations
* Separate shared provision/update logic and wrap with public funcs

* fixes

* more fixes

* Use Errorf

* Perform auto escalation check for update

* Also added Context for update requests

* Trigger metrics for both provision and update

* Add metrics import

* Return correct status code for sync update

* Add some logging

* Implement distinct async Update support

* Move to proper Update job/subscribers instead of trying to use
Provision jobs

* Prevent multiple update pods from spawning

* set plan_updateable, fix Lint errors

* style/syntax fixes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature needs-review size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants