Repository: kubernetes-sigs/node-feature-discovery
Pull request #1167 (2023-04-21 12:45:50)

nfd-topology-updater: fix wrong kubelet_internal_checkpoint path and compare basename to full path
This PR fixes 2 problems with kubeletnotifier:

- `kubelet_internal_checkpoint` file is in `/var/lib/kubelet/device-plugins` (as defined [here](https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/kubelet/pkg/apis/deviceplugin/v1beta1/constants.go#L30) and fsWatcher doesn't watch dirs recursively
- `e.Name` returned from fsWatcher events is a full path not a basename 

We really need tests for this feature 😄 

k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/node-feature-discovery/pull/1167#" title="Author self-approved">PiotrProkop</a>*
**Once this PR has been reviewed and has the lgtm label**, please assign marquiz for approval. For more information see [the Kubernetes Code Review Process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process).

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fnode-feature-discovery).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["marquiz"]} -->
netlify[bot]: ### <span aria-hidden="true">👷</span> Deploy Preview for *kubernetes-sigs-nfd* processing.


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 6215309c88c3c9fbfa420341ed1f3af1716a451c |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/kubernetes-sigs-nfd/deploys/644285815a15d100087a7ad2 |
PiotrProkop: /cc @marquiz @Tal-or @ffromani 
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/node-feature-discovery/pull/1167#pullrequestreview-1396274387" title="Approved">marquiz</a>*, *<a href="https://github.com/kubernetes-sigs/node-feature-discovery/pull/1167#" title="Author self-approved">PiotrProkop</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fnode-feature-discovery).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/OWNERS)~~ [marquiz]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
marquiz: @PiotrProkop one suggestion still: could you add the detailed description you have in the PR into the commit message as well. With that you could shorten the commit message title a bit like `nfd-topology-updater: fix kubelet state file notifier` or smth
Tal-or: > We really need tests for this feature

We have e2e for this feature here: https://github.com/kubernetes-sigs/node-feature-discovery/blob/master/test/e2e/topology_updater_test.go#L275 
ArangoGutierrez: > > @PiotrProkop one suggestion still: could you add the detailed description you have in the PR into the commit message as well. With that you could shorten the commit message title a bit like `nfd-topology-updater: fix kubelet state file notifier` or smth
> 
> commit message changed

Requested: 
`nfd-topology-updater: fix kubelet state file notifier`
Has
`nfd-topology-updater: nfd-topology-updater: fix kubelet state file notifier`

Patch:
diff --git a/OWNERS b/OWNERS
index b68f01f1a..1a4bee721 100644
--- a/OWNERS
+++ b/OWNERS
@@ -3,14 +3,12 @@
 #
 
 approvers:
-  - ArangoGutierrez
   - marquiz
 reviewers:
   - ArangoGutierrez
   - jjacobelli
   - fmuyassarov
   - kad
-  - marquiz
   - PiotrProkop
   - zvonkok
   - adrianchiris
diff --git a/cmd/nfd-master/main.go b/cmd/nfd-master/main.go
index dba7da4f1..340ffcf09 100644
--- a/cmd/nfd-master/main.go
+++ b/cmd/nfd-master/main.go
@@ -20,7 +20,6 @@ import (
 	"flag"
 	"fmt"
 	"os"
-	"time"
 
 	"k8s.io/klog/v2"
 
@@ -68,8 +67,6 @@ func main() {
 			args.Overrides.EnableTaints = overrides.EnableTaints
 		case "no-publish":
 			args.Overrides.NoPublish = overrides.NoPublish
-		case "-resync-period":
-			args.Overrides.ResyncPeriod = overrides.ResyncPeriod
 		}
 	})
 
@@ -134,7 +131,6 @@ func initFlags(flagset *flag.FlagSet) (*master.Args, *master.ConfigOverrideArgs)
 		DenyLabelNs:    &utils.StringSetVal{},
 		ExtraLabelNs:   &utils.StringSetVal{},
 		ResourceLabels: &utils.StringSetVal{},
-		ResyncPeriod:   &utils.DurationVal{Duration: time.Duration(1) * time.Hour},
 	}
 	flagset.Var(overrides.ExtraLabelNs, "extra-label-ns",
 		"Comma separated list of allowed extra label namespaces")
@@ -149,8 +145,6 @@ func initFlags(flagset *flag.FlagSet) (*master.Args, *master.ConfigOverrideArgs)
 		"Comma separated list of denied label namespaces")
 	flagset.Var(overrides.ResourceLabels, "resource-labels",
 		"Comma separated list of labels to be exposed as extended resources. DEPRECATED: use NodeFeatureRule objects instead")
-	flagset.Var(overrides.ResyncPeriod, "resync-period",
-		"Specify the NFD API controller resync period."+
-			"It has an effect when the NodeFeature API has been enabled (with -enable-nodefeature-api).")
+
 	return args, overrides
 }
diff --git a/deployment/components/master-config/nfd-master.conf.example b/deployment/components/master-config/nfd-master.conf.example
index 3760e825d..4f5ffffa3 100644
--- a/deployment/components/master-config/nfd-master.conf.example
+++ b/deployment/components/master-config/nfd-master.conf.example
@@ -4,4 +4,3 @@
 # resourceLabels: ["vendor-1.com/feature-1","vendor-2.io/feature-2"]
 # enableTaints: false
 # labelWhiteList: "foo"
-# resyncPeriod: "2h"
diff --git a/deployment/helm/node-feature-discovery/templates/master.yaml b/deployment/helm/node-feature-discovery/templates/master.yaml
index 96b0ce168..943b5ba1b 100644
--- a/deployment/helm/node-feature-discovery/templates/master.yaml
+++ b/deployment/helm/node-feature-discovery/templates/master.yaml
@@ -104,9 +104,6 @@ spec:
             {{- if .Values.master.featureRulesController | kindIs "invalid" | not }}
             - "-featurerules-controller={{ .Values.master.featureRulesController }}"
             {{- end }}
-            {{- if .Values.master.resyncPeriod }}
-            - "-resync-period={{ .Values.master.resyncPeriod }}"
-            {{- end }}
     {{- if .Values.tls.enable }}
             - "-ca-file=/etc/kubernetes/node-feature-discovery/certs/ca.crt"
             - "-key-file=/etc/kubernetes/node-feature-discovery/certs/tls.key"
diff --git a/deployment/helm/node-feature-discovery/values.yaml b/deployment/helm/node-feature-discovery/values.yaml
index 2822a6de5..fc98f4e92 100644
--- a/deployment/helm/node-feature-discovery/values.yaml
+++ b/deployment/helm/node-feature-discovery/values.yaml
@@ -20,13 +20,11 @@ master:
     # resourceLabels: ["vendor-1.com/feature-1","vendor-2.io/feature-2"]
     # enableTaints: false
     # labelWhiteList: "foo"
-    # resyncPeriod: "2h"
   ### <NFD-MASTER-CONF-END-DO-NOT-REMOVE>
   # The TCP port that nfd-master listens for incoming requests. Default: 8080
   port: 8080
   instance:
   featureApi:
-  resyncPeriod:
   denyLabelNs: []
   extraLabelNs: []
   resourceLabels: []
diff --git a/deployment/overlays/prune/kustomization.yaml b/deployment/overlays/prune/kustomization.yaml
index 903116c19..e9410c21b 100644
--- a/deployment/overlays/prune/kustomization.yaml
+++ b/deployment/overlays/prune/kustomization.yaml
@@ -12,7 +12,6 @@ resources:
 
 components:
 - ../../components/common
-- ../../components/master-config
 
 patches:
 - path: master-clusterrole.yaml
diff --git a/docs/deployment/helm.md b/docs/deployment/helm.md
index a2496f8e1..f6961b686 100644
--- a/docs/deployment/helm.md
+++ b/docs/deployment/helm.md
@@ -110,7 +110,6 @@ We have introduced the following Chart parameters.
 | `master.*`                  | dict    |                                         | NFD master deployment configuration                                                                                                      |
 | `master.port`               | integer |                                         | Specifies the TCP port that nfd-master listens for incoming requests.                                                      |
 | `master.instance`           | string  |                                         | Instance name. Used to separate annotation namespaces for multiple parallel deployments                                                  |
-| `master.resyncPeriod`       | string  |                                         | NFD API controller resync period.                                                  |
 | `master.extraLabelNs`       | array   | []                                      | List of allowed extra label namespaces                                                                                                   |
 | `master.resourceLabels`     | array   | []                                      | List of labels to be registered as extended resources                                                                                          |
 | `master.enableTaints`       | bool    | false                                   | Specifies whether to enable or disable node tainting                                                                                           |
diff --git a/docs/reference/master-commandline-reference.md b/docs/reference/master-commandline-reference.md
index 90ca236a7..c1260e989 100644
--- a/docs/reference/master-commandline-reference.md
+++ b/docs/reference/master-commandline-reference.md
@@ -376,20 +376,3 @@ Default: 0
 Comma-separated list of `pattern=N` settings for file-filtered logging.
 
 Default: *empty*
-
-### -resync-period
-
-The `-resync-period` flag specifies the NFD API controller resync period.
-The resync means nfd-master replaying all NodeFeature and NodeFeatureRule objects,
-thus effectively re-syncing all nodes in the cluster (i.e. ensuring labels, annotations,
-extended resources and taints are in place).
-Only has effect when the [NodeFeature](../usage/custom-resources.md#nodefeature)
-CRD API has been enabled with [`-enable-nodefeature-api`](master-commandline-reference.md#-enable-nodefeature-api).
-
-Default: 1 hour.
-
-Example:
-
-```bash
-nfd-master -resync-period=2h
-```
diff --git a/docs/reference/master-configuration-reference.md b/docs/reference/master-configuration-reference.md
index 5168acefd..b11bb4cb8 100644
--- a/docs/reference/master-configuration-reference.md
+++ b/docs/reference/master-configuration-reference.md
@@ -113,20 +113,3 @@ Example:
 ```yaml
 labelWhiteList: "foo"
 ```
-
-### resyncPeriod
-
-The `resyncPeriod` option specifies the NFD API controller resync period.
-The resync means nfd-master replaying all NodeFeature and NodeFeatureRule objects,
-thus effectively re-syncing all nodes in the cluster (i.e. ensuring labels, annotations,
-extended resources and taints are in place).
-Only has effect when the [NodeFeature](../usage/custom-resources.md#nodefeature)
-CRD API has been enabled with [`-enable-nodefeature-api`](master-commandline-reference.md#-enable-nodefeature-api).
-
-Default: 1 hour.
-
-Example:
-
-```yaml
-resyncPeriod=2h
-```
diff --git a/go.mod b/go.mod
index 98583bcc5..692bd1a5f 100644
--- a/go.mod
+++ b/go.mod
@@ -13,15 +13,15 @@ require (
 	github.com/klauspost/cpuid/v2 v2.2.4
 	github.com/onsi/ginkgo/v2 v2.9.1
 	github.com/onsi/gomega v1.27.4
-	github.com/opencontainers/runc v1.1.6
+	github.com/opencontainers/runc v1.1.4
 	github.com/smartystreets/assertions v1.2.0
 	github.com/smartystreets/goconvey v1.6.4
 	github.com/stretchr/testify v1.8.1
 	github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a
 	golang.org/x/exp v0.0.0-20230307190834-24139beb5833
-	golang.org/x/net v0.9.0
+	golang.org/x/net v0.8.0
 	google.golang.org/grpc v1.51.0
-	google.golang.org/protobuf v1.30.0
+	google.golang.org/protobuf v1.28.1
 	k8s.io/api v0.27.1
 	k8s.io/apiextensions-apiserver v0.27.1
 	k8s.io/apimachinery v0.27.1
@@ -30,7 +30,7 @@ require (
 	k8s.io/kubectl v0.27.1
 	k8s.io/kubelet v0.27.1
 	k8s.io/kubernetes v1.27.1
-	k8s.io/pod-security-admission v0.27.1
+	k8s.io/pod-security-admission v0.0.0
 	k8s.io/utils v0.0.0-20230209194617-a36077c30491
 	sigs.k8s.io/yaml v1.3.0
 )
@@ -47,7 +47,6 @@ require (
 	github.com/Azure/go-autorest/autorest/validation v0.1.0 // indirect
 	github.com/Azure/go-autorest/logger v0.2.1 // indirect
 	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
-	github.com/BurntSushi/toml v1.2.1 // indirect
 	github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b // indirect
 	github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab // indirect
 	github.com/Microsoft/go-winio v0.4.17 // indirect
@@ -63,13 +62,13 @@ require (
 	github.com/cenkalti/backoff/v4 v4.1.3 // indirect
 	github.com/cespare/xxhash/v2 v2.1.2 // indirect
 	github.com/checkpoint-restore/go-criu/v5 v5.3.0 // indirect
-	github.com/cilium/ebpf v0.10.0 // indirect
+	github.com/cilium/ebpf v0.7.0 // indirect
 	github.com/container-storage-interface/spec v1.7.0 // indirect
 	github.com/containerd/cgroups v1.0.1 // indirect
 	github.com/containerd/console v1.0.3 // indirect
 	github.com/containerd/ttrpc v1.1.0 // indirect
 	github.com/coreos/go-semver v0.3.0 // indirect
-	github.com/coreos/go-systemd/v22 v22.5.0 // indirect
+	github.com/coreos/go-systemd/v22 v22.4.0 // indirect
 	github.com/cyphar/filepath-securejoin v0.2.3 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/docker/distribution v2.8.1+incompatible // indirect
@@ -86,7 +85,7 @@ require (
 	github.com/go-openapi/jsonreference v0.20.1 // indirect
 	github.com/go-openapi/swag v0.22.3 // indirect
 	github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
-	github.com/godbus/dbus/v5 v5.1.0 // indirect
+	github.com/godbus/dbus/v5 v5.0.6 // indirect
 	github.com/gofrs/uuid v4.0.0+incompatible // indirect
 	github.com/golang-jwt/jwt/v4 v4.4.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
@@ -125,8 +124,8 @@ require (
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
 	github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
 	github.com/opencontainers/go-digest v1.0.0 // indirect
-	github.com/opencontainers/runtime-spec v1.1.0-rc.2 // indirect
-	github.com/opencontainers/selinux v1.11.0 // indirect
+	github.com/opencontainers/runtime-spec v1.0.3-0.20220909204839-494a5a6aca78 // indirect
+	github.com/opencontainers/selinux v1.10.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/prometheus/client_golang v1.14.0 // indirect
@@ -134,7 +133,7 @@ require (
 	github.com/prometheus/common v0.37.0 // indirect
 	github.com/prometheus/procfs v0.8.0 // indirect
 	github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021 // indirect
-	github.com/seccomp/libseccomp-golang v0.10.0 // indirect
+	github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/spf13/cobra v1.6.0 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
@@ -142,7 +141,7 @@ require (
 	github.com/stretchr/objx v0.5.0 // indirect
 	github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
 	github.com/vishvananda/netlink v1.1.0 // indirect
-	github.com/vishvananda/netns v0.0.4 // indirect
+	github.com/vishvananda/netns v0.0.2 // indirect
 	github.com/vmware/govmomi v0.30.0 // indirect
 	go.etcd.io/etcd/api/v3 v3.5.7 // indirect
 	go.etcd.io/etcd/client/pkg/v3 v3.5.7 // indirect
@@ -165,9 +164,9 @@ require (
 	golang.org/x/crypto v0.1.0 // indirect
 	golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
 	golang.org/x/sync v0.1.0 // indirect
-	golang.org/x/sys v0.7.0 // indirect
-	golang.org/x/term v0.7.0 // indirect
-	golang.org/x/text v0.9.0 // indirect
+	golang.org/x/sys v0.6.0 // indirect
+	golang.org/x/term v0.6.0 // indirect
+	golang.org/x/text v0.8.0 // indirect
 	golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
 	golang.org/x/tools v0.7.0 // indirect
 	google.golang.org/api v0.60.0 // indirect
diff --git a/go.sum b/go.sum
index de8d80b48..b0d20bd49 100644
--- a/go.sum
+++ b/go.sum
@@ -69,9 +69,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
 github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
 github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
 github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
-github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b h1:Heo1J/ttaQFgGJSVnCZquy3e5eH5j1nqxBuomztB3P0=
 github.com/GoogleCloudPlatform/k8s-cloud-provider v1.18.1-0.20220218231025-f11817397a1b/go.mod h1:FNj4KYEAAHfYu68kRYolGoxkaJn+6mdEsaM12VTwuI0=
@@ -132,9 +131,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
 github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
+github.com/cilium/ebpf v0.7.0 h1:1k/q3ATgxSXRdrmPfH8d7YK0GfqVsEKZAX9dQZvs56k=
 github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
-github.com/cilium/ebpf v0.10.0 h1:nk5HPMeoBXtOzbkZBWym+ZWq1GIiHUsBFXxwewXAHLQ=
-github.com/cilium/ebpf v0.10.0/go.mod h1:DPiVdY/kT534dgc9ERmvP8mWA+9gvwgKfRvk4nNWnoE=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
@@ -169,8 +167,8 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
 github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk=
 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
-github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/coreos/go-systemd/v22 v22.4.0 h1:y9YHcjnjynCd/DVbg5j9L/33jQM3MxJlbj/zWskzfGU=
+github.com/coreos/go-systemd/v22 v22.4.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
 github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
@@ -218,8 +216,8 @@ github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwC
 github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
 github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk=
 github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
+github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
 github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
-github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY=
 github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
 github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
@@ -260,9 +258,8 @@ github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8Wd
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
 github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
-github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
 github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4=
@@ -436,8 +433,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
-github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -526,18 +523,15 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
 github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg=
 github.com/opencontainers/runc v1.1.4/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
-github.com/opencontainers/runc v1.1.6 h1:XbhB8IfG/EsnhNvZtNdLB0GBw92GYEFvKlhaJk9jUgA=
-github.com/opencontainers/runc v1.1.6/go.mod h1:CbUumNnWCuTGFukNXahoo/RFBZvDAgRh/smNYNOhA50=
 github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
+github.com/opencontainers/runtime-spec v1.0.3-0.20220909204839-494a5a6aca78 h1:R5M2qXZiK/mWPMT4VldCOiSL9HIAMuxQZWdG0CSM5+4=
 github.com/opencontainers/runtime-spec v1.0.3-0.20220909204839-494a5a6aca78/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
-github.com/opencontainers/runtime-spec v1.1.0-rc.2 h1:ucBtEms2tamYYW/SvGpvq9yUN0NEVL6oyLEwDcTSrk8=
-github.com/opencontainers/runtime-spec v1.1.0-rc.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
+github.com/opencontainers/selinux v1.10.0 h1:rAiKF8hTcgLI3w0DHm6i0ylVVcOrlgR1kK99DRLDhyU=
 github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
-github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaLpt7tQ7oU=
-github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
 github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
 github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
@@ -589,9 +583,8 @@ github.com/rubiojr/go-vhd v0.0.0-20200706105327-02e210299021/go.mod h1:DM5xW0nvf
 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646 h1:RpforrEYXWkmGwJHIGnLZ3tTWStkjVVstwzNGqxX2Ds=
 github.com/seccomp/libseccomp-golang v0.9.2-0.20220502022130-f33da4d89646/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
-github.com/seccomp/libseccomp-golang v0.10.0 h1:aA4bp+/Zzi0BnWZ2F1wgNBs5gTpm+na2rWM6M9YjLpY=
-github.com/seccomp/libseccomp-golang v0.10.0/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
 github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
@@ -651,8 +644,8 @@ github.com/vektra/errors v0.0.0-20140903201135-c64d83aba85a/go.mod h1:KUxJS71XlM
 github.com/vishvananda/netlink v1.1.0 h1:1iyaYNBLmP6L0220aDnYQpo1QEV4t4hJ+xEEhhJH8j0=
 github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
 github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
-github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8=
-github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
+github.com/vishvananda/netns v0.0.2 h1:Cn05BRLm+iRP/DZxyVSsfVyrzgjDbwHwkVt38qvXnNI=
+github.com/vishvananda/netns v0.0.2/go.mod h1:yitZXdAVI+yPFSb4QUe+VW3vOVl4PZPNcBgbPxAtJxw=
 github.com/vmware/govmomi v0.30.0 h1:Fm8ugPnnlMSTSceDKY9goGvjmqc6eQLPUSUeNXdpeXA=
 github.com/vmware/govmomi v0.30.0/go.mod h1:F7adsVewLNHsW/IIm7ziFURaXDaHEwcc+ym4r3INMdY=
 github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8=
@@ -832,9 +825,8 @@ golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
 golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -953,9 +945,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
-golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -963,9 +954,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
 golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
-golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
-golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -979,9 +969,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
-golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
-golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1202,9 +1191,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
 google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
-google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
-google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/pkg/apis/nfd/v1alpha1/feature.go b/pkg/apis/nfd/v1alpha1/feature.go
index 6f112d83e..8615cdf64 100644
--- a/pkg/apis/nfd/v1alpha1/feature.go
+++ b/pkg/apis/nfd/v1alpha1/feature.go
@@ -16,15 +16,6 @@ limitations under the License.
 
 package v1alpha1
 
-// NewNodeFeatureSpec creates a new emprty instance of NodeFeatureSpec type,
-// initializing all fields to proper empty values.
-func NewNodeFeatureSpec() *NodeFeatureSpec {
-	return &NodeFeatureSpec{
-		Features: *NewFeatures(),
-		Labels:   make(map[string]string),
-	}
-}
-
 // NewFeatures creates a new instance of Features, initializing all feature
 // types (flags, attributes and instances) to empty values.
 func NewFeatures() *Features {
@@ -65,11 +56,7 @@ func NewInstanceFeature(attrs map[string]string) *InstanceFeature {
 }
 
 // InsertAttributeFeatures inserts new values into a specific feature.
-// TODO: add unit tests
 func (f *Features) InsertAttributeFeatures(domain, feature string, values map[string]string) {
-	if f.Attributes == nil {
-		f.Attributes = make(map[string]AttributeFeatureSet)
-	}
 	key := domain + "." + feature
 	if _, ok := f.Attributes[key]; !ok {
 		f.Attributes[key] = NewAttributeFeatures(values)
@@ -83,7 +70,6 @@ func (f *Features) InsertAttributeFeatures(domain, feature string, values map[st
 
 // Exists returns a non-empty string if a feature exists. The return value is
 // the type of the feautre, i.e. "flag", "attribute" or "instance".
-// TODO: add unit tests
 func (f *Features) Exists(name string) string {
 	if _, ok := f.Flags[name]; ok {
 		return "flag"
@@ -99,7 +85,6 @@ func (f *Features) Exists(name string) string {
 
 // MergeInto merges two FeatureSpecs into one. Data in the input object takes
 // precedence (overwrite) over data of the existing object we're merging into.
-// TODO: add unit tests
 func (in *NodeFeatureSpec) MergeInto(out *NodeFeatureSpec) {
 	in.Features.MergeInto(&out.Features)
 	if in.Labels != nil {
@@ -115,7 +100,6 @@ func (in *NodeFeatureSpec) MergeInto(out *NodeFeatureSpec) {
 // MergeInto merges two sets of features into one. Features from the input set
 // take precedence (overwrite) features from the existing features of the set
 // we're merging into.
-// TODO: add unit tests
 func (in *Features) MergeInto(out *Features) {
 	if in.Flags != nil {
 		if out.Flags == nil {
@@ -150,7 +134,6 @@ func (in *Features) MergeInto(out *Features) {
 }
 
 // MergeInto merges two sets of flag featues.
-// TODO: add unit tests
 func (in *FlagFeatureSet) MergeInto(out *FlagFeatureSet) {
 	if in.Elements != nil {
 		if out.Elements == nil {
@@ -163,7 +146,6 @@ func (in *FlagFeatureSet) MergeInto(out *FlagFeatureSet) {
 }
 
 // MergeInto merges two sets of attribute featues.
-// TODO: add unit tests
 func (in *AttributeFeatureSet) MergeInto(out *AttributeFeatureSet) {
 	if in.Elements != nil {
 		if out.Elements == nil {
@@ -176,7 +158,6 @@ func (in *AttributeFeatureSet) MergeInto(out *AttributeFeatureSet) {
 }
 
 // MergeInto merges two sets of instance featues.
-// TODO: add unit tests
 func (in *InstanceFeatureSet) MergeInto(out *InstanceFeatureSet) {
 	if in.Elements != nil {
 		if out.Elements == nil {
diff --git a/pkg/nfd-master/nfd-api-controller.go b/pkg/nfd-master/nfd-api-controller.go
index 714037afb..807f72b48 100644
--- a/pkg/nfd-master/nfd-api-controller.go
+++ b/pkg/nfd-master/nfd-api-controller.go
@@ -41,12 +41,7 @@ type nfdController struct {
 	updateOneNodeChan  chan string
 }
 
-type nfdApiControllerOptions struct {
-	disableNodeFeature bool
-	resyncPeriod       time.Duration
-}
-
-func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiControllerOptions) (*nfdController, error) {
+func newNfdController(config *restclient.Config, disableNodeFeature bool) (*nfdController, error) {
 	c := &nfdController{
 		stopChan:           make(chan struct{}, 1),
 		updateAllNodesChan: make(chan struct{}, 1),
@@ -54,10 +49,11 @@ func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiC
 	}
 
 	nfdClient := nfdclientset.NewForConfigOrDie(config)
-	informerFactory := nfdinformers.NewSharedInformerFactory(nfdClient, nfdApiControllerOptions.resyncPeriod)
+
+	informerFactory := nfdinformers.NewSharedInformerFactory(nfdClient, 1*time.Hour)
 
 	// Add informer for NodeFeature objects
-	if !nfdApiControllerOptions.disableNodeFeature {
+	if !disableNodeFeature {
 		featureInformer := informerFactory.Nfd().V1alpha1().NodeFeatures()
 		if _, err := featureInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
 			AddFunc: func(obj interface{}) {
@@ -87,7 +83,7 @@ func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiC
 		AddFunc: func(object interface{}) {
 			key, _ := cache.MetaNamespaceKeyFunc(object)
 			klog.V(2).Infof("NodeFeatureRule %v added", key)
-			if !nfdApiControllerOptions.disableNodeFeature {
+			if !disableNodeFeature {
 				c.updateAllNodes()
 			}
 			// else: rules will be processed only when gRPC requests are received
@@ -95,7 +91,7 @@ func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiC
 		UpdateFunc: func(oldObject, newObject interface{}) {
 			key, _ := cache.MetaNamespaceKeyFunc(newObject)
 			klog.V(2).Infof("NodeFeatureRule %v updated", key)
-			if !nfdApiControllerOptions.disableNodeFeature {
+			if !disableNodeFeature {
 				c.updateAllNodes()
 			}
 			// else: rules will be processed only when gRPC requests are received
@@ -103,7 +99,7 @@ func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiC
 		DeleteFunc: func(object interface{}) {
 			key, _ := cache.MetaNamespaceKeyFunc(object)
 			klog.V(2).Infof("NodeFeatureRule %v deleted", key)
-			if !nfdApiControllerOptions.disableNodeFeature {
+			if !disableNodeFeature {
 				c.updateAllNodes()
 			}
 			// else: rules will be processed only when gRPC requests are received
diff --git a/pkg/nfd-master/nfd-master.go b/pkg/nfd-master/nfd-master.go
index df5cd6c28..21776549b 100644
--- a/pkg/nfd-master/nfd-master.go
+++ b/pkg/nfd-master/nfd-master.go
@@ -70,7 +70,6 @@ type NFDConfig struct {
 	NoPublish      bool
 	ResourceLabels utils.StringSetVal
 	EnableTaints   bool
-	ResyncPeriod   utils.DurationVal
 }
 
 // ConfigOverrideArgs are args that override config file options
@@ -81,7 +80,6 @@ type ConfigOverrideArgs struct {
 	ResourceLabels *utils.StringSetVal
 	EnableTaints   *bool
 	NoPublish      *bool
-	ResyncPeriod   *utils.DurationVal
 }
 
 // Args holds command line arguments
@@ -98,7 +96,6 @@ type Args struct {
 	Prune                bool
 	VerifyNodeName       bool
 	Options              string
-	ResyncPeriod         utils.DurationVal
 
 	Overrides ConfigOverrideArgs
 }
@@ -175,7 +172,6 @@ func newDefaultConfig() *NFDConfig {
 		NoPublish:      false,
 		ResourceLabels: utils.StringSetVal{},
 		EnableTaints:   false,
-		ResyncPeriod:   utils.DurationVal{Duration: time.Duration(1) * time.Hour},
 	}
 }
 
@@ -189,11 +185,6 @@ func (m *nfdMaster) Run() error {
 	klog.Infof("NodeName: %q", m.nodeName)
 	klog.Infof("Kubernetes namespace: %q", m.namespace)
 
-	// Read initial configuration
-	if err := m.configure(m.configFilePath, m.args.Options); err != nil {
-		return err
-	}
-
 	if m.args.Prune {
 		return m.prune()
 	}
@@ -204,20 +195,20 @@ func (m *nfdMaster) Run() error {
 			return err
 		}
 		klog.Info("starting nfd api controller")
-		m.nfdController, err = newNfdController(kubeconfig, nfdApiControllerOptions{
-			disableNodeFeature: !m.args.EnableNodeFeatureApi,
-			resyncPeriod:       m.args.ResyncPeriod.Duration,
-		})
+		m.nfdController, err = newNfdController(kubeconfig, !m.args.EnableNodeFeatureApi)
 		if err != nil {
 			return fmt.Errorf("failed to initialize CRD controller: %w", err)
 		}
 	}
 
-	// Create watcher for config file
+	// Create watcher for config file and read initial configuration
 	configWatch, err := utils.CreateFsWatcher(time.Second, m.configFilePath)
 	if err != nil {
 		return err
 	}
+	if err := m.configure(m.configFilePath, m.args.Options); err != nil {
+		return err
+	}
 
 	if !m.config.NoPublish {
 		err := m.updateMasterNode()
@@ -656,8 +647,7 @@ func (m *nfdMaster) nfdAPIUpdateOneNode(nodeName string) error {
 
 	klog.V(1).Infof("processing node %q, initiated by NodeFeature API", nodeName)
 
-	features := nfdv1alpha1.NewNodeFeatureSpec()
-
+	features := &nfdv1alpha1.NodeFeatureSpec{}
 	annotations := Annotations{}
 
 	if len(objs) > 0 {
@@ -721,10 +711,6 @@ func (m *nfdMaster) filterExtendedResources(features *nfdv1alpha1.Features, exte
 		if strings.HasPrefix(capacity, "@") {
 			// capacity is a string in the form of attribute.featureset.elements
 			split := strings.SplitN(capacity[1:], ".", 3)
-			if len(split) != 3 {
-				klog.Errorf("capacity %s is not in the form of '@domain.feature.element',. Ignoring Extended Resource %q", capacity, extendedResource)
-				continue
-			}
 			featureName := split[0] + "." + split[1]
 			elementName := split[2]
 			attrFeatureSet, ok := features.Attributes[featureName]
@@ -734,7 +720,7 @@ func (m *nfdMaster) filterExtendedResources(features *nfdv1alpha1.Features, exte
 			}
 			element, ok := attrFeatureSet.Elements[elementName]
 			if !ok {
-				klog.Errorf("element %s not found on feature %s. Ignoring Extended Resource %q", elementName, featureName, extendedResource)
+				klog.Errorf("element %s not foundon feature %s. Ignoring Extended Resource %q", elementName, featureName, extendedResource)
 				continue
 			}
 			q, err := k8sQuantity.ParseQuantity(element)
@@ -963,36 +949,27 @@ func (m *nfdMaster) updateNodeObject(cli *kubernetes.Clientset, nodeName string,
 	}
 
 	// Store names of labels in an annotation
-	if len(labels) > 0 {
-		labelKeys := make([]string, 0, len(labels))
-		for key := range labels {
-			// Drop the ns part for labels in the default ns
-			labelKeys = append(labelKeys, strings.TrimPrefix(key, nfdv1alpha1.FeatureLabelNs+"/"))
-		}
-		sort.Strings(labelKeys)
-		annotations[m.instanceAnnotation(nfdv1alpha1.FeatureLabelsAnnotation)] = strings.Join(labelKeys, ",")
+	labelKeys := make([]string, 0, len(labels))
+	for key := range labels {
+		// Drop the ns part for labels in the default ns
+		labelKeys = append(labelKeys, strings.TrimPrefix(key, nfdv1alpha1.FeatureLabelNs+"/"))
 	}
+	sort.Strings(labelKeys)
+	annotations[m.instanceAnnotation(nfdv1alpha1.FeatureLabelsAnnotation)] = strings.Join(labelKeys, ",")
 
 	// Store names of extended resources in an annotation
-	if len(extendedResources) > 0 {
-		extendedResourceKeys := make([]string, 0, len(extendedResources))
-		for key := range extendedResources {
-			// Drop the ns part if in the default ns
-			extendedResourceKeys = append(extendedResourceKeys, strings.TrimPrefix(key, nfdv1alpha1.FeatureLabelNs+"/"))
-		}
-		sort.Strings(extendedResourceKeys)
-		annotations[m.instanceAnnotation(nfdv1alpha1.ExtendedResourceAnnotation)] = strings.Join(extendedResourceKeys, ",")
+	extendedResourceKeys := make([]string, 0, len(extendedResources))
+	for key := range extendedResources {
+		// Drop the ns part if in the default ns
+		extendedResourceKeys = append(extendedResourceKeys, strings.TrimPrefix(key, nfdv1alpha1.FeatureLabelNs+"/"))
 	}
+	sort.Strings(extendedResourceKeys)
+	annotations[m.instanceAnnotation(nfdv1alpha1.ExtendedResourceAnnotation)] = strings.Join(extendedResourceKeys, ",")
 
 	// Create JSON patches for changes in labels and annotations
 	oldLabels := stringToNsNames(node.Annotations[m.instanceAnnotation(nfdv1alpha1.FeatureLabelsAnnotation)], nfdv1alpha1.FeatureLabelNs)
 	patches := createPatches(oldLabels, node.Labels, labels, "/metadata/labels")
-	patches = append(patches,
-		createPatches(
-			[]string{nfdv1alpha1.FeatureLabelsAnnotation, nfdv1alpha1.ExtendedResourceAnnotation},
-			node.Annotations,
-			annotations,
-			"/metadata/annotations")...)
+	patches = append(patches, createPatches(nil, node.Annotations, annotations, "/metadata/annotations")...)
 
 	// patch node status with extended resource changes
 	statusPatches := m.createExtendedResourcePatches(node, extendedResources)
@@ -1140,9 +1117,6 @@ func (m *nfdMaster) configure(filepath string, overrides string) error {
 	if m.args.Overrides.LabelWhiteList != nil {
 		c.LabelWhiteList = *m.args.Overrides.LabelWhiteList
 	}
-	if m.args.Overrides.ResyncPeriod != nil {
-		c.ResyncPeriod = *m.args.Overrides.ResyncPeriod
-	}
 
 	m.config = c
 	if !c.NoPublish {
diff --git a/pkg/nfd-topology-updater/kubeletnotifier/kubeletnotifier.go b/pkg/nfd-topology-updater/kubeletnotifier/kubeletnotifier.go
index c0bd78fa6..3d653b98b 100644
--- a/pkg/nfd-topology-updater/kubeletnotifier/kubeletnotifier.go
+++ b/pkg/nfd-topology-updater/kubeletnotifier/kubeletnotifier.go
@@ -18,6 +18,7 @@ package kubeletnotifier
 
 import (
 	"fmt"
+	"path"
 	"time"
 
 	"k8s.io/apimachinery/pkg/util/sets"
@@ -31,6 +32,8 @@ type EventType string
 const (
 	IntervalBased EventType = "intervalBased"
 	FSUpdate      EventType = "fsUpdate"
+
+	devicePluginsDirName = "device-plugins"
 )
 
 var stateFiles = sets.NewString(
@@ -51,7 +54,8 @@ type Info struct {
 }
 
 func New(sleepInterval time.Duration, dest chan<- Info, kubeletStateDir string) (*Notifier, error) {
-	ch, err := createFSWatcherEvent([]string{kubeletStateDir})
+	devicePluginsDir := path.Join(kubeletStateDir, devicePluginsDirName)
+	ch, err := createFSWatcherEvent([]string{kubeletStateDir, devicePluginsDir})
 	if err != nil {
 		return nil, err
 	}
@@ -77,8 +81,9 @@ func (n *Notifier) Run() {
 			n.dest <- i
 
 		case e := <-n.fsEvent:
-			klog.V(5).Infof("fsnotify event from file %q: %q received", e.Name, e.Op)
-			if stateFiles.Has(e.Name) {
+			basename := path.Base(e.Name)
+			klog.V(5).Infof("fsnotify event from file %q: %q received", basename, e.Op)
+			if stateFiles.Has(basename) {
 				i := Info{Event: FSUpdate}
 				n.dest <- i
 			}
diff --git a/pkg/nfd-worker/nfd-worker.go b/pkg/nfd-worker/nfd-worker.go
index 0be8d0dae..e7f9a46f5 100644
--- a/pkg/nfd-worker/nfd-worker.go
+++ b/pkg/nfd-worker/nfd-worker.go
@@ -80,7 +80,7 @@ type coreConfig struct {
 	FeatureSources []string
 	Sources        *[]string
 	LabelSources   []string
-	SleepInterval  utils.DurationVal
+	SleepInterval  duration
 }
 
 type sourcesConfig map[string]source.Config
@@ -127,6 +127,10 @@ type nfdWorker struct {
 	labelSources        []source.LabelSource
 }
 
+type duration struct {
+	time.Duration
+}
+
 // This ticker can represent infinite and normal intervals.
 type infiniteTicker struct {
 	*time.Ticker
@@ -165,7 +169,7 @@ func newDefaultConfig() *NFDConfig {
 	return &NFDConfig{
 		Core: coreConfig{
 			LabelWhiteList: utils.RegexpVal{Regexp: *regexp.MustCompile("")},
-			SleepInterval:  utils.DurationVal{Duration: 60 * time.Second},
+			SleepInterval:  duration{60 * time.Second},
 			FeatureSources: []string{"all"},
 			LabelSources:   []string{"all"},
 			Klog:           make(map[string]string),
@@ -365,7 +369,7 @@ func (c *coreConfig) sanitize() {
 	if c.SleepInterval.Duration > 0 && c.SleepInterval.Duration < time.Second {
 		klog.Warningf("too short sleep interval specified (%s), forcing to 1s",
 			c.SleepInterval.Duration.String())
-		c.SleepInterval = utils.DurationVal{Duration: time.Second}
+		c.SleepInterval = duration{time.Second}
 	}
 }
 
@@ -752,6 +756,27 @@ func (m *nfdWorker) getNfdClient() (*nfdclient.Clientset, error) {
 	return c, nil
 }
 
+// UnmarshalJSON implements the Unmarshaler interface from "encoding/json"
+func (d *duration) UnmarshalJSON(data []byte) error {
+	var v interface{}
+	if err := json.Unmarshal(data, &v); err != nil {
+		return err
+	}
+	switch val := v.(type) {
+	case float64:
+		d.Duration = time.Duration(val)
+	case string:
+		var err error
+		d.Duration, err = time.ParseDuration(val)
+		if err != nil {
+			return err
+		}
+	default:
+		return fmt.Errorf("invalid duration %s", data)
+	}
+	return nil
+}
+
 // UnmarshalJSON implements the Unmarshaler interface from "encoding/json"
 func (c *sourcesConfig) UnmarshalJSON(data []byte) error {
 	// First do a raw parse to get the per-source data
diff --git a/pkg/utils/flags.go b/pkg/utils/flags.go
index ab9379c05..47bd3f3df 100644
--- a/pkg/utils/flags.go
+++ b/pkg/utils/flags.go
@@ -23,7 +23,6 @@ import (
 	"regexp"
 	"sort"
 	"strings"
-	"time"
 )
 
 // RegexpVal is a wrapper for regexp command line flags
@@ -174,39 +173,3 @@ func NewKlogFlagVal(f *flag.Flag) *KlogFlagVal {
 type boolFlag interface {
 	IsBoolFlag() bool
 }
-
-// DurationVal is a wrapper for handling time.Duration as a command line flag
-type DurationVal struct {
-	time.Duration
-}
-
-// UnmarshalJSON implements the Unmarshaler interface from "encoding/json"
-func (d *DurationVal) UnmarshalJSON(data []byte) error {
-	var v interface{}
-	if err := json.Unmarshal(data, &v); err != nil {
-		return err
-	}
-	switch val := v.(type) {
-	case float64:
-		d.Duration = time.Duration(val)
-	case string:
-		var err error
-		d.Duration, err = time.ParseDuration(val)
-		if err != nil {
-			return err
-		}
-	default:
-		return fmt.Errorf("invalid duration %s", data)
-	}
-	return nil
-}
-
-// Set implements the flag.Value interface
-func (d *DurationVal) Set(val string) error {
-	m, err := time.ParseDuration(val)
-	if err != nil {
-		return err
-	}
-	*d = DurationVal{m}
-	return nil
-}


Test patch:
diff --git a/pkg/nfd-master/nfd-master-internal_test.go b/pkg/nfd-master/nfd-master-internal_test.go
index 52dab52b8..cf821c8e7 100644
--- a/pkg/nfd-master/nfd-master-internal_test.go
+++ b/pkg/nfd-master/nfd-master-internal_test.go
@@ -321,6 +321,7 @@ func TestSetLabels(t *testing.T) {
 			expectedPatches := []apihelper.JsonPatch{
 				apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.WorkerVersionAnnotation, workerVer),
 				apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.FeatureLabelsAnnotation, strings.Join(mockLabelNames, ",")),
+				apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.ExtendedResourceAnnotation, ""),
 			}
 			for k, v := range mockLabels {
 				expectedPatches = append(expectedPatches, apihelper.NewJsonPatch("add", "/metadata/labels", nfdv1alpha1.FeatureLabelNs+"/"+k, v))
@@ -340,6 +341,7 @@ func TestSetLabels(t *testing.T) {
 			expectedPatches := []apihelper.JsonPatch{
 				apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.WorkerVersionAnnotation, workerVer),
 				apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.FeatureLabelsAnnotation, "feature-2"),
+				apihelper.NewJsonPatch("add", "/metadata/annotations", nfdv1alpha1.ExtendedResourceAnnotation, ""),
 				apihelper.NewJsonPatch("add", "/metadata/labels", nfdv1alpha1.FeatureLabelNs+"/feature-2", mockLabels["feature-2"]),
 			}
 
@@ -372,6 +374,7 @@ func TestSetLabels(t *testing.T) {
 				apihelper.NewJsonPatch("add", "/metadata/annotations",
 					instance+"."+nfdv1alpha1.FeatureLabelsAnnotation,
 					"feature-1,valid.ns/feature-2,"+vendorFeatureLabel+","+vendorProfileLabel),
+				apihelper.NewJsonPatch("add", "/metadata/annotations", instance+"."+nfdv1alpha1.ExtendedResourceAnnotation, ""),
 				apihelper.NewJsonPatch("add", "/metadata/labels", nfdv1alpha1.FeatureLabelNs+"/feature-1", mockLabels["feature-1"]),
 				apihelper.NewJsonPatch("add", "/metadata/labels", "valid.ns/feature-2", mockLabels["valid.ns/feature-2"]),
 				apihelper.NewJsonPatch("add", "/metadata/labels", vendorFeatureLabel, mockLabels[vendorFeatureLabel]),
@@ -637,32 +640,24 @@ extraLabelNs: ["added.ns.io"]
 			// Update config and verify the effect
 			writeConfig(`
 extraLabelNs: ["override.ns.io"]
-resyncPeriod: '2h'
 `)
 			So(func() interface{} { return master.config.ExtraLabelNs },
 				withTimeout, 2*time.Second, ShouldResemble, utils.StringSetVal{"override.ns.io": struct{}{}})
-			So(func() interface{} { return master.config.ResyncPeriod.Duration },
-				withTimeout, 2*time.Second, ShouldResemble, time.Duration(2)*time.Hour)
 
 			// Removing config file should get back our defaults
 			err = os.RemoveAll(tmpDir)
 			So(err, ShouldBeNil)
 			So(func() interface{} { return master.config.ExtraLabelNs },
 				withTimeout, 2*time.Second, ShouldResemble, utils.StringSetVal{})
-			So(func() interface{} { return master.config.ResyncPeriod.Duration },
-				withTimeout, 2*time.Second, ShouldResemble, time.Duration(1)*time.Hour)
 
 			// Re-creating config dir and file should change the config
 			err = os.MkdirAll(configDir, 0755)
 			So(err, ShouldBeNil)
 			writeConfig(`
 extraLabelNs: ["another.override.ns"]
-resyncPeriod: '3m'
 `)
 			So(func() interface{} { return master.config.ExtraLabelNs },
 				withTimeout, 2*time.Second, ShouldResemble, utils.StringSetVal{"another.override.ns": struct{}{}})
-			So(func() interface{} { return master.config.ResyncPeriod.Duration },
-				withTimeout, 2*time.Second, ShouldResemble, time.Duration(3)*time.Minute)
 		})
 	})
 }