Skip to content
This repository was archived by the owner on Jun 26, 2024. It is now read-only.

Commit 62a1752

Browse files
committed
fix issue SBR can not be deleted if backend service has been deleted #832
Signed-off-by: qibobo <[email protected]>
1 parent 92752f5 commit 62a1752

File tree

3 files changed

+179
-21
lines changed

3 files changed

+179
-21
lines changed

pkg/controller/servicebinding/reconciler.go

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -165,29 +165,34 @@ func (r *reconciler) Reconcile(request reconcile.Request) (reconcile.Result, err
165165
r.restMapper,
166166
)
167167
if err != nil {
168-
//handle service not found error
169-
if k8serrors.IsNotFound(err) {
170-
err = updateSBRConditions(r.dynClient, sbr,
171-
conditionsv1.Condition{
172-
Type: CollectionReady,
173-
Status: corev1.ConditionFalse,
174-
Reason: ServiceNotFoundReason,
175-
Message: err.Error(),
176-
},
177-
conditionsv1.Condition{
178-
Type: InjectionReady,
179-
Status: corev1.ConditionFalse,
180-
},
181-
conditionsv1.Condition{
182-
Type: BindingReady,
183-
Status: corev1.ConditionFalse,
184-
},
185-
)
186-
if err != nil {
187-
logger.Error(err, "Failed to update SBR conditions", "sbr", sbr)
168+
if sbr.GetDeletionTimestamp() != nil {
169+
serviceCtxs = serviceContextList{}
170+
} else {
171+
//handle service not found error
172+
if k8serrors.IsNotFound(err) {
173+
err = updateSBRConditions(r.dynClient, sbr,
174+
conditionsv1.Condition{
175+
Type: CollectionReady,
176+
Status: corev1.ConditionFalse,
177+
Reason: ServiceNotFoundReason,
178+
Message: err.Error(),
179+
},
180+
conditionsv1.Condition{
181+
Type: InjectionReady,
182+
Status: corev1.ConditionFalse,
183+
},
184+
conditionsv1.Condition{
185+
Type: BindingReady,
186+
Status: corev1.ConditionFalse,
187+
},
188+
)
189+
if err != nil {
190+
logger.Error(err, "Failed to update SBR conditions", "sbr", sbr)
191+
}
188192
}
193+
return requeueError(err)
189194
}
190-
return requeueError(err)
195+
191196
}
192197
binding, err := buildBinding(
193198
r.dynClient,

pkg/controller/servicebinding/reconciler_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"time"
77

88
"github.com/redhat-developer/service-binding-operator/pkg/apis/operators/v1alpha1"
9+
"github.com/redhat-developer/service-binding-operator/pkg/converter"
910
"github.com/redhat-developer/service-binding-operator/pkg/testutils"
1011
"github.com/stretchr/testify/require"
1112
appsv1 "k8s.io/api/apps/v1"
@@ -648,3 +649,98 @@ func TestBindTwoSbrsWithSingleApplication(t *testing.T) {
648649
require.Equal(t, sbrName2, dep.Spec.Template.Spec.Containers[0].EnvFrom[1].SecretRef.LocalObjectReference.Name)
649650
})
650651
}
652+
653+
// TestApplicationByName tests discovery of application by name
654+
func TestUnBind(t *testing.T) {
655+
//create a successful SBR first
656+
backingServiceResourceRef := "test-using-secret"
657+
matchLabels := map[string]string{
658+
"connects-to": "database",
659+
"environment": "reconciler",
660+
}
661+
f := mocks.NewFake(t, reconcilerNs)
662+
f.AddMockedUnstructuredServiceBinding(reconcilerName, backingServiceResourceRef, reconcilerName, deploymentsGVR, matchLabels)
663+
f.AddMockedUnstructuredCSV("cluster-service-version-list")
664+
f.AddMockedUnstructuredDatabaseCRD()
665+
f.AddMockedUnstructuredDatabaseCR(backingServiceResourceRef)
666+
f.AddMockedUnstructuredDeployment(reconcilerName, matchLabels)
667+
f.AddMockedUnstructuredSecret("db-credentials")
668+
669+
fakeDynClient := f.FakeDynClient()
670+
mapper := testutils.BuildTestRESTMapper()
671+
r := &reconciler{dynClient: fakeDynClient, restMapper: mapper, scheme: f.S}
672+
r.resourceWatcher = newFakeResourceWatcher(mapper)
673+
674+
res, err := r.Reconcile(reconcileRequest())
675+
require.NoError(t, err)
676+
require.False(t, res.Requeue)
677+
678+
namespacedName := types.NamespacedName{Namespace: reconcilerNs, Name: reconcilerName}
679+
680+
u, err := fakeDynClient.Resource(deploymentsGVR).Get(reconcilerName, metav1.GetOptions{})
681+
require.NoError(t, err)
682+
683+
d := appsv1.Deployment{}
684+
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &d)
685+
require.NoError(t, err)
686+
687+
containers := d.Spec.Template.Spec.Containers
688+
require.Equal(t, 1, len(containers))
689+
require.Equal(t, 1, len(containers[0].EnvFrom))
690+
require.NotNil(t, containers[0].EnvFrom[0].SecretRef)
691+
require.Equal(t, reconcilerName, containers[0].EnvFrom[0].SecretRef.Name)
692+
693+
namespacedName = types.NamespacedName{Namespace: reconcilerNs, Name: reconcilerName}
694+
sbrOutput, err := r.getServiceBinding(namespacedName)
695+
require.NoError(t, err)
696+
697+
requireConditionPresentAndTrue(t, CollectionReady, sbrOutput.Status.Conditions)
698+
requireConditionPresentAndTrue(t, InjectionReady, sbrOutput.Status.Conditions)
699+
requireConditionPresentAndTrue(t, BindingReady, sbrOutput.Status.Conditions)
700+
701+
require.Equal(t, reconcilerName, sbrOutput.Status.Secret)
702+
703+
require.Equal(t, 1, len(sbrOutput.Status.Applications))
704+
expectedStatus := v1alpha1.BoundApplication{
705+
GroupVersionKind: v1.GroupVersionKind{
706+
Group: deploymentsGVR.Group,
707+
Version: deploymentsGVR.Version,
708+
Kind: "Deployment",
709+
},
710+
LocalObjectReference: corev1.LocalObjectReference{
711+
Name: namespacedName.Name,
712+
},
713+
}
714+
require.True(t, reflect.DeepEqual(expectedStatus, sbrOutput.Status.Applications[0]))
715+
716+
//do the unbind
717+
nsClient := fakeDynClient.
718+
Resource(groupVersion).
719+
Namespace(reconcilerNs)
720+
u, err = nsClient.Get(reconcilerName, metav1.GetOptions{})
721+
require.NoError(t, err)
722+
723+
sbr := &v1alpha1.ServiceBinding{}
724+
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, sbr)
725+
require.NoError(t, err)
726+
deletionTime := metav1.Now()
727+
sbr.SetDeletionTimestamp(&deletionTime)
728+
u, err = converter.ToUnstructured(sbr)
729+
require.NoError(t, err)
730+
_, err = nsClient.Update(u, v1.UpdateOptions{})
731+
require.NoError(t, err)
732+
res, err = r.Reconcile(reconcileRequest())
733+
require.NoError(t, err)
734+
require.False(t, res.Requeue)
735+
736+
u, err = fakeDynClient.Resource(deploymentsGVR).Get(reconcilerName, metav1.GetOptions{})
737+
require.NoError(t, err)
738+
739+
d = appsv1.Deployment{}
740+
err = runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &d)
741+
require.NoError(t, err)
742+
743+
containers = d.Spec.Template.Spec.Containers
744+
require.Equal(t, 1, len(containers))
745+
require.Equal(t, 0, len(containers[0].EnvFrom))
746+
}

test/acceptance/features/unbindAppToService.feature

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,60 @@ Feature: Unbind an application from a service
4949

5050
Then The env var "BACKEND_HOST" is not available to the application
5151
And The env var "BACKEND_USERNAME" is not available to the application
52+
53+
54+
Scenario: Unbind a generic test application from the backing service when the backing service has been deleted
55+
Given OLM Operator "backend" is running
56+
* The Custom Resource is present
57+
"""
58+
apiVersion: "stable.example.com/v1"
59+
kind: Backend
60+
metadata:
61+
name: example-backend
62+
annotations:
63+
service.binding/host: path={.spec.host}
64+
service.binding/username: path={.spec.username}
65+
spec:
66+
host: example.com
67+
username: foo
68+
"""
69+
* Generic test application "generic-app-a-d-u" is running
70+
* Service Binding is applied
71+
"""
72+
apiVersion: operators.coreos.com/v1alpha1
73+
kind: ServiceBinding
74+
metadata:
75+
name: binding-request-a-d-u
76+
spec:
77+
application:
78+
name: generic-app-a-d-u
79+
group: apps
80+
version: v1
81+
resource: deployments
82+
services:
83+
- group: stable.example.com
84+
version: v1
85+
kind: Backend
86+
name: example-backend
87+
id: backend
88+
"""
89+
* The application env var "BACKEND_HOST" has value "example.com"
90+
* The application env var "BACKEND_USERNAME" has value "foo"
91+
92+
When BackingService is deleted
93+
"""
94+
apiVersion: "stable.example.com/v1"
95+
kind: Backend
96+
metadata:
97+
name: example-backend
98+
annotations:
99+
service.binding/host: path={.spec.host}
100+
service.binding/username: path={.spec.username}
101+
spec:
102+
host: example.com
103+
username: foo
104+
"""
105+
When Service binding "binding-request-a-d-u" is deleted
106+
107+
Then The env var "BACKEND_HOST" is not available to the application
108+
And The env var "BACKEND_USERNAME" is not available to the application

0 commit comments

Comments
 (0)