Skip to content

Commit 291f5a4

Browse files
scotwellsclaude
andcommitted
feat: Read instance city and workload from labels in CLI
The platform now stamps city-code, workload-name, workload-deployment-name, and placement-name directly onto Instances at creation time. The CLI can therefore resolve CITY/WORKLOAD/placement directly from those labels without performing cross-object joins. The prior approach keyed the WorkloadDeployment map on UID and looked up instances via WorkloadDeploymentUIDLabel. That UID is the edge/Karmada WD UID, which differs from the project-cluster WD UID, causing the join to fail across federation planes and producing "unknown"/"orphaned" output. The new label-first path reads CityCodeLabel, WorkloadNameLabel, PlacementNameLabel, and WorkloadDeploymentNameLabel (name is identical across all planes) before falling back to the WD Get/List join. A wdNameFromInstanceName helper strips the trailing ordinal suffix from the Instance name as a last-resort fallback for instances created before the labels existed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 8c15212 commit 291f5a4

2 files changed

Lines changed: 142 additions & 17 deletions

File tree

api/v1alpha/labels.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@ const (
55

66
WorkloadUIDLabel = LabelNamespace + "/workload-uid"
77
WorkloadDeploymentUIDLabel = LabelNamespace + "/workload-deployment-uid"
8+
// WorkloadDeploymentNameLabel carries the WorkloadDeployment name on each
9+
// Instance. Unlike WorkloadDeploymentUIDLabel — which carries the
10+
// edge/Karmada UID and therefore differs across federation planes —
11+
// WorkloadDeploymentNameLabel is identical in the project cluster, Karmada,
12+
// and on the edge, making it safe for cross-plane owner-ref resolution and
13+
// CLI lookup.
14+
WorkloadDeploymentNameLabel = LabelNamespace + "/workload-deployment-name"
815

916
InstanceIndexLabel = LabelNamespace + "/instance-index"
17+
18+
// CityCodeLabel carries the city code (e.g. "DFW") that the Instance is
19+
// scheduled to. Stamped at creation time and immutable.
20+
CityCodeLabel = LabelNamespace + "/city-code"
21+
22+
// WorkloadNameLabel carries the name of the Workload that owns this
23+
// Instance. Stamped at creation time and immutable.
24+
WorkloadNameLabel = LabelNamespace + "/workload-name"
25+
26+
// PlacementNameLabel carries the name of the placement entry within the
27+
// Workload spec that produced this Instance. Stamped at creation time and
28+
// immutable.
29+
PlacementNameLabel = LabelNamespace + "/placement-name"
1030
)

internal/cmd/compute/instances/instances.go

Lines changed: 122 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"sort"
77
"strings"
8+
"unicode"
89

910
"github.com/spf13/cobra"
1011
corev1 "k8s.io/api/core/v1"
@@ -118,15 +119,18 @@ func runList(cmd *cobra.Command, opts *listOptions) error {
118119
return util.PrintYAML(cmd.OutOrStdout(), &instList)
119120
}
120121

121-
// List deployments — build map deploymentUID → *WorkloadDeployment.
122+
// List deployments — build map deploymentName → *WorkloadDeployment.
123+
// Keyed by name (not UID) because the WorkloadDeploymentUIDLabel on an
124+
// Instance carries the edge/Karmada WD UID, which differs from the
125+
// project-cluster WD UID. The WD name is identical across all planes.
122126
var deployList computev1alpha.WorkloadDeploymentList
123127
if err := c.List(ctx, &deployList, client.InNamespace(util.ResourceNamespace)); err != nil {
124128
return fmt.Errorf("listing deployments: %w", err)
125129
}
126130
deploymentMap := make(map[string]*computev1alpha.WorkloadDeployment, len(deployList.Items))
127131
for i := range deployList.Items {
128132
d := &deployList.Items[i]
129-
deploymentMap[string(d.UID)] = d
133+
deploymentMap[d.Name] = d
130134
}
131135

132136
// List workloads — build map workloadUID → name.
@@ -142,18 +146,52 @@ func runList(cmd *cobra.Command, opts *listOptions) error {
142146
// Build rows.
143147
var rows []instanceRow
144148
for _, inst := range instList.Items {
145-
depUID := inst.Labels[computev1alpha.WorkloadDeploymentUIDLabel]
146149
wlUID := inst.Labels[computev1alpha.WorkloadUIDLabel]
147150

148151
city := "unknown"
149152
wlName := workloadMap[wlUID]
150153
if wlName == "" {
151154
wlName = "orphaned"
152155
}
153-
if dep, ok := deploymentMap[depUID]; ok {
154-
city = dep.Spec.CityCode
155-
if dep.Spec.WorkloadRef.Name != "" {
156-
wlName = dep.Spec.WorkloadRef.Name
156+
157+
// Prefer self-describing labels stamped at creation time (fast path —
158+
// no join needed). Fall back to the WorkloadDeployment join for older
159+
// instances that predate the labels.
160+
labelCity := inst.Labels[computev1alpha.CityCodeLabel]
161+
labelWLName := inst.Labels[computev1alpha.WorkloadNameLabel]
162+
163+
if labelCity != "" && labelWLName != "" {
164+
// Both labels present: no join needed.
165+
city = labelCity
166+
wlName = labelWLName
167+
} else {
168+
// At least one label absent — fall back to WorkloadDeployment lookup.
169+
// Prefer the explicit WorkloadDeploymentNameLabel; fall back to
170+
// deriving the WD name from the Instance name for existing instances
171+
// that predate the label.
172+
depName := inst.Labels[computev1alpha.WorkloadDeploymentNameLabel]
173+
if depName == "" {
174+
depName = wdNameFromInstanceName(inst.Name)
175+
}
176+
if dep, ok := deploymentMap[depName]; ok {
177+
if labelCity != "" {
178+
city = labelCity
179+
} else {
180+
city = dep.Spec.CityCode
181+
}
182+
if labelWLName != "" {
183+
wlName = labelWLName
184+
} else if dep.Spec.WorkloadRef.Name != "" {
185+
wlName = dep.Spec.WorkloadRef.Name
186+
}
187+
} else {
188+
// Deployment not found — use whatever labels we do have.
189+
if labelCity != "" {
190+
city = labelCity
191+
}
192+
if labelWLName != "" {
193+
wlName = labelWLName
194+
}
157195
}
158196
}
159197

@@ -278,20 +316,61 @@ func runDescribe(cmd *cobra.Command, args []string) error {
278316
return fmt.Errorf("getting instance: %w", err)
279317
}
280318

281-
// Look up deployment.
282-
deploymentUID := inst.Labels[computev1alpha.WorkloadDeploymentUIDLabel]
319+
// Resolve CITY, WORKLOAD, and PLACEMENT. Prefer self-describing labels
320+
// stamped at creation time (no join needed). Fall back to a
321+
// WorkloadDeployment Get when any of the labels are absent, so that older
322+
// instances that predate the stamp still resolve correctly.
283323
workloadName := "orphaned"
284324
city := "unknown"
285325
placementName := ""
286326

287-
if deploymentUID != "" {
288-
depSelector := labels.SelectorFromSet(labels.Set{computev1alpha.WorkloadDeploymentUIDLabel: deploymentUID})
289-
var depList computev1alpha.WorkloadDeploymentList
290-
if err := c.List(ctx, &depList, client.InNamespace(util.ResourceNamespace), client.MatchingLabelsSelector{Selector: depSelector}); err == nil && len(depList.Items) > 0 {
291-
dep := depList.Items[0]
292-
city = dep.Spec.CityCode
293-
placementName = dep.Spec.PlacementName
294-
workloadName = dep.Spec.WorkloadRef.Name
327+
labelCity := inst.Labels[computev1alpha.CityCodeLabel]
328+
labelWLName := inst.Labels[computev1alpha.WorkloadNameLabel]
329+
labelPlacement := inst.Labels[computev1alpha.PlacementNameLabel]
330+
331+
if labelCity != "" && labelWLName != "" && labelPlacement != "" {
332+
// All three labels present: no join needed.
333+
city = labelCity
334+
workloadName = labelWLName
335+
placementName = labelPlacement
336+
} else {
337+
// At least one label absent — fall back to WorkloadDeployment Get.
338+
// Prefer the WorkloadDeploymentNameLabel; fall back to deriving the WD
339+
// name from the Instance name for existing instances that lack the label.
340+
depName := inst.Labels[computev1alpha.WorkloadDeploymentNameLabel]
341+
if depName == "" {
342+
depName = wdNameFromInstanceName(inst.Name)
343+
}
344+
if depName != "" {
345+
var dep computev1alpha.WorkloadDeployment
346+
if err := c.Get(ctx, types.NamespacedName{Namespace: util.ResourceNamespace, Name: depName}, &dep); err == nil {
347+
if labelCity != "" {
348+
city = labelCity
349+
} else {
350+
city = dep.Spec.CityCode
351+
}
352+
if labelPlacement != "" {
353+
placementName = labelPlacement
354+
} else {
355+
placementName = dep.Spec.PlacementName
356+
}
357+
if labelWLName != "" {
358+
workloadName = labelWLName
359+
} else {
360+
workloadName = dep.Spec.WorkloadRef.Name
361+
}
362+
} else {
363+
// WD Get failed — use whatever labels we do have.
364+
if labelCity != "" {
365+
city = labelCity
366+
}
367+
if labelWLName != "" {
368+
workloadName = labelWLName
369+
}
370+
if labelPlacement != "" {
371+
placementName = labelPlacement
372+
}
373+
}
295374
}
296375
}
297376

@@ -385,6 +464,32 @@ func networkSummary(ifaces []computev1alpha.InstanceNetworkInterfaceStatus) stri
385464
return fmt.Sprintf("External: %s Internal: %s", extIP, intIP)
386465
}
387466

467+
// wdNameFromInstanceName derives the WorkloadDeployment name from an Instance
468+
// name by stripping the trailing "-<ordinal>" suffix. Instance names follow the
469+
// convention "<wd-name>-<ordinal>" (e.g. "my-api-default-dfw-0" → "my-api-default-dfw").
470+
// This is used as a fallback when WorkloadDeploymentNameLabel is absent on older
471+
// instances that predate that label.
472+
//
473+
// If the name has no trailing numeric segment (not a standard instance name),
474+
// the original name is returned unchanged so callers can handle it gracefully.
475+
func wdNameFromInstanceName(instanceName string) string {
476+
idx := strings.LastIndex(instanceName, "-")
477+
if idx < 0 {
478+
return instanceName
479+
}
480+
suffix := instanceName[idx+1:]
481+
// The suffix must be entirely numeric digits to qualify as an ordinal.
482+
for _, r := range suffix {
483+
if !unicode.IsDigit(r) {
484+
return instanceName
485+
}
486+
}
487+
if suffix == "" {
488+
return instanceName
489+
}
490+
return instanceName[:idx]
491+
}
492+
388493
// formatEnvVar renders a single EnvVar for display.
389494
func formatEnvVar(e corev1.EnvVar) string {
390495
if e.ValueFrom != nil {

0 commit comments

Comments
 (0)