Skip to content

Commit

Permalink
Merge pull request #1285 from AhmedGrati/feat-add-expiry-date-feature…
Browse files Browse the repository at this point in the history
…-files

Feat: add expiry date for feature files
  • Loading branch information
k8s-ci-robot committed Sep 5, 2023
2 parents 8a1facd + 47aec15 commit 0b218a1
Show file tree
Hide file tree
Showing 8 changed files with 160 additions and 4 deletions.
32 changes: 32 additions & 0 deletions docs/usage/customization-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,38 @@ Label namespace may be specified with `<namespace>/<name>[=<value>]`.

Comment lines (starting with `#`) are ignored.

Adding following line anywhere to feature file defines date when
its content expires / is ignored:

```plaintext
# +expiry-time=2023-07-29T11:22:33Z
```

Also, the expiry-time value would stay the same during the processing of the
feature file until another expiry-time directive is encountered.
Considering the following file:

```plaintext
# +expiry-time=2012-07-28T11:22:33Z
featureKey=featureValue
# +expiry-time=2080-07-28T11:22:33Z
featureKey2=featureValue2
# +expiry-time=2070-07-28T11:22:33Z
featureKey3=featureValue3
# +expiry-time=2002-07-28T11:22:33Z
featureKey4=featureValue4
```

After processing the above file, only `featureKey2` and `featureKey3` would be
included in the list of accepted features.

> **NOTE:** The time format that we are supporting is RFC3339. Also, the `expiry-time`
> tag is only evaluated in each re-discovery period, and the expiration of
> node labels is not tracked.
### Mounts

The standard NFD deployments contain `hostPath` mounts for
Expand Down
64 changes: 60 additions & 4 deletions source/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"os/exec"
"path/filepath"
"strings"
"time"

"k8s.io/klog/v2"

Expand All @@ -37,6 +38,13 @@ const Name = "local"
// LabelFeature of this feature source
const LabelFeature = "label"

// ExpiryTimeKey is the key of this feature source indicating
// when features should be removed.
const ExpiryTimeKey = "expiry-time"

// DirectivePrefix defines the prefix of directives that should be parsed
const DirectivePrefix = "# +"

// Config
var (
featureFilesDir = "/etc/kubernetes/node-feature-discovery/features.d/"
Expand All @@ -53,6 +61,11 @@ type Config struct {
HooksEnabled bool `json:"hooksEnabled,omitempty"`
}

// parsingOpts contains options used for directives parsing
type parsingOpts struct {
ExpiryTime time.Time
}

// Singleton source instance
var (
src = localSource{config: newDefaultConfig()}
Expand Down Expand Up @@ -144,14 +157,56 @@ func (s *localSource) GetFeatures() *nfdv1alpha1.Features {
return s.features
}

func parseFeatures(lines [][]byte) map[string]string {
func parseDirectives(line string, opts *parsingOpts) error {
if !strings.HasPrefix(line, DirectivePrefix) {
return nil
}

directive := line[len(DirectivePrefix):]
split := strings.SplitN(directive, "=", 2)
key := split[0]

if len(split) == 1 {
return fmt.Errorf("invalid directive format in %q, should be '# +key=value'", line)
}
value := split[1]

switch key {
case ExpiryTimeKey:
expiryDate, err := time.Parse(time.RFC3339, strings.TrimSpace(value))
if err != nil {
return fmt.Errorf("failed to parse expiry-date directive: %w", err)
}
opts.ExpiryTime = expiryDate
default:
return fmt.Errorf("unknown feature file directive %q", key)
}

return nil
}

func parseFeatures(lines [][]byte, fileName string) map[string]string {
features := make(map[string]string)
now := time.Now()
parsingOpts := &parsingOpts{
ExpiryTime: now,
}

for _, l := range lines {
line := strings.TrimSpace(string(l))
if len(line) > 0 {
// Skip comment lines
if strings.HasPrefix(line, "#") {
// Parse directives
err := parseDirectives(line, parsingOpts)
if err != nil {
klog.ErrorS(err, "error while parsing directives", "fileName", fileName)
}

continue
}

// handle expiration
if parsingOpts.ExpiryTime.Before(now) {
continue
}

Expand Down Expand Up @@ -197,7 +252,7 @@ func getFeaturesFromHooks() (map[string]string, error) {
}

// Append features
fileFeatures := parseFeatures(lines)
fileFeatures := parseFeatures(lines, fileName)
klog.V(4).InfoS("hook executed", "fileName", fileName, "features", utils.DelayedDumper(fileFeatures))
for k, v := range fileFeatures {
if old, ok := features[k]; ok {
Expand Down Expand Up @@ -273,7 +328,8 @@ func getFeaturesFromFiles() (map[string]string, error) {
}

// Append features
fileFeatures := parseFeatures(lines)
fileFeatures := parseFeatures(lines, fileName)

klog.V(4).InfoS("feature file read", "fileName", fileName, "features", utils.DelayedDumper(fileFeatures))
for k, v := range fileFeatures {
if old, ok := features[k]; ok {
Expand Down
49 changes: 49 additions & 0 deletions source/local/local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ limitations under the License.
package local

import (
"fmt"
"os"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/assert"
)
Expand All @@ -33,3 +37,48 @@ func TestLocalSource(t *testing.T) {
assert.Empty(t, l)

}

func TestGetExpirationDate(t *testing.T) {
expectedFeaturesLen := 5
pwd, _ := os.Getwd()
featureFilesDir = filepath.Join(pwd, "testdata/features.d")

features, err := getFeaturesFromFiles()
fmt.Println(features)
assert.NoError(t, err)
assert.Equal(t, expectedFeaturesLen, len(features))
}

func TestParseDirectives(t *testing.T) {
testCases := []struct {
name string
directive string
wantErr bool
}{
{
name: "valid directive",
directive: "# +expiry-time=2080-07-28T11:22:33Z",
wantErr: false,
},
{
name: "invalid directive",
directive: "# +random-key=random-value",
wantErr: true,
},
{
name: "invalid directive format",
directive: "# + Something",
wantErr: true,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
parsingOpts := parsingOpts{
ExpiryTime: time.Now(),
}
err := parseDirectives(tc.directive, &parsingOpts)
assert.Equal(t, err != nil, tc.wantErr)
})
}
}
2 changes: 2 additions & 0 deletions source/local/testdata/features.d/expired_feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# +expiry-time=2012-07-28T11:22:33Z
featureKeyExpired=featureValue
2 changes: 2 additions & 0 deletions source/local/testdata/features.d/feature_with_comments
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Notes: foo bar
featureKeyRandomComment=featureValue
11 changes: 11 additions & 0 deletions source/local/testdata/features.d/multiple_expiration_dates
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# +expiry-time=2012-07-28T11:22:33Z
featureKey=featureValue

# +expiry-time=2080-07-28T11:22:33Z
featureKey2=featureValue2

# +expiry-time=2070-07-28T11:22:33Z
featureKey3=featureValue3

# +expiry-time=2002-07-28T11:22:33Z
featureKey4=featureValue4
2 changes: 2 additions & 0 deletions source/local/testdata/features.d/unparsable_expiry_comment
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# +expiry-time=2080-07-28T11:22:33X
featureKeyUnparsable=featureValue
2 changes: 2 additions & 0 deletions source/local/testdata/features.d/valid_feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# +expiry-time=2080-07-28T11:22:33Z
featureKeyValid=featureValue

0 comments on commit 0b218a1

Please sign in to comment.