Skip to content

Commit 0301e5a

Browse files
committed
DRA: AdminAccess validate based on namespace label
Signed-off-by: Rita Zhang <[email protected]>
1 parent f007012 commit 0301e5a

File tree

18 files changed

+927
-178
lines changed

18 files changed

+927
-178
lines changed

pkg/apis/resource/types.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,15 @@ const (
385385
DeviceConfigMaxSize = 32
386386
)
387387

388+
// DRAAdminNamespaceLabelKey is a label key used to grant administrative access
389+
// to certain resource.k8s.io API types within a namespace. When this label is
390+
// set on a namespace with the value "true" (case-sensitive), it allows the use
391+
// of adminAccess: true in any namespaced resource.k8s.io API types. Currently,
392+
// this permission applies to ResourceClaim and ResourceClaimTemplate objects.
393+
const (
394+
DRAAdminNamespaceLabelKey = "resource.k8s.io/admin-access"
395+
)
396+
388397
// DeviceRequest is a request for devices required for a claim.
389398
// This is typically a request for a single resource like a device, but can
390399
// also ask for several identical devices.

pkg/controller/resourceclaim/controller_test.go

Lines changed: 68 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"k8s.io/kubernetes/pkg/controller"
4141
"k8s.io/kubernetes/pkg/controller/resourceclaim/metrics"
4242
"k8s.io/kubernetes/test/utils/ktesting"
43+
"k8s.io/utils/ptr"
4344
)
4445

4546
var (
@@ -61,13 +62,15 @@ var (
6162
testClaimReserved = reserveClaim(testClaimAllocated, testPodWithResource)
6263
testClaimReservedTwice = reserveClaim(testClaimReserved, otherTestPod)
6364

64-
generatedTestClaim = makeGeneratedClaim(podResourceClaimName, testPodName+"-"+podResourceClaimName+"-", testNamespace, className, 1, makeOwnerReference(testPodWithResource, true))
65+
generatedTestClaim = makeGeneratedClaim(podResourceClaimName, testPodName+"-"+podResourceClaimName+"-", testNamespace, className, 1, makeOwnerReference(testPodWithResource, true), nil)
66+
generatedTestClaimWithAdmin = makeGeneratedClaim(podResourceClaimName, testPodName+"-"+podResourceClaimName+"-", testNamespace, className, 1, makeOwnerReference(testPodWithResource, true), ptr.To(true))
6567
generatedTestClaimAllocated = allocateClaim(generatedTestClaim)
6668
generatedTestClaimReserved = reserveClaim(generatedTestClaimAllocated, testPodWithResource)
6769

68-
conflictingClaim = makeClaim(testPodName+"-"+podResourceClaimName, testNamespace, className, nil)
69-
otherNamespaceClaim = makeClaim(testPodName+"-"+podResourceClaimName, otherNamespace, className, nil)
70-
template = makeTemplate(templateName, testNamespace, className)
70+
conflictingClaim = makeClaim(testPodName+"-"+podResourceClaimName, testNamespace, className, nil)
71+
otherNamespaceClaim = makeClaim(testPodName+"-"+podResourceClaimName, otherNamespace, className, nil)
72+
template = makeTemplate(templateName, testNamespace, className, nil)
73+
templateWithAdminAccess = makeTemplate(templateName, testNamespace, className, ptr.To(true))
7174

7275
testPodWithNodeName = func() *v1.Pod {
7376
pod := testPodWithResource.DeepCopy()
@@ -78,6 +81,7 @@ var (
7881
})
7982
return pod
8083
}()
84+
adminAccessFeatureOffError = "admin access is requested, but the feature is disabled"
8185
)
8286

8387
func TestSyncHandler(t *testing.T) {
@@ -93,7 +97,7 @@ func TestSyncHandler(t *testing.T) {
9397
templates []*resourceapi.ResourceClaimTemplate
9498
expectedClaims []resourceapi.ResourceClaim
9599
expectedStatuses map[string][]v1.PodResourceClaimStatus
96-
expectedError bool
100+
expectedError string
97101
expectedMetrics expectedMetrics
98102
}{
99103
{
@@ -109,6 +113,27 @@ func TestSyncHandler(t *testing.T) {
109113
},
110114
expectedMetrics: expectedMetrics{1, 0},
111115
},
116+
{
117+
name: "create with admin and feature gate off",
118+
pods: []*v1.Pod{testPodWithResource},
119+
templates: []*resourceapi.ResourceClaimTemplate{templateWithAdminAccess},
120+
key: podKey(testPodWithResource),
121+
expectedError: adminAccessFeatureOffError,
122+
},
123+
{
124+
name: "create with admin and feature gate on",
125+
pods: []*v1.Pod{testPodWithResource},
126+
templates: []*resourceapi.ResourceClaimTemplate{templateWithAdminAccess},
127+
key: podKey(testPodWithResource),
128+
expectedClaims: []resourceapi.ResourceClaim{*generatedTestClaimWithAdmin},
129+
expectedStatuses: map[string][]v1.PodResourceClaimStatus{
130+
testPodWithResource.Name: {
131+
{Name: testPodWithResource.Spec.ResourceClaims[0].Name, ResourceClaimName: &generatedTestClaimWithAdmin.Name},
132+
},
133+
},
134+
adminAccessEnabled: true,
135+
expectedMetrics: expectedMetrics{1, 0},
136+
},
112137
{
113138
name: "nop",
114139
pods: []*v1.Pod{func() *v1.Pod {
@@ -153,7 +178,7 @@ func TestSyncHandler(t *testing.T) {
153178
pods: []*v1.Pod{testPodWithResource},
154179
templates: nil,
155180
key: podKey(testPodWithResource),
156-
expectedError: true,
181+
expectedError: "resource claim template \"my-template\": resourceclaimtemplate.resource.k8s.io \"my-template\" not found",
157182
},
158183
{
159184
name: "find-existing-claim-by-label",
@@ -219,15 +244,15 @@ func TestSyncHandler(t *testing.T) {
219244
key: podKey(testPodWithResource),
220245
claims: []*resourceapi.ResourceClaim{conflictingClaim},
221246
expectedClaims: []resourceapi.ResourceClaim{*conflictingClaim},
222-
expectedError: true,
247+
expectedError: "resource claim template \"my-template\": resourceclaimtemplate.resource.k8s.io \"my-template\" not found",
223248
},
224249
{
225250
name: "create-conflict",
226251
pods: []*v1.Pod{testPodWithResource},
227252
templates: []*resourceapi.ResourceClaimTemplate{template},
228253
key: podKey(testPodWithResource),
229254
expectedMetrics: expectedMetrics{1, 1},
230-
expectedError: true,
255+
expectedError: "create ResourceClaim : Operation cannot be fulfilled on resourceclaims.resource.k8s.io \"fake name\": fake conflict",
231256
},
232257
{
233258
name: "stay-reserved-seen",
@@ -424,11 +449,12 @@ func TestSyncHandler(t *testing.T) {
424449
}
425450

426451
err = ec.syncHandler(tCtx, tc.key)
427-
if err != nil && !tc.expectedError {
428-
t.Fatalf("unexpected error while running handler: %v", err)
452+
if err != nil {
453+
assert.ErrorContains(t, err, tc.expectedError, "the error message should have contained the expected error message")
454+
return
429455
}
430-
if err == nil && tc.expectedError {
431-
t.Fatalf("unexpected success")
456+
if tc.expectedError != "" {
457+
t.Fatalf("expected error, got none")
432458
}
433459

434460
claims, err := fakeKubeClient.ResourceV1beta1().ResourceClaims("").List(tCtx, metav1.ListOptions{})
@@ -558,7 +584,7 @@ func makeClaim(name, namespace, classname string, owner *metav1.OwnerReference)
558584
return claim
559585
}
560586

561-
func makeGeneratedClaim(podClaimName, generateName, namespace, classname string, createCounter int, owner *metav1.OwnerReference) *resourceapi.ResourceClaim {
587+
func makeGeneratedClaim(podClaimName, generateName, namespace, classname string, createCounter int, owner *metav1.OwnerReference, adminAccess *bool) *resourceapi.ResourceClaim {
562588
claim := &resourceapi.ResourceClaim{
563589
ObjectMeta: metav1.ObjectMeta{
564590
Name: fmt.Sprintf("%s-%d", generateName, createCounter),
@@ -570,6 +596,19 @@ func makeGeneratedClaim(podClaimName, generateName, namespace, classname string,
570596
if owner != nil {
571597
claim.OwnerReferences = []metav1.OwnerReference{*owner}
572598
}
599+
if adminAccess != nil {
600+
claim.Spec = resourceapi.ResourceClaimSpec{
601+
Devices: resourceapi.DeviceClaim{
602+
Requests: []resourceapi.DeviceRequest{
603+
{
604+
Name: "req-0",
605+
DeviceClassName: "class",
606+
AdminAccess: adminAccess,
607+
},
608+
},
609+
},
610+
}
611+
}
573612

574613
return claim
575614
}
@@ -618,10 +657,25 @@ func makePod(name, namespace string, uid types.UID, podClaims ...v1.PodResourceC
618657
return pod
619658
}
620659

621-
func makeTemplate(name, namespace, classname string) *resourceapi.ResourceClaimTemplate {
660+
func makeTemplate(name, namespace, classname string, adminAccess *bool) *resourceapi.ResourceClaimTemplate {
622661
template := &resourceapi.ResourceClaimTemplate{
623662
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
624663
}
664+
if adminAccess != nil {
665+
template.Spec = resourceapi.ResourceClaimTemplateSpec{
666+
Spec: resourceapi.ResourceClaimSpec{
667+
Devices: resourceapi.DeviceClaim{
668+
Requests: []resourceapi.DeviceRequest{
669+
{
670+
Name: "req-0",
671+
DeviceClassName: "class",
672+
AdminAccess: adminAccess,
673+
},
674+
},
675+
},
676+
},
677+
}
678+
}
625679
return template
626680
}
627681

pkg/controlplane/instance.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ import (
6363
genericapiserver "k8s.io/apiserver/pkg/server"
6464
serverstorage "k8s.io/apiserver/pkg/server/storage"
6565
utilfeature "k8s.io/apiserver/pkg/util/feature"
66-
clientdiscovery "k8s.io/client-go/discovery"
6766
"k8s.io/client-go/kubernetes"
6867
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
6968
discoveryclient "k8s.io/client-go/kubernetes/typed/discovery/v1"
@@ -328,7 +327,7 @@ func (c CompletedConfig) New(delegationTarget genericapiserver.DelegationTarget)
328327
return nil, err
329328
}
330329

331-
restStorageProviders, err := c.StorageProviders(client.Discovery())
330+
restStorageProviders, err := c.StorageProviders(client)
332331
if err != nil {
333332
return nil, err
334333
}
@@ -379,7 +378,7 @@ func (c CompletedConfig) New(delegationTarget genericapiserver.DelegationTarget)
379378

380379
}
381380

382-
func (c CompletedConfig) StorageProviders(discovery clientdiscovery.DiscoveryInterface) ([]controlplaneapiserver.RESTStorageProvider, error) {
381+
func (c CompletedConfig) StorageProviders(client *kubernetes.Clientset) ([]controlplaneapiserver.RESTStorageProvider, error) {
383382
legacyRESTStorageProvider, err := corerest.New(corerest.Config{
384383
GenericConfig: *c.ControlPlane.NewCoreGenericConfig(),
385384
Proxy: corerest.ProxyConfig{
@@ -425,9 +424,9 @@ func (c CompletedConfig) StorageProviders(discovery clientdiscovery.DiscoveryInt
425424
// keep apps after extensions so legacy clients resolve the extensions versions of shared resource names.
426425
// See https://github.com/kubernetes/kubernetes/issues/42392
427426
appsrest.StorageProvider{},
428-
admissionregistrationrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer, DiscoveryClient: discovery},
427+
admissionregistrationrest.RESTStorageProvider{Authorizer: c.ControlPlane.Generic.Authorization.Authorizer, DiscoveryClient: client.Discovery()},
429428
eventsrest.RESTStorageProvider{TTL: c.ControlPlane.EventTTL},
430-
resourcerest.RESTStorageProvider{},
429+
resourcerest.RESTStorageProvider{NamespaceClient: client.CoreV1().Namespaces()},
431430
}, nil
432431
}
433432

pkg/controlplane/instance_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ func TestGenericStorageProviders(t *testing.T) {
490490
if err != nil {
491491
t.Fatal(err)
492492
}
493-
kube, err := completed.StorageProviders(client.Discovery())
493+
kube, err := completed.StorageProviders(client)
494494
if err != nil {
495495
t.Fatal(err)
496496
}

pkg/registry/resource/resourceclaim/storage/storage.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ package storage
1818

1919
import (
2020
"context"
21+
"fmt"
2122

2223
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2324
"k8s.io/apimachinery/pkg/runtime"
2425
"k8s.io/apiserver/pkg/registry/generic"
2526
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
2627
"k8s.io/apiserver/pkg/registry/rest"
28+
"k8s.io/client-go/kubernetes/typed/core/v1"
2729
"k8s.io/kubernetes/pkg/apis/resource"
2830
"k8s.io/kubernetes/pkg/printers"
2931
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
@@ -38,19 +40,23 @@ type REST struct {
3840
}
3941

4042
// NewREST returns a RESTStorage object that will work against ResourceClaims.
41-
func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
43+
func NewREST(optsGetter generic.RESTOptionsGetter, nsClient v1.NamespaceInterface) (*REST, *StatusREST, error) {
44+
if nsClient == nil {
45+
return nil, nil, fmt.Errorf("namespace client is required")
46+
}
47+
strategy := resourceclaim.NewStrategy(nsClient)
4248
store := &genericregistry.Store{
4349
NewFunc: func() runtime.Object { return &resource.ResourceClaim{} },
4450
NewListFunc: func() runtime.Object { return &resource.ResourceClaimList{} },
4551
PredicateFunc: resourceclaim.Match,
4652
DefaultQualifiedResource: resource.Resource("resourceclaims"),
4753
SingularQualifiedResource: resource.Resource("resourceclaim"),
4854

49-
CreateStrategy: resourceclaim.Strategy,
50-
UpdateStrategy: resourceclaim.Strategy,
51-
DeleteStrategy: resourceclaim.Strategy,
55+
CreateStrategy: strategy,
56+
UpdateStrategy: strategy,
57+
DeleteStrategy: strategy,
5258
ReturnDeletedObject: true,
53-
ResetFieldsStrategy: resourceclaim.Strategy,
59+
ResetFieldsStrategy: strategy,
5460

5561
TableConvertor: printerstorage.TableConvertor{TableGenerator: printers.NewTableGenerator().With(printersinternal.AddHandlers)},
5662
}
@@ -60,8 +66,9 @@ func NewREST(optsGetter generic.RESTOptionsGetter) (*REST, *StatusREST, error) {
6066
}
6167

6268
statusStore := *store
63-
statusStore.UpdateStrategy = resourceclaim.StatusStrategy
64-
statusStore.ResetFieldsStrategy = resourceclaim.StatusStrategy
69+
statusStrategy := resourceclaim.NewStatusStrategy(strategy)
70+
statusStore.UpdateStrategy = statusStrategy
71+
statusStore.ResetFieldsStrategy = statusStrategy
6572

6673
rest := &REST{store}
6774

pkg/registry/resource/resourceclaim/storage/storage_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
genericregistrytest "k8s.io/apiserver/pkg/registry/generic/testing"
3131
"k8s.io/apiserver/pkg/registry/rest"
3232
etcd3testing "k8s.io/apiserver/pkg/storage/etcd3/testing"
33+
"k8s.io/client-go/kubernetes/fake"
3334
"k8s.io/kubernetes/pkg/apis/resource"
3435
_ "k8s.io/kubernetes/pkg/apis/resource/install"
3536
"k8s.io/kubernetes/pkg/registry/registrytest"
@@ -43,7 +44,9 @@ func newStorage(t *testing.T) (*REST, *StatusREST, *etcd3testing.EtcdTestServer)
4344
DeleteCollectionWorkers: 1,
4445
ResourcePrefix: "resourceclaims",
4546
}
46-
resourceClaimStorage, statusStorage, err := NewREST(restOptions)
47+
fakeClient := fake.NewSimpleClientset()
48+
mockNSClient := fakeClient.CoreV1().Namespaces()
49+
resourceClaimStorage, statusStorage, err := NewREST(restOptions, mockNSClient)
4750
if err != nil {
4851
t.Fatalf("unexpected error from REST storage: %v", err)
4952
}

0 commit comments

Comments
 (0)