1
0
Fork 0
mirror of https://github.com/kubernetes-sigs/node-feature-discovery.git synced 2025-03-13 20:30:03 +00:00

nfd-master: handle multiple NodeFeature objects

Implement handling of multiple NodeFeature objects by merging all
objects (targeting a certain node) into one before processing the data.
This patch implements MergeInto() methods for all required data types.

With support for multiple NodeFeature objects per node, The "nfd api
workflow" can be easily demonstrated and tested from the command line.
Creating the folloiwing object (assuming node-n exists in the cluster):

    apiVersion: nfd.k8s-sigs.io/v1alpha1
    kind: NodeFeature
    metadata:
      labels:
        nfd.node.kubernetes.io/node-name: node-n
      name: my-features-for-node-n
    spec:
      # Features for NodeFeatureRule matching
      features:
        flags:
          vendor.domain-a:
            elements:
              feature-x: {}
        attributes:
          vendor.domain-b:
            elements:
              feature-y: "foo"
              feature-z: "123"
        instances:
          vendor.domain-c:
            elements:
            - attributes:
                name: "elem-1"
                vendor: "acme"
            - attributes:
                name: "elem-2"
                vendor: "acme"
      # Labels to be created
      labels:
        vendor-feature.enabled: "true"
        vendor-setting.value: "100"

will create two feature labes:

    feature.node.kubernetes.io/vendor-feature.enabled: "true"
    feature.node.kubernetes.io/vendor-setting.value: "100"

In addition it will advertise hidden/raw features that can be used for
custom rules in NodeFeatureRule objects. Now, creating a NodeFeatureRule
object:

    apiVersion: nfd.k8s-sigs.io/v1alpha1
    kind: NodeFeatureRule
    metadata:
      name: my-rule
    spec:
      rules:
        - name: "my feature rule"
          labels:
            "my-feature": "true"
          matchFeatures:
            - feature: vendor.domain-a
              matchExpressions:
                feature-x: {op: Exists}
            - feature: vendor.domain-c
              matchExpressions:
                vendor: {op: In, value: ["acme"]}

will match the features in the NodeFeature object above and cause one
more label to be created:

    feature.node.kubernetes.io/my-feature: "true"
This commit is contained in:
Markus Lehtonen 2022-09-21 18:57:53 +03:00
parent ee0807da66
commit 79ed747be8
2 changed files with 95 additions and 7 deletions

View file

@ -82,3 +82,89 @@ func (f *Features) Exists(name string) string {
}
return ""
}
// MergeInto merges two FeatureSpecs into one. Data in the input object takes
// precedence (overwrite) over data of the existing object we're merging into.
func (in *NodeFeatureSpec) MergeInto(out *NodeFeatureSpec) {
in.Features.MergeInto(&out.Features)
if in.Labels != nil {
if out.Labels == nil {
out.Labels = make(map[string]string, len(in.Labels))
}
for key, val := range in.Labels {
out.Labels[key] = val
}
}
}
// 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.
func (in *Features) MergeInto(out *Features) {
if in.Flags != nil {
if out.Flags == nil {
out.Flags = make(map[string]FlagFeatureSet, len(in.Flags))
}
for key, val := range in.Flags {
outVal := out.Flags[key]
val.MergeInto(&outVal)
out.Flags[key] = outVal
}
}
if in.Attributes != nil {
if out.Attributes == nil {
out.Attributes = make(map[string]AttributeFeatureSet, len(in.Attributes))
}
for key, val := range in.Attributes {
outVal := out.Attributes[key]
val.MergeInto(&outVal)
out.Attributes[key] = outVal
}
}
if in.Instances != nil {
if out.Instances == nil {
out.Instances = make(map[string]InstanceFeatureSet, len(in.Instances))
}
for key, val := range in.Instances {
outVal := out.Instances[key]
val.MergeInto(&outVal)
out.Instances[key] = outVal
}
}
}
// MergeInto merges two sets of flag featues.
func (in *FlagFeatureSet) MergeInto(out *FlagFeatureSet) {
if in.Elements != nil {
if out.Elements == nil {
out.Elements = make(map[string]Nil, len(in.Elements))
}
for key, val := range in.Elements {
out.Elements[key] = val
}
}
}
// MergeInto merges two sets of attribute featues.
func (in *AttributeFeatureSet) MergeInto(out *AttributeFeatureSet) {
if in.Elements != nil {
if out.Elements == nil {
out.Elements = make(map[string]string, len(in.Elements))
}
for key, val := range in.Elements {
out.Elements[key] = val
}
}
}
// MergeInto merges two sets of instance featues.
func (in *InstanceFeatureSet) MergeInto(out *InstanceFeatureSet) {
if in.Elements != nil {
if out.Elements == nil {
out.Elements = make([]InstanceFeature, 0, len(in.Elements))
}
for _, e := range in.Elements {
out.Elements = append(out.Elements, *e.DeepCopy())
}
}
}

View file

@ -516,12 +516,14 @@ func (m *nfdMaster) nfdAPIUpdateOneNode(nodeName string) error {
// Merge in features
//
// TODO: support multiple NodeFeature objects. There are two obvious options to implement this:
// 1. Merge features of all objects into one joint object
// 2. Change the rule api to support handle multiple objects
// Of these #2 would probably perform better with lot less data to copy. We
// could probably even get rid of the DeepCopy in this scenario.
features := objs[0].DeepCopy()
// NOTE: changing the rule api to support handle multiple objects instead
// of merging would probably perform better with lot less data to copy.
features := objs[0].Spec.DeepCopy()
for _, o := range objs[1:] {
o.Spec.MergeInto(features)
}
utils.KlogDump(4, "Composite NodeFeatureSpec after merge:", " ", features)
annotations := Annotations{}
if objs[0].Namespace == m.namespace && objs[0].Name == nodeName {
@ -536,7 +538,7 @@ func (m *nfdMaster) nfdAPIUpdateOneNode(nodeName string) error {
if err != nil {
return err
}
if err := m.refreshNodeFeatures(cli, nodeName, annotations, features.Spec.Labels, &features.Spec.Features); err != nil {
if err := m.refreshNodeFeatures(cli, nodeName, annotations, features.Labels, &features.Features); err != nil {
return err
}