Repository navigation
Fix continuous reconciliation loop for NetworkConnectivityInternalRange - #12615
Conversation
Field Mutability Analysis for
|
| KRM Spec Field | GCP Proto Field | Status in GCP | Supported in updateMask? |
Notes / API Semantics |
|---|---|---|---|---|
description |
description |
Mutable | ✅ Yes (description) |
Can be updated at any time. |
labels |
labels |
Mutable | ✅ Yes (labels) |
Standard GCP resource labels. |
prefixLength |
prefix_length |
Mutable | ✅ Yes (prefixLength) |
Used to resize IPv4 reservations ("Can also be used during updates to change the range size"). |
allocationOptions |
allocation_options |
Mutable | ✅ Yes (allocationOptions) |
Mutable for RANDOM_FIRST_N_AVAILABLE strategy (e.g. updating firstAvailableRangesLookupSize). |
overlaps |
overlaps |
Mutable | ✅ Yes (overlaps) |
Allows updating overlap permissions (ROUTE_OVERLAP, OVERLAP_EXISTING_SUBNET_RANGE). |
networkRef |
network |
Immutable | ❌ No | GCP docs explicitly specify: "Immutable. The URL or resource ID of the network in which to reserve the internal range." |
ipCIDRRange |
ip_cidr_range |
Immutable | ❌ No | The allocated IP range cannot be changed in-place. Modifying IP address requires recreating the range (resizing is done via prefixLength). |
targetCIDRRange |
target_cidr_range |
Immutable / Create-Only | ❌ No | Search space used only during initial auto-allocation. Not part of the live patch mask. |
usage |
usage |
Immutable | ❌ No | Core purpose (FOR_VPC, EXTERNAL_TO_VPC, FOR_MIGRATION). Cannot be altered after creation. |
peering |
peering |
Immutable | ❌ No | Peering mode (FOR_SELF, FOR_PEER, NOT_SHARED). Cannot be altered after creation. |
migration |
migration |
Immutable | ❌ No | Only used during create when usage: FOR_MIGRATION. |
projectRef / location |
Parent path | Immutable | ❌ No | Resource identity hierarchy. |
The direct controller's Update() diff check covers all 5 mutable fields (description, labels, prefixLength, allocationOptions, overlaps) and excludes the immutable / create-only fields from the update mask.
| // Only mutable fields supported by GCP InternalRange Patch are included in updateMask. | ||
| // Other fields (ipCIDRRange, targetCIDRRange, networkRef, usage, peering) | ||
| // are immutable / create-only parameters and cannot be modified in-place via Patch. | ||
| // prefixLength can be updated to resize the range size in IPv4 reservations. | ||
| // allocationOptions (e.g. firstAvailableRangesLookupSize for RANDOM_FIRST_N_AVAILABLE) can be updated. |
There was a problem hiding this comment.
I don't think this is the correct logic. We should not hide the users' invalid changes by dropping them. We should pass whatever the users want to change to the API so that the API decides whether it is acceptable or not.
And in a no-change re-reconciliation, there shouldn't be any diffs found.
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: ldanielmadariaga The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
31cf5fd
Fixes #12500
Root Cause Analysis
When a
NetworkConnectivityInternalRangeresource was created withspec.allocationOptionsorspec.targetCIDRRange, subsequent reconcile loops immediately failed with:googleapi: Error 400: Unsupported update mask path: "allocation_options": invalid argumentThe issue was caused by:
convertAPIToProtoDeserialization Failure:convertAPIToProtoinpkg/controller/direct/networkconnectivity/utils.goused standardjson.Unmarshal(j, &v)instead ofprotojson.Unmarshal.api.InternalRangeserializes JSON with camelCase tags (allocationOptions,prefixLength,targetCidrRange), whereas protobuf-generated structs have snake_case tags (allocation_options,prefix_length,target_cidr_range).json.Unmarshalfailed to deserialize these fields intoa.actual, leavinga.actual.AllocationOptions,a.actual.TargetCidrRange, anda.actual.PrefixLengthunpopulated (nil/0).Spurious Update Mask Diff on Create-Only / Immutable Fields:
Update()(networkconnectivityinternalrange_controller.go), the controller diffed create-only/immutable fields (allocationOptions,targetCidrRange,ipCidrRange,peering,usage) againsta.actual.a.actuallacked these fields,!reflect.DeepEqual(...)evaluated totrueon every single reconcile loop, appending"allocationOptions"and"targetCidrRange"to the update mask and issuing aPATCHrequest.PatchAPI does not supportallocation_optionsortarget_cidr_rangein the update mask, rejecting the request with Error 400.Missing Re-Reconciliation Check in E2E Tests:
NetworkConnectivityInternalRangewas listed on the exemption list intests/e2e/ratcheting.go, bypassing the steady-state re-reconciliation check during E2E tests.Changes Made
convertAPIToProto(pkg/controller/direct/networkconnectivity/utils.go) to useprotojson.UnmarshalOptions{DiscardUnknown: true}for proper proto unmarshalling.Update()(pkg/controller/direct/networkconnectivity/networkconnectivityinternalrange_controller.go) to only diff mutable fields (description,labels,prefixLength).tests/e2e/ratcheting.go) by removingNetworkConnectivityInternalRangefrom the exemption list.pkg/test/resourcefixture/testdata/basic/networkconnectivity/v1alpha1/networkconnectivityinternalrange/networkconnectivityinternalrange-firstsmallestfitting) reproducing the customer configuration (allocationStrategy: FIRST_SMALLEST_FITTINGandtargetCIDRRange).