diff --git a/api/bases/glance.openstack.org_glances.yaml b/api/bases/glance.openstack.org_glances.yaml index 1eb359973..341ec44af 100644 --- a/api/bases/glance.openstack.org_glances.yaml +++ b/api/bases/glance.openstack.org_glances.yaml @@ -1037,6 +1037,11 @@ spec: type: array databaseHostname: type: string + glanceAPIReadyCounts: + additionalProperties: + format: int32 + type: integer + type: object hash: additionalProperties: type: string diff --git a/api/v1beta1/glance_types.go b/api/v1beta1/glance_types.go index 234755d85..0fcb7ad24 100644 --- a/api/v1beta1/glance_types.go +++ b/api/v1beta1/glance_types.go @@ -165,6 +165,9 @@ type GlanceStatus struct { // Glance Database Hostname DatabaseHostname string `json:"databaseHostname,omitempty"` + + // GlanceAPIReadyCounts - + GlanceAPIReadyCounts map[string]int32 `json:"glanceAPIReadyCounts,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index d896ac8c2..5c36f8049 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -433,6 +433,13 @@ func (in *GlanceStatus) DeepCopyInto(out *GlanceStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + if in.GlanceAPIReadyCounts != nil { + in, out := &in.GlanceAPIReadyCounts, &out.GlanceAPIReadyCounts + *out = make(map[string]int32, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GlanceStatus. diff --git a/config/crd/bases/glance.openstack.org_glances.yaml b/config/crd/bases/glance.openstack.org_glances.yaml index 1eb359973..341ec44af 100644 --- a/config/crd/bases/glance.openstack.org_glances.yaml +++ b/config/crd/bases/glance.openstack.org_glances.yaml @@ -1037,6 +1037,11 @@ spec: type: array databaseHostname: type: string + glanceAPIReadyCounts: + additionalProperties: + format: int32 + type: integer + type: object hash: additionalProperties: type: string diff --git a/controllers/glance_controller.go b/controllers/glance_controller.go index 275c3db28..604047a1b 100644 --- a/controllers/glance_controller.go +++ b/controllers/glance_controller.go @@ -165,7 +165,7 @@ func (r *GlanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res instance.Status.Conditions.Init(&cl) // Register overall status immediately to have an early feedback e.g. in the cli - return ctrl.Result{}, nil + return ctrl.Result{}, err } if instance.Status.Hash == nil { instance.Status.Hash = map[string]string{} @@ -174,6 +174,10 @@ func (r *GlanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res instance.Status.APIEndpoints = map[string]string{} } + if instance.Status.GlanceAPIReadyCounts == nil { + instance.Status.GlanceAPIReadyCounts = map[string]int32{} + } + // Handle service delete if !instance.DeletionTimestamp.IsZero() { return r.reconcileDelete(ctx, instance, helper) @@ -231,7 +235,7 @@ func (r *GlanceReconciler) reconcileDelete(ctx context.Context, instance *glance } } - // Remove the finalizer on rach GlanceAPI CR + // Remove the finalizer on each GlanceAPI CR for name := range instance.Spec.GlanceAPIs { err = r.removeAPIFinalizer(ctx, instance, helper, name) if err != nil { @@ -591,6 +595,10 @@ func (r *GlanceReconciler) reconcileNormal(ctx context.Context, instance *glance if err != nil { return ctrl.Result{}, err } + err = r.glanceAPICleanup(ctx, instance) + if err != nil { + return ctrl.Result{}, err + } } // create CronJobs: DBPurge (always), CacheCleaner and CachePruner if image-cache // is enabled @@ -686,6 +694,11 @@ func (r *GlanceReconciler) ensureAPIDeployment( r.Log.Info(fmt.Sprintf("StatefulSet %s successfully reconciled - operation: %s", instance.Name, string(op))) } + if instance.Status.GlanceAPIReadyCounts == nil { + instance.Status.GlanceAPIReadyCounts = map[string]int32{} + } + instance.Status.GlanceAPIReadyCounts[apiName] = glanceAPI.Status.ReadyCount + apiPubEndpoint := fmt.Sprintf("%s-%s", apiName, string(endpoint.EndpointPublic)) apiIntEndpoint := fmt.Sprintf("%s-%s", apiName, string(endpoint.EndpointInternal)) @@ -956,3 +969,47 @@ func (r *GlanceReconciler) registeredLimitsDelete( } return nil } + +// GlanceAPICleanup - Delete the glanceAPI instance if it no longer appears +// in the spec. +func (r *GlanceReconciler) glanceAPICleanup(ctx context.Context, instance *glancev1.Glance) error { + // Generate a list of GlanceAPI CRs + apis := &glancev1.GlanceAPIList{} + listOpts := []client.ListOption{ + client.InNamespace(instance.Namespace), + } + if err := r.Client.List(ctx, apis, listOpts...); err != nil { + r.Log.Error(err, "Unable to retrieve GlanceAPI CRs %v") + return nil + } + + for _, glanceAPI := range apis.Items { + // Skip any GlanceAPI that we don't own + if glance.GetOwningGlanceName(&glanceAPI) != instance.Name { + continue + } + apiName := glance.GetGlanceAPIName(glanceAPI.Name) + // Simply return if the apiName doesn't match the existing pattern, log but do not + // raise an error + if apiName == "" { + r.Log.Info(fmt.Sprintf("GlanceAPI %s does not match the pattern", glanceAPI.Name)) + return nil + } + // Delete the api if it's no longer in the spec + _, exists := instance.Spec.GlanceAPIs[apiName] + if !exists && glanceAPI.DeletionTimestamp.IsZero() { + err := r.Client.Delete(ctx, &glanceAPI) + if err != nil && !k8s_errors.IsNotFound(err) { + err = fmt.Errorf("Error cleaning up %s: %w", glanceAPI.Name, err) + return err + } + // Update the APIEndpoints in the top-level CR + endpoints := []endpoint.Endpoint{endpoint.EndpointPublic, endpoint.EndpointInternal} + for _, ep := range endpoints { + endpointKey := fmt.Sprintf("%s-%s", apiName, ep) + delete(instance.Status.APIEndpoints, endpointKey) + } + } + } + return nil +} diff --git a/pkg/glance/funcs.go b/pkg/glance/funcs.go index a5822a44e..282a47200 100644 --- a/pkg/glance/funcs.go +++ b/pkg/glance/funcs.go @@ -1,6 +1,7 @@ package glance import ( + glancev1 "github.com/openstack-k8s-operators/glance-operator/api/v1beta1" "sigs.k8s.io/controller-runtime/pkg/client" "strings" ) @@ -40,3 +41,38 @@ func GetEnabledBackends(customServiceConfig string) []string { } return availableBackends } + +// GetGlanceAPIName - For a given full glanceAPIName passed as input, this utility +// resolves the name used in the glance CR to identify the API. +func GetGlanceAPIName(name string) string { + + /*** + A given GlanceAPI name can be found in the form: + + +--------------------------------------------------------+ + | "glance.ServiceName + instance.Name + instance.Type" | + +--------------------------------------------------------+ + + but only "instance.Name" is used to identify the glanceAPI instance in + the main CR. For this reason we cut the string passed as input and we + trim both prefix and suffix. + + Example: + input = "glance-api1-internal" + output = "api1" + ***/ + var api = "" + prefix := ServiceName + "-" + suffixes := []string{ + glancev1.APIInternal, + glancev1.APIExternal, + glancev1.APISingle, + } + for _, suffix := range suffixes { + if strings.Contains(name, suffix) { + apiName := strings.TrimSuffix(name, "-"+suffix) + api = apiName[len(prefix):] + } + } + return api +}