@@ -602,81 +602,123 @@ func reconcileNetworkComponents(scope *scope.WithLogger, cluster *clusterv1.Clus
602
602
}
603
603
604
604
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 {
613
606
return err
614
607
}
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 {
618
610
return err
619
611
}
612
+ } else {
613
+ return fmt .Errorf ("failed to reconcile network: ManagedSubnets only supports one element, %d provided" , len (openStackCluster .Spec .ManagedSubnets ))
614
+ }
620
615
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
+ }
631
621
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
+ }
636
624
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 )
639
639
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 )
641
642
}
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" )
647
646
}
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 ,
652
666
}
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 )
654
678
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
657
680
}
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 )
660
682
}
661
683
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 )
663
689
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 )
666
702
}
667
703
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 {
668
711
// 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 )
678
713
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 :
680
722
loadBalancerService , err := loadbalancer .NewService (scope )
681
723
if err != nil {
682
724
return err
@@ -690,49 +732,63 @@ func reconcileNetworkComponents(scope *scope.WithLogger, cluster *clusterv1.Clus
690
732
}
691
733
return fmt .Errorf ("failed to reconcile load balancer: %w" , err )
692
734
}
693
- }
694
735
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
724
741
}
725
742
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 )
730
756
}
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 ),
731
776
}
732
777
733
778
return nil
734
779
}
735
780
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
+
736
792
func (r * OpenStackClusterReconciler ) SetupWithManager (ctx context.Context , mgr ctrl.Manager , options controller.Options ) error {
737
793
clusterToInfraFn := util .ClusterToInfrastructureMapFunc (ctx , infrav1 .GroupVersion .WithKind ("OpenStackCluster" ), mgr .GetClient (), & infrav1.OpenStackCluster {})
738
794
log := ctrl .LoggerFrom (ctx )
@@ -788,9 +844,9 @@ func handleUpdateOSCError(openstackCluster *infrav1.OpenStackCluster, message er
788
844
openstackCluster .Status .FailureMessage = pointer .String (message .Error ())
789
845
}
790
846
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
794
850
var err error
795
851
openStackClusterSubnets := openStackCluster .Spec .Subnets
796
852
networkID := ""
@@ -800,20 +856,22 @@ func filterSubnets(networkingService *networking.Service, openStackCluster *infr
800
856
801
857
if len (openStackClusterSubnets ) == 0 {
802
858
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" )
804
861
}
862
+
805
863
empty := & infrav1.SubnetFilter {}
806
864
listOpt := empty .ToListOpt ()
807
865
listOpt .NetworkID = networkID
808
- filteredSubnets , err = networkingService .GetSubnetsByFilter (listOpt )
866
+ clusterSubnets , err = networkingService .GetSubnetsByFilter (listOpt )
809
867
if err != nil {
810
868
err = fmt .Errorf ("failed to find subnets: %w" , err )
811
869
if errors .Is (err , networking .ErrFilterMatch ) {
812
870
handleUpdateOSCError (openStackCluster , err )
813
871
}
814
872
return nil , err
815
873
}
816
- if len (filteredSubnets ) > 2 {
874
+ if len (clusterSubnets ) > 2 {
817
875
return nil , fmt .Errorf ("more than two subnets found in the Network. Specify the subnets in the OpenStackCluster.Spec instead" )
818
876
}
819
877
} else {
@@ -826,55 +884,18 @@ func filterSubnets(networkingService *networking.Service, openStackCluster *infr
826
884
}
827
885
return nil , err
828
886
}
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
830
891
}
831
892
}
832
- return filteredSubnets , nil
893
+ return clusterSubnets , nil
833
894
}
834
895
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 ) {
838
898
openStackCluster .Status .Network .ID = network .ID
839
899
openStackCluster .Status .Network .Name = network .Name
840
900
openStackCluster .Status .Network .Tags = network .Tags
841
901
}
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