-
Notifications
You must be signed in to change notification settings - Fork 116
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
Refactor Pod await logic #590
Conversation
a5cdb43
to
a01c1e0
Compare
448a412
to
30e329d
Compare
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 LGTM. Let's get some other eyes on here to review given the size of the PR
2970ee2
to
d5a20f3
Compare
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 with great regret that I must report this is vastly superior to what I originally wrote.
In fact, just today I remarked to @lukehoban that you occasionally hear stories of people writing some random Go and wishing they had a feature of another language, only to rewrite it and find out (a) they didn't need it, and (b) it's simpler without it. I told Luke I had never had this experience and I couldn't tell whether that's because I'm a really bad Go programmer or not.
Well, now that we've rewritten it, the jury is in: I am just a bad Go programmer.
Anyway, a few questions/suggestions below.
@@ -497,7 +497,7 @@ func Test_Apps_Deployment(t *testing.T) { | |||
object: deploymentProgressing(inputNamespace, deploymentInputName, revision1), | |||
subErrors: []string{ | |||
"Minimum number of live Pods was not attained", | |||
`1 Pods failed to run because: [ImagePullBackOff] Back-off pulling image "sdkjlsdlkj"`, | |||
`containers with unready status: [nginx] -- Back-off pulling image "sdkjlsdlkj"`, |
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.
Not a huge thing but as a general rule I think we should keep the ImagePullBackOff
because the standard error codes are easier to Google. It's also easier for people who know how this stuff works to understand what's going on.
|
||
// Message stores a log string and the severity for the log message. | ||
type Message struct { | ||
S 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.
This is a great start, and it's ok if the answer is no -- but if there is additional structured information we can pull out of the S
and into the Message
struct, I think we should do that. Is there anything that seems likely to fit that?
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.
Not off the top of my head. Will keep it in mind as I continue refactoring.
} | ||
|
||
// Empty returns true if the Message is uninitialized, false otherwise. | ||
func (m Message) Empty() bool { |
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.
Curious: why make Empty
a method on Message
instead of making Result#Message
a pointer (in which case you could check if it's "empty" by checking whether it's 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.
Message
is used in a bunch of places. Can't recall the specific reason, but it was easier this way when I wrote it.
scheduleErrorCounts := map[string]int{} | ||
containerErrorCounts := map[string]int{} | ||
for _, pod := range sia.pods { | ||
func (sia *statefulsetInitAwaiter) aggregatePodErrors() logging.Messages { |
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.
Not a big deal, but an alternative design you might consider here is to have it take a lambda as an argument instead of passing an allocated list back, and then allocating another list, again, in response, which is what we're doing in a lot of the callsites.
messages := sia.aggregatePodErrors(func() {
sia.config.logMessage(message)
})
} | ||
messages = append(messages, checker.Update(pod).Warnings()...) |
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.
Nice.
@@ -61,3 +62,149 @@ func PodBasic() *podBasic { | |||
}, | |||
} | |||
} | |||
|
|||
// PodBase returns Pod struct with basic data initialized. | |||
func PodBase(name, namespace string) *corev1.Pod { |
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.
Why build the pods up explicitly in code instead of just deserializing JSON? Seems like it's advantageous to have the verbatim resource definitions as the basis of these tests, and as it's written I can't quite tell if they are exactly correct. Also, the status field for pod in particular is way way fiddly. I consider the comment from @metral below evidence of all of this. :)
|
||
func NewPodChecker() *StateChecker { | ||
return &StateChecker{ | ||
conditions: []Condition{podScheduled, podInitialized, podReady}, |
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.
lol this is way better than the way we're doing it now.
result := Result{Description: fmt.Sprintf("Waiting for Pod %q to be scheduled", fqName(pod))} | ||
|
||
for _, condition := range pod.Status.Conditions { | ||
if condition.Type == v1.PodScheduled { |
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 loop-and-check-type logic seems error-prone, and I think we should consider moving this into its own function, something like:
result := Result{Description: fmt.Sprintf("Waiting for Pod %q to be scheduled", fqName(pod))}
matchCondition(
pod.Status.Conditions,
v1.PodScheduled,
func(v1.PodCondition) { result.Ok = true }, // onTrue
func(condition v1.PodCondition) {
msg := statusFromCondition(condition)
if len(msg) > 0 {
result.Message = logging.StatusMessage(msg)
}
}) // onFalse
Same thing, perhaps, for container status?
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 added a filter function to flatten out the logic, and structured the conditional checking similarly to your suggestion.
"github.com/pulumi/pulumi-kubernetes/pkg/logging" | ||
) | ||
|
||
// Result specifies the result of a Condition applied to an input object. |
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.
Might want to mention that this is a v1.PodCondition
, and write a bit more about what this is actually for -- e.g., it's the Result
of a check that a Pod
has some v1.PodCondition
that is true, with some metadata useful for rendering messages about it.
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 actually isn't specific to PodCondition
s; it's a generic condition that is used to determine readiness of the relevant resource.
// Condition is a function that checks a state and returns a Result. | ||
type Condition func(s interface{}) Result | ||
|
||
// StateChecker holds the data required to generically implement await logic. |
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.
Might be nice to make this clearer, too. StateChecker
checks that a Pod
has a set of v1.PodConditions
that match the desired state.
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.
Again, StateChecker
isn't specific to Pod
; it will be used for every resource type once I'm done with refactoring.
Fixes #450