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