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 }