Skip to content

Commit 0999eb2

Browse files
authored
Merge pull request #920 from rojkov/patcher-mappings
fpga_webhook: never reject already mutated CRs
2 parents 42689ad + 2cb9142 commit 0999eb2

File tree

3 files changed

+197
-45
lines changed

3 files changed

+197
-45
lines changed

pkg/fpgacontroller/patcher/patcher.go

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ type Patcher struct {
8080
afMap map[string]*fpgav2.AcceleratorFunction
8181
resourceMap map[string]string
8282
resourceModeMap map[string]string
83+
84+
// This set is needed to maintain the webhook's idempotence: it must be possible to
85+
// resolve actual resources (AFs and regions) to themselves. For this we build
86+
// a set of identities which must be accepted by the webhook without any transformation.
87+
identitySet map[string]int
8388
}
8489

8590
func newPatcher(log logr.Logger) *Patcher {
@@ -88,6 +93,21 @@ func newPatcher(log logr.Logger) *Patcher {
8893
afMap: make(map[string]*fpgav2.AcceleratorFunction),
8994
resourceMap: make(map[string]string),
9095
resourceModeMap: make(map[string]string),
96+
identitySet: make(map[string]int),
97+
}
98+
}
99+
100+
func (p *Patcher) incIdentity(id string) {
101+
// Initialize to 1 or increment by 1.
102+
p.identitySet[id] = p.identitySet[id] + 1
103+
}
104+
105+
func (p *Patcher) decIdentity(id string) {
106+
counter := p.identitySet[id]
107+
if counter > 1 {
108+
p.identitySet[id] = counter - 1
109+
} else {
110+
delete(p.identitySet, id)
91111
}
92112
}
93113

@@ -103,9 +123,13 @@ func (p *Patcher) AddAf(accfunc *fpgav2.AcceleratorFunction) error {
103123
return err
104124
}
105125

106-
p.resourceMap[namespace+"/"+accfunc.Name] = rfc6901Escaper.Replace(namespace + "/" + devtype)
126+
mapping := rfc6901Escaper.Replace(namespace + "/" + devtype)
127+
p.resourceMap[namespace+"/"+accfunc.Name] = mapping
128+
p.incIdentity(mapping)
107129
} else {
108-
p.resourceMap[namespace+"/"+accfunc.Name] = rfc6901Escaper.Replace(namespace + "/region-" + accfunc.Spec.InterfaceID)
130+
mapping := rfc6901Escaper.Replace(namespace + "/region-" + accfunc.Spec.InterfaceID)
131+
p.resourceMap[namespace+"/"+accfunc.Name] = mapping
132+
p.incIdentity(mapping)
109133
}
110134

111135
p.resourceModeMap[namespace+"/"+accfunc.Name] = accfunc.Spec.Mode
@@ -118,24 +142,32 @@ func (p *Patcher) AddRegion(region *fpgav2.FpgaRegion) {
118142
p.Lock()
119143

120144
p.resourceModeMap[namespace+"/"+region.Name] = regiondevel
121-
p.resourceMap[namespace+"/"+region.Name] = rfc6901Escaper.Replace(namespace + "/region-" + region.Spec.InterfaceID)
145+
mapping := rfc6901Escaper.Replace(namespace + "/region-" + region.Spec.InterfaceID)
146+
p.resourceMap[namespace+"/"+region.Name] = mapping
147+
p.incIdentity(mapping)
122148
}
123149

124150
func (p *Patcher) RemoveAf(name string) {
125151
defer p.Unlock()
126152
p.Lock()
127153

128-
delete(p.afMap, namespace+"/"+name)
129-
delete(p.resourceMap, namespace+"/"+name)
130-
delete(p.resourceModeMap, namespace+"/"+name)
154+
nname := namespace + "/" + name
155+
156+
p.decIdentity(p.resourceMap[nname])
157+
delete(p.afMap, nname)
158+
delete(p.resourceMap, nname)
159+
delete(p.resourceModeMap, nname)
131160
}
132161

133162
func (p *Patcher) RemoveRegion(name string) {
134163
defer p.Unlock()
135164
p.Lock()
136165

137-
delete(p.resourceMap, namespace+"/"+name)
138-
delete(p.resourceModeMap, namespace+"/"+name)
166+
nname := namespace + "/" + name
167+
168+
p.decIdentity(p.resourceMap[nname])
169+
delete(p.resourceMap, nname)
170+
delete(p.resourceModeMap, nname)
139171
}
140172

141173
// sanitizeContainer filters out env variables reserved for CRI hook.
@@ -160,6 +192,16 @@ func sanitizeContainer(container corev1.Container) corev1.Container {
160192
return container
161193
}
162194

195+
func (p *Patcher) getNoopsOrError(name string) ([]string, error) {
196+
if _, isVirtual := p.identitySet[rfc6901Escaper.Replace(name)]; isVirtual {
197+
// `name` is not a real mapping, but a virtual one for an actual resource which
198+
// needs to be resolved to itself with no transformations.
199+
return []string{}, nil
200+
}
201+
202+
return nil, errors.Errorf("no such resource: %q", name)
203+
}
204+
163205
func (p *Patcher) getPatchOps(containerIdx int, container corev1.Container) ([]string, error) {
164206
container = sanitizeContainer(container)
165207

@@ -180,7 +222,7 @@ func (p *Patcher) getPatchOps(containerIdx int, container corev1.Container) ([]s
180222
for rname, quantity := range requestedResources {
181223
mode, found := p.resourceModeMap[rname]
182224
if !found {
183-
return nil, errors.Errorf("no such resource: %q", rname)
225+
return p.getNoopsOrError(rname)
184226
}
185227

186228
switch mode {

pkg/fpgacontroller/patcher/patcher_test.go

Lines changed: 144 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,27 @@ func init() {
3131
_ = flag.Set("v", "4")
3232
}
3333

34+
func checkExpectedError(t *testing.T, expectedErr bool, err error, testName string) {
35+
t.Helper()
36+
37+
if expectedErr && err == nil {
38+
t.Errorf("Test case '%s': no error returned", testName)
39+
}
40+
41+
if !expectedErr && err != nil {
42+
t.Errorf("Test case '%s': unexpected error: %+v", testName, err)
43+
}
44+
}
45+
3446
func TestPatcherStorageFunctions(t *testing.T) {
3547
goodAf := &fpgav2.AcceleratorFunction{
3648
ObjectMeta: metav1.ObjectMeta{
3749
Name: "arria10-nlb0",
3850
},
3951
Spec: fpgav2.AcceleratorFunctionSpec{
40-
AfuID: "d8424dc4a4a3c413f89e433683f9040b",
52+
AfuID: "d8424dc4a4a3c413f89e433683f9040b",
53+
InterfaceID: "ce48969398f05f33946d560708be108a",
54+
Mode: af,
4155
},
4256
}
4357
brokenAf := &fpgav2.AcceleratorFunction{
@@ -58,37 +72,103 @@ func TestPatcherStorageFunctions(t *testing.T) {
5872
InterfaceID: "ce48969398f05f33946d560708be108a",
5973
},
6074
}
61-
62-
p := newPatcher(ctrl.Log.WithName("test"))
63-
64-
if err := p.AddAf(goodAf); err != nil {
65-
t.Error("unexpected error")
66-
}
67-
68-
if len(p.resourceModeMap) != 1 || len(p.afMap) != 1 || len(p.resourceMap) != 1 {
69-
t.Error("Failed to add AF to patcher")
70-
}
71-
72-
if err := p.AddAf(brokenAf); err == nil {
73-
t.Error("AddAf() must fail")
74-
}
75-
76-
p.RemoveAf(goodAf.Name)
77-
78-
if len(p.resourceModeMap) != 0 || len(p.afMap) != 0 || len(p.resourceMap) != 0 {
79-
t.Error("Failed to remove AF from patcher")
75+
regionAlias := &fpgav2.FpgaRegion{
76+
ObjectMeta: metav1.ObjectMeta{
77+
Name: "arria10-alias",
78+
},
79+
Spec: fpgav2.FpgaRegionSpec{
80+
InterfaceID: "ce48969398f05f33946d560708be108a",
81+
},
8082
}
8183

82-
p.AddRegion(region)
83-
84-
if len(p.resourceModeMap) != 1 || len(p.resourceMap) != 1 {
85-
t.Error("Failed to add fpga region to patcher")
84+
tcases := []struct {
85+
name string
86+
afsToAdd []*fpgav2.AcceleratorFunction
87+
afsToRemove []*fpgav2.AcceleratorFunction
88+
regionsToAdd []*fpgav2.FpgaRegion
89+
regionsToRemove []*fpgav2.FpgaRegion
90+
expectedResourceModeMapSize int
91+
expectedResourceMapSize int
92+
expectedIdentitySetSize int
93+
expectedErr bool
94+
}{
95+
{
96+
name: "Add one good af",
97+
afsToAdd: []*fpgav2.AcceleratorFunction{goodAf},
98+
expectedResourceModeMapSize: 1,
99+
expectedResourceMapSize: 1,
100+
expectedIdentitySetSize: 1,
101+
},
102+
{
103+
name: "Add one broken af",
104+
afsToAdd: []*fpgav2.AcceleratorFunction{brokenAf},
105+
expectedErr: true,
106+
},
107+
{
108+
name: "Add one and remove one good af",
109+
afsToAdd: []*fpgav2.AcceleratorFunction{goodAf},
110+
afsToRemove: []*fpgav2.AcceleratorFunction{goodAf},
111+
},
112+
{
113+
name: "Add one good region",
114+
regionsToAdd: []*fpgav2.FpgaRegion{region},
115+
expectedResourceModeMapSize: 1,
116+
expectedResourceMapSize: 1,
117+
expectedIdentitySetSize: 1,
118+
},
119+
{
120+
name: "Add one and remove one good region",
121+
regionsToAdd: []*fpgav2.FpgaRegion{region},
122+
regionsToRemove: []*fpgav2.FpgaRegion{region},
123+
},
124+
{
125+
name: "Add one region and one alias then remove one alias",
126+
regionsToAdd: []*fpgav2.FpgaRegion{region, regionAlias},
127+
regionsToRemove: []*fpgav2.FpgaRegion{region},
128+
expectedResourceModeMapSize: 1,
129+
expectedResourceMapSize: 1,
130+
expectedIdentitySetSize: 1,
131+
},
132+
{
133+
name: "Add one region and one alias",
134+
regionsToAdd: []*fpgav2.FpgaRegion{region, regionAlias},
135+
expectedResourceModeMapSize: 2,
136+
expectedResourceMapSize: 2,
137+
expectedIdentitySetSize: 1,
138+
},
139+
{
140+
name: "Add one region and one alias then remove all",
141+
regionsToAdd: []*fpgav2.FpgaRegion{region, regionAlias},
142+
regionsToRemove: []*fpgav2.FpgaRegion{region, regionAlias},
143+
},
86144
}
87145

88-
p.RemoveRegion(region.Name)
89-
90-
if len(p.resourceModeMap) != 0 || len(p.resourceMap) != 0 {
91-
t.Error("Failed to remove fpga region from patcher")
146+
for _, tt := range tcases {
147+
t.Run(tt.name, func(t *testing.T) {
148+
p := newPatcher(ctrl.Log.WithName("test"))
149+
for _, af := range tt.afsToAdd {
150+
err := p.AddAf(af)
151+
checkExpectedError(t, tt.expectedErr, err, tt.name)
152+
}
153+
for _, reg := range tt.regionsToAdd {
154+
p.AddRegion(reg)
155+
}
156+
for _, af := range tt.afsToRemove {
157+
p.RemoveAf(af.Name)
158+
}
159+
for _, reg := range tt.regionsToRemove {
160+
p.RemoveRegion(reg.Name)
161+
}
162+
if tt.expectedResourceModeMapSize != len(p.resourceModeMap) {
163+
t.Errorf("wrong size of resourceModeMap. Expected %d, but got %d", tt.expectedResourceModeMapSize, len(p.resourceModeMap))
164+
}
165+
if tt.expectedResourceMapSize != len(p.resourceMap) {
166+
t.Errorf("wrong size of resourceMap. Expected %d, but got %d", tt.expectedResourceMapSize, len(p.resourceMap))
167+
}
168+
if tt.expectedIdentitySetSize != len(p.identitySet) {
169+
t.Errorf("wrong size of identitySet. Expected %d, but got %d", tt.expectedIdentitySetSize, len(p.identitySet))
170+
}
171+
})
92172
}
93173
}
94174

@@ -245,6 +325,41 @@ func TestGetPatchOps(t *testing.T) {
245325
},
246326
expectedOps: 4,
247327
},
328+
{
329+
name: "Skip handling for an identity mapping",
330+
container: corev1.Container{
331+
Resources: corev1.ResourceRequirements{
332+
Limits: corev1.ResourceList{
333+
"fpga.intel.com/af-ce4.d84.zkiWk5jwXzOUbVYHCL4QithCTcSko8QT-J5DNoP5BAs": resource.MustParse("1"),
334+
},
335+
Requests: corev1.ResourceList{
336+
"fpga.intel.com/af-ce4.d84.zkiWk5jwXzOUbVYHCL4QithCTcSko8QT-J5DNoP5BAs": resource.MustParse("1"),
337+
},
338+
},
339+
},
340+
afs: []*fpgav2.AcceleratorFunction{
341+
{
342+
ObjectMeta: metav1.ObjectMeta{
343+
Name: "arria10-nlb0",
344+
},
345+
Spec: fpgav2.AcceleratorFunctionSpec{
346+
AfuID: "d8424dc4a4a3c413f89e433683f9040b",
347+
InterfaceID: "ce48969398f05f33946d560708be108a",
348+
Mode: af,
349+
},
350+
},
351+
{
352+
ObjectMeta: metav1.ObjectMeta{
353+
Name: "arria10-nlb0-alias",
354+
},
355+
Spec: fpgav2.AcceleratorFunctionSpec{
356+
AfuID: "d8424dc4a4a3c413f89e433683f9040b",
357+
InterfaceID: "ce48969398f05f33946d560708be108a",
358+
Mode: af,
359+
},
360+
},
361+
},
362+
},
248363
{
249364
name: "Successful handling for regiondevel mode",
250365
container: corev1.Container{
@@ -399,12 +514,7 @@ func TestGetPatchOps(t *testing.T) {
399514
p.AddRegion(region)
400515
}
401516
ops, err := p.getPatchOps(0, tt.container)
402-
if tt.expectedErr && err == nil {
403-
t.Errorf("Test case '%s': no error returned", tt.name)
404-
}
405-
if !tt.expectedErr && err != nil {
406-
t.Errorf("Test case '%s': unexpected error: %+v", tt.name, err)
407-
}
517+
checkExpectedError(t, tt.expectedErr, err, tt.name)
408518
if len(ops) != tt.expectedOps {
409519
t.Errorf("test case '%s': expected %d ops, but got %d\n%v", tt.name, tt.expectedOps, len(ops), ops)
410520
}

pkg/internal/containers/containers.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ func GetRequestedResources(container corev1.Container, ns string) (map[string]in
2727
// Container may happen to have Requests, but not Limits. Check Requests first,
2828
// then in the next loop iterate over Limits.
2929
for resourceName, resourceQuantity := range container.Resources.Requests {
30-
rname := strings.ToLower(string(resourceName))
30+
rname := string(resourceName)
3131
if !strings.HasPrefix(rname, ns) {
3232
continue
3333
}
@@ -42,7 +42,7 @@ func GetRequestedResources(container corev1.Container, ns string) (map[string]in
4242
resources := make(map[string]int64)
4343

4444
for resourceName, resourceQuantity := range container.Resources.Limits {
45-
rname := strings.ToLower(string(resourceName))
45+
rname := string(resourceName)
4646
if !strings.HasPrefix(rname, ns) {
4747
continue
4848
}

0 commit comments

Comments
 (0)