Skip to content

Commit 4558e41

Browse files
author
Kubernetes Submit Queue
authoredMay 3, 2018
Merge pull request kubernetes#62892 from liggitt/node-authorizer-index
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. add index to node-authorizer for high cardinality vertices follow-up to kubernetes#62856 (comment) explores adding an index to high-cardinality vertices in the node authorizer to reduce CPU usage for high density namespaces * first commit is a refactor only - cc @mtaufen * second commit adds an optional per-vertex index we can maintain when there are sufficient outgoing edges. benchmark results: * shared_secret_via_pod cases are ~1000x faster * throughput on processing of graph modifications is 50% higher * there is more variance on graph modifications requiring index updates (though the 100 index-impacting graph modifications per second might be a higher-than-realistic write rate) data profile (5000 pods per namespace, assigned to 5000 nodes, shared service account and secret): ``` opts := sampleDataOpts{ // To simulate high replication in a small number of namespaces: nodes: 5000, namespaces: 10, podsPerNode: 10, ... ``` command: ``` $ go test ./plugin/pkg/auth/authorizer/node/ -bench Authorization -benchmem -v ``` before ``` BenchmarkAuthorization/allowed_node_configmap-8 557 ns/op 530 B/op 11 allocs/op 3000000 BenchmarkAuthorization/allowed_configmap-8 539 ns/op 530 B/op 11 allocs/op 3000000 BenchmarkAuthorization/allowed_secret_via_pod-8 605 ns/op 529 B/op 11 allocs/op 3000000 BenchmarkAuthorization/allowed_shared_secret_via_pod-8 215974 ns/op 792 B/op 19 allocs/op 5000 BenchmarkAuthorization/disallowed_node_configmap-8 823 ns/op 694 B/op 17 allocs/op 2000000 BenchmarkAuthorization/disallowed_configmap-8 888 ns/op 691 B/op 17 allocs/op 2000000 BenchmarkAuthorization/disallowed_secret_via_pod-8 868 ns/op 694 B/op 17 allocs/op 2000000 BenchmarkAuthorization/disallowed_shared_secret_via_pvc-8 1216 ns/op 948 B/op 22 allocs/op 1000000 BenchmarkAuthorization/disallowed_pvc-8 918 ns/op 691 B/op 17 allocs/op 2000000 BenchmarkAuthorization/disallowed_pv-8 1095 ns/op 839 B/op 19 allocs/op 2000000 BenchmarkAuthorization/disallowed_attachment_-_no_relationship-8 867 ns/op 677 B/op 16 allocs/op 2000000 BenchmarkAuthorization/disallowed_attachment_-_feature_disabled-8 220 ns/op 208 B/op 2 allocs/op 10000000 BenchmarkAuthorization/allowed_attachment_-_feature_enabled-8 687 ns/op 594 B/op 12 allocs/op 2000000 BenchmarkAuthorization/contentious_allowed_node_configmap-8 656 ns/op 530 B/op 11 allocs/op 3000000 BenchmarkAuthorization/contentious_allowed_configmap-8 659 ns/op 529 B/op 11 allocs/op 2000000 BenchmarkAuthorization/contentious_allowed_secret_via_pod-8 654 ns/op 529 B/op 11 allocs/op 2000000 BenchmarkAuthorization/contentious_allowed_shared_secret_via_pod-8 234308 ns/op 1022 B/op 22 allocs/op 5000 BenchmarkAuthorization/contentious_disallowed_node_configmap-8 1118 ns/op 692 B/op 17 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_configmap-8 1054 ns/op 692 B/op 17 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_secret_via_pod-8 1059 ns/op 691 B/op 17 allocs/op 2000000 BenchmarkAuthorization/contentious_disallowed_shared_secret_via_pvc-8 1403 ns/op 949 B/op 22 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_pvc-8 1058 ns/op 692 B/op 17 allocs/op 2000000 BenchmarkAuthorization/contentious_disallowed_pv-8 1237 ns/op 838 B/op 19 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_attachment_-_no_relationship-8 1022 ns/op 676 B/op 16 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_attachment_-_feature_disabled-8 260 ns/op 209 B/op 2 allocs/op 5000000 BenchmarkAuthorization/contentious_allowed_attachment_-_feature_enabled-8 793 ns/op 594 B/op 12 allocs/op 2000000 --- BENCH: BenchmarkAuthorization node_authorizer_test.go:596: graph modifications during non-contention test: 0 node_authorizer_test.go:593: graph modifications during contention test: 961 node_authorizer_test.go:594: <1ms=774, <10ms=32, <25ms=14, <50ms=29, <100ms=62, <250ms=46, <500ms=2, <1000ms=1, >1000ms=1 ``` after ``` BenchmarkAuthorization/allowed_node_configmap-8 629 ns/op 530 B/op 11 allocs/op 3000000 BenchmarkAuthorization/allowed_configmap-8 641 ns/op 530 B/op 11 allocs/op 3000000 BenchmarkAuthorization/allowed_secret_via_pod-8 591 ns/op 530 B/op 11 allocs/op 3000000 BenchmarkAuthorization/allowed_shared_secret_via_pod-8 217 ns/op 160 B/op 1 allocs/op 10000000 BenchmarkAuthorization/disallowed_node_configmap-8 912 ns/op 693 B/op 17 allocs/op 2000000 BenchmarkAuthorization/disallowed_configmap-8 913 ns/op 694 B/op 17 allocs/op 2000000 BenchmarkAuthorization/disallowed_secret_via_pod-8 881 ns/op 691 B/op 17 allocs/op 2000000 BenchmarkAuthorization/disallowed_shared_secret_via_pvc-8 1271 ns/op 952 B/op 22 allocs/op 1000000 BenchmarkAuthorization/disallowed_pvc-8 903 ns/op 694 B/op 17 allocs/op 2000000 BenchmarkAuthorization/disallowed_pv-8 1024 ns/op 836 B/op 19 allocs/op 1000000 BenchmarkAuthorization/disallowed_attachment_-_no_relationship-8 1187 ns/op 678 B/op 16 allocs/op 2000000 BenchmarkAuthorization/disallowed_attachment_-_feature_disabled-8 250 ns/op 209 B/op 2 allocs/op 10000000 BenchmarkAuthorization/allowed_attachment_-_feature_enabled-8 694 ns/op 594 B/op 12 allocs/op 2000000 BenchmarkAuthorization/contentious_allowed_node_configmap-8 732 ns/op 530 B/op 11 allocs/op 2000000 BenchmarkAuthorization/contentious_allowed_configmap-8 820 ns/op 530 B/op 11 allocs/op 2000000 BenchmarkAuthorization/contentious_allowed_secret_via_pod-8 1082 ns/op 531 B/op 11 allocs/op 1000000 BenchmarkAuthorization/contentious_allowed_shared_secret_via_pod-8 274 ns/op 160 B/op 1 allocs/op 5000000 BenchmarkAuthorization/contentious_disallowed_node_configmap-8 1332 ns/op 693 B/op 17 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_configmap-8 1534 ns/op 693 B/op 17 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_secret_via_pod-8 1077 ns/op 692 B/op 17 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_shared_secret_via_pvc-8 1976 ns/op 949 B/op 22 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_pvc-8 1297 ns/op 694 B/op 17 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_pv-8 1632 ns/op 837 B/op 19 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_attachment_-_no_relationship-8 1394 ns/op 677 B/op 16 allocs/op 1000000 BenchmarkAuthorization/contentious_disallowed_attachment_-_feature_disabled-8 320 ns/op 209 B/op 2 allocs/op 5000000 BenchmarkAuthorization/contentious_allowed_attachment_-_feature_enabled-8 1055 ns/op 595 B/op 12 allocs/op 2000000 --- BENCH: BenchmarkAuthorization node_authorizer_test.go:629: graph modifications during non-contention test: 0 node_authorizer_test.go:626: graph modifications during contention test: 1424 node_authorizer_test.go:627: <1ms=0, <10ms=569, <25ms=340, <50ms=145, <100ms=101, <250ms=160, <500ms=61, <1000ms=42, >1000ms=6 ``` ```release-note NONE ```
2 parents c968d99 + ff8cdab commit 4558e41

File tree

7 files changed

+298
-43
lines changed

7 files changed

+298
-43
lines changed
 

‎plugin/pkg/auth/authorizer/node/BUILD

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ go_test(
1010
name = "go_default_test",
1111
srcs = [
1212
"graph_test.go",
13+
"intset_test.go",
1314
"node_authorizer_test.go",
1415
],
1516
embed = [":go_default_library"],
@@ -33,6 +34,7 @@ go_library(
3334
srcs = [
3435
"graph.go",
3536
"graph_populator.go",
37+
"intset.go",
3638
"node_authorizer.go",
3739
],
3840
importpath = "k8s.io/kubernetes/plugin/pkg/auth/authorizer/node",

‎plugin/pkg/auth/authorizer/node/graph.go

+103-41
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,11 @@ type Graph struct {
8080
graph *simple.DirectedAcyclicGraph
8181
// vertices is a map of type -> namespace -> name -> vertex
8282
vertices map[vertexType]namespaceVertexMapping
83+
84+
// destinationEdgeIndex is a map of vertex -> set of destination IDs
85+
destinationEdgeIndex map[int]*intSet
86+
// destinationEdgeThreshold is the minimum number of distinct destination IDs at which to maintain an index
87+
destinationEdgeThreshold int
8388
}
8489

8590
// namespaceVertexMapping is a map of namespace -> name -> vertex
@@ -92,6 +97,11 @@ func NewGraph() *Graph {
9297
return &Graph{
9398
vertices: map[vertexType]namespaceVertexMapping{},
9499
graph: simple.NewDirectedAcyclicGraph(0, 0),
100+
101+
destinationEdgeIndex: map[int]*intSet{},
102+
// experimentally determined to be the point at which iteration adds an order of magnitude to the authz check.
103+
// since maintaining indexes costs time/memory while processing graph changes, we don't want to make this too low.
104+
destinationEdgeThreshold: 200,
95105
}
96106
}
97107

@@ -165,6 +175,7 @@ func (g *Graph) deleteVertex_locked(vertexType vertexType, namespace, name strin
165175

166176
// find existing neighbors with a single edge (meaning we are their only neighbor)
167177
neighborsToRemove := []graph.Node{}
178+
neighborsToRecompute := []graph.Node{}
168179
g.graph.VisitFrom(vertex, func(neighbor graph.Node) bool {
169180
// this downstream neighbor has only one edge (which must be from us), so remove them as well
170181
if g.graph.Degree(neighbor) == 1 {
@@ -173,28 +184,27 @@ func (g *Graph) deleteVertex_locked(vertexType vertexType, namespace, name strin
173184
return true
174185
})
175186
g.graph.VisitTo(vertex, func(neighbor graph.Node) bool {
176-
// this upstream neighbor has only one edge (which must be to us), so remove them as well
177187
if g.graph.Degree(neighbor) == 1 {
188+
// this upstream neighbor has only one edge (which must be to us), so remove them as well
178189
neighborsToRemove = append(neighborsToRemove, neighbor)
190+
} else {
191+
// recompute the destination edge index on this neighbor
192+
neighborsToRecompute = append(neighborsToRemove, neighbor)
179193
}
180194
return true
181195
})
182196

183197
// remove the vertex
184-
g.graph.RemoveNode(vertex)
185-
delete(g.vertices[vertexType][namespace], name)
186-
if len(g.vertices[vertexType][namespace]) == 0 {
187-
delete(g.vertices[vertexType], namespace)
188-
}
198+
g.removeVertex_locked(vertex)
189199

190200
// remove neighbors that are now edgeless
191201
for _, neighbor := range neighborsToRemove {
192-
g.graph.RemoveNode(neighbor)
193-
n := neighbor.(*namedVertex)
194-
delete(g.vertices[n.vertexType][n.namespace], n.name)
195-
if len(g.vertices[n.vertexType][n.namespace]) == 0 {
196-
delete(g.vertices[n.vertexType], n.namespace)
197-
}
202+
g.removeVertex_locked(neighbor.(*namedVertex))
203+
}
204+
205+
// recompute destination indexes for neighbors that dropped outbound edges
206+
for _, neighbor := range neighborsToRecompute {
207+
g.recomputeDestinationIndex_locked(neighbor)
198208
}
199209
}
200210

@@ -208,37 +218,81 @@ func (g *Graph) deleteEdges_locked(fromType, toType vertexType, toNamespace, toN
208218
return
209219
}
210220

211-
// get potential "from" verts that match fromType
212-
namespaces, exists := g.vertices[fromType]
213-
if !exists {
221+
// delete all edges between vertices of fromType and toVert
222+
neighborsToRemove := []*namedVertex{}
223+
neighborsToRecompute := []*namedVertex{}
224+
g.graph.VisitTo(toVert, func(from graph.Node) bool {
225+
fromVert := from.(*namedVertex)
226+
if fromVert.vertexType != fromType {
227+
return true
228+
}
229+
// remove the edge
230+
g.graph.RemoveEdge(simple.Edge{F: fromVert, T: toVert})
231+
// track vertexes that changed edges
232+
if g.graph.Degree(fromVert) == 0 {
233+
neighborsToRemove = append(neighborsToRemove, fromVert)
234+
} else {
235+
neighborsToRecompute = append(neighborsToRecompute, fromVert)
236+
}
237+
return true
238+
})
239+
240+
// clean up orphaned verts
241+
for _, v := range neighborsToRemove {
242+
g.removeVertex_locked(v)
243+
}
244+
245+
// recompute destination indexes for neighbors that dropped outbound edges
246+
for _, v := range neighborsToRecompute {
247+
g.recomputeDestinationIndex_locked(v)
248+
}
249+
}
250+
251+
// must be called under write lock
252+
// removeVertex_locked removes the specified vertex from the graph and from the maintained indices.
253+
// It does nothing to indexes of neighbor vertices.
254+
func (g *Graph) removeVertex_locked(v *namedVertex) {
255+
g.graph.RemoveNode(v)
256+
delete(g.destinationEdgeIndex, v.ID())
257+
delete(g.vertices[v.vertexType][v.namespace], v.name)
258+
if len(g.vertices[v.vertexType][v.namespace]) == 0 {
259+
delete(g.vertices[v.vertexType], v.namespace)
260+
}
261+
}
262+
263+
// must be called under write lock
264+
// recomputeDestinationIndex_locked recomputes the index of destination ids for the specified vertex
265+
func (g *Graph) recomputeDestinationIndex_locked(n graph.Node) {
266+
// don't maintain indices for nodes with few edges
267+
edgeCount := g.graph.Degree(n)
268+
if edgeCount < g.destinationEdgeThreshold {
269+
delete(g.destinationEdgeIndex, n.ID())
214270
return
215271
}
216272

217-
// delete all edges between vertices of fromType and toVert
218-
removeVerts := []*namedVertex{}
219-
for _, vertexMapping := range namespaces {
220-
for _, fromVert := range vertexMapping {
221-
if g.graph.HasEdgeBetween(fromVert, toVert) {
222-
// remove the edge (no-op if edge doesn't exist)
223-
g.graph.RemoveEdge(newDestinationEdge(fromVert, toVert, nil))
224-
// remember to clean up the fromVert if we orphaned it
225-
if g.graph.Degree(fromVert) == 0 {
226-
removeVerts = append(removeVerts, fromVert)
227-
}
228-
}
229-
}
273+
// get or create the index
274+
index := g.destinationEdgeIndex[n.ID()]
275+
if index == nil {
276+
index = newIntSet()
277+
} else {
278+
index.startNewGeneration()
230279
}
231280

232-
// clean up orphaned verts
233-
for _, v := range removeVerts {
234-
g.graph.RemoveNode(v)
235-
delete(g.vertices[v.vertexType][v.namespace], v.name)
236-
if len(g.vertices[v.vertexType][v.namespace]) == 0 {
237-
delete(g.vertices[v.vertexType], v.namespace)
238-
}
239-
if len(g.vertices[v.vertexType]) == 0 {
240-
delete(g.vertices, v.vertexType)
281+
// populate the index
282+
g.graph.VisitFrom(n, func(dest graph.Node) bool {
283+
if destinationEdge, ok := g.graph.EdgeBetween(n, dest).(*destinationEdge); ok {
284+
index.mark(destinationEdge.DestinationID())
241285
}
286+
return true
287+
})
288+
289+
// remove existing items no longer in the list
290+
index.sweep()
291+
292+
if len(index.members) < g.destinationEdgeThreshold {
293+
delete(g.destinationEdgeIndex, n.ID())
294+
} else {
295+
g.destinationEdgeIndex[n.ID()] = index
242296
}
243297
}
244298

@@ -265,22 +319,30 @@ func (g *Graph) AddPod(pod *api.Pod) {
265319
//
266320
// ref https://github.com/kubernetes/kubernetes/issues/58790
267321
if len(pod.Spec.ServiceAccountName) > 0 {
268-
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(serviceAccountVertexType, pod.Namespace, pod.Spec.ServiceAccountName), podVertex, nodeVertex))
322+
serviceAccountVertex := g.getOrCreateVertex_locked(serviceAccountVertexType, pod.Namespace, pod.Spec.ServiceAccountName)
323+
g.graph.SetEdge(newDestinationEdge(serviceAccountVertex, podVertex, nodeVertex))
324+
g.recomputeDestinationIndex_locked(serviceAccountVertex)
269325
}
270326

271327
podutil.VisitPodSecretNames(pod, func(secret string) bool {
272-
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(secretVertexType, pod.Namespace, secret), podVertex, nodeVertex))
328+
secretVertex := g.getOrCreateVertex_locked(secretVertexType, pod.Namespace, secret)
329+
g.graph.SetEdge(newDestinationEdge(secretVertex, podVertex, nodeVertex))
330+
g.recomputeDestinationIndex_locked(secretVertex)
273331
return true
274332
})
275333

276334
podutil.VisitPodConfigmapNames(pod, func(configmap string) bool {
277-
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(configMapVertexType, pod.Namespace, configmap), podVertex, nodeVertex))
335+
configmapVertex := g.getOrCreateVertex_locked(configMapVertexType, pod.Namespace, configmap)
336+
g.graph.SetEdge(newDestinationEdge(configmapVertex, podVertex, nodeVertex))
337+
g.recomputeDestinationIndex_locked(configmapVertex)
278338
return true
279339
})
280340

281341
for _, v := range pod.Spec.Volumes {
282342
if v.PersistentVolumeClaim != nil {
283-
g.graph.SetEdge(newDestinationEdge(g.getOrCreateVertex_locked(pvcVertexType, pod.Namespace, v.PersistentVolumeClaim.ClaimName), podVertex, nodeVertex))
343+
pvcVertex := g.getOrCreateVertex_locked(pvcVertexType, pod.Namespace, v.PersistentVolumeClaim.ClaimName)
344+
g.graph.SetEdge(newDestinationEdge(pvcVertex, podVertex, nodeVertex))
345+
g.recomputeDestinationIndex_locked(pvcVertex)
284346
}
285347
}
286348
}

‎plugin/pkg/auth/authorizer/node/graph_test.go

+2
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,15 @@ func TestDeleteEdges_locked(t *testing.T) {
4242
toName: "node1",
4343
start: func() *Graph {
4444
g := NewGraph()
45+
g.getOrCreateVertex_locked(configMapVertexType, "namespace1", "configmap2")
4546
nodeVertex := g.getOrCreateVertex_locked(nodeVertexType, "", "node1")
4647
configmapVertex := g.getOrCreateVertex_locked(configMapVertexType, "namespace1", "configmap1")
4748
g.graph.SetEdge(newDestinationEdge(configmapVertex, nodeVertex, nodeVertex))
4849
return g
4950
}(),
5051
expect: func() *Graph {
5152
g := NewGraph()
53+
g.getOrCreateVertex_locked(configMapVertexType, "namespace1", "configmap2")
5254
g.getOrCreateVertex_locked(nodeVertexType, "", "node1")
5355
return g
5456
}(),
+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package node
18+
19+
// intSet maintains a set of ints, and supports promoting and culling the previous generation.
20+
// this allows tracking a large, mostly-stable set without constantly reallocating the entire set.
21+
type intSet struct {
22+
currentGeneration byte
23+
members map[int]byte
24+
}
25+
26+
func newIntSet() *intSet {
27+
return &intSet{members: map[int]byte{}}
28+
}
29+
30+
// has returns true if the specified int is in the set.
31+
// it is safe to call concurrently, but must not be called concurrently with any of the other methods.
32+
func (s *intSet) has(i int) bool {
33+
if s == nil {
34+
return false
35+
}
36+
_, present := s.members[i]
37+
return present
38+
}
39+
40+
// startNewGeneration begins a new generation.
41+
// it must be followed by a call to mark() for every member of the generation,
42+
// then a call to sweep() to remove members not present in the generation.
43+
// it is not thread-safe.
44+
func (s *intSet) startNewGeneration() {
45+
s.currentGeneration++
46+
}
47+
48+
// mark indicates the specified int belongs to the current generation.
49+
// it is not thread-safe.
50+
func (s *intSet) mark(i int) {
51+
s.members[i] = s.currentGeneration
52+
}
53+
54+
// sweep removes items not in the current generation.
55+
// it is not thread-safe.
56+
func (s *intSet) sweep() {
57+
for k, v := range s.members {
58+
if v != s.currentGeneration {
59+
delete(s.members, k)
60+
}
61+
}
62+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
Copyright 2018 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package node
18+
19+
import (
20+
"testing"
21+
22+
"github.com/stretchr/testify/assert"
23+
)
24+
25+
func TestIntSet(t *testing.T) {
26+
i := newIntSet()
27+
28+
assert.False(t, i.has(1))
29+
assert.False(t, i.has(2))
30+
assert.False(t, i.has(3))
31+
assert.False(t, i.has(4))
32+
33+
i.startNewGeneration()
34+
i.mark(1)
35+
i.mark(2)
36+
i.sweep()
37+
38+
assert.True(t, i.has(1))
39+
assert.True(t, i.has(2))
40+
assert.False(t, i.has(3))
41+
assert.False(t, i.has(4))
42+
43+
i.startNewGeneration()
44+
i.mark(2)
45+
i.mark(3)
46+
i.sweep()
47+
48+
assert.False(t, i.has(1))
49+
assert.True(t, i.has(2))
50+
assert.True(t, i.has(3))
51+
assert.False(t, i.has(4))
52+
53+
i.startNewGeneration()
54+
i.mark(3)
55+
i.mark(4)
56+
i.sweep()
57+
58+
assert.False(t, i.has(1))
59+
assert.False(t, i.has(2))
60+
assert.True(t, i.has(3))
61+
assert.True(t, i.has(4))
62+
}

‎plugin/pkg/auth/authorizer/node/node_authorizer.go

+5
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ func (r *NodeAuthorizer) hasPathFrom(nodeName string, startingType vertexType, s
212212
return false, fmt.Errorf("node %q cannot get unknown %s %s/%s", nodeName, vertexTypes[startingType], startingNamespace, startingName)
213213
}
214214

215+
// Fast check to see if we know of a destination edge
216+
if r.graph.destinationEdgeIndex[startingVertex.ID()].has(nodeVertex.ID()) {
217+
return true, nil
218+
}
219+
215220
found := false
216221
traversal := &traverse.VisitingDepthFirst{
217222
EdgeFilter: func(edge graph.Edge) bool {

‎plugin/pkg/auth/authorizer/node/node_authorizer_test.go

+62-2
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ func TestAuthorizer(t *testing.T) {
222222

223223
func TestAuthorizerSharedResources(t *testing.T) {
224224
g := NewGraph()
225+
g.destinationEdgeThreshold = 1
225226
identifier := nodeidentifier.NewDefaultNodeIdentifier()
226227
authz := NewAuthorizer(g, identifier, bootstrappolicy.NodeRules())
227228

@@ -250,15 +251,17 @@ func TestAuthorizerSharedResources(t *testing.T) {
250251
},
251252
},
252253
})
253-
g.AddPod(&api.Pod{
254+
255+
pod3 := &api.Pod{
254256
ObjectMeta: metav1.ObjectMeta{Name: "pod3-node3", Namespace: "ns1"},
255257
Spec: api.PodSpec{
256258
NodeName: "node3",
257259
Volumes: []api.Volume{
258260
{VolumeSource: api.VolumeSource{Secret: &api.SecretVolumeSource{SecretName: "shared-all"}}},
259261
},
260262
},
261-
})
263+
}
264+
g.AddPod(pod3)
262265

263266
g.SetNodeConfigMap("node1", "shared-configmap", "ns1")
264267
g.SetNodeConfigMap("node2", "shared-configmap", "ns1")
@@ -318,6 +321,30 @@ func TestAuthorizerSharedResources(t *testing.T) {
318321
t.Errorf("%d: expected %v, got %v", i, tc.ExpectAllowed, decision)
319322
}
320323
}
324+
325+
{
326+
node3SharedSecretGet := authorizer.AttributesRecord{User: node3, ResourceRequest: true, Verb: "get", Resource: "secrets", Namespace: "ns1", Name: "shared-all"}
327+
328+
decision, _, err := authz.Authorize(node3SharedSecretGet)
329+
if err != nil {
330+
t.Errorf("unexpected error: %v", err)
331+
}
332+
if decision != authorizer.DecisionAllow {
333+
t.Error("expected allowed")
334+
}
335+
336+
// should trigger recalculation of the shared secret index
337+
pod3.Spec.Volumes = nil
338+
g.AddPod(pod3)
339+
340+
decision, _, err = authz.Authorize(node3SharedSecretGet)
341+
if err != nil {
342+
t.Errorf("unexpected error: %v", err)
343+
}
344+
if decision == authorizer.DecisionAllow {
345+
t.Errorf("unexpectedly allowed")
346+
}
347+
}
321348
}
322349

323350
type sampleDataOpts struct {
@@ -403,6 +430,39 @@ func BenchmarkPopulationRetention(b *testing.B) {
403430
}
404431
}
405432

433+
func BenchmarkWriteIndexMaintenance(b *testing.B) {
434+
435+
// Run with:
436+
// go test ./plugin/pkg/auth/authorizer/node -benchmem -bench BenchmarkWriteIndexMaintenance -run None
437+
438+
opts := sampleDataOpts{
439+
// simulate high replication in a small number of namespaces:
440+
nodes: 5000,
441+
namespaces: 1,
442+
podsPerNode: 1,
443+
attachmentsPerNode: 20,
444+
sharedConfigMapsPerPod: 0,
445+
uniqueConfigMapsPerPod: 1,
446+
sharedSecretsPerPod: 1,
447+
uniqueSecretsPerPod: 1,
448+
sharedPVCsPerPod: 0,
449+
uniquePVCsPerPod: 1,
450+
}
451+
nodes, pods, pvs, attachments := generate(opts)
452+
g := NewGraph()
453+
populate(g, nodes, pods, pvs, attachments)
454+
// Garbage collect before the first iteration
455+
runtime.GC()
456+
b.ResetTimer()
457+
458+
b.SetParallelism(100)
459+
b.RunParallel(func(pb *testing.PB) {
460+
for pb.Next() {
461+
g.AddPod(pods[0])
462+
}
463+
})
464+
}
465+
406466
func BenchmarkAuthorization(b *testing.B) {
407467
g := NewGraph()
408468

0 commit comments

Comments
 (0)
Please sign in to comment.