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

Clean up instance controller #380

Merged
merged 2 commits into from
Jul 10, 2019
Merged

Conversation

gkleiman
Copy link
Contributor

@gkleiman gkleiman commented Jun 22, 2019

What type of PR is this?

Uncomment only one /kind <> line, hit enter to put that in a new line, and remove leading whitespaces from that line:

/kind cleanup

What this PR does / why we need it:
This PR cleans up and improves the readability of the instances controller. It improves comments, includes some refactoring code, and addresses an existing TODO.

Special notes for your reviewer:
I think that most of the code touched here should actually moved to the Reconcile method.

Does this PR introduce a user-facing change?:

NONE

@gkleiman gkleiman requested a review from kensipe as a code owner June 22, 2019 00:32
@gkleiman gkleiman changed the title Gaston/cleanup instance controller Clean up instance controller Jun 22, 2019
Copy link
Contributor

@zen-dog zen-dog left a comment

Choose a reason for hiding this comment

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

Lgtm, though, I believe there is at least one breaking change with the default plan (see comments)

if !planFound {
// The parameter doesn't have a trigger, try to find the corresponding default plan.
names := []string{"update", "deploy"}
for _, n := range names {
Copy link
Contributor

Choose a reason for hiding this comment

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

Since we're refactoring this code - wdyt about extracting this loop into a separate function:

findPlan := func(names []string) (string, boolean) {
for _, n := range names {
   if plan, found = fv.Spec.Plans[n]; found {
       return plan, found
   }
   return "", false
}

and then calling it here (and above) with:

planName, planFound := findPlan([]string{"update", "deploy"})

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 personally think that trying to understand the code for the first time would have to do an extra context switch to see what findPlan() actually does and then come back.

This would be justified if we'd findPlan() very often, but we only use it twice, so my personal opinion is that at this point extracting it into a separate function would only hurt readability.

My opinion on this matter is not very strong, so if you feel stronger about this, I can totally extract it =).

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 probably also extract it but I don't have strong opinion here :)

planName = n
planFound = true
break
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Wait, previously (line 140 on the left) there is a default fallback planName = "deploy" if no plan was found. This is not the case on the right, correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The original line #140 isn't setting planName=deploy as a default fallback — it's actually only executed if ok == true, which means that deploy was found.

Line #151 might be an attempt to use deploy as a default fallback plan, but if that line is ever executed, ok will be false, so the PlanExecution will never be created.

Copy link
Contributor

Choose a reason for hiding this comment

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

You're right. But since we spent quite some time understanding all the ifs and elses of the logic, I believe we need to extract this portion of code into separate methods and provide a few tests for it (similar to what I did for parameterDifference. Wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To be honest I think that instead going further with the refactoring of the predicate function, I think we should get rid of it and move most of the logic to the Reconcile function, see my writeup on #422 for more details.

Instead of writing low level unit tests for something that we should change soon, I would rather start working on #422 or write a few unit tests to prepare the ground for #422.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

BTW it looks like the original intention was to create a PlanExecution for deploy even if no changes were detected.

@runyontr recently created a PR that includes a change to make this happen: https://github.com/kudobuilder/kudo/pull/367/files#r297890298

planName = "update"

if planFound {
// FIXME: this can be misleading, the plan will only be used if this is the last parameter returned by `parameterDifference`.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// FIXME: this can be misleading, the plan will only be used if this is the last parameter returned by `parameterDifference`.
// FIXME: this can be misleading, the plan will only be used if this is the first parameter returned by `parameterDifference`.

afterward, planName (on the left) or planFound (on the right) would be set and we won't land here.

Copy link
Contributor Author

@gkleiman gkleiman Jun 24, 2019

Choose a reason for hiding this comment

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

I think that neither my original comment nor your suggestion are completely accurate 😱.

This is all nested within the loop that starts on line 124 on the right. If for any changed param a trigger was defined (param.trigger != ""), then lines 142-143 will overwrite planName and planFound. This will happen regardless of whether we had previously logged (and "decided") that we were going to use a default plan. This means that the plan that we logged might actually not be used, even when it corresponds to the first parameter returned by parameterDifference.

I think that right now we don't have a test verifying which plan will be executed if parameters with different triggers have changed. We probably also haven't documented this. We should define what we want to happen... and then clean up the code a little more to reflect that and write a test to make sure that the code is correct.

I added this FIXME here to spark this discussion... but I forgot to add a comment when posting the PR. Thanks for noticing this =).

So now my question is: which plan should we run if multiple parameters have been changed triggering different triggers? cc/ @gerred @alenkacz @runyontr

Copy link
Member

Choose a reason for hiding this comment

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

We have another issue to discuss this one, let's not change this here. There's a lot of options and most of them will require a validating admission hook.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cool, I wasn't aware of #354! — I'll change this comment to a TODO mentioning that we might need to revisit this part when #354 is resolved =).

@@ -164,7 +175,7 @@ func add(mgr manager.Manager, r reconcile.Reconciler) error {
if current.Status.State == kudov1alpha1.PhaseStateComplete {
log.Println("InstanceController: Current Plan for Instance is already done, won't change the Suspend flag.")
} else {
log.Println("InstanceController: Setting PlanExecution to Suspend")
log.Println("InstanceController: Suspending the PlanExecution")
Copy link
Contributor

@zen-dog zen-dog Jun 24, 2019

Choose a reason for hiding this comment

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

Suggested change
log.Println("InstanceController: Suspending the PlanExecution")
log.Printf("InstanceController: Suspending the PlanExecution for instance %v", new.Name)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note: this has to be log.Printf(...) instead of log.Println(...), I made the same mistake a few times during this refactoring 😳.

pkg/controller/instance/instance_controller.go Outdated Show resolved Hide resolved
pkg/controller/instance/instance_controller.go Outdated Show resolved Hide resolved
Copy link
Contributor

@alenkacz alenkacz left a comment

Choose a reason for hiding this comment

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

LGTM. Although doing any kind of refactoring while not backed by tests feels very ... dangerous :D I think we should invest in tests next time we're touching any code

planName = "deploy"
} else {
planName = "update"
// Its an upgrade!
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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching the typo!

// Find the right parameter in the FV
// Find the spec of the updated parameter.
//
// TODO: what should we do if more than one parameter was updated?
Copy link
Contributor

Choose a reason for hiding this comment

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

that should be addressed in #354

if !planFound {
// The parameter doesn't have a trigger, try to find the corresponding default plan.
names := []string{"update", "deploy"}
for _, n := range names {
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 probably also extract it but I don't have strong opinion here :)

@gerred gerred added this to the v0.3.0 milestone Jun 25, 2019
@gerred gerred added this to In progress in KUDO via automation Jun 25, 2019
Copy link
Member

@gerred gerred left a comment

Choose a reason for hiding this comment

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

I echo the thoughts here. Let's do a separate extraction PR but also make sure all of that is guarded as testable code so we can do these refactorings safely.

planName = "update"

if planFound {
// FIXME: this can be misleading, the plan will only be used if this is the last parameter returned by `parameterDifference`.
Copy link
Member

Choose a reason for hiding this comment

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

We have another issue to discuss this one, let's not change this here. There's a lot of options and most of them will require a validating admission hook.

@gkleiman
Copy link
Contributor Author

After having spent a considerable amount of time trying to clean up this predicate function, I found a few fundamental issues that I describe in #422.

My proposal on #422 is to get rid of this predicate and to add more logic to the Reconcile method. I would honestly rather focus on that than on minor improvements to the predicate function.

However if you think that this PR is valuable, we can merge it. This should make the current implementation of InstanceController a bit easier to understand and read until #422 is resolved.

@zen-dog
Copy link
Contributor

zen-dog commented Jun 27, 2019

My proposal on #422 is to get rid of this predicate and to add more logic to the Reconcile method. I would honestly rather focus on that than on minor improvements to the predicate function.

Yeah, I agree. #422 looks like the right way to go. I'll retract my request for change and leave the decision of whether to merge it or not up to you ;)

@zen-dog zen-dog dismissed their stale review June 27, 2019 13:44

Retracted

KUDO automation moved this from In progress to Reviewer approved Jun 27, 2019
@kensipe
Copy link
Member

kensipe commented Jun 28, 2019

@gkleiman this is based on an older version... we do not reference Frameworks anymore

NOTE: The update predicate function still inspects the status attribute
and has side-effects. This is an anti-pattern that should be solved in
a different patch, see #422.
@gkleiman gkleiman force-pushed the gaston/cleanup-instance-controller branch from 3bb67e6 to 0fb3be7 Compare July 9, 2019 07:21
@gkleiman
Copy link
Contributor Author

gkleiman commented Jul 9, 2019

I rebased this PR and verified that the integration tests introduced by #504 pass =).

@gkleiman gkleiman requested a review from alenkacz July 9, 2019 07:38
@gerred gerred merged commit 3eebef4 into master Jul 10, 2019
KUDO automation moved this from Reviewer approved to Done Jul 10, 2019
@gerred gerred deleted the gaston/cleanup-instance-controller branch July 10, 2019 21:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
No open projects
KUDO
  
Done
Development

Successfully merging this pull request may close these issues.

None yet

5 participants