@@ -17,20 +17,29 @@ limitations under the License.
17
17
package controllers
18
18
19
19
import (
20
+ "context"
21
+ "testing"
20
22
"time"
21
23
24
+ "github.com/go-logr/logr"
22
25
. "github.com/onsi/ginkgo/v2"
23
26
. "github.com/onsi/gomega"
24
27
"github.com/stretchr/testify/mock"
28
+ "github.com/stretchr/testify/require"
25
29
"github.com/syself/hrobot-go/models"
26
30
corev1 "k8s.io/api/core/v1"
27
31
apierrors "k8s.io/apimachinery/pkg/api/errors"
28
32
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33
+ "k8s.io/apimachinery/pkg/runtime"
29
34
"k8s.io/apimachinery/pkg/selection"
35
+ "k8s.io/apimachinery/pkg/types"
36
+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
30
37
"k8s.io/utils/ptr"
31
38
clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
32
39
"sigs.k8s.io/cluster-api/util/patch"
33
40
"sigs.k8s.io/controller-runtime/pkg/client"
41
+ fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake"
42
+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
34
43
35
44
infrav1 "github.com/syself/cluster-api-provider-hetzner/api/v1beta1"
36
45
"github.com/syself/cluster-api-provider-hetzner/pkg/services/baremetal/baremetal"
@@ -825,4 +834,262 @@ var _ = Describe("HetznerBareMetalMachineReconciler", func() {
825
834
})
826
835
})
827
836
})
837
+
838
+ Context ("Test EnqueueRequestsFromMapFunc BareMetalHostToBareMetalMachines" , func () {
839
+ It ("should enqueue the second HetznerBareMetalMachine, when the first gets deleted" , func () {
840
+ // We test this part in BareMetalHostToBareMetalMachines:
841
+ // We have a free host. Trigger a matching HetznerBareMetalMachine to be reconciled.
842
+ capiMachine := & clusterv1.Machine {
843
+ ObjectMeta : metav1.ObjectMeta {
844
+ GenerateName : "capi-machine-" ,
845
+ Namespace : testNs .Name ,
846
+ Finalizers : []string {clusterv1 .MachineFinalizer },
847
+ Labels : map [string ]string {
848
+ clusterv1 .ClusterNameLabel : capiCluster .Name ,
849
+ },
850
+ },
851
+ Spec : clusterv1.MachineSpec {
852
+ ClusterName : capiCluster .Name ,
853
+ InfrastructureRef : corev1.ObjectReference {
854
+ APIVersion : "infrastructure.cluster.x-k8s.io/v1beta1" ,
855
+ Kind : "HetznerBareMetalMachine" ,
856
+ Name : bmMachineName ,
857
+ },
858
+ FailureDomain : & defaultFailureDomain ,
859
+ Bootstrap : clusterv1.Bootstrap {
860
+ DataSecretName : ptr .To ("bootstrap-secret" ),
861
+ },
862
+ },
863
+ }
864
+ Expect (testEnv .Create (ctx , capiMachine )).To (Succeed ())
865
+
866
+ bmMachine = & infrav1.HetznerBareMetalMachine {
867
+ ObjectMeta : metav1.ObjectMeta {
868
+ Name : bmMachineName ,
869
+ Namespace : testNs .Name ,
870
+ Labels : map [string ]string {
871
+ clusterv1 .ClusterNameLabel : capiCluster .Name ,
872
+ },
873
+ OwnerReferences : []metav1.OwnerReference {
874
+ {
875
+ APIVersion : "cluster.x-k8s.io/v1beta1" ,
876
+ Kind : "Machine" ,
877
+ Name : capiMachine .Name ,
878
+ UID : capiMachine .UID ,
879
+ },
880
+ },
881
+ },
882
+ Spec : getDefaultHetznerBareMetalMachineSpec (),
883
+ }
884
+ Expect (testEnv .Create (ctx , bmMachine )).To (Succeed ())
885
+
886
+ // Create a second Capi- and HetznerBareMetalMachine
887
+ bmMachineName2 := "hbmm2"
888
+ capiMachine2 := & clusterv1.Machine {
889
+ ObjectMeta : metav1.ObjectMeta {
890
+ Name : utils .GenerateName (nil , "capimachine-name-" ),
891
+ Namespace : testNs .Name ,
892
+ Finalizers : []string {clusterv1 .MachineFinalizer },
893
+ Labels : map [string ]string {
894
+ clusterv1 .ClusterNameLabel : capiCluster .Name ,
895
+ },
896
+ },
897
+ Spec : clusterv1.MachineSpec {
898
+ ClusterName : capiCluster .Name ,
899
+ InfrastructureRef : corev1.ObjectReference {
900
+ APIVersion : "infrastructure.cluster.x-k8s.io/v1beta1" ,
901
+ Kind : "HetznerBareMetalMachine" ,
902
+ Name : bmMachineName2 ,
903
+ },
904
+ Bootstrap : clusterv1.Bootstrap {
905
+ DataSecretName : ptr .To ("bootstrap-secret" ),
906
+ },
907
+ FailureDomain : & defaultFailureDomain ,
908
+ },
909
+ }
910
+ Expect (testEnv .Create (ctx , capiMachine2 )).To (Succeed ())
911
+
912
+ bmMachine2 := & infrav1.HetznerBareMetalMachine {
913
+ ObjectMeta : metav1.ObjectMeta {
914
+ Name : bmMachineName2 ,
915
+ Namespace : testNs .Name ,
916
+ Labels : map [string ]string {
917
+ clusterv1 .ClusterNameLabel : capiCluster .Name ,
918
+ },
919
+ OwnerReferences : []metav1.OwnerReference {
920
+ {
921
+ APIVersion : "cluster.x-k8s.io/v1beta1" ,
922
+ Kind : "Machine" ,
923
+ Name : capiMachine2 .Name ,
924
+ UID : capiMachine2 .UID ,
925
+ },
926
+ },
927
+ },
928
+ Spec : getDefaultHetznerBareMetalMachineSpec (),
929
+ }
930
+ Expect (testEnv .Create (ctx , bmMachine2 )).To (Succeed ())
931
+
932
+ osSSHSecret := helpers .GetDefaultSSHSecret ("os-ssh-secret" , testNs .Name )
933
+ Expect (testEnv .Create (ctx , osSSHSecret )).To (Succeed ())
934
+
935
+ host := helpers .BareMetalHost (
936
+ hostName ,
937
+ testNs .Name ,
938
+ helpers .WithRootDeviceHintWWN (),
939
+ helpers .WithHetznerClusterRef (hetznerClusterName ),
940
+ )
941
+ Expect (testEnv .Create (ctx , host )).To (Succeed ())
942
+
943
+ // Wait until bmMachine is provisioned.
944
+ var pickedMachine * infrav1.HetznerBareMetalMachine
945
+ var waitingMachine * infrav1.HetznerBareMetalMachine
946
+
947
+ // The mock should wait until we know which machine is picked.
948
+ w := make (chan time.Time )
949
+ osSSHClient .On ("GetHostName" ).WaitUntil (w )
950
+
951
+ Eventually (func () bool {
952
+ if err := testEnv .Get (ctx , client .ObjectKeyFromObject (host ), host ); err != nil {
953
+ return false
954
+ }
955
+ if host .Spec .ConsumerRef == nil {
956
+ return false
957
+ }
958
+
959
+ if host .Spec .ConsumerRef .Name == bmMachineName {
960
+ pickedMachine = bmMachine
961
+ waitingMachine = bmMachine2
962
+ } else {
963
+ pickedMachine = bmMachine2
964
+ waitingMachine = bmMachine
965
+ }
966
+
967
+ if w != nil {
968
+ // We need to change the mock on-the-fly. There are two
969
+ // machines, and we need to adapt the mock to the one that is
970
+ // picked.
971
+ osSSHClient .On ("GetHostName" ).Return (sshclient.Output {
972
+ StdOut : infrav1 .BareMetalHostNamePrefix + pickedMachine .Name ,
973
+ StdErr : "" ,
974
+ Err : nil ,
975
+ })
976
+ // Close the channel, so that mock "GetHostName" does no longer block.
977
+ close (w )
978
+
979
+ // We updated the mock, and closed the channel. Set channel to nil,
980
+ // so that we don't mock and close again.
981
+ // Closing a closed channel would panic.
982
+ w = nil
983
+ }
984
+
985
+ return host .Spec .Status .ProvisioningState != infrav1 .StateProvisioned
986
+ }, timeout ).Should (BeTrue ())
987
+
988
+ // Ensure the second machine is not ready (waiting for a hbmh)
989
+ Expect (testEnv .Get (ctx , client .ObjectKeyFromObject (waitingMachine ),
990
+ waitingMachine )).To (Succeed ())
991
+ Expect (waitingMachine .Status .Ready ).To (BeFalse ())
992
+
993
+ // Delete the first machine
994
+ Expect (testEnv .Delete (ctx , pickedMachine )).To (Succeed ())
995
+
996
+ // Wait until the waiting machine is ready
997
+ Eventually (func () bool {
998
+ Expect (testEnv .Get (ctx , client .ObjectKeyFromObject (waitingMachine ),
999
+ waitingMachine )).To (Succeed ())
1000
+ return waitingMachine .Status .Ready
1001
+ }, timeout ).Should (BeTrue ())
1002
+ })
1003
+ })
828
1004
})
1005
+
1006
+ func Test_BareMetalHostToBareMetalMachines (t * testing.T ) {
1007
+ ctx := context .Background ()
1008
+ scheme := runtime .NewScheme ()
1009
+ utilruntime .Must (infrav1 .AddToScheme (scheme ))
1010
+ host := & infrav1.HetznerBareMetalHost {
1011
+ ObjectMeta : metav1.ObjectMeta {
1012
+ Name : "test-host" ,
1013
+ Namespace : "test-ns" ,
1014
+ },
1015
+ }
1016
+ hbmm := & infrav1.HetznerBareMetalMachine {
1017
+ ObjectMeta : metav1.ObjectMeta {
1018
+ Name : "test-machine-with-label-foo" ,
1019
+ Namespace : "test-ns" ,
1020
+ },
1021
+ Spec : infrav1.HetznerBareMetalMachineSpec {
1022
+ HostSelector : infrav1.HostSelector {
1023
+ MatchLabels : map [string ]string {
1024
+ "key" : "foo" ,
1025
+ },
1026
+ },
1027
+ },
1028
+ }
1029
+ hbmmWithHostAnnotation := & infrav1.HetznerBareMetalMachine {
1030
+ ObjectMeta : metav1.ObjectMeta {
1031
+ Name : "test-machine-with-host-annotation" ,
1032
+ Namespace : "test-ns" ,
1033
+ Annotations : map [string ]string {
1034
+ infrav1 .HostAnnotation : "test-host" ,
1035
+ },
1036
+ },
1037
+ Spec : infrav1.HetznerBareMetalMachineSpec {
1038
+ HostSelector : infrav1.HostSelector {
1039
+ MatchLabels : map [string ]string {
1040
+ "key" : "foo" ,
1041
+ },
1042
+ },
1043
+ },
1044
+ }
1045
+
1046
+ hbmmWithoutLabel := & infrav1.HetznerBareMetalMachine {
1047
+ ObjectMeta : metav1.ObjectMeta {
1048
+ Name : "test-machine-with-label-bar" ,
1049
+ Namespace : "test-ns" ,
1050
+ },
1051
+ Spec : infrav1.HetznerBareMetalMachineSpec {
1052
+ HostSelector : infrav1.HostSelector {
1053
+ MatchLabels : map [string ]string {
1054
+ "key" : "bar" ,
1055
+ },
1056
+ },
1057
+ },
1058
+ }
1059
+ hbmmOtherNS := & infrav1.HetznerBareMetalMachine {
1060
+ ObjectMeta : metav1.ObjectMeta {
1061
+ Name : "test-machine-other-namespace" ,
1062
+ Namespace : "test-other-namespace" ,
1063
+ },
1064
+ Spec : infrav1.HetznerBareMetalMachineSpec {
1065
+ HostSelector : infrav1.HostSelector {
1066
+ MatchLabels : map [string ]string {
1067
+ "key" : "foo" ,
1068
+ },
1069
+ },
1070
+ },
1071
+ }
1072
+ c := fakeclient .NewClientBuilder ().WithScheme (scheme ).WithObjects (
1073
+ host , hbmm , hbmmOtherNS , hbmmWithoutLabel , hbmmWithHostAnnotation ).Build ()
1074
+
1075
+ // host does not have a label.
1076
+ f := BareMetalHostToBareMetalMachines (c , logr.Logger {})
1077
+ result := f (ctx , host )
1078
+ require .Nil (t , result )
1079
+ host .Labels = map [string ]string {
1080
+ "key" : "foo" ,
1081
+ }
1082
+
1083
+ // host has label "foo"
1084
+ err := c .Update (ctx , host )
1085
+ require .NoError (t , err )
1086
+ result = f (ctx , host )
1087
+ require .Equal (t , []reconcile.Request {
1088
+ {
1089
+ NamespacedName : types.NamespacedName {
1090
+ Name : "test-machine-with-label-foo" ,
1091
+ Namespace : "test-ns" ,
1092
+ },
1093
+ },
1094
+ }, result )
1095
+ }
0 commit comments