-
Notifications
You must be signed in to change notification settings - Fork 91
Implement PATCH /v3/service_instances for managed services #4167
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
base: main
Are you sure you want to change the base?
Implement PATCH /v3/service_instances for managed services #4167
Conversation
danail-branekov
left a comment
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.
Looks promising. The most important bits of my comments are on the managed instance controller
| type PatchManagedSIMessage struct { | ||
| GUID string | ||
| SpaceGUID string | ||
| PlanGUID 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.
I wonder whether PlanGUID should not be a pointer? A patch may want to update the metadata, but not the plan
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.
Updated
| log.Error(err, "failed to provision service instance") | ||
| return ctrl.Result{}, fmt.Errorf("failed to provision service instance: %w", err) | ||
| } | ||
| if serviceInstance.Status.ObservedGeneration == 0 { |
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.
If I get the intention of this check correctly, the idea here is to perform provision when the CFServiceInstance has been just created, otherwise proceed to update.
The generation might drift because of whatever updates (maybe a new label appeared) that may occur while provisioning is already running.
Instead of relying on the observed generation, I would suggest to add PlanGUID to CFServiceInstance.Status. Then the plan guid on the spec would have the semantics of "desired plan", while the plan guid on the status would mean "actual plan". Then if both differ, the controller should perform a plan update. Once the update succeed, the controller should set the new plan guid on the status, thus "writing down" that this is already the actual plan.
Also, it is worth mentioning that once the instance is ready (isReady function returns true), the controller would stop listening for updates. Therefore you should change the function to also check whether the "desired plan" matches the "actual plan"
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 changed the logic here as you proposed to use Status. Please check it.
|
|
||
| if lastOpResponse.State == "failed" { | ||
| meta.SetStatusCondition(&serviceInstance.Status.Conditions, metav1.Condition{ | ||
| Type: korifiv1alpha1.ProvisioningFailedCondition, |
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 you should set UpdateFailedCondition 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.
Thank you for the catch.
| if err != nil { | ||
| return UpdateResponse{}, fmt.Errorf("update request failed: %w", err) | ||
| } | ||
| if statusCode == http.StatusBadRequest || statusCode == http.StatusConflict || statusCode == http.StatusUnprocessableEntity { |
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.
According to the osbapi spec, an update operation cannot result into StatusConflict
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.
Fixed.
bdb5833 to
6651e36
Compare
|
As an updated summary - I applied all suggestions. Maybe used not ideal naming 😁 |
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.
See my comments below, significant bits are on the controller. I think you are going in the right direction.
A general style comment - we usually put a blank line between ginkgo blocks - Describe, When, It, etc to visually separate them. If you could keep that convention in the PR, that would be great.
|
|
||
| It("returns an error", func() { | ||
| expectUnprocessableEntityError("nope") | ||
| When("getting the service instance fails with not found", func() { |
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.
Style: By introducing a separate context for managed services, the describe has now the floowing structure:
Describe("PATCH /v3/service_instances/:guid", func() {
When("updating a user provided service instance", func() { // all existing tests go here
When("updating a managed service instance", func() {
What I see as an issue here is that error cases (such as service instance repository returning an error) are checked in the upsi context, though being irrelevant to whether the service is upsi or not. As a matter of fact, the only difference between uspi and managed is the repository method being invoked.
Therefore, how about the following structure:
Describe("PATCH /v3/service_instances/:guid", func() {
// existing (upsi) tests still here, i.e. this is the "default" case
When("the service instance is managed", func() {
It("patches the managed service instance", func() {
It("returns HTTP 202 Accepted response", func() {
When("patching the managed service instances fails", func() {
It("returns the error", func() {
})
This should also reduce the diff as indentation white spaces would not be there
|
|
||
| func (p ServiceInstancePatch) ToManagedSIPatchMessage(spaceGUID, appGUID string) repositories.PatchManagedSIMessage { | ||
| var planGUID *string | ||
| if p.Relationships.ServicePlan.Data.GUID != "" { |
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.
If I get the intention correctly, you want to check whether the plan is being updated. In that case I think you should be checking on p.Relationships.ServicePlan != nil rather than p.Relationships.ServicePlan.Data.GUID != "". If p.Relationships.ServicePlan is set, then the validation should ensure that p.Relationships.ServicePlan.Data.GUID is not empty.
| return cfServiceInstanceToRecord(*cfServiceInstance), nil | ||
| } | ||
|
|
||
| func (r *ServiceInstanceRepo) PatchManagedServiceInstance(ctx context.Context, authInfo authorization.Info, message PatchManagedSIMessage) (ServiceInstanceRecord, 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.
There are no tests for this new method. They would be pretty much the same as the ones for UPSI but I cannot think a proper way to avoid the duplication
| //+kubebuilder:validation:Optional | ||
| MaintenanceInfo MaintenanceInfo `json:"maintenanceInfo"` | ||
|
|
||
| // The service instance actual plan |
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 add a note that this only makes sense for managed services (see MaintenanceInfo)
|
|
||
| if isFailed(serviceInstance) { | ||
| return ctrl.Result{}, k8s.NewNotReadyError().WithReason("ProvisioningFailed").WithNoRequeue() | ||
| return ctrl.Result{}, k8s.NewNotReadyError().WithReason("ReconciliationFailed").WithNoRequeue() |
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 is not that reconciliation failed per se, it is just that the service instance is in a failed state. Maybe ServiceInstanceFailed would be a proper reason 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.
This file should not be affected by the PR at all. While I agree that removing those new lines makes sense, the generated diff is a high price to pay for such a small improvement.
| return serviceInstance.GUID | ||
| } | ||
|
|
||
| func createManagedServiceInstance(brokerGUID, spaceGUID string, name string) 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.
Is there any particular reason for the name parameter? All references supply an inline-generated guid, therefore they do not seem to care about it. Let's not overload the method signature if that is the case.
| Patch("/v3/service_instances/" + serviceInstanceGUID) | ||
| }) | ||
|
|
||
| When("updating a user-provided service instance", func() { |
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.
Test structure: The describe has two sibling contexts and there is no default case. I would recommend having just introducing the serviceInstanceGUID variable at the root of the context and default it to the upsiGUID in the BeforeEach.
Then, have a context When("the service instance is managed, where you create the managed instance and set serviceInstanceGUID to its guid. Such an approach would also minimise the diff.
| catalogResp, err := adminClient.R().SetResult(&plansResp).Get("/v3/service_plans?service_broker_guids=" + brokerGUID) | ||
| Expect(err).NotTo(HaveOccurred()) | ||
| Expect(catalogResp).To(HaveRestyStatusCode(http.StatusOK)) | ||
| Expect(plansResp.Resources).NotTo(BeEmpty()) |
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.
Maybe assert that Resource has length at least 2 (plansResp.Resources[1] is used below)
| Expect(httpErrorService).NotTo(HaveOccurred()) | ||
| Expect(httpRespService).To(HaveRestyStatusCode(http.StatusOK)) | ||
| Expect( | ||
| result.resource.Relationships["service_plan"].Data.GUID, |
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.
How about checking the service_plan relationship in It("updates a managed service"? Every new It slows down the suite execution time so we have to strike a balance
Is there a related GitHub Issue?
The PR is aimed to solve this Issue 4163
What is this change about?
Implementing PATCH /v3/service_instances for managed services
Does this PR introduce a breaking change?
It shouldn't be a breaking change
Acceptance Steps
The PR is still WIP
Tag your pair, your PM, and/or team
danail-branekov