1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-16 21:38:23 +00:00

nfd-master: predictable handling of unprefixed names

Make the handling of unprefixed names (of labels, annotations and
extended resources) well-defined and predictable. Previously the
resulting output was not predictable in case the same name was coming in
both the unprefixed and prefixed form, say unprefixed "foo=bar" coming from
one source (be it nfd-worker or NodeFeature(Rule)) and
"feature.node.kubernetes.io/foo=baz" from a NodeFeature(Rule).
Previously the output value was randomly either "bar" or "baz".

This patch adds prefixes to all names early in the processing
"pipeline", preventing random name clashes later on.
This commit is contained in:
Markus Lehtonen 2023-11-23 20:07:44 +02:00
parent 678d7e89cb
commit dc5af8be04
3 changed files with 54 additions and 32 deletions

View file

@ -422,7 +422,8 @@ func TestSetLabels(t *testing.T) {
vendorFeatureLabel := "vendor." + nfdv1alpha1.FeatureLabelNs + "/feature-4" vendorFeatureLabel := "vendor." + nfdv1alpha1.FeatureLabelNs + "/feature-4"
vendorProfileLabel := "vendor." + nfdv1alpha1.ProfileLabelNs + "/feature-5" vendorProfileLabel := "vendor." + nfdv1alpha1.ProfileLabelNs + "/feature-5"
mockLabels := map[string]string{ mockLabels := map[string]string{
"feature-1": "val-1", "feature-1": "val-0",
"feature.node.kubernetes.io/feature-1": "val-1",
"valid.ns/feature-2": "val-2", "valid.ns/feature-2": "val-2",
"random.denied.ns/feature-3": "val-3", "random.denied.ns/feature-3": "val-3",
"kubernetes.io/feature-4": "val-4", "kubernetes.io/feature-4": "val-4",
@ -435,7 +436,7 @@ func TestSetLabels(t *testing.T) {
apihelper.NewJsonPatch("add", "/metadata/annotations", apihelper.NewJsonPatch("add", "/metadata/annotations",
instance+"."+nfdv1alpha1.FeatureLabelsAnnotation, instance+"."+nfdv1alpha1.FeatureLabelsAnnotation,
"feature-1,valid.ns/feature-2,"+vendorFeatureLabel+","+vendorProfileLabel), "feature-1,valid.ns/feature-2,"+vendorFeatureLabel+","+vendorProfileLabel),
apihelper.NewJsonPatch("add", "/metadata/labels", nfdv1alpha1.FeatureLabelNs+"/feature-1", mockLabels["feature-1"]), apihelper.NewJsonPatch("add", "/metadata/labels", "feature.node.kubernetes.io/feature-1", mockLabels["feature.node.kubernetes.io/feature-1"]),
apihelper.NewJsonPatch("add", "/metadata/labels", "valid.ns/feature-2", mockLabels["valid.ns/feature-2"]), apihelper.NewJsonPatch("add", "/metadata/labels", "valid.ns/feature-2", mockLabels["valid.ns/feature-2"]),
apihelper.NewJsonPatch("add", "/metadata/labels", vendorFeatureLabel, mockLabels[vendorFeatureLabel]), apihelper.NewJsonPatch("add", "/metadata/labels", vendorFeatureLabel, mockLabels[vendorFeatureLabel]),
apihelper.NewJsonPatch("add", "/metadata/labels", vendorProfileLabel, mockLabels[vendorProfileLabel]), apihelper.NewJsonPatch("add", "/metadata/labels", vendorProfileLabel, mockLabels[vendorProfileLabel]),
@ -468,7 +469,7 @@ func TestSetLabels(t *testing.T) {
apihelper.NewJsonPatch("add", "/status/capacity", nfdv1alpha1.FeatureLabelNs+"/feature-3", mockLabels["feature-3"]), apihelper.NewJsonPatch("add", "/status/capacity", nfdv1alpha1.FeatureLabelNs+"/feature-3", mockLabels["feature-3"]),
} }
mockMaster.config.ResourceLabels = map[string]struct{}{"feature-3": {}, "feature-1": {}} mockMaster.config.ResourceLabels = map[string]struct{}{"feature.node.kubernetes.io/feature-3": {}, "feature-1": {}}
mockHelper.On("GetClient").Return(mockClient, nil) mockHelper.On("GetClient").Return(mockClient, nil)
mockHelper.On("GetNode", mockClient, workerName).Return(mockNode, nil) mockHelper.On("GetNode", mockClient, workerName).Return(mockNode, nil)
mockHelper.On("PatchNodeStatus", mockClient, mockNodeName, mock.MatchedBy(jsonPatchMatcher(expectedStatusPatches))).Return(nil) mockHelper.On("PatchNodeStatus", mockClient, mockNodeName, mock.MatchedBy(jsonPatchMatcher(expectedStatusPatches))).Return(nil)
@ -503,7 +504,7 @@ func TestFilterLabels(t *testing.T) {
mockMaster := newMockMaster(mockHelper) mockMaster := newMockMaster(mockHelper)
Convey("When using dynamic values", t, func() { Convey("When using dynamic values", t, func() {
labelName := "testLabel" labelName := "ns/testLabel"
labelValue := "@test.feature.LSM" labelValue := "@test.feature.LSM"
features := nfdv1alpha1.Features{ features := nfdv1alpha1.Features{
Attributes: map[string]nfdv1alpha1.AttributeFeatureSet{ Attributes: map[string]nfdv1alpha1.AttributeFeatureSet{

View file

@ -523,9 +523,6 @@ func (m *nfdMaster) updateMasterNode() error {
func (m *nfdMaster) filterFeatureLabels(labels Labels, features *nfdv1alpha1.Features) (Labels, ExtendedResources) { func (m *nfdMaster) filterFeatureLabels(labels Labels, features *nfdv1alpha1.Features) (Labels, ExtendedResources) {
outLabels := Labels{} outLabels := Labels{}
for name, value := range labels { for name, value := range labels {
// Add possibly missing default ns
name := addNs(name, nfdv1alpha1.FeatureLabelNs)
if value, err := m.filterFeatureLabel(name, value, features); err != nil { if value, err := m.filterFeatureLabel(name, value, features); err != nil {
klog.ErrorS(err, "ignoring label", "labelKey", name, "labelValue", value) klog.ErrorS(err, "ignoring label", "labelKey", name, "labelValue", value)
nodeLabelsRejected.Inc() nodeLabelsRejected.Inc()
@ -537,8 +534,7 @@ func (m *nfdMaster) filterFeatureLabels(labels Labels, features *nfdv1alpha1.Fea
// Remove labels which are intended to be extended resources // Remove labels which are intended to be extended resources
extendedResources := ExtendedResources{} extendedResources := ExtendedResources{}
for extendedResourceName := range m.config.ResourceLabels { for extendedResourceName := range m.config.ResourceLabels {
// Add possibly missing default ns extendedResourceName := addNs(extendedResourceName, nfdv1alpha1.FeatureLabelNs)
extendedResourceName = addNs(extendedResourceName, nfdv1alpha1.FeatureLabelNs)
if value, ok := outLabels[extendedResourceName]; ok { if value, ok := outLabels[extendedResourceName]; ok {
if _, err := strconv.Atoi(value); err != nil { if _, err := strconv.Atoi(value); err != nil {
klog.ErrorS(err, "bad label value encountered for extended resource", "labelKey", extendedResourceName, "labelValue", value) klog.ErrorS(err, "bad label value encountered for extended resource", "labelKey", extendedResourceName, "labelValue", value)
@ -563,6 +559,9 @@ func (m *nfdMaster) filterFeatureLabel(name, value string, features *nfdv1alpha1
// Check label namespace, filter out if ns is not whitelisted // Check label namespace, filter out if ns is not whitelisted
ns, base := splitNs(name) ns, base := splitNs(name)
if ns == "" {
return "", fmt.Errorf("labels without namespace (prefix/) not allowed")
}
if ns != nfdv1alpha1.FeatureLabelNs && ns != nfdv1alpha1.ProfileLabelNs && if ns != nfdv1alpha1.FeatureLabelNs && ns != nfdv1alpha1.ProfileLabelNs &&
!strings.HasSuffix(ns, nfdv1alpha1.FeatureLabelSubNsSuffix) && !strings.HasSuffix(ns, nfdv1alpha1.ProfileLabelSubNsSuffix) { !strings.HasSuffix(ns, nfdv1alpha1.FeatureLabelSubNsSuffix) && !strings.HasSuffix(ns, nfdv1alpha1.ProfileLabelSubNsSuffix) {
// If the namespace is denied, and not present in the extraLabelNs, label will be ignored // If the namespace is denied, and not present in the extraLabelNs, label will be ignored
@ -764,8 +763,11 @@ func (m *nfdMaster) nfdAPIUpdateOneNode(nodeName string) error {
// NOTE: changing the rule api to support handle multiple objects instead // NOTE: changing the rule api to support handle multiple objects instead
// of merging would probably perform better with lot less data to copy. // of merging would probably perform better with lot less data to copy.
features = objs[0].Spec.DeepCopy() features = objs[0].Spec.DeepCopy()
features.Labels = addNsToMapKeys(features.Labels, nfdv1alpha1.FeatureLabelNs)
for _, o := range objs[1:] { for _, o := range objs[1:] {
o.Spec.MergeInto(features) s := o.Spec.DeepCopy()
s.Labels = addNsToMapKeys(s.Labels, nfdv1alpha1.FeatureLabelNs)
s.MergeInto(features)
} }
klog.V(4).InfoS("merged nodeFeatureSpecs", "newNodeFeatureSpec", utils.DelayedDumper(features)) klog.V(4).InfoS("merged nodeFeatureSpecs", "newNodeFeatureSpec", utils.DelayedDumper(features))
@ -790,9 +792,6 @@ func (m *nfdMaster) nfdAPIUpdateOneNode(nodeName string) error {
func filterExtendedResources(features *nfdv1alpha1.Features, extendedResources ExtendedResources) ExtendedResources { func filterExtendedResources(features *nfdv1alpha1.Features, extendedResources ExtendedResources) ExtendedResources {
outExtendedResources := ExtendedResources{} outExtendedResources := ExtendedResources{}
for name, value := range extendedResources { for name, value := range extendedResources {
// Add possibly missing default ns
name = addNs(name, nfdv1alpha1.ExtendedResourceNs)
capacity, err := filterExtendedResource(name, value, features) capacity, err := filterExtendedResource(name, value, features)
if err != nil { if err != nil {
klog.ErrorS(err, "failed to create extended resources", "extendedResourceName", name, "extendedResourceValue", value) klog.ErrorS(err, "failed to create extended resources", "extendedResourceName", name, "extendedResourceValue", value)
@ -807,6 +806,9 @@ func filterExtendedResources(features *nfdv1alpha1.Features, extendedResources E
func filterExtendedResource(name, value string, features *nfdv1alpha1.Features) (string, error) { func filterExtendedResource(name, value string, features *nfdv1alpha1.Features) (string, error) {
// Check if given NS is allowed // Check if given NS is allowed
ns, _ := splitNs(name) ns, _ := splitNs(name)
if ns == "" {
return "", fmt.Errorf("extended resource without namespace (prefix/) not allowed")
}
if ns != nfdv1alpha1.ExtendedResourceNs && !strings.HasSuffix(ns, nfdv1alpha1.ExtendedResourceSubNsSuffix) { if ns != nfdv1alpha1.ExtendedResourceNs && !strings.HasSuffix(ns, nfdv1alpha1.ExtendedResourceSubNsSuffix) {
if ns == "kubernetes.io" || strings.HasSuffix(ns, ".kubernetes.io") { if ns == "kubernetes.io" || strings.HasSuffix(ns, ".kubernetes.io") {
return "", fmt.Errorf("namespace %q is not allowed", ns) return "", fmt.Errorf("namespace %q is not allowed", ns)
@ -834,9 +836,7 @@ func filterExtendedResource(name, value string, features *nfdv1alpha1.Features)
} }
func (m *nfdMaster) refreshNodeFeatures(cli *kubernetes.Clientset, nodeName string, labels map[string]string, features *nfdv1alpha1.Features) error { func (m *nfdMaster) refreshNodeFeatures(cli *kubernetes.Clientset, nodeName string, labels map[string]string, features *nfdv1alpha1.Features) error {
if labels == nil { labels = addNsToMapKeys(labels, nfdv1alpha1.FeatureLabelNs)
labels = make(map[string]string)
}
crLabels, crAnnotations, crExtendedResources, crTaints := m.processNodeFeatureRule(nodeName, features) crLabels, crAnnotations, crExtendedResources, crTaints := m.processNodeFeatureRule(nodeName, features)
@ -1011,13 +1011,13 @@ func (m *nfdMaster) processNodeFeatureRule(nodeName string, features *nfdv1alpha
continue continue
} }
taints = append(taints, ruleOut.Taints...) taints = append(taints, ruleOut.Taints...)
for k, v := range ruleOut.Labels { for k, v := range addNsToMapKeys(ruleOut.Labels, nfdv1alpha1.FeatureLabelNs) {
labels[k] = v labels[k] = v
} }
for k, v := range ruleOut.ExtendedResources { for k, v := range addNsToMapKeys(ruleOut.ExtendedResources, nfdv1alpha1.ExtendedResourceNs) {
extendedResources[k] = v extendedResources[k] = v
} }
for k, v := range ruleOut.Annotations { for k, v := range addNsToMapKeys(ruleOut.Annotations, nfdv1alpha1.FeatureAnnotationNs) {
annotations[k] = v annotations[k] = v
} }
@ -1285,6 +1285,25 @@ func (m *nfdMaster) configure(filepath string, overrides string) error {
return nil return nil
} }
// addNsToMapKeys creates a copy of a map with the namespace (prefix) added to
// unprefixed keys. Prefixed keys in the input map will take presedence, i.e.
// if the input contains both prefixed (say "prefix/name") and unprefixed
// ("name") name the unprefixed key will be ignored.
func addNsToMapKeys(in map[string]string, nsToAdd string) map[string]string {
out := make(map[string]string, len(in))
for k, v := range in {
if strings.Contains(k, "/") {
out[k] = v
} else {
fqn := path.Join(nsToAdd, k)
if _, ok := in[fqn]; !ok {
out[fqn] = v
}
}
}
return out
}
// addNs adds a namespace if one isn't already found from src string // addNs adds a namespace if one isn't already found from src string
func addNs(src string, nsToAdd string) string { func addNs(src string, nsToAdd string) string {
if strings.Contains(src, "/") { if strings.Contains(src, "/") {
@ -1404,11 +1423,11 @@ func (m *nfdMaster) filterFeatureAnnotations(annotations map[string]string) map[
outAnnotations := make(map[string]string) outAnnotations := make(map[string]string)
for annotation, value := range annotations { for annotation, value := range annotations {
// Add possibly missing default ns
annotation := addNs(annotation, nfdv1alpha1.FeatureAnnotationNs)
ns, _ := splitNs(annotation) ns, _ := splitNs(annotation)
if ns == "" {
klog.ErrorS(fmt.Errorf("annotations without namespace (prefix/) not allowed"), fmt.Sprintf("Ignoring annotation %s", annotation))
continue
}
// Check annotation namespace, filter out if ns is not whitelisted // Check annotation namespace, filter out if ns is not whitelisted
if ns != nfdv1alpha1.FeatureAnnotationNs && !strings.HasSuffix(ns, nfdv1alpha1.FeatureAnnotationSubNsSuffix) { if ns != nfdv1alpha1.FeatureAnnotationNs && !strings.HasSuffix(ns, nfdv1alpha1.FeatureAnnotationSubNsSuffix) {
// If the namespace is denied, and not present in the extraLabelNs, label will be ignored // If the namespace is denied, and not present in the extraLabelNs, label will be ignored

View file

@ -25,7 +25,9 @@ spec:
attr_2: "9" attr_2: "9"
# Labels to be created # Labels to be created
labels: labels:
e2e-nodefeature-test-1: "obj-1" e2e-nodefeature-test-1: "foo"
# The prefixed name should take precedence over the non-prefixed name above
feature.node.kubernetes.io/e2e-nodefeature-test-1: "obj-1"
e2e-nodefeature-test-2: "obj-1" e2e-nodefeature-test-2: "obj-1"
# Override feature from nfd-worker # Override feature from nfd-worker
fake-fakefeature3: "overridden" fake-fakefeature3: "overridden"