Skip to content

Preserve autoscaling target identity and link HPA Object metric sources - #2009

Merged
nadaverell merged 13 commits into
mainfrom
feature/relationship-autoscaling-links
Oct 14, 2026
Merged

nadaverell merged 13 commits into
mainfrom
feature/relationship-autoscaling-links

Conversation

@nadaverell

@nadaverell nadaverell commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

An HPA can scale a Deployment using an Object metric from an Ingress, but Radar's metric card currently gives no route to that source. KEDA ScaledObjects targeting StatefulSets also lose their topology edge because targets are resolved before StatefulSet nodes exist.

Design

  • Show each HPA Object source inside its existing metric card, separately from the workload being scaled. Carry the source's API group, selector and scope, and pair observations by structured metric identity.
  • Preserve declared target API identity for HPA, VPA and KEDA links using the shared reference converter. VPA keeps one Target link and ordinary versionless built-in navigation.
  • Apply KEDA's documented apps/v1 Deployment defaults consistently in the UI, topology, missing-reference detection and compact context. Resolve target edges after supported workload nodes exist, retaining uses edges and scaleTarget/scaledBy projections.

No endpoint, read privilege or custom scale-subresource graph support is added. Merge after #1996, which supplies exact-reference navigation.

Testing

TypeScript, the complete Go pkg suite, both focused topology regressions and all 19 autoscaler renderer tests pass. Component regressions exercise an Ingress Object source, exact declared groups and versionless VPA links; builder regressions exercise KEDA StatefulSet edges and both relationship projections. The documented KEDA setup fails against main and passes with this change in a synthetic builder experiment.

Merge verification remains blocked: full frontend runs reported failures in unchanged tests under severe host contention, including the known kind-view flake, which also failed in isolation. Root Go build/internal tests, full application build and lint did not complete; rerun those checks before merging. Assertions and timeouts were not weakened.

The supplied clusters have ten Resource CPU HPAs and no KEDA or VPA CRDs. Historical live HPA fixtures validated API admission and source navigation. No custom-metrics adapter or KEDA controller reconciled them; source links do not establish working metrics or successful scaling.

Historical synthetic HPA Object-source navigation
Historical synthetic Namespace Object-source navigation

@nadaverell
nadaverell requested a review from hisco as a code owner October 7, 2026 05:42
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Preserve autoscaling target identity and link HPA Object metric sources

🐞 Bug fix âœĻ Enhancement 🧊 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

â€Ē Preserve API group and scope when navigating from HPA, VPA, and KEDA targets.
â€Ē Add separate links to HPA Object metric sources, including the HPA’s own Namespace.
â€Ē Keep incomplete HPA and VPA references non-navigable; test identity, defaults, and navigation.
Diagram

graph TD
  HPA["HPA drawer"] --> Metrics["Object sources"] --> Scope{"Core Namespace?"} --> Converter["Reference converter"] --> Link["Resource link"] --> Navigation["Resource navigation"]
  Metrics --> Converter
  HPA --> Converter
  VPA["VPA drawer"] --> Converter
  KEDA["KEDA drawer"] --> Converter
Loading
High-Level Assessment

Using the existing group- and scope-aware converter is the best fit. Continuing to construct links separately in each renderer would retain the identity loss and duplicate scope rules; this PR keeps KEDA’s documented defaults at its renderer boundary.

Files changed (4) +239 / -33

Enhancement (1) +62 / -11
HPARenderer.tsxResolve HPA targets and expose separate Object metric sources +62/-11

Resolve HPA targets and expose separate Object metric sources

â€Ē Converts scale targets and Object metric described objects through the shared reference converter, retaining API group and scope. Adds an Object metric sources section and explicitly identifies core Namespace metrics as measuring the HPA’s own Namespace; incomplete targets remain plain text.

packages/k8s-ui/src/components/resources/renderers/HPARenderer.tsx

Bug fix (2) +19 / -22
KedaScaledObjectRenderer.tsxPreserve KEDA target API identity with documented defaults +12/-11

Preserve KEDA target API identity with documented defaults

â€Ē Uses the shared converter for scale target links, defaulting omitted API version and kind to apps/v1 and Deployment. Explicit custom API identities remain intact, and unresolvable targets render as text.

packages/k8s-ui/src/components/resources/renderers/KedaScaledObjectRenderer.tsx

VPARenderer.tsxPreserve VPA target group and scope in navigation +7/-11

Preserve VPA target group and scope in navigation

â€Ē Replaces inferred target links with references from the shared converter. Targets lacking sufficient identity render as plain text rather than linking to a guessed resource.

packages/k8s-ui/src/components/resources/renderers/VPARenderer.tsx

Tests (1) +158 / -0
AutoscalingReferences.test.tsxTest autoscaling reference identity and Object metric links +158/-0

Test autoscaling reference identity and Object metric links

â€Ē Adds DOM navigation tests for custom-group HPA and VPA targets, HPA Object metric sources, and KEDA defaults versus explicit API identity. Also verifies that incomplete HPA and VPA targets remain non-navigable and that core Namespace metrics link to the HPA’s Namespace.

packages/k8s-ui/src/components/resources/renderers/AutoscalingReferences.test.tsx

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) ðŸŽĻ UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Incomplete object metrics crash the drawer 🐞 Bug ☞ Reliability
Description
HPARenderer filters for metric.object but then reads metric.object.metric.name without
checking that metric exists. An Object metric with an incomplete nested metric, such as `{ type:
'Object', object: {} }`, passes the filter and throws while the HPA drawer renders.
Code

packages/k8s-ui/src/components/resources/renderers/HPARenderer.tsx[142]

+                  key={`${metric.object.metric.name}-${i}`}
Evidence
Raw drawer data reaches this renderer; the new filter checks metric.object only, while the new
card dereferences metric.object.metric.name for both its key and displayed value.

packages/k8s-ui/src/components/shared/ResourceRendererDispatch.tsx[841-841]
packages/k8s-ui/src/components/resources/renderers/HPARenderer.tsx[40-44]
packages/k8s-ui/src/components/resources/renderers/HPARenderer.tsx[126-148]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An Object metric with no nested `metric` passes the new filter and crashes the HPA drawer when its name is read.
## Fix Focus Areas
- packages/k8s-ui/src/components/resources/renderers/HPARenderer.tsx[44-44]
- packages/k8s-ui/src/components/resources/renderers/HPARenderer.tsx[126-148]
## Recommended Fix
Guard the nested metric before reading its name, or render incomplete metric entries with a safe fallback. Add a test with an incomplete Object metric.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Empty target kinds lose their links ✓ Resolved
Description
KedaScaledObjectRenderer uses ?? to default target.kind, so an empty string reaches the
reference converter instead of becoming Deployment. When a ScaledObject has kind: '', the drawer
displays a Deployment target but renders it as plain text because the converter rejects the empty
kind.
Code

packages/k8s-ui/src/components/resources/renderers/KedaScaledObjectRenderer.tsx[84]

+                kind: target.kind ?? 'Deployment',
Evidence
The existing display helper and backend reference parsing both default empty kinds to Deployment,
but the new converter rejects an empty kind, producing no link.

packages/k8s-ui/src/components/resources/renderers/KedaScaledObjectRenderer.tsx[77-87]
packages/k8s-ui/src/components/resources/resource-utils-keda.ts[67-71]
internal/k8s/detect_crd_refs.go[138-151]
packages/k8s-ui/src/utils/navigation.ts[233-244]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
An explicitly empty ScaledObject target kind displays as Deployment but loses its navigation link.
## Fix Focus Areas
- packages/k8s-ui/src/components/resources/renderers/KedaScaledObjectRenderer.tsx[81-87]
## Recommended Fix
Apply the same empty-string fallback used by the existing target display helper before converting the reference. Add a navigation test for `kind: ''`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
ðŸ’Ą Did you know, you can switch off images and animations for a plain-text comment

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread packages/k8s-ui/src/components/resources/renderers/HPARenderer.tsx Outdated
Comment thread packages/k8s-ui/src/components/resources/renderers/KedaScaledObjectRenderer.tsx Outdated
@nadaverell

Copy link
Copy Markdown
Contributor Author

Qodo finding (incomplete Object metric crashes the drawer): not changing this. The HPA API server rejects an Object metric source without metric.name (validateMetricIdentifier in pkg/apis/autoscaling/validation requires it, and describedObject and target are required too), so {type: Object, object: {}} can't be stored in a cluster. Guarding a shape the API can't hold would be a speculative fallback.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Qodo finding (incomplete Object metric crashes the drawer): not changing this. The HPA API server rejects an Object metric source without metric.name (validateMetricIdentifier in pkg/apis/autoscaling/validation requires it, and describedObject and target are required too), so {type: Object, object: {}} can't be stored in a cluster. Guarding a shape the API can't hold would be a speculative fallback.

Regarding finding 1, your API-validation point is valid for objects accepted by the current HPA API server: a persisted Object metric should include metric.name, describedObject, and its target.

The finding is still defensive rather than evidence of a valid Kubernetes object. The renderer receives raw drawer data, which can also originate from cached objects, older API versions, mocks, or malformed data. A small guard would prevent the entire HPA drawer from crashing if such data reaches the UI, but I agree this is lower priority and may be out of scope if the application guarantees server-validated inputs.

No change is required if that input guarantee is intentional; otherwise, the minimal fix is to safely handle a missing nested metric object.

@cursor

cursor Bot commented Oct 9, 2026

Copy link
Copy Markdown

Bugbot needs on-demand usage enabled

Bugbot uses usage-based billing for this team and requires on-demand usage to be enabled.

A team admin can enable on-demand usage in the Cursor dashboard.

@nadaverell
nadaverell force-pushed the feature/relationship-endpoint-identities branch from b0da042 to 9373bf5 Compare October 13, 2026 02:26
@nadaverell
nadaverell force-pushed the feature/relationship-autoscaling-links branch from 86e716b to 24cc14f Compare October 13, 2026 02:26
nadaverell added a commit that referenced this pull request Oct 13, 2026
â€Ķference links (#1996)

EndpointSlice, Knative and Crossplane references can name a custom
resource whose Kind collides with a core resource. Preserve the declared
API group and discovered scope so clicking a reference opens the
intended object rather than a same-named object in another API.

## Changes

- Resolve EndpointSlice targets with their own namespace and discovered
scope. Keep controller-written Pod references without apiVersion
navigable, and make nodeName a core Node link.
- Resolve group-qualified plurals before group-blind Kind aliases, while
protecting canonical core plurals against discovery collisions.
- Use one Knative reference reader and link component for versioned and
group-only declarations. Incomplete custom identities remain visible
text.
- Use the same group-qualified plural for Crossplane composed-resource
status fetches and navigation.

## Validation

Go build, pkg and internal tests, TypeScript checks and the complete
shared UI suite pass (4,534 tests, one existing skip). Full
frontend/embed/backend build passes. Web tests passed except one
existing timer under concurrent load; all seven tests in that file
passed on an isolated rerun.

Compared main and this change against the EKS nonprod and management
test clusters using owned fixtures, REST, read-only MCP calls and
headless Chromium. EndpointSlice and Knative links open the custom
object rather than the core decoy. Crossplane custom Node status uses
its discovered workerhosts plural and succeeds; its custom Endpoints
alias already resolves on main and remains correct. Controller-shaped
Pod and core Node clicks remain functional. Browser runs recorded no
page errors.

The Knative fixture was not Ready, and Crossplane fixtures were manually
created composite/managed-resource shapes; the evidence establishes
identity and navigation, not successful controller reconciliation.
Group-only Knative references are component-tested because the installed
PingSource schema rejects that experimental field.

![EndpointSlice exact
destination](https://github.com/user-attachments/assets/04cf2037-abb4-4c05-8a66-81727e74ab07)
![Knative sink
destination](https://github.com/user-attachments/assets/7c710cd5-eba3-45a0-af36-fc5e8d31284c)

## Notes

#2009 builds on this reference converter. The overlapping #2047 should
follow this stack and retain these shared Knative helpers, collision
protections and tests alongside its additional brokerRef changes. No
endpoint or authorization behavior changes.
Base automatically changed from feature/relationship-endpoint-identities to main October 13, 2026 10:31
@nadaverell
nadaverell force-pushed the feature/relationship-autoscaling-links branch from 24cc14f to 62f2950 Compare October 13, 2026 11:11
Keep resource navigation faithful to the declared API identity so colliding
Kinds cannot route operators to a different object.
Keep resource navigation faithful to the declared API identity so colliding
Kinds cannot route operators to a different object.
Keep resource navigation faithful to the declared API identity so colliding
Kinds cannot route operators to a different object.
Keep resource navigation faithful to the declared API identity so colliding
Kinds cannot route operators to a different object.
â€Ķd detection

KEDA reads an omitted apiVersion as apps/v1, so `kind: Rollout` without a
version names an apps Rollout, not the same-named Argo Rollout. Topology
joined it to the Argo Rollout. Topology and missing-target detection now
share one reader with KEDA's defaults, matching the detail view.
The ScaledObject pass ran before the StatefulSet pass, so a ScaledObject
scaling a StatefulSet never got its edge: the StatefulSet showed no
autoscaler and MCP scaledBy was empty.
Current relationship projection distinguishes autoscaling from other uses edges by API group. Exercise both directions for a StatefulSet target and require the omitted-version Rollout to remain unlinked so the delayed target pass cannot silently regress these surfaces.
Core Namespace Object metrics use the HPA namespace even when apiVersion is omitted. Standard VPA workload Kinds use built-in selectors without consulting apiVersion, so keep omitted-version links usable and distinguish a declared custom target from the built-in selector target when they differ.
VPA selects built-in workload Pods by Kind before consulting apiVersion, so topology and related-resource projections must follow the same selector policy as the drawer. Refuse unqualified non-standard targets rather than attaching them to an unrelated API group.

Object metric observations must match their declared API group and selector. Normalize Namespace metrics to the HPA namespace and put each source link in its existing metric card, keeping references and reported values together.
Match HPA observations by structured object and selector identity while retaining readable metric labels. Show KEDA defaults in the drawer and compact context, and share VPA target contract cases between the backend and renderer tests.
Retain HPA Object metric source links and exact source/value pairing, and
KEDA's documented defaults and delayed StatefulSet target edges.

Remove the custom-group VPA Pod-selector override, dual-target drawer,
exported policy helper and shared mismatch contract. The supplied clusters
and upstream usage evidence do not establish that configuration as a user
workflow. Keep a single declared target and versionless built-in links,
reusing the existing built-in metadata instead of a controller policy table.
Remove the bespoke bare-Rollout advice; the effective Target API suffices.

Replace the intentionally removed mismatch contract tests with ordinary
VPA navigation/projection coverage. Exercise the documented Ingress Object
source at the renderer callback seam and assert a single target link.
The test passed a state the HPA diagnosis type does not define, which fails the shared UI type-check.
@nadaverell
nadaverell force-pushed the feature/relationship-autoscaling-links branch from 79d1e3f to 048ab7c Compare October 13, 2026 14:06
Document exact target groups, KEDA defaults and HPA metric status meanings.
Remove drawer details and unsupported controller-policy claims from MCP docs.
@nadaverell
nadaverell merged commit c9962a3 into main Oct 14, 2026
9 checks passed
@nadaverell
nadaverell deleted the feature/relationship-autoscaling-links branch October 14, 2026 17:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants