Skip to content

stop using cluster-service for frontend nodepool reads - #5058

Merged
openshift-merge-bot[bot] merged 6 commits into
mainfrom
cs-161-nodepool-no-cluster-service
May 4, 2026
Merged

stop using cluster-service for frontend nodepool reads#5058
openshift-merge-bot[bot] merged 6 commits into
mainfrom
cs-161-nodepool-no-cluster-service

Conversation

@deads2k

Copy link
Copy Markdown
Collaborator

this simplifies the read path and allows us to later disconnect the write path.

this simplifies the read path and allows us to later disconnect the
write path.
Comment thread frontend/pkg/frontend/node_pool.go Outdated
Comment thread internal/api/types_nodepool.go Outdated
Comment thread frontend/pkg/frontend/node_pool.go
@miguelsorianod

Copy link
Copy Markdown
Collaborator

/hold see #5058 (comment)

@deads2k

Copy link
Copy Markdown
Collaborator Author

/hold see #5058 (comment)

now addressed. Subnet is set via admission.

@deads2k

Copy link
Copy Markdown
Collaborator Author

/hold cancel

Copilot AI review requested due to automatic review settings April 29, 2026 02:58
@deads2k
David Eads (deads2k) force-pushed the cs-161-nodepool-no-cluster-service branch from 410e122 to a3b87f7 Compare April 29, 2026 02:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR removes the dependency on cluster-service for frontend NodePool read/list flows, shifting the read path to rely on Cosmos-backed NodePool documents and adding admission-time defaulting so newly created NodePools persist subnetId directly.

Changes:

  • Frontend NodePool list/get now serialize NodePools directly from Cosmos, removing cluster-service merge logic.
  • Added NodePool admission mutation to default a missing NodePool subnetId on create to the parent cluster subnet, plus unit tests.
  • Updated integration/version compliance artifacts to reflect subnetId now being present in NodePool payloads.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
frontend/pkg/frontend/node_pool.go Removes cluster-service read/merge from list/get and wires in admission mutation before validation on create/update.
internal/admission/admit_nodepool.go Introduces NodePool mutation that defaults SubnetID on create using cluster context.
internal/admission/admit_nodepool_test.go Adds unit coverage for subnet defaulting behavior in mutation.
internal/validation/validate_nodepools.go Exposes helper accessors used by admission via safe.Field.
internal/api/types_nodepool.go Updates NodePool platform comments around subnet/AZ behavior.
test-integration/frontend/artifacts/VersionCompliance/NodePool/basic-nodepool/expected/list/2025-12-23-preview.json Updates expected list payload with subnetId.
test-integration/frontend/artifacts/VersionCompliance/NodePool/basic-nodepool/expected/list/2024-06-10-preview.json Updates expected list payload with subnetId.
test-integration/frontend/artifacts/VersionCompliance/NodePool/basic-nodepool/expected/get/2025-12-23-preview.json Updates expected get payload with subnetId.
test-integration/frontend/artifacts/VersionCompliance/NodePool/basic-nodepool/expected/get/2024-06-10-preview.json Updates expected get payload with subnetId.
test-integration/frontend/artifacts/FrontendCRUD/NodePool/create-current/06-cosmosCompare-confirm-content/nodepool-node-pool-02.json Updates expected Cosmos compare content with subnetId.
test-integration/frontend/artifacts/FrontendCRUD/NodePool/create-current/06-cosmosCompare-confirm-content/nodepool-basic-node-pool.json Updates expected Cosmos compare content with subnetId.
test-integration/frontend/artifacts/FrontendCRUD/Cluster/delete-with-pending-nodepool-operation/05-cosmosCompare-final-state/nodepool-test-nodepool.json Updates expected final Cosmos state with subnetId.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/pkg/frontend/node_pool.go Outdated
Comment thread frontend/pkg/frontend/node_pool.go Outdated
Comment on lines 40 to 44
var (
toNodePoolTrackedResource = func(oldObj *api.HCPOpenShiftClusterNodePool) *arm.TrackedResource { return &oldObj.TrackedResource }
toNodePoolProperties = func(oldObj *api.HCPOpenShiftClusterNodePool) *api.HCPOpenShiftClusterNodePoolProperties {
ToNodePoolProperties = func(oldObj *api.HCPOpenShiftClusterNodePool) *api.HCPOpenShiftClusterNodePoolProperties {
return &oldObj.Properties
}

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

ToNodePoolProperties is now an exported package-level variable holding a function. Consider making this an exported function instead (or keep it unexported and add a dedicated accessor) so other packages can’t accidentally reassign it, and to better communicate intent.

Copilot uses AI. Check for mistakes.
Comment on lines 76 to 79
}
toNodePoolPropertiesPlatform = func(oldObj *api.HCPOpenShiftClusterNodePoolProperties) *api.NodePoolPlatformProfile {
ToNodePoolPropertiesPlatform = func(oldObj *api.HCPOpenShiftClusterNodePoolProperties) *api.NodePoolPlatformProfile {
return &oldObj.Platform
}

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

Same concern as ToNodePoolProperties: ToNodePoolPropertiesPlatform is exported as a mutable package-level variable. Prefer an exported function (or other non-assignable API) to avoid accidental reassignment and make the API surface safer.

Copilot uses AI. Check for mistakes.
Comment thread internal/api/types_nodepool.go Outdated
// NodePoolPlatformProfile represents a worker node pool configuration.
// Visibility for the entire struct is "read create".
type NodePoolPlatformProfile struct {
// Subnet can be nil and when it is nil, that means "use the cluster subnet field"

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

The SubnetID field comment is a bit misleading/ambiguous: mutation now defaults a nil SubnetID on CREATE to the cluster subnet, so it’s not really “nil means use the cluster subnet” at runtime. Suggest rewording to explicitly describe the defaulting behavior (and refer to SubnetID rather than “Subnet”).

Suggested change
// Subnet can be nil and when it is nil, that means "use the cluster subnet field"
// If SubnetID is nil on CREATE, mutation defaults it to the cluster subnet.

Copilot uses AI. Check for mistakes.
Comment on lines 86 to 90
pagedResponse := arm.NewPagedResponse()

nodePoolsByClusterServiceID := make(map[string]*api.HCPOpenShiftClusterNodePool)
internalNodePoolIterator, err := f.dbClient.HCPClusters(subscriptionID, resourceGroupName).NodePools(clusterName).List(ctx, dbListOptionsFromRequest(request))
if err != nil {
return utils.TrackError(err)

Copilot AI Apr 29, 2026

Copy link

Choose a reason for hiding this comment

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

ArmResourceListNodePools no longer verifies that the parent cluster exists before listing node pools. Because the Cosmos List() call is prefix-based, this will return 200 with an empty list for a non-existent cluster rather than a 404 (and differs from ArmResourceListExternalAuths which does a parent Get first). Consider fetching the cluster from storage first (for existence) and returning NotFound when absent.

Copilot uses AI. Check for mistakes.
func TestMutateNodePool(t *testing.T) {
const (
clusterSubnet = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/cluster-vnet/subnets/cluster-subnet"
nodePoolSubnet = "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/np-vnet/subnets/np-subnet"

@miguelsorianod Miguel Soriano (miguelsorianod) Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

While reading this it made me think that we should have this validation, which is being performed on CS side:
" The subnet resource ID must belong to the same VNet as the parent cluster's subnet".

I think we are also missing API documentation about it at ARM level, because it's being enforced by CS but it's not documented at ARM API level.

@miguelsorianod Miguel Soriano (miguelsorianod) Apr 29, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

There is additionally another restriction (this one is documented at ARM API level already) that we should enforce:

"Note that a subnet cannot be reused between ARO-HCP Clusters, however the same subnet can be used for NodePools of the same cluster"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

There is additionally another restriction (this one is documented at ARM API level already) that we should enforce:

"Note that a subnet cannot be reused between ARO-HCP Clusters, however the same subnet can be used for NodePools of the same cluster"

Another PR. It likely requires a reservation type at the subscription level in the controllers and best effort in the frontend admission validation. How is clsuter-service ensuring it? Transaction lock?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

While reading this it made me think that we should have this validation, which is being performed on CS side: " The subnet resource ID must belong to the same VNet as the parent cluster's subnet".

I think we are also missing API documentation about it at ARM level, because it's being enforced by CS but it's not documented at ARM API level.

Already present. pushed some tests showing it.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Another PR. It likely requires a reservation type at the subscription level in the controllers and best effort in the frontend admission validation. How is clsuter-service ensuring it? Transaction lock?

Yes. A postgresql AdvisoryLock is used where the key is the subnet resource id. Also it's important to notice that the check occurs when both creating a cluster and when creating a nodepool, and it needs to check whether other clusters already use it, but also whether other clusters' nodepools use it.

Copilot AI review requested due to automatic review settings April 30, 2026 20:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +171 to +172
oldObj: nodePoolWithSubnet(sameVNetSubnet),
newObj: nodePoolWithSubnet(sameVNetSubnet),

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

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

The test case name says “different VNet” but uses sameVNetSubnet for both oldObj and newObj, so it doesn’t cover the intended behavior. More importantly, the current AdmitNodePool change-detection uses pointer inequality (newSubnetID != oldSubnetID), which will treat equal-but-distinct *azcorearm.ResourceID values as “changed” and can incorrectly re-validate/reject updates (especially for legacy persisted data). Update the test to use differentVNetSubnet for both old/new (and expect no error when unchanged), and update AdmitNodePool to compare subnet IDs by value (e.g., normalized string/ID) rather than pointer identity.

Suggested change
oldObj: nodePoolWithSubnet(sameVNetSubnet),
newObj: nodePoolWithSubnet(sameVNetSubnet),
oldObj: nodePoolWithSubnet(differentVNetSubnet),
newObj: nodePoolWithSubnet(differentVNetSubnet),

Copilot uses AI. Check for mistakes.
func toNodePoolTrackedResource(oldObj *api.HCPOpenShiftClusterNodePool) *arm.TrackedResource {
return &oldObj.TrackedResource
}

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

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

ToNodePoolProperties and ToNodePoolPropertiesPlatform are newly exported, but they don’t have GoDoc comments. If the repo runs linting (e.g., revive/golangci-lint) this typically fails CI; add doc comments starting with the function name, or keep them unexported and expose them another way.

Suggested change
// ToNodePoolProperties returns the properties of the provided node pool.

Copilot uses AI. Check for mistakes.
Comment on lines +80 to +82
func ToNodePoolPropertiesPlatform(oldObj *api.HCPOpenShiftClusterNodePoolProperties) *api.NodePoolPlatformProfile {
return &oldObj.Platform
}

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

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

ToNodePoolProperties and ToNodePoolPropertiesPlatform are newly exported, but they don’t have GoDoc comments. If the repo runs linting (e.g., revive/golangci-lint) this typically fails CI; add doc comments starting with the function name, or keep them unexported and expose them another way.

Copilot uses AI. Check for mistakes.
Comment on lines 32 to +36

type NodePoolAdmissionContext struct {
Cluster *api.HCPOpenShiftCluster
}

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

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

NodePoolAdmissionContext and MutateNodePool are newly exported but lack GoDoc comments. Add doc comments (starting with the identifier name) to avoid lint failures and to make the new admission/mutation entry points discoverable for callers.

Suggested change
type NodePoolAdmissionContext struct {
Cluster *api.HCPOpenShiftCluster
}
// NodePoolAdmissionContext carries additional cluster context used during
// node pool admission and mutation.
type NodePoolAdmissionContext struct {
Cluster *api.HCPOpenShiftCluster
}
// MutateNodePool applies admission-time mutations to a node pool and returns
// any field errors encountered while mutating the object.

Copilot uses AI. Check for mistakes.
Comment on lines +223 to +227
func (f *Frontend) newNodePoolAdmissionContext(ctx context.Context, cluster *api.HCPOpenShiftCluster) (*admission.NodePoolAdmissionContext, error) {
return &admission.NodePoolAdmissionContext{
Cluster: cluster,
}, nil
}

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

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

newNodePoolAdmissionContext currently doesn’t use ctx and always returns nil error, which adds indirection/signature complexity without functional value. Consider returning *admission.NodePoolAdmissionContext directly (no error) and dropping ctx, or add the intended error-producing logic now (e.g., context-building that can actually fail).

Copilot uses AI. Check for mistakes.
// Check only if it is a creating nodepool or a change in the Subnet.
// Compare by string value (not pointer identity) so equal-but-distinct
// *azcorearm.ResourceID values aren't treated as a change.
if newNodePool.Properties.Platform.SubnetID != nil && cluster.CustomerProperties.Platform.SubnetID != nil {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is the cluster.CustomerProperties.Platform.SubnetID != nil check needed? The cluster's subnet is a required attribute on creation.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The bot wants it. We can keep arguing with it or deal with the cruft it leaves everywhere. I have no interest in bothering to argue.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I understand this only occurs when the content is changed so it shouldn't impact future PRs.

Seeing the nil check though implies this could be nil for some reason which makes it confusing to reason about.

I don't have a strong preference so non blocker for me.

Comment thread internal/admission/admit_nodepool.go
@miguelsorianod

Copy link
Copy Markdown
Collaborator

/lgtm

@openshift-ci openshift-ci Bot added the lgtm label May 4, 2026
@openshift-ci

openshift-ci Bot commented May 4, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: deads2k, miguelsorianod

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

The pull request process is described 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

@openshift-merge-bot
openshift-merge-bot Bot merged commit 5941153 into main May 4, 2026
15 checks passed
@openshift-merge-bot
openshift-merge-bot Bot deleted the cs-161-nodepool-no-cluster-service branch May 4, 2026 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants