@@ -215,10 +215,24 @@ func deleteBastion(scope scope.Scope, cluster *clusterv1.Cluster, openStackClust
215215 return err
216216 }
217217
218- instanceName := fmt .Sprintf ("%s-bastion" , cluster .Name )
219- instanceStatus , err := computeService .GetInstanceStatusByName (openStackCluster , instanceName )
220- if err != nil {
221- return err
218+ if openStackCluster .Status .Bastion != nil && openStackCluster .Status .Bastion .FloatingIP != "" {
219+ if err = networkingService .DeleteFloatingIP (openStackCluster , openStackCluster .Status .Bastion .FloatingIP ); err != nil {
220+ handleUpdateOSCError (openStackCluster , fmt .Errorf ("failed to delete floating IP: %w" , err ))
221+ return fmt .Errorf ("failed to delete floating IP: %w" , err )
222+ }
223+ }
224+
225+ var instanceStatus * compute.InstanceStatus
226+ if openStackCluster .Status .Bastion != nil && openStackCluster .Status .Bastion .ID != "" {
227+ instanceStatus , err = computeService .GetInstanceStatus (openStackCluster .Status .Bastion .ID )
228+ if err != nil {
229+ return err
230+ }
231+ } else {
232+ instanceStatus , err = computeService .GetInstanceStatusByName (openStackCluster , bastionName (cluster .Name ))
233+ if err != nil {
234+ return err
235+ }
222236 }
223237
224238 if instanceStatus != nil {
@@ -230,6 +244,7 @@ func deleteBastion(scope scope.Scope, cluster *clusterv1.Cluster, openStackClust
230244
231245 for _ , address := range addresses {
232246 if address .Type == corev1 .NodeExternalIP {
247+ // Floating IP may not have properly saved in bastion status (thus not deleted above), delete any remaining floating IP
233248 if err = networkingService .DeleteFloatingIP (openStackCluster , address .Address ); err != nil {
234249 handleUpdateOSCError (openStackCluster , fmt .Errorf ("failed to delete floating IP: %w" , err ))
235250 return fmt .Errorf ("failed to delete floating IP: %w" , err )
@@ -277,8 +292,9 @@ func reconcileNormal(scope scope.Scope, cluster *clusterv1.Cluster, openStackClu
277292 return reconcile.Result {}, err
278293 }
279294
280- if err = reconcileBastion (scope , cluster , openStackCluster ); err != nil {
281- return reconcile.Result {}, err
295+ result , err := reconcileBastion (scope , cluster , openStackCluster )
296+ if err != nil || ! reflect .DeepEqual (result , reconcile.Result {}) {
297+ return result , err
282298 }
283299
284300 availabilityZones , err := computeService .GetAvailabilityZones ()
@@ -308,88 +324,112 @@ func reconcileNormal(scope scope.Scope, cluster *clusterv1.Cluster, openStackClu
308324 return reconcile.Result {}, nil
309325}
310326
311- func reconcileBastion (scope scope.Scope , cluster * clusterv1.Cluster , openStackCluster * infrav1.OpenStackCluster ) error {
327+ func reconcileBastion (scope scope.Scope , cluster * clusterv1.Cluster , openStackCluster * infrav1.OpenStackCluster ) (ctrl. Result , error ) {
312328 scope .Logger ().Info ("Reconciling Bastion" )
313329
314330 if openStackCluster .Spec .Bastion == nil || ! openStackCluster .Spec .Bastion .Enabled {
315- return deleteBastion (scope , cluster , openStackCluster )
331+ return reconcile. Result {}, deleteBastion (scope , cluster , openStackCluster )
316332 }
317333
318334 computeService , err := compute .NewService (scope )
319335 if err != nil {
320- return err
336+ return reconcile. Result {}, err
321337 }
322338
323339 instanceSpec := bastionToInstanceSpec (openStackCluster , cluster .Name )
324340 bastionHash , err := compute .HashInstanceSpec (instanceSpec )
325341 if err != nil {
326- return fmt .Errorf ("failed computing bastion hash from instance spec: %w" , err )
342+ return reconcile.Result {}, fmt .Errorf ("failed computing bastion hash from instance spec: %w" , err )
343+ }
344+ if bastionHashHasChanged (bastionHash , openStackCluster .ObjectMeta .Annotations ) {
345+ if err := deleteBastion (scope , cluster , openStackCluster ); err != nil {
346+ return ctrl.Result {}, err
347+ }
327348 }
328349
329- instanceStatus , err := computeService .GetInstanceStatusByName (openStackCluster , fmt .Sprintf ("%s-bastion" , cluster .Name ))
330- if err != nil {
331- return err
350+ var instanceStatus * compute.InstanceStatus
351+ if openStackCluster .Status .Bastion != nil && openStackCluster .Status .Bastion .ID != "" {
352+ if instanceStatus , err = computeService .GetInstanceStatus (openStackCluster .Status .Bastion .ID ); err != nil {
353+ return reconcile.Result {}, err
354+ }
332355 }
333- if instanceStatus != nil {
334- if ! bastionHashHasChanged (bastionHash , openStackCluster .ObjectMeta .Annotations ) {
335- bastion , err := instanceStatus .BastionStatus (openStackCluster )
336- if err != nil {
337- return err
338- }
339- // Add the current hash if no annotation is set.
340- if _ , ok := openStackCluster .ObjectMeta .Annotations [BastionInstanceHashAnnotation ]; ! ok {
341- annotations .AddAnnotations (openStackCluster , map [string ]string {BastionInstanceHashAnnotation : bastionHash })
342- }
343- openStackCluster .Status .Bastion = bastion
344- return nil
356+ if instanceStatus == nil {
357+ // Check if there is an existing instance with bastion name, in case where bastion ID would not have been properly stored in cluster status
358+ if instanceStatus , err = computeService .GetInstanceStatusByName (openStackCluster , instanceSpec .Name ); err != nil {
359+ return reconcile.Result {}, err
345360 }
346-
347- if err := deleteBastion (scope , cluster , openStackCluster ); err != nil {
348- return err
361+ }
362+ if instanceStatus == nil {
363+ instanceStatus , err = computeService .CreateInstance (openStackCluster , openStackCluster , instanceSpec , cluster .Name )
364+ if err != nil {
365+ return reconcile.Result {}, fmt .Errorf ("failed to create bastion: %w" , err )
349366 }
350367 }
351368
352- instanceStatus , err = computeService .CreateInstance (openStackCluster , openStackCluster , instanceSpec , cluster .Name )
353- if err != nil {
354- return fmt .Errorf ("failed to reconcile bastion: %w" , err )
369+ // Save hash & status as soon as we know we have an instance
370+ instanceStatus .UpdateBastionStatus (openStackCluster )
371+ annotations .AddAnnotations (openStackCluster , map [string ]string {BastionInstanceHashAnnotation : bastionHash })
372+
373+ // Make sure that bastion instance has a valid state
374+ switch instanceStatus .State () {
375+ case infrav1 .InstanceStateError :
376+ return ctrl.Result {}, fmt .Errorf ("failed to reconcile bastion, instance state is ERROR" )
377+ case infrav1 .InstanceStateBuild , infrav1 .InstanceStateUndefined :
378+ scope .Logger ().Info ("Waiting for bastion instance to become ACTIVE" , "id" , instanceStatus .ID (), "status" , instanceStatus .State ())
379+ return ctrl.Result {RequeueAfter : waitForBuildingInstanceToReconcile }, nil
380+ case infrav1 .InstanceStateDeleted :
381+ // This should normally be handled by deleteBastion
382+ openStackCluster .Status .Bastion = nil
383+ return ctrl.Result {}, nil
355384 }
356385
357386 networkingService , err := networking .NewService (scope )
358387 if err != nil {
359- return err
360- }
361- clusterName := fmt .Sprintf ("%s-%s" , cluster .Namespace , cluster .Name )
362- fp , err := networkingService .GetOrCreateFloatingIP (openStackCluster , openStackCluster , clusterName , openStackCluster .Spec .Bastion .Instance .FloatingIP )
363- if err != nil {
364- handleUpdateOSCError (openStackCluster , fmt .Errorf ("failed to get or create floating IP for bastion: %w" , err ))
365- return fmt .Errorf ("failed to get or create floating IP for bastion: %w" , err )
388+ return ctrl.Result {}, err
366389 }
367390 port , err := computeService .GetManagementPort (openStackCluster , instanceStatus )
368391 if err != nil {
369392 err = fmt .Errorf ("getting management port for bastion: %w" , err )
370393 handleUpdateOSCError (openStackCluster , err )
371- return err
394+ return ctrl. Result {}, err
372395 }
373- err = networkingService .AssociateFloatingIP ( openStackCluster , fp , port .ID )
396+ fp , err : = networkingService .GetFloatingIPByPortID ( port .ID )
374397 if err != nil {
375- handleUpdateOSCError (openStackCluster , fmt .Errorf ("failed to associate floating IP with bastion: %w" , err ))
376- return fmt .Errorf ("failed to associate floating IP with bastion: %w" , err )
398+ handleUpdateOSCError (openStackCluster , fmt .Errorf ("failed to get or create floating IP for bastion: %w" , err ))
399+ return ctrl.Result {}, fmt .Errorf ("failed to get floating IP for bastion port: %w" , err )
400+ }
401+ if fp != nil {
402+ // Floating IP is already attached to bastion, no need to proceed
403+ openStackCluster .Status .Bastion .FloatingIP = fp .FloatingIP
404+ return ctrl.Result {}, nil
377405 }
378406
379- bastion , err := instanceStatus .BastionStatus (openStackCluster )
407+ clusterName := fmt .Sprintf ("%s-%s" , cluster .Namespace , cluster .Name )
408+ floatingIP := openStackCluster .Spec .Bastion .Instance .FloatingIP
409+ if openStackCluster .Status .Bastion .FloatingIP != "" {
410+ // Some floating IP has already been created for this bastion, make sure we re-use it
411+ floatingIP = openStackCluster .Status .Bastion .FloatingIP
412+ }
413+ // Check if there is an existing floating IP attached to bastion, in case where FloatingIP would not yet have been stored in cluster status
414+ fp , err = networkingService .GetOrCreateFloatingIP (openStackCluster , openStackCluster , clusterName , floatingIP )
380415 if err != nil {
381- return err
416+ handleUpdateOSCError (openStackCluster , fmt .Errorf ("failed to get or create floating IP for bastion: %w" , err ))
417+ return ctrl.Result {}, fmt .Errorf ("failed to get or create floating IP for bastion: %w" , err )
382418 }
383- bastion .FloatingIP = fp .FloatingIP
384- openStackCluster .Status .Bastion = bastion
385- annotations .AddAnnotations (openStackCluster , map [string ]string {BastionInstanceHashAnnotation : bastionHash })
386- return nil
419+ openStackCluster .Status .Bastion .FloatingIP = fp .FloatingIP
420+
421+ err = networkingService .AssociateFloatingIP (openStackCluster , fp , port .ID )
422+ if err != nil {
423+ handleUpdateOSCError (openStackCluster , fmt .Errorf ("failed to associate floating IP with bastion: %w" , err ))
424+ return ctrl.Result {}, fmt .Errorf ("failed to associate floating IP with bastion: %w" , err )
425+ }
426+
427+ return ctrl.Result {}, nil
387428}
388429
389430func bastionToInstanceSpec (openStackCluster * infrav1.OpenStackCluster , clusterName string ) * compute.InstanceSpec {
390- name := fmt .Sprintf ("%s-bastion" , clusterName )
391431 instanceSpec := & compute.InstanceSpec {
392- Name : name ,
432+ Name : bastionName ( clusterName ) ,
393433 Flavor : openStackCluster .Spec .Bastion .Instance .Flavor ,
394434 SSHKeyName : openStackCluster .Spec .Bastion .Instance .SSHKeyName ,
395435 Image : openStackCluster .Spec .Bastion .Instance .Image ,
@@ -412,6 +452,10 @@ func bastionToInstanceSpec(openStackCluster *infrav1.OpenStackCluster, clusterNa
412452 return instanceSpec
413453}
414454
455+ func bastionName (clusterName string ) string {
456+ return fmt .Sprintf ("%s-bastion" , clusterName )
457+ }
458+
415459// bastionHashHasChanged returns a boolean whether if the latest bastion hash, built from the instance spec, has changed or not.
416460func bastionHashHasChanged (computeHash string , clusterAnnotations map [string ]string ) bool {
417461 latestHash , ok := clusterAnnotations [BastionInstanceHashAnnotation ]
0 commit comments