@@ -23,14 +23,19 @@ import (
2323 "fmt"
2424 "slices"
2525
26+ "github.com/go-logr/logr"
27+ "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
2628 "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
2729 "k8s.io/apimachinery/pkg/runtime"
2830 "k8s.io/apimachinery/pkg/types"
2931 "sigs.k8s.io/controller-runtime/pkg/client"
3032 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3133
3234 k8s_errors "k8s.io/apimachinery/pkg/api/errors"
35+ apimeta "k8s.io/apimachinery/pkg/api/meta"
3336 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37+ "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
38+ "k8s.io/apimachinery/pkg/runtime/schema"
3439)
3540
3641// CheckOwnerRefExist - returns true if the owner is already in the owner ref list
@@ -181,3 +186,113 @@ func ManageConsumerFinalizer(
181186
182187 return nil
183188}
189+
190+ // IsOwnerReady checks if the controller owner of this object is ready.
191+ // It reads the owner via the supplied client.Reader — callers should pass an
192+ // uncached (API-server) reader to avoid stale informer data and phantom
193+ // informers for cross-operator types the calling controller does not watch.
194+ // Returns true if the owner is ready, false if not ready, and error only for
195+ // unexpected failures.
196+ // If there's no owner with controller=true, it returns true (safe to proceed).
197+ func IsOwnerReady (
198+ ctx context.Context ,
199+ reader client.Reader ,
200+ log logr.Logger ,
201+ obj client.Object ,
202+ ) (bool , error ) {
203+ var ownerRef * metav1.OwnerReference
204+ for _ , owner := range obj .GetOwnerReferences () {
205+ if owner .Controller != nil && * owner .Controller {
206+ ownerRef = & owner
207+ break
208+ }
209+ }
210+
211+ if ownerRef == nil {
212+ log .V (1 ).Info ("No controller owner found, owner is considered ready" )
213+ return true , nil
214+ }
215+
216+ gv , err := schema .ParseGroupVersion (ownerRef .APIVersion )
217+ if err != nil {
218+ log .Error (err , "Failed to parse owner APIVersion" , "apiVersion" , ownerRef .APIVersion )
219+ return false , err
220+ }
221+
222+ owner := & unstructured.Unstructured {}
223+ owner .SetGroupVersionKind (schema.GroupVersionKind {
224+ Group : gv .Group ,
225+ Version : gv .Version ,
226+ Kind : ownerRef .Kind ,
227+ })
228+
229+ err = reader .Get (ctx , types.NamespacedName {
230+ Name : ownerRef .Name ,
231+ Namespace : obj .GetNamespace (),
232+ }, owner )
233+
234+ if err != nil {
235+ if k8s_errors .IsNotFound (err ) || apimeta .IsNoMatchError (err ) {
236+ log .V (1 ).Info ("Owner resource not found, owner is considered ready" ,
237+ "kind" , ownerRef .Kind , "name" , ownerRef .Name )
238+ return true , nil
239+ }
240+ log .Error (err , "Failed to fetch owner resource" ,
241+ "kind" , ownerRef .Kind , "name" , ownerRef .Name )
242+ return false , err
243+ }
244+
245+ conditions , found , err := unstructured .NestedSlice (owner .Object , "status" , "conditions" )
246+ if err != nil || ! found {
247+ log .V (1 ).Info ("No conditions found in owner status, waiting" ,
248+ "kind" , ownerRef .Kind , "name" , ownerRef .Name )
249+ return false , nil
250+ }
251+
252+ conditionsJSON , err := json .Marshal (conditions )
253+ if err != nil {
254+ log .V (1 ).Info ("Failed to marshal owner conditions, waiting" ,
255+ "kind" , ownerRef .Kind , "name" , ownerRef .Name )
256+ return false , nil
257+ }
258+
259+ var ownerConditions condition.Conditions
260+ err = json .Unmarshal (conditionsJSON , & ownerConditions )
261+ if err != nil {
262+ log .V (1 ).Info ("Failed to unmarshal owner conditions, waiting" ,
263+ "kind" , ownerRef .Kind , "name" , ownerRef .Name )
264+ return false , nil
265+ }
266+
267+ if ! ownerConditions .IsTrue (condition .ReadyCondition ) {
268+ log .V (1 ).Info ("Owner not ready, waiting" ,
269+ "kind" , ownerRef .Kind , "name" , ownerRef .Name )
270+ return false , nil
271+ }
272+
273+ generation , foundGen , err := unstructured .NestedInt64 (owner .Object , "metadata" , "generation" )
274+ if err != nil || ! foundGen {
275+ log .V (1 ).Info ("Could not get owner generation, waiting" ,
276+ "kind" , ownerRef .Kind , "name" , ownerRef .Name )
277+ return false , nil
278+ }
279+
280+ observedGeneration , foundObsGen , err := unstructured .NestedInt64 (owner .Object , "status" , "observedGeneration" )
281+ if err != nil || ! foundObsGen {
282+ log .V (1 ).Info ("Could not get owner observedGeneration, waiting" ,
283+ "kind" , ownerRef .Kind , "name" , ownerRef .Name )
284+ return false , nil
285+ }
286+
287+ if observedGeneration != generation {
288+ log .V (1 ).Info ("Owner has not reconciled yet, waiting" ,
289+ "kind" , ownerRef .Kind ,
290+ "name" , ownerRef .Name ,
291+ "generation" , generation ,
292+ "observedGeneration" , observedGeneration )
293+ return false , nil
294+ }
295+
296+ log .Info ("Owner is ready" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
297+ return true , nil
298+ }
0 commit comments