Worker set its working directory (#8461)
* set working directory * consolidate to worker directory * working directory * correct directory name * refactoring to use wildcard matcher * simplify * cleaning ec working directory * fix reference * clean * adjust test
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
|
||||
)
|
||||
|
||||
// LRUNode represents a node in the doubly-linked list for efficient LRU operations
|
||||
@@ -210,7 +211,7 @@ func (e *StringLikeEvaluator) Evaluate(conditionValue interface{}, contextValues
|
||||
patterns := getCachedNormalizedValues(conditionValue)
|
||||
for _, pattern := range patterns {
|
||||
for _, contextValue := range contextValues {
|
||||
if MatchesWildcard(pattern, contextValue) {
|
||||
if wildcard.MatchesWildcard(pattern, contextValue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -225,7 +226,7 @@ func (e *StringNotLikeEvaluator) Evaluate(conditionValue interface{}, contextVal
|
||||
patterns := getCachedNormalizedValues(conditionValue)
|
||||
for _, pattern := range patterns {
|
||||
for _, contextValue := range contextValues {
|
||||
if MatchesWildcard(pattern, contextValue) {
|
||||
if wildcard.MatchesWildcard(pattern, contextValue) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -628,7 +629,7 @@ func (e *ArnLikeEvaluator) Evaluate(conditionValue interface{}, contextValues []
|
||||
patterns := getCachedNormalizedValues(conditionValue)
|
||||
for _, pattern := range patterns {
|
||||
for _, contextValue := range contextValues {
|
||||
if MatchesWildcard(pattern, contextValue) {
|
||||
if wildcard.MatchesWildcard(pattern, contextValue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
|
||||
)
|
||||
|
||||
// tagsToEntry converts a map of tag key-value pairs to the entry.Extended format
|
||||
@@ -749,7 +750,7 @@ func TestWildcardMatching(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := MatchesWildcard(tt.pattern, tt.str)
|
||||
result := wildcard.MatchesWildcard(tt.pattern, tt.str)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, tt.str, tt.expected, result)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
s3const "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
|
||||
)
|
||||
|
||||
// Policy Engine Types
|
||||
@@ -180,9 +181,9 @@ type CompiledPolicy struct {
|
||||
// CompiledStatement represents a compiled policy statement
|
||||
type CompiledStatement struct {
|
||||
Statement *PolicyStatement
|
||||
ActionMatchers []*WildcardMatcher
|
||||
ResourceMatchers []*WildcardMatcher
|
||||
PrincipalMatchers []*WildcardMatcher
|
||||
ActionMatchers []*wildcard.WildcardMatcher
|
||||
ResourceMatchers []*wildcard.WildcardMatcher
|
||||
PrincipalMatchers []*wildcard.WildcardMatcher
|
||||
// Keep regex patterns for backward compatibility
|
||||
ActionPatterns []*regexp.Regexp
|
||||
ResourcePatterns []*regexp.Regexp
|
||||
@@ -195,7 +196,7 @@ type CompiledStatement struct {
|
||||
|
||||
// NotResource patterns (resource should NOT match these)
|
||||
NotResourcePatterns []*regexp.Regexp
|
||||
NotResourceMatchers []*WildcardMatcher
|
||||
NotResourceMatchers []*wildcard.WildcardMatcher
|
||||
DynamicNotResourcePatterns []string
|
||||
}
|
||||
|
||||
@@ -328,7 +329,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
|
||||
}
|
||||
compiled.ActionPatterns = append(compiled.ActionPatterns, pattern)
|
||||
|
||||
matcher, err := NewWildcardMatcher(action)
|
||||
matcher, err := wildcard.NewWildcardMatcher(action)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create action matcher %s: %v", action, err)
|
||||
}
|
||||
@@ -352,7 +353,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
|
||||
}
|
||||
compiled.ResourcePatterns = append(compiled.ResourcePatterns, pattern)
|
||||
|
||||
matcher, err := NewWildcardMatcher(resource)
|
||||
matcher, err := wildcard.NewWildcardMatcher(resource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create resource matcher %s: %v", resource, err)
|
||||
}
|
||||
@@ -377,7 +378,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
|
||||
}
|
||||
compiled.PrincipalPatterns = append(compiled.PrincipalPatterns, pattern)
|
||||
|
||||
matcher, err := NewWildcardMatcher(principal)
|
||||
matcher, err := wildcard.NewWildcardMatcher(principal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create principal matcher %s: %v", principal, err)
|
||||
}
|
||||
@@ -403,7 +404,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
|
||||
}
|
||||
compiled.NotResourcePatterns = append(compiled.NotResourcePatterns, pattern)
|
||||
|
||||
matcher, err := NewWildcardMatcher(notResource)
|
||||
matcher, err := wildcard.NewWildcardMatcher(notResource)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create NotResource matcher %s: %v", notResource, err)
|
||||
}
|
||||
@@ -419,7 +420,7 @@ func compileStatement(stmt *PolicyStatement) (*CompiledStatement, error) {
|
||||
|
||||
// compilePattern compiles a wildcard pattern to regex
|
||||
func compilePattern(pattern string) (*regexp.Regexp, error) {
|
||||
return CompileWildcardPattern(pattern)
|
||||
return wildcard.CompileWildcardPattern(pattern)
|
||||
}
|
||||
|
||||
// normalizeToStringSlice converts various types to string slice - kept for backward compatibility
|
||||
@@ -571,11 +572,11 @@ func (cp *CompiledPolicy) EvaluatePolicy(args *PolicyEvaluationArgs) (bool, Poli
|
||||
|
||||
// FastMatchesWildcard uses cached WildcardMatcher for performance
|
||||
func FastMatchesWildcard(pattern, str string) bool {
|
||||
matcher, err := GetCachedWildcardMatcher(pattern)
|
||||
matcher, err := wildcard.GetCachedWildcardMatcher(pattern)
|
||||
if err != nil {
|
||||
glog.Errorf("Error getting cached WildcardMatcher for pattern %s: %v", pattern, err)
|
||||
// Fall back to the original implementation
|
||||
return MatchesWildcard(pattern, str)
|
||||
return wildcard.MatchesWildcard(pattern, str)
|
||||
}
|
||||
return matcher.Match(str)
|
||||
}
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
package policy_engine
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/glog"
|
||||
)
|
||||
|
||||
// WildcardMatcher provides unified wildcard matching functionality
|
||||
type WildcardMatcher struct {
|
||||
// Use regex for complex patterns with ? wildcards
|
||||
// Use string manipulation for simple * patterns (better performance)
|
||||
useRegex bool
|
||||
regex *regexp.Regexp
|
||||
pattern string
|
||||
}
|
||||
|
||||
// WildcardMatcherCache provides caching for WildcardMatcher instances
|
||||
type WildcardMatcherCache struct {
|
||||
mu sync.RWMutex
|
||||
matchers map[string]*WildcardMatcher
|
||||
maxSize int
|
||||
accessOrder []string // For LRU eviction
|
||||
}
|
||||
|
||||
// NewWildcardMatcherCache creates a new WildcardMatcherCache with a configurable maxSize
|
||||
func NewWildcardMatcherCache(maxSize int) *WildcardMatcherCache {
|
||||
if maxSize <= 0 {
|
||||
maxSize = 1000 // Default value
|
||||
}
|
||||
return &WildcardMatcherCache{
|
||||
matchers: make(map[string]*WildcardMatcher),
|
||||
maxSize: maxSize,
|
||||
}
|
||||
}
|
||||
|
||||
// Global cache instance
|
||||
var wildcardMatcherCache = NewWildcardMatcherCache(1000) // Default maxSize
|
||||
|
||||
// GetCachedWildcardMatcher gets or creates a cached WildcardMatcher for the given pattern
|
||||
func GetCachedWildcardMatcher(pattern string) (*WildcardMatcher, error) {
|
||||
// Fast path: check if already in cache
|
||||
wildcardMatcherCache.mu.RLock()
|
||||
if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
|
||||
wildcardMatcherCache.mu.RUnlock()
|
||||
wildcardMatcherCache.updateAccessOrder(pattern)
|
||||
return matcher, nil
|
||||
}
|
||||
wildcardMatcherCache.mu.RUnlock()
|
||||
|
||||
// Slow path: create new matcher and cache it
|
||||
wildcardMatcherCache.mu.Lock()
|
||||
defer wildcardMatcherCache.mu.Unlock()
|
||||
|
||||
// Double-check after acquiring write lock
|
||||
if matcher, exists := wildcardMatcherCache.matchers[pattern]; exists {
|
||||
wildcardMatcherCache.updateAccessOrderLocked(pattern)
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
// Create new matcher
|
||||
matcher, err := NewWildcardMatcher(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Evict old entries if cache is full
|
||||
if len(wildcardMatcherCache.matchers) >= wildcardMatcherCache.maxSize {
|
||||
wildcardMatcherCache.evictLeastRecentlyUsed()
|
||||
}
|
||||
|
||||
// Cache it
|
||||
wildcardMatcherCache.matchers[pattern] = matcher
|
||||
wildcardMatcherCache.accessOrder = append(wildcardMatcherCache.accessOrder, pattern)
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
// updateAccessOrder updates the access order for LRU eviction (with read lock)
|
||||
func (c *WildcardMatcherCache) updateAccessOrder(pattern string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.updateAccessOrderLocked(pattern)
|
||||
}
|
||||
|
||||
// updateAccessOrderLocked updates the access order for LRU eviction (without locking)
|
||||
func (c *WildcardMatcherCache) updateAccessOrderLocked(pattern string) {
|
||||
// Remove pattern from its current position
|
||||
for i, p := range c.accessOrder {
|
||||
if p == pattern {
|
||||
c.accessOrder = append(c.accessOrder[:i], c.accessOrder[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// Add pattern to the end (most recently used)
|
||||
c.accessOrder = append(c.accessOrder, pattern)
|
||||
}
|
||||
|
||||
// evictLeastRecentlyUsed removes the least recently used pattern from the cache
|
||||
func (c *WildcardMatcherCache) evictLeastRecentlyUsed() {
|
||||
if len(c.accessOrder) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Remove the least recently used pattern (first in the list)
|
||||
lruPattern := c.accessOrder[0]
|
||||
c.accessOrder = c.accessOrder[1:]
|
||||
delete(c.matchers, lruPattern)
|
||||
}
|
||||
|
||||
// ClearCache clears all cached patterns (useful for testing)
|
||||
func (c *WildcardMatcherCache) ClearCache() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.matchers = make(map[string]*WildcardMatcher)
|
||||
c.accessOrder = c.accessOrder[:0]
|
||||
}
|
||||
|
||||
// GetCacheStats returns cache statistics
|
||||
func (c *WildcardMatcherCache) GetCacheStats() (size int, maxSize int) {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return len(c.matchers), c.maxSize
|
||||
}
|
||||
|
||||
// NewWildcardMatcher creates a new wildcard matcher for the given pattern
|
||||
// The matcher uses an efficient string-based algorithm that handles both * and ? wildcards
|
||||
// without requiring regex compilation.
|
||||
func NewWildcardMatcher(pattern string) (*WildcardMatcher, error) {
|
||||
matcher := &WildcardMatcher{
|
||||
pattern: pattern,
|
||||
useRegex: false, // String-based matching now handles both * and ?
|
||||
}
|
||||
|
||||
return matcher, nil
|
||||
}
|
||||
|
||||
// Match checks if a string matches the wildcard pattern
|
||||
func (m *WildcardMatcher) Match(str string) bool {
|
||||
if m.useRegex {
|
||||
return m.regex.MatchString(str)
|
||||
}
|
||||
return matchWildcardString(m.pattern, str)
|
||||
}
|
||||
|
||||
// MatchesWildcard provides a simple function interface for wildcard matching
|
||||
// This function consolidates the logic from the previous separate implementations
|
||||
//
|
||||
// Rules:
|
||||
// - '*' matches any sequence of characters (including empty string)
|
||||
// - '?' matches exactly one character (any character)
|
||||
func MatchesWildcard(pattern, str string) bool {
|
||||
// matchWildcardString now handles both * and ? efficiently without regex
|
||||
return matchWildcardString(pattern, str)
|
||||
}
|
||||
|
||||
// CompileWildcardPattern converts a wildcard pattern to a compiled regex
|
||||
// This replaces the previous compilePattern function
|
||||
func CompileWildcardPattern(pattern string) (*regexp.Regexp, error) {
|
||||
return compileWildcardPattern(pattern)
|
||||
}
|
||||
|
||||
// matchWildcardString uses efficient string manipulation for * and ? wildcards
|
||||
// This implementation uses a backtracking algorithm that handles both wildcard types
|
||||
// without requiring regex compilation.
|
||||
//
|
||||
// Rules:
|
||||
// - '*' matches any sequence of characters (including empty string)
|
||||
// - '?' matches exactly one character (any character)
|
||||
func matchWildcardString(pattern, str string) bool {
|
||||
// Handle simple cases
|
||||
if pattern == "*" {
|
||||
return true
|
||||
}
|
||||
if pattern == str {
|
||||
return true
|
||||
}
|
||||
|
||||
targetIndex := 0
|
||||
patternIndex := 0
|
||||
|
||||
// Index of the most recent '*' in the pattern (-1 if none)
|
||||
lastStarIndex := -1
|
||||
|
||||
// Index in target where the last '*' started matching
|
||||
lastStarMatchIndex := 0
|
||||
|
||||
for targetIndex < len(str) {
|
||||
switch {
|
||||
// Case 1: Current characters match directly or '?' matches any single character
|
||||
case patternIndex < len(pattern) &&
|
||||
(pattern[patternIndex] == '?' || pattern[patternIndex] == str[targetIndex]):
|
||||
|
||||
targetIndex++
|
||||
patternIndex++
|
||||
|
||||
// Case 2: Wildcard '*' found in pattern
|
||||
case patternIndex < len(pattern) &&
|
||||
pattern[patternIndex] == '*':
|
||||
|
||||
lastStarIndex = patternIndex
|
||||
lastStarMatchIndex = targetIndex
|
||||
patternIndex++
|
||||
|
||||
// Case 3: Previous '*' can absorb one more character
|
||||
case lastStarIndex != -1:
|
||||
|
||||
patternIndex = lastStarIndex + 1
|
||||
lastStarMatchIndex++
|
||||
targetIndex = lastStarMatchIndex
|
||||
|
||||
// Case 4: No match possible
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Consume any trailing '*' in the pattern
|
||||
for patternIndex < len(pattern) && pattern[patternIndex] == '*' {
|
||||
patternIndex++
|
||||
}
|
||||
|
||||
// Match is valid only if the entire pattern is consumed
|
||||
return patternIndex == len(pattern)
|
||||
}
|
||||
|
||||
// matchWildcardRegex uses WildcardMatcher for patterns with ? wildcards
|
||||
func matchWildcardRegex(pattern, str string) bool {
|
||||
matcher, err := GetCachedWildcardMatcher(pattern)
|
||||
if err != nil {
|
||||
glog.Errorf("Error getting WildcardMatcher for pattern %s: %v. Falling back to matchWildcardString.", pattern, err)
|
||||
// Fallback to matchWildcardString
|
||||
return matchWildcardString(pattern, str)
|
||||
}
|
||||
return matcher.Match(str)
|
||||
}
|
||||
|
||||
// compileWildcardPattern converts a wildcard pattern to regex
|
||||
func compileWildcardPattern(pattern string) (*regexp.Regexp, error) {
|
||||
// Escape special regex characters except * and ?
|
||||
escaped := regexp.QuoteMeta(pattern)
|
||||
|
||||
// Replace escaped wildcards with regex equivalents
|
||||
escaped = strings.ReplaceAll(escaped, `\*`, `.*`)
|
||||
escaped = strings.ReplaceAll(escaped, `\?`, `.`)
|
||||
|
||||
// Anchor the pattern
|
||||
escaped = "^" + escaped + "$"
|
||||
|
||||
return regexp.Compile(escaped)
|
||||
}
|
||||
@@ -1,469 +0,0 @@
|
||||
package policy_engine
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMatchesWildcard(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pattern string
|
||||
str string
|
||||
expected bool
|
||||
}{
|
||||
// Basic functionality tests
|
||||
{
|
||||
name: "Exact match",
|
||||
pattern: "test",
|
||||
str: "test",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Single wildcard",
|
||||
pattern: "*",
|
||||
str: "anything",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Empty string with wildcard",
|
||||
pattern: "*",
|
||||
str: "",
|
||||
expected: true,
|
||||
},
|
||||
|
||||
// Star (*) wildcard tests
|
||||
{
|
||||
name: "Prefix wildcard",
|
||||
pattern: "test*",
|
||||
str: "test123",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Suffix wildcard",
|
||||
pattern: "*test",
|
||||
str: "123test",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Middle wildcard",
|
||||
pattern: "test*123",
|
||||
str: "testABC123",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Multiple wildcards",
|
||||
pattern: "test*abc*123",
|
||||
str: "testXYZabcDEF123",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "No match",
|
||||
pattern: "test*",
|
||||
str: "other",
|
||||
expected: false,
|
||||
},
|
||||
|
||||
// Question mark (?) wildcard tests
|
||||
{
|
||||
name: "Single question mark",
|
||||
pattern: "test?",
|
||||
str: "test1",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Multiple question marks",
|
||||
pattern: "test??",
|
||||
str: "test12",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Question mark no match",
|
||||
pattern: "test?",
|
||||
str: "test12",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Mixed wildcards",
|
||||
pattern: "test*abc?def",
|
||||
str: "testXYZabc1def",
|
||||
expected: true,
|
||||
},
|
||||
|
||||
// Edge cases
|
||||
{
|
||||
name: "Empty pattern",
|
||||
pattern: "",
|
||||
str: "",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Empty pattern with string",
|
||||
pattern: "",
|
||||
str: "test",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Pattern with string empty",
|
||||
pattern: "test",
|
||||
str: "",
|
||||
expected: false,
|
||||
},
|
||||
|
||||
// Special characters
|
||||
{
|
||||
name: "Pattern with regex special chars",
|
||||
pattern: "test[abc]",
|
||||
str: "test[abc]",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Pattern with dots",
|
||||
pattern: "test.txt",
|
||||
str: "test.txt",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "Pattern with dots and wildcard",
|
||||
pattern: "*.txt",
|
||||
str: "test.txt",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := MatchesWildcard(tt.pattern, tt.str)
|
||||
if result != tt.expected {
|
||||
t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, tt.str, tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWildcardMatcher(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pattern string
|
||||
strings []string
|
||||
expected []bool
|
||||
}{
|
||||
{
|
||||
name: "Simple star pattern",
|
||||
pattern: "test*",
|
||||
strings: []string{"test", "test123", "testing", "other"},
|
||||
expected: []bool{true, true, true, false},
|
||||
},
|
||||
{
|
||||
name: "Question mark pattern",
|
||||
pattern: "test?",
|
||||
strings: []string{"test1", "test2", "test", "test12"},
|
||||
expected: []bool{true, true, false, false},
|
||||
},
|
||||
{
|
||||
name: "Mixed pattern",
|
||||
pattern: "*.txt",
|
||||
strings: []string{"file.txt", "test.txt", "file.doc", "txt"},
|
||||
expected: []bool{true, true, false, false},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
matcher, err := NewWildcardMatcher(tt.pattern)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create matcher: %v", err)
|
||||
}
|
||||
|
||||
for i, str := range tt.strings {
|
||||
result := matcher.Match(str)
|
||||
if result != tt.expected[i] {
|
||||
t.Errorf("Pattern %s against %s: expected %v, got %v", tt.pattern, str, tt.expected[i], result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompileWildcardPattern(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pattern string
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{"Star wildcard", "s3:Get*", "s3:GetObject", true},
|
||||
{"Question mark wildcard", "s3:Get?bject", "s3:GetObject", true},
|
||||
{"Mixed wildcards", "s3:*Object*", "s3:GetObjectAcl", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
regex, err := CompileWildcardPattern(tt.pattern)
|
||||
if err != nil {
|
||||
t.Errorf("CompileWildcardPattern() error = %v", err)
|
||||
return
|
||||
}
|
||||
got := regex.MatchString(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("CompileWildcardPattern() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWildcardMatchingPerformance demonstrates the performance benefits of caching
|
||||
func BenchmarkWildcardMatchingPerformance(b *testing.B) {
|
||||
patterns := []string{
|
||||
"s3:Get*",
|
||||
"s3:Put*",
|
||||
"s3:Delete*",
|
||||
"s3:List*",
|
||||
"arn:aws:s3:::bucket/*",
|
||||
"arn:aws:s3:::bucket/prefix*",
|
||||
"user:*",
|
||||
"user:admin-*",
|
||||
}
|
||||
|
||||
inputs := []string{
|
||||
"s3:GetObject",
|
||||
"s3:PutObject",
|
||||
"s3:DeleteObject",
|
||||
"s3:ListBucket",
|
||||
"arn:aws:s3:::bucket/file.txt",
|
||||
"arn:aws:s3:::bucket/prefix/file.txt",
|
||||
"user:admin",
|
||||
"user:admin-john",
|
||||
}
|
||||
|
||||
b.Run("WithoutCache", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, pattern := range patterns {
|
||||
for _, input := range inputs {
|
||||
MatchesWildcard(pattern, input)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("WithCache", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
for _, pattern := range patterns {
|
||||
for _, input := range inputs {
|
||||
FastMatchesWildcard(pattern, input)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BenchmarkWildcardMatcherReuse demonstrates the performance benefits of reusing WildcardMatcher instances
|
||||
func BenchmarkWildcardMatcherReuse(b *testing.B) {
|
||||
pattern := "s3:Get*"
|
||||
input := "s3:GetObject"
|
||||
|
||||
b.Run("NewMatcherEveryTime", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
matcher, _ := NewWildcardMatcher(pattern)
|
||||
matcher.Match(input)
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("CachedMatcher", func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
matcher, _ := GetCachedWildcardMatcher(pattern)
|
||||
matcher.Match(input)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestWildcardMatcherCaching verifies that caching works correctly
|
||||
func TestWildcardMatcherCaching(t *testing.T) {
|
||||
pattern := "s3:Get*"
|
||||
|
||||
// Get the first matcher
|
||||
matcher1, err := GetCachedWildcardMatcher(pattern)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get cached matcher: %v", err)
|
||||
}
|
||||
|
||||
// Get the second matcher - should be the same instance
|
||||
matcher2, err := GetCachedWildcardMatcher(pattern)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get cached matcher: %v", err)
|
||||
}
|
||||
|
||||
// Check that they're the same instance (same pointer)
|
||||
if matcher1 != matcher2 {
|
||||
t.Errorf("Expected same matcher instance, got different instances")
|
||||
}
|
||||
|
||||
// Test that both matchers work correctly
|
||||
testInput := "s3:GetObject"
|
||||
if !matcher1.Match(testInput) {
|
||||
t.Errorf("First matcher failed to match %s", testInput)
|
||||
}
|
||||
if !matcher2.Match(testInput) {
|
||||
t.Errorf("Second matcher failed to match %s", testInput)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFastMatchesWildcard verifies that the fast matching function works correctly
|
||||
func TestFastMatchesWildcard(t *testing.T) {
|
||||
tests := []struct {
|
||||
pattern string
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{"s3:Get*", "s3:GetObject", true},
|
||||
{"s3:Put*", "s3:GetObject", false},
|
||||
{"arn:aws:s3:::bucket/*", "arn:aws:s3:::bucket/file.txt", true},
|
||||
{"user:admin-*", "user:admin-john", true},
|
||||
{"user:admin-*", "user:guest-john", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.pattern+"_"+tt.input, func(t *testing.T) {
|
||||
got := FastMatchesWildcard(tt.pattern, tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("FastMatchesWildcard(%q, %q) = %v, want %v", tt.pattern, tt.input, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestWildcardMatcherCacheBounding tests the bounded cache functionality
|
||||
func TestWildcardMatcherCacheBounding(t *testing.T) {
|
||||
// Clear cache before test
|
||||
wildcardMatcherCache.ClearCache()
|
||||
|
||||
// Get original max size
|
||||
originalMaxSize := wildcardMatcherCache.maxSize
|
||||
|
||||
// Set a small max size for testing
|
||||
wildcardMatcherCache.maxSize = 3
|
||||
defer func() {
|
||||
wildcardMatcherCache.maxSize = originalMaxSize
|
||||
wildcardMatcherCache.ClearCache()
|
||||
}()
|
||||
|
||||
// Add patterns up to max size
|
||||
patterns := []string{"pattern1", "pattern2", "pattern3"}
|
||||
for _, pattern := range patterns {
|
||||
_, err := GetCachedWildcardMatcher(pattern)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify cache size
|
||||
size, maxSize := wildcardMatcherCache.GetCacheStats()
|
||||
if size != 3 {
|
||||
t.Errorf("Expected cache size 3, got %d", size)
|
||||
}
|
||||
if maxSize != 3 {
|
||||
t.Errorf("Expected max size 3, got %d", maxSize)
|
||||
}
|
||||
|
||||
// Add another pattern, should evict the least recently used
|
||||
_, err := GetCachedWildcardMatcher("pattern4")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get cached matcher for pattern4: %v", err)
|
||||
}
|
||||
|
||||
// Cache should still be at max size
|
||||
size, _ = wildcardMatcherCache.GetCacheStats()
|
||||
if size != 3 {
|
||||
t.Errorf("Expected cache size 3 after eviction, got %d", size)
|
||||
}
|
||||
|
||||
// The first pattern should have been evicted
|
||||
wildcardMatcherCache.mu.RLock()
|
||||
if _, exists := wildcardMatcherCache.matchers["pattern1"]; exists {
|
||||
t.Errorf("Expected pattern1 to be evicted, but it still exists")
|
||||
}
|
||||
if _, exists := wildcardMatcherCache.matchers["pattern4"]; !exists {
|
||||
t.Errorf("Expected pattern4 to be in cache, but it doesn't exist")
|
||||
}
|
||||
wildcardMatcherCache.mu.RUnlock()
|
||||
}
|
||||
|
||||
// TestWildcardMatcherCacheLRU tests the LRU eviction policy
|
||||
func TestWildcardMatcherCacheLRU(t *testing.T) {
|
||||
// Clear cache before test
|
||||
wildcardMatcherCache.ClearCache()
|
||||
|
||||
// Get original max size
|
||||
originalMaxSize := wildcardMatcherCache.maxSize
|
||||
|
||||
// Set a small max size for testing
|
||||
wildcardMatcherCache.maxSize = 3
|
||||
defer func() {
|
||||
wildcardMatcherCache.maxSize = originalMaxSize
|
||||
wildcardMatcherCache.ClearCache()
|
||||
}()
|
||||
|
||||
// Add patterns to fill cache
|
||||
patterns := []string{"pattern1", "pattern2", "pattern3"}
|
||||
for _, pattern := range patterns {
|
||||
_, err := GetCachedWildcardMatcher(pattern)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Access pattern1 to make it most recently used
|
||||
_, err := GetCachedWildcardMatcher("pattern1")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to access pattern1: %v", err)
|
||||
}
|
||||
|
||||
// Add another pattern, should evict pattern2 (now least recently used)
|
||||
_, err = GetCachedWildcardMatcher("pattern4")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get cached matcher for pattern4: %v", err)
|
||||
}
|
||||
|
||||
// pattern1 should still be in cache (was accessed recently)
|
||||
// pattern2 should be evicted (was least recently used)
|
||||
wildcardMatcherCache.mu.RLock()
|
||||
if _, exists := wildcardMatcherCache.matchers["pattern1"]; !exists {
|
||||
t.Errorf("Expected pattern1 to remain in cache (most recently used)")
|
||||
}
|
||||
if _, exists := wildcardMatcherCache.matchers["pattern2"]; exists {
|
||||
t.Errorf("Expected pattern2 to be evicted (least recently used)")
|
||||
}
|
||||
if _, exists := wildcardMatcherCache.matchers["pattern3"]; !exists {
|
||||
t.Errorf("Expected pattern3 to remain in cache")
|
||||
}
|
||||
if _, exists := wildcardMatcherCache.matchers["pattern4"]; !exists {
|
||||
t.Errorf("Expected pattern4 to be in cache")
|
||||
}
|
||||
wildcardMatcherCache.mu.RUnlock()
|
||||
}
|
||||
|
||||
// TestWildcardMatcherCacheClear tests the cache clearing functionality
|
||||
func TestWildcardMatcherCacheClear(t *testing.T) {
|
||||
// Add some patterns to cache
|
||||
patterns := []string{"pattern1", "pattern2", "pattern3"}
|
||||
for _, pattern := range patterns {
|
||||
_, err := GetCachedWildcardMatcher(pattern)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get cached matcher for %s: %v", pattern, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify cache has patterns
|
||||
size, _ := wildcardMatcherCache.GetCacheStats()
|
||||
if size == 0 {
|
||||
t.Errorf("Expected cache to have patterns before clearing")
|
||||
}
|
||||
|
||||
// Clear cache
|
||||
wildcardMatcherCache.ClearCache()
|
||||
|
||||
// Verify cache is empty
|
||||
size, _ = wildcardMatcherCache.GetCacheStats()
|
||||
if size != 0 {
|
||||
t.Errorf("Expected cache to be empty after clearing, got size %d", size)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user