Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: More optimal IterateHierarchyV2 and iterateChildrenV2 [#600] #601

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d777c9a
chore: More optimal IterateHierarchyV2 and iterateChildrenV2 [#600]
andrii-korotkov-verkada Jul 4, 2024
39bba43
improvements to graph building
crenshaw-dev Jul 16, 2024
120afb4
use old name
crenshaw-dev Jul 16, 2024
335ff88
chore: More optimal IterateHierarchyV2 and iterateChildrenV2 [#600]
andrii-korotkov-verkada Jul 4, 2024
1efd9be
Merge remote-tracking branch 'andrii/600-more-optimal-iterate-hierarc…
crenshaw-dev Jul 16, 2024
0fb5064
finish merge
crenshaw-dev Jul 16, 2024
af08910
chore: More optimal IterateHierarchyV2 and iterateChildrenV2 [#600]
andrii-korotkov-verkada Jul 4, 2024
1d56552
Merge remote-tracking branch 'andrii/600-more-optimal-iterate-hierarc…
crenshaw-dev Jul 16, 2024
837b536
Merge pull request #1 from crenshaw-dev/iterate-improvements
andrii-korotkov-verkada Jul 17, 2024
703a60d
discard unneeded copies of child resources as we go
crenshaw-dev Jul 17, 2024
19aa0bf
remove unnecessary comment
crenshaw-dev Jul 17, 2024
0f77c57
Merge pull request #2 from crenshaw-dev/iterate-improvements
andrii-korotkov-verkada Jul 17, 2024
38701d0
make childrenByUID sparse
crenshaw-dev Jul 17, 2024
8284fb0
eliminate duplicate map
crenshaw-dev Jul 17, 2024
5c23ab5
fix comment
crenshaw-dev Jul 17, 2024
8dbcf05
add useful comment back
crenshaw-dev Jul 17, 2024
3ef3651
use nsNodes instead of dupe map
crenshaw-dev Jul 17, 2024
9a98c83
Merge pull request #3 from crenshaw-dev/iterate-improvements
andrii-korotkov-verkada Jul 17, 2024
e2fb782
remove unused struct
crenshaw-dev Jul 18, 2024
0b6e366
skip invalid APIVersion
crenshaw-dev Jul 18, 2024
d162159
Merge pull request #4 from crenshaw-dev/reuse-nsnodes
andrii-korotkov-verkada Jul 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions pkg/cache/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ type ClusterCache interface {
// IterateHierarchy iterates resource tree starting from the specified top level resource and executes callback for each resource in the tree.
// The action callback returns true if iteration should continue and false otherwise.
IterateHierarchy(key kube.ResourceKey, action func(resource *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool)
// IterateHierarchyV2 iterates resource tree starting from the specified top level resources and executes callback for each resource in the tree.
// The action callback returns true if iteration should continue and false otherwise.
IterateHierarchyV2(keys []kube.ResourceKey, action func(resource *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool)
// IsNamespaced answers if specified group/kind is a namespaced resource API or not
IsNamespaced(gk schema.GroupKind) (bool, error)
// GetManagedLiveObjs helps finding matching live K8S resources for a given resources list.
Expand Down Expand Up @@ -1004,6 +1007,107 @@ func (c *clusterCache) IterateHierarchy(key kube.ResourceKey, action func(resour
}
}

// IterateHierarchy iterates resource tree starting from the specified top level resources and executes callback for each resource in the tree
func (c *clusterCache) IterateHierarchyV2(keys []kube.ResourceKey, action func(resource *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool) {
c.lock.RLock()
defer c.lock.RUnlock()
keysPerNamespace := make(map[string][]kube.ResourceKey)
for _, key := range keys {
_, ok := c.resources[key]
if !ok {
continue
}
keysPerNamespace[key.Namespace] = append(keysPerNamespace[key.Namespace], key)
crenshaw-dev marked this conversation as resolved.
Show resolved Hide resolved
}
for namespace, namespaceKeys := range keysPerNamespace {
nsNodes := c.nsIndex[namespace]
graph := buildGraph(nsNodes)
visited := make(map[kube.ResourceKey]int)
for _, key := range namespaceKeys {
visited[key] = 0
}
for _, key := range namespaceKeys {
// The check for existence of key is done above.
res := c.resources[key]
if visited[key] == 2 || !action(res, nsNodes) {
continue
}
visited[key] = 1
if _, ok := graph[key]; ok {
for _, child := range graph[key] {
if visited[child.ResourceKey()] == 0 && action(child, nsNodes) {
child.iterateChildrenV2(graph, nsNodes, visited, func(err error, child *Resource, namespaceResources map[kube.ResourceKey]*Resource) bool {
if err != nil {
c.log.V(2).Info(err.Error())
return false
}
return action(child, namespaceResources)
})
}
}
}
visited[key] = 2
}
}
}

func buildGraph(nsNodes map[kube.ResourceKey]*Resource) map[kube.ResourceKey]map[types.UID]*Resource {
// Prepare to construct a graph
nodesByUID := make(map[types.UID][]*Resource, len(nsNodes))
for _, node := range nsNodes {
nodesByUID[node.Ref.UID] = append(nodesByUID[node.Ref.UID], node)
}

// In graph, they key is the parent and the value is a list of children.
graph := make(map[kube.ResourceKey]map[types.UID]*Resource)

// Loop through all nodes, calling each one "childNode," because we're only bothering with it if it has a parent.
for _, childNode := range nsNodes {
for i, ownerRef := range childNode.OwnerRefs {
// First, backfill UID of inferred owner child references.
if ownerRef.UID == "" {
group, err := schema.ParseGroupVersion(ownerRef.APIVersion)
if err != nil {
// APIVersion is invalid, so we couldn't find the parent.
continue
}
graphKeyNode, ok := nsNodes[kube.ResourceKey{Group: group.Group, Kind: ownerRef.Kind, Namespace: childNode.Ref.Namespace, Name: ownerRef.Name}]
if ok {
ownerRef.UID = graphKeyNode.Ref.UID
childNode.OwnerRefs[i] = ownerRef
} else {
// No resource found with the given graph key, so move on.
continue
}
}

// Now that we have the UID of the parent, update the graph.
uidNodes, ok := nodesByUID[ownerRef.UID]
if ok {
for _, uidNode := range uidNodes {
// Update the graph for this owner to include the child.
if _, ok := graph[uidNode.ResourceKey()]; !ok {
graph[uidNode.ResourceKey()] = make(map[types.UID]*Resource)
}
r, ok := graph[uidNode.ResourceKey()][childNode.Ref.UID]
if !ok {
graph[uidNode.ResourceKey()][childNode.Ref.UID] = childNode
} else if r != nil {
// The object might have multiple children with the same UID (e.g. replicaset from apps and extensions group).
// It is ok to pick any object, but we need to make sure we pick the same child after every refresh.
key1 := r.ResourceKey()
key2 := childNode.ResourceKey()
if strings.Compare(key1.String(), key2.String()) > 0 {
graph[uidNode.ResourceKey()][childNode.Ref.UID] = childNode
}
}
}
}
}
}
return graph
}

// IsNamespaced answers if specified group/kind is a namespaced resource API or not
func (c *clusterCache) IsNamespaced(gk schema.GroupKind) (bool, error) {
if isNamespaced, ok := c.namespacedResources[gk]; ok {
Expand Down
Loading
Loading