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
110 changes: 110 additions & 0 deletions modules/common/object/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -31,6 +32,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 @@ -181,3 +184,110 @@ func ManageConsumerFinalizer(

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 thread
lmiccini marked this conversation as resolved.

// 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
}

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
}

// 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
}

// 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
Loading