Skip to content

Commit e7056bb

Browse files
Add tier for -claw namespace (#1282)
* Add tier for -claw namespace * update * fix
1 parent 8a09e02 commit e7056bb

5 files changed

Lines changed: 268 additions & 3 deletions

File tree

deploy/base1ns-gotemplate/ns_dev.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,14 @@ spec:
121121
limits.ephemeral-storage: '{{.EPHEMERAL_STORAGE_LIMIT}}'
122122
requests.ephemeral-storage: '{{.EPHEMERAL_STORAGE_REQUEST}}'
123123
requests.storage: '{{.STORAGE_REQUEST}}'
124+
- apiVersion: v1
125+
kind: ResourceQuota
126+
metadata:
127+
name: compute-spacerequests
128+
namespace: '{{.SPACE_NAME}}-dev'
129+
spec:
130+
hard:
131+
count/spacerequests.toolchain.dev.openshift.com: "1"
124132
- apiVersion: v1
125133
kind: LimitRange
126134
metadata:

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: 185 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
}
@@ -113,6 +116,7 @@ func (a *baseTierChecks) GetNamespaceObjectChecks(nsType string) []namespaceObje
113116
checks := []namespaceObjectsCheck{
114117
numberOfLimitRanges(1),
115118
limitRange("1", "1Gi", "10m", "64Mi"),
119+
resourceQuotaSpaceRequests(),
116120
execPodsRole(),
117121
crtadminPodsRoleBinding(),
118122
crtadminViewRoleBinding(),
@@ -197,6 +201,7 @@ func (a *base1nsTierChecks) GetNamespaceObjectChecks(_ string) []namespaceObject
197201
corev1.ResourceName("limits.nvidia.com/gpu"): "0",
198202
}),
199203
resourceQuotaStorage("15Gi", "80Gi", "15Gi", "10"),
204+
resourceQuotaSpaceRequests(),
200205
limitRange("1", "1000Mi", "10m", "64Mi"),
201206
numberOfLimitRanges(1),
202207
execPodsRole(),
@@ -525,6 +530,171 @@ func (a *appstudioEnvTierChecks) GetClusterObjectChecks() []clusterObjectsCheck
525530
idlers(0, "env"))
526531
}
527532

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

901+
func resourceQuotaSpaceRequests() namespaceObjectsCheck {
902+
return func(t *testing.T, ns *corev1.Namespace, memberAwait *wait.MemberAwaitility, _ string) {
903+
var err error
904+
spec := corev1.ResourceQuotaSpec{
905+
Hard: make(map[corev1.ResourceName]resource.Quantity),
906+
}
907+
spec.Hard["count/spacerequests.toolchain.dev.openshift.com"], err = resource.ParseQuantity("1")
908+
require.NoError(t, err)
909+
910+
criteria := resourceQuotaMatches(ns.Name, "compute-spacerequests", spec)
911+
_, err = memberAwait.WaitForResourceQuota(t, ns.Name, "compute-spacerequests", criteria)
912+
require.NoError(t, err)
913+
}
914+
}
915+
731916
func resourceQuotaToolchainCrds(spaceRequestLimit string) namespaceObjectsCheck {
732917
return func(t *testing.T, ns *corev1.Namespace, memberAwait *wait.MemberAwaitility, _ string) {
733918
var err error

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)