Skip to content

Commit

Permalink
Implement legacy namespace quota mode (#119)
Browse files Browse the repository at this point in the history
  • Loading branch information
bastjan authored Aug 28, 2024
1 parent a00c8e5 commit 90c3196
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 8 deletions.
3 changes: 3 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ type Config struct {
// AllowedLabels is a list of labels that are allowed on namespaces.
// Supports '*' and '?' wildcards.
AllowedLabels []string

// LegacyNamespaceQuota is the default quota for namespaces if no ZoneUsageProfile is selected.
LegacyNamespaceQuota int
}

func ConfigFromFile(path string) (c Config, warn []string, err error) {
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,8 @@ func main() {

SelectedProfile: selectedUsageProfile,
QuotaOverrideNamespace: conf.QuotaOverrideNamespace,

LegacyNamespaceQuota: conf.LegacyNamespaceQuota,
},
})

Expand Down
32 changes: 24 additions & 8 deletions webhooks/namespace_quota_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,23 @@ type NamespaceQuotaValidator struct {
UserDefaultOrganizationAnnotation string

// SelectedProfile is the name of the ZoneUsageProfile to use for the quota
// An empty string means that the legacy namespace quota is used if set.
SelectedProfile string

// QuotaOverrideNamespace is the namespace in which the quota overrides are stored
QuotaOverrideNamespace string

// LegacyNamespaceQuota is the namespace quota for legacy mode.
// It is used if no ZoneUsageProfile is selected.
LegacyNamespaceQuota int
}

// Handle handles the admission requests
func (v *NamespaceQuotaValidator) Handle(ctx context.Context, req admission.Request) admission.Response {
ctx = log.IntoContext(ctx, log.FromContext(ctx).
WithName("webhook.validate-namespace-quota.appuio.io").
WithValues("id", req.UID, "user", req.UserInfo.Username).
WithValues("legacyMode", v.legacyMode()).
WithValues("namespace", req.Namespace, "name", req.Name,
"group", req.Kind.Group, "version", req.Kind.Version, "kind", req.Kind.Kind))

Expand Down Expand Up @@ -110,16 +116,21 @@ func (v *NamespaceQuotaValidator) handle(ctx context.Context, req admission.Requ
return admission.Allowed("skipped quota validation")
}

if v.SelectedProfile == "" {
return admission.Denied("No ZoneUsageProfile selected")
}
var nsCountLimit int
if v.legacyMode() {
nsCountLimit = v.LegacyNamespaceQuota
} else {
if v.SelectedProfile == "" {
return admission.Denied("No ZoneUsageProfile selected")
}

var profile cloudagentv1.ZoneUsageProfile
if err := v.Client.Get(ctx, types.NamespacedName{Name: v.SelectedProfile}, &profile); err != nil {
l.Error(err, "error while fetching zone usage profile")
return admission.Errored(http.StatusInternalServerError, err)
var profile cloudagentv1.ZoneUsageProfile
if err := v.Client.Get(ctx, types.NamespacedName{Name: v.SelectedProfile}, &profile); err != nil {
l.Error(err, "error while fetching zone usage profile")
return admission.Errored(http.StatusInternalServerError, err)
}
nsCountLimit = profile.Spec.UpstreamSpec.NamespaceCount
}
nsCountLimit := profile.Spec.UpstreamSpec.NamespaceCount

var overrideCM corev1.ConfigMap
if err := v.Client.Get(ctx, types.NamespacedName{Name: fmt.Sprintf("override-%s", organizationName), Namespace: v.QuotaOverrideNamespace}, &overrideCM); err == nil {
Expand Down Expand Up @@ -152,6 +163,11 @@ func (v *NamespaceQuotaValidator) handle(ctx context.Context, req admission.Requ
return admission.Allowed("allowed")
}

// legacyMode returns true if the legacy namespace quota is set and no ZoneUsageProfile is selected.
func (v *NamespaceQuotaValidator) legacyMode() bool {
return v.SelectedProfile == "" && v.LegacyNamespaceQuota > 0
}

// logAdmissionResponse logs the admission response to the logger derived from the given context and returns it unchanged.
func logAdmissionResponse(ctx context.Context, res admission.Response) admission.Response {
l := log.FromContext(ctx)
Expand Down
68 changes: 68 additions & 0 deletions webhooks/namespace_quota_validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ func TestNamespaceQuotaValidator_Handle(t *testing.T) {
allowed bool
skipQuotaValidation bool
matchMessage string
disableProfile bool
legacyQuota int
}{
"Allow Namespace": {
initObjects: []client.Object{
Expand Down Expand Up @@ -261,6 +263,67 @@ func TestNamespaceQuotaValidator_Handle(t *testing.T) {
skipQuotaValidation: true,
allowed: false,
},

"LegacyMode: Allow Namespace": {
initObjects: []client.Object{
newNamespace("a", map[string]string{orgLabel: "other"}, nil), newNamespace("b", map[string]string{orgLabel: "other"}, nil),
newNamespace("an", nil, nil), newNamespace("bn", nil, nil),
},
object: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Labels: map[string]string{
orgLabel: "testorg",
},
},
},
allowed: true,
legacyQuota: 1,
disableProfile: true,
},
"LegacyMode: Deny Namespace TooMany": {
initObjects: []client.Object{
newNamespace("a", map[string]string{orgLabel: "testorg"}, nil),
},
object: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Labels: map[string]string{
orgLabel: "testorg",
},
},
},
allowed: false,
legacyQuota: 1,
disableProfile: true,
},
"LegacyMode: Allow Namespace Override": {
initObjects: []client.Object{
newNamespace("a", map[string]string{orgLabel: "testorg"}, nil),
newNamespace("b", map[string]string{orgLabel: "testorg"}, nil),
newNamespace("c", map[string]string{orgLabel: "testorg"}, nil),
&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "override-testorg",
Namespace: "test",
},
Data: map[string]string{
"namespaceQuota": "4",
},
},
},
object: &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Labels: map[string]string{
orgLabel: "testorg",
},
},
},
allowed: true,
legacyQuota: 1,
disableProfile: true,
},
}

for name, test := range tests {
Expand All @@ -278,6 +341,11 @@ func TestNamespaceQuotaValidator_Handle(t *testing.T) {

SelectedProfile: "test",
QuotaOverrideNamespace: "test",
LegacyNamespaceQuota: test.legacyQuota,
}

if test.disableProfile {
subject.SelectedProfile = ""
}

require.NoError(t, c.Create(ctx, &cloudagentv1.ZoneUsageProfile{
Expand Down

0 comments on commit 90c3196

Please sign in to comment.