Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopting Orphan Instances via Kubernetes Annotation #50

Merged
merged 16 commits into from
Jun 25, 2024

Conversation

santiago-ventura
Copy link
Contributor

Motivation

We introduce the new annotation service-operator.cf.cs.sap.com/adopt-instances to facilitate the adoption of orphan instances.
Currently, the only way is via a call to the Cloud Foundry API, as explained in the documentation "Adopt existing resources".

This allows updating the Orphaned Cloud Foundry instances with the label owner and the annotations generation and parameter-hash.

@@ -29,10 +44,19 @@ func (c *spaceClient) GetBinding(ctx context.Context, owner string) (*facade.Bin
if len(serviceBindings) == 0 {
return nil, nil
} else if len(serviceBindings) > 1 {
return nil, fmt.Errorf("found multiple service bindings with owner: %s", owner)
return nil, fmt.Errorf("found multiple service bindings with owner: %s", value)
Copy link
Contributor

@RalfHammer RalfHammer Jun 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

message "found multiple service bindings with owner" is only correct in case of empty bindingName
Either use a more generic message or distinguish both cases in this message.

internal/cf/binding.go Outdated Show resolved Hide resolved
Comment on lines 29 to 33
if bindingName != "" {
listOpts.Names.EqualTo(bindingName)
} else {
listOpts.LabelSelector.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, owner))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runtime check is a little ugly. How about taking the listOpts as an argument?

Comment on lines 51 to 58
// add parameter values to the cf instance
if bindingName != "" {
generationvalue := "0"
serviceBinding.Metadata.Annotations[annotationGeneration] = &generationvalue
parameterHashValue := "0"
serviceBinding.Metadata.Annotations[annotationParameterHash] = &parameterHashValue
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a parameter to the binding in a method called GetBinding is a bit unexpected for me. Consider moving this to the reconcile function.

Comment on lines 29 to 33
if instanceName != "" {
listOpts.Names.EqualTo(instanceName)
} else {
listOpts.LabelSelector.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, owner))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above

internal/cf/instance.go Outdated Show resolved Hide resolved
Comment on lines 51 to 58
// add parameter values to the orphan cf instance
if instanceName != "" {
generationvalue := "0"
serviceInstance.Metadata.Annotations[annotationGeneration] = &generationvalue
parameterHashValue := "0"
serviceInstance.Metadata.Annotations[annotationParameterHash] = &parameterHashValue
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above

if err != nil {
return ctrl.Result{}, err
}
if exists && bindingName != "" && cfbinding != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are already in an if exists && cfbinding != nil block, and we just set bindingName to serviceBinding.Name (which is guaranteed to exist in the type). This if statement can be simplified to if true, therefore removed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was mistaken here, the cfbinding variable is shadowed, so we still need to check for cfbinding != nil

if err != nil {
return ctrl.Result{}, err
}
if exists && instanceName != "" && cfinstance != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above for bindings

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above, cfinstance is shadowed, so we need the check for cfinstance != nil

api/v1alpha1/constants.go Outdated Show resolved Hide resolved
Comment on lines 27 to 34
func (c *spaceClient) GetBinding(ctx context.Context, mapBindingOpts map[string]string) (*facade.Binding, error) {
listOpts := cfclient.NewServiceCredentialBindingListOptions()
listOpts.LabelSelector.EqualTo(labelPrefix + "/" + labelKeyOwner + "=" + owner)
if mapBindingOpts["name"] != "" {
listOpts.Names.EqualTo(mapBindingOpts["name"])
} else {
listOpts.LabelSelector.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, mapBindingOpts["owner"]))
}

Copy link
Contributor

@bKiralyWdf bKiralyWdf Jun 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about replacing this with

func (c *spaceClient) GetBinding(ctx context.Context, listOpts cfclient.ServiceCredentialBindingListOptions) (*facade.Binding, error) {

And then just creating the listOpts at the servicebinding_controller level?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suggested change is not viable. ServiceCredentialBindingListOptions is part of the go-cfclient library's client package.

The package controller does not have direct access to the go-cfclient library's client package. Instead, it has an interface called SpaceClient, which wraps any interaction with the go-client library.

That is a problem because changing the function GetBinding to receive listOpts will break the designed separation between the controller and the go-cfclient library.

Also, we will have to create the LabelSelector in the controller:
listOpts.LabelSelector.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, mapBindingOpts["owner"]))

labelPrefix: is only visible within the cf package, not the controller package.
labelKeyOwner: is only visible within the cf package, not the controller package.

Again, this breaks the current design where those variables are only visible within the cf package.

Change this:
const (
labelPrefix = "service-operator.cf.cs.sap.com"
labelKeyOwner = "owner"
labelOwner = labelPrefix + "/" + labelKeyOwner
annotationPrefix = "service-operator.cf.cs.sap.com"
annotationKeyGeneration = "generation"
annotationGeneration = annotationPrefix + "/" + annotationKeyGeneration
annotationKeyParameterHash = "parameter-hash"
annotationParameterHash = annotationPrefix + "/" + annotationKeyParameterHash
)

to

const (
LabelPrefix = "service-operator.cf.cs.sap.com"
LabelKeyOwner = "owner"
LabelOwner = labelPrefix + "/" + labelKeyOwner
AnnotationPrefix = "service-operator.cf.cs.sap.com"
AnnotationKeyGeneration = "generation"
AnnotationGeneration = annotationPrefix + "/" + annotationKeyGeneration
AnnotationKeyParameterHash = "parameter-hash"
AnnotationParameterHash = annotationPrefix + "/" + annotationKeyParameterHash
)

This change is not going to be accepted because we will have to break the separation of concerns between the controller and the cf client.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right!

I thought about including the gocfclient in the controller might not be very nice, I guess it is worse than I thought since you have to also expose the constants.

Maybe add a new type instead, to pass the owner or name? Like this:

type BindingFilter interface {
	GetListOptions() *cfclient.ServiceCredentialBindingListOptions
}

type BindingFilterName struct {
	name string
}
type BindingFilterOwner struct {
	owner string
}

func (bn *BindingFilterName) GetListOptions() *cfclient.ServiceCredentialBindingListOptions {
	listOpts := cfclient.NewServiceCredentialBindingListOptions()
	listOpts.Names.EqualTo(bn.name)
	return listOpts
}

func (bo *BindingFilterOwner) GetListOptions() *cfclient.ServiceCredentialBindingListOptions {
	listOpts := cfclient.NewServiceCredentialBindingListOptions()
	listOpts.LabelSelector.EqualTo(fmt.Sprintf("%s/%s=%s", labelPrefix, labelKeyOwner, bo.owner))
	return listOpts
}

func (c *spaceClient) GetBinding(ctx context.Context, filter BindindingFilter) (*facade.Binding, error) {
  listOpts := filter.GetListOptions()
  //...

This way above in the Reconcile, you could just call the function like so GetBinding(ctx, BindingFilterOwner{name: "foo"}) or GetBinding(BindingFilterOwner{owner: "bar"}. This is nice, since the type is checked at compile time, so we don't need the runtime check for the string. It also makes the function signature nicer and removes the need to initialize a complex string map.

bKiralyWdf
bKiralyWdf previously approved these changes Jun 21, 2024
@santiago-ventura santiago-ventura marked this pull request as ready for review June 21, 2024 13:56
@TheBigElmo TheBigElmo merged commit 3d152c6 into main Jun 25, 2024
6 checks passed
@TheBigElmo TheBigElmo deleted the adoption-for-orphan-instances-2 branch June 25, 2024 16:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants