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

feat: Implement SpaceBindingRequest controller main logic #779

Merged
merged 16 commits into from
Aug 23, 2023

Conversation

mfrancisc
Copy link
Contributor

@mfrancisc mfrancisc commented Aug 14, 2023

Add e2e tests for SpaceBindingRequests controller.

Scenarios covered:

  • creation of a spacebindingrequest
  • update of a spacebindingrequest
  • deletion of a spacebindingrequest
  • validate spacebindingrequest fields

Jira: https://issues.redhat.com/browse/ASC-397

Paired with: codeready-toolchain/host-operator#848

Related PRs:

api codeready-toolchain/api#372
toolchain-common codeready-toolchain/toolchain-common#322

make/clean.mk Outdated
@@ -13,6 +13,7 @@ clean-users:
$(Q)-oc delete usersignups --all --all-namespaces
$(Q)-oc delete spacerequests --all --all-namespaces
$(Q)-oc delete spaces --all --all-namespaces
$(Q)-oc delete spacebindingrequests --all --all-namespaces
Copy link
Collaborator

Choose a reason for hiding this comment

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

this should be executed before deleting Spaces. Deletion of Spaces should trigger deletion of everything in the namespaces, including SBR. That being said - do we need to execute the deletion of SRs and SBRs explicitly at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right , we don't need to delete spacebindingrequests , but we do need to delete spacerequests because the spaces created by those cannot be deleted without deleting their SR resource.

I'll remove this line.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the deletion of spacebindingrequests in 9fda3e4

t.Run("create space binding request", func(t *testing.T) {
// when
// we create a space to share , a new MUR and a SpaceBindingRequest
space, spaceBindingRequest, spaceBinding := NewSpaceBindingRequest(t, awaitilities, memberAwait, hostAwait, "admin")
Copy link
Collaborator

Choose a reason for hiding this comment

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

NewSpaceBindingRequest name is very misleading when I look at what it all does. Not sure how to name it better 🤷‍♂️

Suggested change
space, spaceBindingRequest, spaceBinding := NewSpaceBindingRequest(t, awaitilities, memberAwait, hostAwait, "admin")
space, spaceBindingRequest, spaceBinding := CreateSbrForMur(t, awaitilities, memberAwait, hostAwait, "admin")

Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe

Suggested change
space, spaceBindingRequest, spaceBinding := NewSpaceBindingRequest(t, awaitilities, memberAwait, hostAwait, "admin")
space, spaceBindingRequest, spaceBinding := EnsureSpaceBindingRequest(t, awaitilities, memberAwait, hostAwait, "admin")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I named it this way because we also have NewSpaceRequest , which does similar thing but for SpaceRequest , if we want to rename it I would opt for EnsureSpaceBindingRequest.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Another alternative could be: setupSpaceBindingRequest .

Copy link
Contributor

Choose a reason for hiding this comment

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

what about CreateSpaceBindingRequest, since we also have CreateSpace? (which is also called in this func, btw)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CreateSpaceBindingRequest makes sense , but I've already used it here https://github.com/codeready-toolchain/toolchain-e2e/pull/779/files#diff-d21939085e6a53e4b5e5b3b1158e837fe749eee6e124ba8a29645c714f0a859dR33 😄 , so not sure ...

Copy link
Collaborator

Choose a reason for hiding this comment

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

naming is hard 😄

Comment on lines +231 to +233
// wait for the namespace to be provisioned since we will be creating the SpaceBindingRequest into it.
space, err := hostAwait.WaitForSpace(t, space.Name, UntilSpaceHasAnyProvisionedNamespaces())
require.NoError(t, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

the CreateSpace function already executes:

		_, err = memberAwait.WaitForNSTmplSet(t, space.Name,
			wait.UntilNSTemplateSetHasSpaceRoles(
				wait.SpaceRole(tier.Spec.SpaceRoles["admin"].TemplateRef, mur.Name)))

so we can assume that it's ready for new SpaceBindings to be created

Suggested change
// wait for the namespace to be provisioned since we will be creating the SpaceBindingRequest into it.
space, err := hostAwait.WaitForSpace(t, space.Name, UntilSpaceHasAnyProvisionedNamespaces())
require.NoError(t, err)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see your point, but the reason I added this wait , is because even when the NSTemplateSet was ready the Space.Status.ProvisionedNamespaces field was not populated yet, and I'm taking the name of the namespace from there and creating the SBR in that namespace, see few line below at https://github.com/codeready-toolchain/toolchain-e2e/pull/779/files#diff-c5776fe9a155ff05f73263e14f488ec3b27200aa736d4d367cc1c86b4bedb72eR248

but maybe I'm doing it in the wrong way?

Copy link
Collaborator

Choose a reason for hiding this comment

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

ah, makes sense, I missed that part. Thanks for explanation 👍

// a new SpaceBinding is created
// with the same name but creation timestamp should be greater (more recent).
require.NoError(t, err)
spaceBinding, err = hostAwait.WithRetryOptions(TimeoutOption(time.Second*10), RetryInterval(time.Second*2)).WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name,
Copy link
Collaborator

Choose a reason for hiding this comment

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

is there any reason for using the different retry options?

  • shorter timeout maybe could cause flakiness if the go routine executing this test gets paused for a few seconds
  • I don't see any reasoning for the longer retry interval - we could keep it short to have fast feedback loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I increased the retry interval and timeout because I'm noticing the test fails from time to time with this error :

    ----
        diffs:
        expected SpaceBinding to be created after 2023-08-17 08:43:00 +0000 UTC; Actual creation timestamp 2023-08-17 08:43:00 +0000 UTC

full logs here
I'm pretty sure the SpaceBinding get's deleted, because I've observed that and manually tested it on my temp cluster, but I think this WaitForSpaceBinding function is picking up the SpaceBinding before it get's deleted, for this reason I've tried waiting more and I've also added this check on the UID : UntilSpaceBindingHasDifferentUID(oldUID)

maybe I can remove now the retry interval and see if it's more stable, or at least add a comment to explain why is there. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I've noticed after that the default timeout was actually bigger than what I was setting, so I've removed that and also removed the creationTimeStamp check ,I've left only the UID one, please see: 88eaeb5

Copy link
Collaborator

Choose a reason for hiding this comment

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

cool, thanks 👍

Comment on lines +103 to +105
// wait for the namespace to be provisioned since we will be creating the SpaceBindingRequest into it.
space, err := hostAwait.WaitForSpace(t, space.Name, UntilSpaceHasAnyProvisionedNamespaces())
require.NoError(t, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

the same - no need for another wait

Suggested change
// wait for the namespace to be provisioned since we will be creating the SpaceBindingRequest into it.
space, err := hostAwait.WaitForSpace(t, space.Name, UntilSpaceHasAnyProvisionedNamespaces())
require.NoError(t, err)

}
}

func WithNamespace(ns string) SpaceBindingRequestOption {
Copy link
Collaborator

Choose a reason for hiding this comment

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

wouldn't be InNamespace more appropriate?

Copy link
Contributor Author

@mfrancisc mfrancisc Aug 17, 2023

Choose a reason for hiding this comment

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

right , that is how I wanted to name it too but that function already exists for SpaceRequests in the same package:

func InNamespace(namespace string) SpaceRequestOption {
return func(s *toolchainv1alpha1.SpaceRequest) {
s.ObjectMeta.Namespace = namespace
}
}

Maybe I can make this more generic and use it for both ?

Copy link
Contributor Author

@mfrancisc mfrancisc Aug 21, 2023

Choose a reason for hiding this comment

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

Alternatively we could group the files under testsupport folder into smaller packages , for example we can move:

  • space.go , spacerequest.go into a new folder testsupport/space
  • spacebinding.go , spacebindingrequest.go into testsupport/spacebinding

but I suggest we do this in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I like the With prefix because it's the same for all Option.
There's a pattern that works in case we want to keep everything in the same package, but it requires a bit of refactoring:

Something like:


type SpaceOption interface {
  ApplyToSpace(s *Space)
}

type SpaceRequestOption interface {
  ApplyToSpaceRequest(sr *SpaceRequest)
}

type WithNamespace struct {
  ns string
}

var _ SpaceOption = WithNamespace{}  // requires a `ApplyToSpace(s *Space)` method 

func (w WithNamespace) ApplyToSpace(s *Space) {...}

var _ SpaceRequestOption = WithNamespace{} // requires a `ApplyToSpaceRequest(s *SpaceRequest)` method 

func (w WithNamespace) ApplyToSpaceRequest(sr *SpaceRequest) {...}

hence, we can have the same WithNamespace(...) option to initialize Spaces and SpaceRequests resources.

Copy link
Contributor

Choose a reason for hiding this comment

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

that being said, having a sub package for each type is fine and probably easier to do, given the current codebase!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@xcoulon thanks a lot! indeed your pattern proposal is another option. I don't have a strong opinion, even if looking at it probably the package segregation would be "easier" ( just changing the import statements ), while this new pattern I'm not sure I saw it used anywhere else in our code ?

My proposal is to decide which path we want to go and I'll address it in a follow up PR. WDYT ?

Copy link
Contributor

Choose a reason for hiding this comment

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

I believe that splitting (or segregating) the code in testsupport/space + testsupport/spacebinding is probably the easiest thing to do for now, so let's go with that. Also, that's what we also did in toolchain-common/pkg/test anyways (see all the subfolders for masteruserrecords, etc.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, thanks ! I'm adding that to my backlog and will come up with a PR once I've merged the existing ones.

test/e2e/parallel/spacebindingrequest_test.go Show resolved Hide resolved
test/e2e/parallel/spacebindingrequest_test.go Show resolved Hide resolved
spaceBindingRequest, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()},
UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.Ready()),
)
require.NoError(t, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

but we could maybe check that everything was propagated to the NSTemplateSet correctly here, WDYT?
something like this:

	if spaceRole == "admin" {
		_, err = memberAwait.WaitForNSTmplSet(t, space.Name,
			UntilNSTemplateSetHasSpaceRoles(
				SpaceRole(tier.Spec.SpaceRoles[spaceRole].TemplateRef, userSignup.Status.CompliantUsername, userSignup.mur.Name)))
	} else {
		_, err = memberAwait.WaitForNSTmplSet(t, space.Name,
			UntilNSTemplateSetHasSpaceRoles(
				SpaceRole(tier.Spec.SpaceRoles["admin"].TemplateRef, userSignup.Status.CompliantUsername),
				SpaceRole(tier.Spec.SpaceRoles[spaceRole].TemplateRef, userSignup.mur.Name)))
	}
	VerifyResourcesProvisionedForSpace(t, awaitilities, space.Name)

you know - to be sure that the SpaceBinding that we created will be properly processed by the Space controller

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok I see what you meant , but I'm not 100% sure I understood your example code - why do we need to do it this way?

If I'm not mistaken the usernames added to the NSTemplateSet are the just the SpaceBinding.Spec.MasterUserRecord : https://github.com/codeready-toolchain/host-operator/blob/723b3c9794f442315117b4fd55dc4eb028930246/controllers/space/space_controller.go#L458-L471

So in theory this could be just:

tier, err := awaitilities.Host().WaitForNSTemplateTier(t, space.Spec.TierName)
_, err = memberAwait.WaitForNSTmplSet(t, space.Name,
      wait.UntilNSTemplateSetHasSpaceRoles(
	wait.SpaceRole(tier.Spec.SpaceRoles[spaceRole].TemplateRef, mur.Name)))
require.NoError(t, err)
VerifyResourcesProvisionedForSpace(t, awaitilities, space.Name)

but I'm sure I'm missing something.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I now understand why we need todo it this way, it's because we have 2 murs 🤦 .
Done in 88eaeb5

require.NoError(t, err)
})

t.Run("update space binding request MasterUserRecord", func(t *testing.T) {
Copy link
Collaborator

@MatousJobanek MatousJobanek Aug 17, 2023

Choose a reason for hiding this comment

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

I'm wondering how much we want to support this scenario. I would love to make the MUR immutable, which would require an admission webhook of course...
After all, the SpaceBinding name contains the MUR name, so as soon as we would change it, then it wouldn't match anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Makes sense, I haven't considered that the name doesn't change!
I'm now updating the mur name in the spacebinding spec and label but not changing the name, so maybe we should not allow this sort of update. But right now the user/client has the ability to update this field, so wanted to make sure the code works as "expected"

Copy link
Collaborator

Choose a reason for hiding this comment

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

sure, that was more as a follow up task

@mfrancisc
Copy link
Contributor Author

/retest

UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.UnableToCreateSpaceBinding("unable to get MUR: MasterUserRecord.toolchain.dev.openshift.com \"invalidMUR\" not found")),
)
require.NoError(t, err)
bindings, err := hostAwait.ListSpaceBindings(space.Name)
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe it would make more sense to check the number of space bindings first, but it's just a minor suggestion

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry, you mean checking the number of spacebindings before waiting for the SpaceBindingRequest to have the expected conditions ? Not sure I understood your suggestion ..

Copy link
Contributor

Choose a reason for hiding this comment

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

ah never mind, I mixed up space bindings and space requests 🤦‍♂️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, no worries, just wanted to be sure!

Copy link
Contributor

@xcoulon xcoulon left a comment

Choose a reason for hiding this comment

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

looks good to me, only a couple of minor suggestions but nothing critical

@openshift-ci
Copy link

openshift-ci bot commented Aug 22, 2023

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: mfrancisc, xcoulon

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@openshift-ci openshift-ci bot removed the lgtm label Aug 23, 2023
@openshift-ci
Copy link

openshift-ci bot commented Aug 23, 2023

New changes are detected. LGTM label has been removed.

@sonarcloud
Copy link

sonarcloud bot commented Aug 23, 2023

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 3 Code Smells

No Coverage information No Coverage information
3.4% 3.4% Duplication

@mfrancisc
Copy link
Contributor Author

@MatousJobanek I'm merging this , but we can keep the discussion going, and I'll address any other comment in follow up PRs.

@mfrancisc mfrancisc merged commit 56cc5bc into codeready-toolchain:master Aug 23, 2023
7 checks passed
@mfrancisc mfrancisc deleted the sbrmain branch August 23, 2023 11:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants