@@ -80,6 +80,11 @@ type Graph struct {
80
80
graph * simple.DirectedAcyclicGraph
81
81
// vertices is a map of type -> namespace -> name -> vertex
82
82
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
83
88
}
84
89
85
90
// namespaceVertexMapping is a map of namespace -> name -> vertex
@@ -92,6 +97,11 @@ func NewGraph() *Graph {
92
97
return & Graph {
93
98
vertices : map [vertexType ]namespaceVertexMapping {},
94
99
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 ,
95
105
}
96
106
}
97
107
@@ -165,6 +175,7 @@ func (g *Graph) deleteVertex_locked(vertexType vertexType, namespace, name strin
165
175
166
176
// find existing neighbors with a single edge (meaning we are their only neighbor)
167
177
neighborsToRemove := []graph.Node {}
178
+ neighborsToRecompute := []graph.Node {}
168
179
g .graph .VisitFrom (vertex , func (neighbor graph.Node ) bool {
169
180
// this downstream neighbor has only one edge (which must be from us), so remove them as well
170
181
if g .graph .Degree (neighbor ) == 1 {
@@ -173,28 +184,27 @@ func (g *Graph) deleteVertex_locked(vertexType vertexType, namespace, name strin
173
184
return true
174
185
})
175
186
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
177
187
if g .graph .Degree (neighbor ) == 1 {
188
+ // this upstream neighbor has only one edge (which must be to us), so remove them as well
178
189
neighborsToRemove = append (neighborsToRemove , neighbor )
190
+ } else {
191
+ // recompute the destination edge index on this neighbor
192
+ neighborsToRecompute = append (neighborsToRemove , neighbor )
179
193
}
180
194
return true
181
195
})
182
196
183
197
// 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 )
189
199
190
200
// remove neighbors that are now edgeless
191
201
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 )
198
208
}
199
209
}
200
210
@@ -208,37 +218,81 @@ func (g *Graph) deleteEdges_locked(fromType, toType vertexType, toNamespace, toN
208
218
return
209
219
}
210
220
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 ())
214
270
return
215
271
}
216
272
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 ()
230
279
}
231
280
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 ())
241
285
}
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
242
296
}
243
297
}
244
298
@@ -265,22 +319,30 @@ func (g *Graph) AddPod(pod *api.Pod) {
265
319
//
266
320
// ref https://github.com/kubernetes/kubernetes/issues/58790
267
321
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 )
269
325
}
270
326
271
327
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 )
273
331
return true
274
332
})
275
333
276
334
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 )
278
338
return true
279
339
})
280
340
281
341
for _ , v := range pod .Spec .Volumes {
282
342
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 )
284
346
}
285
347
}
286
348
}
0 commit comments