@@ -25,6 +25,7 @@ import (
2525 . "github.com/onsi/ginkgo/v2"
2626 . "github.com/onsi/gomega"
2727 corev1 "k8s.io/api/core/v1"
28+ apierrors "k8s.io/apimachinery/pkg/api/errors"
2829 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2930 "k8s.io/klog/v2"
3031 "k8s.io/utils/ptr"
@@ -879,6 +880,274 @@ var _ = Describe("Hetzner ClusterReconciler", func() {
879880 }, timeout ).Should (BeTrue ())
880881 },
881882 )
883+
884+ Describe ("For an existing Network" , func () {
885+ It ("should attach the existing unlabeled Network by ID and not create a new one" , func () {
886+ networkName := utils .GenerateName (nil , "network1-" )
887+ network , err := hcloudClient .CreateNetwork (context .Background (), hcloud.NetworkCreateOpts {Name : networkName })
888+ Expect (err ).To (Succeed ())
889+ defer func () {
890+ err := hcloudClient .DeleteNetwork (context .Background (), network )
891+ Expect (err ).To (Succeed ())
892+ }()
893+ networksBeforeClusterCreate , err := hcloudClient .ListNetworks (context .Background (), hcloud.NetworkListOpts {})
894+ Expect (err ).To (Succeed ())
895+
896+ hetznerClusterName := utils .GenerateName (nil , "test1-" )
897+
898+ capiCluster := & clusterv1.Cluster {
899+ ObjectMeta : metav1.ObjectMeta {
900+ GenerateName : "capi-test1-" ,
901+ Namespace : namespace ,
902+ Finalizers : []string {clusterv1 .ClusterFinalizer },
903+ },
904+ Spec : clusterv1.ClusterSpec {
905+ InfrastructureRef : & corev1.ObjectReference {
906+ APIVersion : infrav1 .GroupVersion .String (),
907+ Kind : "HetznerCluster" ,
908+ Name : hetznerClusterName ,
909+ Namespace : namespace ,
910+ },
911+ },
912+ }
913+ Expect (testEnv .Create (ctx , capiCluster )).To (Succeed ())
914+ defer func () {
915+ Expect (testEnv .Cleanup (ctx , capiCluster )).To (Succeed ())
916+ }()
917+
918+ instance := & infrav1.HetznerCluster {
919+ ObjectMeta : metav1.ObjectMeta {
920+ Name : hetznerClusterName ,
921+ Namespace : namespace ,
922+ OwnerReferences : []metav1.OwnerReference {
923+ {
924+ APIVersion : "cluster.x-k8s.io/v1beta1" ,
925+ Kind : "Cluster" ,
926+ Name : capiCluster .Name ,
927+ UID : capiCluster .UID ,
928+ },
929+ },
930+ },
931+ Spec : getDefaultHetznerClusterSpec (),
932+ }
933+ // the creation of a HetznerCluster should not lead to the creation of a network when the ID of an
934+ // existing network was given.
935+ instance .Spec .HCloudNetwork .ID = ptr .To (network .ID )
936+ Expect (testEnv .Create (ctx , instance )).To (Succeed ())
937+ defer func () {
938+ Expect (testEnv .Cleanup (ctx , instance )).To (Succeed ())
939+ }()
940+
941+ key := client.ObjectKey {Namespace : instance .Namespace , Name : instance .Name }
942+
943+ Eventually (func () bool {
944+ if err := testEnv .Get (ctx , key , instance ); err != nil {
945+ return false
946+ }
947+ if isPresentAndTrue (key , instance , infrav1 .NetworkReadyCondition ) && instance .Status .Network != nil {
948+ return true
949+ }
950+ return false
951+ }, timeout ).Should (BeTrue ())
952+
953+ networksAfterClusterCreate , err := hcloudClient .ListNetworks (ctx , hcloud.NetworkListOpts {})
954+ Expect (err ).To (Succeed ())
955+
956+ Expect (len (networksAfterClusterCreate )).To (Equal (len (networksBeforeClusterCreate )))
957+
958+ var found bool
959+ for _ , n := range networksAfterClusterCreate {
960+ if n .ID == instance .Status .Network .ID {
961+ found = true
962+ break
963+ }
964+ }
965+ Expect (found ).To (Equal (true ))
966+ })
967+ It ("should not delete the existing unlabeled Network when deleting the Cluster" , func () {
968+ networkName := utils .GenerateName (nil , "network2-" )
969+ network , err := hcloudClient .CreateNetwork (context .Background (), hcloud.NetworkCreateOpts {Name : networkName })
970+ Expect (err ).To (Succeed ())
971+ defer func () {
972+ err := hcloudClient .DeleteNetwork (context .Background (), network )
973+ Expect (err ).To (Succeed ())
974+ }()
975+ networksBeforeClusterDelete , err := hcloudClient .ListNetworks (context .Background (), hcloud.NetworkListOpts {})
976+ Expect (err ).To (Succeed ())
977+
978+ hetznerClusterName := utils .GenerateName (nil , "test1-" )
979+
980+ capiCluster := & clusterv1.Cluster {
981+ ObjectMeta : metav1.ObjectMeta {
982+ GenerateName : "capi-test1-" ,
983+ Namespace : namespace ,
984+ Finalizers : []string {clusterv1 .ClusterFinalizer },
985+ },
986+ Spec : clusterv1.ClusterSpec {
987+ InfrastructureRef : & corev1.ObjectReference {
988+ APIVersion : infrav1 .GroupVersion .String (),
989+ Kind : "HetznerCluster" ,
990+ Name : hetznerClusterName ,
991+ Namespace : namespace ,
992+ },
993+ },
994+ }
995+ Expect (testEnv .Create (ctx , capiCluster )).To (Succeed ())
996+
997+ instance := & infrav1.HetznerCluster {
998+ ObjectMeta : metav1.ObjectMeta {
999+ Name : hetznerClusterName ,
1000+ Namespace : namespace ,
1001+ OwnerReferences : []metav1.OwnerReference {
1002+ {
1003+ APIVersion : "cluster.x-k8s.io/v1beta1" ,
1004+ Kind : "Cluster" ,
1005+ Name : capiCluster .Name ,
1006+ UID : capiCluster .UID ,
1007+ },
1008+ },
1009+ },
1010+ Spec : getDefaultHetznerClusterSpec (),
1011+ }
1012+ instance .Spec .HCloudNetwork .ID = ptr .To (network .ID )
1013+ Expect (testEnv .Create (ctx , instance )).To (Succeed ())
1014+
1015+ key := client.ObjectKey {Namespace : instance .Namespace , Name : instance .Name }
1016+
1017+ var networkID * int64
1018+ Eventually (func () bool {
1019+ if err := testEnv .Get (ctx , key , instance ); err != nil {
1020+ return false
1021+ }
1022+ if isPresentAndTrue (key , instance , infrav1 .NetworkReadyCondition ) && instance .Status .Network != nil {
1023+ networkID = & instance .Status .Network .ID
1024+ return true
1025+ }
1026+ return false
1027+ }, timeout ).Should (BeTrue ())
1028+
1029+ Expect (networkID ).ToNot (BeNil ())
1030+
1031+ // the deletion of a HetznerCluster should not lead to the deletion of an existing network
1032+ // when the network misses the `owned` label.
1033+ Expect (testEnv .Cleanup (ctx , instance , capiCluster )).To (Succeed ())
1034+
1035+ Eventually (func () bool {
1036+ if err := testEnv .Get (ctx , client.ObjectKey {Namespace : instance .Namespace , Name : instance .Name }, instance ); err != nil && apierrors .IsNotFound (err ) {
1037+ return true
1038+ } else if err != nil {
1039+ return false
1040+ }
1041+ return false
1042+ }, timeout ).Should (BeTrue ())
1043+
1044+ networksAfterClusterDelete , err := hcloudClient .ListNetworks (ctx , hcloud.NetworkListOpts {})
1045+ Expect (err ).To (Succeed ())
1046+
1047+ Expect (len (networksAfterClusterDelete )).To (Equal (len (networksBeforeClusterDelete )))
1048+
1049+ var found bool
1050+ for _ , n := range networksAfterClusterDelete {
1051+ if n .ID == * networkID {
1052+ found = true
1053+ break
1054+ }
1055+ }
1056+ Expect (found ).To (Equal (true ))
1057+ })
1058+ It (`should delete the existing "owned" labeled Network when deleting the Cluster` , func () {
1059+ hetznerClusterName := utils .GenerateName (nil , "test1-" )
1060+
1061+ capiCluster := & clusterv1.Cluster {
1062+ ObjectMeta : metav1.ObjectMeta {
1063+ GenerateName : "capi-test1-" ,
1064+ Namespace : namespace ,
1065+ Finalizers : []string {clusterv1 .ClusterFinalizer },
1066+ },
1067+ Spec : clusterv1.ClusterSpec {
1068+ InfrastructureRef : & corev1.ObjectReference {
1069+ APIVersion : infrav1 .GroupVersion .String (),
1070+ Kind : "HetznerCluster" ,
1071+ Name : hetznerClusterName ,
1072+ Namespace : namespace ,
1073+ },
1074+ },
1075+ }
1076+ Expect (testEnv .Create (ctx , capiCluster )).To (Succeed ())
1077+
1078+ instance := & infrav1.HetznerCluster {
1079+ ObjectMeta : metav1.ObjectMeta {
1080+ Name : hetznerClusterName ,
1081+ Namespace : namespace ,
1082+ OwnerReferences : []metav1.OwnerReference {
1083+ {
1084+ APIVersion : "cluster.x-k8s.io/v1beta1" ,
1085+ Kind : "Cluster" ,
1086+ Name : capiCluster .Name ,
1087+ UID : capiCluster .UID ,
1088+ },
1089+ },
1090+ },
1091+ Spec : getDefaultHetznerClusterSpec (),
1092+ }
1093+
1094+ networkName := utils .GenerateName (nil , "network3-" )
1095+ network , err := hcloudClient .CreateNetwork (context .Background (), hcloud.NetworkCreateOpts {
1096+ Name : networkName ,
1097+ Labels : map [string ]string {instance .ClusterTagKey (): "owned" },
1098+ })
1099+ Expect (err ).To (Succeed ())
1100+
1101+ networksBeforeClusterDelete , err := hcloudClient .ListNetworks (context .Background (), hcloud.NetworkListOpts {})
1102+ Expect (err ).To (Succeed ())
1103+
1104+ instance .Spec .HCloudNetwork .ID = ptr .To (network .ID )
1105+ Expect (testEnv .Create (ctx , instance )).To (Succeed ())
1106+
1107+ key := client.ObjectKey {Namespace : instance .Namespace , Name : instance .Name }
1108+
1109+ var networkID * int64
1110+ Eventually (func () bool {
1111+ if err := testEnv .Get (ctx , key , instance ); err != nil {
1112+ return false
1113+ }
1114+ if isPresentAndTrue (key , instance , infrav1 .NetworkReadyCondition ) && instance .Status .Network != nil {
1115+ networkID = & instance .Status .Network .ID
1116+ return true
1117+ }
1118+ return false
1119+ }, timeout ).Should (BeTrue ())
1120+
1121+ Expect (networkID ).ToNot (BeNil ())
1122+
1123+ // As the network has the `owned` label, the deletion of a HetznerCluster will also lead to the
1124+ // deletion of the network.
1125+ Expect (testEnv .Cleanup (ctx , instance , capiCluster )).To (Succeed ())
1126+
1127+ Eventually (func () bool {
1128+ if err := testEnv .Get (ctx , client.ObjectKey {Namespace : instance .Namespace , Name : instance .Name }, instance ); err != nil && apierrors .IsNotFound (err ) {
1129+ return true
1130+ } else if err != nil {
1131+ return false
1132+ }
1133+ return false
1134+ }, timeout ).Should (BeTrue ())
1135+
1136+ networksAfterClusterDelete , err := hcloudClient .ListNetworks (ctx , hcloud.NetworkListOpts {})
1137+ Expect (err ).To (Succeed ())
1138+
1139+ Expect (len (networksAfterClusterDelete )).To (Equal (len (networksBeforeClusterDelete ) - 1 ))
1140+
1141+ var found bool
1142+ for _ , n := range networksAfterClusterDelete {
1143+ if n .ID == * networkID {
1144+ found = true
1145+ break
1146+ }
1147+ }
1148+ Expect (found ).To (Not (Equal (true )))
1149+ })
1150+ })
8821151 })
8831152 })
8841153})
0 commit comments