@@ -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
@@ -114,3 +117,110 @@ func EnsureOwnerRef(
114117
115118 return nil
116119}
120+
121+ // IsOwnerServiceReady checks if the owner service that owns this object is ready.
122+ // Returns true if the owner is ready, false if not ready, and error only for unexpected failures.
123+ // If there's no owner with controller=true, it returns true (safe to proceed).
124+ func IsOwnerServiceReady (
125+ ctx context.Context ,
126+ h * helper.Helper ,
127+ obj client.Object ,
128+ ) (bool , error ) {
129+ // Find the controller owner reference (e.g., Cinder, Nova, etc.)
130+ var ownerRef * metav1.OwnerReference
131+ for _ , owner := range obj .GetOwnerReferences () {
132+ if owner .Controller != nil && * owner .Controller {
133+ ownerRef = & owner
134+ break
135+ }
136+ }
137+
138+ // If no controlling owner, safe to proceed
139+ if ownerRef == nil {
140+ h .GetLogger ().Info ("No controller owner found, owner is considered ready" )
141+ return true , nil
142+ }
143+
144+ // Parse the APIVersion to extract group and version
145+ gv , err := schema .ParseGroupVersion (ownerRef .APIVersion )
146+ if err != nil {
147+ h .GetLogger ().Error (err , "Failed to parse owner APIVersion" , "apiVersion" , ownerRef .APIVersion )
148+ return false , err
149+ }
150+
151+ // Fetch the owner resource using unstructured client
152+ owner := & unstructured.Unstructured {}
153+ owner .SetGroupVersionKind (schema.GroupVersionKind {
154+ Group : gv .Group ,
155+ Version : gv .Version ,
156+ Kind : ownerRef .Kind ,
157+ })
158+
159+ err = h .GetClient ().Get (ctx , types.NamespacedName {
160+ Name : ownerRef .Name ,
161+ Namespace : obj .GetNamespace (),
162+ }, owner )
163+
164+ if err != nil {
165+ if k8s_errors .IsNotFound (err ) {
166+ // Owner deleted, safe to proceed
167+ h .GetLogger ().Info ("Owner resource not found, owner is considered ready" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
168+ return true , nil
169+ }
170+ // Unexpected error, log and return error
171+ h .GetLogger ().Error (err , "Failed to fetch owner resource" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
172+ return false , err
173+ }
174+
175+ // Check status.conditions for Ready condition
176+ conditions , found , err := unstructured .NestedSlice (owner .Object , "status" , "conditions" )
177+ if err != nil || ! found {
178+ h .GetLogger ().Info ("No conditions found in owner status, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
179+ return false , nil
180+ }
181+
182+ // Marshal unstructured conditions to condition.Conditions to use existing helper functions
183+ conditionsJSON , err := json .Marshal (conditions )
184+ if err != nil {
185+ h .GetLogger ().Info ("Failed to marshal owner conditions, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
186+ return false , nil
187+ }
188+
189+ var ownerConditions condition.Conditions
190+ err = json .Unmarshal (conditionsJSON , & ownerConditions )
191+ if err != nil {
192+ h .GetLogger ().Info ("Failed to unmarshal owner conditions, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
193+ return false , nil
194+ }
195+
196+ // Use existing helper function to check if Ready condition is True
197+ if ! ownerConditions .IsTrue (condition .ReadyCondition ) {
198+ h .GetLogger ().Info ("Owner service not ready, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
199+ return false , nil
200+ }
201+
202+ // Check if owner has reconciled (observedGeneration matches generation)
203+ generation , foundGen , err := unstructured .NestedInt64 (owner .Object , "metadata" , "generation" )
204+ if err != nil || ! foundGen {
205+ h .GetLogger ().Info ("Could not get owner generation, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
206+ return false , nil
207+ }
208+
209+ observedGeneration , foundObsGen , err := unstructured .NestedInt64 (owner .Object , "status" , "observedGeneration" )
210+ if err != nil || ! foundObsGen {
211+ h .GetLogger ().Info ("Could not get owner observedGeneration, waiting" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
212+ return false , nil
213+ }
214+
215+ if observedGeneration != generation {
216+ h .GetLogger ().Info ("Owner service has not reconciled yet (observedGeneration != generation), waiting" ,
217+ "kind" , ownerRef .Kind ,
218+ "name" , ownerRef .Name ,
219+ "generation" , generation ,
220+ "observedGeneration" , observedGeneration )
221+ return false , nil
222+ }
223+
224+ h .GetLogger ().Info ("Owner service is ready and has reconciled, safe to proceed" , "kind" , ownerRef .Kind , "name" , ownerRef .Name )
225+ return true , nil
226+ }
0 commit comments