Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vpc reconcile lbs #2023

Merged
merged 2 commits into from
Nov 12, 2024
Merged

Conversation

cjschaef
Copy link
Contributor

What this PR does / why we need it: Add support to reconcile extended VPC Load Balancers

Which issue(s) this PR fixes (optional, in fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when PR gets merged):
Fixes #

Special notes for your reviewer:

/area provider/ibmcloud

  1. Please confirm that if this PR changes any image versions, then that's the sole change this PR makes.

Release note:

Add reconciliation support for extended VPC Load Balancer definitions

@k8s-ci-robot k8s-ci-robot added the area/provider/ibmcloud Issues or PRs related to ibmcloud provider label Oct 22, 2024
@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Oct 22, 2024
@k8s-ci-robot
Copy link
Contributor

Hi @cjschaef. Thanks for your PR.

I'm waiting for a kubernetes-sigs member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work. Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot k8s-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Oct 22, 2024
Copy link

netlify bot commented Oct 22, 2024

Deploy Preview for kubernetes-sigs-cluster-api-ibmcloud ready!

Name Link
🔨 Latest commit 48eebb2
🔍 Latest deploy log https://app.netlify.com/sites/kubernetes-sigs-cluster-api-ibmcloud/deploys/672d13499f850f00084a8577
😎 Deploy Preview https://deploy-preview-2023--kubernetes-sigs-cluster-api-ibmcloud.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@Karthik-K-N
Copy link
Contributor

/ok-to-test

@k8s-ci-robot k8s-ci-robot added ok-to-test Indicates a non-member PR verified by an org member that is safe to test. and removed needs-ok-to-test Indicates a PR that requires an org member to verify it is safe to test. labels Oct 23, 2024
@Karthik-K-N
Copy link
Contributor

/cc @dharaneeshvrd


// Mark cluster as ready.
clusterScope.IBMVPCCluster.Status.Ready = true
clusterScope.Info("cluster infrastructure is now ready for cluster", "clusterName", clusterScope.IBMVPCCluster.Name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Before setting status.Ready to true, we need to update the spec.Controlplane with loadbalancer hostname, This is as per the CAPI contract.
For reference this is what we do currently for PowerVS.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I will add the necessary logic to retrieve those values to set.


// ReconcileLoadBalancers reconciles Load Balancers.
func (s *VPCClusterScope) ReconcileLoadBalancers() (bool, error) {
// TODO(cjschaef): Determine if we want to use default LB configuration or require at least one is defined in Cluster spec.
Copy link
Contributor

Choose a reason for hiding this comment

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

Like other we can make a note of it to add it to webhook for good UX.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I'll add another comment.

Comment on lines 1754 to 1755
// If state is active, true is returned (no requeue required).
// In all other cases, it returns false, indicating a requeue for reconciliation.
Copy link
Contributor

Choose a reason for hiding this comment

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

In general in function comments, we just add what the function does and avoid what caller will do, Since it may be called at different places, So I think we can avoid mentioning(no reque required) here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I'll only mention return values.

if lbStatus != nil {
s.SetLoadBalancerStatus(lbStatus)
// If the Load Balancer status isn't ready, flag for requeue and continue to next Load Balancer.
if isReady := s.isLoadBalancerReady(string(lbStatus.State)); !isReady {
Copy link
Contributor

Choose a reason for hiding this comment

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

why its converted to string, Why can't we use VPCLoadBalancerState instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can move things around.

return nil, nil
}
} else {
loadBalancer, err = s.VPCClient.GetLoadBalancerByName(lb.Name)
Copy link
Contributor

Choose a reason for hiding this comment

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

Even both LB id and name can be nil right, Should we add a check and log proper message?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, name cannot be nil for a LB based on the spec.

So, my assumption based on that, was if ID is not set, we expect the Name to be set.
If there is a use case to have ID == nil && Name == "", I can another check to try to use a default LB name instead.

if loadBalancer.BackendPools != nil {
for _, pool := range loadBalancer.BackendPools {
backendPool := s.buildLoadBalancerBackendPool(pool)

Copy link
Contributor

Choose a reason for hiding this comment

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

empty line can be removed.

}
options.SetListeners(listeners)

// Create the load balancer.
Copy link
Contributor

Choose a reason for hiding this comment

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

May be if required we can log the options with higher V level for easy debugging.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can add a log call with v(5) with the options object prior to creation....unless you would rather have each field logged out separately in the log call?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think logging options should be sufficient.

Comment on lines 229 to 230
if s.NetworkStatus() != nil {
if s.NetworkStatus().ControlPlaneSubnets != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if s.NetworkStatus() != nil {
if s.NetworkStatus().ControlPlaneSubnets != nil {
if s.NetworkStatus() != nil && if s.NetworkStatus().ControlPlaneSubnets != nil {

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will consolidate.

// SetLoadBalancerStatus sets the status for a Load Balancer.
func (s *VPCClusterScope) SetLoadBalancerStatus(loadBalancer *infrav1beta2.VPCLoadBalancerStatus) {
// Ignore attempts to set status without a load balancer.
if loadBalancer == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not needed IMO, since it's an internal func and you are populating loadBalancer status or doing a nil check before calling the func on both the places.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is a public function, so it could be called wherever, I can make it private and remove the check. I'd like to avoid go panics whenever possible.

if loadBalancer.Subnets != nil {
for _, subnet := range loadBalancer.Subnets {
if subnet.ID != nil {
subnetIDs = append(subnetIDs, *subnet.ID)
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't you need to verify that subnet with this id exists? Since it's coming from user spec.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, I could perform lookups to check if they exist.

if loadBalancer.SecurityGroups != nil {
for _, securityGroup := range loadBalancer.SecurityGroups {
if securityGroup.ID != nil {
securityGroupIDs = append(securityGroupIDs, *securityGroup.ID)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as previous comment on verifying user provided SG.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same, I could check if they exist.

}

// getDefaultBalancerBackendPools returns a list of default Load Balancer Backend Pools for a Load Balancer.
func (s *VPCClusterScope) getDefaultLoadBalancerBackendPools() []vpcv1.LoadBalancerPoolPrototype {
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason for creating a slice and appending one item and in caller again unpacking the slice?
Can't we return the item directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I did this if any additional pools were necessary to add to the defaults.

}

// getDefaultLoadBalancerListeners returns a list of default Load Balancer Listeners for a Load Balancer.
func (s *VPCClusterScope) getDefaultLoadBalancerListeners(defaultBackendPool bool) []vpcv1.LoadBalancerListenerPrototypeLoadBalancerContext {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment as getDefaultLoadBalancerBackendPools()'s comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as the pools, regarding additional default listeners.

@mkumatag mkumatag added this to the v0.9.0 milestone Oct 29, 2024
@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Oct 29, 2024
// NOTE(cjschaef): A webhook validation check could help ensure this.
func (s *VPCClusterScope) GetLoadBalancerHostName() (*string, error) {
// If no Status or Load Balancer Status is populated, assume the Load Balancer's are not ready (have not been reconciled), so no hostname will be available.
if s.NetworkStatus() == nil || s.NetworkStatus().LoadBalancers == nil || len(s.NetworkStatus().LoadBalancers) == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

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

we are returning nil here for len(s.NetworkStatus().LoadBalancers) and same codition returns error next step, May be we can simplify or remove if not required.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, the first set of checks looks at Status. Assuming if there is no Status or no LB status, the situation needs to wait for Status to exist or be updated.

The second check is against Spec. If Status was set, but Spec was not, that is an error. Checks are made immediately in the function to short circuit.

Copy link
Contributor

@Karthik-K-N Karthik-K-N Nov 5, 2024

Choose a reason for hiding this comment

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

apologies, thanks for the explanation, Its correct, I just overlooked spec and status.

ID: lb.ID,
}
loadBalancer, detailedResponse, err = s.VPCClient.GetLoadBalancer(getLBOptions)
if detailedResponse != nil && detailedResponse.StatusCode == http.StatusNotFound {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we can log these cases with higher level V values for easy debugging in future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I bumped to v5.

}

options.SetIsPublic(isPublic)
options.SetName(loadBalancer.Name)
Copy link
Contributor

Choose a reason for hiding this comment

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

What if loadbalancer.Name is empty?
In getLoadBalancer() function we have this condition

name := lb.Name
if name == "" {
	name = *s.GetServiceName(infrav1beta2.ResourceTypeLoadBalancer)
}

Should we do something similar here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm hesitant to create LB's with a default service name, especially if multiple LB's get defined with empty names.
Given the structure of the VPCLoadBalancerStatus, we rely on ID as a priority field. We can let VPC API generate a Name, and rely on the ID from Status.

The default service name was added by request, but I don't expect to use it for most use cases. This is likely to be refactored, likely not relying on a default name, if possible.

}
options.SetListeners(listeners)

// Create the load balancer.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think logging options should be sufficient.


// If only one Load Balancer was provided, attempt to use that Load Balancer's hostname.
if len(s.NetworkSpec().LoadBalancers) == 1 {
return getHostName(s.NetworkSpec().LoadBalancers[0])
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason for handling this special case, In case of 1 lb defined it can be private as well is this the reason?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, if a Public LB was not found, and there was only one LB, we expect it must be a Private LB, and Public traffic isn't expected to be enabled. This isn't the normal path, but still need to cover this design.


// NOTE(cjschaef): As a default Load Balancer isn't supported (when none are provided in Spec), this should result in an error.
// TODO(cjschaef): Likely would be beneficial to have webhook validation for this case.
if loadBalancer.Name == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure this check is needed here? in getLoadBalancer() we get the default name incase of name is empty.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll be refactoring, will see what makes sense.

} else {
name := lb.Name
if name == "" {
name = *s.GetServiceName(infrav1beta2.ResourceTypeLoadBalancer)
Copy link
Contributor

Choose a reason for hiding this comment

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

In case of multiple loadbalancers configured without name in spec, This may not work as expected as we end up getting same name for all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That is why we do not create an LB with the default Name (let API generate one). But that will generate its own problems.

I will need to rethink how the default service name is being used, as I don't think it makes sense to use it as requested (perhaps only in the rare case that one LB was defined in Spec). Or perhaps I can try refactoring to append public/private and hope that doesn't cause overlap (although I think that case is just as susceptible to this same situation).
Perhaps I need to add webhook validation to limit the number of LB's defined in Spec to only be 2, in additional to the refactoring. But that can be very limiting to the design and logic as well.

Copy link
Contributor

Choose a reason for hiding this comment

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

}
loadBalancer, detailedResponse, err = s.VPCClient.GetLoadBalancer(getLBOptions)
if detailedResponse != nil && detailedResponse.StatusCode == http.StatusNotFound {
return nil, nil
Copy link
Contributor

@dharaneeshvrd dharaneeshvrd Nov 5, 2024

Choose a reason for hiding this comment

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

Generally if user provides an id, we expect the resource to be exist with the id, if not we would throw an error and not proceed with the reconciliation. It's ok in case of name is provided and if resource not exist, we can create it.
Also If you return nil here and create a new load balancer, then the status and spec will have different ids.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay, I'll return an error.

Add support to reconcile VPC Load Balancers as part of the
extended VPC support.

Related: kubernetes-sigs#1896
@Karthik-K-N
Copy link
Contributor

@cjschaef Is this PR updated or tested?

Copy link
Contributor

@Karthik-K-N Karthik-K-N left a comment

Choose a reason for hiding this comment

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

/lgtm
/assign @dharaneeshvrd

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Nov 12, 2024
@dharaneeshvrd
Copy link
Contributor

/lgtm

@Karthik-K-N
Copy link
Contributor

/assign @mkumatag @Prajyot-Parab

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: cjschaef, mkumatag

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

The pull request process is described here

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

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Nov 12, 2024
@k8s-ci-robot k8s-ci-robot merged commit a71c64a into kubernetes-sigs:main Nov 12, 2024
13 checks passed
@cjschaef cjschaef deleted the vpc_reconcile_lbs branch November 12, 2024 16:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. area/provider/ibmcloud Issues or PRs related to ibmcloud provider cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. ok-to-test Indicates a non-member PR verified by an org member that is safe to test. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants