Skip to content

Commit a405eeb

Browse files
authored
Merge pull request #1905 from shiftstack/reconcileNetworkComponents
🌱 Reduce cyclomatic complexity of reconcileNetworkComponents
2 parents 0c5f230 + 268645a commit a405eeb

File tree

2 files changed

+205
-142
lines changed

2 files changed

+205
-142
lines changed

controllers/openstackcluster_controller.go

Lines changed: 160 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -602,81 +602,123 @@ func reconcileNetworkComponents(scope *scope.WithLogger, cluster *clusterv1.Clus
602602
}
603603

604604
if len(openStackCluster.Spec.ManagedSubnets) == 0 {
605-
scope.Logger().V(4).Info("No need to reconcile network, searching network and subnet instead")
606-
607-
if openStackCluster.Status.Network == nil {
608-
openStackCluster.Status.Network = &infrav1.NetworkStatusWithSubnets{}
609-
}
610-
611-
err := getCAPONetwork(openStackCluster, networkingService)
612-
if err != nil {
605+
if err := reconcilePreExistingNetworkComponents(scope, networkingService, openStackCluster); err != nil {
613606
return err
614607
}
615-
616-
filteredSubnets, err := filterSubnets(networkingService, openStackCluster)
617-
if err != nil {
608+
} else if len(openStackCluster.Spec.ManagedSubnets) == 1 {
609+
if err := reconcileProvisionedNetworkComponents(networkingService, openStackCluster, clusterName); err != nil {
618610
return err
619611
}
612+
} else {
613+
return fmt.Errorf("failed to reconcile network: ManagedSubnets only supports one element, %d provided", len(openStackCluster.Spec.ManagedSubnets))
614+
}
620615

621-
var subnets []infrav1.Subnet
622-
for subnet := range filteredSubnets {
623-
filterSubnet := &filteredSubnets[subnet]
624-
subnets = append(subnets, infrav1.Subnet{
625-
ID: filterSubnet.ID,
626-
Name: filterSubnet.Name,
627-
CIDR: filterSubnet.CIDR,
628-
Tags: filterSubnet.Tags,
629-
})
630-
}
616+
err = networkingService.ReconcileSecurityGroups(openStackCluster, clusterName)
617+
if err != nil {
618+
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile security groups: %w", err))
619+
return fmt.Errorf("failed to reconcile security groups: %w", err)
620+
}
631621

632-
if err := utils.ValidateSubnets(subnets); err != nil {
633-
return err
634-
}
635-
openStackCluster.Status.Network.Subnets = subnets
622+
return reconcileControlPlaneEndpoint(scope, networkingService, openStackCluster, clusterName)
623+
}
636624

637-
// If network is not yet populated on the Status, use networkID defined in the filtered subnets to get the Network.
638-
err = populateCAPONetworkFromSubnet(networkingService, filteredSubnets, openStackCluster)
625+
// reconcilePreExistingNetworkComponents reconciles the cluster network status when the cluster is
626+
// using pre-existing networks and subnets which are not provisioned by the
627+
// cluster controller.
628+
func reconcilePreExistingNetworkComponents(scope *scope.WithLogger, networkingService *networking.Service, openStackCluster *infrav1.OpenStackCluster) error {
629+
scope.Logger().V(4).Info("No need to reconcile network, searching network and subnet instead")
630+
631+
if openStackCluster.Status.Network == nil {
632+
openStackCluster.Status.Network = &infrav1.NetworkStatusWithSubnets{}
633+
}
634+
635+
emptyNetwork := infrav1.NetworkFilter{}
636+
if openStackCluster.Spec.Network != emptyNetwork {
637+
netOpts := openStackCluster.Spec.Network.ToListOpt()
638+
networkList, err := networkingService.GetNetworksByFilter(&netOpts)
639639
if err != nil {
640-
return err
640+
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find network: %w", err))
641+
return fmt.Errorf("error fetching networks: %w", err)
641642
}
642-
} else if len(openStackCluster.Spec.ManagedSubnets) == 1 {
643-
err := networkingService.ReconcileNetwork(openStackCluster, clusterName)
644-
if err != nil {
645-
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile network: %w", err))
646-
return fmt.Errorf("failed to reconcile network: %w", err)
643+
if len(networkList) == 0 {
644+
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find any network"))
645+
return fmt.Errorf("failed to find any network")
647646
}
648-
err = networkingService.ReconcileSubnet(openStackCluster, clusterName)
649-
if err != nil {
650-
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile subnets: %w", err))
651-
return fmt.Errorf("failed to reconcile subnets: %w", err)
647+
if len(networkList) == 1 {
648+
setClusterNetwork(openStackCluster, &networkList[0])
649+
}
650+
}
651+
652+
subnets, err := getClusterSubnets(networkingService, openStackCluster)
653+
if err != nil {
654+
return err
655+
}
656+
657+
// Populate the cluster status with the cluster subnets
658+
capoSubnets := make([]infrav1.Subnet, len(subnets))
659+
for i := range subnets {
660+
subnet := &subnets[i]
661+
capoSubnets[i] = infrav1.Subnet{
662+
ID: subnet.ID,
663+
Name: subnet.Name,
664+
CIDR: subnet.CIDR,
665+
Tags: subnet.Tags,
652666
}
653-
err = networkingService.ReconcileRouter(openStackCluster, clusterName)
667+
}
668+
if err := utils.ValidateSubnets(capoSubnets); err != nil {
669+
return err
670+
}
671+
openStackCluster.Status.Network.Subnets = capoSubnets
672+
673+
// If network is not yet populated, use networkID defined on the first
674+
// cluster subnet to get the Network. Cluster subnets are constrained to
675+
// be in the same network.
676+
if openStackCluster.Status.Network.ID == "" && len(subnets) > 0 {
677+
network, err := networkingService.GetNetworkByID(subnets[0].NetworkID)
654678
if err != nil {
655-
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile router: %w", err))
656-
return fmt.Errorf("failed to reconcile router: %w", err)
679+
return err
657680
}
658-
} else {
659-
return fmt.Errorf("failed to reconcile network: ManagedSubnets only supports one element, %d provided", len(openStackCluster.Spec.ManagedSubnets))
681+
setClusterNetwork(openStackCluster, network)
660682
}
661683

662-
err = networkingService.ReconcileSecurityGroups(openStackCluster, clusterName)
684+
return nil
685+
}
686+
687+
func reconcileProvisionedNetworkComponents(networkingService *networking.Service, openStackCluster *infrav1.OpenStackCluster, clusterName string) error {
688+
err := networkingService.ReconcileNetwork(openStackCluster, clusterName)
663689
if err != nil {
664-
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile security groups: %w", err))
665-
return fmt.Errorf("failed to reconcile security groups: %w", err)
690+
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile network: %w", err))
691+
return fmt.Errorf("failed to reconcile network: %w", err)
692+
}
693+
err = networkingService.ReconcileSubnet(openStackCluster, clusterName)
694+
if err != nil {
695+
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile subnets: %w", err))
696+
return fmt.Errorf("failed to reconcile subnets: %w", err)
697+
}
698+
err = networkingService.ReconcileRouter(openStackCluster, clusterName)
699+
if err != nil {
700+
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to reconcile router: %w", err))
701+
return fmt.Errorf("failed to reconcile router: %w", err)
666702
}
667703

704+
return nil
705+
}
706+
707+
// reconcileControlPlaneEndpoint configures the control plane endpoint for the
708+
// cluster, creating it if necessary, and updates ControlPlaneEndpoint in the
709+
// cluster spec.
710+
func reconcileControlPlaneEndpoint(scope *scope.WithLogger, networkingService *networking.Service, openStackCluster *infrav1.OpenStackCluster, clusterName string) error {
668711
// Calculate the port that we will use for the API server
669-
var apiServerPort int
670-
switch {
671-
case openStackCluster.Spec.ControlPlaneEndpoint.IsValid():
672-
apiServerPort = int(openStackCluster.Spec.ControlPlaneEndpoint.Port)
673-
case openStackCluster.Spec.APIServerPort != 0:
674-
apiServerPort = openStackCluster.Spec.APIServerPort
675-
default:
676-
apiServerPort = 6443
677-
}
712+
apiServerPort := getAPIServerPort(openStackCluster)
678713

679-
if openStackCluster.Spec.APIServerLoadBalancer.Enabled {
714+
// host must be set by a matching control plane endpoint provider below
715+
var host string
716+
717+
switch {
718+
// API server load balancer is enabled. Create an Octavia load balancer.
719+
// Note that we reconcile the load balancer even if the control plane
720+
// endpoint is already set.
721+
case openStackCluster.Spec.APIServerLoadBalancer.Enabled:
680722
loadBalancerService, err := loadbalancer.NewService(scope)
681723
if err != nil {
682724
return err
@@ -690,49 +732,63 @@ func reconcileNetworkComponents(scope *scope.WithLogger, cluster *clusterv1.Clus
690732
}
691733
return fmt.Errorf("failed to reconcile load balancer: %w", err)
692734
}
693-
}
694735

695-
if !openStackCluster.Spec.ControlPlaneEndpoint.IsValid() {
696-
var host string
697-
// If there is a load balancer use the floating IP for it if set, falling back to the internal IP
698-
switch {
699-
case openStackCluster.Spec.APIServerLoadBalancer.Enabled:
700-
if openStackCluster.Status.APIServerLoadBalancer.IP != "" {
701-
host = openStackCluster.Status.APIServerLoadBalancer.IP
702-
} else {
703-
host = openStackCluster.Status.APIServerLoadBalancer.InternalIP
704-
}
705-
case !openStackCluster.Spec.DisableAPIServerFloatingIP:
706-
// If floating IPs are not disabled, get one to use as the VIP for the control plane
707-
fp, err := networkingService.GetOrCreateFloatingIP(openStackCluster, openStackCluster, clusterName, openStackCluster.Spec.APIServerFloatingIP)
708-
if err != nil {
709-
handleUpdateOSCError(openStackCluster, fmt.Errorf("floating IP cannot be got or created: %w", err))
710-
return fmt.Errorf("floating IP cannot be got or created: %w", err)
711-
}
712-
host = fp.FloatingIP
713-
case openStackCluster.Spec.APIServerFixedIP != "":
714-
// If a fixed IP was specified, assume that the user is providing the extra configuration
715-
// to use that IP as the VIP for the API server, e.g. using keepalived or kube-vip
716-
host = openStackCluster.Spec.APIServerFixedIP
717-
default:
718-
// For now, we do not provide a managed VIP without either a load balancer or a floating IP
719-
// In the future, we could manage a VIP port on the cluster network and set allowedAddressPairs
720-
// accordingly when creating control plane machines
721-
// However this would require us to deploy software on the control plane hosts to manage the
722-
// VIP (e.g. keepalived/kube-vip)
723-
return fmt.Errorf("unable to determine VIP for API server")
736+
// Control plane endpoint is the floating IP if one was defined, otherwise the VIP address
737+
if openStackCluster.Status.APIServerLoadBalancer.IP != "" {
738+
host = openStackCluster.Status.APIServerLoadBalancer.IP
739+
} else {
740+
host = openStackCluster.Status.APIServerLoadBalancer.InternalIP
724741
}
725742

726-
// Set APIEndpoints so the Cluster API Cluster Controller can pull them
727-
openStackCluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{
728-
Host: host,
729-
Port: int32(apiServerPort),
743+
// Control plane endpoint is already set
744+
// Note that checking this here means that we don't re-execute any of
745+
// the branches below if the control plane endpoint is already set.
746+
case openStackCluster.Spec.ControlPlaneEndpoint.IsValid():
747+
host = openStackCluster.Spec.ControlPlaneEndpoint.Host
748+
749+
// API server load balancer is disabled, but floating IP is not. Create
750+
// a floating IP to be attached directly to a control plane host.
751+
case !openStackCluster.Spec.DisableAPIServerFloatingIP:
752+
fp, err := networkingService.GetOrCreateFloatingIP(openStackCluster, openStackCluster, clusterName, openStackCluster.Spec.APIServerFloatingIP)
753+
if err != nil {
754+
handleUpdateOSCError(openStackCluster, fmt.Errorf("floating IP cannot be got or created: %w", err))
755+
return fmt.Errorf("floating IP cannot be got or created: %w", err)
730756
}
757+
host = fp.FloatingIP
758+
759+
// API server load balancer is disabled and we aren't using a control
760+
// plane floating IP. In this case we configure APIServerFixedIP as the
761+
// control plane endpoint and leave it to the user to configure load
762+
// balancing.
763+
case openStackCluster.Spec.APIServerFixedIP != "":
764+
host = openStackCluster.Spec.APIServerFixedIP
765+
766+
// Control plane endpoint is not set, and none can be created
767+
default:
768+
err := fmt.Errorf("unable to determine control plane endpoint")
769+
handleUpdateOSCError(openStackCluster, err)
770+
return err
771+
}
772+
773+
openStackCluster.Spec.ControlPlaneEndpoint = clusterv1.APIEndpoint{
774+
Host: host,
775+
Port: int32(apiServerPort),
731776
}
732777

733778
return nil
734779
}
735780

781+
// getAPIServerPort returns the port to use for the API server based on the cluster spec.
782+
func getAPIServerPort(openStackCluster *infrav1.OpenStackCluster) int {
783+
switch {
784+
case openStackCluster.Spec.ControlPlaneEndpoint.IsValid():
785+
return int(openStackCluster.Spec.ControlPlaneEndpoint.Port)
786+
case openStackCluster.Spec.APIServerPort != 0:
787+
return openStackCluster.Spec.APIServerPort
788+
}
789+
return 6443
790+
}
791+
736792
func (r *OpenStackClusterReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager, options controller.Options) error {
737793
clusterToInfraFn := util.ClusterToInfrastructureMapFunc(ctx, infrav1.GroupVersion.WithKind("OpenStackCluster"), mgr.GetClient(), &infrav1.OpenStackCluster{})
738794
log := ctrl.LoggerFrom(ctx)
@@ -788,9 +844,9 @@ func handleUpdateOSCError(openstackCluster *infrav1.OpenStackCluster, message er
788844
openstackCluster.Status.FailureMessage = pointer.String(message.Error())
789845
}
790846

791-
// filterSubnets retrieves the subnets based on the Subnet filters specified on OpenstackCluster.
792-
func filterSubnets(networkingService *networking.Service, openStackCluster *infrav1.OpenStackCluster) ([]subnets.Subnet, error) {
793-
var filteredSubnets []subnets.Subnet
847+
// getClusterSubnets retrieves the subnets based on the Subnet filters specified on OpenstackCluster.
848+
func getClusterSubnets(networkingService *networking.Service, openStackCluster *infrav1.OpenStackCluster) ([]subnets.Subnet, error) {
849+
var clusterSubnets []subnets.Subnet
794850
var err error
795851
openStackClusterSubnets := openStackCluster.Spec.Subnets
796852
networkID := ""
@@ -800,20 +856,22 @@ func filterSubnets(networkingService *networking.Service, openStackCluster *infr
800856

801857
if len(openStackClusterSubnets) == 0 {
802858
if networkID == "" {
803-
return nil, nil
859+
// This should be a validation error
860+
return nil, fmt.Errorf("no network or subnets specified in OpenStackCluster spec")
804861
}
862+
805863
empty := &infrav1.SubnetFilter{}
806864
listOpt := empty.ToListOpt()
807865
listOpt.NetworkID = networkID
808-
filteredSubnets, err = networkingService.GetSubnetsByFilter(listOpt)
866+
clusterSubnets, err = networkingService.GetSubnetsByFilter(listOpt)
809867
if err != nil {
810868
err = fmt.Errorf("failed to find subnets: %w", err)
811869
if errors.Is(err, networking.ErrFilterMatch) {
812870
handleUpdateOSCError(openStackCluster, err)
813871
}
814872
return nil, err
815873
}
816-
if len(filteredSubnets) > 2 {
874+
if len(clusterSubnets) > 2 {
817875
return nil, fmt.Errorf("more than two subnets found in the Network. Specify the subnets in the OpenStackCluster.Spec instead")
818876
}
819877
} else {
@@ -826,55 +884,18 @@ func filterSubnets(networkingService *networking.Service, openStackCluster *infr
826884
}
827885
return nil, err
828886
}
829-
filteredSubnets = append(filteredSubnets, *filteredSubnet)
887+
clusterSubnets = append(clusterSubnets, *filteredSubnet)
888+
889+
// Constrain the next search to the network of the first subnet
890+
networkID = filteredSubnet.NetworkID
830891
}
831892
}
832-
return filteredSubnets, nil
893+
return clusterSubnets, nil
833894
}
834895

835-
// convertOpenStackNetworkToCAPONetwork converts an OpenStack network to a capo network.
836-
// It returns the converted subnet.
837-
func convertOpenStackNetworkToCAPONetwork(openStackCluster *infrav1.OpenStackCluster, network *networks.Network) {
896+
// setClusterNetwork sets network information in the cluster status from an OpenStack network.
897+
func setClusterNetwork(openStackCluster *infrav1.OpenStackCluster, network *networks.Network) {
838898
openStackCluster.Status.Network.ID = network.ID
839899
openStackCluster.Status.Network.Name = network.Name
840900
openStackCluster.Status.Network.Tags = network.Tags
841901
}
842-
843-
// populateCAPONetworkFromSubnet gets a network based on the networkID of the subnets and converts it to the CAPO format.
844-
// It returns an error in case it failed to retrieve the network.
845-
func populateCAPONetworkFromSubnet(networkingService *networking.Service, subnets []subnets.Subnet, openStackCluster *infrav1.OpenStackCluster) error {
846-
if openStackCluster.Status.Network.ID == "" && len(subnets) > 0 {
847-
if len(subnets) > 1 && subnets[0].NetworkID != subnets[1].NetworkID {
848-
return fmt.Errorf("unable to identify the network to use. NetworkID %s from subnet %s does not match NetworkID %s from subnet %s", subnets[0].NetworkID, subnets[0].ID, subnets[1].NetworkID, subnets[1].ID)
849-
}
850-
851-
network, err := networkingService.GetNetworkByID(subnets[0].NetworkID)
852-
if err != nil {
853-
return err
854-
}
855-
convertOpenStackNetworkToCAPONetwork(openStackCluster, network)
856-
}
857-
return nil
858-
}
859-
860-
// getCAPONetwork gets a network based on a filter, if defined, and convert the network to the CAPO format.
861-
// It returns an error in case it failed to retrieve the network.
862-
func getCAPONetwork(openStackCluster *infrav1.OpenStackCluster, networkingService *networking.Service) error {
863-
emptyNetwork := infrav1.NetworkFilter{}
864-
if openStackCluster.Spec.Network != emptyNetwork {
865-
netOpts := openStackCluster.Spec.Network.ToListOpt()
866-
networkList, err := networkingService.GetNetworksByFilter(&netOpts)
867-
if err != nil {
868-
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find network: %w", err))
869-
return fmt.Errorf("failed to find network: %w", err)
870-
}
871-
if len(networkList) == 0 {
872-
handleUpdateOSCError(openStackCluster, fmt.Errorf("failed to find any network"))
873-
return fmt.Errorf("failed to find any network")
874-
}
875-
if len(networkList) == 1 {
876-
convertOpenStackNetworkToCAPONetwork(openStackCluster, &networkList[0])
877-
}
878-
}
879-
return nil
880-
}

0 commit comments

Comments
 (0)