diff --git a/pkg/appsrv/cache_test.go b/pkg/appsrv/cache_test.go deleted file mode 100644 index ffbe68abe5..0000000000 --- a/pkg/appsrv/cache_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package appsrv - -import ( - "testing" -) - -func TestCache(t *testing.T) { - c := NewCache(1024) - c.Set("123", 123) - c.Set("456", 456) - v := c.Get("123") - if v == nil || v.(int) != 123 { - t.Error("Key 123 not found") - } - v = c.Get("456") - if v == nil || v.(int) != 456 { - t.Error("Key 456 not found") - } - c.Set("456", 789) - v = c.Get("456") - if v == nil || v.(int) != 789 { - t.Error("Key 456 not changed") - } -} diff --git a/pkg/appsrv/context.go b/pkg/appsrv/context.go index e82203721b..2df21196f1 100644 --- a/pkg/appsrv/context.go +++ b/pkg/appsrv/context.go @@ -5,6 +5,7 @@ import ( "database/sql" "yunion.io/x/onecloud/pkg/appctx" + "yunion.io/x/onecloud/pkg/util/hashcache" ) func AppContextDB(ctx context.Context) *sql.DB { @@ -15,12 +16,12 @@ func AppContextDB(ctx context.Context) *sql.DB { return val.(*sql.DB) } -func AppContextCache(ctx context.Context) *Cache { +func AppContextCache(ctx context.Context) *hashcache.Cache { val := ctx.Value(appctx.APP_CONTEXT_KEY_CACHE) if val == nil { return nil } - return val.(*Cache) + return val.(*hashcache.Cache) } func AppContextApp(ctx context.Context) *Application { diff --git a/pkg/cloudcommon/policy/policy.go b/pkg/cloudcommon/policy/policy.go index df51273491..dde27fd630 100644 --- a/pkg/cloudcommon/policy/policy.go +++ b/pkg/cloudcommon/policy/policy.go @@ -1,6 +1,9 @@ package policy import ( + "fmt" + "sort" + "strings" "time" "yunion.io/x/jsonutils" @@ -12,6 +15,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/auth" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/util/conditionparser" + "yunion.io/x/onecloud/pkg/util/hashcache" "yunion.io/x/onecloud/pkg/util/rbacutils" ) @@ -147,6 +151,8 @@ type SPolicyManager struct { failedRetryInterval time.Duration refreshInterval time.Duration + + cache *hashcache.Cache // policy cache } func parseJsonPolicy(obj jsonutils.JSONObject) (string, rbacutils.SRbacPolicy, error) { @@ -226,6 +232,8 @@ func (manager *SPolicyManager) start(refreshInterval time.Duration, retryInterva Rules: rbacutils.CompactRules(defaultRules), } } + + manager.cache = hashcache.NewCache(2048, manager.refreshInterval/2) manager.sync() } @@ -243,7 +251,48 @@ func (manager *SPolicyManager) sync() { time.AfterFunc(manager.refreshInterval, manager.sync) } +func queryKey(isAdmin bool, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) string { + queryKeys := []string{fmt.Sprintf("%v", isAdmin)} + queryKeys = append(queryKeys, userCred.GetProjectId(), userCred.GetDomainId(), userCred.GetUserId()) + roles := userCred.GetRoles() + if len(roles) > 0 { + sort.Strings(roles) + } + queryKeys = append(queryKeys, strings.Join(roles, ":")) + if rbacutils.WILD_MATCH == service || len(service) == 0 { + service = rbacutils.WILD_MATCH + } + queryKeys = append(queryKeys, service) + if rbacutils.WILD_MATCH == resource || len(resource) == 0 { + resource = rbacutils.WILD_MATCH + } + queryKeys = append(queryKeys, resource) + if rbacutils.WILD_MATCH == action || len(action) == 0 { + action = rbacutils.WILD_MATCH + } + queryKeys = append(queryKeys, action) + if len(extra) > 0 { + queryKeys = append(queryKeys, extra...) + } + return strings.Join(queryKeys, "-") +} + func (manager *SPolicyManager) Allow(isAdmin bool, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.TRbacResult { + if manager.cache != nil { + key := queryKey(isAdmin, userCred, service, resource, action, extra...) + val := manager.cache.Get(key) + if val != nil { + return val.(rbacutils.TRbacResult) + } + result := manager.allowWithoutCache(isAdmin, userCred, service, resource, action, extra...) + manager.cache.Set(key, result) + return result + } else { + return manager.allowWithoutCache(isAdmin, userCred, service, resource, action, extra...) + } +} + +func (manager *SPolicyManager) allowWithoutCache(isAdmin bool, userCred mcclient.TokenCredential, service string, resource string, action string, extra ...string) rbacutils.TRbacResult { var policies map[string]rbacutils.SRbacPolicy if isAdmin { policies = manager.adminPolicies diff --git a/pkg/appsrv/cache.go b/pkg/util/hashcache/cache.go similarity index 65% rename from pkg/appsrv/cache.go rename to pkg/util/hashcache/cache.go index dbe97ad19b..fe053bddaf 100644 --- a/pkg/appsrv/cache.go +++ b/pkg/util/hashcache/cache.go @@ -1,25 +1,28 @@ -package appsrv +package hashcache import ( "crypto/md5" "crypto/sha1" "crypto/sha256" "sync" + "time" ) type cacheNode struct { - key string - value interface{} + key string + expire time.Time + value interface{} } type Cache struct { - table []cacheNode - lock *sync.Mutex - size uint32 + table []cacheNode + lock *sync.Mutex + size uint32 + defaultTtl time.Duration } -func NewCache(size uint32) *Cache { - ca := &Cache{table: make([]cacheNode, size), lock: &sync.Mutex{}, size: size} +func NewCache(size uint32, defaultTTL time.Duration) *Cache { + ca := &Cache{table: make([]cacheNode, size), lock: &sync.Mutex{}, size: size, defaultTtl: defaultTTL} return ca } @@ -51,10 +54,14 @@ func checksum(alg int, key string) uint32 { func (c *Cache) find(key string) (bool, uint32) { var idx uint32 + now := time.Now() for _, alg := range []int{HASH_ALG_MD5, HASH_ALG_SHA1, HASH_ALG_SHA256} { idx = checksum(alg, key) % c.size if c.table[idx].key == key { - return true, idx + if c.table[idx].expire.IsZero() || c.table[idx].expire.After(now) { + return true, idx + } + break } } return false, idx @@ -74,13 +81,18 @@ func (c *Cache) AtomicGet(key string) interface{} { return c.Get(key) } -func (c *Cache) Set(key string, val interface{}) { +func (c *Cache) Set(key string, val interface{}, expire ...time.Time) { find, idx := c.find(key) - if find { - c.table[idx].value = val - } else { + if !find { c.table[idx].key = key - c.table[idx].value = val + } + c.table[idx].value = val + if len(expire) > 0 && !expire[0].IsZero() { + c.table[idx].expire = expire[0] + } else if c.defaultTtl > time.Millisecond { + c.table[idx].expire = time.Now().Add(c.defaultTtl) + } else { + c.table[idx].expire = time.Time{} } } diff --git a/pkg/util/hashcache/cache_test.go b/pkg/util/hashcache/cache_test.go new file mode 100644 index 0000000000..3394e9ed14 --- /dev/null +++ b/pkg/util/hashcache/cache_test.go @@ -0,0 +1,49 @@ +package hashcache + +import ( + "testing" + "time" +) + +func TestCache(t *testing.T) { + c := NewCache(1024, time.Second) + c.Set("123", 123) + c.Set("456", 456) + v := c.Get("123") + if v == nil || v.(int) != 123 { + t.Error("Key 123 not found") + } + v = c.Get("456") + if v == nil || v.(int) != 456 { + t.Error("Key 456 not found") + } + c.Set("456", 789) + v = c.Get("456") + if v == nil || v.(int) != 789 { + t.Error("Key 456 not changed") + } + + time.Sleep(time.Second) + + v = c.Get("123") + if v != nil { + t.Errorf("key 123 shoud expire") + } + c.Set("123", 1234) + c.Set("456", 4567) + v = c.Get("123") + if v == nil || v.(int) != 1234 { + t.Error("Key 123 not found") + } + v = c.Get("456") + if v == nil || v.(int) != 4567 { + t.Error("Key 456 not found") + } + + time.Sleep(time.Second) + + v = c.Get("123") + if v != nil { + t.Errorf("key 123 shoud expire") + } +}