Skip to content

Commit 8287436

Browse files
committed
Add tier for -claw namespace
1 parent b57dc7a commit 8287436

4 files changed

Lines changed: 242 additions & 3 deletions

File tree

test/e2e/parallel/nstemplatetier_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,9 +301,9 @@ func TestTierTemplates(t *testing.T) {
301301
allTiers := &toolchainv1alpha1.TierTemplateList{}
302302
err = hostAwait.Client.List(context.TODO(), allTiers, client.InNamespace(hostAwait.Namespace), notCreatedByE2e)
303303
require.NoError(t, err)
304-
// We have 19 tier templates (base: 3, base1ns: 2, base1nsnoidling: 2, base1ns6didler: 3, appstudio: 3, appstudiolarge: 3, appstudio-env: 3)
304+
// We have 22 tier templates (base: 3, base1ns: 2, base1nsnoidling: 2, base1ns6didler: 3, appstudio: 3, appstudiolarge: 3, appstudio-env: 3, claw: 3)
305305
// But we cannot verify the exact number of tiers, because during the operator update it may happen that more TierTemplates are created
306-
assert.GreaterOrEqual(t, len(allTiers.Items), 19)
306+
assert.GreaterOrEqual(t, len(allTiers.Items), 22)
307307
}
308308

309309
func TestFeatureToggles(t *testing.T) {

test/e2e/parallel/spacerequest_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import (
1111
. "github.com/codeready-toolchain/toolchain-e2e/testsupport/space"
1212
"github.com/codeready-toolchain/toolchain-e2e/testsupport/spaceprovisionerconfig"
1313
"github.com/codeready-toolchain/toolchain-e2e/testsupport/wait"
14+
"github.com/stretchr/testify/assert"
1415
"github.com/stretchr/testify/require"
16+
"k8s.io/apimachinery/pkg/api/errors"
1517
"k8s.io/apimachinery/pkg/types"
1618
"sigs.k8s.io/controller-runtime/pkg/client"
1719
)
@@ -340,3 +342,73 @@ func TestUpdateSpaceRequest(t *testing.T) {
340342
require.NoError(t, err)
341343
})
342344
}
345+
346+
func TestCreateClawSpaceRequest(t *testing.T) {
347+
t.Parallel()
348+
awaitilities := WaitForDeployments(t)
349+
memberAwait := awaitilities.Member1()
350+
351+
// Create a user with base1ns tier (which includes SpaceRequest RBAC and quota)
352+
user := NewSignupRequest(awaitilities).
353+
ManuallyApprove().
354+
RequireConditions(wait.ConditionSet(wait.Default(), wait.ApprovedByAdmin())...).
355+
TargetCluster(memberAwait).
356+
SpaceTier("base1ns").
357+
EnsureMUR().
358+
Execute(t)
359+
parentSpace := user.Space
360+
361+
t.Run("provision claw sub-space via SpaceRequest", func(t *testing.T) {
362+
// Create a SpaceRequest for the claw tier in the user's -dev namespace
363+
spaceRequest := NewSpaceRequest(t,
364+
WithSpecTierName("claw"),
365+
WithNamespace(GetDefaultNamespace(parentSpace.Status.ProvisionedNamespaces)),
366+
)
367+
err := memberAwait.CreateWithCleanup(t, spaceRequest)
368+
require.NoError(t, err)
369+
370+
// Wait for the sub-space to be created and provisioned
371+
subSpace, err := awaitilities.Host().WaitForSubSpace(t, spaceRequest.Name, spaceRequest.Namespace, parentSpace.GetName(),
372+
wait.UntilSpaceHasTier("claw"),
373+
wait.UntilSpaceHasAnyProvisionedNamespaces(),
374+
)
375+
require.NoError(t, err)
376+
377+
// Verify all resources provisioned for the claw sub-space
378+
// (namespace objects, cluster objects, and space roles)
379+
subSpace, _ = VerifyResourcesProvisionedForSpace(t, awaitilities, subSpace.Name, wait.UntilSpaceHasAnyTargetClusterSet())
380+
381+
// Verify SpaceRequest status is provisioned
382+
spaceRequest, err = memberAwait.WaitForSpaceRequest(t, types.NamespacedName{Namespace: spaceRequest.GetNamespace(), Name: spaceRequest.GetName()},
383+
wait.UntilSpaceRequestHasConditions(wait.Provisioned()),
384+
wait.UntilSpaceRequestHasNamespaceAccess(subSpace),
385+
wait.UntilSpaceRequestHasNamespaceAccessWithoutSecretRef(),
386+
)
387+
require.NoError(t, err)
388+
389+
t.Run("second SpaceRequest is rejected by quota", func(t *testing.T) {
390+
// The base1ns ns_dev.yaml limits count/spacerequests.toolchain.dev.openshift.com to 1.
391+
// Creating a second SpaceRequest should be rejected by the ResourceQuota.
392+
secondSR := NewSpaceRequest(t,
393+
WithSpecTierName("claw"),
394+
WithNamespace(GetDefaultNamespace(parentSpace.Status.ProvisionedNamespaces)),
395+
)
396+
err := memberAwait.Client.Create(context.TODO(), secondSR)
397+
require.Error(t, err)
398+
assert.True(t, errors.IsForbidden(err), "expected Forbidden error due to ResourceQuota, got: %v", err)
399+
})
400+
401+
t.Run("delete SpaceRequest cleans up sub-space", func(t *testing.T) {
402+
// Delete the SpaceRequest and verify the claw sub-space is removed
403+
err := memberAwait.Client.Delete(context.TODO(), spaceRequest)
404+
require.NoError(t, err)
405+
406+
err = memberAwait.WaitUntilNamespaceDeleted(t, subSpace.Name, "claw")
407+
require.NoError(t, err)
408+
err = memberAwait.WaitUntilNSTemplateSetDeleted(t, subSpace.Name)
409+
require.NoError(t, err)
410+
err = awaitilities.Host().WaitUntilSpaceAndSpaceBindingsDeleted(t, subSpace.Name)
411+
require.NoError(t, err)
412+
})
413+
})
414+
}

testsupport/tiers/checks.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const (
3232
base1ns = "base1ns"
3333
base1ns6didler = "base1ns6didler"
3434
base1nsnoidling = "base1nsnoidling"
35+
claw = "claw"
3536

3637
// common CPU limits
3738
baseCPULimit = "40000m"
@@ -62,6 +63,8 @@ func NewChecksForTier(tier *toolchainv1alpha1.NSTemplateTier) (TierChecks, error
6263
return &appstudiolargeTierChecks{appstudioTierChecks{tierName: appstudiolarge}}, nil
6364
case appstudioEnv:
6465
return &appstudioEnvTierChecks{tierName: appstudioEnv}, nil
66+
case claw:
67+
return &clawTierChecks{tierName: claw}, nil
6568
default:
6669
return nil, fmt.Errorf("no assertion implementation found for %s", tier.Name)
6770
}
@@ -525,6 +528,170 @@ func (a *appstudioEnvTierChecks) GetClusterObjectChecks() []clusterObjectsCheck
525528
idlers(0, "env"))
526529
}
527530

531+
type clawTierChecks struct {
532+
tierName string
533+
}
534+
535+
func (a *clawTierChecks) GetNamespaceObjectChecks(_ string) []namespaceObjectsCheck {
536+
checks := []namespaceObjectsCheck{
537+
resourceQuotaComputeDeployNoScope("8", "10Gi", "1", "3Gi"),
538+
resourceQuotaStorage("5Gi", "15Gi", "5Gi", "1"),
539+
limitRange("500m", "512Mi", "10m", "64Mi"),
540+
numberOfLimitRanges(1),
541+
execPodsRole(),
542+
crtadminPodsRoleBinding(),
543+
crtadminViewRoleBinding(),
544+
networkPolicySameNamespace(),
545+
networkPolicyAllowFromIngress(),
546+
networkPolicyAllowFromMonitoring(),
547+
networkPolicyAllowFromOlmNamespaces(),
548+
networkPolicyAllowFromConsoleNamespaces(),
549+
numberOfNetworkPolicies(5),
550+
}
551+
return checks
552+
}
553+
554+
func (a *clawTierChecks) GetSpaceRoleChecks(spaceRoles map[string][]string) ([]spaceRoleObjectsCheck, error) {
555+
checks := []spaceRoleObjectsCheck{}
556+
roles := 0
557+
rolebindings := 0
558+
for role, usernames := range spaceRoles {
559+
switch role {
560+
case "admin":
561+
checks = append(checks, clawUserRole())
562+
roles++
563+
for _, userName := range usernames {
564+
checks = append(checks, clawUserRoleBinding(userName))
565+
rolebindings++
566+
}
567+
default:
568+
return nil, fmt.Errorf("unexpected template name: '%s'", role)
569+
}
570+
}
571+
checks = append(checks,
572+
numberOfToolchainRoles(roles+1), // +1 for `exec-pods`
573+
numberOfToolchainRoleBindings(rolebindings+2), // +2 for `crtadmin-pods` and `crtadmin-view`
574+
)
575+
return checks, nil
576+
}
577+
578+
func (a *clawTierChecks) GetExpectedTemplateRefs(t *testing.T, hostAwait *wait.HostAwaitility) TemplateRefs {
579+
templateRefs := GetTemplateRefs(t, hostAwait, a.tierName)
580+
verifyNsTypes(t, a.tierName, templateRefs, "claw")
581+
return templateRefs
582+
}
583+
584+
func (a *clawTierChecks) GetClusterObjectChecks() []clusterObjectsCheck {
585+
return clusterObjectsChecks(
586+
clusterResourceQuotaClaw(),
587+
numberOfClusterResourceQuotas(1),
588+
idlers(43200, "claw"))
589+
}
590+
591+
func clusterResourceQuotaClaw() clusterObjectsCheckCreator {
592+
return func() clusterObjectsCheck {
593+
return func(t *testing.T, memberAwait *wait.MemberAwaitility, userName, tierLabel string) {
594+
var err error
595+
hard := make(map[corev1.ResourceName]resource.Quantity)
596+
hard[count("deployments.apps")], err = resource.ParseQuantity("5")
597+
require.NoError(t, err)
598+
hard[count(corev1.ResourcePods)], err = resource.ParseQuantity("10")
599+
require.NoError(t, err)
600+
hard[count("routes.route.openshift.io")], err = resource.ParseQuantity("3")
601+
require.NoError(t, err)
602+
hard[count(corev1.ResourceServices)], err = resource.ParseQuantity("5")
603+
require.NoError(t, err)
604+
hard[count(corev1.ResourceSecrets)], err = resource.ParseQuantity("50")
605+
require.NoError(t, err)
606+
hard[count(corev1.ResourceConfigMaps)], err = resource.ParseQuantity("10")
607+
require.NoError(t, err)
608+
609+
_, err = memberAwait.WaitForClusterResourceQuota(t, fmt.Sprintf("for-%s-claw", userName),
610+
crqToolchainLabelsWaitCriterion(userName),
611+
clusterResourceQuotaMatches(userName, tierLabel, hard),
612+
)
613+
require.NoError(t, err)
614+
}
615+
}
616+
}
617+
618+
func resourceQuotaComputeDeployNoScope(cpuLimit, memoryLimit, cpuRequest, memoryRequest string) namespaceObjectsCheck {
619+
return func(t *testing.T, ns *corev1.Namespace, memberAwait *wait.MemberAwaitility, _ string) {
620+
var err error
621+
spec := corev1.ResourceQuotaSpec{
622+
Hard: make(map[corev1.ResourceName]resource.Quantity),
623+
}
624+
spec.Hard[corev1.ResourceLimitsCPU], err = resource.ParseQuantity(cpuLimit)
625+
require.NoError(t, err)
626+
spec.Hard[corev1.ResourceLimitsMemory], err = resource.ParseQuantity(memoryLimit)
627+
require.NoError(t, err)
628+
spec.Hard[corev1.ResourceRequestsCPU], err = resource.ParseQuantity(cpuRequest)
629+
require.NoError(t, err)
630+
spec.Hard[corev1.ResourceRequestsMemory], err = resource.ParseQuantity(memoryRequest)
631+
require.NoError(t, err)
632+
633+
criteria := resourceQuotaMatches(ns.Name, "compute-deploy", spec)
634+
_, err = memberAwait.WaitForResourceQuota(t, ns.Name, "compute-deploy", criteria)
635+
require.NoError(t, err)
636+
}
637+
}
638+
639+
func clawUserRole() spaceRoleObjectsCheck {
640+
return func(t *testing.T, ns *corev1.Namespace, memberAwait *wait.MemberAwaitility, owner string) {
641+
role, err := memberAwait.WaitForRole(t, ns, "claw-user", toolchainLabelsWaitCriterion(owner)...)
642+
require.NoError(t, err)
643+
expected := &rbacv1.Role{
644+
Rules: []rbacv1.PolicyRule{
645+
{
646+
APIGroups: []string{"claw.sandbox.redhat.com"},
647+
Resources: []string{"claws"},
648+
Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"},
649+
},
650+
{
651+
APIGroups: []string{""},
652+
Resources: []string{"pods"},
653+
Verbs: []string{"get", "list", "watch"},
654+
},
655+
{
656+
APIGroups: []string{""},
657+
Resources: []string{"pods/log"},
658+
Verbs: []string{"get", "list"},
659+
},
660+
{
661+
APIGroups: []string{""},
662+
Resources: []string{"events"},
663+
Verbs: []string{"get", "list", "watch"},
664+
},
665+
{
666+
APIGroups: []string{"route.openshift.io"},
667+
Resources: []string{"routes"},
668+
Verbs: []string{"get", "list", "watch"},
669+
},
670+
{
671+
APIGroups: []string{""},
672+
Resources: []string{"secrets"},
673+
Verbs: []string{"create", "update", "patch", "delete"},
674+
},
675+
},
676+
}
677+
assert.Len(t, role.Rules, len(expected.Rules))
678+
assert.Equal(t, expected.Rules, role.Rules)
679+
}
680+
}
681+
682+
func clawUserRoleBinding(userName string) spaceRoleObjectsCheck {
683+
return func(t *testing.T, ns *corev1.Namespace, memberAwait *wait.MemberAwaitility, owner string) {
684+
rb, err := memberAwait.WaitForRoleBinding(t, ns, userName+"-claw-user", toolchainLabelsWaitCriterion(owner)...)
685+
require.NoError(t, err)
686+
assert.Len(t, rb.Subjects, 1)
687+
assert.Equal(t, "User", rb.Subjects[0].Kind)
688+
assert.Equal(t, userName, rb.Subjects[0].Name)
689+
assert.Equal(t, "claw-user", rb.RoleRef.Name)
690+
assert.Equal(t, "Role", rb.RoleRef.Kind)
691+
assert.Equal(t, "rbac.authorization.k8s.io", rb.RoleRef.APIGroup)
692+
}
693+
}
694+
528695
// verifyNsTypes checks that there's a namespace.TemplateRef that begins with `<tier>-<type>` for each given templateRef (and no more, no less)
529696
func verifyNsTypes(t *testing.T, tier string, templateRefs TemplateRefs, expectedNSTypes ...string) {
530697
require.Len(t, templateRefs.Namespaces, len(expectedNSTypes))

testsupport/wait/host.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ import (
5050
)
5151

5252
var (
53-
BundledNSTemplateTiers []string = []string{"base1ns", "base1nsnoidling", "base1ns6didler", "base"}
53+
BundledNSTemplateTiers []string = []string{"base1ns", "base1nsnoidling", "base1ns6didler", "base", "claw"}
5454
CustomNSTemplateTiers []string = []string{"appstudio", "appstudiolarge", "appstudio-env"}
5555
AllE2eNSTemplateTiers []string = append(BundledNSTemplateTiers, CustomNSTemplateTiers...)
5656
)

0 commit comments

Comments
 (0)