@@ -23,14 +23,18 @@ import (
2323 "fmt"
2424 "slices"
2525
26+ "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
2627 "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
2728 "k8s.io/apimachinery/pkg/runtime"
2829 "k8s.io/apimachinery/pkg/types"
2930 "sigs.k8s.io/controller-runtime/pkg/client"
3031 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3132
33+ apimeta "k8s.io/apimachinery/pkg/api/meta"
3234 k8s_errors "k8s.io/apimachinery/pkg/api/errors"
3335 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
37+ "k8s.io/apimachinery/pkg/runtime/schema"
3438)
3539
3640// CheckOwnerRefExist - returns true if the owner is already in the owner ref list
@@ -181,3 +185,108 @@ func ManageConsumerFinalizer(
181185
182186 return nil
183187}
188+
189+ // IsOwnerReady checks if the controller owner of this object is ready.
190+ // Returns true if the owner is ready, false if not ready, and error only for unexpected failures.
191+ // If there's no owner with controller=true, it returns true (safe to proceed).
192+ func IsOwnerReady (
193+ ctx context.Context ,
194+ h * helper.Helper ,
195+ obj client.Object ,
196+ ) (bool , error ) {
197+ // Find the controller owner reference (e.g., Cinder, Nova, etc.)
198+ var ownerRef * metav1.OwnerReference
199+ for _ , owner := range obj .GetOwnerReferences () {
200+ if owner .Controller != nil && * owner .Controller {
201+ ownerRef = & owner
202+ break
203+ }
204+ }
205+
206+ // If no controlling owner, safe to proceed
207+ if ownerRef == nil {
208+ h .GetLogger ().Info ("No controller owner found, owner is considered ready" )
209+ return true , nil
210+ }
211+
212+ // Parse the APIVersion to extract group and version
213+ gv , err := schema .ParseGroupVersion (ownerRef .APIVersion )
214+ if err != nil {
215+ h .GetLogger ().Error (err , "Failed to parse owner APIVersion" , "apiVersion" , ownerRef .APIVersion )
216+ return false , err
217+ }
218+
219+ // Fetch the owner resource using unstructured client
220+ owner := & unstructured.Unstructured {}
221+ owner .SetGroupVersionKind (schema.GroupVersionKind {
222+ Group : gv .Group ,
223+ Version : gv .Version ,
224+ Kind : ownerRef .Kind ,
225+ })
226+
227+ err = h .GetClient ().Get (ctx , types.NamespacedName {
228+ Name : ownerRef .Name ,
229+ Namespace : obj .GetNamespace (),
230+ }, owner )
231+
232+ if err != nil {
233+ if k8s_errors .IsNotFound (err ) || apimeta .IsNoMatchError (err ) {
234+ h .GetLogger ().Info ("Owner resource not found, owner is considered ready" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
235+ return true , nil
236+ }
237+ h .GetLogger ().Error (err , "Failed to fetch owner resource" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
238+ return false , err
239+ }
240+
241+ // Check status.conditions for Ready condition
242+ conditions , found , err := unstructured .NestedSlice (owner .Object , "status" , "conditions" )
243+ if err != nil || ! found {
244+ h .GetLogger ().Info ("No conditions found in owner status, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
245+ return false , nil
246+ }
247+
248+ // Marshal unstructured conditions to condition.Conditions to use existing helper functions
249+ conditionsJSON , err := json .Marshal (conditions )
250+ if err != nil {
251+ h .GetLogger ().Info ("Failed to marshal owner conditions, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
252+ return false , nil
253+ }
254+
255+ var ownerConditions condition.Conditions
256+ err = json .Unmarshal (conditionsJSON , & ownerConditions )
257+ if err != nil {
258+ h .GetLogger ().Info ("Failed to unmarshal owner conditions, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
259+ return false , nil
260+ }
261+
262+ // Use existing helper function to check if Ready condition is True
263+ if ! ownerConditions .IsTrue (condition .ReadyCondition ) {
264+ h .GetLogger ().Info ("Owner service not ready, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
265+ return false , nil
266+ }
267+
268+ // Check if owner has reconciled (observedGeneration matches generation)
269+ generation , foundGen , err := unstructured .NestedInt64 (owner .Object , "metadata" , "generation" )
270+ if err != nil || ! foundGen {
271+ h .GetLogger ().Info ("Could not get owner generation, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
272+ return false , nil
273+ }
274+
275+ observedGeneration , foundObsGen , err := unstructured .NestedInt64 (owner .Object , "status" , "observedGeneration" )
276+ if err != nil || ! foundObsGen {
277+ h .GetLogger ().Info ("Could not get owner observedGeneration, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
278+ return false , nil
279+ }
280+
281+ if observedGeneration != generation {
282+ h .GetLogger ().Info ("Owner service has not reconciled yet (observedGeneration != generation), waiting" ,
283+ "kind" , ownerRef .Kind ,
284+ "name" , ownerRef .Name ,
285+ "generation" , generation ,
286+ "observedGeneration" , observedGeneration )
287+ return false , nil
288+ }
289+
290+ h .GetLogger ().Info ("Owner service is ready and has reconciled, safe to proceed" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
291+ return true , nil
292+ }
0 commit comments