@@ -291,6 +291,75 @@ func (r *OpenStackVersionReconciler) Reconcile(ctx context.Context, req ctrl.Req
291291 Log .Info ("Waiting on OVN Dataplane updates to complete" )
292292 return ctrl.Result {}, nil
293293 }
294+
295+ // When the OVN controller image is the same between the deployed
296+ // version and the target version, the image comparison above always
297+ // passes because the nodeset already has the matching image from
298+ // the previous update. In this case we need additional checks to
299+ // confirm the OVN dataplane deployment for this update cycle has
300+ // actually completed.
301+ //
302+ // We use the saved condition state (from before Init reset) to
303+ // track whether we have observed a running OVN deployment during
304+ // this update cycle:
305+ // - If we see a running OVN deployment now: set condition False
306+ // (RequestedReason) to record that we observed one
307+ // - If no running OVN deployment AND the previous condition was
308+ // False/RequestedReason: the deployment we saw previously has
309+ // completed → proceed (fall through to set True)
310+ // - If no running OVN deployment AND the previous condition was
311+ // NOT False/RequestedReason (e.g. still Unknown from Init):
312+ // we haven't seen a deployment yet → keep waiting
313+ //
314+ // When the image differs between versions, the image match alone
315+ // is sufficient proof that a deployment updated it, since the
316+ // nodeset's ContainerImages are only set on successful completion.
317+ deployedDefaults , hasDeployedDefaults := instance .Status .ContainerImageVersionDefaults [* instance .Status .DeployedVersion ]
318+ if hasDeployedDefaults &&
319+ deployedDefaults .OvnControllerImage != nil &&
320+ instance .Status .ContainerImages .OvnControllerImage != nil &&
321+ * deployedDefaults .OvnControllerImage == * instance .Status .ContainerImages .OvnControllerImage {
322+
323+ ovnDeploymentRunning , err := openstack .IsDataplaneDeploymentRunningForServiceType (
324+ ctx , versionHelper , instance .Namespace , dataplaneNodesets , "ovn" )
325+ if err != nil {
326+ return ctrl.Result {}, err
327+ }
328+
329+ if ovnDeploymentRunning {
330+ // OVN deployment is actively running — record this in
331+ // the condition so we can detect its completion later.
332+ instance .Status .Conditions .Set (condition .FalseCondition (
333+ corev1beta1 .OpenStackVersionMinorUpdateOVNDataplane ,
334+ condition .RequestedReason ,
335+ condition .SeverityInfo ,
336+ corev1beta1 .OpenStackVersionMinorUpdateReadyRunningMessage ))
337+ Log .Info ("Waiting on OVN Dataplane deployment to complete (OVN image unchanged between versions)" )
338+ return ctrl.Result {}, nil
339+ }
340+
341+ // No OVN deployment running. Check the saved condition state
342+ // from the previous reconciliation to determine if we ever
343+ // observed one running during this update cycle.
344+ prevOvnDataplaneCond := savedConditions .Get (corev1beta1 .OpenStackVersionMinorUpdateOVNDataplane )
345+ if prevOvnDataplaneCond == nil ||
346+ prevOvnDataplaneCond .Reason != condition .RequestedReason {
347+ // We have never observed a running OVN deployment in
348+ // this update cycle — the deployment has not been
349+ // created yet. Keep waiting.
350+ instance .Status .Conditions .Set (condition .FalseCondition (
351+ corev1beta1 .OpenStackVersionMinorUpdateOVNDataplane ,
352+ condition .InitReason ,
353+ condition .SeverityInfo ,
354+ corev1beta1 .OpenStackVersionMinorUpdateReadyRunningMessage ))
355+ Log .Info ("Waiting for OVN Dataplane deployment to be created (OVN image unchanged between versions)" )
356+ return ctrl.Result {}, nil
357+ }
358+ // Previously saw a running OVN deployment (condition was
359+ // False/RequestedReason), now no OVN deployment is running
360+ // → the deployment has completed. Fall through to set True.
361+ Log .Info ("OVN Dataplane deployment completed (OVN image unchanged between versions)" )
362+ }
294363 }
295364 instance .Status .Conditions .MarkTrue (
296365 corev1beta1 .OpenStackVersionMinorUpdateOVNDataplane ,
0 commit comments