@@ -32,6 +32,7 @@ import (
3232 "github.com/gophercloud/gophercloud/v2/openstack/loadbalancer/v2/providers"
3333 . "github.com/onsi/gomega" //nolint:revive
3434 "go.uber.org/mock/gomock"
35+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3536 "k8s.io/utils/ptr"
3637 clusterv1beta1 "sigs.k8s.io/cluster-api/api/core/v1beta1"
3738
@@ -926,3 +927,252 @@ func Test_getOrCreateAPILoadBalancer(t *testing.T) {
926927 })
927928 }
928929}
930+
931+ func Test_ReconcileLoadBalancerMember (t * testing.T ) {
932+ g := NewWithT (t )
933+ mockCtrl := gomock .NewController (t )
934+ defer mockCtrl .Finish ()
935+
936+ const (
937+ clusterName = "AAAAA"
938+ clusterResourceName = "k8s-clusterapi-cluster-AAAAA"
939+ memberIP = "10.0.0.1"
940+ wrongMemberIP = "10.0.0.20"
941+ port = 6443
942+ machineName = "machine-1"
943+
944+ clusterNetID = "aaaaaaaa-bbbb-cccc-dddd-111111111111"
945+ subnetID = "aaaaaaaa-bbbb-cccc-dddd-222222222222"
946+ lbID = "aaaaaaaa-bbbb-cccc-dddd-333333333333"
947+ listenerID = "aaaaaaaa-bbbb-cccc-dddd-444444444444"
948+ poolID = "aaaaaaaa-bbbb-cccc-dddd-555555555555"
949+ memberID = "aaaaaaaa-bbbb-cccc-dddd-666666666666"
950+ lbNetOtherID = "aaaaaaaa-bbbb-cccc-dddd-999999999999"
951+ )
952+
953+ makeCluster := func (provider * string , lbNetworkID string ) * infrav1.OpenStackCluster {
954+ return & infrav1.OpenStackCluster {
955+ Spec : infrav1.OpenStackClusterSpec {
956+ APIServerLoadBalancer : & infrav1.APIServerLoadBalancer {
957+ Enabled : ptr .To (true ),
958+ Provider : provider ,
959+ Network : & infrav1.NetworkParam {
960+ ID : & lbNetworkID ,
961+ },
962+ },
963+ DisableAPIServerFloatingIP : ptr .To (true ),
964+ ControlPlaneEndpoint : & clusterv1beta1.APIEndpoint {
965+ Host : apiHostname ,
966+ Port : port ,
967+ },
968+ Tags : []string {"k8s" , "clusterapi" },
969+ },
970+ Status : infrav1.OpenStackClusterStatus {
971+ APIServerLoadBalancer : & infrav1.LoadBalancer {
972+ ID : lbID ,
973+ LoadBalancerNetwork : & infrav1.NetworkStatusWithSubnets {
974+ NetworkStatus : infrav1.NetworkStatus {
975+ ID : lbNetworkID ,
976+ },
977+ },
978+ },
979+ Network : & infrav1.NetworkStatusWithSubnets {
980+ NetworkStatus : infrav1.NetworkStatus {
981+ ID : clusterNetID ,
982+ },
983+ Subnets : []infrav1.Subnet {
984+ {ID : subnetID },
985+ },
986+ },
987+ },
988+ }
989+ }
990+
991+ openStackMachine := & infrav1.OpenStackMachine {
992+ ObjectMeta : metav1.ObjectMeta {Name : machineName },
993+ }
994+
995+ lbtests := []struct {
996+ name string
997+ clusterSpec * infrav1.OpenStackCluster
998+ expectNetwork func (m * mock.MockNetworkClientMockRecorder )
999+ expectLoadBalancer func (m * mock.MockLbClientMockRecorder )
1000+ wantError error
1001+ }{
1002+ {
1003+ name : "LB member exists, dont create" ,
1004+ clusterSpec : makeCluster (nil , clusterNetID ),
1005+ expectNetwork : func (* mock.MockNetworkClientMockRecorder ) {},
1006+ expectLoadBalancer : func (m * mock.MockLbClientMockRecorder ) {
1007+ activeLB := loadbalancers.LoadBalancer {
1008+ ID : lbID ,
1009+ Name : clusterResourceName + "-kubeapi" ,
1010+ ProvisioningStatus : "ACTIVE" ,
1011+ }
1012+ m .GetLoadBalancer (lbID ).Return (& activeLB , nil ).AnyTimes ()
1013+
1014+ pool := pools.Pool {
1015+ ID : poolID ,
1016+ Name : fmt .Sprintf ("%s-kubeapi-%d" , clusterResourceName , port ),
1017+ }
1018+ m .ListPools (pools.ListOpts {Name : pool .Name }).Return ([]pools.Pool {pool }, nil )
1019+
1020+ member := pools.Member {
1021+ Name : fmt .Sprintf ("%s-kubeapi-%d-%s" , clusterResourceName , port , machineName ),
1022+ Address : memberIP ,
1023+ }
1024+ m .ListPoolMember (poolID , pools.ListMembersOpts {Name : member .Name }).Return ([]pools.Member {member }, nil )
1025+ },
1026+ wantError : nil ,
1027+ },
1028+ {
1029+ name : "No LB member, create" ,
1030+ clusterSpec : makeCluster (nil , clusterNetID ),
1031+ expectNetwork : func (* mock.MockNetworkClientMockRecorder ) {},
1032+ expectLoadBalancer : func (m * mock.MockLbClientMockRecorder ) {
1033+ activeLB := loadbalancers.LoadBalancer {
1034+ ID : lbID ,
1035+ Name : clusterResourceName + "-kubeapi" ,
1036+ ProvisioningStatus : "ACTIVE" ,
1037+ }
1038+ m .GetLoadBalancer (lbID ).Return (& activeLB , nil ).AnyTimes ()
1039+
1040+ pool := pools.Pool {
1041+ ID : poolID ,
1042+ Name : fmt .Sprintf ("%s-kubeapi-%d" , clusterResourceName , port ),
1043+ }
1044+ m .ListPools (pools.ListOpts {Name : pool .Name }).Return ([]pools.Pool {pool }, nil )
1045+
1046+ poolMemberName := fmt .Sprintf ("%s-kubeapi-%d-%s" , clusterResourceName , port , machineName )
1047+ m .ListPoolMember (poolID , pools.ListMembersOpts {Name : poolMemberName }).Return ([]pools.Member {}, nil )
1048+
1049+ m .CreatePoolMember (
1050+ poolID ,
1051+ gomock .AssignableToTypeOf (pools.CreateMemberOpts {}),
1052+ ).DoAndReturn (func (_ string , got pools.CreateMemberOpts ) (* pools.Member , error ) {
1053+ // SubnetID must be empty here
1054+ g .Expect (got .SubnetID ).To (Equal ("" ))
1055+ return & pools.Member {ID : "member-2" }, nil
1056+ })
1057+ },
1058+ wantError : nil ,
1059+ },
1060+ {
1061+ name : "No pool found, return error" ,
1062+ clusterSpec : makeCluster (nil , clusterNetID ),
1063+ expectNetwork : func (* mock.MockNetworkClientMockRecorder ) {},
1064+ expectLoadBalancer : func (m * mock.MockLbClientMockRecorder ) {
1065+ activeLB := loadbalancers.LoadBalancer {
1066+ ID : lbID ,
1067+ Name : clusterResourceName + "-kubeapi" ,
1068+ ProvisioningStatus : "ACTIVE" ,
1069+ }
1070+ m .GetLoadBalancer (lbID ).Return (& activeLB , nil ).AnyTimes ()
1071+
1072+ poolName := fmt .Sprintf ("%s-kubeapi-%d" , clusterResourceName , port )
1073+ m .ListPools (pools.ListOpts {Name : poolName }).Return ([]pools.Pool {}, nil )
1074+ },
1075+ wantError : errors .New ("load balancer pool does not exist yet" ),
1076+ },
1077+ {
1078+ name : "LB member with wrong address, re-create" ,
1079+ clusterSpec : makeCluster (nil , clusterNetID ),
1080+ expectNetwork : func (* mock.MockNetworkClientMockRecorder ) {},
1081+ expectLoadBalancer : func (m * mock.MockLbClientMockRecorder ) {
1082+ activeLB := loadbalancers.LoadBalancer {
1083+ ID : lbID ,
1084+ Name : clusterResourceName + "-kubeapi" ,
1085+ ProvisioningStatus : "ACTIVE" ,
1086+ }
1087+ m .GetLoadBalancer (lbID ).Return (& activeLB , nil ).AnyTimes ()
1088+
1089+ pool := pools.Pool {
1090+ ID : poolID ,
1091+ Name : fmt .Sprintf ("%s-kubeapi-%d" , clusterResourceName , port ),
1092+ }
1093+ m .ListPools (pools.ListOpts {Name : pool .Name }).Return ([]pools.Pool {pool }, nil )
1094+
1095+ member := pools.Member {
1096+ Name : fmt .Sprintf ("%s-kubeapi-%d-%s" , clusterResourceName , port , machineName ),
1097+ Address : wrongMemberIP ,
1098+ ID : memberID ,
1099+ }
1100+ m .ListPoolMember (poolID , pools.ListMembersOpts {Name : member .Name }).Return ([]pools.Member {member }, nil )
1101+
1102+ m .DeletePoolMember (poolID , memberID ).Return (nil )
1103+
1104+ m .CreatePoolMember (
1105+ poolID ,
1106+ gomock .AssignableToTypeOf (pools.CreateMemberOpts {}),
1107+ ).DoAndReturn (func (_ string , got pools.CreateMemberOpts ) (* pools.Member , error ) {
1108+ // SubnetID must be empty here
1109+ g .Expect (got .SubnetID ).To (Equal ("" ))
1110+ return & pools.Member {ID : "member-2" }, nil
1111+ })
1112+ },
1113+ wantError : nil ,
1114+ },
1115+ {
1116+ name : "different LB and cluster networks, set SubnetID on member create" ,
1117+ clusterSpec : makeCluster (nil , lbNetOtherID ),
1118+ expectNetwork : func (* mock.MockNetworkClientMockRecorder ) {
1119+ // not used by this path
1120+ },
1121+ expectLoadBalancer : func (m * mock.MockLbClientMockRecorder ) {
1122+ // LB initially ACTIVE whenever we wait
1123+ activeLB := loadbalancers.LoadBalancer {
1124+ ID : lbID ,
1125+ Name : clusterResourceName + "-kubeapi" ,
1126+ ProvisioningStatus : "ACTIVE" ,
1127+ }
1128+ m .GetLoadBalancer (lbID ).Return (& activeLB , nil ).AnyTimes ()
1129+
1130+ pool := pools.Pool {
1131+ ID : poolID ,
1132+ Name : fmt .Sprintf ("%s-kubeapi-%d" , clusterResourceName , port ),
1133+ }
1134+ m .ListPools (pools.ListOpts {Name : pool .Name }).Return ([]pools.Pool {pool }, nil )
1135+
1136+ memberName := fmt .Sprintf ("%s-kubeapi-%d-%s" , clusterResourceName , port , machineName )
1137+ m .ListPoolMember (poolID , pools.ListMembersOpts {Name : memberName }).Return ([]pools.Member {}, nil )
1138+
1139+ // Expect CreatePoolMember; capture opts to assert SubnetID is set
1140+ m .CreatePoolMember (
1141+ poolID ,
1142+ gomock .AssignableToTypeOf (pools.CreateMemberOpts {}),
1143+ ).DoAndReturn (func (_ string , got pools.CreateMemberOpts ) (* pools.Member , error ) {
1144+ g .Expect (got .Address ).To (Equal (memberIP ))
1145+ g .Expect (got .ProtocolPort ).To (Equal (port ))
1146+ expName := fmt .Sprintf ("%s-kubeapi-%d-%s" , clusterResourceName , port , machineName )
1147+ g .Expect (got .Name ).To (Equal (expName ))
1148+ g .Expect (got .SubnetID ).To (Equal (subnetID ))
1149+ // Tags should pass through
1150+ g .Expect (got .Tags ).To (ConsistOf ("k8s" , "clusterapi" ))
1151+ return & pools.Member {ID : "member-1" , Address : memberIP , ProtocolPort : port }, nil
1152+ })
1153+ },
1154+ wantError : nil ,
1155+ },
1156+ }
1157+
1158+ for _ , tt := range lbtests {
1159+ t .Run (tt .name , func (t * testing.T ) {
1160+ g := NewWithT (t )
1161+ log := testr .New (t )
1162+
1163+ mockScopeFactory := scope .NewMockScopeFactory (mockCtrl , "" )
1164+ lbs , err := NewService (scope .NewWithLogger (mockScopeFactory , log ))
1165+ g .Expect (err ).NotTo (HaveOccurred ())
1166+
1167+ tt .expectNetwork (mockScopeFactory .NetworkClient .EXPECT ())
1168+ tt .expectLoadBalancer (mockScopeFactory .LbClient .EXPECT ())
1169+
1170+ err = lbs .ReconcileLoadBalancerMember (tt .clusterSpec , openStackMachine , clusterName , memberIP )
1171+ if tt .wantError != nil {
1172+ g .Expect (err ).To (MatchError (tt .wantError ))
1173+ } else {
1174+ g .Expect (err ).NotTo (HaveOccurred ())
1175+ }
1176+ })
1177+ }
1178+ }
0 commit comments