Update DRA e2e test framework to allow publishing advanced ResourceSlices

This commit is contained in:
Morten Torkildsen
2025-04-11 18:32:15 +00:00
parent 48fcf418ce
commit ece35e5882
5 changed files with 204 additions and 193 deletions

View File

@@ -57,6 +57,7 @@ import (
"k8s.io/client-go/restmapper"
"k8s.io/client-go/tools/cache"
"k8s.io/dynamic-resource-allocation/kubeletplugin"
"k8s.io/dynamic-resource-allocation/resourceslice"
"k8s.io/klog/v2"
"k8s.io/kubectl/pkg/cmd/exec"
"k8s.io/kubernetes/test/e2e/dra/test-driver/app"
@@ -80,20 +81,6 @@ type Nodes struct {
tempDir string
}
type Resources struct {
NodeLocal bool
// Nodes is a fixed list of node names on which resources are
// available. Mutually exclusive with NodeLabels.
Nodes []string
// Number of devices called "device-000", "device-001", ... on each node or in the cluster.
MaxAllocations int
// Tainted causes all devices to be published with a NoExecute taint.
Tainted bool
}
//go:embed test-driver/deploy/example/plugin-permissions.yaml
var pluginPermissions string
@@ -185,16 +172,25 @@ func validateClaim(claim *resourceapi.ResourceClaim) {
}
}
const (
networkPool = "network-pool"
)
// driverResourcesGenFunc defines the callback that will be invoked by the driver to generate the
// DriverResources that will be used to construct the ResourceSlices.
type driverResourcesGenFunc func(nodes *Nodes) map[string]resourceslice.DriverResources
// NewDriver sets up controller (as client of the cluster) and
// kubelet plugin (via proxy) before the test runs. It cleans
// up after the test.
//
// Call this outside of ginkgo.It, then use the instance inside ginkgo.It.
func NewDriver(f *framework.Framework, nodes *Nodes, configureResources func() Resources, devicesPerNode ...map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute) *Driver {
func NewDriver(f *framework.Framework, nodes *Nodes, driverResourcesGenerator driverResourcesGenFunc) *Driver {
d := NewDriverInstance(f)
ginkgo.BeforeEach(func() {
d.Run(nodes, configureResources, devicesPerNode...)
driverResources := driverResourcesGenerator(nodes)
d.Run(nodes, driverResources)
})
return d
}
@@ -214,14 +210,8 @@ func NewDriverInstance(f *framework.Framework) *Driver {
return d
}
func (d *Driver) Run(nodes *Nodes, configureResources func() Resources, devicesPerNode ...map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute) {
resources := configureResources()
if len(resources.Nodes) == 0 {
// This always has to be set because the driver might
// not run on all nodes.
resources.Nodes = nodes.NodeNames
}
d.SetUp(nodes, resources, devicesPerNode...)
func (d *Driver) Run(nodes *Nodes, driverResources map[string]resourceslice.DriverResources) {
d.SetUp(nodes, driverResources)
ginkgo.DeferCleanup(d.TearDown)
}
@@ -281,7 +271,7 @@ func (d *Driver) initName() {
d.Name = d.f.UniqueName + d.NameSuffix + ".k8s.io"
}
func (d *Driver) SetUp(nodes *Nodes, resources Resources, devicesPerNode ...map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute) {
func (d *Driver) SetUp(nodes *Nodes, driverResources map[string]resourceslice.DriverResources) {
d.initName()
ginkgo.By(fmt.Sprintf("deploying driver %s on nodes %v", d.Name, nodes.NodeNames))
d.Nodes = make(map[string]KubeletPlugin)
@@ -297,56 +287,35 @@ func (d *Driver) SetUp(nodes *Nodes, resources Resources, devicesPerNode ...map[
d.ctx = ctx
d.cleanup = append(d.cleanup, func(context.Context) { cancel() })
if !resources.NodeLocal {
// Publish one resource pool with "network-attached" devices.
slice := &resourceapi.ResourceSlice{
ObjectMeta: metav1.ObjectMeta{
Name: d.Name, // globally unique
},
Spec: resourceapi.ResourceSliceSpec{
Driver: d.Name,
Pool: resourceapi.ResourcePool{
Name: "network",
Generation: 1,
ResourceSliceCount: 1,
},
NodeSelector: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{{
// MatchExpressions allow multiple values,
// MatchFields don't.
MatchExpressions: []v1.NodeSelectorRequirement{{
Key: "kubernetes.io/hostname",
Operator: v1.NodeSelectorOpIn,
Values: nodes.NodeNames,
}},
}},
},
},
}
maxAllocations := resources.MaxAllocations
if maxAllocations <= 0 {
// Cannot be empty, otherwise nothing runs.
maxAllocations = 10
}
for i := 0; i < maxAllocations; i++ {
device := resourceapi.Device{
Name: fmt.Sprintf("device-%d", i),
driverResource, found := driverResources[networkPool]
// If found, we create a network ResourcSlice, i.e. one that is not
// associated with a specific node.
if found {
for poolName, pool := range driverResource.Pools {
for i, slice := range pool.Slices {
// Publish one resource pool with "network-attached" devices.
resourceSlice := &resourceapi.ResourceSlice{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%d", d.Name, i), // globally unique
},
Spec: resourceapi.ResourceSliceSpec{
Driver: d.Name,
Pool: resourceapi.ResourcePool{
Name: poolName,
Generation: pool.Generation,
ResourceSliceCount: int64(len(pool.Slices)),
},
NodeSelector: pool.NodeSelector,
Devices: slice.Devices,
},
}
_, err := d.f.ClientSet.ResourceV1beta2().ResourceSlices().Create(ctx, resourceSlice, metav1.CreateOptions{})
framework.ExpectNoError(err)
ginkgo.DeferCleanup(func(ctx context.Context) {
framework.ExpectNoError(d.f.ClientSet.ResourceV1beta2().ResourceSlices().Delete(ctx, resourceSlice.Name, metav1.DeleteOptions{}))
})
}
if resources.Tainted {
device.Taints = []resourceapi.DeviceTaint{{
Key: "example.com/taint",
Value: "tainted",
Effect: resourceapi.DeviceTaintEffectNoSchedule,
}}
}
slice.Spec.Devices = append(slice.Spec.Devices, device)
}
_, err := d.f.ClientSet.ResourceV1beta2().ResourceSlices().Create(ctx, slice, metav1.CreateOptions{})
framework.ExpectNoError(err)
ginkgo.DeferCleanup(func(ctx context.Context) {
framework.ExpectNoError(d.f.ClientSet.ResourceV1beta2().ResourceSlices().Delete(ctx, slice.Name, metav1.DeleteOptions{}))
})
}
manifests := []string{
@@ -354,10 +323,6 @@ func (d *Driver) SetUp(nodes *Nodes, resources Resources, devicesPerNode ...map[
// container names, etc.).
"test/e2e/testing-manifests/dra/dra-test-driver-proxy.yaml",
}
var numDevices = -1 // disabled
if resources.NodeLocal {
numDevices = resources.MaxAllocations
}
// Create service account and corresponding RBAC rules.
d.serviceAccountName = "dra-kubelet-plugin-" + d.Name + d.InstanceSuffix + "-service-account"
@@ -427,7 +392,7 @@ func (d *Driver) SetUp(nodes *Nodes, resources Resources, devicesPerNode ...map[
})
// Run registrar and plugin for each of the pods.
for i, pod := range pods.Items {
for _, pod := range pods.Items {
// Need a local variable, not the loop variable, for the anonymous
// callback functions below.
pod := pod
@@ -460,11 +425,9 @@ func (d *Driver) SetUp(nodes *Nodes, resources Resources, devicesPerNode ...map[
return d.removeFile(&pod, name)
},
}
if i < len(devicesPerNode) {
fileOps.Devices = devicesPerNode[i]
fileOps.NumDevices = -1
} else {
fileOps.NumDevices = numDevices
if dr, ok := driverResources[nodename]; ok {
fileOps.DriverResources = &dr
}
// All listeners running in this pod use a new unique local port number
// by atomically incrementing this variable.

View File

@@ -60,24 +60,6 @@ const (
podStartTimeout = 5 * time.Minute
)
// networkResources can be passed to NewDriver directly.
func networkResources() Resources {
return Resources{}
}
// perNode returns a function which can be passed to NewDriver. The nodes
// parameter has be instantiated, but not initialized yet, so the returned
// function has to capture it and use it when being called.
func perNode(maxAllocations int, nodes *Nodes) func() Resources {
return func() Resources {
return Resources{
NodeLocal: true,
MaxAllocations: maxAllocations,
Nodes: nodes.NodeNames,
}
}
}
// TODO: remove feature.DynamicResourceAllocation here and add it only for those tests
// which really need node-level support for DRA.
//
@@ -91,7 +73,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
ginkgo.Context("kubelet", func() {
nodes := NewNodes(f, 1, 1)
driver := NewDriver(f, nodes, networkResources)
driver := NewDriver(f, nodes, networkResources(10, false))
b := newBuilder(f, driver)
ginkgo.It("registers plugin", func() {
@@ -480,11 +462,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
nodes := NewNodes(f, 1, 1)
maxAllocations := 1
numPods := 10
generateResources := func() Resources {
resources := perNode(maxAllocations, nodes)()
return resources
}
driver := NewDriver(f, nodes, generateResources) // All tests get their own driver instance.
driver := NewDriver(f, nodes, driverResources(maxAllocations)) // All tests get their own driver instance.
b := newBuilder(f, driver)
// We have to set the parameters *before* creating the class.
b.classParameters = `{"x":"y"}`
@@ -650,7 +628,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
},
},
}
driver := NewDriver(f, nodes, perNode(-1, nodes), devicesPerNode...)
driver := NewDriver(f, nodes, driverResources(-1, devicesPerNode...))
b := newBuilder(f, driver)
ginkgo.It("keeps pod pending because of CEL runtime errors", func(ctx context.Context) {
@@ -708,7 +686,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
})
ginkgo.Context("with node-local resources", func() {
driver := NewDriver(f, nodes, perNode(1, nodes))
driver := NewDriver(f, nodes, driverResources(1))
b := newBuilder(f, driver)
ginkgo.It("uses all resources", func(ctx context.Context) {
@@ -750,7 +728,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
})
ginkgo.Context("with network-attached resources", func() {
driver := NewDriver(f, nodes, networkResources)
driver := NewDriver(f, nodes, networkResources(10, false))
b := newBuilder(f, driver)
f.It("supports sharing a claim sequentially", f.WithSlow(), func(ctx context.Context) {
@@ -867,7 +845,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
driver1Params, driver1Env := `{"driver":"1"}`, []string{"admin_driver", "1"}
driver2Params, driver2Env := `{"driver":"2"}`, []string{"admin_driver", "2"}
driver1 := NewDriver(f, nodes, perNode(-1, nodes), []map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
driver1 := NewDriver(f, nodes, driverResources(-1, []map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
{
"device-1-1": {
"dra.example.com/version": {StringValue: ptr.To("1.0.0")},
@@ -878,19 +856,19 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
"dra.example.com/pcieRoot": {StringValue: ptr.To("foo")},
},
},
}...)
}...))
driver1.NameSuffix = "-1"
b1 := newBuilder(f, driver1)
b1.classParameters = driver1Params
driver2 := NewDriver(f, nodes, perNode(-1, nodes), []map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
driver2 := NewDriver(f, nodes, driverResources(-1, []map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
{
"device-2-1": {
"dra.example.com/version": {StringValue: ptr.To("1.0.0")},
"dra.example.com/pcieRoot": {StringValue: ptr.To("foo")},
},
},
}...)
}...))
driver2.NameSuffix = "-2"
b2 := newBuilder(f, driver2)
b2.classParameters = driver2Params
@@ -1273,10 +1251,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
v1beta2Tests := func() {
nodes := NewNodes(f, 1, 1)
maxAllocations := 1
generateResources := func() Resources {
return perNode(maxAllocations, nodes)()
}
driver := NewDriver(f, nodes, generateResources) // All tests get their own driver instance.
driver := NewDriver(f, nodes, driverResources(maxAllocations))
b := newBuilder(f, driver)
// We have to set the parameters *before* creating the class.
b.classParameters = `{"x":"y"}`
@@ -1370,11 +1345,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
framework.Context(f.WithFeatureGate(features.DRADeviceTaints), func() {
nodes := NewNodes(f, 1, 1)
driver := NewDriver(f, nodes, func() Resources {
return Resources{
Tainted: true,
}
})
driver := NewDriver(f, nodes, networkResources(10, true))
b := newBuilder(f, driver)
f.It("DeviceTaint keeps pod pending", func(ctx context.Context) {
@@ -1544,7 +1515,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
ginkgo.Context("cluster", func() {
nodes := NewNodes(f, 1, 1)
driver := NewDriver(f, nodes, networkResources)
driver := NewDriver(f, nodes, networkResources(10, false))
b := newBuilder(f, driver)
f.It("validate ResourceClaimTemplate and ResourceClaim for admin access", f.WithFeatureGate(features.DRAAdminAccess), func(ctx context.Context) {
@@ -1697,7 +1668,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
ginkgo.Context("cluster", func() {
nodes := NewNodes(f, 1, 4)
driver := NewDriver(f, nodes, perNode(1, nodes))
driver := NewDriver(f, nodes, driverResources(1))
f.It("must apply per-node permission checks", func(ctx context.Context) {
// All of the operations use the client set of a kubelet plugin for
@@ -1901,11 +1872,11 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
multipleDrivers := func(nodeV1beta1 bool) {
nodes := NewNodes(f, 1, 4)
driver1 := NewDriver(f, nodes, perNode(2, nodes))
driver1 := NewDriver(f, nodes, driverResources(2))
driver1.NodeV1beta1 = nodeV1beta1
b1 := newBuilder(f, driver1)
driver2 := NewDriver(f, nodes, perNode(2, nodes))
driver2 := NewDriver(f, nodes, driverResources(2))
driver2.NodeV1beta1 = nodeV1beta1
driver2.NameSuffix = "-other"
b2 := newBuilder(f, driver2)
@@ -1952,7 +1923,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
framework.ExpectNoError(e2epod.WaitForPodNameUnschedulableInNamespace(ctx, f.ClientSet, pod.Name, pod.Namespace))
// Set up driver, which makes devices available.
driver.Run(nodes, perNode(1, nodes))
driver.Run(nodes, driverResourcesNow(nodes, 1))
// Now it should run.
b.testPod(ctx, f, pod)
@@ -1970,7 +1941,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
oldDriver := NewDriverInstance(f)
oldDriver.InstanceSuffix = "-old"
oldDriver.RollingUpdate = true
oldDriver.Run(nodes, perNode(1, nodes))
oldDriver.Run(nodes, driverResourcesNow(nodes, 1))
// We expect one ResourceSlice per node from the driver.
getSlices := oldDriver.NewGetSlices()
@@ -1982,7 +1953,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
newDriver := NewDriverInstance(f)
newDriver.InstanceSuffix = "-new"
newDriver.RollingUpdate = true
newDriver.Run(nodes, perNode(1, nodes))
newDriver.Run(nodes, driverResourcesNow(nodes, 1))
// Stop old driver instance.
oldDriver.TearDown(ctx)
@@ -2012,7 +1983,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
oldDriver := NewDriverInstance(f)
oldDriver.InstanceSuffix = "-old"
oldDriver.RollingUpdate = true
oldDriver.Run(nodes, perNode(1, nodes))
oldDriver.Run(nodes, driverResourcesNow(nodes, 1))
// We expect one ResourceSlice per node from the driver.
getSlices := oldDriver.NewGetSlices()
@@ -2024,7 +1995,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
newDriver := NewDriverInstance(f)
newDriver.InstanceSuffix = "-new"
newDriver.RollingUpdate = true
newDriver.Run(nodes, perNode(1, nodes))
newDriver.Run(nodes, driverResourcesNow(nodes, 1))
// Stop new driver instance, simulating the failure of the new instance.
// The kubelet should still have the old instance.
@@ -2055,7 +2026,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
// Same driver name, same socket path.
oldDriver := NewDriverInstance(f)
oldDriver.InstanceSuffix = "-old"
oldDriver.Run(nodes, perNode(1, nodes))
oldDriver.Run(nodes, driverResourcesNow(nodes, 1))
// Collect set of resource slices for that driver.
listSlices := framework.ListObjects(f.ClientSet.ResourceV1beta2().ResourceSlices().List, metav1.ListOptions{
@@ -2076,7 +2047,7 @@ var _ = framework.SIGDescribe("node")(framework.WithLabel("DRA"), feature.Dynami
oldDriver.TearDown(ctx)
newDriver := NewDriverInstance(f)
newDriver.InstanceSuffix = "-new"
newDriver.Run(nodes, perNode(1, nodes))
newDriver.Run(nodes, driverResourcesNow(nodes, 1))
updateDuration := time.Since(start)
// Build behaves the same for both driver instances.
@@ -2511,3 +2482,94 @@ func (b *builder) listTestPods(ctx context.Context) ([]v1.Pod, error) {
}
return testPods, nil
}
func networkResources(maxAllocations int, tainted bool) driverResourcesGenFunc {
return func(nodes *Nodes) map[string]resourceslice.DriverResources {
driverResources := make(map[string]resourceslice.DriverResources)
devices := make([]resourceapi.Device, 0)
for i := 0; i < maxAllocations; i++ {
device := resourceapi.Device{
Name: fmt.Sprintf("device-%d", i),
Basic: &resourceapi.BasicDevice{},
}
if tainted {
device.Basic.Taints = []resourceapi.DeviceTaint{{
Key: "example.com/taint",
Value: "tainted",
Effect: resourceapi.DeviceTaintEffectNoSchedule,
}}
}
devices = append(devices, device)
}
driverResources[networkPool] = resourceslice.DriverResources{
Pools: map[string]resourceslice.Pool{
"network": {
Slices: []resourceslice.Slice{{
Devices: devices,
}},
NodeSelector: &v1.NodeSelector{
NodeSelectorTerms: []v1.NodeSelectorTerm{{
// MatchExpressions allow multiple values,
// MatchFields don't.
MatchExpressions: []v1.NodeSelectorRequirement{{
Key: "kubernetes.io/hostname",
Operator: v1.NodeSelectorOpIn,
Values: nodes.NodeNames,
}},
}},
},
Generation: 1,
},
},
}
return driverResources
}
}
func driverResources(maxAllocations int, devicesPerNode ...map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute) driverResourcesGenFunc {
return func(nodes *Nodes) map[string]resourceslice.DriverResources {
return driverResourcesNow(nodes, maxAllocations, devicesPerNode...)
}
}
func driverResourcesNow(nodes *Nodes, maxAllocations int, devicesPerNode ...map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute) map[string]resourceslice.DriverResources {
driverResources := make(map[string]resourceslice.DriverResources)
for i, nodename := range nodes.NodeNames {
if i < len(devicesPerNode) {
devices := make([]resourceapi.Device, 0)
for deviceName, attributes := range devicesPerNode[i] {
devices = append(devices, resourceapi.Device{
Name: deviceName,
Basic: &resourceapi.BasicDevice{Attributes: attributes},
})
}
driverResources[nodename] = resourceslice.DriverResources{
Pools: map[string]resourceslice.Pool{
nodename: {
Slices: []resourceslice.Slice{{
Devices: devices,
}},
},
},
}
} else if maxAllocations >= 0 {
devices := make([]resourceapi.Device, maxAllocations)
for i := 0; i < maxAllocations; i++ {
devices[i] = resourceapi.Device{
Name: fmt.Sprintf("device-%02d", i),
Basic: &resourceapi.BasicDevice{},
}
}
driverResources[nodename] = resourceslice.DriverResources{
Pools: map[string]resourceslice.Pool{
nodename: {
Slices: []resourceslice.Slice{{
Devices: devices,
}},
},
},
}
}
}
return driverResources
}

View File

@@ -35,7 +35,6 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/kubernetes"
cgoresource "k8s.io/client-go/kubernetes/typed/resource/v1beta2"
draclient "k8s.io/dynamic-resource-allocation/client"
@@ -52,10 +51,9 @@ type ExamplePlugin struct {
d *kubeletplugin.Helper
fileOps FileOperations
cdiDir string
driverName string
nodeName string
deviceNames sets.Set[string]
cdiDir string
driverName string
nodeName string
// The mutex is needed because there are other goroutines checking the state.
// Serializing in the gRPC server alone is not enough because writing would
@@ -121,16 +119,11 @@ type FileOperations struct {
// file does not exist.
Remove func(name string) error
// NumDevices determines whether the plugin reports devices
// and how many. It reports nothing if negative.
NumDevices int
// Pre-defined devices, with each device name mapped to
// the device attributes. Not used if NumDevices >= 0.
Devices map[string]map[resourceapi.QualifiedName]resourceapi.DeviceAttribute
// ErrorHandler is an optional callback for ResourceSlice publishing problems.
ErrorHandler func(ctx context.Context, err error, msg string)
// DriverResources provides the information that the driver will use to
// construct the ResourceSlices that it will publish.
DriverResources *resourceslice.DriverResources
}
// StartPlugin sets up the servers that are necessary for a DRA kubelet plugin.
@@ -159,15 +152,8 @@ func StartPlugin(ctx context.Context, cdiDir, driverName string, kubeClient kube
driverName: driverName,
nodeName: nodeName,
prepared: make(map[ClaimID][]kubeletplugin.Device),
deviceNames: sets.New[string](),
}
for i := 0; i < ex.fileOps.NumDevices; i++ {
ex.deviceNames.Insert(fmt.Sprintf("device-%02d", i))
}
for deviceName := range ex.fileOps.Devices {
ex.deviceNames.Insert(deviceName)
}
opts = append(opts,
kubeletplugin.DriverName(driverName),
kubeletplugin.NodeName(nodeName),
@@ -181,43 +167,8 @@ func StartPlugin(ctx context.Context, cdiDir, driverName string, kubeClient kube
}
ex.d = d
if fileOps.NumDevices >= 0 {
devices := make([]resourceapi.Device, ex.fileOps.NumDevices)
for i := 0; i < ex.fileOps.NumDevices; i++ {
devices[i] = resourceapi.Device{
Name: fmt.Sprintf("device-%02d", i),
}
}
driverResources := resourceslice.DriverResources{
Pools: map[string]resourceslice.Pool{
nodeName: {
Slices: []resourceslice.Slice{{
Devices: devices,
}},
},
},
}
if err := ex.d.PublishResources(ctx, driverResources); err != nil {
return nil, fmt.Errorf("start kubelet plugin: publish resources: %w", err)
}
} else if len(ex.fileOps.Devices) > 0 {
devices := make([]resourceapi.Device, len(ex.fileOps.Devices))
for i, deviceName := range sets.List(ex.deviceNames) {
devices[i] = resourceapi.Device{
Name: deviceName,
Attributes: ex.fileOps.Devices[deviceName],
}
}
driverResources := resourceslice.DriverResources{
Pools: map[string]resourceslice.Pool{
nodeName: {
Slices: []resourceslice.Slice{{
Devices: devices,
}},
},
},
}
if err := ex.d.PublishResources(ctx, driverResources); err != nil {
if fileOps.DriverResources != nil {
if err := ex.d.PublishResources(ctx, *fileOps.DriverResources); err != nil {
return nil, fmt.Errorf("start kubelet plugin: publish resources: %w", err)
}
}

View File

@@ -34,6 +34,7 @@ import (
"github.com/spf13/cobra"
"k8s.io/component-base/metrics"
resourceapi "k8s.io/api/resource/v1beta1"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
@@ -45,6 +46,7 @@ import (
"k8s.io/component-base/metrics/legacyregistry"
"k8s.io/component-base/term"
"k8s.io/dynamic-resource-allocation/kubeletplugin"
"k8s.io/dynamic-resource-allocation/resourceslice"
"k8s.io/klog/v2"
)
@@ -204,7 +206,24 @@ func NewCommand() *cobra.Command {
return errors.New("--node-name not set")
}
plugin, err := StartPlugin(cmd.Context(), *cdiDir, *driverName, clientset, *nodeName, FileOperations{NumDevices: *numDevices},
devices := make([]resourceapi.Device, *numDevices)
for i := 0; i < *numDevices; i++ {
devices[i] = resourceapi.Device{
Name: fmt.Sprintf("device-%02d", i),
Basic: &resourceapi.BasicDevice{},
}
}
driverResources := resourceslice.DriverResources{
Pools: map[string]resourceslice.Pool{
*nodeName: {
Slices: []resourceslice.Slice{{
Devices: devices,
}},
},
},
}
plugin, err := StartPlugin(cmd.Context(), *cdiDir, *driverName, clientset, *nodeName, FileOperations{DriverResources: &driverResources},
kubeletplugin.PluginDataDirectoryPath(datadir),
kubeletplugin.RegistrarDirectoryPath(*kubeletRegistryDir),
)

View File

@@ -54,6 +54,7 @@ import (
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
"k8s.io/dynamic-resource-allocation/kubeletplugin"
"k8s.io/dynamic-resource-allocation/resourceslice"
testdriver "k8s.io/kubernetes/test/e2e/dra/test-driver/app"
)
@@ -566,7 +567,22 @@ func newKubeletPlugin(ctx context.Context, clientSet kubernetes.Interface, nodeN
pluginName,
clientSet,
nodeName,
testdriver.FileOperations{},
testdriver.FileOperations{
DriverResources: &resourceslice.DriverResources{
Pools: map[string]resourceslice.Pool{
nodeName: {
Slices: []resourceslice.Slice{{
Devices: []resourceapi.Device{
{
Name: "device-00",
Basic: &resourceapi.BasicDevice{},
},
},
}},
},
},
},
},
)
framework.ExpectNoError(err)