github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/process/deprovisioning/btp_operator_cleanup_test.go (about)

     1  package deprovisioning
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/kyma-project/control-plane/components/provisioner/pkg/gqlschema"
     9  	"github.com/kyma-project/kyma-environment-broker/internal"
    10  	"github.com/kyma-project/kyma-environment-broker/internal/broker"
    11  	"github.com/kyma-project/kyma-environment-broker/internal/fixture"
    12  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    13  	"github.com/sirupsen/logrus"
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  	corev1 "k8s.io/api/core/v1"
    17  	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    18  	"k8s.io/apimachinery/pkg/api/meta"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    21  	"k8s.io/apimachinery/pkg/runtime"
    22  	"k8s.io/apimachinery/pkg/runtime/schema"
    23  	"k8s.io/apimachinery/pkg/runtime/serializer"
    24  	"sigs.k8s.io/controller-runtime/pkg/client"
    25  	"sigs.k8s.io/controller-runtime/pkg/client/fake"
    26  )
    27  
    28  var siCRD = []byte(`
    29  apiVersion: apiextensions.k8s.io/v1
    30  kind: CustomResourceDefinition
    31  metadata:
    32    name: serviceinstances.services.cloud.sap.com
    33  spec:
    34    group: services.cloud.sap.com
    35    names:
    36      kind: ServiceInstance
    37      listKind: ServiceInstanceList
    38      plural: serviceinstances
    39      singular: serviceinstance
    40    scope: Namespaced
    41  `)
    42  
    43  var sbCRD = []byte(`
    44  apiVersion: apiextensions.k8s.io/v1
    45  kind: CustomResourceDefinition
    46  metadata:
    47    name: servicebindings.services.cloud.sap.com
    48  spec:
    49    group: services.cloud.sap.com
    50    names:
    51      kind: ServiceBinding
    52      listKind: ServiceBindingList
    53      plural: servicebindings
    54      singular: servicebinding
    55    scope: Namespaced
    56  `)
    57  
    58  func TestProvisionerDoesNotProvideKubeconfig(t *testing.T) {
    59  	// given
    60  	log := logrus.New()
    61  	ms := storage.NewMemoryStorage()
    62  	fakeProvisionerClient := newEmptyProvisionerClient()
    63  	step := NewBTPOperatorCleanupStep(ms.Operations(), fakeProvisionerClient, func(k string) (client.Client, error) { return nil, nil })
    64  	op := fixture.FixSuspensionOperationAsOperation(fixOperationID, fixInstanceID)
    65  	op.State = "in progress"
    66  
    67  	// when
    68  	_, backoff, err := step.Run(op, log)
    69  
    70  	// then
    71  	assert.NoError(t, err)
    72  	assert.Zero(t, backoff)
    73  
    74  }
    75  
    76  func TestRemoveServiceInstanceStep(t *testing.T) {
    77  	t.Run("should remove all service instances and bindings from btp operator as part of trial suspension", func(t *testing.T) {
    78  		// given
    79  		log := logrus.New()
    80  		ms := storage.NewMemoryStorage()
    81  		si := &unstructured.Unstructured{Object: map[string]interface{}{
    82  			"apiVersion": "apiextensions.k8s.io/v1",
    83  			"kind":       "ServiceInstance",
    84  			"metadata": map[string]interface{}{
    85  				"name":      "test-instance",
    86  				"namespace": "kyma-system",
    87  			},
    88  		}}
    89  		ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "kyma-system"}}
    90  
    91  		scheme := internal.NewSchemeForTests()
    92  		err := apiextensionsv1.AddToScheme(scheme)
    93  		decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer()
    94  		obj, gvk, err := decoder.Decode(siCRD, nil, nil)
    95  		fmt.Println(gvk)
    96  		require.NoError(t, err)
    97  
    98  		k8sCli := &fakeK8sClientWrapper{fake: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(obj, ns).Build()}
    99  		err = k8sCli.Create(context.TODO(), si)
   100  		require.NoError(t, err)
   101  
   102  		op := fixture.FixSuspensionOperationAsOperation(fixOperationID, fixInstanceID)
   103  		op.State = "in progress"
   104  		fakeProvisionerClient := fakeProvisionerClient{}
   105  		step := NewBTPOperatorCleanupStep(ms.Operations(), fakeProvisionerClient, func(k string) (client.Client, error) { return k8sCli, nil })
   106  
   107  		// when
   108  		entry := log.WithFields(logrus.Fields{"step": "TEST"})
   109  		_, _, err = step.Run(op, entry)
   110  
   111  		// then
   112  		assert.NoError(t, err)
   113  
   114  		// given
   115  		emptySI := &unstructured.Unstructured{}
   116  		emptySI.SetGroupVersionKind(schema.GroupVersionKind{
   117  			Group:   "services.cloud.sap.com",
   118  			Version: "v1",
   119  			Kind:    "ServiceInstance",
   120  		})
   121  
   122  		// then
   123  		assert.True(t, k8sCli.cleanupInstances)
   124  		assert.True(t, k8sCli.cleanupBindings)
   125  	})
   126  
   127  	t.Run("should skip btp-cleanup if not trial", func(t *testing.T) {
   128  		log := logrus.New()
   129  		ms := storage.NewMemoryStorage()
   130  		si := &unstructured.Unstructured{Object: map[string]interface{}{
   131  			"apiVersion": "apiextensions.k8s.io/v1",
   132  			"kind":       "ServiceInstance",
   133  			"metadata": map[string]interface{}{
   134  				"name":      "test-instance",
   135  				"namespace": "kyma-system",
   136  			},
   137  		}}
   138  		ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "kyma-system"}}
   139  
   140  		scheme := internal.NewSchemeForTests()
   141  		err := apiextensionsv1.AddToScheme(scheme)
   142  		decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer()
   143  		obj, gvk, err := decoder.Decode(siCRD, nil, nil)
   144  		fmt.Println(gvk)
   145  		require.NoError(t, err)
   146  
   147  		k8sCli := &fakeK8sClientWrapper{fake: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(obj, ns).Build()}
   148  		err = k8sCli.Create(context.TODO(), si)
   149  		require.NoError(t, err)
   150  
   151  		op := fixture.FixSuspensionOperationAsOperation(fixOperationID, fixInstanceID)
   152  		op.ProvisioningParameters.PlanID = broker.AWSPlanID
   153  		op.State = "in progress"
   154  		fakeProvisionerClient := fakeProvisionerClient{}
   155  		step := NewBTPOperatorCleanupStep(ms.Operations(), fakeProvisionerClient, func(k string) (client.Client, error) { return k8sCli, nil })
   156  
   157  		// when
   158  		entry := log.WithFields(logrus.Fields{"step": "TEST"})
   159  		_, _, err = step.Run(op, entry)
   160  
   161  		// then
   162  		assert.NoError(t, err)
   163  
   164  		// given
   165  		emptySI := &unstructured.Unstructured{}
   166  		emptySI.SetGroupVersionKind(schema.GroupVersionKind{
   167  			Group:   "services.cloud.sap.com",
   168  			Version: "v1",
   169  			Kind:    "ServiceInstance",
   170  		})
   171  
   172  		// then
   173  		assert.False(t, k8sCli.cleanupInstances)
   174  		assert.False(t, k8sCli.cleanupBindings)
   175  	})
   176  
   177  	t.Run("should skip btp-cleanup if not suspension", func(t *testing.T) {
   178  		log := logrus.New()
   179  		ms := storage.NewMemoryStorage()
   180  		si := &unstructured.Unstructured{Object: map[string]interface{}{
   181  			"apiVersion": "apiextensions.k8s.io/v1",
   182  			"kind":       "ServiceInstance",
   183  			"metadata": map[string]interface{}{
   184  				"name":      "test-instance",
   185  				"namespace": "kyma-system",
   186  			},
   187  		}}
   188  		ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "kyma-system"}}
   189  
   190  		scheme := internal.NewSchemeForTests()
   191  		err := apiextensionsv1.AddToScheme(scheme)
   192  		decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer()
   193  		obj, gvk, err := decoder.Decode(siCRD, nil, nil)
   194  		fmt.Println(gvk)
   195  		require.NoError(t, err)
   196  
   197  		k8sCli := &fakeK8sClientWrapper{fake: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(obj, ns).Build()}
   198  		err = k8sCli.Create(context.TODO(), si)
   199  		require.NoError(t, err)
   200  
   201  		op := fixture.FixSuspensionOperationAsOperation(fixOperationID, fixInstanceID)
   202  		op.State = "in progress"
   203  		op.Temporary = false
   204  		fakeProvisionerClient := fakeProvisionerClient{}
   205  		step := NewBTPOperatorCleanupStep(ms.Operations(), fakeProvisionerClient, func(k string) (client.Client, error) { return k8sCli, nil })
   206  
   207  		// when
   208  		entry := log.WithFields(logrus.Fields{"step": "TEST"})
   209  		_, _, err = step.Run(op, entry)
   210  
   211  		// then
   212  		assert.NoError(t, err)
   213  
   214  		// given
   215  		emptySI := &unstructured.Unstructured{}
   216  		emptySI.SetGroupVersionKind(schema.GroupVersionKind{
   217  			Group:   "services.cloud.sap.com",
   218  			Version: "v1",
   219  			Kind:    "ServiceInstance",
   220  		})
   221  
   222  		// then
   223  		assert.False(t, k8sCli.cleanupInstances)
   224  		assert.False(t, k8sCli.cleanupBindings)
   225  	})
   226  }
   227  
   228  func TestBTPOperatorCleanupStep_SoftDelete(t *testing.T) {
   229  	t.Run("should skip resources deletion when CRDs are missing", func(t *testing.T) {
   230  		// given
   231  		log := logrus.New()
   232  		ms := storage.NewMemoryStorage()
   233  		ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "kyma-system"}}
   234  
   235  		scheme := internal.NewSchemeForTests()
   236  		err := apiextensionsv1.AddToScheme(scheme)
   237  
   238  		k8sCli := &fakeK8sClientWrapper{fake: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(ns).Build()}
   239  		require.NoError(t, err)
   240  
   241  		op := fixture.FixDeprovisioningOperation(fixOperationID, fixInstanceID)
   242  		op.UserAgent = broker.AccountCleanupJob
   243  		op.State = "in progress"
   244  		fakeProvisionerClient := fakeProvisionerClient{}
   245  		step := NewBTPOperatorCleanupStep(ms.Operations(), fakeProvisionerClient, func(k string) (client.Client, error) { return k8sCli, nil })
   246  
   247  		// when
   248  		entry := log.WithFields(logrus.Fields{"step": "TEST"})
   249  		_, _, err = step.Run(op.Operation, entry)
   250  
   251  		// then
   252  		assert.NoError(t, err)
   253  		assert.False(t, k8sCli.cleanupInstances)
   254  		assert.False(t, k8sCli.cleanupBindings)
   255  	})
   256  
   257  	t.Run("should delete SI and skip SB deletion ", func(t *testing.T) {
   258  		log := logrus.New()
   259  		ms := storage.NewMemoryStorage()
   260  		si := &unstructured.Unstructured{Object: map[string]interface{}{
   261  			"apiVersion": "apiextensions.k8s.io/v1",
   262  			"kind":       "ServiceInstance",
   263  			"metadata": map[string]interface{}{
   264  				"name":      "test-instance",
   265  				"namespace": "kyma-system",
   266  			},
   267  		}}
   268  		ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "kyma-system"}}
   269  
   270  		scheme := internal.NewSchemeForTests()
   271  		err := apiextensionsv1.AddToScheme(scheme)
   272  		decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer()
   273  		obj, gvk, err := decoder.Decode(siCRD, nil, nil)
   274  		fmt.Println(gvk)
   275  		require.NoError(t, err)
   276  
   277  		k8sCli := &fakeK8sClientWrapper{fake: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(obj, ns).Build()}
   278  		err = k8sCli.Create(context.TODO(), si)
   279  		require.NoError(t, err)
   280  
   281  		op := fixture.FixDeprovisioningOperation(fixOperationID, fixInstanceID)
   282  		op.UserAgent = broker.AccountCleanupJob
   283  		op.State = "in progress"
   284  		fakeProvisionerClient := fakeProvisionerClient{}
   285  		step := NewBTPOperatorCleanupStep(ms.Operations(), fakeProvisionerClient, func(k string) (client.Client, error) { return k8sCli, nil })
   286  
   287  		// when
   288  		entry := log.WithFields(logrus.Fields{"step": "TEST"})
   289  		_, _, err = step.Run(op.Operation, entry)
   290  
   291  		// then
   292  		assert.NoError(t, err)
   293  		assert.True(t, k8sCli.cleanupInstances)
   294  		assert.False(t, k8sCli.cleanupBindings)
   295  	})
   296  
   297  	t.Run("should delete SB and skip SI deletion ", func(t *testing.T) {
   298  		log := logrus.New()
   299  		ms := storage.NewMemoryStorage()
   300  		sb := &unstructured.Unstructured{Object: map[string]interface{}{
   301  			"apiVersion": "apiextensions.k8s.io/v1",
   302  			"kind":       "ServiceBinding",
   303  			"metadata": map[string]interface{}{
   304  				"name":      "test-binding",
   305  				"namespace": "kyma-system",
   306  			},
   307  		}}
   308  		ns := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "kyma-system"}}
   309  
   310  		scheme := internal.NewSchemeForTests()
   311  		err := apiextensionsv1.AddToScheme(scheme)
   312  		decoder := serializer.NewCodecFactory(scheme).UniversalDeserializer()
   313  		obj, gvk, err := decoder.Decode(sbCRD, nil, nil)
   314  		fmt.Println(gvk)
   315  		require.NoError(t, err)
   316  
   317  		k8sCli := &fakeK8sClientWrapper{fake: fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(obj, ns).Build()}
   318  		err = k8sCli.Create(context.TODO(), sb)
   319  		require.NoError(t, err)
   320  
   321  		op := fixture.FixDeprovisioningOperation(fixOperationID, fixInstanceID)
   322  		op.UserAgent = broker.AccountCleanupJob
   323  		op.State = "in progress"
   324  		fakeProvisionerClient := fakeProvisionerClient{}
   325  		step := NewBTPOperatorCleanupStep(ms.Operations(), fakeProvisionerClient, func(k string) (client.Client, error) { return k8sCli, nil })
   326  
   327  		// when
   328  		entry := log.WithFields(logrus.Fields{"step": "TEST"})
   329  		_, _, err = step.Run(op.Operation, entry)
   330  
   331  		// then
   332  		assert.NoError(t, err)
   333  		assert.False(t, k8sCli.cleanupInstances)
   334  		assert.True(t, k8sCli.cleanupBindings)
   335  	})
   336  }
   337  
   338  type fakeK8sClientWrapper struct {
   339  	fake             client.Client
   340  	cleanupInstances bool
   341  	cleanupBindings  bool
   342  }
   343  
   344  func (f *fakeK8sClientWrapper) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error {
   345  	return f.fake.Get(ctx, key, obj)
   346  }
   347  
   348  func (f *fakeK8sClientWrapper) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error {
   349  	if u, ok := list.(*unstructured.UnstructuredList); ok {
   350  		switch u.Object["kind"] {
   351  		case "ServiceBindingList":
   352  			f.cleanupBindings = true
   353  		case "ServiceInstanceList":
   354  			f.cleanupInstances = true
   355  		}
   356  	}
   357  	return f.fake.List(ctx, list, opts...)
   358  }
   359  
   360  func (f *fakeK8sClientWrapper) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
   361  	return f.fake.Create(ctx, obj, opts...)
   362  }
   363  
   364  func (f *fakeK8sClientWrapper) Delete(ctx context.Context, obj client.Object, opts ...client.DeleteOption) error {
   365  	return f.fake.Delete(ctx, obj, opts...)
   366  }
   367  
   368  func (f *fakeK8sClientWrapper) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
   369  	return f.fake.Update(ctx, obj, opts...)
   370  }
   371  
   372  func (f *fakeK8sClientWrapper) Patch(ctx context.Context, obj client.Object, patch client.Patch, opts ...client.PatchOption) error {
   373  	return f.fake.Patch(ctx, obj, patch, opts...)
   374  }
   375  
   376  func (f *fakeK8sClientWrapper) DeleteAllOf(ctx context.Context, obj client.Object, opts ...client.DeleteAllOfOption) error {
   377  	if u, ok := obj.(*unstructured.Unstructured); ok {
   378  		switch u.Object["kind"] {
   379  		case "ServiceBinding":
   380  			f.cleanupBindings = true
   381  		case "ServiceInstance":
   382  			f.cleanupInstances = true
   383  		}
   384  	}
   385  	return f.fake.DeleteAllOf(ctx, obj, opts...)
   386  }
   387  
   388  func (f *fakeK8sClientWrapper) Status() client.StatusWriter {
   389  	return f.fake.Status()
   390  }
   391  
   392  func (f *fakeK8sClientWrapper) Scheme() *runtime.Scheme {
   393  	return f.fake.Scheme()
   394  }
   395  
   396  func (f *fakeK8sClientWrapper) RESTMapper() meta.RESTMapper {
   397  	return f.fake.RESTMapper()
   398  }
   399  
   400  func (f *fakeK8sClientWrapper) SubResource(subresource string) client.SubResourceClient {
   401  	return f.fake.SubResource(subresource)
   402  }
   403  
   404  type fakeProvisionerClient struct {
   405  	empty bool
   406  }
   407  
   408  func newEmptyProvisionerClient() fakeProvisionerClient {
   409  	return fakeProvisionerClient{true}
   410  }
   411  
   412  func (f fakeProvisionerClient) ProvisionRuntime(accountID, subAccountID string, config gqlschema.ProvisionRuntimeInput) (gqlschema.OperationStatus, error) {
   413  	panic("not implemented")
   414  }
   415  
   416  func (f fakeProvisionerClient) DeprovisionRuntime(accountID, runtimeID string) (string, error) {
   417  	panic("not implemented")
   418  }
   419  
   420  func (f fakeProvisionerClient) UpgradeRuntime(accountID, runtimeID string, config gqlschema.UpgradeRuntimeInput) (gqlschema.OperationStatus, error) {
   421  	panic("not implemented")
   422  }
   423  
   424  func (f fakeProvisionerClient) UpgradeShoot(accountID, runtimeID string, config gqlschema.UpgradeShootInput) (gqlschema.OperationStatus, error) {
   425  	panic("not implemented")
   426  }
   427  
   428  func (f fakeProvisionerClient) ReconnectRuntimeAgent(accountID, runtimeID string) (string, error) {
   429  	panic("not implemented")
   430  }
   431  
   432  func (f fakeProvisionerClient) RuntimeOperationStatus(accountID, operationID string) (gqlschema.OperationStatus, error) {
   433  	panic("not implemented")
   434  }
   435  
   436  func (f fakeProvisionerClient) RuntimeStatus(accountID, runtimeID string) (gqlschema.RuntimeStatus, error) {
   437  	if f.empty {
   438  		return gqlschema.RuntimeStatus{}, fmt.Errorf("not found")
   439  	}
   440  	kubeconfig := "sample fake kubeconfig"
   441  	return gqlschema.RuntimeStatus{
   442  		RuntimeConfiguration: &gqlschema.RuntimeConfig{
   443  			Kubeconfig: &kubeconfig,
   444  		},
   445  	}, nil
   446  }