-
Notifications
You must be signed in to change notification settings - Fork 84
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
Conversation
Update requests can be performed with asbcli as well, but for sake of demonstrating bad input"
|
There was a problem hiding this 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 .
pkg/handler/handler.go
Outdated
|
||
if err != nil { | ||
switch err { | ||
case broker.ErrorAlreadyProvisioned: |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
pkg/broker/broker.go
Outdated
|
||
// If no plan was specified we'll continue with the current plan | ||
if req.PlanID == "" { | ||
req.PlanID = (*si.Parameters)["_apb_plan_id"].(string) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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? |
There was a problem hiding this comment.
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.
pkg/broker/provision_job.go
Outdated
} | ||
|
||
// 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) |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 spec transforms look good.
pkg/handler/handler.go
Outdated
} else if async { | ||
writeDefaultResponse(w, http.StatusAccepted, resp, err) | ||
} else { | ||
writeDefaultResponse(w, http.StatusCreated, resp, err) |
There was a problem hiding this comment.
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
* Abstract out validations * Separate shared provision/update logic and wrap with public funcs
Instance update refactoring
Confirmed I'm able to transition from "dev" to "prod" plans as well as modify |
pkg/apb/provision_or_update.go
Outdated
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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")) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
* Also added Context for update requests
Check autoescalate and user privileges for update
* Move to proper Update job/subscribers instead of trying to use Provision jobs
Full async update support and burst request guard
There was a problem hiding this 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, |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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) | ||
} |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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, | ||
) |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 :)
The web console will only allow you to change plans if planUpdatable is true. |
@spadgett thanks for the confirmation that's what I suspected based on the spec. So we'll fix it. |
* 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
No description provided.