-
Notifications
You must be signed in to change notification settings - Fork 562
wip(operators): watch preferred kinds dynamically #1670
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
Conversation
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: njhale The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
Watches(&source.Kind{Type: &apiregistrationv1.APIService{}}, enqueueCSV). | ||
Watches(&source.Kind{Type: &operatorsv1alpha1.Subscription{}}, enqueueCSV). | ||
Watches(&source.Kind{Type: &operatorsv1alpha1.InstallPlan{}}, enqueueCSV). | ||
Watches(r.source, enqueueCSV). |
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 need to figure out a way to share caches/watches between controllers under a single manager. I don't like the idea watching and storing k|cluster resources|
.
var componentLists []runtime.Object | ||
for _, gvk := range informable { | ||
gvk.Kind = gvk.Kind + "List" | ||
ul := &unstructured.UnstructuredList{} | ||
ul.SetGroupVersionKind(gvk) | ||
componentLists = append(componentLists, ul) |
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.
Needs to be deduplicated across the operator and adoption controller. This is probably generic enough to be it's own layer on top of the controller-runtime client.
@@ -218,6 +218,7 @@ func (o *Operator) AdoptComponent(component runtime.Object) (adopted bool, err e | |||
labels[labelKey] = "" | |||
adopted = true | |||
} | |||
m.SetLabels(labels) |
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.
Makes the changes stick for unstructured.Unstructured
.
) | ||
|
||
// InformableGVKs returns the set of GVKs available in discovery that support the list and watch verbs (informable). | ||
func (d *Dynamic) InformableGVKs() (informables []schema.GroupVersionKind, err 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.
Going to factor this out of Dynamic
.
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.
getInformableGVKs
might make the code more readable at the site this function is called.
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 actually quite a useful function in a standalone way
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.
getInformableGVKs
might make the code more readable at the site this function is called.
I actually disagree, I think it's Go code convention not to have things like Get...
in function names, unlike say JavaScript
https://golang.org/doc/effective_go.html#Getters
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 pet peeve of mine 😄
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.
Okay fair point. I concede.
} | ||
|
||
var resourceLists []*metav1.APIResourceList | ||
resourceLists, err = d.client.ServerPreferredResources() |
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.
Fails when discovery breaks for any resource.
informablesLock.RLock() | ||
defer informablesLock.RUnlock() | ||
log.Error(err, "could not get preferred resources, returning latest known gvks") | ||
return knownInformables, 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.
Going to memoize the last successful APIResourceList
and whittle it down with direct checks against the discovery client instead. This will let us weed out the problem resource types before returning.
} | ||
} | ||
|
||
}, 15*time.Second, d.stop) |
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.
What do we think about triggering this when APIServices and CRDs change?
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.
Makes sense. Maybe we can expand the InformableGVKs()
function to not only memoize on known GVKs in case of discovery errors but also use that as a cache so that when new CRDs (GVKs) become available we can use the diff instead of going through the ServerPreferredResources
again
} | ||
} | ||
|
||
}, 15*time.Second, d.stop) |
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.
Make this configurable?
|
||
}, 15*time.Second, d.stop) | ||
|
||
synced.Wait() |
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.
Block until we set up the initial set of sources.
r := &unstructured.Unstructured{} | ||
r.SetGroupVersionKind(gvk) | ||
s := &source.Kind{Type: r} | ||
if err = s.InjectCache(d.cache); err != 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.
controller-runtime doesn't expose a way to stop individual sources/informers, see:
- Allow removing individual informers from the Cache kubernetes-sigs/controller-runtime#935
- ✨ Allow removing individual informers from the cache (#935) kubernetes-sigs/controller-runtime#936
- https://github.com/gardener/gardener-resource-manager/issues/61
I'm curious to see if anyone has suggestions for how to get around this short of reimplementing controller-runtime's informerCache.
This PR failed tests for 1 times with 1 individual failed tests and 4 skipped tests. A test is considered flaky if failed on multiple commits. totaltestcount: 1
|
/test e2e-gcp-upgrade |
} | ||
|
||
// InjectConfig injects the the REST Config dependency. | ||
func (d *Dynamic) InjectConfig(config *rest.Config) (err 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.
Is InjectRESTConfigDependency
too big of a name? It'll would get rid of the comment, and be self explanatory in the calling site too.
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 don't disagree, but we don't control the method signatures used for injection.
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'll would get rid of the comment
Also, golangci-lint generally complains about exports w/o comments.
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.
Alas.
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.
Cool! just leaving some general feedback
synced.Add(1) | ||
|
||
go wait.Until(func() { | ||
defer once.Do(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.
The intention here is to only call synced.Done()
once after the function exits - but since the function is called within the Until()
scope every 15 seconds are you sure this doesn't just get triggered the first time the function is executed within the Until
loop?
This is probably standard code, just want to confirm.
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 intention here is to only call synced.Done() once after the function exits - but since the function is called within the Until() scope every 15 seconds are you sure this doesn't just get triggered the first time the function is executed within the Until loop?
I'm not understanding the delineation you're making here. Could you rephrase?
} | ||
} | ||
|
||
}, 15*time.Second, d.stop) |
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.
Makes sense. Maybe we can expand the InformableGVKs()
function to not only memoize on known GVKs in case of discovery errors but also use that as a cache so that when new CRDs (GVKs) become available we can use the diff instead of going through the ServerPreferredResources
again
) | ||
|
||
// InformableGVKs returns the set of GVKs available in discovery that support the list and watch verbs (informable). | ||
func (d *Dynamic) InformableGVKs() (informables []schema.GroupVersionKind, err 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.
It's actually quite a useful function in a standalone way
} | ||
|
||
if len(informables) > 0 { | ||
// Memoize the latest informables in case discovery fails on successive calls |
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 like this touch a lot. May be interesting to play around with an e2e test that fails discovery calls intentionally and sees how the operator API behaves
&operatorsv1alpha1.SubscriptionList{}, | ||
&operatorsv1alpha1.InstallPlanList{}, | ||
&operatorsv1alpha1.ClusterServiceVersionList{}, | ||
informable, err := r.source.InformableGVKs() |
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.
nit: this may take a while on an Openshift cluster with a ton of GVKs...maybe adding a log message indicating that this process is happening could be insightful
I think a lot of this work will be useful in the "OLM supports arbitrary kube types" feature upcoming in the next release. In fact if there's anything here that we could expose to that effort that would be super helpful. |
@njhale: PR needs rebase. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
@njhale: The following tests failed, say
Full PR test history. Your PR dashboard. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. I understand the commands that are listed here. |
Closing this out to pursue an alternative: for each API group, whenever the default version set changes, spin up a new pod to watch the resources of that set. |
Description of the change:
Operator
resource.This means that any resource on the cluster can be labelled as an
Operator
component.Motivation for the change:
The set of possible resource types that can comprise an operator changes dynamically as a cluster's control-plane is extended; w/ CRDs and extension APIServices. Often, an operator author considers certain custom resources of kinds they provide to be a component of their operator deployment ( e.g. a config CR).