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 }