diff --git a/controllers/capabilities/application_status_reconciler_test.go b/controllers/capabilities/application_status_reconciler_test.go index f0bc2ae4f..41064bf7e 100644 --- a/controllers/capabilities/application_status_reconciler_test.go +++ b/controllers/capabilities/application_status_reconciler_test.go @@ -1,6 +1,10 @@ package controllers import ( + "reflect" + "testing" + "time" + capabilitiesv1beta1 "github.com/3scale/3scale-operator/apis/capabilities/v1beta1" "github.com/3scale/3scale-operator/pkg/apispkg/common" controllerhelper "github.com/3scale/3scale-operator/pkg/controller/helper" @@ -9,11 +13,9 @@ import ( v1 "github.com/openshift/api/config/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "reflect" + "k8s.io/utils/ptr" logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "testing" - "time" ) func getApplicationCR() (CR *capabilitiesv1beta1.Application) { @@ -42,6 +44,7 @@ func getApplicationCR() (CR *capabilitiesv1beta1.Application) { } return CR } + func getApplicationCRSuspend() (CR *capabilitiesv1beta1.Application) { statusID := int64(3) CR = &capabilitiesv1beta1.Application{ @@ -160,6 +163,7 @@ func getFailedApplicationCR() (CR *capabilitiesv1beta1.Application) { } return CR } + func unknowAccountApplicationCR() (CR *capabilitiesv1beta1.Application) { CR = &capabilitiesv1beta1.Application{ TypeMeta: metav1.TypeMeta{}, @@ -182,6 +186,7 @@ func unknowAccountApplicationCR() (CR *capabilitiesv1beta1.Application) { } return CR } + func getApplicationProductCR() (CR *capabilitiesv1beta1.Product) { // used for string pointer test := "test" @@ -213,7 +218,7 @@ func getApplicationProductCR() (CR *capabilitiesv1beta1.Product) { }, }, Status: capabilitiesv1beta1.ProductStatus{ - ID: create(3), + ID: ptr.To(int64(3)), ProviderAccountHost: "some string", ObservedGeneration: 1, Conditions: common.Conditions{common.Condition{ @@ -233,7 +238,8 @@ func getApplicationProductList() (productList *capabilitiesv1beta1.ProductList) TypeMeta: metav1.TypeMeta{}, ListMeta: metav1.ListMeta{}, Items: []capabilitiesv1beta1.Product{ - {TypeMeta: metav1.TypeMeta{}, + { + TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", @@ -260,7 +266,7 @@ func getApplicationProductList() (productList *capabilitiesv1beta1.ProductList) }, }, Status: capabilitiesv1beta1.ProductStatus{ - ID: create(3), + ID: ptr.To(int64(3)), ProviderAccountHost: "some string", ObservedGeneration: 1, Conditions: nil, @@ -290,7 +296,6 @@ func getProviderAccountRefSecret() (secret *corev1.Secret) { } func getApplicationDeveloperAccount() (CR *capabilitiesv1beta1.DeveloperAccount) { - CR = &capabilitiesv1beta1.DeveloperAccount{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ @@ -306,7 +311,7 @@ func getApplicationDeveloperAccount() (CR *capabilitiesv1beta1.DeveloperAccount) }, }, Status: capabilitiesv1beta1.DeveloperAccountStatus{ - ID: create(3), + ID: ptr.To(int64(3)), ProviderAccountHost: "some string", Conditions: common.Conditions{ common.Condition{ @@ -384,7 +389,7 @@ func TestApplicationStatusReconciler_Reconcile(t *testing.T) { } func TestApplicationStatusReconciler_calculateStatus(t *testing.T) { - //ID := int64(3) + // ID := int64(3) type fields struct { BaseReconciler *reconcilers.BaseReconciler applicationResource *capabilitiesv1beta1.Application diff --git a/controllers/capabilities/backend_threescale_reconciler.go b/controllers/capabilities/backend_threescale_reconciler.go index 93147812d..805c94f97 100644 --- a/controllers/capabilities/backend_threescale_reconciler.go +++ b/controllers/capabilities/backend_threescale_reconciler.go @@ -30,7 +30,6 @@ func NewThreescaleReconciler(b *reconcilers.BaseReconciler, backendRemoteIndex *controllerhelper.BackendAPIRemoteIndex, providerAccount *controllerhelper.ProviderAccount, ) *BackendThreescaleReconciler { - return &BackendThreescaleReconciler{ BaseReconciler: b, backendResource: backendResource, @@ -139,26 +138,6 @@ func (t *BackendThreescaleReconciler) syncMethods(_ interface{}) error { existingMap[systemName] = existing.Element } - // - // Deleted existing and not desired - // - notDesiredExistingKeys := helper.ArrayStringDifference(existingKeys, desiredKeys) - notDesiredMap := map[string]threescaleapi.MethodItem{} - for _, systemName := range notDesiredExistingKeys { - // key is expected to exist - // notDesiredExistingKeys is a subset of the existingMap key set - notDesiredMap[systemName] = existingMap[systemName] - } - err = t.deleteNotDesiredMethodsFrom3scale(notDesiredMap) - if err != nil { - return fmt.Errorf("Error sync backend methods [%s]: %w", t.backendResource.Spec.SystemName, err) - } - - err = t.deleteExternalMetricReferences(notDesiredExistingKeys) - if err != nil { - return fmt.Errorf("Error sync backend methods [%s]: %w", t.backendResource.Spec.SystemName, err) - } - // // Reconcile existing and changed // @@ -191,6 +170,26 @@ func (t *BackendThreescaleReconciler) syncMethods(_ interface{}) error { return fmt.Errorf("Error sync backend methods [%s]: %w", t.backendResource.Spec.SystemName, err) } + // + // Deleted existing and not desired + // + notDesiredExistingKeys := helper.ArrayStringDifference(existingKeys, desiredKeys) + notDesiredMap := map[string]threescaleapi.MethodItem{} + for _, systemName := range notDesiredExistingKeys { + // key is expected to exist + // notDesiredExistingKeys is a subset of the existingMap key set + notDesiredMap[systemName] = existingMap[systemName] + } + err = t.deleteNotDesiredMethodsFrom3scale(notDesiredMap) + if err != nil { + return fmt.Errorf("error sync backend methods [%s]: %w", t.backendResource.Spec.SystemName, err) + } + + err = t.deleteExternalMetricReferences(notDesiredExistingKeys) + if err != nil { + return fmt.Errorf("error sync backend methods [%s]: %w", t.backendResource.Spec.SystemName, err) + } + return nil } @@ -346,27 +345,6 @@ func (t *BackendThreescaleReconciler) syncMetrics(_ interface{}) error { existingMap[systemName] = existing.Element } - // - // Deleted existing and not desired metrics - // - - notDesiredExistingKeys := helper.ArrayStringDifference(existingKeys, desiredKeys) - notDesiredMap := map[string]threescaleapi.MetricItem{} - for _, systemName := range notDesiredExistingKeys { - // key is expected to exist - // notDesiredExistingKeys is a subset of the existingMap key set - notDesiredMap[systemName] = existingMap[systemName] - } - err = t.deleteNotDesiredMetricsFrom3scale(notDesiredMap) - if err != nil { - return fmt.Errorf("Error sync backend metrics [%s]: %w", t.backendResource.Spec.SystemName, err) - } - - err = t.deleteExternalMetricReferences(notDesiredExistingKeys) - if err != nil { - return fmt.Errorf("Error sync backend metrics [%s]: %w", t.backendResource.Spec.SystemName, err) - } - // // Reconcile existing and changed metrics // @@ -401,6 +379,27 @@ func (t *BackendThreescaleReconciler) syncMetrics(_ interface{}) error { return fmt.Errorf("Error sync backend metrics [%s]: %w", t.backendResource.Spec.SystemName, err) } + // + // Deleted existing and not desired metrics + // + + notDesiredExistingKeys := helper.ArrayStringDifference(existingKeys, desiredKeys) + notDesiredMap := map[string]threescaleapi.MetricItem{} + for _, systemName := range notDesiredExistingKeys { + // key is expected to exist + // notDesiredExistingKeys is a subset of the existingMap key set + notDesiredMap[systemName] = existingMap[systemName] + } + err = t.deleteNotDesiredMetricsFrom3scale(notDesiredMap) + if err != nil { + return fmt.Errorf("error sync backend metrics [%s]: %w", t.backendResource.Spec.SystemName, err) + } + + err = t.deleteExternalMetricReferences(notDesiredExistingKeys) + if err != nil { + return fmt.Errorf("error sync backend metrics [%s]: %w", t.backendResource.Spec.SystemName, err) + } + return nil } diff --git a/controllers/capabilities/methods.go b/controllers/capabilities/methods.go index d005c6d89..9802230bf 100644 --- a/controllers/capabilities/methods.go +++ b/controllers/capabilities/methods.go @@ -33,22 +33,6 @@ func (t *ProductThreescaleReconciler) syncMethods(_ interface{}) error { existingMap[systemName] = existing.Element } - // - // Deleted existing and not desired - // - notDesiredExistingKeys := helper.ArrayStringDifference(existingKeys, desiredKeys) - t.logger.V(1).Info("syncMethods", "notDesiredExistingKeys", notDesiredExistingKeys) - notDesiredMap := map[string]threescaleapi.MethodItem{} - for _, systemName := range notDesiredExistingKeys { - // key is expected to exist - // notDesiredExistingKeys is a subset of the existingMap key set - notDesiredMap[systemName] = existingMap[systemName] - } - err = t.processNotDesiredMethods(notDesiredMap) - if err != nil { - return fmt.Errorf("Error sync product methods [%s]: %w", t.resource.Spec.SystemName, err) - } - // // Reconcile existing and changed // @@ -83,6 +67,22 @@ func (t *ProductThreescaleReconciler) syncMethods(_ interface{}) error { return fmt.Errorf("Error sync product methods [%s]: %w", t.resource.Spec.SystemName, err) } + // + // Deleted existing and not desired + // + notDesiredExistingKeys := helper.ArrayStringDifference(existingKeys, desiredKeys) + t.logger.V(1).Info("syncMethods", "notDesiredExistingKeys", notDesiredExistingKeys) + notDesiredMap := map[string]threescaleapi.MethodItem{} + for _, systemName := range notDesiredExistingKeys { + // key is expected to exist + // notDesiredExistingKeys is a subset of the existingMap key set + notDesiredMap[systemName] = existingMap[systemName] + } + err = t.processNotDesiredMethods(notDesiredMap) + if err != nil { + return fmt.Errorf("error sync product methods [%s]: %w", t.resource.Spec.SystemName, err) + } + return nil } diff --git a/controllers/capabilities/metrics.go b/controllers/capabilities/metrics.go index a26681c6a..10af0e1ff 100644 --- a/controllers/capabilities/metrics.go +++ b/controllers/capabilities/metrics.go @@ -33,23 +33,6 @@ func (t *ProductThreescaleReconciler) syncMetrics(_ interface{}) error { existingMap[systemName] = existing.Element } - // - // Deleted existing and not desired metrics - // - - notDesiredExistingKeys := helper.ArrayStringDifference(existingKeys, desiredKeys) - t.logger.V(1).Info("syncMetrics", "notDesiredExistingKeys", notDesiredExistingKeys) - notDesiredMap := map[string]threescaleapi.MetricItem{} - for _, systemName := range notDesiredExistingKeys { - // key is expected to exist - // notDesiredExistingKeys is a subset of the existingMap key set - notDesiredMap[systemName] = existingMap[systemName] - } - err = t.processNotDesiredMetrics(notDesiredMap) - if err != nil { - return fmt.Errorf("Error sync product metrics [%s]: %w", t.resource.Spec.SystemName, err) - } - // // Reconcile existing and changed metrics // @@ -86,6 +69,23 @@ func (t *ProductThreescaleReconciler) syncMetrics(_ interface{}) error { return fmt.Errorf("Error sync product metrics [%s]: %w", t.resource.Spec.SystemName, err) } + // + // Deleted existing and not desired metrics + // + + notDesiredExistingKeys := helper.ArrayStringDifference(existingKeys, desiredKeys) + t.logger.V(1).Info("syncMetrics", "notDesiredExistingKeys", notDesiredExistingKeys) + notDesiredMap := map[string]threescaleapi.MetricItem{} + for _, systemName := range notDesiredExistingKeys { + // key is expected to exist + // notDesiredExistingKeys is a subset of the existingMap key set + notDesiredMap[systemName] = existingMap[systemName] + } + err = t.processNotDesiredMetrics(notDesiredMap) + if err != nil { + return fmt.Errorf("error sync product metrics [%s]: %w", t.resource.Spec.SystemName, err) + } + return nil } diff --git a/controllers/capabilities/proxyconfigpromote_controller.go b/controllers/capabilities/proxyconfigpromote_controller.go index 9ad35a4a0..66ac5123a 100644 --- a/controllers/capabilities/proxyconfigpromote_controller.go +++ b/controllers/capabilities/proxyconfigpromote_controller.go @@ -144,7 +144,10 @@ func (r *ProxyConfigPromoteReconciler) proxyConfigPromoteReconciler(proxyConfigP var currentStagingVersion int // Only proceed with proxyConfigPromote if status of the product is marked as "Completed" - if product.Status.Conditions.IsTrueFor(capabilitiesv1beta1.ProductSyncedConditionType) { + // OR the status is marked with ErrReferencedMethodIsBeingDeleted + failedCond := product.Status.Conditions.GetCondition(capabilitiesv1beta1.ProductFailedConditionType) + if product.Status.Conditions.IsTrueFor(capabilitiesv1beta1.ProductSyncedConditionType) || + (failedCond != nil && failedCond.IsTrue() && failedCond.Message == helper.ErrReferencedMethodIsBeingDeleted.Error()) { productID := product.Status.ID productIDInt64 := *productID productIDStr := strconv.Itoa(int(productIDInt64)) diff --git a/controllers/capabilities/proxyconfigpromote_controller_test.go b/controllers/capabilities/proxyconfigpromote_controller_test.go index 874d83e16..10be4f862 100644 --- a/controllers/capabilities/proxyconfigpromote_controller_test.go +++ b/controllers/capabilities/proxyconfigpromote_controller_test.go @@ -4,27 +4,20 @@ import ( "bytes" "encoding/json" "fmt" - capabilitiesv1beta1 "github.com/3scale/3scale-operator/apis/capabilities/v1beta1" - "github.com/3scale/3scale-operator/pkg/apispkg/common" - "github.com/3scale/3scale-operator/pkg/reconcilers" - "github.com/3scale/3scale-porta-go-client/client" - "github.com/go-logr/logr" "io/ioutil" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "net/http" "reflect" - logf "sigs.k8s.io/controller-runtime/pkg/log" "testing" "time" -) -func create(x int64) *int64 { - return &x -} - -type ( - fakeThreescaleClient struct{} + capabilitiesv1beta1 "github.com/3scale/3scale-operator/apis/capabilities/v1beta1" + "github.com/3scale/3scale-operator/pkg/apispkg/common" + "github.com/3scale/3scale-operator/pkg/helper" + "github.com/3scale/3scale-porta-go-client/client" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + logf "sigs.k8s.io/controller-runtime/pkg/log" ) type RoundTripFunc func(req *http.Request) *http.Response @@ -32,11 +25,13 @@ type RoundTripFunc func(req *http.Request) *http.Response func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req), nil } + func NewTestClient(fn RoundTripFunc) *http.Client { return &http.Client{ Transport: RoundTripFunc(fn), } } + func getProviderAccount() (Secret *v1.Secret) { Secret = &v1.Secret{ TypeMeta: metav1.TypeMeta{}, @@ -53,10 +48,7 @@ func getProviderAccount() (Secret *v1.Secret) { } return Secret } -func newTrue() *bool { - b := true - return &b -} + func getProxyConfigPromoteCRStaging() (CR *capabilitiesv1beta1.ProxyConfigPromote) { CR = &capabilitiesv1beta1.ProxyConfigPromote{ ObjectMeta: metav1.ObjectMeta{ @@ -69,6 +61,7 @@ func getProxyConfigPromoteCRStaging() (CR *capabilitiesv1beta1.ProxyConfigPromot } return CR } + func getProxyConfigPromoteCRProduction() (CR *capabilitiesv1beta1.ProxyConfigPromote) { CR = &capabilitiesv1beta1.ProxyConfigPromote{ ObjectMeta: metav1.ObjectMeta{ @@ -77,7 +70,7 @@ func getProxyConfigPromoteCRProduction() (CR *capabilitiesv1beta1.ProxyConfigPro }, Spec: capabilitiesv1beta1.ProxyConfigPromoteSpec{ ProductCRName: "test", - Production: newTrue(), + Production: ptr.To(true), }, } return CR @@ -88,7 +81,8 @@ func getProductList() (productList *capabilitiesv1beta1.ProductList) { TypeMeta: metav1.TypeMeta{}, ListMeta: metav1.ListMeta{}, Items: []capabilitiesv1beta1.Product{ - {TypeMeta: metav1.TypeMeta{}, + { + TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ Name: "test", Namespace: "test", @@ -99,7 +93,7 @@ func getProductList() (productList *capabilitiesv1beta1.ProductList) { Description: "test", }, Status: capabilitiesv1beta1.ProductStatus{ - ID: create(3), + ID: ptr.To(int64(3)), ProviderAccountHost: "some string", ObservedGeneration: 1, Conditions: nil, @@ -111,7 +105,6 @@ func getProductList() (productList *capabilitiesv1beta1.ProductList) { } func getProductCR() (CR *capabilitiesv1beta1.Product) { - CR = &capabilitiesv1beta1.Product{ TypeMeta: metav1.TypeMeta{}, ObjectMeta: metav1.ObjectMeta{ @@ -124,7 +117,7 @@ func getProductCR() (CR *capabilitiesv1beta1.Product) { Description: "test", }, Status: capabilitiesv1beta1.ProductStatus{ - ID: create(3), + ID: ptr.To(int64(3)), ProviderAccountHost: "some string", ObservedGeneration: 1, Conditions: common.Conditions{common.Condition{ @@ -135,6 +128,7 @@ func getProductCR() (CR *capabilitiesv1beta1.Product) { } return CR } + func mockHttpClient(proxyJson *client.ProxyJSON, productList *client.ProductList, proxyConfigElementSandbox *client.ProxyConfigElement, proxyConfigElementProduction *client.ProxyConfigElement) *http.Client { // override httpClient httpClient := NewTestClient(func(req *http.Request) *http.Response { @@ -207,7 +201,7 @@ func emptyMockHttpClient() *http.Client { func responseBody(class interface{}) (responseBodyBytes []byte) { responseBodyBytes, err := json.Marshal(class) if err != nil { - fmt.Println("json marshal error", "err") + fmt.Println("json marshal error", err) } return responseBodyBytes } @@ -308,32 +302,23 @@ func TestProxyConfigPromoteReconciler_proxyConfigPromoteReconciler(t *testing.T) // new adminportal ap, _ := client.NewAdminPortalFromStr("https://3scale-admin.test.3scale.net") - type fields struct { - BaseReconciler *reconcilers.BaseReconciler - } type args struct { - proxyConfigPromote *capabilitiesv1beta1.ProxyConfigPromote - reqLogger logr.Logger - threescaleAPIClient *client.ThreeScaleClient - product *capabilitiesv1beta1.Product + proxyConfigPromote *capabilitiesv1beta1.ProxyConfigPromote + httpClient *http.Client + product *capabilitiesv1beta1.Product } tests := []struct { name string - fields fields args args want *ProxyConfigPromoteStatusReconciler wantErr bool }{ { name: "Test promotion to Staging Completed", - fields: fields{ - BaseReconciler: getBaseReconciler(), - }, args: args{ - proxyConfigPromote: getProxyConfigPromoteCRStaging(), - reqLogger: logf.Log.WithName("test reqlogger"), - threescaleAPIClient: client.NewThreeScale(ap, "test", mockHttpClient(proxyJson, productList, proxyConfigElementSandbox, failedProxyConfigElementProduction)), - product: getProductCR(), + proxyConfigPromote: getProxyConfigPromoteCRStaging(), + httpClient: mockHttpClient(proxyJson, productList, proxyConfigElementSandbox, failedProxyConfigElementProduction), + product: getProductCR(), }, want: &ProxyConfigPromoteStatusReconciler{ BaseReconciler: getBaseReconciler(), @@ -348,14 +333,10 @@ func TestProxyConfigPromoteReconciler_proxyConfigPromoteReconciler(t *testing.T) }, { name: "Test promotion to Staging not Completed", - fields: fields{ - BaseReconciler: getBaseReconciler(), - }, args: args{ - proxyConfigPromote: getProxyConfigPromoteCRStaging(), - reqLogger: logf.Log.WithName("test reqlogger"), - threescaleAPIClient: client.NewThreeScale(ap, "test", mockHttpClient(proxyJson, productList, proxyConfigElementSandbox, failedProxyConfigElementProduction)), - product: getProductCR(), + proxyConfigPromote: getProxyConfigPromoteCRStaging(), + httpClient: mockHttpClient(proxyJson, productList, proxyConfigElementSandbox, failedProxyConfigElementProduction), + product: getProductCR(), }, want: &ProxyConfigPromoteStatusReconciler{ BaseReconciler: getBaseReconciler(), @@ -370,14 +351,10 @@ func TestProxyConfigPromoteReconciler_proxyConfigPromoteReconciler(t *testing.T) }, { name: "Test empty staging environment response", - fields: fields{ - BaseReconciler: getBaseReconciler(), - }, args: args{ - proxyConfigPromote: getProxyConfigPromoteCRStaging(), - reqLogger: logf.Log.WithName("test reqlogger"), - threescaleAPIClient: client.NewThreeScale(ap, "test", emptyMockHttpClient()), - product: getProductCR(), + proxyConfigPromote: getProxyConfigPromoteCRStaging(), + httpClient: emptyMockHttpClient(), + product: getProductCR(), }, want: &ProxyConfigPromoteStatusReconciler{ BaseReconciler: getBaseReconciler(), @@ -392,14 +369,10 @@ func TestProxyConfigPromoteReconciler_proxyConfigPromoteReconciler(t *testing.T) }, { name: "Test promotion to Production Completed", - fields: fields{ - BaseReconciler: getBaseReconciler(), - }, args: args{ - proxyConfigPromote: getProxyConfigPromoteCRProduction(), - reqLogger: logf.Log.WithName("test reqlogger"), - threescaleAPIClient: client.NewThreeScale(ap, "test", mockHttpClient(proxyJson, productList, proxyConfigElementSandbox, proxyConfigElementProduction)), - product: getProductCR(), + proxyConfigPromote: getProxyConfigPromoteCRProduction(), + httpClient: mockHttpClient(proxyJson, productList, proxyConfigElementSandbox, proxyConfigElementProduction), + product: getProductCR(), }, want: &ProxyConfigPromoteStatusReconciler{ BaseReconciler: getBaseReconciler(), @@ -413,25 +386,82 @@ func TestProxyConfigPromoteReconciler_proxyConfigPromoteReconciler(t *testing.T) wantErr: false, }, { - name: "Test promotion to Production Failed", - fields: fields{ - BaseReconciler: getBaseReconciler(), - }, + name: "Test promotion with Product in Failed status", args: args{ - proxyConfigPromote: getProxyConfigPromoteCRProduction(), - reqLogger: logf.Log.WithName("test reqlogger"), - threescaleAPIClient: client.NewThreeScale(ap, "test", mockHttpClient(proxyJson, productList, failedProxyConfigElementSandbox, failedProxyConfigElementProduction)), - product: getProductCR(), + proxyConfigPromote: getProxyConfigPromoteCRProduction(), + httpClient: mockHttpClient(proxyJson, productList, failedProxyConfigElementSandbox, failedProxyConfigElementProduction), + product: &capabilitiesv1beta1.Product{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: capabilitiesv1beta1.ProductSpec{ + Name: "test", + SystemName: "test", + Description: "test", + }, + Status: capabilitiesv1beta1.ProductStatus{ + ID: ptr.To(int64(3)), + ProviderAccountHost: "some string", + ObservedGeneration: 1, + Conditions: common.Conditions{common.Condition{ + Type: capabilitiesv1beta1.ProductFailedConditionType, + Status: v1.ConditionTrue, + }}, + }, + }, }, wantErr: true, }, + { + name: "Allow promote Product with Failed and ErrReferencedMethodIsBeingDeleted message ", + args: args{ + proxyConfigPromote: getProxyConfigPromoteCRProduction(), + httpClient: mockHttpClient(proxyJson, productList, proxyConfigElementSandbox, proxyConfigElementProduction), + product: &capabilitiesv1beta1.Product{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: capabilitiesv1beta1.ProductSpec{ + Name: "test", + SystemName: "test", + Description: "test", + }, + Status: capabilitiesv1beta1.ProductStatus{ + ID: ptr.To(int64(3)), + ProviderAccountHost: "some string", + ObservedGeneration: 1, + Conditions: common.Conditions{common.Condition{ + Type: capabilitiesv1beta1.ProductFailedConditionType, + Message: helper.ErrReferencedMethodIsBeingDeleted.Error(), + Status: v1.ConditionTrue, + }}, + }, + }, + }, + want: &ProxyConfigPromoteStatusReconciler{ + BaseReconciler: getBaseReconciler(), + resource: getProxyConfigPromoteCRProduction(), + productID: "3", + latestProductionVersion: 1, + latestStagingVersion: 1, + reconcileError: nil, + logger: logf.Log.WithValues("Status Reconciler", "test"), + }, + wantErr: false, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { r := &ProxyConfigPromoteReconciler{ - BaseReconciler: tt.fields.BaseReconciler, + BaseReconciler: getBaseReconciler(), } - got, err := r.proxyConfigPromoteReconciler(tt.args.proxyConfigPromote, tt.args.reqLogger, tt.args.threescaleAPIClient, tt.args.product) + reqLogger := logf.Log.WithName("test reqlogger") + threescaleAPIClient := client.NewThreeScale(ap, "test", tt.args.httpClient) + got, err := r.proxyConfigPromoteReconciler(tt.args.proxyConfigPromote, reqLogger, threescaleAPIClient, tt.args.product) if (err != nil) && tt.wantErr { t.Logf("proxyConfigPromoteReconciler(), wantErr %v", tt.wantErr) return diff --git a/pkg/controller/helper/backend_list.go b/pkg/controller/helper/backend_list.go index 4fc2d6bd7..dfe3763f9 100644 --- a/pkg/controller/helper/backend_list.go +++ b/pkg/controller/helper/backend_list.go @@ -5,6 +5,7 @@ import ( "fmt" capabilitiesv1beta1 "github.com/3scale/3scale-operator/apis/capabilities/v1beta1" + "github.com/3scale/3scale-operator/pkg/helper" "github.com/go-logr/logr" "sigs.k8s.io/controller-runtime/pkg/client" @@ -27,22 +28,21 @@ func BackendList(ns string, cl client.Client, providerAccountURLStr string, logg logger.V(1).Info("Backend resources", "total", len(backendList.Items)) validBackends := make([]capabilitiesv1beta1.Backend, 0) - for idx := range backendList.Items { + for idx, backend := range backendList.Items { // Filter by synchronized - if !backendList.Items[idx].IsSynced() { - continue + failedCond := backend.Status.Conditions.GetCondition(capabilitiesv1beta1.BackendFailedConditionType) + if backend.IsSynced() || (failedCond != nil && failedCond.IsTrue() && failedCond.Message == helper.ErrReferencedMethodIsBeingDeleted.Error()) { + backendProviderAccount, err := LookupProviderAccount(cl, ns, backendList.Items[idx].Spec.ProviderAccountRef, logger) + if err != nil { + return nil, fmt.Errorf("BackendList: %w", err) + } + + // Filter by provider account + if providerAccountURLStr != backendProviderAccount.AdminURLStr { + continue + } + validBackends = append(validBackends, backendList.Items[idx]) } - - backendProviderAccount, err := LookupProviderAccount(cl, ns, backendList.Items[idx].Spec.ProviderAccountRef, logger) - if err != nil { - return nil, fmt.Errorf("BackendList: %w", err) - } - - // Filter by provider account - if providerAccountURLStr != backendProviderAccount.AdminURLStr { - continue - } - validBackends = append(validBackends, backendList.Items[idx]) } logger.V(1).Info("Backend valid resources", "total", len(validBackends)) diff --git a/pkg/helper/errors.go b/pkg/helper/errors.go index d152b67df..d87d53953 100644 --- a/pkg/helper/errors.go +++ b/pkg/helper/errors.go @@ -1,9 +1,15 @@ package helper import ( + "errors" + "k8s.io/apimachinery/pkg/util/validation/field" ) +// ErrReferencedMethodIsBeingDeleted is an error type that is returned when +// an active Metric entity references in Product or Backend which is being deleted. +var ErrReferencedMethodIsBeingDeleted = errors.New("method is used by the latest gateway configuration, it will be removed from 3scale only once the promotion without the mapping rule that uses the deleted method is done") + type FieldTypeError int const ( diff --git a/pkg/helper/task_runner.go b/pkg/helper/task_runner.go index f543c134d..201c6ab7b 100644 --- a/pkg/helper/task_runner.go +++ b/pkg/helper/task_runner.go @@ -51,7 +51,7 @@ func (t *taskRunnerImpl) Run() error { t.logger.V(1).Info("Measure", task.Name, elapsed) } if reqerr { - return fmt.Errorf("Method is used by the latest gateway configuration, it will be removed from 3scale only once the promotion without the mapping rule that uses the deleted method is done.") + return ErrReferencedMethodIsBeingDeleted } return nil }