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

Option to restrict informer cache to a namespace #136

Merged

Conversation

hasbro17
Copy link
Contributor

@hasbro17 hasbro17 commented Sep 9, 2018

Fixes #124

A namespace option can be passed from the manager to the cache which restricts the ListWatch for all informers to the desired namespace.
This way a manager can be run with a Role instead of a ClusterRole.

@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Sep 9, 2018
@k8s-ci-robot k8s-ci-robot added the size/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Sep 9, 2018
@hasbro17
Copy link
Contributor Author

hasbro17 commented Sep 9, 2018

@DirectXMan12 PTAL
First time writing a test in Ginkgo so I'm not sure if the namespaced cache test case should be in it's own Describe block. Seemed more concise to add it as a sub-case to the Reader.

@@ -128,7 +132,7 @@ func New(config *rest.Config, options Options) (Manager, error) {
}

// Create the cache for the cached read client and registering informers
cache, err := options.newCache(config, cache.Options{Scheme: options.Scheme, Mapper: mapper, Resync: options.SyncPeriod})
cache, err := options.newCache(config, cache.Options{Scheme: options.Scheme, Mapper: mapper, Resync: options.SyncPeriod, Namespace: options.Namespace})
Copy link
Contributor

Choose a reason for hiding this comment

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

When I need to watch all namespace events, set nil to options.Namespace or set other value?
Thanks.

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 watch all namespaces you set options.Namespaces = "" or metav1.NamespaceAll which is the same thing.
But since the default string value is empty you can just not set the Namespace option in cache.Options and it should watch all namespaces by default.

@@ -217,14 +223,14 @@ func (ip *InformersMap) newListWatch(gvk schema.GroupVersionKind) (*cache.ListWa
return &cache.ListWatch{
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
res := listObj.DeepCopyObject()
err := client.Get().Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Do().Into(res)
err := client.Get().Namespace(ip.namespace).Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Do().Into(res)
Copy link
Contributor

Choose a reason for hiding this comment

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

does this need to be NamespaceIfScoped(ip.namespace, ip.namespace == "")?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You could do without NamespaceIfScoped since the URL constructed ignores namespaces if namespace == "".
https://github.com/kubernetes/client-go/blob/master/rest/request.go#L424
But that behavior is implicit so I've added NamespaceIfScoped(ip.namespace, ip.namespace != "") to make the distinction more clear.

@hasbro17
Copy link
Contributor Author

@DirectXMan12 The request is explicitly namespace scoped now. PTAL.

@hasbro17
Copy link
Contributor Author

@droot can you please review this in case @DirectXMan12 is busy.

@droot
Copy link
Contributor

droot commented Sep 20, 2018

@hasbro17 change looks good to me. @DirectXMan12 , would you like to take a final look ?

@hasbro17
Copy link
Contributor Author

@droot thanks. I think @DirectXMan12 should be back on Monday to take a look.
I'll rebase my changes in the meanwhile since #101 was merged.

@hasbro17
Copy link
Contributor Author

Rebased and added another namespaced cache test for an unstructured object.

Expect(out.Items).Should(HaveLen(1))
Expect(out.Items[0].GetNamespace()).To(Equal(testNamespaceOne))
})

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 for covering this.

@@ -106,6 +106,10 @@ type Options struct {
// will use for holding the leader lock.
LeaderElectionID string

// Namespace if specified restricts the manager's cache to watch the desired namespace
// Defaults to all namespaces
Copy link
Contributor

Choose a reason for hiding this comment

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

May be make it more explicit:

watch objects in the desired namespace

Also one more q: if I specify the namespace here and later setup a watch for a cluster scoped resource (say Node), will I get error, what would be the UX ?
We should document that behavior here probably.

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 tested it out by specifying the manager's namespace and added a controller to watch nodes:

$ ./main --namespace=default
{"level":"info","ts":1537598131.6718707,"logger":"kubebuilder.controller","caller":"controller/controller.go:120","msg":"Starting EventSource","Controller":"node-controller","Source":{"Type":{"metadata":{"creationTimestamp":null},"spec":{},"status":{"daemonEndpoints":{"kubeletEndpoint":{"Port":0}},"nodeInfo":{"machineID":"","systemUUID":"","bootID":"","kernelVersion":"","osImage":"","containerRuntimeVersion":"","kubeletVersion":"","kubeProxyVersion":"","operatingSystem":"","architecture":""}}}}}
E0921 23:35:31.674413   15574 reflector.go:205] sigs.k8s.io/controller-runtime/pkg/cache/internal/informers_map.go:126: Failed to list *v1.Node: the server could not find the requested resource
E0921 23:35:32.680154   15574 reflector.go:205] sigs.k8s.io/controller-runtime/pkg/cache/internal/informers_map.go:126: Failed to list *v1.Node: the server could not find the requested resource

As expected the ListWatcher fails to List the nodes because when the namespace is set it will form an incorrect request to list Nodes by adding the namespace in the REST path.
https://github.com/kubernetes-sigs/controller-runtime/pull/136/files#diff-fc36bd1640f2ed10bc9e4f4d24789edfR235

The behavior seems consistent with the need for namespacing the manager i.e you don't expect to Watch cluster-scoped resources if your permissions are restricted to a namespace.

I've updated the comment to document this behavior.

Copy link
Contributor

@DirectXMan12 DirectXMan12 left a comment

Choose a reason for hiding this comment

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

realized that we also should check if the resource is namespaced in the first place.

@@ -225,14 +232,14 @@ func createStructuredListWatch(gvk schema.GroupVersionKind, ip *specificInformer
return &cache.ListWatch{
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
res := listObj.DeepCopyObject()
err := client.Get().Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Do().Into(res)
err := client.Get().NamespaceIfScoped(ip.namespace, ip.namespace != "").Resource(mapping.Resource.Resource).VersionedParams(&opts, ip.paramCodec).Do().Into(res)
Copy link
Contributor

Choose a reason for hiding this comment

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

it occurs to me that there's one last wrinkle for this -- we need to also check if the resource is namespace-scoped to begin with using the rest mapping -- i.e. you should be able to watch nodes when watching pods in a specific namespace.

Copy link
Contributor

Choose a reason for hiding this comment

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

so this should be NamespaceIfScoped(ip.namespace, ip.namespace != "" && mapping.Scope == apimeta.RESTScopeNameRoot)

Copy link
Contributor

Choose a reason for hiding this comment

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

and below (in the watch)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed. Minor nit being it's:

ip.namespace != "" && mapping.Scope != apimeta.RESTScopeNameRoot

@@ -252,12 +259,18 @@ func createUnstructuredListWatch(gvk schema.GroupVersionKind, ip *specificInform
// Create a new ListWatch for the obj
return &cache.ListWatch{
ListFunc: func(opts metav1.ListOptions) (runtime.Object, error) {
if ip.namespace != "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto here on the "is this namespaced"

@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Sep 25, 2018
A namespace option can be passed from the manager to
the cache which restricts the ListWatch for all informers
to the desired namespace. This way a manager can be run
with a Role instead of a ClusterRole.
@hasbro17
Copy link
Contributor Author

@DirectXMan12 I've updated the ListWatchers to check if the resource is namespace scoped before making a namespaced request.
With this change ,even if the cache is namespaced, it can still be used to read cluster-scoped resources.

I've updated the tests with the additional case of ensuring a namespaced cache will still let you List a cluster-scoped resource(corev1.Namespace).
Also updated the manager's Namespace option comment to clarify this.

@DirectXMan12
Copy link
Contributor

/lgtm
/approve

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Sep 25, 2018
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: DirectXMan12, hasbro17

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 /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Sep 25, 2018
@k8s-ci-robot k8s-ci-robot merged commit 53fc44b into kubernetes-sigs:master Sep 25, 2018
justinsb pushed a commit to justinsb/controller-runtime that referenced this pull request Dec 7, 2018
…rs-namespaced

Option to restrict informer cache to a namespace
DirectXMan12 pushed a commit that referenced this pull request Jan 31, 2020
Use "dep" to import dependencies for generated project
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants