diff --git a/apix/v1alpha2/inferencepool_conversion.go b/apix/v1alpha2/inferencepool_conversion.go index dcc36353c..31ea997bf 100644 --- a/apix/v1alpha2/inferencepool_conversion.go +++ b/apix/v1alpha2/inferencepool_conversion.go @@ -95,49 +95,51 @@ func convertStatusToV1(src *InferencePoolStatus) (*v1.InferencePoolStatus, error if src == nil { return nil, errors.New("src cannot be nil") } - if src.Parents == nil { + if len(src.Parents) == 0 { return &v1.InferencePoolStatus{}, nil } out := &v1.InferencePoolStatus{ Parents: make([]v1.ParentStatus, 0, len(src.Parents)), } for _, p := range src.Parents { + // Drop the synthetic "status/default" parent entirely. + if isV1Alpha2DefaultParent(p) { + continue + } ps := v1.ParentStatus{ ParentRef: toV1ParentRef(p.GatewayRef), - Conditions: make([]metav1.Condition, 0), + Conditions: nil, // start nil; only allocate if we actually keep any } for _, c := range p.Conditions { - cc := c - if isV1Alpha2DefaultConditon(c) { + // Drop the synthetic default condition. + if isV1Alpha2DefaultCondition(c) { continue } - // v1alpha2: "Accepted" -> v1: "SupportedByParent" - if cc.Type == string(v1.InferencePoolConditionAccepted) && - cc.Reason == string(InferencePoolReasonAccepted) { - cc.Reason = string(v1.InferencePoolReasonAccepted) + cc := c + if cc.Type == string(v1.InferencePoolConditionAccepted) { + cc.Reason = mapAcceptedReasonToV1(cc.Reason) } ps.Conditions = append(ps.Conditions, cc) } - if len(ps.Conditions) == 0 && len(src.Parents) == 1 { - // Reset the conditions to nil since v1 version does not have default condition. - // Default is only configured when length of src.Parents is 1. - ps.Conditions = nil - } + + // If no real conditions remain, leave nil (omitted in JSON). out.Parents = append(out.Parents, ps) } - return out, nil -} -func isV1Alpha2DefaultConditon(c metav1.Condition) bool { - return InferencePoolConditionType(c.Type) == InferencePoolConditionAccepted && - c.Status == metav1.ConditionUnknown && InferencePoolReason(c.Reason) == InferencePoolReasonPending + // If all parents were synthetic and were dropped, normalize to empty status. + if len(out.Parents) == 0 { + return &v1.InferencePoolStatus{}, nil + } + return out, nil } func convertStatusFromV1(src *v1.InferencePoolStatus) (*InferencePoolStatus, error) { if src == nil { return nil, errors.New("src cannot be nil") } - if src.Parents == nil { + if len(src.Parents) == 0 { + // Do not synthesize the default parent here; v1alpha2 CRD defaults cover creation-time, + // and conversion should reflect the true empty set. return &InferencePoolStatus{}, nil } out := &InferencePoolStatus{ @@ -147,23 +149,60 @@ func convertStatusFromV1(src *v1.InferencePoolStatus) (*InferencePoolStatus, err ps := PoolStatus{ GatewayRef: fromV1ParentRef(p.ParentRef), } - if p.Conditions != nil { - ps.Conditions = make([]metav1.Condition, len(p.Conditions)) - } - for idx, c := range p.Conditions { - cc := c - // v1: "SupportedByParent" -> v1alpha2: "Accepted" - if cc.Type == string(v1.InferencePoolConditionAccepted) && - cc.Reason == string(v1.InferencePoolReasonAccepted) { - cc.Reason = string(InferencePoolReasonAccepted) + if n := len(p.Conditions); n > 0 { + ps.Conditions = make([]metav1.Condition, 0, n) + for _, c := range p.Conditions { + cc := c + if cc.Type == string(v1.InferencePoolConditionAccepted) { + cc.Reason = mapAcceptedReasonFromV1(cc.Reason) + } + ps.Conditions = append(ps.Conditions, cc) } - ps.Conditions[idx] = cc } out.Parents = append(out.Parents, ps) } return out, nil } +// isV1Alpha2DefaultParent returns true for the synthetic "no parents yet" entry. +func isV1Alpha2DefaultParent(p PoolStatus) bool { + if p.GatewayRef.Kind == nil || p.GatewayRef.Name == "" { + return false + } + return *p.GatewayRef.Kind == "Status" && p.GatewayRef.Name == "default" +} + +// Map v1alpha2 -> v1 reasons for the "Accepted" condition. +func mapAcceptedReasonToV1(r string) string { + switch InferencePoolReason(r) { + case InferencePoolReasonAccepted: + return string(v1.InferencePoolReasonAccepted) + case InferencePoolReasonNotSupportedByGateway: + return string(v1.InferencePoolReasonNotSupportedByParent) + default: + // Keep other reasons like "HTTPRouteNotAccepted" or "Pending" as-is. + return r + } +} + +// Map v1 -> v1alpha2 reasons for the "Accepted" condition. +func mapAcceptedReasonFromV1(r string) string { + switch v1.InferencePoolReason(r) { + case v1.InferencePoolReasonAccepted: + return string(InferencePoolReasonAccepted) + case v1.InferencePoolReasonNotSupportedByParent: + return string(InferencePoolReasonNotSupportedByGateway) + default: + return r + } +} + +func isV1Alpha2DefaultCondition(c metav1.Condition) bool { + return InferencePoolConditionType(c.Type) == InferencePoolConditionAccepted && + c.Status == metav1.ConditionUnknown && + InferencePoolReason(c.Reason) == InferencePoolReasonPending +} + func toV1ParentRef(in ParentGatewayReference) v1.ParentReference { out := v1.ParentReference{ Name: v1.ObjectName(in.Name), diff --git a/apix/v1alpha2/inferencepool_conversion_test.go b/apix/v1alpha2/inferencepool_conversion_test.go index 3db69e9a3..a4147f2df 100644 --- a/apix/v1alpha2/inferencepool_conversion_test.go +++ b/apix/v1alpha2/inferencepool_conversion_test.go @@ -186,6 +186,202 @@ func TestInferencePoolConvertTo(t *testing.T) { Parents: []v1.ParentStatus{ { ParentRef: v1.ParentReference{Name: "my-gateway"}, + // Conditions omitted (nil) after dropping the synthetic default. + }, + }, + }, + }, + wantErr: false, + }, + { + name: "v1alpha2 -> v1 maps NotSupportedByGateway to NotSupportedByParent", + src: &InferencePool{ + TypeMeta: metav1.TypeMeta{ + Kind: "InferencePool", + APIVersion: GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pool", + Namespace: "ns", + }, + Spec: InferencePoolSpec{ + Selector: map[LabelKey]LabelValue{"app": "m"}, + TargetPortNumber: 8080, + }, + Status: InferencePoolStatus{ + Parents: []PoolStatus{ + { + GatewayRef: ParentGatewayReference{Name: "gw"}, + Conditions: []metav1.Condition{ + { + Type: string(InferencePoolConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(InferencePoolReasonNotSupportedByGateway), + LastTransitionTime: timestamp, + }, + }, + }, + }, + }, + }, + want: &v1.InferencePool{ + TypeMeta: metav1.TypeMeta{ + Kind: "InferencePool", + APIVersion: v1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pool", + Namespace: "ns", + }, + Spec: v1.InferencePoolSpec{ + Selector: v1.LabelSelector{MatchLabels: map[v1.LabelKey]v1.LabelValue{"app": "m"}}, + TargetPorts: []v1.Port{ + {Number: v1.PortNumber(8080)}, + }, + }, + Status: v1.InferencePoolStatus{ + Parents: []v1.ParentStatus{ + { + ParentRef: v1.ParentReference{Name: "gw"}, + Conditions: []metav1.Condition{ + { + Type: string(v1.InferencePoolConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(v1.InferencePoolReasonNotSupportedByParent), + LastTransitionTime: timestamp, + }, + }, + }, + }, + }, + }, + wantErr: false, + }, + { + name: "v1alpha2 -> v1 drops synthetic default parent", + src: &InferencePool{ + TypeMeta: metav1.TypeMeta{ + Kind: "InferencePool", + APIVersion: GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pool", + Namespace: "ns", + }, + Spec: InferencePoolSpec{ + Selector: map[LabelKey]LabelValue{"app": "m"}, + TargetPortNumber: 8080, + }, + Status: InferencePoolStatus{ + Parents: []PoolStatus{ + { + GatewayRef: func() ParentGatewayReference { + k := Kind("Status") + return ParentGatewayReference{ + Kind: &k, Name: "default", + } + }(), + Conditions: []metav1.Condition{ + { + Type: string(InferencePoolConditionAccepted), + Status: metav1.ConditionUnknown, + Reason: string(InferencePoolReasonPending), + LastTransitionTime: timestamp, + }, + }, + }, + }, + }, + }, + want: &v1.InferencePool{ + TypeMeta: metav1.TypeMeta{ + Kind: "InferencePool", + APIVersion: v1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pool", + Namespace: "ns", + }, + Spec: v1.InferencePoolSpec{ + Selector: v1.LabelSelector{MatchLabels: map[v1.LabelKey]v1.LabelValue{"app": "m"}}, + TargetPorts: []v1.Port{{Number: v1.PortNumber(8080)}}, + }, + Status: v1.InferencePoolStatus{ + // All parents dropped -> empty status. + }, + }, + wantErr: false, + }, + { + name: "v1alpha2 -> v1 drops synthetic default parent but keeps real parent", + src: &InferencePool{ + TypeMeta: metav1.TypeMeta{ + Kind: "InferencePool", + APIVersion: GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pool", + Namespace: "ns", + }, + Spec: InferencePoolSpec{ + Selector: map[LabelKey]LabelValue{"app": "m"}, + TargetPortNumber: 8080, + }, + Status: InferencePoolStatus{ + Parents: []PoolStatus{ + { + GatewayRef: func() ParentGatewayReference { + k := Kind("Status") + return ParentGatewayReference{Kind: &k, Name: "default"} + }(), + Conditions: []metav1.Condition{ + { + Type: string(InferencePoolConditionAccepted), + Status: metav1.ConditionUnknown, + Reason: string(InferencePoolReasonPending), + LastTransitionTime: timestamp, + }, + }, + }, + { + GatewayRef: ParentGatewayReference{Name: "real-gw"}, + Conditions: []metav1.Condition{ + { + Type: string(InferencePoolConditionResolvedRefs), + Status: metav1.ConditionTrue, + Reason: string(InferencePoolReasonResolvedRefs), + LastTransitionTime: timestamp, + }, + }, + }, + }, + }, + }, + want: &v1.InferencePool{ + TypeMeta: metav1.TypeMeta{ + Kind: "InferencePool", + APIVersion: v1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pool", + Namespace: "ns", + }, + Spec: v1.InferencePoolSpec{ + Selector: v1.LabelSelector{MatchLabels: map[v1.LabelKey]v1.LabelValue{"app": "m"}}, + TargetPorts: []v1.Port{{Number: v1.PortNumber(8080)}}, + }, + Status: v1.InferencePoolStatus{ + Parents: []v1.ParentStatus{ + { + ParentRef: v1.ParentReference{Name: "real-gw"}, + Conditions: []metav1.Condition{ + { + Type: string(v1.InferencePoolConditionResolvedRefs), + Status: metav1.ConditionTrue, + Reason: string(v1.InferencePoolReasonResolvedRefs), + LastTransitionTime: timestamp, + }, + }, }, }, }, @@ -396,7 +592,70 @@ func TestInferencePoolConvertFrom(t *testing.T) { Parents: []PoolStatus{ { GatewayRef: ParentGatewayReference{Name: "my-gateway"}, - Conditions: []metav1.Condition{}, + Conditions: nil, // Conditions omitted because the converter leaves it nil when v1 had []. + + }, + }, + }, + }, + wantErr: false, + }, + { + name: "v1 -> v1alpha2 maps NotSupportedByParent to NotSupportedByGateway", + src: &v1.InferencePool{ + TypeMeta: metav1.TypeMeta{ + Kind: "InferencePool", + APIVersion: v1.GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pool", + Namespace: "ns", + }, + Spec: v1.InferencePoolSpec{ + Selector: v1.LabelSelector{MatchLabels: map[v1.LabelKey]v1.LabelValue{"app": "m"}}, + TargetPorts: []v1.Port{{Number: v1.PortNumber(8080)}}, + }, + Status: v1.InferencePoolStatus{ + Parents: []v1.ParentStatus{ + { + ParentRef: v1.ParentReference{Name: "gw"}, + Conditions: []metav1.Condition{ + { + Type: string(v1.InferencePoolConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(v1.InferencePoolReasonNotSupportedByParent), + LastTransitionTime: timestamp, + }, + }, + }, + }, + }, + }, + want: &InferencePool{ + TypeMeta: metav1.TypeMeta{ + Kind: "InferencePool", + APIVersion: GroupVersion.String(), + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "pool", + Namespace: "ns", + }, + Spec: InferencePoolSpec{ + Selector: map[LabelKey]LabelValue{"app": "m"}, + TargetPortNumber: 8080, + }, + Status: InferencePoolStatus{ + Parents: []PoolStatus{ + { + GatewayRef: ParentGatewayReference{Name: "gw"}, + Conditions: []metav1.Condition{ + { + Type: string(InferencePoolConditionAccepted), + Status: metav1.ConditionFalse, + Reason: string(InferencePoolReasonNotSupportedByGateway), + LastTransitionTime: timestamp, + }, + }, }, }, },