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
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module github.com/codeready-toolchain/toolchain-e2e

require (
github.com/codeready-toolchain/api v0.0.0-20230711103642-544bb7e0cf9e
github.com/codeready-toolchain/api v0.0.0-20230809072818-b2867db6f98e
github.com/codeready-toolchain/toolchain-common v0.0.0-20230710095440-719b09376de3
github.com/davecgh/go-spew v1.1.1
github.com/fatih/color v1.12.0
Expand Down Expand Up @@ -121,3 +121,7 @@ require (
)

go 1.19

replace github.com/codeready-toolchain/api => github.com/mfrancisc/api v0.0.0-20230816100836-fac7152b505d

replace github.com/codeready-toolchain/toolchain-common => github.com/mfrancisc/toolchain-common v0.0.0-20230816123918-46ac43731884
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/codeready-toolchain/api v0.0.0-20230711103642-544bb7e0cf9e h1:T6Ay8YwCGPXCufh1toy7QdCwqPJc5SIA0kkOh/be9ZE=
github.com/codeready-toolchain/api v0.0.0-20230711103642-544bb7e0cf9e/go.mod h1:nn3W6eKb9PFIVwSwZW7wDeLACMBOwAV+4kddGuN+ARM=
github.com/codeready-toolchain/toolchain-common v0.0.0-20230710095440-719b09376de3 h1:zPxv/JJRZsXS+OVgsrF1egFlTi45DXJ8MTPi50meujI=
github.com/codeready-toolchain/toolchain-common v0.0.0-20230710095440-719b09376de3/go.mod h1:vtUfWOJBDxQP1DtcIoxfjI5heBGcT8D+C8ux+PLouyg=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
Expand Down Expand Up @@ -473,6 +469,10 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/mfrancisc/api v0.0.0-20230816100836-fac7152b505d h1:0Lf0q+eoy0pa5TOb0JQEbW8sLRa5L+S7wg2OiTkJaoo=
github.com/mfrancisc/api v0.0.0-20230816100836-fac7152b505d/go.mod h1:nn3W6eKb9PFIVwSwZW7wDeLACMBOwAV+4kddGuN+ARM=
github.com/mfrancisc/toolchain-common v0.0.0-20230816123918-46ac43731884 h1:0AGkS6mBP2NULh5xmOpEVct5r2jcnY2vdfn5FwTfHPY=
github.com/mfrancisc/toolchain-common v0.0.0-20230816123918-46ac43731884/go.mod h1:7QL2bsy2U3YcmIPAqm/x/Fje9STEa3IKKn1ICiCPzxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/migueleliasweb/go-github-mock v0.0.18 h1:0lWt9MYmZQGnQE2rFtjlft/YtD6hzxuN6JJRFpujzEI=
github.com/migueleliasweb/go-github-mock v0.0.18/go.mod h1:CcgXcbMoRnf3rRVHqGssuBquZDIcaplxL2W6G+xs7kM=
Expand Down
1 change: 1 addition & 0 deletions make/clean.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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

$(Q)-oc wait --for=delete namespaces -l toolchain.dev.openshift.com/type

.PHONY: clean-cluster-wide-config
Expand Down
267 changes: 267 additions & 0 deletions test/e2e/parallel/spacebindingrequest_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
package parallel

import (
"context"
"fmt"
"testing"
"time"

toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/gofrs/uuid"

spacebindingrequesttestcommon "github.com/codeready-toolchain/toolchain-common/pkg/test/spacebindingrequest"

testspace "github.com/codeready-toolchain/toolchain-common/pkg/test/space"
. "github.com/codeready-toolchain/toolchain-e2e/testsupport"
. "github.com/codeready-toolchain/toolchain-e2e/testsupport/wait"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/types"
)

func TestCreateSpaceBindingRequest(t *testing.T) {
// given
t.Parallel()
// make sure everything is ready before running the actual tests
awaitilities := WaitForDeployments(t)
hostAwait := awaitilities.Host()
memberAwait := awaitilities.Member1()

t.Run("success", func(t *testing.T) {
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 😄


t.Run("spaceBinding is recreated if deleted ", func(t *testing.T) {
// now, delete the SpaceBinding,
// a new SpaceBinding will be provisioned by the SpaceBindingRequest.
//
// save the creation timestamp that will be used to ensure that a new SpaceBinding was created with the same name.
oldSpaceCreationTimeStamp := spaceBinding.CreationTimestamp
// save the old UID that will be used to ensure that a new SpaceBinding was created with the same name but new UID
oldUID := spaceBinding.UID

// when
err := hostAwait.Client.Delete(context.TODO(), spaceBinding)

// then
// 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 👍

UntilSpaceBindingHasMurName(spaceBindingRequest.Spec.MasterUserRecord),
UntilSpaceBindingHasSpaceName(space.Name),
UntilSpaceBindingHasSpaceRole(spaceBindingRequest.Spec.SpaceRole),
UntilSpaceBindingHasCreationTimestampGreaterThan(oldSpaceCreationTimeStamp.Time),
UntilSpaceBindingHasDifferentUID(oldUID),
UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestLabelKey, spaceBindingRequest.GetName()),
UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestNamespaceLabelKey, spaceBindingRequest.GetNamespace()),
)
require.NoError(t, err)

t.Run("SpaceBinding always reflects values from spaceBindingRequest ", func(t *testing.T) {
// given
// something/someone updates the SpaceRole directly on the SpaceBinding object

// when
spaceBinding, err = hostAwait.UpdateSpaceBinding(t, spaceBinding.Name, func(s *toolchainv1alpha1.SpaceBinding) {
s.Spec.SpaceRole = "invalidRole" // let's change the role
})
require.NoError(t, err)

// then
// spaceBindingRequest should reset back the SpaceRole
spaceBinding, err = hostAwait.WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name,
UntilSpaceBindingHasMurName(spaceBindingRequest.Spec.MasterUserRecord),
UntilSpaceBindingHasSpaceName(space.Name),
UntilSpaceBindingHasSpaceRole(spaceBindingRequest.Spec.SpaceRole), // should have back the role from the SBR
UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestLabelKey, spaceBindingRequest.GetName()),
UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestNamespaceLabelKey, spaceBindingRequest.GetNamespace()),
)
require.NoError(t, err)

t.Run("delete space binding request", func(t *testing.T) {
// now, delete the SpaceBindingRequest and expect that the SpaceBinding will be deleted as well,

// when
err := memberAwait.Client.Delete(context.TODO(), spaceBindingRequest)

// then
// spaceBinding should be deleted as well
require.NoError(t, err)
err = hostAwait.WaitUntilSpaceBindingDeleted(spaceBinding.Name)
require.NoError(t, err)
})
})
})
})
})

t.Run("error", func(t *testing.T) {
t.Run("unable create space binding request with invalid SpaceRole", func(t *testing.T) {
space, _, _ := CreateSpace(t, awaitilities, testspace.WithTierName("appstudio"), testspace.WithSpecTargetCluster(memberAwait.ClusterName))
// 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)
Comment on lines +101 to +103
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)

// let's create a new MUR that will have access to the space
username := uuid.Must(uuid.NewV4()).String()
_, mur := NewSignupRequest(awaitilities).
Username(username).
Email(username + "@acme.com").
ManuallyApprove().
TargetCluster(memberAwait).
RequireConditions(ConditionSet(Default(), ApprovedByAdmin())...).
NoSpace().
WaitForMUR().Execute(t).Resources()
// create the spacebinding request
spaceBindingRequest := CreateSpaceBindingRequest(t, awaitilities, memberAwait.ClusterName,
WithSpecSpaceRole("invalid"), // set invalid spacerole
WithSpecMasterUserRecord(mur.GetName()),
WithNamespace(GetDefaultNamespace(space.Status.ProvisionedNamespaces)),
)

// then
require.NoError(t, err)
// wait for spacebinding request status to be set
_, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()},
UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.UnableToCreateSpaceBinding(fmt.Sprintf("invalid role 'invalid' for space '%s'", space.Name))),
)
require.NoError(t, err)
mfrancisc marked this conversation as resolved.
Show resolved Hide resolved
})

t.Run("unable create space binding request with invalid MasterUserRecord", func(t *testing.T) {
space, _, _ := CreateSpace(t, awaitilities, testspace.WithTierName("appstudio"), testspace.WithSpecTargetCluster(memberAwait.ClusterName))
// 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)
// create the spacebinding request
spaceBindingRequest := CreateSpaceBindingRequest(t, awaitilities, memberAwait.ClusterName,
WithSpecSpaceRole("admin"),
WithSpecMasterUserRecord("invalidMUR"), // we set an invalid MUR
WithNamespace(GetDefaultNamespace(space.Status.ProvisionedNamespaces)),
)

// then
require.NoError(t, err)
// wait for spacebinding request status to be set
_, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()},
UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.UnableToCreateSpaceBinding("unable to get MUR: MasterUserRecord.toolchain.dev.openshift.com \"invalidMUR\" not found")),
)
require.NoError(t, err)
mfrancisc marked this conversation as resolved.
Show resolved Hide resolved
})
})
}

func TestUpdateSpaceBindingRequest(t *testing.T) {
// given
t.Parallel()
// make sure everything is ready before running the actual tests
awaitilities := WaitForDeployments(t)
hostAwait := awaitilities.Host()
memberAwait := awaitilities.Member1()

t.Run("update space binding request SpaceRole", func(t *testing.T) {
// when
space, spaceBindingRequest, _ := NewSpaceBindingRequest(t, awaitilities, memberAwait, hostAwait, "contributor")
_, err := memberAwait.UpdateSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.Namespace, Name: spaceBindingRequest.Name},
func(s *toolchainv1alpha1.SpaceBindingRequest) {
s.Spec.SpaceRole = "admin" // set to admin from contributor
},
)
require.NoError(t, err)

//then
// wait for both SpaceBindingRequest and SpaceBinding to have same SpaceRole
spaceBindingRequest, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()},
UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.Ready()),
UntilSpaceBindingRequestHasSpecSpaceRole("admin"), // has admin role
UntilSpaceBindingRequestHasSpecMUR(spaceBindingRequest.Spec.MasterUserRecord),
)
require.NoError(t, err)
_, err = hostAwait.WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name,
UntilSpaceBindingHasMurName(spaceBindingRequest.Spec.MasterUserRecord),
UntilSpaceBindingHasSpaceName(space.Name),
UntilSpaceBindingHasSpaceRole("admin"), // has admin role
UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestLabelKey, spaceBindingRequest.GetName()),
UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestNamespaceLabelKey, spaceBindingRequest.GetNamespace()),
)
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

// when
space, spaceBindingRequest, _ := NewSpaceBindingRequest(t, awaitilities, memberAwait, hostAwait, "admin")
// let's create another MUR that will have access to the space
username := uuid.Must(uuid.NewV4()).String()
_, newmur := NewSignupRequest(awaitilities).
Username(username).
Email(username + "@acme.com").
ManuallyApprove().
TargetCluster(memberAwait).
RequireConditions(ConditionSet(Default(), ApprovedByAdmin())...).
NoSpace().
WaitForMUR().Execute(t).Resources()
// and we update the MUR in the SBR
_, err := memberAwait.UpdateSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.Namespace, Name: spaceBindingRequest.Name},
func(s *toolchainv1alpha1.SpaceBindingRequest) {
s.Spec.MasterUserRecord = newmur.GetName() // set to the new MUR
},
)
require.NoError(t, err)

//then
// wait for both SpaceBindingRequest and SpaceBinding to have same MUR
spaceBindingRequest, err = memberAwait.WaitForSpaceBindingRequest(t, types.NamespacedName{Namespace: spaceBindingRequest.GetNamespace(), Name: spaceBindingRequest.GetName()},
UntilSpaceBindingRequestHasConditions(spacebindingrequesttestcommon.Ready()),
UntilSpaceBindingRequestHasSpecSpaceRole(spaceBindingRequest.Spec.SpaceRole),
UntilSpaceBindingRequestHasSpecMUR(newmur.GetName()), // new MUR
)
require.NoError(t, err)
_, err = hostAwait.WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name,
UntilSpaceBindingHasMurName(newmur.GetName()), // has new MUR
UntilSpaceBindingHasSpaceName(space.Name),
UntilSpaceBindingHasSpaceRole(spaceBindingRequest.Spec.SpaceRole),
)
require.NoError(t, err)
})
}

func NewSpaceBindingRequest(t *testing.T, awaitilities Awaitilities, memberAwait *MemberAwaitility, hostAwait *HostAwaitility, spaceRole string) (*toolchainv1alpha1.Space, *toolchainv1alpha1.SpaceBindingRequest, *toolchainv1alpha1.SpaceBinding) {
space, _, _ := CreateSpace(t, awaitilities, testspace.WithTierName("appstudio"), testspace.WithSpecTargetCluster(memberAwait.ClusterName))
// 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)
Comment on lines +235 to +237
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 👍

// let's create a new MUR that will have access to the space
username := uuid.Must(uuid.NewV4()).String()
_, mur := NewSignupRequest(awaitilities).
Username(username).
Email(username + "@acme.com").
ManuallyApprove().
TargetCluster(memberAwait).
RequireConditions(ConditionSet(Default(), ApprovedByAdmin())...).
NoSpace().
WaitForMUR().Execute(t).Resources()
// create the spacebinding request
spaceBindingRequest := CreateSpaceBindingRequest(t, awaitilities, memberAwait.ClusterName,
WithSpecSpaceRole(spaceRole),
WithSpecMasterUserRecord(mur.GetName()),
WithNamespace(GetDefaultNamespace(space.Status.ProvisionedNamespaces)),
)

// then
// check for the spaceBinding creation
spaceBinding, err := hostAwait.WaitForSpaceBinding(t, spaceBindingRequest.Spec.MasterUserRecord, space.Name,
UntilSpaceBindingHasMurName(spaceBindingRequest.Spec.MasterUserRecord),
UntilSpaceBindingHasSpaceName(space.Name),
UntilSpaceBindingHasSpaceRole(spaceBindingRequest.Spec.SpaceRole),
UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestLabelKey, spaceBindingRequest.GetName()),
UntilSpaceBindingHasLabel(toolchainv1alpha1.SpaceBindingRequestNamespaceLabelKey, spaceBindingRequest.GetNamespace()),
)
require.NoError(t, err)
// wait for spacebinding request status
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

return space, spaceBindingRequest, spaceBinding
}
50 changes: 50 additions & 0 deletions testsupport/spacebindingrequest.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package testsupport

import (
"testing"

toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/codeready-toolchain/toolchain-e2e/testsupport/wait"

"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type SpaceBindingRequestOption func(request *toolchainv1alpha1.SpaceBindingRequest)

func WithSpecSpaceRole(spaceRole string) SpaceBindingRequestOption {
return func(s *toolchainv1alpha1.SpaceBindingRequest) {
s.Spec.SpaceRole = spaceRole
}
}

func WithSpecMasterUserRecord(mur string) SpaceBindingRequestOption {
return func(s *toolchainv1alpha1.SpaceBindingRequest) {
s.Spec.MasterUserRecord = mur
}
}

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.

return func(s *toolchainv1alpha1.SpaceBindingRequest) {
s.ObjectMeta.Namespace = ns
}
}

func CreateSpaceBindingRequest(t *testing.T, awaitilities wait.Awaitilities, memberName string, opts ...SpaceBindingRequestOption) *toolchainv1alpha1.SpaceBindingRequest {
memberAwait, err := awaitilities.Member(memberName)
require.NoError(t, err)
namePrefix := NewObjectNamePrefix(t)

spaceBindingRequest := &toolchainv1alpha1.SpaceBindingRequest{
ObjectMeta: metav1.ObjectMeta{
GenerateName: namePrefix + "-",
},
}
for _, apply := range opts {
apply(spaceBindingRequest)
}
err = memberAwait.CreateWithCleanup(t, spaceBindingRequest)
require.NoError(t, err)

return spaceBindingRequest
}
Loading
Loading