mirror of
https://github.com/kubernetes-sigs/node-feature-discovery.git
synced 2024-12-14 11:57:51 +00:00
Add NodeFeatureGroup CRD
The NodeFeatureGroup is an NFD-specific custom resource that is designed for grouping nodes based on their features. NFD-Master watches for NodeFeatureGroup objects in the cluster and updates the status of the NodeFeatureGroup object with the list of nodes that match the feature group rules. The NodeFeatureGroup rules follow the same syntax as the NodeFeatureRule rules. Signed-off-by: Carlos Eduardo Arango Gutierrez <eduardoa@nvidia.com>
This commit is contained in:
parent
6644b6a7f6
commit
47c054e1db
38 changed files with 2063 additions and 218 deletions
|
@ -4,8 +4,6 @@ FROM ${BUILDER_IMAGE} as builder
|
|||
# Install tools
|
||||
RUN go install github.com/vektra/mockery/v2@v2.42.0 && \
|
||||
go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.14.0 && \
|
||||
git clone https://github.com/kubernetes/code-generator -b v0.29.0 --depth 1 && \
|
||||
go install k8s.io/code-generator/cmd/go-to-protobuf/...@v0.29.0 && \
|
||||
go install golang.org/x/tools/cmd/goimports@v0.11.0 && \
|
||||
go install github.com/golang/protobuf/protoc-gen-go@v1.4.3
|
||||
|
||||
|
|
|
@ -32,6 +32,10 @@ func (c *FakeNfdV1alpha1) NodeFeatures(namespace string) v1alpha1.NodeFeatureInt
|
|||
return &FakeNodeFeatures{c, namespace}
|
||||
}
|
||||
|
||||
func (c *FakeNfdV1alpha1) NodeFeatureGroups(namespace string) v1alpha1.NodeFeatureGroupInterface {
|
||||
return &FakeNodeFeatureGroups{c, namespace}
|
||||
}
|
||||
|
||||
func (c *FakeNfdV1alpha1) NodeFeatureRules() v1alpha1.NodeFeatureRuleInterface {
|
||||
return &FakeNodeFeatureRules{c}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
v1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
// FakeNodeFeatureGroups implements NodeFeatureGroupInterface
|
||||
type FakeNodeFeatureGroups struct {
|
||||
Fake *FakeNfdV1alpha1
|
||||
ns string
|
||||
}
|
||||
|
||||
var nodefeaturegroupsResource = v1alpha1.SchemeGroupVersion.WithResource("nodefeaturegroups")
|
||||
|
||||
var nodefeaturegroupsKind = v1alpha1.SchemeGroupVersion.WithKind("NodeFeatureGroup")
|
||||
|
||||
// Get takes name of the nodeFeatureGroup, and returns the corresponding nodeFeatureGroup object, and an error if there is any.
|
||||
func (c *FakeNodeFeatureGroups) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.NodeFeatureGroup, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewGetAction(nodefeaturegroupsResource, c.ns, name), &v1alpha1.NodeFeatureGroup{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.NodeFeatureGroup), err
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of NodeFeatureGroups that match those selectors.
|
||||
func (c *FakeNodeFeatureGroups) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.NodeFeatureGroupList, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewListAction(nodefeaturegroupsResource, nodefeaturegroupsKind, c.ns, opts), &v1alpha1.NodeFeatureGroupList{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||
if label == nil {
|
||||
label = labels.Everything()
|
||||
}
|
||||
list := &v1alpha1.NodeFeatureGroupList{ListMeta: obj.(*v1alpha1.NodeFeatureGroupList).ListMeta}
|
||||
for _, item := range obj.(*v1alpha1.NodeFeatureGroupList).Items {
|
||||
if label.Matches(labels.Set(item.Labels)) {
|
||||
list.Items = append(list.Items, item)
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested nodeFeatureGroups.
|
||||
func (c *FakeNodeFeatureGroups) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
|
||||
return c.Fake.
|
||||
InvokesWatch(testing.NewWatchAction(nodefeaturegroupsResource, c.ns, opts))
|
||||
|
||||
}
|
||||
|
||||
// Create takes the representation of a nodeFeatureGroup and creates it. Returns the server's representation of the nodeFeatureGroup, and an error, if there is any.
|
||||
func (c *FakeNodeFeatureGroups) Create(ctx context.Context, nodeFeatureGroup *v1alpha1.NodeFeatureGroup, opts v1.CreateOptions) (result *v1alpha1.NodeFeatureGroup, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewCreateAction(nodefeaturegroupsResource, c.ns, nodeFeatureGroup), &v1alpha1.NodeFeatureGroup{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.NodeFeatureGroup), err
|
||||
}
|
||||
|
||||
// Update takes the representation of a nodeFeatureGroup and updates it. Returns the server's representation of the nodeFeatureGroup, and an error, if there is any.
|
||||
func (c *FakeNodeFeatureGroups) Update(ctx context.Context, nodeFeatureGroup *v1alpha1.NodeFeatureGroup, opts v1.UpdateOptions) (result *v1alpha1.NodeFeatureGroup, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateAction(nodefeaturegroupsResource, c.ns, nodeFeatureGroup), &v1alpha1.NodeFeatureGroup{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.NodeFeatureGroup), err
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *FakeNodeFeatureGroups) UpdateStatus(ctx context.Context, nodeFeatureGroup *v1alpha1.NodeFeatureGroup, opts v1.UpdateOptions) (*v1alpha1.NodeFeatureGroup, error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateSubresourceAction(nodefeaturegroupsResource, "status", c.ns, nodeFeatureGroup), &v1alpha1.NodeFeatureGroup{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.NodeFeatureGroup), err
|
||||
}
|
||||
|
||||
// Delete takes name of the nodeFeatureGroup and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeNodeFeatureGroups) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteActionWithOptions(nodefeaturegroupsResource, c.ns, name, opts), &v1alpha1.NodeFeatureGroup{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *FakeNodeFeatureGroups) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
|
||||
action := testing.NewDeleteCollectionAction(nodefeaturegroupsResource, c.ns, listOpts)
|
||||
|
||||
_, err := c.Fake.Invokes(action, &v1alpha1.NodeFeatureGroupList{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched nodeFeatureGroup.
|
||||
func (c *FakeNodeFeatureGroups) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NodeFeatureGroup, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceAction(nodefeaturegroupsResource, c.ns, name, pt, data, subresources...), &v1alpha1.NodeFeatureGroup{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v1alpha1.NodeFeatureGroup), err
|
||||
}
|
|
@ -20,4 +20,6 @@ package v1alpha1
|
|||
|
||||
type NodeFeatureExpansion interface{}
|
||||
|
||||
type NodeFeatureGroupExpansion interface{}
|
||||
|
||||
type NodeFeatureRuleExpansion interface{}
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
type NfdV1alpha1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
NodeFeaturesGetter
|
||||
NodeFeatureGroupsGetter
|
||||
NodeFeatureRulesGetter
|
||||
}
|
||||
|
||||
|
@ -41,6 +42,10 @@ func (c *NfdV1alpha1Client) NodeFeatures(namespace string) NodeFeatureInterface
|
|||
return newNodeFeatures(c, namespace)
|
||||
}
|
||||
|
||||
func (c *NfdV1alpha1Client) NodeFeatureGroups(namespace string) NodeFeatureGroupInterface {
|
||||
return newNodeFeatureGroups(c, namespace)
|
||||
}
|
||||
|
||||
func (c *NfdV1alpha1Client) NodeFeatureRules() NodeFeatureRuleInterface {
|
||||
return newNodeFeatureRules(c)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
rest "k8s.io/client-go/rest"
|
||||
scheme "sigs.k8s.io/node-feature-discovery/api/generated/clientset/versioned/scheme"
|
||||
v1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
// NodeFeatureGroupsGetter has a method to return a NodeFeatureGroupInterface.
|
||||
// A group's client should implement this interface.
|
||||
type NodeFeatureGroupsGetter interface {
|
||||
NodeFeatureGroups(namespace string) NodeFeatureGroupInterface
|
||||
}
|
||||
|
||||
// NodeFeatureGroupInterface has methods to work with NodeFeatureGroup resources.
|
||||
type NodeFeatureGroupInterface interface {
|
||||
Create(ctx context.Context, nodeFeatureGroup *v1alpha1.NodeFeatureGroup, opts v1.CreateOptions) (*v1alpha1.NodeFeatureGroup, error)
|
||||
Update(ctx context.Context, nodeFeatureGroup *v1alpha1.NodeFeatureGroup, opts v1.UpdateOptions) (*v1alpha1.NodeFeatureGroup, error)
|
||||
UpdateStatus(ctx context.Context, nodeFeatureGroup *v1alpha1.NodeFeatureGroup, opts v1.UpdateOptions) (*v1alpha1.NodeFeatureGroup, error)
|
||||
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
|
||||
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
|
||||
Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.NodeFeatureGroup, error)
|
||||
List(ctx context.Context, opts v1.ListOptions) (*v1alpha1.NodeFeatureGroupList, error)
|
||||
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NodeFeatureGroup, err error)
|
||||
NodeFeatureGroupExpansion
|
||||
}
|
||||
|
||||
// nodeFeatureGroups implements NodeFeatureGroupInterface
|
||||
type nodeFeatureGroups struct {
|
||||
client rest.Interface
|
||||
ns string
|
||||
}
|
||||
|
||||
// newNodeFeatureGroups returns a NodeFeatureGroups
|
||||
func newNodeFeatureGroups(c *NfdV1alpha1Client, namespace string) *nodeFeatureGroups {
|
||||
return &nodeFeatureGroups{
|
||||
client: c.RESTClient(),
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Get takes name of the nodeFeatureGroup, and returns the corresponding nodeFeatureGroup object, and an error if there is any.
|
||||
func (c *nodeFeatureGroups) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1alpha1.NodeFeatureGroup, err error) {
|
||||
result = &v1alpha1.NodeFeatureGroup{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("nodefeaturegroups").
|
||||
Name(name).
|
||||
VersionedParams(&options, scheme.ParameterCodec).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of NodeFeatureGroups that match those selectors.
|
||||
func (c *nodeFeatureGroups) List(ctx context.Context, opts v1.ListOptions) (result *v1alpha1.NodeFeatureGroupList, err error) {
|
||||
var timeout time.Duration
|
||||
if opts.TimeoutSeconds != nil {
|
||||
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||
}
|
||||
result = &v1alpha1.NodeFeatureGroupList{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("nodefeaturegroups").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Timeout(timeout).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested nodeFeatureGroups.
|
||||
func (c *nodeFeatureGroups) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
|
||||
var timeout time.Duration
|
||||
if opts.TimeoutSeconds != nil {
|
||||
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||
}
|
||||
opts.Watch = true
|
||||
return c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("nodefeaturegroups").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Timeout(timeout).
|
||||
Watch(ctx)
|
||||
}
|
||||
|
||||
// Create takes the representation of a nodeFeatureGroup and creates it. Returns the server's representation of the nodeFeatureGroup, and an error, if there is any.
|
||||
func (c *nodeFeatureGroups) Create(ctx context.Context, nodeFeatureGroup *v1alpha1.NodeFeatureGroup, opts v1.CreateOptions) (result *v1alpha1.NodeFeatureGroup, err error) {
|
||||
result = &v1alpha1.NodeFeatureGroup{}
|
||||
err = c.client.Post().
|
||||
Namespace(c.ns).
|
||||
Resource("nodefeaturegroups").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(nodeFeatureGroup).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a nodeFeatureGroup and updates it. Returns the server's representation of the nodeFeatureGroup, and an error, if there is any.
|
||||
func (c *nodeFeatureGroups) Update(ctx context.Context, nodeFeatureGroup *v1alpha1.NodeFeatureGroup, opts v1.UpdateOptions) (result *v1alpha1.NodeFeatureGroup, err error) {
|
||||
result = &v1alpha1.NodeFeatureGroup{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("nodefeaturegroups").
|
||||
Name(nodeFeatureGroup.Name).
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(nodeFeatureGroup).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclient:noStatus comment above the type to avoid generating UpdateStatus().
|
||||
func (c *nodeFeatureGroups) UpdateStatus(ctx context.Context, nodeFeatureGroup *v1alpha1.NodeFeatureGroup, opts v1.UpdateOptions) (result *v1alpha1.NodeFeatureGroup, err error) {
|
||||
result = &v1alpha1.NodeFeatureGroup{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("nodefeaturegroups").
|
||||
Name(nodeFeatureGroup.Name).
|
||||
SubResource("status").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(nodeFeatureGroup).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the nodeFeatureGroup and deletes it. Returns an error if one occurs.
|
||||
func (c *nodeFeatureGroups) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("nodefeaturegroups").
|
||||
Name(name).
|
||||
Body(&opts).
|
||||
Do(ctx).
|
||||
Error()
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *nodeFeatureGroups) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
|
||||
var timeout time.Duration
|
||||
if listOpts.TimeoutSeconds != nil {
|
||||
timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second
|
||||
}
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("nodefeaturegroups").
|
||||
VersionedParams(&listOpts, scheme.ParameterCodec).
|
||||
Timeout(timeout).
|
||||
Body(&opts).
|
||||
Do(ctx).
|
||||
Error()
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched nodeFeatureGroup.
|
||||
func (c *nodeFeatureGroups) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v1alpha1.NodeFeatureGroup, err error) {
|
||||
result = &v1alpha1.NodeFeatureGroup{}
|
||||
err = c.client.Patch(pt).
|
||||
Namespace(c.ns).
|
||||
Resource("nodefeaturegroups").
|
||||
Name(name).
|
||||
SubResource(subresources...).
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(data).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
|
@ -55,6 +55,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
|
|||
// Group=nfd.k8s-sigs.io, Version=v1alpha1
|
||||
case v1alpha1.SchemeGroupVersion.WithResource("nodefeatures"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Nfd().V1alpha1().NodeFeatures().Informer()}, nil
|
||||
case v1alpha1.SchemeGroupVersion.WithResource("nodefeaturegroups"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Nfd().V1alpha1().NodeFeatureGroups().Informer()}, nil
|
||||
case v1alpha1.SchemeGroupVersion.WithResource("nodefeaturerules"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Nfd().V1alpha1().NodeFeatureRules().Informer()}, nil
|
||||
|
||||
|
|
|
@ -26,6 +26,8 @@ import (
|
|||
type Interface interface {
|
||||
// NodeFeatures returns a NodeFeatureInformer.
|
||||
NodeFeatures() NodeFeatureInformer
|
||||
// NodeFeatureGroups returns a NodeFeatureGroupInformer.
|
||||
NodeFeatureGroups() NodeFeatureGroupInformer
|
||||
// NodeFeatureRules returns a NodeFeatureRuleInformer.
|
||||
NodeFeatureRules() NodeFeatureRuleInformer
|
||||
}
|
||||
|
@ -46,6 +48,11 @@ func (v *version) NodeFeatures() NodeFeatureInformer {
|
|||
return &nodeFeatureInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
}
|
||||
|
||||
// NodeFeatureGroups returns a NodeFeatureGroupInformer.
|
||||
func (v *version) NodeFeatureGroups() NodeFeatureGroupInformer {
|
||||
return &nodeFeatureGroupInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
}
|
||||
|
||||
// NodeFeatureRules returns a NodeFeatureRuleInformer.
|
||||
func (v *version) NodeFeatureRules() NodeFeatureRuleInformer {
|
||||
return &nodeFeatureRuleInformer{factory: v.factory, tweakListOptions: v.tweakListOptions}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
time "time"
|
||||
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
versioned "sigs.k8s.io/node-feature-discovery/api/generated/clientset/versioned"
|
||||
internalinterfaces "sigs.k8s.io/node-feature-discovery/api/generated/informers/externalversions/internalinterfaces"
|
||||
v1alpha1 "sigs.k8s.io/node-feature-discovery/api/generated/listers/nfd/v1alpha1"
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
// NodeFeatureGroupInformer provides access to a shared informer and lister for
|
||||
// NodeFeatureGroups.
|
||||
type NodeFeatureGroupInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() v1alpha1.NodeFeatureGroupLister
|
||||
}
|
||||
|
||||
type nodeFeatureGroupInformer struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewNodeFeatureGroupInformer constructs a new informer for NodeFeatureGroup type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewNodeFeatureGroupInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredNodeFeatureGroupInformer(client, namespace, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredNodeFeatureGroupInformer constructs a new informer for NodeFeatureGroup type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewFilteredNodeFeatureGroupInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||
return cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.NfdV1alpha1().NodeFeatureGroups(namespace).List(context.TODO(), options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.NfdV1alpha1().NodeFeatureGroups(namespace).Watch(context.TODO(), options)
|
||||
},
|
||||
},
|
||||
&nfdv1alpha1.NodeFeatureGroup{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *nodeFeatureGroupInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||
return NewFilteredNodeFeatureGroupInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *nodeFeatureGroupInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.factory.InformerFor(&nfdv1alpha1.NodeFeatureGroup{}, f.defaultInformer)
|
||||
}
|
||||
|
||||
func (f *nodeFeatureGroupInformer) Lister() v1alpha1.NodeFeatureGroupLister {
|
||||
return v1alpha1.NewNodeFeatureGroupLister(f.Informer().GetIndexer())
|
||||
}
|
|
@ -26,6 +26,14 @@ type NodeFeatureListerExpansion interface{}
|
|||
// NodeFeatureNamespaceLister.
|
||||
type NodeFeatureNamespaceListerExpansion interface{}
|
||||
|
||||
// NodeFeatureGroupListerExpansion allows custom methods to be added to
|
||||
// NodeFeatureGroupLister.
|
||||
type NodeFeatureGroupListerExpansion interface{}
|
||||
|
||||
// NodeFeatureGroupNamespaceListerExpansion allows custom methods to be added to
|
||||
// NodeFeatureGroupNamespaceLister.
|
||||
type NodeFeatureGroupNamespaceListerExpansion interface{}
|
||||
|
||||
// NodeFeatureRuleListerExpansion allows custom methods to be added to
|
||||
// NodeFeatureRuleLister.
|
||||
type NodeFeatureRuleListerExpansion interface{}
|
||||
|
|
99
api/generated/listers/nfd/v1alpha1/nodefeaturegroup.go
Normal file
99
api/generated/listers/nfd/v1alpha1/nodefeaturegroup.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
v1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
|
||||
)
|
||||
|
||||
// NodeFeatureGroupLister helps list NodeFeatureGroups.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type NodeFeatureGroupLister interface {
|
||||
// List lists all NodeFeatureGroups in the indexer.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []*v1alpha1.NodeFeatureGroup, err error)
|
||||
// NodeFeatureGroups returns an object that can list and get NodeFeatureGroups.
|
||||
NodeFeatureGroups(namespace string) NodeFeatureGroupNamespaceLister
|
||||
NodeFeatureGroupListerExpansion
|
||||
}
|
||||
|
||||
// nodeFeatureGroupLister implements the NodeFeatureGroupLister interface.
|
||||
type nodeFeatureGroupLister struct {
|
||||
indexer cache.Indexer
|
||||
}
|
||||
|
||||
// NewNodeFeatureGroupLister returns a new NodeFeatureGroupLister.
|
||||
func NewNodeFeatureGroupLister(indexer cache.Indexer) NodeFeatureGroupLister {
|
||||
return &nodeFeatureGroupLister{indexer: indexer}
|
||||
}
|
||||
|
||||
// List lists all NodeFeatureGroups in the indexer.
|
||||
func (s *nodeFeatureGroupLister) List(selector labels.Selector) (ret []*v1alpha1.NodeFeatureGroup, err error) {
|
||||
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v1alpha1.NodeFeatureGroup))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// NodeFeatureGroups returns an object that can list and get NodeFeatureGroups.
|
||||
func (s *nodeFeatureGroupLister) NodeFeatureGroups(namespace string) NodeFeatureGroupNamespaceLister {
|
||||
return nodeFeatureGroupNamespaceLister{indexer: s.indexer, namespace: namespace}
|
||||
}
|
||||
|
||||
// NodeFeatureGroupNamespaceLister helps list and get NodeFeatureGroups.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type NodeFeatureGroupNamespaceLister interface {
|
||||
// List lists all NodeFeatureGroups in the indexer for a given namespace.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []*v1alpha1.NodeFeatureGroup, err error)
|
||||
// Get retrieves the NodeFeatureGroup from the indexer for a given namespace and name.
|
||||
// Objects returned here must be treated as read-only.
|
||||
Get(name string) (*v1alpha1.NodeFeatureGroup, error)
|
||||
NodeFeatureGroupNamespaceListerExpansion
|
||||
}
|
||||
|
||||
// nodeFeatureGroupNamespaceLister implements the NodeFeatureGroupNamespaceLister
|
||||
// interface.
|
||||
type nodeFeatureGroupNamespaceLister struct {
|
||||
indexer cache.Indexer
|
||||
namespace string
|
||||
}
|
||||
|
||||
// List lists all NodeFeatureGroups in the indexer for a given namespace.
|
||||
func (s nodeFeatureGroupNamespaceLister) List(selector labels.Selector) (ret []*v1alpha1.NodeFeatureGroup, err error) {
|
||||
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v1alpha1.NodeFeatureGroup))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get retrieves the NodeFeatureGroup from the indexer for a given namespace and name.
|
||||
func (s nodeFeatureGroupNamespaceLister) Get(name string) (*v1alpha1.NodeFeatureGroup, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(v1alpha1.Resource("nodefeaturegroup"), name)
|
||||
}
|
||||
return obj.(*v1alpha1.NodeFeatureGroup), nil
|
||||
}
|
|
@ -42,6 +42,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
|||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&NodeFeature{},
|
||||
&NodeFeatureRule{},
|
||||
&NodeFeatureGroup{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
|
|
|
@ -131,6 +131,63 @@ type NodeFeatureRuleSpec struct {
|
|||
Rules []Rule `json:"rules"`
|
||||
}
|
||||
|
||||
// NodeFeatureGroup resource holds Node pools by featureGroup
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:resource:scope=Namespaced,shortName=nfg
|
||||
// +kubebuilder:subresource:status
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +genclient
|
||||
type NodeFeatureGroup struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec NodeFeatureGroupSpec `json:"spec"`
|
||||
Status NodeFeatureGroupStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// NodeFeatureGroupSpec describes a NodeFeatureGroup object.
|
||||
type NodeFeatureGroupSpec struct {
|
||||
Rules []GroupRule `json:"featureGroupRules"`
|
||||
}
|
||||
|
||||
type NodeFeatureGroupStatus struct {
|
||||
// Nodes is a list of FeatureGroupNode in the cluster that match the featureGroupRules
|
||||
// +optional
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
Nodes []FeatureGroupNode `json:"nodes"`
|
||||
}
|
||||
|
||||
type FeatureGroupNode struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// NodeFeatureGroupList contains a list of NodeFeatureGroup objects.
|
||||
// +kubebuilder:object:root=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type NodeFeatureGroupList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
|
||||
Items []NodeFeatureGroup `json:"items"`
|
||||
}
|
||||
|
||||
// GroupRule defines a rule for nodegroup filtering.
|
||||
type GroupRule struct {
|
||||
// Name of the rule.
|
||||
Name string `json:"name"`
|
||||
|
||||
// MatchFeatures specifies a set of matcher terms all of which must match.
|
||||
// +optional
|
||||
MatchFeatures FeatureMatcher `json:"matchFeatures"`
|
||||
|
||||
// MatchAny specifies a list of matchers one of which must match.
|
||||
// +optional
|
||||
MatchAny []MatchAnyElem `json:"matchAny"`
|
||||
}
|
||||
|
||||
// Rule defines a rule for node customization such as labeling.
|
||||
type Rule struct {
|
||||
// Name of the rule.
|
||||
|
|
|
@ -49,6 +49,22 @@ func (in *AttributeFeatureSet) DeepCopy() *AttributeFeatureSet {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *FeatureGroupNode) DeepCopyInto(out *FeatureGroupNode) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FeatureGroupNode.
|
||||
func (in *FeatureGroupNode) DeepCopy() *FeatureGroupNode {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(FeatureGroupNode)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in FeatureMatcher) DeepCopyInto(out *FeatureMatcher) {
|
||||
{
|
||||
|
@ -171,6 +187,36 @@ func (in *FlagFeatureSet) DeepCopy() *FlagFeatureSet {
|
|||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GroupRule) DeepCopyInto(out *GroupRule) {
|
||||
*out = *in
|
||||
if in.MatchFeatures != nil {
|
||||
in, out := &in.MatchFeatures, &out.MatchFeatures
|
||||
*out = make(FeatureMatcher, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.MatchAny != nil {
|
||||
in, out := &in.MatchAny, &out.MatchAny
|
||||
*out = make([]MatchAnyElem, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupRule.
|
||||
func (in *GroupRule) DeepCopy() *GroupRule {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GroupRule)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InstanceFeature) DeepCopyInto(out *InstanceFeature) {
|
||||
*out = *in
|
||||
|
@ -354,6 +400,111 @@ func (in *NodeFeature) DeepCopyObject() runtime.Object {
|
|||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NodeFeatureGroup) DeepCopyInto(out *NodeFeatureGroup) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureGroup.
|
||||
func (in *NodeFeatureGroup) DeepCopy() *NodeFeatureGroup {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NodeFeatureGroup)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *NodeFeatureGroup) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NodeFeatureGroupList) DeepCopyInto(out *NodeFeatureGroupList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]NodeFeatureGroup, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureGroupList.
|
||||
func (in *NodeFeatureGroupList) DeepCopy() *NodeFeatureGroupList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NodeFeatureGroupList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *NodeFeatureGroupList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NodeFeatureGroupSpec) DeepCopyInto(out *NodeFeatureGroupSpec) {
|
||||
*out = *in
|
||||
if in.Rules != nil {
|
||||
in, out := &in.Rules, &out.Rules
|
||||
*out = make([]GroupRule, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureGroupSpec.
|
||||
func (in *NodeFeatureGroupSpec) DeepCopy() *NodeFeatureGroupSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NodeFeatureGroupSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NodeFeatureGroupStatus) DeepCopyInto(out *NodeFeatureGroupStatus) {
|
||||
*out = *in
|
||||
if in.Nodes != nil {
|
||||
in, out := &in.Nodes, &out.Nodes
|
||||
*out = make([]FeatureGroupNode, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeFeatureGroupStatus.
|
||||
func (in *NodeFeatureGroupStatus) DeepCopy() *NodeFeatureGroupStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(NodeFeatureGroupStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *NodeFeatureList) DeepCopyInto(out *NodeFeatureList) {
|
||||
*out = *in
|
||||
|
|
|
@ -117,6 +117,271 @@ spec:
|
|||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: nodefeaturegroups.nfd.k8s-sigs.io
|
||||
spec:
|
||||
group: nfd.k8s-sigs.io
|
||||
names:
|
||||
kind: NodeFeatureGroup
|
||||
listKind: NodeFeatureGroupList
|
||||
plural: nodefeaturegroups
|
||||
shortNames:
|
||||
- nfg
|
||||
singular: nodefeaturegroup
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: NodeFeatureGroup resource holds Node pools by featureGroup
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: NodeFeatureGroupSpec describes a NodeFeatureGroup object.
|
||||
properties:
|
||||
featureGroupRules:
|
||||
items:
|
||||
description: GroupRule defines a rule for nodegroup filtering.
|
||||
properties:
|
||||
matchAny:
|
||||
description: MatchAny specifies a list of matchers one of which
|
||||
must match.
|
||||
items:
|
||||
description: MatchAnyElem specifies one sub-matcher of MatchAny.
|
||||
properties:
|
||||
matchFeatures:
|
||||
description: MatchFeatures specifies a set of matcher
|
||||
terms all of which must match.
|
||||
items:
|
||||
description: |-
|
||||
FeatureMatcherTerm defines requirements against one feature set. All
|
||||
requirements (specified as MatchExpressions) are evaluated against each
|
||||
element in the feature set.
|
||||
properties:
|
||||
feature:
|
||||
description: Feature is the name of the feature
|
||||
set to match against.
|
||||
type: string
|
||||
matchExpressions:
|
||||
additionalProperties:
|
||||
description: |-
|
||||
MatchExpression specifies an expression to evaluate against a set of input
|
||||
values. It contains an operator that is applied when matching the input and
|
||||
an array of values that the operator evaluates the input against.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: |-
|
||||
Value is the list of values that the operand evaluates the input
|
||||
against. Value should be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly one element if the
|
||||
operator is Gt or Lt and exactly two elements if the operator is GtLt.
|
||||
In other cases Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
description: |-
|
||||
MatchExpressions is the set of per-element expressions evaluated. These
|
||||
match against the value of the specified elements.
|
||||
type: object
|
||||
matchName:
|
||||
description: |-
|
||||
MatchName in an expression that is matched against the name of each
|
||||
element in the feature set.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: |-
|
||||
Value is the list of values that the operand evaluates the input
|
||||
against. Value should be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly one element if the
|
||||
operator is Gt or Lt and exactly two elements if the operator is GtLt.
|
||||
In other cases Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
required:
|
||||
- feature
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- matchFeatures
|
||||
type: object
|
||||
type: array
|
||||
matchFeatures:
|
||||
description: MatchFeatures specifies a set of matcher terms
|
||||
all of which must match.
|
||||
items:
|
||||
description: |-
|
||||
FeatureMatcherTerm defines requirements against one feature set. All
|
||||
requirements (specified as MatchExpressions) are evaluated against each
|
||||
element in the feature set.
|
||||
properties:
|
||||
feature:
|
||||
description: Feature is the name of the feature set to
|
||||
match against.
|
||||
type: string
|
||||
matchExpressions:
|
||||
additionalProperties:
|
||||
description: |-
|
||||
MatchExpression specifies an expression to evaluate against a set of input
|
||||
values. It contains an operator that is applied when matching the input and
|
||||
an array of values that the operator evaluates the input against.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: |-
|
||||
Value is the list of values that the operand evaluates the input
|
||||
against. Value should be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly one element if the
|
||||
operator is Gt or Lt and exactly two elements if the operator is GtLt.
|
||||
In other cases Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
description: |-
|
||||
MatchExpressions is the set of per-element expressions evaluated. These
|
||||
match against the value of the specified elements.
|
||||
type: object
|
||||
matchName:
|
||||
description: |-
|
||||
MatchName in an expression that is matched against the name of each
|
||||
element in the feature set.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: |-
|
||||
Value is the list of values that the operand evaluates the input
|
||||
against. Value should be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly one element if the
|
||||
operator is Gt or Lt and exactly two elements if the operator is GtLt.
|
||||
In other cases Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
required:
|
||||
- feature
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: Name of the rule.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- featureGroupRules
|
||||
type: object
|
||||
status:
|
||||
properties:
|
||||
nodes:
|
||||
description: Nodes is a list of FeatureGroupNode in the cluster that
|
||||
match the featureGroupRules
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
|
|
|
@ -18,10 +18,18 @@ rules:
|
|||
resources:
|
||||
- nodefeatures
|
||||
- nodefeaturerules
|
||||
- nodefeaturegroups
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- nfd.k8s-sigs.io
|
||||
resources:
|
||||
- nodefeaturegroup/status
|
||||
verbs:
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
|
|
|
@ -117,6 +117,271 @@ spec:
|
|||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
name: nodefeaturegroups.nfd.k8s-sigs.io
|
||||
spec:
|
||||
group: nfd.k8s-sigs.io
|
||||
names:
|
||||
kind: NodeFeatureGroup
|
||||
listKind: NodeFeatureGroupList
|
||||
plural: nodefeaturegroups
|
||||
shortNames:
|
||||
- nfg
|
||||
singular: nodefeaturegroup
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: NodeFeatureGroup resource holds Node pools by featureGroup
|
||||
properties:
|
||||
apiVersion:
|
||||
description: |-
|
||||
APIVersion defines the versioned schema of this representation of an object.
|
||||
Servers should convert recognized schemas to the latest internal value, and
|
||||
may reject unrecognized values.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
|
||||
type: string
|
||||
kind:
|
||||
description: |-
|
||||
Kind is a string value representing the REST resource this object represents.
|
||||
Servers may infer this from the endpoint the client submits requests to.
|
||||
Cannot be updated.
|
||||
In CamelCase.
|
||||
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: NodeFeatureGroupSpec describes a NodeFeatureGroup object.
|
||||
properties:
|
||||
featureGroupRules:
|
||||
items:
|
||||
description: GroupRule defines a rule for nodegroup filtering.
|
||||
properties:
|
||||
matchAny:
|
||||
description: MatchAny specifies a list of matchers one of which
|
||||
must match.
|
||||
items:
|
||||
description: MatchAnyElem specifies one sub-matcher of MatchAny.
|
||||
properties:
|
||||
matchFeatures:
|
||||
description: MatchFeatures specifies a set of matcher
|
||||
terms all of which must match.
|
||||
items:
|
||||
description: |-
|
||||
FeatureMatcherTerm defines requirements against one feature set. All
|
||||
requirements (specified as MatchExpressions) are evaluated against each
|
||||
element in the feature set.
|
||||
properties:
|
||||
feature:
|
||||
description: Feature is the name of the feature
|
||||
set to match against.
|
||||
type: string
|
||||
matchExpressions:
|
||||
additionalProperties:
|
||||
description: |-
|
||||
MatchExpression specifies an expression to evaluate against a set of input
|
||||
values. It contains an operator that is applied when matching the input and
|
||||
an array of values that the operator evaluates the input against.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: |-
|
||||
Value is the list of values that the operand evaluates the input
|
||||
against. Value should be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly one element if the
|
||||
operator is Gt or Lt and exactly two elements if the operator is GtLt.
|
||||
In other cases Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
description: |-
|
||||
MatchExpressions is the set of per-element expressions evaluated. These
|
||||
match against the value of the specified elements.
|
||||
type: object
|
||||
matchName:
|
||||
description: |-
|
||||
MatchName in an expression that is matched against the name of each
|
||||
element in the feature set.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: |-
|
||||
Value is the list of values that the operand evaluates the input
|
||||
against. Value should be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly one element if the
|
||||
operator is Gt or Lt and exactly two elements if the operator is GtLt.
|
||||
In other cases Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
required:
|
||||
- feature
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- matchFeatures
|
||||
type: object
|
||||
type: array
|
||||
matchFeatures:
|
||||
description: MatchFeatures specifies a set of matcher terms
|
||||
all of which must match.
|
||||
items:
|
||||
description: |-
|
||||
FeatureMatcherTerm defines requirements against one feature set. All
|
||||
requirements (specified as MatchExpressions) are evaluated against each
|
||||
element in the feature set.
|
||||
properties:
|
||||
feature:
|
||||
description: Feature is the name of the feature set to
|
||||
match against.
|
||||
type: string
|
||||
matchExpressions:
|
||||
additionalProperties:
|
||||
description: |-
|
||||
MatchExpression specifies an expression to evaluate against a set of input
|
||||
values. It contains an operator that is applied when matching the input and
|
||||
an array of values that the operator evaluates the input against.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: |-
|
||||
Value is the list of values that the operand evaluates the input
|
||||
against. Value should be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly one element if the
|
||||
operator is Gt or Lt and exactly two elements if the operator is GtLt.
|
||||
In other cases Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
description: |-
|
||||
MatchExpressions is the set of per-element expressions evaluated. These
|
||||
match against the value of the specified elements.
|
||||
type: object
|
||||
matchName:
|
||||
description: |-
|
||||
MatchName in an expression that is matched against the name of each
|
||||
element in the feature set.
|
||||
properties:
|
||||
op:
|
||||
description: Op is the operator to be applied.
|
||||
enum:
|
||||
- In
|
||||
- NotIn
|
||||
- InRegexp
|
||||
- Exists
|
||||
- DoesNotExist
|
||||
- Gt
|
||||
- Lt
|
||||
- GtLt
|
||||
- IsTrue
|
||||
- IsFalse
|
||||
type: string
|
||||
value:
|
||||
description: |-
|
||||
Value is the list of values that the operand evaluates the input
|
||||
against. Value should be empty if the operator is Exists, DoesNotExist,
|
||||
IsTrue or IsFalse. Value should contain exactly one element if the
|
||||
operator is Gt or Lt and exactly two elements if the operator is GtLt.
|
||||
In other cases Value should contain at least one element.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
required:
|
||||
- op
|
||||
type: object
|
||||
required:
|
||||
- feature
|
||||
type: object
|
||||
type: array
|
||||
name:
|
||||
description: Name of the rule.
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
required:
|
||||
- featureGroupRules
|
||||
type: object
|
||||
status:
|
||||
properties:
|
||||
nodes:
|
||||
description: Nodes is a list of FeatureGroupNode in the cluster that
|
||||
match the featureGroupRules
|
||||
items:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
type: object
|
||||
type: array
|
||||
x-kubernetes-list-map-keys:
|
||||
- name
|
||||
x-kubernetes-list-type: map
|
||||
type: object
|
||||
required:
|
||||
- spec
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.14.0
|
||||
|
|
|
@ -21,10 +21,18 @@ rules:
|
|||
resources:
|
||||
- nodefeatures
|
||||
- nodefeaturerules
|
||||
- nodefeaturegroups
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- nfd.k8s-sigs.io
|
||||
resources:
|
||||
- nodefeaturegroups/status
|
||||
verbs:
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- coordination.k8s.io
|
||||
resources:
|
||||
|
|
|
@ -14,6 +14,7 @@ enableNodeFeatureApi: true
|
|||
|
||||
featureGates:
|
||||
NodeFeatureAPI: true
|
||||
NodeFeatureGroupAPI: false
|
||||
|
||||
priorityClassName: ""
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ The feature gates are set using the `-feature-gates` command line flag or
|
|||
| --------------------- | ------- | ------ | ------- | ------ |
|
||||
| `NodeFeatureAPI` | true | Beta | V0.14 | |
|
||||
| `DisableAutoPrefix` | false | Alpha | V0.16 | |
|
||||
| `NodeFeatureGroupAPI` | false | Alpha | V0.16 | |
|
||||
|
||||
## NodeFeatureAPI
|
||||
|
||||
|
@ -27,6 +28,14 @@ server. The Node Feature API is used to expose node-specific hardware and
|
|||
software features to the Kubernetes scheduler. The Node Feature API is a beta
|
||||
feature and is enabled by default.
|
||||
|
||||
## NodeFeatureGroupAPI
|
||||
|
||||
The `NodeFeatureGroupAPI` feature gate enables the Node Feature Group API.
|
||||
When enabled, NFD will register the Node Feature Group API with the Kubernetes API
|
||||
server. The Node Feature Group API is used to create node groups based on
|
||||
hardware and software features. The Node Feature Group API is an alpha feature
|
||||
and is disabled by default.
|
||||
|
||||
## DisableAutoPrefix
|
||||
|
||||
The `DisableAutoPrefix` feature gate controls the automatic prefixing of names.
|
||||
|
|
|
@ -51,6 +51,28 @@ spec:
|
|||
vendor-xpu-present: "true"
|
||||
```
|
||||
|
||||
## NodeFeatureGroup
|
||||
|
||||
NodeFeatureGroup is an NFD-specific custom resource that is designed for
|
||||
grouping nodes based on their features. NFD-Master watches for NodeFeatureGroup
|
||||
objects in the cluster and updates the status of the NodeFeatureGroup object
|
||||
with the list of nodes that match the feature group rules. The NodeFeatureGroup
|
||||
rules follow the same syntax as the NodeFeatureRule rules.
|
||||
|
||||
```yaml
|
||||
apiVersion: nfd.k8s-sigs.io/v1alpha1
|
||||
kind: NodeFeatureGroup
|
||||
metadata:
|
||||
name: node-feature-group-example
|
||||
spec:
|
||||
featureGroupRules:
|
||||
- name: "node has kernel version discovered"
|
||||
matchFeatures:
|
||||
- feature: kernel.version
|
||||
matchExpressions:
|
||||
major: {op: Exists}
|
||||
```
|
||||
|
||||
## NodeFeatureRule
|
||||
|
||||
NodeFeatureRule is an NFD-specific custom resource that is designed for
|
||||
|
|
|
@ -187,6 +187,49 @@ to specify taints in the NodeFeatureRule object.
|
|||
> not tolerate the taint are evicted immediately from the node including the
|
||||
> nfd-worker pod.
|
||||
|
||||
## NodeFeatureGroup custom resource
|
||||
|
||||
`NodeFeatureGroup` objects provide a way to create node groups that share the
|
||||
same set of features. The `NodeFeatureGroup` object spec consists of a list of
|
||||
`NodeFeatureRule` that follow the same format as the `NodeFeatureRule`,
|
||||
but the difference in this case is that nodes that match any of the rules in the
|
||||
`NodeFeatureGroup` will be listed in the `NodeFeatureGroup` status.
|
||||
|
||||
### A NodeFeatureGroup example
|
||||
|
||||
Consider the following referential example:
|
||||
|
||||
```yaml
|
||||
apiVersion: nfd.k8s-sigs.io/v1alpha1
|
||||
kind: NodeFeatureGroup
|
||||
metadata:
|
||||
name: node-feature-group-example
|
||||
spec:
|
||||
featureGroupRules:
|
||||
- name: "kernel version"
|
||||
matchFeatures:
|
||||
- feature: kernel.version
|
||||
matchExpressions:
|
||||
major: {op: In, value: ["6"]}
|
||||
status:
|
||||
nodes:
|
||||
- name: node-1
|
||||
- name: node-2
|
||||
- name: node-3
|
||||
```
|
||||
|
||||
The object specifies a group of nodes that share the same
|
||||
`kernel.version.major` (Linux kernel v6.x).
|
||||
|
||||
Create a `NodeFeatureGroup` with a yaml file:
|
||||
|
||||
```bash
|
||||
kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/node-feature-discovery/{{ site.release }}/examples/nodefeaturegroup.yaml
|
||||
```
|
||||
|
||||
See [Feature rule format](#feature-rule-format) for detailed description of
|
||||
available fields and how to write group filtering rules.
|
||||
|
||||
## Local feature source
|
||||
|
||||
NFD-Worker has a special feature source named `local` which is an integration
|
||||
|
|
11
examples/nodefeaturegroup.yaml
Normal file
11
examples/nodefeaturegroup.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
apiVersion: nfd.k8s-sigs.io/v1alpha1
|
||||
kind: NodeFeatureGroup
|
||||
metadata:
|
||||
name: node-feature-group-example
|
||||
spec:
|
||||
featureGroupRules:
|
||||
- name: "kernel version"
|
||||
matchFeatures:
|
||||
- feature: kernel.version
|
||||
matchExpressions:
|
||||
major: {op: In, value: ["6"]}
|
|
@ -27,6 +27,10 @@ function cleanup() {
|
|||
"${GO_CMD}" mod tidy
|
||||
}
|
||||
|
||||
# Temporal work around until https://github.com/kubernetes/kubernetes/pull/125051 is merged
|
||||
# and added to a release.
|
||||
find api/generated/ -name 'nodefeature*' | xargs rm
|
||||
|
||||
trap cleanup EXIT
|
||||
GO_CMD=${1:-go}
|
||||
NFD_ROOT=$(realpath $(dirname ${BASH_SOURCE[0]})/..)
|
||||
|
|
|
@ -108,6 +108,41 @@ func Execute(r *nfdv1alpha1.Rule, features *nfdv1alpha1.Features) (RuleOutput, e
|
|||
return ret, nil
|
||||
}
|
||||
|
||||
// ExecuteGroupRule executes the GroupRule against a set of input features, and return true if the
|
||||
// rule matches.
|
||||
func ExecuteGroupRule(r *nfdv1alpha1.GroupRule, features *nfdv1alpha1.Features) (bool, error) {
|
||||
matched := false
|
||||
if len(r.MatchAny) > 0 {
|
||||
// Logical OR over the matchAny matchers
|
||||
for _, matcher := range r.MatchAny {
|
||||
if isMatch, matches, err := evaluateMatchAnyElem(&matcher, features); err != nil {
|
||||
return false, err
|
||||
} else if isMatch {
|
||||
matched = true
|
||||
klog.V(4).InfoS("matchAny matched", "ruleName", r.Name, "matchedFeatures", utils.DelayedDumper(matches))
|
||||
// there's no need to evaluate other matchers in MatchAny
|
||||
// One match is enough for MatchAny
|
||||
break
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.MatchFeatures) > 0 {
|
||||
if isMatch, _, err := evaluateFeatureMatcher(&r.MatchFeatures, features); err != nil {
|
||||
return false, err
|
||||
} else if !isMatch {
|
||||
klog.V(2).InfoS("rule did not match", "ruleName", r.Name)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
klog.V(2).InfoS("rule matched", "ruleName", r.Name)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func executeLabelsTemplate(r *nfdv1alpha1.Rule, in matchedFeatures, out map[string]string) error {
|
||||
if r.LabelsTemplate == "" {
|
||||
return nil
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
const (
|
||||
NodeFeatureAPI featuregate.Feature = "NodeFeatureAPI"
|
||||
DisableAutoPrefix featuregate.Feature = "DisableAutoPrefix"
|
||||
NodeFeatureGroupAPI featuregate.Feature = "NodeFeatureGroupAPI"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -36,4 +37,5 @@ var (
|
|||
var DefaultNFDFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||
NodeFeatureAPI: {Default: true, PreRelease: featuregate.Beta},
|
||||
DisableAutoPrefix: {Default: false, PreRelease: featuregate.Alpha},
|
||||
NodeFeatureGroupAPI: {Default: false, PreRelease: featuregate.Alpha},
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ const (
|
|||
buildInfoQuery = "nfd_master_build_info"
|
||||
nodeUpdateRequestsQuery = "nfd_node_update_requests_total"
|
||||
nodeUpdatesQuery = "nfd_node_updates_total"
|
||||
nodeFeatureGroupUpdateRequestsQuery = "nfd_node_feature_group_update_requests_total"
|
||||
nodeUpdateFailuresQuery = "nfd_node_update_failures_total"
|
||||
nodeLabelsRejectedQuery = "nfd_node_labels_rejected_total"
|
||||
nodeERsRejectedQuery = "nfd_node_extendedresources_rejected_total"
|
||||
|
@ -46,6 +47,10 @@ var (
|
|||
Name: nodeUpdateRequestsQuery,
|
||||
Help: "Number of node update requests processed by the master.",
|
||||
})
|
||||
nodeFeatureGroupUpdateRequests = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: nodeFeatureGroupUpdateRequestsQuery,
|
||||
Help: "Number of cluster feature update requests processed by the master.",
|
||||
})
|
||||
nodeUpdates = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Name: nodeUpdatesQuery,
|
||||
Help: "Number of nodes updated by the master.",
|
||||
|
|
|
@ -37,15 +37,19 @@ import (
|
|||
type nfdController struct {
|
||||
featureLister nfdlisters.NodeFeatureLister
|
||||
ruleLister nfdlisters.NodeFeatureRuleLister
|
||||
featureGroupLister nfdlisters.NodeFeatureGroupLister
|
||||
|
||||
stopChan chan struct{}
|
||||
|
||||
updateAllNodesChan chan struct{}
|
||||
updateOneNodeChan chan string
|
||||
updateAllNodeFeatureGroupsChan chan struct{}
|
||||
updateNodeFeatureGroupChan chan string
|
||||
}
|
||||
|
||||
type nfdApiControllerOptions struct {
|
||||
DisableNodeFeature bool
|
||||
DisableNodeFeatureGroup bool
|
||||
ResyncPeriod time.Duration
|
||||
}
|
||||
|
||||
|
@ -58,6 +62,8 @@ func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiC
|
|||
stopChan: make(chan struct{}),
|
||||
updateAllNodesChan: make(chan struct{}, 1),
|
||||
updateOneNodeChan: make(chan string),
|
||||
updateAllNodeFeatureGroupsChan: make(chan struct{}, 1),
|
||||
updateNodeFeatureGroupChan: make(chan string),
|
||||
}
|
||||
|
||||
nfdClient := nfdclientset.NewForConfigOrDie(config)
|
||||
|
@ -73,16 +79,25 @@ func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiC
|
|||
nfr := obj.(*nfdv1alpha1.NodeFeature)
|
||||
klog.V(2).InfoS("NodeFeature added", "nodefeature", klog.KObj(nfr))
|
||||
c.updateOneNode("NodeFeature", nfr)
|
||||
if !nfdApiControllerOptions.DisableNodeFeatureGroup {
|
||||
c.updateAllNodeFeatureGroups()
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
nfr := newObj.(*nfdv1alpha1.NodeFeature)
|
||||
klog.V(2).InfoS("NodeFeature updated", "nodefeature", klog.KObj(nfr))
|
||||
c.updateOneNode("NodeFeature", nfr)
|
||||
if !nfdApiControllerOptions.DisableNodeFeatureGroup {
|
||||
c.updateAllNodeFeatureGroups()
|
||||
}
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
nfr := obj.(*nfdv1alpha1.NodeFeature)
|
||||
klog.V(2).InfoS("NodeFeature deleted", "nodefeature", klog.KObj(nfr))
|
||||
c.updateOneNode("NodeFeature", nfr)
|
||||
if !nfdApiControllerOptions.DisableNodeFeatureGroup {
|
||||
c.updateAllNodeFeatureGroups()
|
||||
}
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
|
@ -91,8 +106,8 @@ func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiC
|
|||
}
|
||||
|
||||
// Add informer for NodeFeatureRule objects
|
||||
ruleInformer := informerFactory.Nfd().V1alpha1().NodeFeatureRules()
|
||||
if _, err := ruleInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
nodeFeatureRuleInformer := informerFactory.Nfd().V1alpha1().NodeFeatureRules()
|
||||
if _, err := nodeFeatureRuleInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(object interface{}) {
|
||||
klog.V(2).InfoS("NodeFeatureRule added", "nodefeaturerule", klog.KObj(object.(metav1.Object)))
|
||||
if !nfdApiControllerOptions.DisableNodeFeature {
|
||||
|
@ -117,7 +132,32 @@ func newNfdController(config *restclient.Config, nfdApiControllerOptions nfdApiC
|
|||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.ruleLister = ruleInformer.Lister()
|
||||
c.ruleLister = nodeFeatureRuleInformer.Lister()
|
||||
|
||||
// Add informer for NodeFeatureGroup objects
|
||||
if !nfdApiControllerOptions.DisableNodeFeatureGroup {
|
||||
nodeFeatureGroupInformer := informerFactory.Nfd().V1alpha1().NodeFeatureGroups()
|
||||
if _, err := nodeFeatureGroupInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
nfg := obj.(*nfdv1alpha1.NodeFeatureGroup)
|
||||
klog.V(2).InfoS("NodeFeatureGroup added", "nodeFeatureGroup", klog.KObj(nfg))
|
||||
c.updateNodeFeatureGroup(nfg.Name)
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
nfg := newObj.(*nfdv1alpha1.NodeFeatureGroup)
|
||||
klog.V(2).InfoS("NodeFeatureGroup updated", "nodeFeatureGroup", klog.KObj(nfg))
|
||||
c.updateNodeFeatureGroup(nfg.Name)
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
nfg := obj.(*nfdv1alpha1.NodeFeatureGroup)
|
||||
klog.V(2).InfoS("NodeFeatureGroup deleted", "nodeFeatureGroup", klog.KObj(nfg))
|
||||
c.updateNodeFeatureGroup(nfg.Name)
|
||||
},
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.featureGroupLister = nodeFeatureGroupInformer.Lister()
|
||||
}
|
||||
|
||||
// Start informers
|
||||
informerFactory.Start(c.stopChan)
|
||||
|
@ -129,15 +169,6 @@ func (c *nfdController) stop() {
|
|||
close(c.stopChan)
|
||||
}
|
||||
|
||||
func (c *nfdController) updateOneNode(typ string, obj metav1.Object) {
|
||||
nodeName, err := getNodeNameForObj(obj)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to determine node name for object", "type", typ, "object", klog.KObj(obj))
|
||||
return
|
||||
}
|
||||
c.updateOneNodeChan <- nodeName
|
||||
}
|
||||
|
||||
func getNodeNameForObj(obj metav1.Object) (string, error) {
|
||||
nodeName, ok := obj.GetLabels()[nfdv1alpha1.NodeFeatureObjNodeNameLabel]
|
||||
if !ok {
|
||||
|
@ -149,9 +180,29 @@ func getNodeNameForObj(obj metav1.Object) (string, error) {
|
|||
return nodeName, nil
|
||||
}
|
||||
|
||||
func (c *nfdController) updateOneNode(typ string, obj metav1.Object) {
|
||||
nodeName, err := getNodeNameForObj(obj)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to determine node name for object", "type", typ, "object", klog.KObj(obj))
|
||||
return
|
||||
}
|
||||
c.updateOneNodeChan <- nodeName
|
||||
}
|
||||
|
||||
func (c *nfdController) updateAllNodes() {
|
||||
select {
|
||||
case c.updateAllNodesChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (c *nfdController) updateNodeFeatureGroup(nodeFeatureGroup string) {
|
||||
c.updateNodeFeatureGroupChan <- nodeFeatureGroup
|
||||
}
|
||||
|
||||
func (c *nfdController) updateAllNodeFeatureGroups() {
|
||||
select {
|
||||
case c.updateAllNodeFeatureGroupsChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
|
|
@ -775,10 +775,10 @@ func BenchmarkNfdAPIUpdateAllNodes(b *testing.B) {
|
|||
fakeMaster := newFakeMaster(WithKubernetesClient(fakeCli))
|
||||
fakeMaster.nfdController = newFakeNfdAPIController(fakenfdclient.NewSimpleClientset())
|
||||
|
||||
nodeUpdaterPool := newNodeUpdaterPool(fakeMaster)
|
||||
fakeMaster.nodeUpdaterPool = nodeUpdaterPool
|
||||
updaterPool := newUpdaterPool(fakeMaster)
|
||||
fakeMaster.updaterPool = updaterPool
|
||||
|
||||
nodeUpdaterPool.start(10)
|
||||
updaterPool.start(10)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
|
|
|
@ -40,7 +40,9 @@ import (
|
|||
"google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/peer"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
k8sLabels "k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
k8sclient "k8s.io/client-go/kubernetes"
|
||||
|
@ -54,6 +56,7 @@ import (
|
|||
taintutils "k8s.io/kubernetes/pkg/util/taints"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
nfdclientset "sigs.k8s.io/node-feature-discovery/api/generated/clientset/versioned"
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
|
||||
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/nodefeaturerule"
|
||||
"sigs.k8s.io/node-feature-discovery/pkg/apis/nfd/validate"
|
||||
|
@ -154,7 +157,8 @@ type nfdMaster struct {
|
|||
ready chan struct{}
|
||||
kubeconfig *restclient.Config
|
||||
k8sClient k8sclient.Interface
|
||||
nodeUpdaterPool *nodeUpdaterPool
|
||||
nfdClient *nfdclientset.Clientset
|
||||
updaterPool *updaterPool
|
||||
deniedNs
|
||||
config *NFDConfig
|
||||
}
|
||||
|
@ -211,7 +215,21 @@ func NewNfdMaster(opts ...NfdMasterOption) (NfdMaster, error) {
|
|||
nfd.k8sClient = cli
|
||||
}
|
||||
|
||||
nfd.nodeUpdaterPool = newNodeUpdaterPool(nfd)
|
||||
// nfdClient
|
||||
if nfd.kubeconfig != nil {
|
||||
kubeconfig, err := utils.GetKubeconfig(nfd.args.Kubeconfig)
|
||||
if err != nil {
|
||||
return nfd, err
|
||||
}
|
||||
nfd.kubeconfig = kubeconfig
|
||||
nfdClient, err := nfdclientset.NewForConfig(nfd.kubeconfig)
|
||||
if err != nil {
|
||||
return nfd, err
|
||||
}
|
||||
nfd.nfdClient = nfdClient
|
||||
}
|
||||
|
||||
nfd.updaterPool = newUpdaterPool(nfd)
|
||||
|
||||
return nfd, nil
|
||||
}
|
||||
|
@ -283,7 +301,7 @@ func (m *nfdMaster) Run() error {
|
|||
}
|
||||
}
|
||||
|
||||
m.nodeUpdaterPool.start(m.config.NfdApiParallelism)
|
||||
m.updaterPool.start(m.config.NfdApiParallelism)
|
||||
|
||||
// Create watcher for config file
|
||||
configWatch, err := utils.CreateFsWatcher(time.Second, m.configFilePath)
|
||||
|
@ -354,10 +372,10 @@ func (m *nfdMaster) Run() error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Stop the nodeUpdaterPool so that no node updates are underway
|
||||
// Stop the updaterPool so that no node updates are underway
|
||||
// while we reconfigure the NFD API controller (including the
|
||||
// listers) below
|
||||
m.nodeUpdaterPool.stop()
|
||||
m.updaterPool.stop()
|
||||
|
||||
// restart NFD API controller
|
||||
if m.nfdController != nil {
|
||||
|
@ -370,8 +388,8 @@ func (m *nfdMaster) Run() error {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
// Restart the nodeUpdaterPool
|
||||
m.nodeUpdaterPool.start(m.config.NfdApiParallelism)
|
||||
// Restart the updaterPool
|
||||
m.updaterPool.start(m.config.NfdApiParallelism)
|
||||
|
||||
// Update all nodes when the configuration changes
|
||||
if m.nfdController != nil && nfdfeatures.NFDFeatureGate.Enabled(nfdfeatures.NodeFeatureAPI) && m.args.EnableNodeFeatureApi {
|
||||
|
@ -474,6 +492,8 @@ func (m *nfdMaster) nfdAPIUpdateHandler() {
|
|||
// disabled (i.e. NodeFeature API is enabled)
|
||||
updateAll := nfdfeatures.NFDFeatureGate.Enabled(nfdfeatures.NodeFeatureAPI) && m.args.EnableNodeFeatureApi
|
||||
updateNodes := make(map[string]struct{})
|
||||
nodeFeatureGroup := make(map[string]struct{})
|
||||
updateAllNodeFeatureGroups := false
|
||||
rateLimit := time.After(time.Second)
|
||||
for {
|
||||
select {
|
||||
|
@ -481,7 +501,12 @@ func (m *nfdMaster) nfdAPIUpdateHandler() {
|
|||
updateAll = true
|
||||
case nodeName := <-m.nfdController.updateOneNodeChan:
|
||||
updateNodes[nodeName] = struct{}{}
|
||||
case <-m.nfdController.updateAllNodeFeatureGroupsChan:
|
||||
updateAllNodeFeatureGroups = true
|
||||
case nodeFeatureGroupName := <-m.nfdController.updateNodeFeatureGroupChan:
|
||||
nodeFeatureGroup[nodeFeatureGroupName] = struct{}{}
|
||||
case <-rateLimit:
|
||||
// NodeFeature
|
||||
errUpdateAll := false
|
||||
if updateAll {
|
||||
if err := m.nfdAPIUpdateAllNodes(); err != nil {
|
||||
|
@ -490,12 +515,26 @@ func (m *nfdMaster) nfdAPIUpdateHandler() {
|
|||
}
|
||||
} else {
|
||||
for nodeName := range updateNodes {
|
||||
m.nodeUpdaterPool.addNode(nodeName)
|
||||
m.updaterPool.addNode(nodeName)
|
||||
}
|
||||
}
|
||||
// NodeFeatureGroup
|
||||
errUpdateAllNFG := false
|
||||
if updateAllNodeFeatureGroups {
|
||||
if err := m.nfdAPIUpdateAllNodeFeatureGroups(); err != nil {
|
||||
klog.ErrorS(err, "failed to update NodeFeatureGroups")
|
||||
errUpdateAllNFG = true
|
||||
}
|
||||
} else {
|
||||
for nodeFeatureGroupName := range nodeFeatureGroup {
|
||||
m.updaterPool.addNodeFeatureGroup(nodeFeatureGroupName)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset "work queue" and timer
|
||||
updateAll = errUpdateAll
|
||||
updateAllNodeFeatureGroups = errUpdateAllNFG
|
||||
nodeFeatureGroup = map[string]struct{}{}
|
||||
updateNodes = map[string]struct{}{}
|
||||
rateLimit = time.After(time.Second)
|
||||
}
|
||||
|
@ -515,7 +554,7 @@ func (m *nfdMaster) Stop() {
|
|||
m.nfdController.stop()
|
||||
}
|
||||
|
||||
m.nodeUpdaterPool.stop()
|
||||
m.updaterPool.stop()
|
||||
|
||||
close(m.stop)
|
||||
}
|
||||
|
@ -758,21 +797,30 @@ func (m *nfdMaster) nfdAPIUpdateAllNodes() error {
|
|||
}
|
||||
|
||||
for _, node := range nodes.Items {
|
||||
m.nodeUpdaterPool.addNode(node.Name)
|
||||
m.updaterPool.addNode(node.Name)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *nfdMaster) nfdAPIUpdateOneNode(cli k8sclient.Interface, node *corev1.Node) error {
|
||||
if m.nfdController == nil || m.nfdController.featureLister == nil {
|
||||
return nil
|
||||
// getAndMergeNodeFeatures merges the NodeFeature objects of the given node into a single NodeFeatureSpec.
|
||||
// The Name field of the returned NodeFeatureSpec contains the node name.
|
||||
func (m *nfdMaster) getAndMergeNodeFeatures(nodeName string) (*nfdv1alpha1.NodeFeature, error) {
|
||||
nodeFeatures := &nfdv1alpha1.NodeFeature{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: nodeName,
|
||||
},
|
||||
}
|
||||
|
||||
sel := k8sLabels.SelectorFromSet(k8sLabels.Set{nfdv1alpha1.NodeFeatureObjNodeNameLabel: node.Name})
|
||||
sel := k8sLabels.SelectorFromSet(k8sLabels.Set{nfdv1alpha1.NodeFeatureObjNodeNameLabel: nodeName})
|
||||
objs, err := m.nfdController.featureLister.List(sel)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get NodeFeature resources for node %q: %w", node.Name, err)
|
||||
return &nfdv1alpha1.NodeFeature{}, fmt.Errorf("failed to get NodeFeature resources for node %q: %w", nodeName, err)
|
||||
}
|
||||
|
||||
// Node without a running NFD-Worker
|
||||
if len(objs) == 0 {
|
||||
return &nfdv1alpha1.NodeFeature{}, nil
|
||||
}
|
||||
|
||||
// Sort our objects
|
||||
|
@ -792,16 +840,12 @@ func (m *nfdMaster) nfdAPIUpdateOneNode(cli k8sclient.Interface, node *corev1.No
|
|||
return objs[i].Namespace < objs[j].Namespace
|
||||
})
|
||||
|
||||
klog.V(1).InfoS("processing of node initiated by NodeFeature API", "nodeName", node.Name)
|
||||
|
||||
features := nfdv1alpha1.NewNodeFeatureSpec()
|
||||
|
||||
if len(objs) > 0 {
|
||||
// Merge in features
|
||||
//
|
||||
// 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()
|
||||
features := objs[0].Spec.DeepCopy()
|
||||
if !nfdfeatures.NFDFeatureGate.Enabled(nfdfeatures.DisableAutoPrefix) && m.config.AutoDefaultNs {
|
||||
features.Labels = addNsToMapKeys(features.Labels, nfdv1alpha1.FeatureLabelNs)
|
||||
}
|
||||
|
@ -813,19 +857,123 @@ func (m *nfdMaster) nfdAPIUpdateOneNode(cli k8sclient.Interface, node *corev1.No
|
|||
s.MergeInto(features)
|
||||
}
|
||||
|
||||
// Set the merged features to the NodeFeature object
|
||||
nodeFeatures.Spec = *features
|
||||
|
||||
klog.V(4).InfoS("merged nodeFeatureSpecs", "newNodeFeatureSpec", utils.DelayedDumper(features))
|
||||
}
|
||||
|
||||
return nodeFeatures, nil
|
||||
}
|
||||
|
||||
func (m *nfdMaster) nfdAPIUpdateOneNode(cli k8sclient.Interface, node *corev1.Node) error {
|
||||
if m.nfdController == nil || m.nfdController.featureLister == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Merge all NodeFeature objects into a single NodeFeatureSpec
|
||||
nodeFeatures, err := m.getAndMergeNodeFeatures(node.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to merge NodeFeature objects for node %q: %w", node.Name, err)
|
||||
}
|
||||
|
||||
// Update node labels et al. This may also mean removing all NFD-owned
|
||||
// labels (et al.), for example in the case no NodeFeature objects are
|
||||
// present.
|
||||
if err := m.refreshNodeFeatures(cli, node, features.Labels, &features.Features); err != nil {
|
||||
if err := m.refreshNodeFeatures(cli, node, nodeFeatures.Spec.Labels, &nodeFeatures.Spec.Features); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *nfdMaster) nfdAPIUpdateAllNodeFeatureGroups() error {
|
||||
klog.V(1).InfoS("updating all NodeFeatureGroups")
|
||||
|
||||
nodeFeatureGroupsList, err := m.nfdController.featureGroupLister.List(labels.Everything())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get NodeFeatureGroup objects: %w", err)
|
||||
}
|
||||
|
||||
if len(nodeFeatureGroupsList) > 0 {
|
||||
for _, nodeFeatureGroup := range nodeFeatureGroupsList {
|
||||
m.updaterPool.nfgQueue.Add(nodeFeatureGroup.Name)
|
||||
}
|
||||
} else {
|
||||
klog.V(2).InfoS("no NodeFeatureGroup objects found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *nfdMaster) nfdAPIUpdateNodeFeatureGroup(nfdClient *nfdclientset.Clientset, nodeFeatureGroup *nfdv1alpha1.NodeFeatureGroup) error {
|
||||
klog.V(2).InfoS("evaluating NodeFeatureGroup", "nodeFeatureGroup", klog.KObj(nodeFeatureGroup))
|
||||
if m.nfdController == nil || m.nfdController.featureLister == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get all Nodes
|
||||
nodes, err := getNodes(m.k8sClient)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get nodes: %w", err)
|
||||
}
|
||||
nodeFeaturesList := make([]*nfdv1alpha1.NodeFeature, 0)
|
||||
for _, node := range nodes.Items {
|
||||
// Merge all NodeFeature objects into a single NodeFeatureSpec
|
||||
nodeFeatures, err := m.getAndMergeNodeFeatures(node.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to merge NodeFeature objects for node %q: %w", node.Name, err)
|
||||
}
|
||||
if nodeFeatures.Name == "" {
|
||||
// Nothing to do for this node
|
||||
continue
|
||||
}
|
||||
nodeFeaturesList = append(nodeFeaturesList, nodeFeatures)
|
||||
}
|
||||
|
||||
// Execute rules and create matching groups
|
||||
nodePool := make([]nfdv1alpha1.FeatureGroupNode, 0)
|
||||
nodeGroupValidator := make(map[string]bool)
|
||||
for _, rule := range nodeFeatureGroup.Spec.Rules {
|
||||
for _, feature := range nodeFeaturesList {
|
||||
match, err := nodefeaturerule.ExecuteGroupRule(&rule, &feature.Spec.Features)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "failed to evaluate rule", "ruleName", rule.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
if match {
|
||||
klog.ErrorS(err, "failed to evaluate rule", "ruleName", rule.Name, "nodeName", feature.Name)
|
||||
system := feature.Spec.Features.Attributes["system.name"]
|
||||
nodeName := system.Elements["nodename"]
|
||||
if _, ok := nodeGroupValidator[nodeName]; !ok {
|
||||
nodePool = append(nodePool, nfdv1alpha1.FeatureGroupNode{
|
||||
Name: nodeName,
|
||||
})
|
||||
nodeGroupValidator[nodeName] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the NodeFeatureGroup object with the updated featureGroupRules
|
||||
nodeFeatureGroupUpdated := nodeFeatureGroup.DeepCopy()
|
||||
nodeFeatureGroupUpdated.Status.Nodes = nodePool
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(nodeFeatureGroup, nodeFeatureGroupUpdated) {
|
||||
klog.InfoS("updating NodeFeatureGroup object", "nodeFeatureGroup", klog.KObj(nodeFeatureGroup))
|
||||
nodeFeatureGroupUpdated, err = nfdClient.NfdV1alpha1().NodeFeatureGroups(m.namespace).UpdateStatus(context.TODO(), nodeFeatureGroupUpdated, metav1.UpdateOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update NodeFeatureGroup object: %w", err)
|
||||
}
|
||||
klog.V(4).InfoS("NodeFeatureGroup object updated", "nodeFeatureGroup", utils.DelayedDumper(nodeFeatureGroupUpdated))
|
||||
} else {
|
||||
klog.V(1).InfoS("no changes in NodeFeatureGroup, object is up to date", "nodeFeatureGroup", klog.KObj(nodeFeatureGroup))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// filterExtendedResources filters extended resources and returns a map
|
||||
// of valid extended resources.
|
||||
func (m *nfdMaster) filterExtendedResources(features *nfdv1alpha1.Features, extendedResources ExtendedResources) ExtendedResources {
|
||||
|
@ -1433,6 +1581,10 @@ func getNode(cli k8sclient.Interface, nodeName string) (*corev1.Node, error) {
|
|||
return cli.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func getNodeFeatureGroup(cli nfdclientset.Interface, namespace, name string) (*nfdv1alpha1.NodeFeatureGroup, error) {
|
||||
return cli.NfdV1alpha1().NodeFeatureGroups(namespace).Get(context.TODO(), name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func getNodes(cli k8sclient.Interface) (*corev1.NodeList, error) {
|
||||
return cli.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
|
||||
}
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nfdmaster
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
k8sclient "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
type nodeUpdaterPool struct {
|
||||
queue workqueue.RateLimitingInterface
|
||||
sync.RWMutex
|
||||
|
||||
wg sync.WaitGroup
|
||||
nfdMaster *nfdMaster
|
||||
}
|
||||
|
||||
func newNodeUpdaterPool(nfdMaster *nfdMaster) *nodeUpdaterPool {
|
||||
return &nodeUpdaterPool{
|
||||
nfdMaster: nfdMaster,
|
||||
wg: sync.WaitGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
func (u *nodeUpdaterPool) processNodeUpdateRequest(cli k8sclient.Interface, queue workqueue.RateLimitingInterface) bool {
|
||||
n, quit := queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
nodeName := n.(string)
|
||||
|
||||
defer queue.Done(nodeName)
|
||||
|
||||
nodeUpdateRequests.Inc()
|
||||
|
||||
// Check if node exists
|
||||
if node, err := getNode(cli, nodeName); apierrors.IsNotFound(err) {
|
||||
klog.InfoS("node not found, skip update", "nodeName", nodeName)
|
||||
} else if err := u.nfdMaster.nfdAPIUpdateOneNode(cli, node); err != nil {
|
||||
if n := queue.NumRequeues(nodeName); n < 15 {
|
||||
klog.InfoS("retrying node update", "nodeName", nodeName, "lastError", err, "numRetries", n)
|
||||
} else {
|
||||
klog.ErrorS(err, "node update failed, queuing for retry ", "nodeName", nodeName, "numRetries", n)
|
||||
// Count only long-failing attempts
|
||||
nodeUpdateFailures.Inc()
|
||||
}
|
||||
queue.AddRateLimited(nodeName)
|
||||
return true
|
||||
}
|
||||
queue.Forget(nodeName)
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *nodeUpdaterPool) runNodeUpdater(queue workqueue.RateLimitingInterface) {
|
||||
var cli k8sclient.Interface
|
||||
if u.nfdMaster.kubeconfig != nil {
|
||||
// For normal execution, initialize a separate api client for each updater
|
||||
cli = k8sclient.NewForConfigOrDie(u.nfdMaster.kubeconfig)
|
||||
} else {
|
||||
// For tests, re-use the api client from nfd-master
|
||||
cli = u.nfdMaster.k8sClient
|
||||
}
|
||||
for u.processNodeUpdateRequest(cli, queue) {
|
||||
}
|
||||
u.wg.Done()
|
||||
}
|
||||
|
||||
func (u *nodeUpdaterPool) start(parallelism int) {
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
if u.queue != nil && !u.queue.ShuttingDown() {
|
||||
klog.InfoS("the NFD master node updater pool is already running.")
|
||||
return
|
||||
}
|
||||
|
||||
klog.InfoS("starting the NFD master node updater pool", "parallelism", parallelism)
|
||||
|
||||
// Create ratelimiter. Mimic workqueue.DefaultControllerRateLimiter() but
|
||||
// with modified per-item (node) rate limiting parameters.
|
||||
rl := workqueue.NewMaxOfRateLimiter(
|
||||
workqueue.NewItemExponentialFailureRateLimiter(50*time.Millisecond, 100*time.Second),
|
||||
&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
|
||||
)
|
||||
u.queue = workqueue.NewRateLimitingQueue(rl)
|
||||
|
||||
for i := 0; i < parallelism; i++ {
|
||||
u.wg.Add(1)
|
||||
go u.runNodeUpdater(u.queue)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *nodeUpdaterPool) stop() {
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
if u.queue == nil || u.queue.ShuttingDown() {
|
||||
klog.InfoS("the NFD master node updater pool is not running.")
|
||||
return
|
||||
}
|
||||
|
||||
klog.InfoS("stopping the NFD master node updater pool")
|
||||
u.queue.ShutDown()
|
||||
u.wg.Wait()
|
||||
}
|
||||
|
||||
func (u *nodeUpdaterPool) addNode(nodeName string) {
|
||||
u.RLock()
|
||||
defer u.RUnlock()
|
||||
u.queue.Add(nodeName)
|
||||
}
|
194
pkg/nfd-master/updater-pool.go
Normal file
194
pkg/nfd-master/updater-pool.go
Normal file
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package nfdmaster
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/time/rate"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
k8sclient "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/util/workqueue"
|
||||
"k8s.io/klog/v2"
|
||||
nfdclientset "sigs.k8s.io/node-feature-discovery/api/generated/clientset/versioned"
|
||||
nfdv1alpha1 "sigs.k8s.io/node-feature-discovery/api/nfd/v1alpha1"
|
||||
"sigs.k8s.io/node-feature-discovery/pkg/features"
|
||||
)
|
||||
|
||||
type updaterPool struct {
|
||||
queue workqueue.RateLimitingInterface
|
||||
nfgQueue workqueue.RateLimitingInterface
|
||||
sync.RWMutex
|
||||
|
||||
wg sync.WaitGroup
|
||||
nfgWg sync.WaitGroup
|
||||
nfdMaster *nfdMaster
|
||||
}
|
||||
|
||||
func newUpdaterPool(nfdMaster *nfdMaster) *updaterPool {
|
||||
return &updaterPool{
|
||||
nfdMaster: nfdMaster,
|
||||
wg: sync.WaitGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
func (u *updaterPool) processNodeUpdateRequest(cli k8sclient.Interface, queue workqueue.RateLimitingInterface) bool {
|
||||
n, quit := queue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
nodeName := n.(string)
|
||||
|
||||
defer queue.Done(nodeName)
|
||||
|
||||
nodeUpdateRequests.Inc()
|
||||
|
||||
// Check if node exists
|
||||
if node, err := getNode(cli, nodeName); apierrors.IsNotFound(err) {
|
||||
klog.InfoS("node not found, skip update", "nodeName", nodeName)
|
||||
} else if err := u.nfdMaster.nfdAPIUpdateOneNode(cli, node); err != nil {
|
||||
if n := queue.NumRequeues(nodeName); n < 15 {
|
||||
klog.InfoS("retrying node update", "nodeName", nodeName, "lastError", err, "numRetries", n)
|
||||
} else {
|
||||
klog.ErrorS(err, "node update failed, queuing for retry ", "nodeName", nodeName, "numRetries", n)
|
||||
// Count only long-failing attempts
|
||||
nodeUpdateFailures.Inc()
|
||||
}
|
||||
queue.AddRateLimited(nodeName)
|
||||
return true
|
||||
}
|
||||
queue.Forget(nodeName)
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *updaterPool) runNodeUpdater(queue workqueue.RateLimitingInterface) {
|
||||
var cli k8sclient.Interface
|
||||
if u.nfdMaster.kubeconfig != nil {
|
||||
// For normal execution, initialize a separate api client for each updater
|
||||
cli = k8sclient.NewForConfigOrDie(u.nfdMaster.kubeconfig)
|
||||
} else {
|
||||
// For tests, re-use the api client from nfd-master
|
||||
cli = u.nfdMaster.k8sClient
|
||||
}
|
||||
for u.processNodeUpdateRequest(cli, queue) {
|
||||
}
|
||||
u.wg.Done()
|
||||
}
|
||||
|
||||
func (u *updaterPool) processNodeFeatureGroupUpdateRequest(cli nfdclientset.Interface, ngfQueue workqueue.RateLimitingInterface) bool {
|
||||
nfgName, quit := ngfQueue.Get()
|
||||
if quit {
|
||||
return false
|
||||
}
|
||||
defer ngfQueue.Done(nfgName)
|
||||
|
||||
nodeFeatureGroupUpdateRequests.Inc()
|
||||
|
||||
// Check if NodeFeatureGroup exists
|
||||
var nfg *nfdv1alpha1.NodeFeatureGroup
|
||||
var err error
|
||||
if nfg, err = getNodeFeatureGroup(cli, u.nfdMaster.namespace, nfgName.(string)); apierrors.IsNotFound(err) {
|
||||
klog.InfoS("NodeFeatureGroup not found, skip update", "NodeFeatureGroupName", nfgName)
|
||||
} else if err := u.nfdMaster.nfdAPIUpdateNodeFeatureGroup(u.nfdMaster.nfdClient, nfg); err != nil {
|
||||
if n := ngfQueue.NumRequeues(nfgName); n < 15 {
|
||||
klog.InfoS("retrying NodeFeatureGroup update", "nodeFeatureGroup", klog.KObj(nfg), "lastError", err)
|
||||
} else {
|
||||
klog.ErrorS(err, "failed to update NodeFeatureGroup, queueing for retry", "nodeFeatureGroup", klog.KObj(nfg), "lastError", err, "numRetries", n)
|
||||
}
|
||||
ngfQueue.AddRateLimited(nfgName)
|
||||
return true
|
||||
}
|
||||
|
||||
ngfQueue.Forget(nfgName)
|
||||
return true
|
||||
}
|
||||
|
||||
func (u *updaterPool) runNodeFeatureGroupUpdater(ngfQueue workqueue.RateLimitingInterface) {
|
||||
cli := nfdclientset.NewForConfigOrDie(u.nfdMaster.kubeconfig)
|
||||
for u.processNodeFeatureGroupUpdateRequest(cli, ngfQueue) {
|
||||
}
|
||||
u.nfgWg.Done()
|
||||
}
|
||||
|
||||
func (u *updaterPool) start(parallelism int) {
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
if u.queue != nil && !u.queue.ShuttingDown() {
|
||||
klog.InfoS("the NFD master updater pool is already running.")
|
||||
return
|
||||
}
|
||||
|
||||
if u.nfgQueue != nil && !u.nfgQueue.ShuttingDown() {
|
||||
klog.InfoS("the NFD master node feature group updater pool is already running.")
|
||||
return
|
||||
}
|
||||
|
||||
klog.InfoS("starting the NFD master updater pool", "parallelism", parallelism)
|
||||
|
||||
// Create ratelimiter. Mimic workqueue.DefaultControllerRateLimiter() but
|
||||
// with modified per-item (node) rate limiting parameters.
|
||||
rl := workqueue.NewMaxOfRateLimiter(
|
||||
workqueue.NewItemExponentialFailureRateLimiter(50*time.Millisecond, 100*time.Second),
|
||||
&workqueue.BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
|
||||
)
|
||||
u.queue = workqueue.NewRateLimitingQueue(rl)
|
||||
u.nfgQueue = workqueue.NewRateLimitingQueue(rl)
|
||||
|
||||
for i := 0; i < parallelism; i++ {
|
||||
u.wg.Add(1)
|
||||
go u.runNodeUpdater(u.queue)
|
||||
if features.NFDFeatureGate.Enabled(features.NodeFeatureGroupAPI) {
|
||||
u.nfgWg.Add(1)
|
||||
go u.runNodeFeatureGroupUpdater(u.nfgQueue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (u *updaterPool) stop() {
|
||||
u.Lock()
|
||||
defer u.Unlock()
|
||||
|
||||
if u.queue == nil || u.queue.ShuttingDown() {
|
||||
klog.InfoS("the NFD master updater pool is not running.")
|
||||
return
|
||||
}
|
||||
|
||||
if u.nfgQueue == nil || u.nfgQueue.ShuttingDown() {
|
||||
klog.InfoS("the NFD master updater pool is not running.")
|
||||
return
|
||||
}
|
||||
|
||||
klog.InfoS("stopping the NFD master updater pool")
|
||||
u.queue.ShutDown()
|
||||
u.wg.Wait()
|
||||
u.nfgQueue.ShutDown()
|
||||
u.nfgWg.Wait()
|
||||
}
|
||||
|
||||
func (u *updaterPool) addNode(nodeName string) {
|
||||
u.RLock()
|
||||
defer u.RUnlock()
|
||||
u.queue.Add(nodeName)
|
||||
}
|
||||
|
||||
func (u *updaterPool) addNodeFeatureGroup(nodeFeatureGroupName string) {
|
||||
u.RLock()
|
||||
defer u.RUnlock()
|
||||
u.nfgQueue.Add(nodeFeatureGroupName)
|
||||
}
|
|
@ -26,44 +26,44 @@ import (
|
|||
fakenfdclient "sigs.k8s.io/node-feature-discovery/api/generated/clientset/versioned/fake"
|
||||
)
|
||||
|
||||
func newFakeNodeUpdaterPool(nfdMaster *nfdMaster) *nodeUpdaterPool {
|
||||
return &nodeUpdaterPool{
|
||||
func newFakeupdaterPool(nfdMaster *nfdMaster) *updaterPool {
|
||||
return &updaterPool{
|
||||
nfdMaster: nfdMaster,
|
||||
wg: sync.WaitGroup{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeUpdaterStart(t *testing.T) {
|
||||
func TestUpdaterStart(t *testing.T) {
|
||||
fakeMaster := newFakeMaster()
|
||||
nodeUpdaterPool := newFakeNodeUpdaterPool(fakeMaster)
|
||||
updaterPool := newFakeupdaterPool(fakeMaster)
|
||||
|
||||
Convey("When starting the node updater pool", t, func() {
|
||||
nodeUpdaterPool.start(10)
|
||||
q := nodeUpdaterPool.queue
|
||||
updaterPool.start(10)
|
||||
q := updaterPool.queue
|
||||
Convey("Node updater pool queue properties should change", func() {
|
||||
So(q, ShouldNotBeNil)
|
||||
So(q.ShuttingDown(), ShouldBeFalse)
|
||||
})
|
||||
|
||||
nodeUpdaterPool.start(10)
|
||||
updaterPool.start(10)
|
||||
Convey("Node updater pool queue should not change", func() {
|
||||
So(nodeUpdaterPool.queue, ShouldEqual, q)
|
||||
So(updaterPool.queue, ShouldEqual, q)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestNodeUpdaterStop(t *testing.T) {
|
||||
fakeMaster := newFakeMaster()
|
||||
nodeUpdaterPool := newFakeNodeUpdaterPool(fakeMaster)
|
||||
updaterPool := newFakeupdaterPool(fakeMaster)
|
||||
|
||||
nodeUpdaterPool.start(10)
|
||||
updaterPool.start(10)
|
||||
|
||||
Convey("When stoping the node updater pool", t, func() {
|
||||
nodeUpdaterPool.stop()
|
||||
updaterPool.stop()
|
||||
Convey("Node updater pool queue should be removed", func() {
|
||||
// Wait for the wg.Done()
|
||||
So(func() interface{} {
|
||||
return nodeUpdaterPool.queue.ShuttingDown()
|
||||
return updaterPool.queue.ShuttingDown()
|
||||
}, withTimeout, 2*time.Second, ShouldBeTrue)
|
||||
})
|
||||
})
|
||||
|
@ -72,15 +72,31 @@ func TestNodeUpdaterStop(t *testing.T) {
|
|||
func TestRunNodeUpdater(t *testing.T) {
|
||||
fakeMaster := newFakeMaster(WithKubernetesClient(fakek8sclient.NewSimpleClientset()))
|
||||
fakeMaster.nfdController = newFakeNfdAPIController(fakenfdclient.NewSimpleClientset())
|
||||
nodeUpdaterPool := newFakeNodeUpdaterPool(fakeMaster)
|
||||
updaterPool := newFakeupdaterPool(fakeMaster)
|
||||
|
||||
nodeUpdaterPool.start(10)
|
||||
updaterPool.start(10)
|
||||
Convey("Queue has no element", t, func() {
|
||||
So(nodeUpdaterPool.queue.Len(), ShouldEqual, 0)
|
||||
So(updaterPool.queue.Len(), ShouldEqual, 0)
|
||||
})
|
||||
nodeUpdaterPool.queue.Add(testNodeName)
|
||||
updaterPool.queue.Add(testNodeName)
|
||||
Convey("Added element to the queue should be removed", t, func() {
|
||||
So(func() interface{} { return nodeUpdaterPool.queue.Len() },
|
||||
So(func() interface{} { return updaterPool.queue.Len() },
|
||||
withTimeout, 2*time.Second, ShouldEqual, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunNodeFeatureGroupUpdater(t *testing.T) {
|
||||
fakeMaster := newFakeMaster(WithKubernetesClient(fakek8sclient.NewSimpleClientset()))
|
||||
fakeMaster.nfdController = newFakeNfdAPIController(fakenfdclient.NewSimpleClientset())
|
||||
updaterPool := newFakeupdaterPool(fakeMaster)
|
||||
|
||||
updaterPool.start(10)
|
||||
Convey("Queue has no element", t, func() {
|
||||
So(updaterPool.nfgQueue.Len(), ShouldEqual, 0)
|
||||
})
|
||||
updaterPool.nfgQueue.Add(testNodeName)
|
||||
Convey("Added element to the queue should be removed", t, func() {
|
||||
So(func() interface{} { return updaterPool.queue.Len() },
|
||||
withTimeout, 2*time.Second, ShouldEqual, 0)
|
||||
})
|
||||
}
|
|
@ -53,6 +53,7 @@ func setupTest(args *master.Args) testContext {
|
|||
os.Exit(1)
|
||||
}
|
||||
_ = features.NFDMutableFeatureGate.OverrideDefault(features.NodeFeatureAPI, false)
|
||||
_ = features.NFDMutableFeatureGate.OverrideDefault(features.NodeFeatureGroupAPI, false)
|
||||
m, err := master.NewNfdMaster(
|
||||
master.WithArgs(args),
|
||||
master.WithKubernetesClient(fakeclient.NewSimpleClientset()))
|
||||
|
|
11
test/e2e/data/nodefeaturegroup-1.yaml
Normal file
11
test/e2e/data/nodefeaturegroup-1.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
apiVersion: nfd.k8s-sigs.io/v1alpha1
|
||||
kind: NodeFeatureGroup
|
||||
metadata:
|
||||
name: e2e-test-1
|
||||
spec:
|
||||
featureGroupRules:
|
||||
- name: "e2e-attribute-test-1"
|
||||
matchFeatures:
|
||||
- feature: kernel.version
|
||||
matchExpressions:
|
||||
major: {op: Exists}
|
|
@ -22,6 +22,7 @@ import (
|
|||
"fmt"
|
||||
"maps"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -181,6 +182,19 @@ func cleanupCRs(ctx context.Context, cli *nfdclient.Clientset, namespace string)
|
|||
}()).NotTo(HaveOccurred())
|
||||
}
|
||||
}
|
||||
|
||||
// Drop NodeFeatureGroup objects
|
||||
nfgs, err := cli.NfdV1alpha1().NodeFeatureGroups(namespace).List(ctx, metav1.ListOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
if len(nfgs.Items) != 0 {
|
||||
By("Deleting NodeFeatureGroup objects from namespace " + namespace)
|
||||
for _, nfg := range nfgs.Items {
|
||||
err = cli.NfdV1alpha1().NodeFeatureGroups(namespace).Delete(ctx, nfg.Name, metav1.DeleteOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Actual test suite
|
||||
|
@ -873,6 +887,61 @@ core:
|
|||
})
|
||||
})
|
||||
|
||||
// Test NodeFeatureGroups
|
||||
Context("and NodeFeatureGroups objects deployed", Label("nodefeaturegroup"), func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
// We need a NodeFeature from the node, can't be a fake one
|
||||
if !useNodeFeatureApi {
|
||||
Skip("NodeFeature API not enabled")
|
||||
}
|
||||
// enable the node feature group api
|
||||
extraMasterPodSpecOpts = []testpod.SpecOption{
|
||||
testpod.SpecWithContainerExtraArgs(
|
||||
"--feature-gates=NodeFeatureGroupAPI=true",
|
||||
),
|
||||
}
|
||||
})
|
||||
It("custom NodeFeatureGroup should be updated", func(ctx context.Context) {
|
||||
By("Creating nfd-worker daemonset")
|
||||
podSpecOpts := createPodSpecOpts(
|
||||
testpod.SpecWithContainerImage(dockerImage()),
|
||||
)
|
||||
workerDS := testds.NFDWorker(podSpecOpts...)
|
||||
workerDS, err := f.ClientSet.AppsV1().DaemonSets(f.Namespace.Name).Create(ctx, workerDS, metav1.CreateOptions{})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Waiting for worker daemonset pods to be ready")
|
||||
Expect(testpod.WaitForReady(ctx, f.ClientSet, f.Namespace.Name, workerDS.Spec.Template.Labels["name"], 2)).NotTo(HaveOccurred())
|
||||
|
||||
nodes, err := getNonControlPlaneNodes(ctx, f.ClientSet)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
By("Creating NodeFeatureGroups #1")
|
||||
Expect(testutils.CreateNodeFeatureGroupsFromFile(ctx, nfdClient, f.Namespace.Name, "nodefeaturegroup-1.yaml")).NotTo(HaveOccurred())
|
||||
|
||||
By("Verifying NodeFeatureGroups #1")
|
||||
targetNodes := make([]nfdv1alpha1.FeatureGroupNode, 0)
|
||||
for _, node := range nodes {
|
||||
targetNodes = append(targetNodes, nfdv1alpha1.FeatureGroupNode{
|
||||
Name: node.Name,
|
||||
})
|
||||
}
|
||||
|
||||
expectedGroup := nfdv1alpha1.NodeFeatureGroup{
|
||||
Status: nfdv1alpha1.NodeFeatureGroupStatus{
|
||||
Nodes: targetNodes,
|
||||
},
|
||||
}
|
||||
Eventually(func() bool {
|
||||
group, err := nfdClient.NfdV1alpha1().NodeFeatureGroups(f.Namespace.Name).Get(ctx, "e2e-test-1", metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return reflect.DeepEqual(group.Status, expectedGroup.Status)
|
||||
}, 5*time.Minute, 5*time.Second).Should(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Context("and check whether master config passed successfully or not", func() {
|
||||
BeforeEach(func(ctx context.Context) {
|
||||
extraMasterPodSpecOpts = []testpod.SpecOption{
|
||||
|
|
|
@ -123,6 +123,21 @@ func CreateNodeFeatureRulesFromFile(ctx context.Context, cli nfdclientset.Interf
|
|||
return nil
|
||||
}
|
||||
|
||||
// CreateNodeFeatureGroupsFromFile creates a NodeFeatureGroup object from a given file located under test data directory.
|
||||
func CreateNodeFeatureGroupsFromFile(ctx context.Context, cli nfdclientset.Interface, namespace, filename string) error {
|
||||
objs, err := nodeFeatureGroupsFromFile(filepath.Join(packagePath, "..", "data", filename))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, obj := range objs {
|
||||
if _, err = cli.NfdV1alpha1().NodeFeatureGroups(namespace).Create(ctx, obj, metav1.CreateOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateNodeFeatureRulesFromFile updates existing NodeFeatureRule object from a given file located under test data directory.
|
||||
func UpdateNodeFeatureRulesFromFile(ctx context.Context, cli nfdclientset.Interface, filename string) error {
|
||||
objs, err := nodeFeatureRulesFromFile(filepath.Join(packagePath, "..", "data", filename))
|
||||
|
@ -238,6 +253,25 @@ func nodeFeatureRulesFromFile(path string) ([]*nfdv1alpha1.NodeFeatureRule, erro
|
|||
return crs, nil
|
||||
}
|
||||
|
||||
func nodeFeatureGroupsFromFile(path string) ([]*nfdv1alpha1.NodeFeatureGroup, error) {
|
||||
objs, err := apiObjsFromFile(path, nfdscheme.Codecs.UniversalDeserializer())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
crs := make([]*nfdv1alpha1.NodeFeatureGroup, len(objs))
|
||||
|
||||
for i, obj := range objs {
|
||||
var ok bool
|
||||
crs[i], ok = obj.(*nfdv1alpha1.NodeFeatureGroup)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unexpected type %t when reading %q", obj, path)
|
||||
}
|
||||
}
|
||||
|
||||
return crs, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
_, thisFile, _, _ := runtime.Caller(0)
|
||||
packagePath = filepath.Dir(thisFile)
|
||||
|
|
|
@ -186,6 +186,16 @@ func createClusterRoleMaster(ctx context.Context, cs clientset.Interface) (*rbac
|
|||
Resources: []string{"nodefeatures", "nodefeaturerules"},
|
||||
Verbs: []string{"get", "list", "watch"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"nfd.k8s-sigs.io"},
|
||||
Resources: []string{"nodefeaturegroups"},
|
||||
Verbs: []string{"get", "list", "watch", "update"},
|
||||
},
|
||||
{
|
||||
APIGroups: []string{"nfd.k8s-sigs.io"},
|
||||
Resources: []string{"nodefeaturegroups/status"},
|
||||
Verbs: []string{"patch", "update"},
|
||||
},
|
||||
},
|
||||
}
|
||||
if *openShift {
|
||||
|
|
Loading…
Reference in a new issue