github.com/kyma-project/kyma-environment-broker@v0.0.1/internal/environmentscleanup/service_test.go (about)

     1  package environmentscleanup
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/kyma-project/kyma-environment-broker/internal"
    10  	mocks "github.com/kyma-project/kyma-environment-broker/internal/environmentscleanup/automock"
    11  	"github.com/kyma-project/kyma-environment-broker/internal/storage"
    12  	"github.com/sirupsen/logrus"
    13  	"github.com/stretchr/testify/assert"
    14  	"github.com/stretchr/testify/mock"
    15  	"github.com/stretchr/testify/require"
    16  
    17  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    18  )
    19  
    20  const (
    21  	fixInstanceID1 = "instance-1"
    22  	fixInstanceID2 = "instance-2"
    23  	fixInstanceID3 = "instance-3"
    24  	fixRuntimeID1  = "runtime-1"
    25  	fixRuntimeID2  = "runtime-2"
    26  	fixRuntimeID3  = "rntime-3"
    27  	fixOperationID = "operation-id"
    28  
    29  	fixAccountID       = "account-id"
    30  	maxShootAge        = 24 * time.Hour
    31  	shootLabelSelector = "owner.do-not-delete!=true"
    32  )
    33  
    34  func TestService_PerformCleanup(t *testing.T) {
    35  	t.Run("happy path", func(t *testing.T) {
    36  		// given
    37  		gcMock := &mocks.GardenerClient{}
    38  		gcMock.On("List", mock.Anything, mock.AnythingOfType("v1.ListOptions")).Return(fixShootList(), nil)
    39  		gcMock.On("Delete", mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("v1.DeleteOptions")).Return(nil)
    40  		gcMock.On("Update", mock.Anything, mock.Anything, mock.AnythingOfType("v1.UpdateOptions")).Return(nil, nil)
    41  		bcMock := &mocks.BrokerClient{}
    42  		bcMock.On("Deprovision", mock.AnythingOfType("internal.Instance")).Return(fixOperationID, nil)
    43  		pMock := &mocks.ProvisionerClient{}
    44  		pMock.On("DeprovisionRuntime", fixAccountID, fixRuntimeID3).Return("", nil)
    45  
    46  		memoryStorage := storage.NewMemoryStorage()
    47  		memoryStorage.Instances().Insert(internal.Instance{
    48  			InstanceID: fixInstanceID1,
    49  			RuntimeID:  fixRuntimeID1,
    50  		})
    51  		memoryStorage.Instances().Insert(internal.Instance{
    52  			InstanceID: fixInstanceID2,
    53  			RuntimeID:  fixRuntimeID2,
    54  		})
    55  		logger := logrus.New()
    56  
    57  		svc := NewService(gcMock, bcMock, pMock, memoryStorage.Instances(), logger, maxShootAge, shootLabelSelector)
    58  
    59  		// when
    60  		err := svc.PerformCleanup()
    61  
    62  		// then
    63  		bcMock.AssertExpectations(t)
    64  		gcMock.AssertExpectations(t)
    65  		pMock.AssertExpectations(t)
    66  		assert.NoError(t, err)
    67  	})
    68  
    69  	t.Run("should fail when unable to fetch shoots from gardener", func(t *testing.T) {
    70  		// given
    71  		gcMock := &mocks.GardenerClient{}
    72  		gcMock.On("List", mock.Anything, mock.AnythingOfType("v1.ListOptions")).Return(&unstructured.
    73  			UnstructuredList{}, fmt.Errorf("failed to reach gardener"))
    74  
    75  		bcMock := &mocks.BrokerClient{}
    76  		pMock := &mocks.ProvisionerClient{}
    77  
    78  		memoryStorage := storage.NewMemoryStorage()
    79  		logger := logrus.New()
    80  
    81  		svc := NewService(gcMock, bcMock, pMock, memoryStorage.Instances(), logger, maxShootAge, shootLabelSelector)
    82  
    83  		// when
    84  		err := svc.PerformCleanup()
    85  
    86  		// then
    87  		bcMock.AssertExpectations(t)
    88  		gcMock.AssertExpectations(t)
    89  		assert.Error(t, err)
    90  	})
    91  
    92  	t.Run("should return error when unable to find instance in db", func(t *testing.T) {
    93  		// given
    94  		gcMock := &mocks.GardenerClient{}
    95  		gcMock.On("List", mock.Anything, mock.AnythingOfType("v1.ListOptions")).Return(fixShootList(), nil)
    96  		gcMock.On("Delete", mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("v1.DeleteOptions")).Return(nil)
    97  		gcMock.On("Update", mock.Anything, mock.Anything, mock.AnythingOfType("v1.UpdateOptions")).Return(nil, nil)
    98  
    99  		bcMock := &mocks.BrokerClient{}
   100  		bcMock.On("Deprovision", mock.AnythingOfType("internal.Instance")).Return(fixOperationID, nil)
   101  		pMock := &mocks.ProvisionerClient{}
   102  		pMock.On("DeprovisionRuntime", fixAccountID, fixRuntimeID3).Return("", nil)
   103  
   104  		memoryStorage := storage.NewMemoryStorage()
   105  		memoryStorage.Instances().Insert(internal.Instance{
   106  			InstanceID: "some-instance-id",
   107  			RuntimeID:  "not-matching-id",
   108  		})
   109  		memoryStorage.Instances().Insert(internal.Instance{
   110  			InstanceID: fixInstanceID1,
   111  			RuntimeID:  fixRuntimeID1,
   112  		})
   113  		memoryStorage.Instances().Insert(internal.Instance{
   114  			InstanceID: fixInstanceID2,
   115  			RuntimeID:  fixRuntimeID2,
   116  		})
   117  		logger := logrus.New()
   118  
   119  		svc := NewService(gcMock, bcMock, pMock, memoryStorage.Instances(), logger, maxShootAge, shootLabelSelector)
   120  
   121  		// when
   122  		err := svc.PerformCleanup()
   123  
   124  		// then
   125  		bcMock.AssertExpectations(t)
   126  		gcMock.AssertExpectations(t)
   127  		pMock.AssertExpectations(t)
   128  
   129  		assert.NoError(t, err)
   130  	})
   131  
   132  	t.Run("should return error on KEB deprovision call failure", func(t *testing.T) {
   133  		// given
   134  		gcMock := &mocks.GardenerClient{}
   135  		gcMock.On("List", mock.Anything, mock.AnythingOfType("v1.ListOptions")).Return(fixShootList(), nil)
   136  		bcMock := &mocks.BrokerClient{}
   137  		bcMock.On("Deprovision", mock.AnythingOfType("internal.Instance")).Return("",
   138  			fmt.Errorf("failed to deprovision instance"))
   139  
   140  		pMock := &mocks.ProvisionerClient{}
   141  
   142  		memoryStorage := storage.NewMemoryStorage()
   143  		memoryStorage.Instances().Insert(internal.Instance{
   144  			InstanceID: fixInstanceID1,
   145  			RuntimeID:  fixRuntimeID1,
   146  		})
   147  		memoryStorage.Instances().Insert(internal.Instance{
   148  			InstanceID: fixInstanceID2,
   149  			RuntimeID:  fixRuntimeID2,
   150  		})
   151  
   152  		memoryStorage.Instances().Insert(internal.Instance{
   153  			InstanceID: fixInstanceID3,
   154  			RuntimeID:  fixRuntimeID3,
   155  		})
   156  
   157  		logger := logrus.New()
   158  
   159  		svc := NewService(gcMock, bcMock, pMock, memoryStorage.Instances(), logger, maxShootAge, shootLabelSelector)
   160  
   161  		// when
   162  		err := svc.PerformCleanup()
   163  
   164  		// then
   165  		bcMock.AssertExpectations(t)
   166  		gcMock.AssertExpectations(t)
   167  		pMock.AssertExpectations(t)
   168  		assert.Error(t, err)
   169  	})
   170  
   171  	t.Run("should return error on Provisioner deprovision call failure", func(t *testing.T) {
   172  		// given
   173  		gcMock := &mocks.GardenerClient{}
   174  		gcMock.On("List", mock.Anything, mock.AnythingOfType("v1.ListOptions")).Return(fixShootList(), nil)
   175  
   176  		bcMock := &mocks.BrokerClient{}
   177  
   178  		pMock := &mocks.ProvisionerClient{}
   179  		bcMock.On("Deprovision", mock.AnythingOfType("internal.Instance")).Return("", nil)
   180  		pMock.On("DeprovisionRuntime", fixAccountID, fixRuntimeID2).Return("", fmt.Errorf("some error"))
   181  		pMock.On("DeprovisionRuntime", fixAccountID, fixRuntimeID3).Return("", fmt.Errorf("some other error"))
   182  
   183  		memoryStorage := storage.NewMemoryStorage()
   184  		memoryStorage.Instances().Insert(internal.Instance{
   185  			InstanceID: fixInstanceID1,
   186  			RuntimeID:  fixRuntimeID1,
   187  		})
   188  
   189  		logger := logrus.New()
   190  
   191  		svc := NewService(gcMock, bcMock, pMock, memoryStorage.Instances(), logger, maxShootAge, shootLabelSelector)
   192  
   193  		// when
   194  		err := svc.PerformCleanup()
   195  
   196  		// then
   197  		bcMock.AssertExpectations(t)
   198  		gcMock.AssertExpectations(t)
   199  		pMock.AssertExpectations(t)
   200  		assert.Error(t, err)
   201  	})
   202  
   203  	t.Run("should pass when shoot has no runtime id annotation or account label", func(t *testing.T) {
   204  		// given
   205  		gcMock := &mocks.GardenerClient{}
   206  		creationTime, parseErr := time.Parse(time.RFC3339, "2020-01-02T10:00:00-05:00")
   207  		require.NoError(t, parseErr)
   208  		unl := unstructured.UnstructuredList{
   209  			Items: []unstructured.Unstructured{
   210  				{
   211  					Object: map[string]interface{}{
   212  						"metadata": map[string]interface{}{
   213  							"name":              "az-1234",
   214  							"creationTimestamp": creationTime,
   215  							"annotations": map[string]interface{}{
   216  								shootAnnotationRuntimeId: fixRuntimeID1,
   217  							},
   218  							"clusterName": "cluster-one",
   219  						},
   220  						"spec": map[string]interface{}{
   221  							"cloudProfileName": "az",
   222  						},
   223  					},
   224  				},
   225  				{
   226  					Object: map[string]interface{}{
   227  						"metadata": map[string]interface{}{
   228  							"name":              "az-1234",
   229  							"creationTimestamp": creationTime,
   230  							"clusterName":       "cluster-one",
   231  							"annotations":       map[string]interface{}{},
   232  						},
   233  						"spec": map[string]interface{}{
   234  							"cloudProfileName": "az",
   235  						},
   236  					},
   237  				},
   238  			},
   239  		}
   240  		gcMock.On("List", mock.Anything, mock.AnythingOfType("v1.ListOptions")).Return(&unl, nil)
   241  		gcMock.On("Delete", mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("v1.DeleteOptions")).Return(nil)
   242  		gcMock.On("Update", mock.Anything, mock.Anything, mock.AnythingOfType("v1.UpdateOptions")).Return(nil, nil)
   243  
   244  		bcMock := &mocks.BrokerClient{}
   245  		pMock := &mocks.ProvisionerClient{}
   246  
   247  		memoryStorage := storage.NewMemoryStorage()
   248  		memoryStorage.Instances().Insert(internal.Instance{
   249  			InstanceID: fixInstanceID1,
   250  			RuntimeID:  fixRuntimeID1,
   251  		})
   252  
   253  		var actualLog bytes.Buffer
   254  		logger := logrus.New()
   255  		logger.SetFormatter(&logrus.TextFormatter{
   256  			DisableTimestamp: true,
   257  		})
   258  		logger.SetOutput(&actualLog)
   259  
   260  		svc := NewService(gcMock, bcMock, pMock, memoryStorage.Instances(), logger, maxShootAge, shootLabelSelector)
   261  
   262  		// when
   263  		err := svc.PerformCleanup()
   264  
   265  		// then
   266  		bcMock.AssertExpectations(t)
   267  		gcMock.AssertExpectations(t)
   268  		assert.NoError(t, err)
   269  	})
   270  
   271  }
   272  
   273  func fixShootList() *unstructured.UnstructuredList {
   274  	return &unstructured.UnstructuredList{
   275  		Items: fixShootListItems(),
   276  	}
   277  }
   278  
   279  func fixShootListItems() []unstructured.Unstructured {
   280  	creationTime, _ := time.Parse(time.RFC3339, "2020-01-02T10:00:00-05:00")
   281  	unl := unstructured.UnstructuredList{
   282  		Items: []unstructured.Unstructured{
   283  			{
   284  				Object: map[string]interface{}{
   285  					"metadata": map[string]interface{}{
   286  						"name":              "simple-shoot",
   287  						"creationTimestamp": creationTime,
   288  						"labels": map[string]interface{}{
   289  							"should-be-deleted": "true",
   290  						},
   291  						"annotations": map[string]interface{}{},
   292  					},
   293  					"spec": map[string]interface{}{
   294  						"cloudProfileName": "az",
   295  					},
   296  				},
   297  			},
   298  			{
   299  				Object: map[string]interface{}{
   300  					"metadata": map[string]interface{}{
   301  						"name":              "az-1234",
   302  						"creationTimestamp": creationTime,
   303  						"labels": map[string]interface{}{
   304  							"should-be-deleted": "true",
   305  							shootLabelAccountId: fixAccountID,
   306  						},
   307  						"annotations": map[string]interface{}{
   308  							shootAnnotationRuntimeId: fixRuntimeID1,
   309  						},
   310  						"clusterName": "cluster-one",
   311  					},
   312  					"spec": map[string]interface{}{
   313  						"cloudProfileName": "az",
   314  					},
   315  				},
   316  			},
   317  			{
   318  				Object: map[string]interface{}{
   319  					"metadata": map[string]interface{}{
   320  						"name":              "gcp-1234",
   321  						"creationTimestamp": creationTime,
   322  						"labels": map[string]interface{}{
   323  							"should-be-deleted": "true",
   324  							shootLabelAccountId: fixAccountID,
   325  						},
   326  						"annotations": map[string]interface{}{
   327  							shootAnnotationRuntimeId: fixRuntimeID2,
   328  						},
   329  						"clusterName": "cluster-two",
   330  					},
   331  					"spec": map[string]interface{}{
   332  						"cloudProfileName": "gcp",
   333  					},
   334  				},
   335  			},
   336  			{
   337  				Object: map[string]interface{}{
   338  					"metadata": map[string]interface{}{
   339  						"name":              "az-4567",
   340  						"creationTimestamp": creationTime,
   341  						"labels": map[string]interface{}{
   342  							"should-be-deleted-by-provisioner": "true",
   343  							shootLabelAccountId:                fixAccountID,
   344  						},
   345  						"annotations": map[string]interface{}{
   346  							shootAnnotationRuntimeId: fixRuntimeID3,
   347  						},
   348  						"clusterName": "cluster-one",
   349  					},
   350  					"spec": map[string]interface{}{
   351  						"cloudProfileName": "az",
   352  					},
   353  				},
   354  			},
   355  		},
   356  	}
   357  	return unl.Items
   358  }