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 #848

Merged
merged 14 commits into from
Aug 23, 2023
Merged
362 changes: 361 additions & 1 deletion controllers/spacebindingrequest/spacebindingrequest_controller.go

Large diffs are not rendered by default.

Large diffs are not rendered by default.

33 changes: 7 additions & 26 deletions controllers/spacerequest/spacerequest_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"github.com/stretchr/testify/require"
"gopkg.in/h2non/gock.v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
Expand All @@ -37,7 +36,7 @@ func TestCreateSpaceRequest(t *testing.T) {
err := apis.AddToScheme(scheme.Scheme)
require.NoError(t, err)
appstudioTier := tiertest.AppStudioTier(t, tiertest.AppStudioTemplates)
srNamespace := newNamespace("jane")
srNamespace := spacerequesttest.NewNamespace("jane")
srClusterRoles := []string{commoncluster.RoleLabel(commoncluster.Tenant)}
parentSpace := spacetest.NewSpace(test.HostOperatorNs, "jane")
t.Run("success", func(t *testing.T) {
Expand Down Expand Up @@ -317,7 +316,7 @@ func TestCreateSpaceRequest(t *testing.T) {
spaceRequest := spacerequesttest.NewSpaceRequest("jane", "jane-tenant",
spacerequesttest.WithTierName("appstudio"),
spacerequesttest.WithTargetClusterRoles([]string{"member-2"})) // the provisioned namespace is targeted for member-2
spaceRequestNamespace := newNamespace("jane")
spaceRequestNamespace := spacerequesttest.NewNamespace("jane")
member1 := NewMemberClusterWithClient(test.NewFakeClient(t, spaceRequest, spaceRequestNamespace), "member-1", corev1.ConditionTrue) // spacerequest is created on member-1 but has target cluster member-2
// the provisioned namespace is on different cluster then the spacerequest resource
member2 := NewMemberClusterWithClient(test.NewFakeClient(t), "member-2", corev1.ConditionTrue,
Expand Down Expand Up @@ -431,7 +430,7 @@ func TestCreateSpaceRequest(t *testing.T) {

t.Run("unable to find space label in spaceRequest namespace", func(t *testing.T) {
// given
srNamespace := newNamespace("nospace")
srNamespace := spacerequesttest.NewNamespace("nospace")
sr := spacerequesttest.NewSpaceRequest("jane", srNamespace.GetName(),
spacerequesttest.WithTierName("appstudio"),
spacerequesttest.WithTargetClusterRoles(srClusterRoles))
Expand Down Expand Up @@ -661,7 +660,7 @@ func TestUpdateSpaceRequest(t *testing.T) {
err := apis.AddToScheme(scheme.Scheme)
require.NoError(t, err)
appstudioTier := tiertest.AppStudioTier(t, tiertest.AppStudioTemplates)
srNamespace := newNamespace("jane")
srNamespace := spacerequesttest.NewNamespace("jane")
parentSpace := spacetest.NewSpace(test.HostOperatorNs, "jane")
srClusterRoles := []string{commoncluster.RoleLabel(commoncluster.Tenant)}

Expand Down Expand Up @@ -853,7 +852,7 @@ func TestDeleteSpaceRequest(t *testing.T) {
err := apis.AddToScheme(scheme.Scheme)
require.NoError(t, err)
appstudioTier := tiertest.AppStudioTier(t, tiertest.AppStudioTemplates)
srNamespace := newNamespace("jane")
srNamespace := spacerequesttest.NewNamespace("jane")

Choose a reason for hiding this comment

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

Not your code per se -- can we put "jane" in a const? Seems it's been used in many place, to avoid any typos in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, thanks for pointing this out, it doesn't look very clean, there are usually some strings that can be extracted to const or variable in our testing code, just didn't saw this aligned with the other unit tests for other controllers.

Or maybe we could put the string into a variable instead of const ? 🤔

srClusterRoles := []string{commoncluster.RoleLabel(commoncluster.Tenant)}
parentSpace := spacetest.NewSpace(test.HostOperatorNs, "jane")
sr := spacerequesttest.NewSpaceRequest("jane",
Expand Down Expand Up @@ -921,8 +920,8 @@ func TestDeleteSpaceRequest(t *testing.T) {
// then
// space request is gone
require.NoError(t, err)
spacerequesttest.AssertThatSpaceRequest(t, srNamespace.Name, "jane-tenant", member1.Client).
DoesNotExist()
spacerequesttest.AssertThatSpaceRequest(t, srNamespace.Name, sr.GetName(), member1.Client).
HasNoFinalizers()
})
})

Expand Down Expand Up @@ -1050,24 +1049,6 @@ func requestFor(s *toolchainv1alpha1.SpaceRequest) reconcile.Request {
}
}

func newNamespace(spacename string) *corev1.Namespace {
labels := map[string]string{
toolchainv1alpha1.TypeLabelKey: "tenant",
toolchainv1alpha1.ProviderLabelKey: toolchainv1alpha1.ProviderLabelValue,
}
if spacename != "nospace" {
labels[toolchainv1alpha1.SpaceLabelKey] = spacename
}
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-tenant", spacename),
Labels: labels,
},
Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive},
}
return ns
}

func mockGetSpaceRequestFail(cl runtimeclient.Client) func(ctx context.Context, key runtimeclient.ObjectKey, obj runtimeclient.Object, opts ...runtimeclient.GetOption) error {
return func(ctx context.Context, key runtimeclient.ObjectKey, obj runtimeclient.Object, opts ...runtimeclient.GetOption) error {
if _, ok := obj.(*toolchainv1alpha1.SpaceRequest); ok {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,5 @@ require (
)

go 1.19

replace github.com/codeready-toolchain/api => github.com/mfrancisc/api v0.0.0-20230814144046-ecb17d118474
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,6 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH
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-20230809072818-b2867db6f98e h1:7USP/1To/io70sHjfwVu2o1ZeYh6ihbS8CDotKNNvSA=
github.com/codeready-toolchain/api v0.0.0-20230809072818-b2867db6f98e/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=
Expand Down Expand Up @@ -438,6 +436,8 @@ 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-20230814144046-ecb17d118474 h1:jFBMoQY/wQ1quvCwe7ReIi6qJ8a9TZ6BikVkHQ6j0UA=
github.com/mfrancisc/api v0.0.0-20230814144046-ecb17d118474/go.mod h1:nn3W6eKb9PFIVwSwZW7wDeLACMBOwAV+4kddGuN+ARM=
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
70 changes: 68 additions & 2 deletions test/spacebinding/spacebinding.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ package spacebinding

import (
"fmt"
"time"

"github.com/codeready-toolchain/api/api/v1alpha1"
toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/codeready-toolchain/toolchain-common/pkg/test"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func NewSpaceBinding(mur, space, spaceRole, creator string) *v1alpha1.SpaceBinding {
return &v1alpha1.SpaceBinding{
type Option func(spaceRequest *toolchainv1alpha1.SpaceBinding)

func NewSpaceBinding(mur, space, spaceRole, creator string, options ...Option) *v1alpha1.SpaceBinding {
sb := &v1alpha1.SpaceBinding{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-%s", mur, space),
Namespace: test.HostOperatorNs,
Expand All @@ -25,4 +30,65 @@ func NewSpaceBinding(mur, space, spaceRole, creator string) *v1alpha1.SpaceBindi
SpaceRole: spaceRole,
},
}

for _, apply := range options {
apply(sb)
}
return sb
}

func WithSpaceBindingRequest(sbr *toolchainv1alpha1.SpaceBindingRequest) Option {
return func(spaceBinding *toolchainv1alpha1.SpaceBinding) {
spaceBinding.Labels[toolchainv1alpha1.SpaceBindingRequestLabelKey] = sbr.Name
spaceBinding.Labels[toolchainv1alpha1.SpaceBindingRequestNamespaceLabelKey] = sbr.Namespace
}
}

func WithDeletionTimestamp() Option {
return func(spaceBinding *toolchainv1alpha1.SpaceBinding) {
now := metav1.NewTime(time.Now())
spaceBinding.DeletionTimestamp = &now
}
}

func Provisioning() toolchainv1alpha1.Condition {
return toolchainv1alpha1.Condition{
Type: toolchainv1alpha1.ConditionReady,
Status: corev1.ConditionFalse,
Reason: toolchainv1alpha1.SpaceBindingProvisioningReason,
}
}

func Ready() toolchainv1alpha1.Condition {
return toolchainv1alpha1.Condition{
Type: toolchainv1alpha1.ConditionReady,
Status: corev1.ConditionTrue,
Reason: toolchainv1alpha1.SpaceBindingProvisionedReason,
}
}

func Terminating() toolchainv1alpha1.Condition {
return toolchainv1alpha1.Condition{
Type: toolchainv1alpha1.ConditionReady,
Status: corev1.ConditionFalse,
Reason: toolchainv1alpha1.SpaceBindingTerminatingReason,
}
}

func TerminatingFailed(msg string) toolchainv1alpha1.Condition {
return toolchainv1alpha1.Condition{
Type: toolchainv1alpha1.ConditionReady,
Status: corev1.ConditionFalse,
Reason: toolchainv1alpha1.SpaceBindingTerminatingFailedReason,
Message: msg,
}
}

func ProvisioningFailed(msg string) toolchainv1alpha1.Condition {
return toolchainv1alpha1.Condition{
Type: toolchainv1alpha1.ConditionReady,
Status: corev1.ConditionFalse,
Reason: toolchainv1alpha1.SpaceBindingProvisioningFailedReason,
Message: msg,
}
}
15 changes: 15 additions & 0 deletions test/spacebindingrequest/spacebindingrequest.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package spacebindingrequest

import (
"time"

toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/gofrs/uuid"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -34,3 +36,16 @@ func WithSpaceRole(spaceRole string) Option {
spaceBindingRequest.Spec.SpaceRole = spaceRole
}
}

func WithDeletionTimestamp() Option {
return func(spaceBindingRequest *toolchainv1alpha1.SpaceBindingRequest) {
now := metav1.NewTime(time.Now())
spaceBindingRequest.DeletionTimestamp = &now
}
}

func WithFinalizer() Option {
return func(spaceBindingRequest *toolchainv1alpha1.SpaceBindingRequest) {
spaceBindingRequest.Finalizers = append(spaceBindingRequest.Finalizers, toolchainv1alpha1.FinalizerName)
Copy link

@drpaneas drpaneas Aug 14, 2023

Choose a reason for hiding this comment

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

what if the spaceBindingRequest object is not nil before applying the finalizer? Would that be a problem? If so:

if spaceBindingRequest != nil {
	spaceBindingRequest.Finalizers = append(spaceBindingRequest.Finalizers, toolchainv1alpha1.FinalizerName)
} else {
	// Handle the case where the input spaceBindingRequest is nil, possibly with logging or an error return.
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unless I'm missing something, it cannot be nil, since those Option functions are being passed to the NewSpaceBindingRequest function, which takes care of creating the SpaceBindingRequest object: https://github.com/codeready-toolchain/host-operator/blob/master/test/spacebindingrequest/spacebindingrequest.go#L13-L19

Of course if somehow misused you could break them, but those are "only" used for unit testing, and we might want to modify all our With... helper in this case (also the ones in toolchain-common), since they all follow the same style, eg: https://github.com/codeready-toolchain/toolchain-common/blob/master/pkg/test/usersignup/usersignup.go

Choose a reason for hiding this comment

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

same mistake from my part. I didn't see this is for testing purposes. Please ignore.

}
}
20 changes: 20 additions & 0 deletions test/spacerequest/spacerequest.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package spacerequest

import (
"fmt"
"time"

toolchainv1alpha1 "github.com/codeready-toolchain/api/api/v1alpha1"
"github.com/gofrs/uuid"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
Expand Down Expand Up @@ -61,3 +63,21 @@ func WithStatusNamespaceAccess(namespaceAccess toolchainv1alpha1.NamespaceAccess
spaceRequest.Status.NamespaceAccess = []toolchainv1alpha1.NamespaceAccess{namespaceAccess}
}
}

func NewNamespace(spacename string) *corev1.Namespace {
labels := map[string]string{
toolchainv1alpha1.TypeLabelKey: "tenant",
toolchainv1alpha1.ProviderLabelKey: toolchainv1alpha1.ProviderLabelValue,
}
if spacename != "nospace" {

Choose a reason for hiding this comment

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

What happens if == nospace ? Is it still a valid namespace? Do think this ever returns an invalid namespace? I mean should we return (*corev1.Namespace, error) or not necessary?

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 function is used only for mocking namespace while unit testing. When is being called with "nospace" is done with the purpose of configuring (misconfiguring) the namespace, and verify the corner case in which a required label is missing, for example in this unit test: https://github.com/codeready-toolchain/host-operator/pull/848/files#diff-f15c73194e22112fd24e7e852200c395d2cddb8cb016fe76710f4c38bab782e1R232-R252

In short, I want the controller to throw this error here: https://github.com/codeready-toolchain/host-operator/pull/848/files#diff-e48d49741db80226348ae5ffd08100dd10afa2e83e5d18b2a94776dfdfb616f3R333-R335

Hope this makes sense.

Choose a reason for hiding this comment

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

yes it makes sense. Sorry I missed the test part ... sigh...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No worries! My pleasure getting more feedback/reviews and discussing alternative solutions! Thanks for checking!

labels[toolchainv1alpha1.SpaceLabelKey] = spacename
}
ns := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: fmt.Sprintf("%s-tenant", spacename),
Labels: labels,
},
Status: corev1.NamespaceStatus{Phase: corev1.NamespaceActive},
}
return ns
}
Loading