Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MGDAPI-3467 tenant deletion support #715

Merged
merged 10 commits into from
Feb 24, 2022
82 changes: 68 additions & 14 deletions controllers/capabilities/tenant_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,27 @@ import (
"fmt"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"

capabilitiesv1alpha1 "github.com/3scale/3scale-operator/apis/capabilities/v1alpha1"
"github.com/3scale/3scale-operator/pkg/3scale/amp/component"
controllerhelper "github.com/3scale/3scale-operator/pkg/controller/helper"
)

// tenant deletion state
const scheduledForDeletionState = "scheduled_for_deletion"

// tenant finalizer
const tenantFinalizer = "tenant.capabilities.3scale.net/finalizer"

// Secret field name with Tenant's admin user password
const TenantAdminPasswordSecretField = "admin_password"

Expand All @@ -45,9 +54,10 @@ const TenantAdminDomainKeySecretField = "adminURL"

// TenantReconciler reconciles a Tenant object
type TenantReconciler struct {
Client client.Client
Log logr.Logger
Scheme *runtime.Scheme
Client client.Client
Log logr.Logger
Scheme *runtime.Scheme
EventRecorder record.EventRecorder
}

// +kubebuilder:rbac:groups=capabilities.3scale.net,namespace=placeholder,resources=tenants,verbs=get;list;watch;create;update;patch;delete
Expand All @@ -73,17 +83,6 @@ func (r *TenantReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, err
}

changed := tenantR.SetDefaults()
MStokluska marked this conversation as resolved.
Show resolved Hide resolved
if changed {
err = r.Client.Update(context.TODO(), tenantR)
if err != nil {
return ctrl.Result{}, err
}
reqLogger.Info("Tenant resource updated with defaults")
// Expect for re-trigger
return ctrl.Result{}, nil
}

masterAccessToken, err := r.FetchMasterCredentials(r.Client, tenantR)
MStokluska marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
reqLogger.Error(err, "Error fetching master credentials secret")
Expand All @@ -98,6 +97,61 @@ func (r *TenantReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, err
}

// Tenant has been marked for deletion
if tenantR.GetDeletionTimestamp() != nil && controllerutil.ContainsFinalizer(tenantR, tenantFinalizer) {
eguzki marked this conversation as resolved.
Show resolved Hide resolved
existingTenant, err := controllerhelper.FetchTenant(tenantR, portaClient)
if err != nil {
return ctrl.Result{}, err
}

// delete tenantCR if tenant is present in 3scale
if existingTenant != nil {
// do not attempt to delete tenant that is already scheduled for deletion
if existingTenant.Signup.Account.State != scheduledForDeletionState {
err := portaClient.DeleteTenant(tenantR.Status.TenantId)
if err != nil {
r.EventRecorder.Eventf(tenantR, corev1.EventTypeWarning, "Failed to delete tenant", "%v", err)
return ctrl.Result{}, err
}
} else {
reqLogger.Info("Removing tenant CR - tenant is already scheduled for deletion %v", existingTenant.Signup.Account.ID)
}
}

// add or remove finalizer, tenant CR will be deleted if tenant in 3scale does not exists, if tenant is deleted or if it's already marked for deletion
err = controllerhelper.ReconcileFinalizers(tenantR, r.Client, tenantFinalizer)
if err != nil {
return ctrl.Result{}, err
}

return ctrl.Result{}, nil
}

// Ignore deleted resources, this can happen when foregroundDeletion is enabled
// https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/#foreground-cascading-deletion
if tenantR.GetDeletionTimestamp() != nil {
return ctrl.Result{}, nil
}

eguzki marked this conversation as resolved.
Show resolved Hide resolved
if !controllerutil.ContainsFinalizer(tenantR, tenantFinalizer) {
err = controllerhelper.ReconcileFinalizers(tenantR, r.Client, tenantFinalizer)
if err != nil {
return ctrl.Result{}, err
}
return ctrl.Result{}, nil
}

changed := tenantR.SetDefaults()
if changed {
err = r.Client.Update(context.TODO(), tenantR)
if err != nil {
return ctrl.Result{}, err
}
reqLogger.Info("Tenant resource updated with defaults")
// Expect for re-trigger
return ctrl.Result{}, nil
eguzki marked this conversation as resolved.
Show resolved Hide resolved
}

internalReconciler := NewTenantInternalReconciler(r.Client, tenantR, portaClient, reqLogger)
err = internalReconciler.Run()
if err != nil {
Expand Down
18 changes: 1 addition & 17 deletions controllers/capabilities/tenant_internal_reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (r *TenantInternalReconciler) Run() error {
// * tenant will exist
// * tenant's attributes will be updated if required
func (r *TenantInternalReconciler) reconcileTenant() (*porta_client_pkg.Tenant, error) {
tenantDef, err := r.fetchTenant()
tenantDef, err := controllerhelper.FetchTenant(r.tenantR, r.portaClient)
if err != nil {
return nil, err
}
Expand All @@ -90,22 +90,6 @@ func (r *TenantInternalReconciler) reconcileTenant() (*porta_client_pkg.Tenant,
return tenantDef, nil
}

func (r *TenantInternalReconciler) fetchTenant() (*porta_client_pkg.Tenant, error) {
if r.tenantR.Status.TenantId == 0 {
// tenantId not in status field
// Tenant has to be created
return nil, nil
}

tenantDef, err := r.portaClient.ShowTenant(r.tenantR.Status.TenantId)
if err != nil && porta_client_pkg.IsNotFound(err) {
return nil, nil
} else if err != nil {
return nil, err
}
return tenantDef, nil
}

func (r *TenantInternalReconciler) syncTenant(tenantDef *porta_client_pkg.Tenant) error {
// If tenant desired state is not current state, update
triggerSync := func() bool {
Expand Down
5 changes: 5 additions & 0 deletions doc/operator-application-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ The following diagram shows available custom resource definitions and their rela
* [Tenant custom resource](#tenant-custom-resource)
* [Preparation before deploying the new tenant](#preparation-before-deploying-the-new-tenant)
* [Deploy the new tenant custom resource](#deploy-the-new-tenant-custom-resource)
* [Tenant deletion](#tenant-deletion)
* [DeveloperAccount custom resource](#developeraccount-custom-resource)
* [DeveloperAccount custom resource status field](#developeraccount-custom-resource-status-field)
* [Link your DeveloperAccount to your 3scale tenant or provider account](#link-your-developeraccount-to-your-3scale-tenant-or-provider-account)
Expand Down Expand Up @@ -1171,6 +1172,10 @@ stringData:

Refer to [Tenant CRD Reference](tenant-reference.md) documentation for more information.

### Tenant deletion

If a tenant has been created via CR it can be marked for deletion in 3scale API Management solution by deleting the tenant CR.

## DeveloperAccount custom resource

The minimum configuration required to deploy and manage one 3scale developer account is:
Expand Down
7 changes: 4 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,10 @@ func main() {
}

if err = (&capabilitiescontroller.TenantReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Tenant"),
Scheme: mgr.GetScheme(),
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Tenant"),
Scheme: mgr.GetScheme(),
EventRecorder: mgr.GetEventRecorderFor("Tenant"),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Tenant")
os.Exit(1)
Expand Down
31 changes: 31 additions & 0 deletions pkg/controller/helper/finalizers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package helper

import (
"context"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

/*
ReconcileFinalizers reconciles the finalizers, it requires
- object
- k8client
- finalizer
If the deletion timestamp is found, the finalizer will be removed.
If the deletion timestamp is not present, a finalizer will be reconciled
*/
func ReconcileFinalizers(object controllerutil.Object, client client.Client, finalizer string) error {
var err error
if object.GetDeletionTimestamp() == nil {
controllerutil.AddFinalizer(object, finalizer)
err = client.Update(context.TODO(), object)
} else {
controllerutil.RemoveFinalizer(object, finalizer)
err = client.Update(context.TODO(), object)
}
if err != nil {
return err
}

return nil
}
27 changes: 27 additions & 0 deletions pkg/controller/helper/tenant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package helper

import (
capabilitiesv1alpha1 "github.com/3scale/3scale-operator/apis/capabilities/v1alpha1"
porta_client_pkg "github.com/3scale/3scale-porta-go-client/client"
)

/*
FetchTenant fetches tenant from 3scale
- tenant
- portaClient
*/
func FetchTenant(tenant *capabilitiesv1alpha1.Tenant, portaClient *porta_client_pkg.ThreeScaleClient) (*porta_client_pkg.Tenant, error) {
if tenant.Status.TenantId == 0 {
// tenantId not in status field
// Tenant has to be created
return nil, nil
}

tenantDef, err := portaClient.ShowTenant(tenant.Status.TenantId)
if err != nil && porta_client_pkg.IsNotFound(err) {
return nil, nil
} else if err != nil {
return nil, err
}
return tenantDef, nil
}