Skip to content

feat(topology): add resource topology visualization#494

Open
SunsetB612 wants to merge 1 commit intokarmada-io:mainfrom
SunsetB612:feat/resource-topology-ui
Open

feat(topology): add resource topology visualization#494
SunsetB612 wants to merge 1 commit intokarmada-io:mainfrom
SunsetB612:feat/resource-topology-ui

Conversation

@SunsetB612
Copy link
Copy Markdown
Contributor

No description provided.

@karmada-bot karmada-bot requested review from devadapter and jhnine April 8, 2026 00:25
@karmada-bot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign kevin-wangzefeng for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@karmada-bot karmada-bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Apr 8, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a resource topology feature in the Karmada dashboard, providing a visual representation of the propagation chain from control-plane workloads to member clusters. Key additions include a backend API that utilizes custom informer indexers for resource tracing and a frontend graph component built with React Flow. Feedback identifies several areas for refinement, such as adopting a dynamic client to support CRDs, ensuring proper context propagation, and improving the robustness of cluster name extraction. Additionally, the tracing logic needs to prevent duplicate node IDs, and the UI's resource search should be dynamically populated to improve the user experience.

Comment on lines +37 to +72
func getWorkload(ctx context.Context, k8sClient kubeclient.Interface, namespace, name, kind string) (types.UID, map[string]string, error) {
switch kind {
case "Deployment":
obj, err := k8sClient.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", nil, err
}
return obj.UID, obj.Annotations, nil
case "StatefulSet":
obj, err := k8sClient.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", nil, err
}
return obj.UID, obj.Annotations, nil
case "DaemonSet":
obj, err := k8sClient.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", nil, err
}
return obj.UID, obj.Annotations, nil
case "Job":
obj, err := k8sClient.BatchV1().Jobs(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", nil, err
}
return obj.UID, obj.Annotations, nil
case "CronJob":
obj, err := k8sClient.BatchV1().CronJobs(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return "", nil, err
}
return obj.UID, obj.Annotations, nil
default:
return "", nil, fmt.Errorf("unsupported workload kind: %s", kind)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The getWorkload function is currently limited to a hardcoded set of built-in Kubernetes resources (Deployment, StatefulSet, etc.) because it uses the typed kubernetes.Interface. Karmada is designed to propagate any resource type, including Custom Resource Definitions (CRDs).

Consider using a dynamic.Interface to fetch the workload. This would make the topology feature generic and compatible with all resources managed by Karmada.

return
}

result, err := topology.GetResourceTopology(k8sClient, namespace, name, kind)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The request context should be passed to GetResourceTopology to ensure that any cancellation or timeout of the HTTP request is correctly propagated to the underlying resource fetching logic.

Suggested change
result, err := topology.GetResourceTopology(k8sClient, namespace, name, kind)
result, err := topology.GetResourceTopology(c.Request.Context(), k8sClient, namespace, name, kind)

Comment on lines +26 to +30
func GetResourceTopology(
k8sClient kubeclient.Interface,
namespace, name, kind string) (*TopologyResponse, error) {
return traceChain(context.TODO(), k8sClient, namespace, name, kind)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The GetResourceTopology function should accept a context.Context parameter instead of using context.TODO(). This allows for proper request lifecycle management and cancellation propagation.

Suggested change
func GetResourceTopology(
k8sClient kubeclient.Interface,
namespace, name, kind string) (*TopologyResponse, error) {
return traceChain(context.TODO(), k8sClient, namespace, name, kind)
}
func GetResourceTopology(
ctx context.Context,
k8sClient kubeclient.Interface,
namespace, name, kind string) (*TopologyResponse, error) {
return traceChain(ctx, k8sClient, namespace, name, kind)
}

Comment on lines +141 to +148
// clusterNameFromWorkNamespace extracts cluster name from "karmada-es-{cluster}" namespace.
func clusterNameFromWorkNamespace(ns string) string {
const prefix = "karmada-es-"
if len(ns) > len(prefix) {
return ns[len(prefix):]
}
return ""
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic for extracting the cluster name is fragile. It currently assumes that any namespace longer than the prefix starts with that prefix, which can lead to incorrect cluster names if a resource is in a different namespace that happens to be long enough. It's safer to use strings.HasPrefix and strings.TrimPrefix.

Suggested change
// clusterNameFromWorkNamespace extracts cluster name from "karmada-es-{cluster}" namespace.
func clusterNameFromWorkNamespace(ns string) string {
const prefix = "karmada-es-"
if len(ns) > len(prefix) {
return ns[len(prefix):]
}
return ""
}
// clusterNameFromWorkNamespace extracts cluster name from "karmada-es-{cluster}" namespace.
func clusterNameFromWorkNamespace(ns string) string {
const prefix = "karmada-es-"
if strings.HasPrefix(ns, prefix) {
return strings.TrimPrefix(ns, prefix)
}
return ""
}

Comment on lines +151 to +170
func getMemberWorkloadStatus(ctx context.Context, clusterName, namespace, name, kind string) NodeStatus {
memberClient := client.InClusterClientForMemberCluster(clusterName)
if memberClient == nil {
return NodeStatusAbnormal
}
switch kind {
case "Deployment":
deploy, err := memberClient.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
klog.V(4).InfoS("Failed to get member deployment", "cluster", clusterName, "err", err)
return NodeStatusAbnormal
}
if deploy.Spec.Replicas != nil && deploy.Status.ReadyReplicas == *deploy.Spec.Replicas {
return NodeStatusHealthy
}
return NodeStatusProgressing
default:
return NodeStatusHealthy
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The getMemberWorkloadStatus function only implements health checks for Deployment. For all other resource types, it defaults to NodeStatusHealthy, which could be misleading if the actual resource in the member cluster is in a failing state.

Consider implementing status checks for other common types or using a more generic approach to determine resource health.

k8sClient kubeclient.Interface,
namespace, name, kind string,
) (*TopologyResponse, error) {
resp := &TopologyResponse{}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The traceChain function appends nodes to a slice without checking for duplicate IDs. In complex propagation scenarios, multiple paths might lead to the same resource (e.g., the same member cluster workload). Duplicate IDs in the TopologyResponse will cause issues in the UI as React Flow requires unique node IDs.

Consider using a map to track added nodes by their ID before appending them to resp.Nodes.

allowClear
value={name || undefined}
onChange={(v) => setName(v || '')}
options={[]}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The options for the "Resource Name" Select are hardcoded to an empty array. This makes it difficult for users to find resources.

Ideally, this Select should be populated with resource names fetched from the API based on the selected namespace and kind.

@SunsetB612 SunsetB612 force-pushed the feat/resource-topology-ui branch from f97033c to 1c47416 Compare April 8, 2026 02:54
@SunsetB612 SunsetB612 changed the title feat(ui): add resource topology page with DAG visualization feat(topology): add resource topology visualization Apr 8, 2026
@SunsetB612 SunsetB612 force-pushed the feat/resource-topology-ui branch 4 times, most recently from fcb96ab to ad34869 Compare April 8, 2026 13:51
Signed-off-by: SunsetB612 <10235101575@stu.ecnu.edu.cn>
@SunsetB612 SunsetB612 force-pushed the feat/resource-topology-ui branch from ad34869 to bf17c36 Compare April 9, 2026 09:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants