1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2024-12-14 11:57:51 +00:00

Allow to change labels namespace

The aim here is to allow to override the default namespace
of NFD. The allowed namespaces are whitelisted.
See https://github.com/kubernetes-sigs/node-feature-discovery/issues/227

Signed-off-by: Jordan Jacobelli <jjacobelli@nvidia.com>
This commit is contained in:
Jordan Jacobelli 2019-04-05 15:31:40 -07:00
parent d09a7cb77c
commit 40918827f6
No known key found for this signature in database
GPG key ID: 8148DA8F04B91641
6 changed files with 123 additions and 47 deletions

View file

@ -53,7 +53,7 @@ nfd-master.
Usage:
nfd-master [--no-publish] [--label-whitelist=<pattern>] [--port=<port>]
[--ca-file=<path>] [--cert-file=<path>] [--key-file=<path>]
[--verify-node-name]
[--verify-node-name] [--extra-label-ns=<list>]
nfd-master -h | --help
nfd-master --version
@ -74,6 +74,9 @@ nfd-master.
--no-publish Do not publish feature labels
--label-whitelist=<pattern> Regular expression to filter label names to
publish to the Kubernetes API server. [Default: ]
--extra-label-ns=<list> Comma separated list of allowed extra label namespaces
[Default: ]
```
### NFD-Worker
@ -368,8 +371,13 @@ slash (`/`) it is used as the label name as is, without any additional prefix.
This makes it possible for the user to fully control the feature label names,
e.g. for overriding labels created by other feature sources.
The value of the label is either `true` (for binary labels) or `<value>`
(for non-binary labels).
You can also override the default namespace of your labels using this format:
`<namespace>/<name>[=<value>]`. You must whitelist your namespace using the
`--extra-label-ns` option on the master. In this case, the name of the
file will not be added to the label name. For example, if you want to add the
label `my.namespace.org/my-label=value`, your hook output or file must contains
`my.namespace.org/my-label=value` and you must add
`--extra-label-ns=my.namespace.org` on the master command line.
`stderr` output of the hooks is propagated to NFD log so it can be used for
debugging and logging.
@ -383,6 +391,7 @@ MY_FEATURE_1
MY_FEATURE_2=myvalue
/override_source-OVERRIDE_BOOL
/override_source-OVERRIDE_VALUE=123
override.namespace/value=456
```
which, in turn, will translate into the following node labels:
```
@ -390,6 +399,7 @@ feature.node.kubernetes.io/my-source-MY_FEATURE_1=true
feature.node.kubernetes.io/my-source-MY_FEATURE_2=myvalue
feature.node.kubernetes.io/override_source-OVERRIDE_BOOL=true
feature.node.kubernetes.io/override_source-OVERRIDE_VALUE=123
override.namespace/value=456
```
**A file example:**<br/>
@ -401,6 +411,7 @@ MY_FEATURE_1
MY_FEATURE_2=myvalue
/override_source-OVERRIDE_BOOL
/override_source-OVERRIDE_VALUE=123
override.namespace/value=456
```
which, in turn, will translate into the following node labels:
```
@ -408,6 +419,7 @@ feature.node.kubernetes.io/my-source-MY_FEATURE_1=true
feature.node.kubernetes.io/my-source-MY_FEATURE_2=myvalue
feature.node.kubernetes.io/override_source-OVERRIDE_BOOL=true
feature.node.kubernetes.io/override_source-OVERRIDE_VALUE=123
override.namespace/value=456
```
NFD tries to run any regular files found from the hooks directory. Any

View file

@ -21,6 +21,7 @@ import (
"log"
"regexp"
"strconv"
"strings"
"github.com/docopt/docopt-go"
master "sigs.k8s.io/node-feature-discovery/pkg/nfd-master"
@ -64,7 +65,7 @@ func argsParse(argv []string) (master.Args, error) {
Usage:
%s [--no-publish] [--label-whitelist=<pattern>] [--port=<port>]
[--ca-file=<path>] [--cert-file=<path>] [--key-file=<path>]
[--verify-node-name]
[--verify-node-name] [--extra-label-ns=<list>]
%s -h | --help
%s --version
@ -84,7 +85,9 @@ func argsParse(argv []string) (master.Args, error) {
has been enabled.
--no-publish Do not publish feature labels
--label-whitelist=<pattern> Regular expression to filter label names to
publish to the Kubernetes API server. [Default: ]`,
publish to the Kubernetes API server. [Default: ]
--extra-label-ns=<list> Comma separated list of allowed extra label namespaces
[Default: ]`,
ProgramName,
ProgramName,
ProgramName,
@ -109,6 +112,7 @@ func argsParse(argv []string) (master.Args, error) {
return args, fmt.Errorf("error parsing whitelist regex (%s): %s", arguments["--label-whitelist"], err)
}
args.VerifyNodeName = arguments["--verify-node-name"].(bool)
args.ExtraLabelNs = strings.Split(arguments["--extra-label-ns"].(string), ",")
return args, nil
}

View file

@ -237,6 +237,29 @@ func TestSetLabels(t *testing.T) {
})
})
Convey("When --extra-label-ns is specified", func() {
mockServer.args.ExtraLabelNs = []string{"valid.ns"}
mockHelper.On("GetClient").Return(mockClient, nil)
mockHelper.On("GetNode", mockClient, workerName).Return(mockNode, nil)
mockHelper.On("UpdateNode", mockClient, mockNode).Return(nil)
mockLabels := map[string]string{"feature-1": "val-1",
"valid.ns/feature-2": "val-2",
"invalid.ns/feature-3": "val-3"}
mockReq := &labeler.SetLabelsRequest{NodeName: workerName, NfdVersion: workerVer, Labels: mockLabels}
_, err := mockServer.SetLabels(mockCtx, mockReq)
Convey("Error is nil", func() {
So(err, ShouldBeNil)
})
Convey("Node object should only have allowed label namespaces", func() {
So(len(mockNode.Labels), ShouldEqual, 2)
So(mockNode.Labels, ShouldResemble, map[string]string{labelNs + "feature-1": "val-1", "valid.ns/feature-2": "val-2"})
a := map[string]string{annotationNs + "worker.version": workerVer, annotationNs + "feature-labels": "feature-1,valid.ns/feature-2"}
So(len(mockNode.Annotations), ShouldEqual, len(a))
So(mockNode.Annotations, ShouldResemble, a)
})
})
mockErr := errors.New("mock-error")
Convey("When node update fails", func() {
mockHelper.On("GetClient").Return(mockClient, mockErr)

View file

@ -64,6 +64,7 @@ type Annotations map[string]string
type Args struct {
CaFile string
CertFile string
ExtraLabelNs []string
KeyFile string
LabelWhiteList *regexp.Regexp
NoPublish bool
@ -202,13 +203,30 @@ func updateMasterNode(helper apihelper.APIHelpers) error {
return nil
}
// Filter labels if whitelist has been defined
func filterFeatureLabels(labels Labels, labelWhiteList *regexp.Regexp) Labels {
for name := range labels {
// Filter labels by namespace and name whitelist
func filterFeatureLabels(labels Labels, extraLabelNs []string, labelWhiteList *regexp.Regexp) Labels {
for label := range labels {
split := strings.SplitN(label, "/", 2)
name := split[0]
// Check namespaced labels, filter out if ns is not whitelisted
if len(split) == 2 {
ns := split[0]
name = split[1]
for i, extraNs := range extraLabelNs {
if ns == extraNs {
break
} else if i == len(extraLabelNs)-1 {
stderrLogger.Printf("Namespace '%s' is not allowed. Ignoring label '%s'\n", ns, label)
delete(labels, label)
}
}
}
// Skip if label doesn't match labelWhiteList
if !labelWhiteList.MatchString(name) {
stderrLogger.Printf("%s does not match the whitelist (%s) and will not be published.", name, labelWhiteList.String())
delete(labels, name)
delete(labels, label)
}
}
return labels
@ -247,7 +265,7 @@ func (s *labelerServer) SetLabels(c context.Context, r *pb.SetLabelsRequest) (*p
}
stdoutLogger.Printf("REQUEST Node: %s NFD-version: %s Labels: %s", r.NodeName, r.NfdVersion, r.Labels)
labels := filterFeatureLabels(r.Labels, s.args.LabelWhiteList)
labels := filterFeatureLabels(r.Labels, s.args.ExtraLabelNs, s.args.LabelWhiteList)
if !s.args.NoPublish {
// Advertise NFD worker version and label names as annotations
@ -320,15 +338,23 @@ func removeLabelsWithPrefix(n *api.Node, search string) {
// Removes NFD labels from a Node object
func removeLabels(n *api.Node, labelNames []string) {
for _, l := range labelNames {
if strings.Contains(l, "/") {
delete(n.Labels, l)
} else {
delete(n.Labels, labelNs+l)
}
}
}
// Add NFD labels to a Node object.
func addLabels(n *api.Node, labels map[string]string) {
for k, v := range labels {
if strings.Contains(k, "/") {
n.Labels[k] = v
} else {
n.Labels[labelNs+k] = v
}
}
}
// Add Annotations to a Node object

View file

@ -324,7 +324,12 @@ func getFeatureLabels(source source.FeatureSource) (labels Labels, err error) {
label := prefix + k
// Validate label name. Use dummy namespace 'ns' because there is no
// function to validate just the name part
errs := validation.IsQualifiedName("ns/" + label)
labelName := "ns/" + label
// Do not use dummy namespace if there is already a namespace
if strings.Contains(label, "/") {
labelName = label
}
errs := validation.IsQualifiedName(labelName)
if len(errs) > 0 {
stderrLogger.Printf("Ignoring invalid feature name '%s': %s", label, errs)
continue

View file

@ -71,9 +71,15 @@ func parseFeatures(lines [][]byte, prefix string) source.Features {
lineSplit := strings.SplitN(string(line), "=", 2)
// Check if we need to add prefix
key := prefix + "-" + lineSplit[0]
var key string
if strings.Contains(lineSplit[0], "/") {
if lineSplit[0][0] == '/' {
key = lineSplit[0][1:]
} else {
key = lineSplit[0]
}
} else {
key = prefix + "-" + lineSplit[0]
}
// Check if it's a boolean value