Skip to content

Commit 3d152c6

Browse files
Adopting Orphan Instances via Kubernetes Annotation (#50)
* Initial commit for adopting orphan instances in cf * Adoption for the existing service bindings * update for adopting instance annotation * Change GetInstance by name or by owner. Update CF instance with label and annotation. * new branch for orphan instances adoption * revert to original defer function * implement for orphan service binding * Update markdown documentation with the new annotation, adopt-instances. * Modify GetInstance and GetBinding functions based on PR comments * Add changes based or reviewer comments * changes for comments on listoptions * Update the documentation * Changes to getinstance call in clinet_test.go file * Update generated deepcopy go * Update adopt.md file following comment from reviewer --------- Co-authored-by: shilparamasamyreddy <164521358+shilparamasamyreddy@users.noreply.github.com>
1 parent ccfa83d commit 3d152c6

9 files changed

Lines changed: 272 additions & 76 deletions

File tree

api/v1alpha1/constants.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@ const (
1717
AnnotationMaxRetries = "service-operator.cf.cs.sap.com/max-retries"
1818
// annotation to hold the reconciliation timeout value
1919
AnnotationReconcileTimeout = "service-operator.cf.cs.sap.com/timeout-on-reconcile"
20+
// annotation to adopt orphan CF resources. If set to 'adopt', the operator will adopt orphan CF resource.
21+
// Ex. "service-operator.cf.cs.sap.com/adopt-cf-resources"="adopt"
22+
AnnotationAdoptCFResources = "service-operator.cf.cs.sap.com/adopt-cf-resources"
2023
)

internal/cf/binding.go

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,64 @@ import (
1818
"github.com/sap/cf-service-operator/internal/facade"
1919
)
2020

21-
func (c *spaceClient) GetBinding(ctx context.Context, owner string) (*facade.Binding, error) {
21+
type bindingFilter interface {
22+
getListOptions() *cfclient.ServiceCredentialBindingListOptions
23+
}
24+
25+
type bindingFilterName struct {
26+
name string
27+
}
28+
type bindingFilterOwner struct {
29+
owner string
30+
}
31+
32+
func (bn *bindingFilterName) getListOptions() *cfclient.ServiceCredentialBindingListOptions {
2233
listOpts := cfclient.NewServiceCredentialBindingListOptions()
23-
listOpts.LabelSelector.EqualTo(labelPrefix + "/" + labelKeyOwner + "=" + owner)
34+
listOpts.Names.EqualTo(bn.name)
35+
return listOpts
36+
}
37+
38+
func (bo *bindingFilterOwner) getListOptions() *cfclient.ServiceCredentialBindingListOptions {
39+
listOpts := cfclient.NewServiceCredentialBindingListOptions()
40+
listOpts.LabelSelector.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, bo.owner))
41+
return listOpts
42+
}
43+
44+
// GetBinding returns the binding with the given bindingOpts["owner"] or bindingOpts["name"].
45+
// If bindingOpts["name"] is empty, the binding with the given bindingOpts["owner"] is returned.
46+
// If bindingOpts["name"] is not empty, the binding with the given Name is returned for orphan bindings.
47+
// If no binding is found, nil is returned.
48+
// If multiple bindings are found, an error is returned.
49+
// The function add the parameter values to the orphan cf binding, so that can be adopted.
50+
func (c *spaceClient) GetBinding(ctx context.Context, bindingOpts map[string]string) (*facade.Binding, error) {
51+
var filterOpts bindingFilter
52+
if bindingOpts["name"] != "" {
53+
filterOpts = &bindingFilterName{name: bindingOpts["name"]}
54+
} else {
55+
filterOpts = &bindingFilterOwner{owner: bindingOpts["owner"]}
56+
}
57+
listOpts := filterOpts.getListOptions()
2458
serviceBindings, err := c.client.ServiceCredentialBindings.ListAll(ctx, listOpts)
2559
if err != nil {
26-
return nil, err
60+
return nil, fmt.Errorf("failed to list service credential bindings: %w", err)
2761
}
2862

2963
if len(serviceBindings) == 0 {
3064
return nil, nil
3165
} else if len(serviceBindings) > 1 {
32-
return nil, fmt.Errorf("found multiple service bindings with owner: %s", owner)
66+
return nil, errors.New(fmt.Sprintf("found multiple service bindings with owner: %s", bindingOpts["owner"]))
3367
}
68+
3469
serviceBinding := serviceBindings[0]
3570

71+
// add parameter values to the cf orphan binding
72+
if bindingOpts["name"] != "" {
73+
generationvalue := "0"
74+
serviceBinding.Metadata.Annotations[annotationGeneration] = &generationvalue
75+
parameterHashValue := "0"
76+
serviceBinding.Metadata.Annotations[annotationParameterHash] = &parameterHashValue
77+
}
78+
3679
guid := serviceBinding.GUID
3780
name := serviceBinding.Name
3881
generation, err := strconv.ParseInt(*serviceBinding.Metadata.Annotations[annotationGeneration], 10, 64)
@@ -71,7 +114,7 @@ func (c *spaceClient) GetBinding(ctx context.Context, owner string) (*facade.Bin
71114
return &facade.Binding{
72115
Guid: guid,
73116
Name: name,
74-
Owner: owner,
117+
Owner: bindingOpts["owner"],
75118
Generation: generation,
76119
ParameterHash: parameterHash,
77120
State: state,
@@ -101,12 +144,18 @@ func (c *spaceClient) CreateBinding(ctx context.Context, name string, serviceIns
101144
}
102145

103146
// Required parameters (may not be initial): guid, generation
104-
func (c *spaceClient) UpdateBinding(ctx context.Context, guid string, generation int64) error {
147+
func (c *spaceClient) UpdateBinding(ctx context.Context, guid string, generation int64, parameters map[string]interface{}) error {
105148
// TODO: why is there no cfresource.NewServiceCredentialBindingUpdate() method ?
106149
req := &cfresource.ServiceCredentialBindingUpdate{}
107150
req.Metadata = cfresource.NewMetadata().
108151
WithAnnotation(annotationPrefix, annotationKeyGeneration, strconv.FormatInt(generation, 10))
109152

153+
if parameters != nil {
154+
req.Metadata.WithAnnotation(annotationPrefix, annotationKeyParameterHash, facade.ObjectHash(parameters))
155+
if parameters["owner"] != nil {
156+
req.Metadata.WithLabel(labelPrefix, labelKeyOwner, parameters["owner"].(string))
157+
}
158+
}
110159
_, err := c.client.ServiceCredentialBindings.Update(ctx, guid, req)
111160
return err
112161
}

internal/cf/client_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ var _ = Describe("CF Client tests", Ordered, func() {
220220
spaceClient, err := NewSpaceClient(OrgName, url, Username, Password)
221221
Expect(err).To(BeNil())
222222

223-
spaceClient.GetInstance(ctx, Owner)
223+
spaceClient.GetInstance(ctx, map[string]string{"owner": Owner})
224224

225225
// Discover UAA endpoint
226226
Expect(server.ReceivedRequests()[0].Method).To(Equal("GET"))
@@ -239,10 +239,10 @@ var _ = Describe("CF Client tests", Ordered, func() {
239239
spaceClient, err := NewSpaceClient(OrgName, url, Username, Password)
240240
Expect(err).To(BeNil())
241241

242-
spaceClient.GetInstance(ctx, Owner)
242+
spaceClient.GetInstance(ctx, map[string]string{"owner": Owner})
243243
spaceClient, err = NewSpaceClient(OrgName, url, Username, Password)
244244
Expect(err).To(BeNil())
245-
spaceClient.GetInstance(ctx, Owner)
245+
spaceClient.GetInstance(ctx, map[string]string{"owner": Owner})
246246

247247
// Discover UAA endpoint
248248
Expect(server.ReceivedRequests()[0].Method).To(Equal("GET"))
@@ -265,7 +265,7 @@ var _ = Describe("CF Client tests", Ordered, func() {
265265
// test space 1
266266
spaceClient1, err1 := NewSpaceClient(SpaceName, url, Username, Password)
267267
Expect(err1).To(BeNil())
268-
spaceClient1.GetInstance(ctx, Owner)
268+
spaceClient1.GetInstance(ctx, map[string]string{"owner": Owner})
269269
// Discover UAA endpoint
270270
Expect(server.ReceivedRequests()[0].Method).To(Equal("GET"))
271271
Expect(server.ReceivedRequests()[0].URL.Path).To(Equal("/"))
@@ -279,7 +279,7 @@ var _ = Describe("CF Client tests", Ordered, func() {
279279
// test space 2
280280
spaceClient2, err2 := NewSpaceClient(SpaceName2, url, Username, Password)
281281
Expect(err2).To(BeNil())
282-
spaceClient2.GetInstance(ctx, Owner2)
282+
spaceClient2.GetInstance(ctx, map[string]string{"owner": Owner2})
283283
// no discovery of UAA endpoint or oAuth token here due to caching
284284
// Get instance
285285
Expect(server.ReceivedRequests()[3].Method).To(Equal("GET"))

internal/cf/instance.go

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,65 @@ import (
1818
"github.com/sap/cf-service-operator/internal/facade"
1919
)
2020

21-
func (c *spaceClient) GetInstance(ctx context.Context, owner string) (*facade.Instance, error) {
21+
type instanceFilter interface {
22+
getListOptions() *cfclient.ServiceInstanceListOptions
23+
}
24+
25+
type instanceFilterName struct {
26+
name string
27+
}
28+
type instanceFilterOwner struct {
29+
owner string
30+
}
31+
32+
func (in *instanceFilterName) getListOptions() *cfclient.ServiceInstanceListOptions {
33+
listOpts := cfclient.NewServiceInstanceListOptions()
34+
listOpts.Names.EqualTo(in.name)
35+
return listOpts
36+
}
37+
38+
func (io *instanceFilterOwner) getListOptions() *cfclient.ServiceInstanceListOptions {
2239
listOpts := cfclient.NewServiceInstanceListOptions()
23-
listOpts.LabelSelector.EqualTo(labelPrefix + "/" + labelKeyOwner + "=" + owner)
40+
listOpts.LabelSelector.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, io.owner))
41+
return listOpts
42+
}
43+
44+
// GetInstance returns the instance with the given instanceOpts["owner"] or instanceOpts["name"].
45+
// If instanceOpts["name"] is empty, the instance with the given instanceOpts["owner"] is returned.
46+
// If instanceOpts["name"] is not empty, the instance with the given Name is returned for orphan instances.
47+
// If no instance is found, nil is returned.
48+
// If multiple instances are found, an error is returned.
49+
// The function add the parameter values to the orphan cf instance, so that can be adopted.
50+
func (c *spaceClient) GetInstance(ctx context.Context, instanceOpts map[string]string) (*facade.Instance, error) {
51+
52+
var filterOpts instanceFilter
53+
if instanceOpts["name"] != "" {
54+
filterOpts = &instanceFilterName{name: instanceOpts["name"]}
55+
} else {
56+
filterOpts = &instanceFilterOwner{owner: instanceOpts["owner"]}
57+
}
58+
listOpts := filterOpts.getListOptions()
2459
serviceInstances, err := c.client.ServiceInstances.ListAll(ctx, listOpts)
2560
if err != nil {
26-
return nil, err
61+
return nil, fmt.Errorf("failed to list service instances: %w", err)
2762
}
2863

2964
if len(serviceInstances) == 0 {
3065
return nil, nil
3166
} else if len(serviceInstances) > 1 {
32-
return nil, fmt.Errorf("found multiple service instances with owner: %s", owner)
67+
return nil, errors.New(fmt.Sprintf("found multiple service instances with owner: %s", instanceOpts["owner"]))
3368
}
69+
3470
serviceInstance := serviceInstances[0]
3571

72+
// add parameter values to the orphan cf instance
73+
if instanceOpts["name"] != "" {
74+
generationvalue := "0"
75+
serviceInstance.Metadata.Annotations[annotationGeneration] = &generationvalue
76+
parameterHashValue := "0"
77+
serviceInstance.Metadata.Annotations[annotationParameterHash] = &parameterHashValue
78+
}
79+
3680
guid := serviceInstance.GUID
3781
name := serviceInstance.Name
3882
servicePlanGuid := serviceInstance.Relationships.ServicePlan.Data.GUID
@@ -70,7 +114,7 @@ func (c *spaceClient) GetInstance(ctx context.Context, owner string) (*facade.In
70114
Guid: guid,
71115
Name: name,
72116
ServicePlanGuid: servicePlanGuid,
73-
Owner: owner,
117+
Owner: instanceOpts["owner"],
74118
Generation: generation,
75119
ParameterHash: parameterHash,
76120
State: state,
@@ -127,6 +171,10 @@ func (c *spaceClient) UpdateInstance(ctx context.Context, guid string, name stri
127171
WithAnnotation(annotationPrefix, annotationKeyGeneration, strconv.FormatInt(generation, 10))
128172
if parameters != nil {
129173
req.Metadata.WithAnnotation(annotationPrefix, annotationKeyParameterHash, facade.ObjectHash(parameters))
174+
if parameters["owner"] != nil {
175+
// Adding label to the metadata for orphan instance
176+
req.Metadata.WithLabel(labelPrefix, labelKeyOwner, parameters["owner"].(string))
177+
}
130178
}
131179

132180
_, _, err := c.client.ServiceInstances.UpdateManaged(ctx, guid, req)

internal/controllers/servicebinding_controller.go

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,49 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque
174174
}
175175
}
176176

177-
// Retrieve cloud foundry instance
177+
// Retrieve cloud foundry binding
178178
var cfbinding *facade.Binding
179+
bindingOpts := map[string]string{"name": "", "owner": string(serviceBinding.UID)}
179180
if client != nil {
180-
cfbinding, err = client.GetBinding(ctx, string(serviceBinding.UID))
181+
cfbinding, err = client.GetBinding(ctx, bindingOpts)
181182
if err != nil {
182183
return ctrl.Result{}, err
183184
}
185+
orphan, exists := serviceBinding.Annotations[cfv1alpha1.AnnotationAdoptCFResources]
186+
if exists && cfbinding == nil && orphan == "adopt" {
187+
// find orphaned binding by name
188+
bindingOpts["name"] = serviceBinding.Name
189+
cfbinding, err = client.GetBinding(ctx, bindingOpts)
190+
if err != nil {
191+
return ctrl.Result{}, err
192+
}
193+
194+
//Add parameters to adopt the orphaned binding
195+
var parameterObjects []map[string]interface{}
196+
paramMap := make(map[string]interface{})
197+
paramMap["parameter-hash"] = cfbinding.ParameterHash
198+
paramMap["owner"] = cfbinding.Owner
199+
parameterObjects = append(parameterObjects, paramMap)
200+
parameters, err := mergeObjects(parameterObjects...)
201+
if err != nil {
202+
return ctrl.Result{}, errors.Wrap(err, "failed to unmarshal/merge parameters")
203+
}
204+
// update the orphaned cloud foundry service binding
205+
log.V(1).Info("triggering update")
206+
if err := client.UpdateBinding(
207+
ctx,
208+
cfbinding.Guid,
209+
serviceBinding.Generation,
210+
parameters,
211+
); err != nil {
212+
return ctrl.Result{}, err
213+
}
214+
status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0]
215+
216+
// return the reconcile function to requeue inmediatly after the update
217+
serviceBinding.SetReadyCondition(cfv1alpha1.ConditionUnknown, string(cfbinding.State), cfbinding.StateDescription)
218+
return ctrl.Result{Requeue: true}, nil
219+
}
184220
}
185221

186222
if serviceBinding.DeletionTimestamp.IsZero() {
@@ -268,10 +304,12 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque
268304
} else if cfbinding.Generation < serviceBinding.Generation {
269305
// metadata updates (such as updating the generation here) are possible with service bindings
270306
log.V(1).Info("triggering update")
307+
271308
if err := client.UpdateBinding(
272309
ctx,
273310
cfbinding.Guid,
274311
serviceBinding.Generation,
312+
nil,
275313
); err != nil {
276314
return ctrl.Result{}, err
277315
}
@@ -284,8 +322,8 @@ func (r *ServiceBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque
284322
status.ServiceInstanceDigest = serviceInstance.Status.ServiceInstanceDigest
285323

286324
if cfbinding == nil {
287-
// Re-retrieve cloud foundry binding; this happens exactly if the binding was created or updated above
288-
cfbinding, err = client.GetBinding(ctx, string(serviceBinding.UID))
325+
// Re-retrieve cloud foundry binding by UID; this happens exactly if the binding was created or updated above
326+
cfbinding, err = client.GetBinding(ctx, bindingOpts)
289327
if err != nil {
290328
return ctrl.Result{}, err
291329
}

internal/controllers/serviceinstance_controller.go

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,49 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ
192192

193193
// Retrieve cloud foundry instance
194194
var cfinstance *facade.Instance
195+
instanceOpts := map[string]string{"name": "", "owner": string(serviceInstance.UID)}
195196
if client != nil {
196-
cfinstance, err = client.GetInstance(ctx, string(serviceInstance.UID))
197+
cfinstance, err = client.GetInstance(ctx, instanceOpts)
197198
if err != nil {
198199
return ctrl.Result{}, err
199200
}
201+
orphan, exists := serviceInstance.Annotations[cfv1alpha1.AnnotationAdoptCFResources]
202+
if exists && cfinstance == nil && orphan == "adopt" {
203+
// find orphaned instance by name
204+
instanceOpts["name"] = serviceInstance.Name
205+
cfinstance, err = client.GetInstance(ctx, instanceOpts)
206+
if err != nil {
207+
return ctrl.Result{}, err
208+
}
209+
210+
//Add parameters to adopt the orphaned instance
211+
var parameterObjects []map[string]interface{}
212+
paramMap := make(map[string]interface{})
213+
paramMap["parameter-hash"] = cfinstance.ParameterHash
214+
paramMap["owner"] = cfinstance.Owner
215+
parameterObjects = append(parameterObjects, paramMap)
216+
parameters, err := mergeObjects(parameterObjects...)
217+
if err != nil {
218+
return ctrl.Result{}, errors.Wrap(err, "failed to unmarshal/merge parameters")
219+
}
220+
// update the orphaned cloud foundry instance
221+
log.V(1).Info("triggering update")
222+
if err := client.UpdateInstance(
223+
ctx,
224+
cfinstance.Guid,
225+
spec.Name,
226+
"",
227+
parameters,
228+
nil,
229+
serviceInstance.Generation,
230+
); err != nil {
231+
return ctrl.Result{}, err
232+
}
233+
status.LastModifiedAt = &[]metav1.Time{metav1.Now()}[0]
234+
// return the reconcile function to requeue inmediatly after the update
235+
serviceInstance.SetReadyCondition(cfv1alpha1.ConditionUnknown, string(cfinstance.State), cfinstance.StateDescription)
236+
return ctrl.Result{Requeue: true}, nil
237+
}
200238
}
201239

202240
if serviceInstance.DeletionTimestamp.IsZero() {
@@ -243,6 +281,7 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ
243281
return ctrl.Result{}, fmt.Errorf("secret key not found, secret name: %s, key: %s", secretName, pf.SecretKeyRef.Key)
244282
}
245283
}
284+
246285
parameters, err := mergeObjects(parameterObjects...)
247286
if err != nil {
248287
return ctrl.Result{}, errors.Wrap(err, "failed to unmarshal/merge parameters")
@@ -324,8 +363,8 @@ func (r *ServiceInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Requ
324363
}
325364

326365
if cfinstance == nil {
327-
// Re-retrieve cloud foundry instance; this happens exactly if the instance was created or updated above
328-
cfinstance, err = client.GetInstance(ctx, string(serviceInstance.UID))
366+
// Re-retrieve cloud foundry instance by UID; this happens exactly if the instance was created or updated above
367+
cfinstance, err = client.GetInstance(ctx, instanceOpts)
329368
if err != nil {
330369
return ctrl.Result{}, err
331370
}

internal/facade/client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ type OrganizationClientBuilder func(string, string, string, string) (Organizatio
7777

7878
//counterfeiter:generate . SpaceClient
7979
type SpaceClient interface {
80-
GetInstance(ctx context.Context, owner string) (*Instance, error)
80+
GetInstance(ctx context.Context, instanceOpts map[string]string) (*Instance, error)
8181
CreateInstance(ctx context.Context, name string, servicePlanGuid string, parameters map[string]interface{}, tags []string, owner string, generation int64) error
8282
UpdateInstance(ctx context.Context, guid string, name string, servicePlanGuid string, parameters map[string]interface{}, tags []string, generation int64) error
8383
DeleteInstance(ctx context.Context, guid string) error
8484

85-
GetBinding(ctx context.Context, owner string) (*Binding, error)
85+
GetBinding(ctx context.Context, bindingOpts map[string]string) (*Binding, error)
8686
CreateBinding(ctx context.Context, name string, serviceInstanceGuid string, parameters map[string]interface{}, owner string, generation int64) error
87-
UpdateBinding(ctx context.Context, guid string, generation int64) error
87+
UpdateBinding(ctx context.Context, guid string, generation int64, parameters map[string]interface{}) error
8888
DeleteBinding(ctx context.Context, guid string) error
8989

9090
FindServicePlan(ctx context.Context, serviceOfferingName string, servicePlanName string, spaceGuid string) (string, error)

0 commit comments

Comments
 (0)