dra api: rename NodeResourceSlice -> ResourceSlice

While currently those objects only get published by the kubelet for node-local
resources, this could change once we also support network-attached
resources. Dropping the "Node" prefix enables such a future extension.

The NodeName in ResourceSlice and StructuredResourceHandle then becomes
optional. The kubelet still needs to provide one and it must match its own node
name, otherwise it doesn't have permission to access ResourceSlice objects.
This commit is contained in:
Patrick Ohly
2024-03-07 10:14:11 +01:00
parent 42ee56f093
commit 0b6a0d686a
60 changed files with 3868 additions and 3859 deletions

View File

@@ -110,13 +110,13 @@ func (p *Plugin) ValidateInitialization() error {
}
var (
podResource = api.Resource("pods")
nodeResource = api.Resource("nodes")
pvcResource = api.Resource("persistentvolumeclaims")
svcacctResource = api.Resource("serviceaccounts")
leaseResource = coordapi.Resource("leases")
csiNodeResource = storage.Resource("csinodes")
nodeResourceSliceResource = resource.Resource("noderesourceslices")
podResource = api.Resource("pods")
nodeResource = api.Resource("nodes")
pvcResource = api.Resource("persistentvolumeclaims")
svcacctResource = api.Resource("serviceaccounts")
leaseResource = coordapi.Resource("leases")
csiNodeResource = storage.Resource("csinodes")
resourceSliceResource = resource.Resource("resourceslices")
)
// Admit checks the admission policy and triggers corresponding actions
@@ -168,8 +168,8 @@ func (p *Plugin) Admit(ctx context.Context, a admission.Attributes, o admission.
case csiNodeResource:
return p.admitCSINode(nodeName, a)
case nodeResourceSliceResource:
return p.admitNodeResourceSlice(nodeName, a)
case resourceSliceResource:
return p.admitResourceSlice(nodeName, a)
default:
return nil
@@ -639,17 +639,17 @@ func (p *Plugin) admitCSINode(nodeName string, a admission.Attributes) error {
return nil
}
func (p *Plugin) admitNodeResourceSlice(nodeName string, a admission.Attributes) error {
func (p *Plugin) admitResourceSlice(nodeName string, a admission.Attributes) error {
// The create request must come from a node with the same name as the NodeName field.
// Other requests gets checked by the node authorizer.
if a.GetOperation() == admission.Create {
slice, ok := a.GetObject().(*resource.NodeResourceSlice)
slice, ok := a.GetObject().(*resource.ResourceSlice)
if !ok {
return admission.NewForbidden(a, fmt.Errorf("unexpected type %T", a.GetObject()))
}
if slice.NodeName != nodeName {
return admission.NewForbidden(a, errors.New("can only create NodeResourceSlice with the same NodeName as the requesting node"))
return admission.NewForbidden(a, errors.New("can only create ResourceSlice with the same NodeName as the requesting node"))
}
}

View File

@@ -1603,19 +1603,19 @@ func createPodAttributes(pod *api.Pod, user user.Info) admission.Attributes {
return admission.NewAttributesRecord(pod, nil, podKind, pod.Namespace, pod.Name, podResource, "", admission.Create, &metav1.CreateOptions{}, false, user)
}
func TestAdmitNodeResourceSlice(t *testing.T) {
apiResource := resourceapi.SchemeGroupVersion.WithResource("noderesourceslices")
func TestAdmitResourceSlice(t *testing.T) {
apiResource := resourceapi.SchemeGroupVersion.WithResource("resourceslices")
nodename := "mynode"
mynode := &user.DefaultInfo{Name: "system:node:" + nodename, Groups: []string{"system:nodes"}}
err := "can only create NodeResourceSlice with the same NodeName as the requesting node"
err := "can only create ResourceSlice with the same NodeName as the requesting node"
sliceNode := &resourceapi.NodeResourceSlice{
sliceNode := &resourceapi.ResourceSlice{
ObjectMeta: metav1.ObjectMeta{
Name: "something",
},
NodeName: nodename,
}
sliceOtherNode := &resourceapi.NodeResourceSlice{
sliceOtherNode := &resourceapi.ResourceSlice{
ObjectMeta: metav1.ObjectMeta{
Name: "something",
},

View File

@@ -127,7 +127,7 @@ const (
var vertexTypes = map[vertexType]string{
configMapVertexType: "configmap",
sliceVertexType: "noderesourceslice",
sliceVertexType: "resourceslice",
nodeVertexType: "node",
podVertexType: "pod",
pvcVertexType: "pvc",
@@ -495,13 +495,13 @@ func (g *Graph) DeleteVolumeAttachment(name string) {
g.deleteVertex_locked(vaVertexType, "", name)
}
// AddNodeResourceSlice sets up edges for the following relationships:
// AddResourceSlice sets up edges for the following relationships:
//
// node resource slice -> node
func (g *Graph) AddNodeResourceSlice(sliceName, nodeName string) {
func (g *Graph) AddResourceSlice(sliceName, nodeName string) {
start := time.Now()
defer func() {
graphActionsDuration.WithLabelValues("AddNodeResourceSlice").Observe(time.Since(start).Seconds())
graphActionsDuration.WithLabelValues("AddResourceSlice").Observe(time.Since(start).Seconds())
}()
g.lock.Lock()
defer g.lock.Unlock()
@@ -516,10 +516,10 @@ func (g *Graph) AddNodeResourceSlice(sliceName, nodeName string) {
g.graph.SetEdge(newDestinationEdge(sliceVertex, nodeVertex, nodeVertex))
}
}
func (g *Graph) DeleteNodeResourceSlice(sliceName string) {
func (g *Graph) DeleteResourceSlice(sliceName string) {
start := time.Now()
defer func() {
graphActionsDuration.WithLabelValues("DeleteNodeResourceSlice").Observe(time.Since(start).Seconds())
graphActionsDuration.WithLabelValues("DeleteResourceSlice").Observe(time.Since(start).Seconds())
}()
g.lock.Lock()
defer g.lock.Unlock()

View File

@@ -41,7 +41,7 @@ func AddGraphEventHandlers(
pods corev1informers.PodInformer,
pvs corev1informers.PersistentVolumeInformer,
attachments storageinformers.VolumeAttachmentInformer,
slices resourcev1alpha2informers.NodeResourceSliceInformer,
slices resourcev1alpha2informers.ResourceSliceInformer,
) {
g := &graphPopulator{
graph: graph,
@@ -71,9 +71,9 @@ func AddGraphEventHandlers(
if slices != nil {
sliceHandler, _ := slices.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: g.addNodeResourceSlice,
AddFunc: g.addResourceSlice,
UpdateFunc: nil, // Not needed, NodeName is immutable.
DeleteFunc: g.deleteNodeResourceSlice,
DeleteFunc: g.deleteResourceSlice,
})
synced = append(synced, sliceHandler.HasSynced)
}
@@ -200,23 +200,23 @@ func (g *graphPopulator) deleteVolumeAttachment(obj interface{}) {
g.graph.DeleteVolumeAttachment(attachment.Name)
}
func (g *graphPopulator) addNodeResourceSlice(obj interface{}) {
slice, ok := obj.(*resourcev1alpha2.NodeResourceSlice)
func (g *graphPopulator) addResourceSlice(obj interface{}) {
slice, ok := obj.(*resourcev1alpha2.ResourceSlice)
if !ok {
klog.Infof("unexpected type %T", obj)
return
}
g.graph.AddNodeResourceSlice(slice.Name, slice.NodeName)
g.graph.AddResourceSlice(slice.Name, slice.NodeName)
}
func (g *graphPopulator) deleteNodeResourceSlice(obj interface{}) {
func (g *graphPopulator) deleteResourceSlice(obj interface{}) {
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
obj = tombstone.Obj
}
slice, ok := obj.(*resourcev1alpha2.NodeResourceSlice)
slice, ok := obj.(*resourcev1alpha2.ResourceSlice)
if !ok {
klog.Infof("unexpected type %T", obj)
return
}
g.graph.DeleteNodeResourceSlice(slice.Name)
g.graph.DeleteResourceSlice(slice.Name)
}

View File

@@ -50,7 +50,7 @@ import (
// node <- pod <- pvc <- pv
// node <- pod <- pvc <- pv <- secret
// node <- pod <- ResourceClaim
// 4. If a request is for a noderesourceslice, then authorize access if there is an
// 4. If a request is for a resourceslice, then authorize access if there is an
// edge from the existing slice object to the node, which is the case if the
// existing object has the node in its NodeName field. For create, the access gets
// granted because the noderestriction admission plugin checks that the NodeName
@@ -81,7 +81,7 @@ func NewAuthorizer(graph *Graph, identifier nodeidentifier.NodeIdentifier, rules
var (
configMapResource = api.Resource("configmaps")
secretResource = api.Resource("secrets")
nodeResourceSlice = resourceapi.Resource("noderesourceslices")
resourceSlice = resourceapi.Resource("resourceslices")
pvcResource = api.Resource("persistentvolumeclaims")
pvResource = api.Resource("persistentvolumes")
resourceClaimResource = resourceapi.Resource("resourceclaims")
@@ -136,8 +136,8 @@ func (r *NodeAuthorizer) Authorize(ctx context.Context, attrs authorizer.Attribu
return r.authorizeLease(nodeName, attrs)
case csiNodeResource:
return r.authorizeCSINode(nodeName, attrs)
case nodeResourceSlice:
return r.authorizeNodeResourceSlice(nodeName, attrs)
case resourceSlice:
return r.authorizeResourceSlice(nodeName, attrs)
}
}
@@ -302,11 +302,11 @@ func (r *NodeAuthorizer) authorizeCSINode(nodeName string, attrs authorizer.Attr
return authorizer.DecisionAllow, "", nil
}
// authorizeNodeResourceSlice authorizes node requests to NodeResourceSlice resource.k8s.io/noderesourceslices
func (r *NodeAuthorizer) authorizeNodeResourceSlice(nodeName string, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
// authorizeResourceSlice authorizes node requests to ResourceSlice resource.k8s.io/resourceslices
func (r *NodeAuthorizer) authorizeResourceSlice(nodeName string, attrs authorizer.Attributes) (authorizer.Decision, string, error) {
if len(attrs.GetSubresource()) > 0 {
klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "cannot authorize NodeResourceSlice subresources", nil
return authorizer.DecisionNoOpinion, "cannot authorize ResourceSlice subresources", nil
}
// allowed verbs: get, create, update, patch, delete
@@ -319,10 +319,10 @@ func (r *NodeAuthorizer) authorizeNodeResourceSlice(nodeName string, attrs autho
return authorizer.DecisionAllow, "", nil
default:
klog.V(2).Infof("NODE DENY: '%s' %#v", nodeName, attrs)
return authorizer.DecisionNoOpinion, "can only get, create, update, patch, or delete a NodeResourceSlice", nil
return authorizer.DecisionNoOpinion, "can only get, create, update, patch, or delete a ResourceSlice", nil
}
// The request must come from a node with the same name as the NodeResourceSlice.NodeName field.
// The request must come from a node with the same name as the ResourceSlice.NodeName field.
//
// For create, the noderestriction admission plugin is performing this check.
// Here we don't have access to the content of the new object.

View File

@@ -338,65 +338,65 @@ func TestAuthorizer(t *testing.T) {
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "csinodes", APIGroup: "storage.k8s.io", Name: "node0"},
expect: authorizer.DecisionAllow,
},
// NodeResourceSlice
// ResourceSlice
{
name: "disallowed NodeResourceSlice with subresource",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "noderesourceslices", Subresource: "status", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
name: "disallowed ResourceSlice with subresource",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceslices", Subresource: "status", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed get another node's NodeResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "noderesourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"},
name: "disallowed get another node's ResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed update another node's NodeResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "noderesourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"},
name: "disallowed update another node's ResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed patch another node's NodeResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "noderesourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"},
name: "disallowed patch another node's ResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "disallowed delete another node's NodeResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "noderesourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"},
name: "disallowed delete another node's ResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node1"},
expect: authorizer.DecisionNoOpinion,
},
{
name: "allowed list NodeResourceSlices",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "list", Resource: "noderesourceslices", APIGroup: "resource.k8s.io"},
name: "allowed list ResourceSlices",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "list", Resource: "resourceslices", APIGroup: "resource.k8s.io"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed watch NodeResourceSlices",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "watch", Resource: "noderesourceslices", APIGroup: "resource.k8s.io"},
name: "allowed watch ResourceSlices",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "watch", Resource: "resourceslices", APIGroup: "resource.k8s.io"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed get NodeResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "noderesourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
name: "allowed get ResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "get", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed create NodeResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "noderesourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
name: "allowed create ResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "create", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed update NodeResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "noderesourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
name: "allowed update ResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "update", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed patch NodeResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "noderesourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
name: "allowed patch ResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "patch", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
expect: authorizer.DecisionAllow,
},
{
name: "allowed delete NodeResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "noderesourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
name: "allowed delete ResourceSlice",
attrs: authorizer.AttributesRecord{User: node0, ResourceRequest: true, Verb: "delete", Resource: "resourceslices", APIGroup: "resource.k8s.io", Name: "slice0-node0"},
expect: authorizer.DecisionAllow,
},
}
@@ -831,7 +831,7 @@ func BenchmarkAuthorization(b *testing.B) {
}
}
func populate(graph *Graph, nodes []*corev1.Node, pods []*corev1.Pod, pvs []*corev1.PersistentVolume, attachments []*storagev1.VolumeAttachment, slices []*resourcev1alpha2.NodeResourceSlice) {
func populate(graph *Graph, nodes []*corev1.Node, pods []*corev1.Pod, pvs []*corev1.PersistentVolume, attachments []*storagev1.VolumeAttachment, slices []*resourcev1alpha2.ResourceSlice) {
p := &graphPopulator{}
p.graph = graph
for _, pod := range pods {
@@ -844,7 +844,7 @@ func populate(graph *Graph, nodes []*corev1.Node, pods []*corev1.Pod, pvs []*cor
p.addVolumeAttachment(attachment)
}
for _, slice := range slices {
p.addNodeResourceSlice(slice)
p.addResourceSlice(slice)
}
}
@@ -859,12 +859,12 @@ func randomSubset(a, b int) []int {
// the secret/configmap/pvc/node references in the pod and pv objects are named to indicate the connections between the objects.
// for example, secret0-pod0-node0 is a secret referenced by pod0 which is bound to node0.
// when populated into the graph, the node authorizer should allow node0 to access that secret, but not node1.
func generate(opts *sampleDataOpts) ([]*corev1.Node, []*corev1.Pod, []*corev1.PersistentVolume, []*storagev1.VolumeAttachment, []*resourcev1alpha2.NodeResourceSlice) {
func generate(opts *sampleDataOpts) ([]*corev1.Node, []*corev1.Pod, []*corev1.PersistentVolume, []*storagev1.VolumeAttachment, []*resourcev1alpha2.ResourceSlice) {
nodes := make([]*corev1.Node, 0, opts.nodes)
pods := make([]*corev1.Pod, 0, opts.nodes*opts.podsPerNode)
pvs := make([]*corev1.PersistentVolume, 0, (opts.nodes*opts.podsPerNode*opts.uniquePVCsPerPod)+(opts.sharedPVCsPerPod*opts.namespaces))
attachments := make([]*storagev1.VolumeAttachment, 0, opts.nodes*opts.attachmentsPerNode)
slices := make([]*resourcev1alpha2.NodeResourceSlice, 0, opts.nodes*opts.nodeResourceCapacitiesPerNode)
slices := make([]*resourcev1alpha2.ResourceSlice, 0, opts.nodes*opts.nodeResourceCapacitiesPerNode)
rand.Seed(12345)
@@ -893,7 +893,7 @@ func generate(opts *sampleDataOpts) ([]*corev1.Node, []*corev1.Pod, []*corev1.Pe
for p := 0; p <= opts.nodeResourceCapacitiesPerNode; p++ {
name := fmt.Sprintf("slice%d-%s", p, nodeName)
slice := &resourcev1alpha2.NodeResourceSlice{
slice := &resourcev1alpha2.ResourceSlice{
ObjectMeta: metav1.ObjectMeta{Name: name},
NodeName: nodeName,
}

View File

@@ -582,7 +582,7 @@ func ClusterRoles() []rbacv1.ClusterRole {
rbacv1helpers.NewRule(ReadWrite...).Groups(resourceGroup).Resources("podschedulingcontexts").RuleOrDie(),
rbacv1helpers.NewRule(Read...).Groups(resourceGroup).Resources("podschedulingcontexts/status").RuleOrDie(),
rbacv1helpers.NewRule(ReadUpdate...).Groups(legacyGroup).Resources("pods/finalizers").RuleOrDie(),
rbacv1helpers.NewRule(Read...).Groups(resourceGroup).Resources("noderesourceslices", "resourceclassparameters", "resourceclaimparameters").RuleOrDie(),
rbacv1helpers.NewRule(Read...).Groups(resourceGroup).Resources("resourceslices", "resourceclassparameters", "resourceclaimparameters").RuleOrDie(),
)
}
roles = append(roles, rbacv1.ClusterRole{