diff --git a/deployment/base/rbac-topologyupdater/topologyupdater-clusterrole.yaml b/deployment/base/rbac-topologyupdater/topologyupdater-clusterrole.yaml index 493f396c12..94d4e5e2cf 100644 --- a/deployment/base/rbac-topologyupdater/topologyupdater-clusterrole.yaml +++ b/deployment/base/rbac-topologyupdater/topologyupdater-clusterrole.yaml @@ -10,6 +10,12 @@ rules: verbs: - get - list +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get - apiGroups: - "" resources: diff --git a/deployment/helm/node-feature-discovery/templates/clusterrole.yaml b/deployment/helm/node-feature-discovery/templates/clusterrole.yaml index e652e1df8c..5ffb21c4d4 100644 --- a/deployment/helm/node-feature-discovery/templates/clusterrole.yaml +++ b/deployment/helm/node-feature-discovery/templates/clusterrole.yaml @@ -58,6 +58,12 @@ rules: verbs: - get - list +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get - apiGroups: - "" resources: diff --git a/pkg/nfd-topology-updater/nfd-topology-updater.go b/pkg/nfd-topology-updater/nfd-topology-updater.go index 0f8095fb8a..29de2102e7 100644 --- a/pkg/nfd-topology-updater/nfd-topology-updater.go +++ b/pkg/nfd-topology-updater/nfd-topology-updater.go @@ -26,6 +26,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" k8sclient "k8s.io/client-go/kubernetes" "k8s.io/klog/v2" kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1" @@ -80,6 +81,9 @@ type nfdTopologyUpdater struct { eventSource <-chan kubeletnotifier.Info configFilePath string config *NFDConfig + kubernetesNamespace string + ownerRefs []metav1.OwnerReference + k8sClient k8sclient.Interface kubeletConfigFunc func() (*kubeletconfigv1beta1.KubeletConfiguration, error) } @@ -105,6 +109,8 @@ func NewTopologyUpdater(args Args, resourcemonitorArgs resourcemonitor.Args) (Nf nodeName: utils.NodeName(), eventSource: eventSource, config: &NFDConfig{}, + kubernetesNamespace: utils.GetKubernetesNamespace(), + ownerRefs: []metav1.OwnerReference{}, kubeletConfigFunc: kubeletConfigFunc, } if args.ConfigFile != "" { @@ -146,6 +152,7 @@ func (w *nfdTopologyUpdater) Run() error { if err != nil { return err } + w.k8sClient = k8sClient if err := w.configure(); err != nil { return fmt.Errorf("faild to configure Node Feature Discovery Topology Updater: %w", err) @@ -222,11 +229,29 @@ func (w *nfdTopologyUpdater) Stop() { } func (w *nfdTopologyUpdater) updateNodeResourceTopology(zoneInfo v1alpha2.ZoneList, scanResponse resourcemonitor.ScanResponse, readKubeletConfig bool) error { + + if len(w.ownerRefs) == 0 { + ns, err := w.k8sClient.CoreV1().Namespaces().Get(context.TODO(), w.kubernetesNamespace, metav1.GetOptions{}) + if err != nil { + klog.ErrorS(err, "Cannot get NodeResourceTopology owner reference") + } else { + w.ownerRefs = []metav1.OwnerReference{ + { + APIVersion: ns.APIVersion, + Kind: ns.Kind, + Name: ns.Name, + UID: types.UID(ns.UID), + }, + } + } + } + nrt, err := w.topoClient.TopologyV1alpha2().NodeResourceTopologies().Get(context.TODO(), w.nodeName, metav1.GetOptions{}) if errors.IsNotFound(err) { nrtNew := v1alpha2.NodeResourceTopology{ ObjectMeta: metav1.ObjectMeta{ - Name: w.nodeName, + Name: w.nodeName, + OwnerReferences: w.ownerRefs, }, Zones: zoneInfo, Attributes: v1alpha2.AttributeList{}, @@ -248,6 +273,7 @@ func (w *nfdTopologyUpdater) updateNodeResourceTopology(zoneInfo v1alpha2.ZoneLi nrtMutated := nrt.DeepCopy() nrtMutated.Zones = zoneInfo + nrtMutated.OwnerReferences = w.ownerRefs attributes := scanResponse.Attributes diff --git a/test/e2e/topology_updater_test.go b/test/e2e/topology_updater_test.go index 8b463ed283..92c53f7a4e 100644 --- a/test/e2e/topology_updater_test.go +++ b/test/e2e/topology_updater_test.go @@ -261,6 +261,44 @@ var _ = NFDDescribe(Label("nfd-topology-updater"), func() { return true }, time.Minute, 5*time.Second).Should(BeTrue(), "didn't get updated node topology info") }) + + It("should check that that topology object is garbage colleted", func(ctx context.Context) { + + By("Check if the topology object has owner reference") + ns, err := f.ClientSet.CoreV1().Namespaces().Get(ctx, f.Namespace.Name, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + t, err := topologyClient.TopologyV1alpha2().NodeResourceTopologies().Get(ctx, topologyUpdaterNode.Name, metav1.GetOptions{}) + Expect(err).NotTo(HaveOccurred()) + + owned := false + for _, r := range t.OwnerReferences { + if r.UID == ns.UID { + owned = true + break + } + } + Expect(owned).Should(BeTrue()) + + By("Deleting the nfd-topology namespace") + err = f.ClientSet.CoreV1().Namespaces().Delete(ctx, f.Namespace.Name, metav1.DeleteOptions{}) + Expect(err).NotTo(HaveOccurred()) + + By("checking that topology was garbage collected") + Eventually(func() bool { + t, err := topologyClient.TopologyV1alpha2().NodeResourceTopologies().Get(ctx, topologyUpdaterNode.Name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + framework.Logf("missing node topology resource for %q", topologyUpdaterNode.Name) + return false + } + framework.Logf("failed to get the node topology resource: %v", err) + return true + } + framework.Logf("topology resource: %v", t) + return true + }).WithPolling(15 * time.Second).WithTimeout(60 * time.Second).Should(BeFalse()) + }) }) When("sleep interval disabled", func() { diff --git a/test/e2e/utils/rbac.go b/test/e2e/utils/rbac.go index 8549256aa3..c71e372486 100644 --- a/test/e2e/utils/rbac.go +++ b/test/e2e/utils/rbac.go @@ -22,6 +22,7 @@ import ( corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" ) @@ -105,10 +106,6 @@ func DeconfigureRBAC(ctx context.Context, cs clientset.Interface, ns string) err if err != nil { return err } - err = cs.RbacV1().RoleBindings(ns).Delete(ctx, "nfd-worker-e2e", metav1.DeleteOptions{}) - if err != nil { - return err - } err = cs.RbacV1().ClusterRoleBindings().Delete(ctx, "nfd-gc-e2e", metav1.DeleteOptions{}) if err != nil { return err @@ -121,11 +118,24 @@ func DeconfigureRBAC(ctx context.Context, cs clientset.Interface, ns string) err if err != nil { return err } - err = cs.RbacV1().Roles(ns).Delete(ctx, "nfd-worker-e2e", metav1.DeleteOptions{}) + err = cs.RbacV1().ClusterRoles().Delete(ctx, "nfd-gc-e2e", metav1.DeleteOptions{}) if err != nil { return err } - err = cs.RbacV1().ClusterRoles().Delete(ctx, "nfd-gc-e2e", metav1.DeleteOptions{}) + + // Skip the deletion of namespaced objects in case the namespace is gone + _, err = cs.CoreV1().Namespaces().Get(ctx, ns, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + return nil + } + return err + } + err = cs.RbacV1().RoleBindings(ns).Delete(ctx, "nfd-worker-e2e", metav1.DeleteOptions{}) + if err != nil { + return err + } + err = cs.RbacV1().Roles(ns).Delete(ctx, "nfd-worker-e2e", metav1.DeleteOptions{}) if err != nil { return err } @@ -251,6 +261,11 @@ func createClusterRoleTopologyUpdater(ctx context.Context, cs clientset.Interfac Resources: []string{"pods"}, Verbs: []string{"get", "list", "watch"}, }, + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"get"}, + }, { APIGroups: []string{"topology.node.k8s.io"}, Resources: []string{"noderesourcetopologies"},