mirror of
https://github.com/lukaszraczylo/kubemirror.git
synced 2026-07-05 10:44:52 +00:00
initial commit
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
// Package filter provides namespace filtering and pattern matching functionality.
|
||||
package filter
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/lukaszraczylo/kubemirror/pkg/constants"
|
||||
)
|
||||
|
||||
// NamespaceFilter handles namespace filtering logic including patterns and exclusions.
|
||||
type NamespaceFilter struct {
|
||||
excludedNamespaces map[string]bool
|
||||
includedPatterns []string
|
||||
}
|
||||
|
||||
// NewNamespaceFilter creates a new NamespaceFilter with the given exclusions and inclusions.
|
||||
func NewNamespaceFilter(excluded, included []string) *NamespaceFilter {
|
||||
excludedMap := make(map[string]bool)
|
||||
for _, ns := range excluded {
|
||||
excludedMap[ns] = true
|
||||
}
|
||||
|
||||
return &NamespaceFilter{
|
||||
excludedNamespaces: excludedMap,
|
||||
includedPatterns: included,
|
||||
}
|
||||
}
|
||||
|
||||
// IsAllowed checks if a namespace is allowed based on filters.
|
||||
// Returns true if the namespace passes all filters.
|
||||
func (nf *NamespaceFilter) IsAllowed(namespace string) bool {
|
||||
// Check if explicitly excluded
|
||||
if nf.excludedNamespaces[namespace] {
|
||||
return false
|
||||
}
|
||||
|
||||
// If no include patterns specified, allow all (except excluded)
|
||||
if len(nf.includedPatterns) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if matches any include pattern
|
||||
for _, pattern := range nf.includedPatterns {
|
||||
if matchesPattern(namespace, pattern) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchesPattern checks if a namespace name matches the given pattern.
|
||||
// Supports glob-style patterns: "app-*", "*-prod", "stage-*-db"
|
||||
func matchesPattern(namespace, pattern string) bool {
|
||||
// Direct match
|
||||
if namespace == pattern {
|
||||
return true
|
||||
}
|
||||
|
||||
// Use filepath.Match for glob-style matching
|
||||
// filepath.Match supports * (any sequence) and ? (single char)
|
||||
matched, err := filepath.Match(pattern, namespace)
|
||||
if err != nil {
|
||||
// Invalid pattern, no match
|
||||
return false
|
||||
}
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
// ParseTargetNamespaces parses the target-namespaces annotation value.
|
||||
// Returns a list of namespace patterns or special keywords.
|
||||
// Input: "ns1,ns2,app-*" or "all" or "all-labeled"
|
||||
func ParseTargetNamespaces(value string) []string {
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Trim whitespace
|
||||
value = strings.TrimSpace(value)
|
||||
|
||||
// Handle special keywords
|
||||
if value == constants.TargetNamespacesAll || value == constants.TargetNamespacesAllLabeled {
|
||||
return []string{value}
|
||||
}
|
||||
|
||||
// Split by comma and trim each entry
|
||||
parts := strings.Split(value, ",")
|
||||
result := make([]string, 0, len(parts))
|
||||
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part != "" {
|
||||
result = append(result, part)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// ResolveTargetNamespaces resolves namespace patterns to concrete namespace names.
|
||||
// Handles "all", "all-labeled", and glob patterns.
|
||||
// Parameters:
|
||||
// - patterns: namespace patterns from annotation
|
||||
// - allNamespaces: list of all namespaces in cluster
|
||||
// - allowMirrorsNamespaces: namespaces with allow-mirrors label
|
||||
// - sourceNamespace: exclude this namespace to prevent self-copy
|
||||
// - filter: namespace filter for exclusions
|
||||
//
|
||||
// Returns: list of concrete target namespace names
|
||||
func ResolveTargetNamespaces(
|
||||
patterns []string,
|
||||
allNamespaces []string,
|
||||
allowMirrorsNamespaces []string,
|
||||
sourceNamespace string,
|
||||
filter *NamespaceFilter,
|
||||
) []string {
|
||||
if len(patterns) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Use map to deduplicate
|
||||
targetMap := make(map[string]bool)
|
||||
|
||||
for _, pattern := range patterns {
|
||||
switch pattern {
|
||||
case constants.TargetNamespacesAll:
|
||||
// Mirror to all namespaces (except source and excluded)
|
||||
for _, ns := range allNamespaces {
|
||||
if ns != sourceNamespace && filter.IsAllowed(ns) {
|
||||
targetMap[ns] = true
|
||||
}
|
||||
}
|
||||
|
||||
case constants.TargetNamespacesAllLabeled:
|
||||
// Mirror only to namespaces with allow-mirrors label
|
||||
for _, ns := range allowMirrorsNamespaces {
|
||||
if ns != sourceNamespace && filter.IsAllowed(ns) {
|
||||
targetMap[ns] = true
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
// Check if it's a pattern or direct namespace name
|
||||
if strings.Contains(pattern, "*") || strings.Contains(pattern, "?") {
|
||||
// It's a glob pattern - match against all namespaces
|
||||
for _, ns := range allNamespaces {
|
||||
if matchesPattern(ns, pattern) && ns != sourceNamespace && filter.IsAllowed(ns) {
|
||||
targetMap[ns] = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Direct namespace name
|
||||
if pattern != sourceNamespace && filter.IsAllowed(pattern) {
|
||||
targetMap[pattern] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert map to slice
|
||||
result := make([]string, 0, len(targetMap))
|
||||
for ns := range targetMap {
|
||||
result = append(result, ns)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,587 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/lukaszraczylo/kubemirror/pkg/constants"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNamespaceFilter_IsAllowed(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
excluded []string
|
||||
included []string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "allow when no filters",
|
||||
excluded: []string{},
|
||||
included: []string{},
|
||||
namespace: "app1",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "deny when explicitly excluded",
|
||||
excluded: []string{"kube-system", "kube-public"},
|
||||
included: []string{},
|
||||
namespace: "kube-system",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "allow when not excluded",
|
||||
excluded: []string{"kube-system"},
|
||||
included: []string{},
|
||||
namespace: "app1",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "allow when matches include pattern",
|
||||
excluded: []string{},
|
||||
included: []string{"app-*"},
|
||||
namespace: "app-frontend",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "deny when doesn't match include pattern",
|
||||
excluded: []string{},
|
||||
included: []string{"app-*"},
|
||||
namespace: "backend",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "deny when excluded even if matches include",
|
||||
excluded: []string{"app-bad"},
|
||||
included: []string{"app-*"},
|
||||
namespace: "app-bad",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "allow when matches one of multiple patterns",
|
||||
excluded: []string{},
|
||||
included: []string{"app-*", "prod-*"},
|
||||
namespace: "prod-db",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "allow direct match in include list",
|
||||
excluded: []string{},
|
||||
included: []string{"specific-ns"},
|
||||
namespace: "specific-ns",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
nf := NewNamespaceFilter(tt.excluded, tt.included)
|
||||
got := nf.IsAllowed(tt.namespace)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchesPattern(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
pattern string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "exact match",
|
||||
namespace: "app-frontend",
|
||||
pattern: "app-frontend",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "wildcard at end",
|
||||
namespace: "app-frontend",
|
||||
pattern: "app-*",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "wildcard at start",
|
||||
namespace: "app-frontend",
|
||||
pattern: "*-frontend",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "wildcard in middle",
|
||||
namespace: "app-prod-frontend",
|
||||
pattern: "app-*-frontend",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "multiple wildcards",
|
||||
namespace: "my-app-prod-db",
|
||||
pattern: "*-app-*-db",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "single char wildcard",
|
||||
namespace: "app1",
|
||||
pattern: "app?",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "no match",
|
||||
namespace: "backend",
|
||||
pattern: "app-*",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "empty pattern matches empty string",
|
||||
namespace: "",
|
||||
pattern: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "pattern doesn't match different namespace",
|
||||
namespace: "production-app",
|
||||
pattern: "prod-*",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := matchesPattern(tt.namespace, tt.pattern)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTargetNamespaces(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "empty string",
|
||||
value: "",
|
||||
want: nil,
|
||||
},
|
||||
{
|
||||
name: "single namespace",
|
||||
value: "app1",
|
||||
want: []string{"app1"},
|
||||
},
|
||||
{
|
||||
name: "multiple namespaces",
|
||||
value: "app1,app2,app3",
|
||||
want: []string{"app1", "app2", "app3"},
|
||||
},
|
||||
{
|
||||
name: "with whitespace",
|
||||
value: "app1, app2 , app3",
|
||||
want: []string{"app1", "app2", "app3"},
|
||||
},
|
||||
{
|
||||
name: "special keyword 'all'",
|
||||
value: "all",
|
||||
want: []string{"all"},
|
||||
},
|
||||
{
|
||||
name: "special keyword 'all-labeled'",
|
||||
value: "all-labeled",
|
||||
want: []string{"all-labeled"},
|
||||
},
|
||||
{
|
||||
name: "mixed patterns",
|
||||
value: "app1,app-*,prod-*",
|
||||
want: []string{"app1", "app-*", "prod-*"},
|
||||
},
|
||||
{
|
||||
name: "trailing comma",
|
||||
value: "app1,app2,",
|
||||
want: []string{"app1", "app2"},
|
||||
},
|
||||
{
|
||||
name: "empty entries ignored",
|
||||
value: "app1,,app2",
|
||||
want: []string{"app1", "app2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ParseTargetNamespaces(tt.value)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveTargetNamespaces(t *testing.T) {
|
||||
allNamespaces := []string{"app1", "app2", "app-frontend", "app-backend", "prod-db", "prod-api", "kube-system", "default"}
|
||||
allowMirrorsNamespaces := []string{"app1", "app-frontend", "prod-db"}
|
||||
excludeFilter := NewNamespaceFilter([]string{"kube-system"}, []string{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
patterns []string
|
||||
allNamespaces []string
|
||||
allowMirrorsNamespaces []string
|
||||
sourceNamespace string
|
||||
filter *NamespaceFilter
|
||||
wantContains []string
|
||||
wantNotContains []string
|
||||
}{
|
||||
{
|
||||
name: "empty patterns",
|
||||
patterns: []string{},
|
||||
allNamespaces: allNamespaces,
|
||||
allowMirrorsNamespaces: allowMirrorsNamespaces,
|
||||
sourceNamespace: "default",
|
||||
filter: excludeFilter,
|
||||
wantContains: []string{},
|
||||
wantNotContains: allNamespaces,
|
||||
},
|
||||
{
|
||||
name: "all keyword",
|
||||
patterns: []string{constants.TargetNamespacesAll},
|
||||
allNamespaces: allNamespaces,
|
||||
allowMirrorsNamespaces: allowMirrorsNamespaces,
|
||||
sourceNamespace: "default",
|
||||
filter: excludeFilter,
|
||||
wantContains: []string{"app1", "app2", "app-frontend", "prod-db"},
|
||||
wantNotContains: []string{"default", "kube-system"}, // excluded: source and kube-system
|
||||
},
|
||||
{
|
||||
name: "all-labeled keyword",
|
||||
patterns: []string{constants.TargetNamespacesAllLabeled},
|
||||
allNamespaces: allNamespaces,
|
||||
allowMirrorsNamespaces: allowMirrorsNamespaces,
|
||||
sourceNamespace: "default",
|
||||
filter: excludeFilter,
|
||||
wantContains: []string{"app1", "app-frontend", "prod-db"},
|
||||
wantNotContains: []string{"app2", "app-backend", "default"},
|
||||
},
|
||||
{
|
||||
name: "glob pattern app-*",
|
||||
patterns: []string{"app-*"},
|
||||
allNamespaces: allNamespaces,
|
||||
allowMirrorsNamespaces: allowMirrorsNamespaces,
|
||||
sourceNamespace: "default",
|
||||
filter: excludeFilter,
|
||||
wantContains: []string{"app-frontend", "app-backend"},
|
||||
wantNotContains: []string{"app1", "app2", "prod-db"},
|
||||
},
|
||||
{
|
||||
name: "multiple patterns",
|
||||
patterns: []string{"app-*", "prod-*"},
|
||||
allNamespaces: allNamespaces,
|
||||
allowMirrorsNamespaces: allowMirrorsNamespaces,
|
||||
sourceNamespace: "default",
|
||||
filter: excludeFilter,
|
||||
wantContains: []string{"app-frontend", "app-backend", "prod-db", "prod-api"},
|
||||
wantNotContains: []string{"app1", "app2", "default"},
|
||||
},
|
||||
{
|
||||
name: "direct namespace names",
|
||||
patterns: []string{"app1", "app2"},
|
||||
allNamespaces: allNamespaces,
|
||||
allowMirrorsNamespaces: allowMirrorsNamespaces,
|
||||
sourceNamespace: "default",
|
||||
filter: excludeFilter,
|
||||
wantContains: []string{"app1", "app2"},
|
||||
wantNotContains: []string{"app-frontend", "prod-db", "default"},
|
||||
},
|
||||
{
|
||||
name: "exclude source namespace",
|
||||
patterns: []string{"app1"},
|
||||
allNamespaces: allNamespaces,
|
||||
allowMirrorsNamespaces: allowMirrorsNamespaces,
|
||||
sourceNamespace: "app1",
|
||||
filter: excludeFilter,
|
||||
wantContains: []string{},
|
||||
wantNotContains: []string{"app1"}, // app1 is source, excluded
|
||||
},
|
||||
{
|
||||
name: "deduplication",
|
||||
patterns: []string{"app-*", "app-frontend"}, // app-frontend matches both
|
||||
allNamespaces: allNamespaces,
|
||||
allowMirrorsNamespaces: allowMirrorsNamespaces,
|
||||
sourceNamespace: "default",
|
||||
filter: excludeFilter,
|
||||
wantContains: []string{"app-frontend", "app-backend"},
|
||||
wantNotContains: []string{"app1"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := ResolveTargetNamespaces(
|
||||
tt.patterns,
|
||||
tt.allNamespaces,
|
||||
tt.allowMirrorsNamespaces,
|
||||
tt.sourceNamespace,
|
||||
tt.filter,
|
||||
)
|
||||
|
||||
// Check that all expected namespaces are present
|
||||
for _, ns := range tt.wantContains {
|
||||
assert.Contains(t, got, ns, "should contain %s", ns)
|
||||
}
|
||||
|
||||
// Check that unwanted namespaces are not present
|
||||
for _, ns := range tt.wantNotContains {
|
||||
assert.NotContains(t, got, ns, "should not contain %s", ns)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Edge case tests
|
||||
func TestResolveTargetNamespaces_EdgeCases(t *testing.T) {
|
||||
t.Run("no namespaces in cluster", func(t *testing.T) {
|
||||
got := ResolveTargetNamespaces(
|
||||
[]string{"all"},
|
||||
[]string{},
|
||||
[]string{},
|
||||
"default",
|
||||
NewNamespaceFilter([]string{}, []string{}),
|
||||
)
|
||||
assert.Empty(t, got)
|
||||
})
|
||||
|
||||
t.Run("invalid pattern doesn't crash", func(t *testing.T) {
|
||||
// filepath.Match should handle this gracefully
|
||||
got := ResolveTargetNamespaces(
|
||||
[]string{"[invalid"},
|
||||
[]string{"app1"},
|
||||
[]string{},
|
||||
"default",
|
||||
NewNamespaceFilter([]string{}, []string{}),
|
||||
)
|
||||
assert.NotNil(t, got)
|
||||
})
|
||||
|
||||
t.Run("all excludes everything when filter denies all", func(t *testing.T) {
|
||||
strictFilter := NewNamespaceFilter([]string{}, []string{"specific-ns"})
|
||||
got := ResolveTargetNamespaces(
|
||||
[]string{"all"},
|
||||
[]string{"app1", "app2", "app3"},
|
||||
[]string{},
|
||||
"default",
|
||||
strictFilter,
|
||||
)
|
||||
// Only "specific-ns" would be allowed, but it's not in allNamespaces
|
||||
assert.Empty(t, got)
|
||||
})
|
||||
}
|
||||
|
||||
// Benchmark tests for critical paths
|
||||
|
||||
func BenchmarkParseTargetNamespaces(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
value string
|
||||
}{
|
||||
{
|
||||
name: "single namespace",
|
||||
value: "app1",
|
||||
},
|
||||
{
|
||||
name: "10 namespaces",
|
||||
value: "app1,app2,app3,app4,app5,app6,app7,app8,app9,app10",
|
||||
},
|
||||
{
|
||||
name: "50 namespaces with whitespace",
|
||||
value: "app1, app2, app3, app4, app5, app6, app7, app8, app9, app10, app11, app12, app13, app14, app15, app16, app17, app18, app19, app20, app21, app22, app23, app24, app25, app26, app27, app28, app29, app30, app31, app32, app33, app34, app35, app36, app37, app38, app39, app40, app41, app42, app43, app44, app45, app46, app47, app48, app49, app50",
|
||||
},
|
||||
{
|
||||
name: "mixed patterns",
|
||||
value: "app1,app-*,prod-*,staging-*,dev-*",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ParseTargetNamespaces(tt.value)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkMatchesPattern(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
pattern string
|
||||
}{
|
||||
{
|
||||
name: "exact match",
|
||||
namespace: "app-frontend",
|
||||
pattern: "app-frontend",
|
||||
},
|
||||
{
|
||||
name: "simple wildcard",
|
||||
namespace: "app-frontend",
|
||||
pattern: "app-*",
|
||||
},
|
||||
{
|
||||
name: "complex wildcard",
|
||||
namespace: "my-app-prod-db",
|
||||
pattern: "*-app-*-db",
|
||||
},
|
||||
{
|
||||
name: "no match",
|
||||
namespace: "production-api",
|
||||
pattern: "app-*",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = matchesPattern(tt.namespace, tt.pattern)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkNamespaceFilter_IsAllowed(b *testing.B) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filter *NamespaceFilter
|
||||
namespace string
|
||||
}{
|
||||
{
|
||||
name: "no filters (always allow)",
|
||||
filter: NewNamespaceFilter([]string{}, []string{}),
|
||||
namespace: "app1",
|
||||
},
|
||||
{
|
||||
name: "simple exclusion",
|
||||
filter: NewNamespaceFilter([]string{"kube-system", "kube-public", "kube-node-lease"}, []string{}),
|
||||
namespace: "app1",
|
||||
},
|
||||
{
|
||||
name: "pattern inclusion",
|
||||
filter: NewNamespaceFilter([]string{}, []string{"app-*", "prod-*"}),
|
||||
namespace: "app-frontend",
|
||||
},
|
||||
{
|
||||
name: "complex filtering",
|
||||
filter: NewNamespaceFilter([]string{"kube-system", "test-*"}, []string{"app-*", "prod-*"}),
|
||||
namespace: "prod-api",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = tt.filter.IsAllowed(tt.namespace)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkResolveTargetNamespaces(b *testing.B) {
|
||||
// Generate realistic namespace list
|
||||
allNamespaces := make([]string, 100)
|
||||
for i := 0; i < 100; i++ {
|
||||
if i < 30 {
|
||||
allNamespaces[i] = fmt.Sprintf("app-%d", i)
|
||||
} else if i < 60 {
|
||||
allNamespaces[i] = fmt.Sprintf("prod-%d", i)
|
||||
} else if i < 90 {
|
||||
allNamespaces[i] = fmt.Sprintf("staging-%d", i)
|
||||
} else {
|
||||
allNamespaces[i] = fmt.Sprintf("test-%d", i)
|
||||
}
|
||||
}
|
||||
|
||||
allowMirrorsNamespaces := allNamespaces[:50] // Half have opt-in label
|
||||
filter := NewNamespaceFilter([]string{"kube-system", "kube-public"}, []string{})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
patterns []string
|
||||
}{
|
||||
{
|
||||
name: "all keyword",
|
||||
patterns: []string{constants.TargetNamespacesAll},
|
||||
},
|
||||
{
|
||||
name: "all-labeled keyword",
|
||||
patterns: []string{constants.TargetNamespacesAllLabeled},
|
||||
},
|
||||
{
|
||||
name: "single pattern",
|
||||
patterns: []string{"app-*"},
|
||||
},
|
||||
{
|
||||
name: "multiple patterns",
|
||||
patterns: []string{"app-*", "prod-*", "staging-*"},
|
||||
},
|
||||
{
|
||||
name: "direct names",
|
||||
patterns: []string{"app-1", "app-2", "prod-1", "prod-2"},
|
||||
},
|
||||
{
|
||||
name: "mixed direct and patterns",
|
||||
patterns: []string{"app-1", "prod-*", "staging-5"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
b.Run(tt.name, func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ResolveTargetNamespaces(
|
||||
tt.patterns,
|
||||
allNamespaces,
|
||||
allowMirrorsNamespaces,
|
||||
"default",
|
||||
filter,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkResolveTargetNamespaces_LargeScale(b *testing.B) {
|
||||
// Simulate large cluster (1000 namespaces)
|
||||
allNamespaces := make([]string, 1000)
|
||||
for i := 0; i < 1000; i++ {
|
||||
allNamespaces[i] = fmt.Sprintf("namespace-%d", i)
|
||||
}
|
||||
|
||||
allowMirrorsNamespaces := allNamespaces[:500]
|
||||
filter := NewNamespaceFilter(constants.DefaultExcludedNamespaces, []string{})
|
||||
|
||||
b.Run("1000 namespaces with 'all' keyword", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ResolveTargetNamespaces(
|
||||
[]string{constants.TargetNamespacesAll},
|
||||
allNamespaces,
|
||||
allowMirrorsNamespaces,
|
||||
"default",
|
||||
filter,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("1000 namespaces with pattern matching", func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = ResolveTargetNamespaces(
|
||||
[]string{"namespace-*"},
|
||||
allNamespaces,
|
||||
allowMirrorsNamespaces,
|
||||
"default",
|
||||
filter,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user