Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions modules/common/object/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (

k8s_errors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// CheckOwnerRefExist - returns true if the owner is already in the owner ref list
Expand Down Expand Up @@ -114,3 +116,112 @@ func EnsureOwnerRef(

return nil
}

// IsOwnerServiceReady checks if the owner service that owns this object is ready.
// Returns true if the owner is ready, false if not ready, and error only for unexpected failures.
// If there's no owner with controller=true, it returns true (safe to proceed).
func IsOwnerServiceReady(
ctx context.Context,
h *helper.Helper,
obj client.Object,
) (bool, error) {
// Find the controller owner reference (e.g., Cinder, Nova, etc.)
var ownerRef *metav1.OwnerReference
for _, owner := range obj.GetOwnerReferences() {
if owner.Controller != nil && *owner.Controller {
ownerRef = &owner
break
}
}

// If no controlling owner, safe to proceed
if ownerRef == nil {
h.GetLogger().Info("No controller owner found, owner is considered ready")
return true, nil
}

// Parse the APIVersion to extract group and version
gv, err := schema.ParseGroupVersion(ownerRef.APIVersion)
if err != nil {
h.GetLogger().Error(err, "Failed to parse owner APIVersion", "apiVersion", ownerRef.APIVersion)
return false, err
}

// Fetch the owner resource using unstructured client
owner := &unstructured.Unstructured{}
owner.SetGroupVersionKind(schema.GroupVersionKind{
Group: gv.Group,
Version: gv.Version,
Kind: ownerRef.Kind,
})

err = h.GetClient().Get(ctx, types.NamespacedName{
Name: ownerRef.Name,
Namespace: obj.GetNamespace(),
}, owner)

if err != nil {
if k8s_errors.IsNotFound(err) {
// Owner deleted, safe to proceed
h.GetLogger().Info("Owner resource not found, owner is considered ready", "kind", ownerRef.Kind, "name", ownerRef.Name)
return true, nil
}
// Unexpected error, log and return error
h.GetLogger().Error(err, "Failed to fetch owner resource", "kind", ownerRef.Kind, "name", ownerRef.Name)
return false, err
}

// Check status.conditions for Ready condition
conditions, found, err := unstructured.NestedSlice(owner.Object, "status", "conditions")
if err != nil || !found {
h.GetLogger().Info("No conditions found in owner status, waiting", "kind", ownerRef.Kind, "name", ownerRef.Name)
return false, nil
}
Comment on lines +174 to +179
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't it be easier at some point to marshal/unmarshal the unstructured conditions and use existing functionality from the conditions pkg to validate their status, something like the following, instead of manual parsing the unstructured data?

diff --git a/modules/common/object/metadata.go b/modules/common/object/metadata.go
index fd2fc81..ee93ec3 100644
--- a/modules/common/object/metadata.go
+++ b/modules/common/object/metadata.go
@@ -23,6 +23,7 @@ import (
        "fmt"
        "slices"
 
+       "github.com/openstack-k8s-operators/lib-common/modules/common/condition"
        "github.com/openstack-k8s-operators/lib-common/modules/common/helper"
        "k8s.io/apimachinery/pkg/runtime"
        "k8s.io/apimachinery/pkg/types"
@@ -178,24 +179,22 @@ func IsOwnerServiceReady(
                return false, nil
        }
 
-       // Look for Ready condition with status=True
-       isReady := false
-       for _, c := range conditions {
-               condition, ok := c.(map[string]any)
-               if !ok {
-                       continue
-               }
-
-               condType, _, _ := unstructured.NestedString(condition, "type")
-               status, _, _ := unstructured.NestedString(condition, "status")
+       // Marshal unstructured conditions to condition.Conditions to use existing helper functions
+       conditionsJSON, err := json.Marshal(conditions)
+       if err != nil {
+               h.GetLogger().Info("Failed to marshal owner conditions, waiting", "kind", ownerRef.Kind, "name", ownerRef.Name)
+               return false, nil
+       }
 
-               if condType == "Ready" && status == "True" {
-                       isReady = true
-                       break
-               }
+       var ownerConditions condition.Conditions
+       err = json.Unmarshal(conditionsJSON, &ownerConditions)
+       if err != nil {
+               h.GetLogger().Info("Failed to unmarshal owner conditions, waiting", "kind", ownerRef.Kind, "name", ownerRef.Name)
+               return false, nil
        }
 
-       if !isReady {
+       // Use existing helper function to check if Ready condition is True
+       if !ownerConditions.IsTrue(condition.ReadyCondition) {
                h.GetLogger().Info("Owner service not ready, waiting", "kind", ownerRef.Kind, "name", ownerRef.Name)
                return false, nil
        }


// Look for Ready condition with status=True
isReady := false
for _, c := range conditions {
condition, ok := c.(map[string]any)
if !ok {
continue
}

condType, _, _ := unstructured.NestedString(condition, "type")
status, _, _ := unstructured.NestedString(condition, "status")

if condType == "Ready" && status == "True" {
isReady = true
break
}
}

if !isReady {
h.GetLogger().Info("Owner service not ready, waiting", "kind", ownerRef.Kind, "name", ownerRef.Name)
return false, nil
}

// Check if owner has reconciled (observedGeneration matches generation)
generation, foundGen, err := unstructured.NestedInt64(owner.Object, "metadata", "generation")
if err != nil || !foundGen {
h.GetLogger().Info("Could not get owner generation, waiting", "kind", ownerRef.Kind, "name", ownerRef.Name)
return false, nil
}

observedGeneration, foundObsGen, err := unstructured.NestedInt64(owner.Object, "status", "observedGeneration")
if err != nil || !foundObsGen {
h.GetLogger().Info("Could not get owner observedGeneration, waiting", "kind", ownerRef.Kind, "name", ownerRef.Name)
return false, nil
}

if observedGeneration != generation {
h.GetLogger().Info("Owner service has not reconciled yet (observedGeneration != generation), waiting",
"kind", ownerRef.Kind,
"name", ownerRef.Name,
"generation", generation,
"observedGeneration", observedGeneration)
return false, nil
}

h.GetLogger().Info("Owner service is ready and has reconciled, safe to proceed", "kind", ownerRef.Kind, "name", ownerRef.Name)
return true, nil
}
Loading