diff --git a/api/cache/cache_test.go b/api/cache/cache_test.go index 825c4e67..095a8fce 100644 --- a/api/cache/cache_test.go +++ b/api/cache/cache_test.go @@ -3,10 +3,13 @@ package cache import ( "testing" + "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/util" "github.com/stretchr/testify/require" "go.uber.org/zap" "go.uber.org/zap/zaptest/observer" @@ -194,6 +197,44 @@ func TestNotificationConfigurationCacheType(t *testing.T) { assertInvalidCacheEntry(t, cache.GetNotificationConfiguration(key), observedLog) } +func TestFrostFSIDSubjectCacheType(t *testing.T) { + logger, observedLog := getObservedLogger() + cache := NewFrostfsIDCache(DefaultFrostfsIDConfig(logger)) + + key, err := util.Uint160DecodeStringLE("4ea976429703418ef00fc4912a409b6a0b973034") + require.NoError(t, err) + value := &client.SubjectExtended{} + + err = cache.PutSubject(key, value) + require.NoError(t, err) + val := cache.GetSubject(key) + require.Equal(t, value, val) + require.Equal(t, 0, observedLog.Len()) + + err = cache.cache.Set(key, "tmp") + require.NoError(t, err) + assertInvalidCacheEntry(t, cache.GetSubject(key), observedLog) +} + +func TestFrostFSIDUserKeyCacheType(t *testing.T) { + logger, observedLog := getObservedLogger() + cache := NewFrostfsIDCache(DefaultFrostfsIDConfig(logger)) + + ns, name := "ns", "name" + value, err := keys.NewPrivateKey() + require.NoError(t, err) + + err = cache.PutUserKey(ns, name, value.PublicKey()) + require.NoError(t, err) + val := cache.GetUserKey(ns, name) + require.Equal(t, value.PublicKey(), val) + require.Equal(t, 0, observedLog.Len()) + + err = cache.cache.Set(ns+"/"+name, "tmp") + require.NoError(t, err) + assertInvalidCacheEntry(t, cache.GetUserKey(ns, name), observedLog) +} + func assertInvalidCacheEntry(t *testing.T, val interface{}, observedLog *observer.ObservedLogs) { require.Nil(t, val) require.Equal(t, 1, observedLog.Len()) diff --git a/api/cache/frostfsid.go b/api/cache/frostfsid.go new file mode 100644 index 00000000..da093ecd --- /dev/null +++ b/api/cache/frostfsid.go @@ -0,0 +1,84 @@ +package cache + +import ( + "fmt" + "time" + + "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" + "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" + "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" + "github.com/bluele/gcache" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/util" + "go.uber.org/zap" +) + +// FrostfsIDCache provides lru cache for frostfsid contract. +type FrostfsIDCache struct { + cache gcache.Cache + logger *zap.Logger +} + +type FrostfsIDCacheKey struct { + Target engine.Target + Name chain.Name +} + +const ( + // DefaultFrostfsIDCacheSize is a default maximum number of entries in cache. + DefaultFrostfsIDCacheSize = 1e4 + // DefaultFrostfsIDCacheLifetime is a default lifetime of entries in cache. + DefaultFrostfsIDCacheLifetime = time.Minute +) + +// DefaultFrostfsIDConfig returns new default cache expiration values. +func DefaultFrostfsIDConfig(logger *zap.Logger) *Config { + return &Config{ + Size: DefaultFrostfsIDCacheSize, + Lifetime: DefaultFrostfsIDCacheLifetime, + Logger: logger, + } +} + +// NewFrostfsIDCache creates an object of FrostfsIDCache. +func NewFrostfsIDCache(config *Config) *FrostfsIDCache { + gc := gcache.New(config.Size).LRU().Expiration(config.Lifetime).Build() + return &FrostfsIDCache{cache: gc, logger: config.Logger} +} + +// GetSubject returns a cached client.SubjectExtended. Returns nil if value is missing. +func (c *FrostfsIDCache) GetSubject(key util.Uint160) *client.SubjectExtended { + return get[client.SubjectExtended](c, key) +} + +// PutSubject puts a client.SubjectExtended to cache. +func (c *FrostfsIDCache) PutSubject(key util.Uint160, subject *client.SubjectExtended) error { + return c.cache.Set(key, subject) +} + +// GetUserKey returns a cached *keys.PublicKey. Returns nil if value is missing. +func (c *FrostfsIDCache) GetUserKey(ns, name string) *keys.PublicKey { + return get[keys.PublicKey](c, ns+"/"+name) +} + +// PutUserKey puts a client.SubjectExtended to cache. +func (c *FrostfsIDCache) PutUserKey(ns, name string, userKey *keys.PublicKey) error { + return c.cache.Set(ns+"/"+name, userKey) +} + +func get[T any](c *FrostfsIDCache, key any) *T { + entry, err := c.cache.Get(key) + if err != nil { + return nil + } + + result, ok := entry.(*T) + if !ok { + c.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)), + zap.String("expected", fmt.Sprintf("%T", result))) + return nil + } + + return result +} diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index af7e35a3..182e719a 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -958,6 +958,15 @@ func getMorphPolicyCacheConfig(v *viper.Viper, l *zap.Logger) *cache.Config { return cacheCfg } +func getFrostfsIDCacheConfig(v *viper.Viper, l *zap.Logger) *cache.Config { + cacheCfg := cache.DefaultFrostfsIDConfig(l) + + cacheCfg.Lifetime = fetchCacheLifetime(v, l, cfgFrostfsIDCacheLifetime, cacheCfg.Lifetime) + cacheCfg.Size = fetchCacheSize(v, l, cfgFrostfsIDCacheSize, cacheCfg.Size) + + return cacheCfg +} + func (a *App) initHandler() { var err error diff --git a/cmd/s3-gw/app_settings.go b/cmd/s3-gw/app_settings.go index 2025765f..79bbb228 100644 --- a/cmd/s3-gw/app_settings.go +++ b/cmd/s3-gw/app_settings.go @@ -116,6 +116,8 @@ const ( // Settings. cfgAccessControlCacheSize = "cache.accesscontrol.size" cfgMorphPolicyCacheLifetime = "cache.morph_policy.lifetime" cfgMorphPolicyCacheSize = "cache.morph_policy.size" + cfgFrostfsIDCacheLifetime = "cache.frostfsid.lifetime" + cfgFrostfsIDCacheSize = "cache.frostfsid.size" cfgAccessBoxCacheRemovingCheckInterval = "cache.accessbox.removing_check_interval" diff --git a/config/config.env b/config/config.env index 7110985a..a35dda6c 100644 --- a/config/config.env +++ b/config/config.env @@ -107,6 +107,9 @@ S3_GW_CACHE_ACCESSCONTROL_SIZE=100000 # Cache which stores list of policy chains S3_GW_CACHE_MORPH_POLICY_LIFETIME=1m S3_GW_CACHE_MORPH_POLICY_SIZE=10000 +# Cache which stores frostfsid subject info +S3_GW_CACHE_FROSTFSID_LIFETIME=1m +S3_GW_CACHE_FROSTFSID_SIZE=10000 # NATS S3_GW_NATS_ENABLED=true diff --git a/config/config.yaml b/config/config.yaml index 16c8513f..cfc66c6f 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -131,6 +131,10 @@ cache: morph_policy: lifetime: 1m size: 10000 + # Cache which stores frostfsid subject info + frostfsid: + lifetime: 1m + size: 10000 nats: enabled: true diff --git a/docs/configuration.md b/docs/configuration.md index 280d9dc9..a9decf3e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -421,6 +421,9 @@ cache: morph_policy: lifetime: 30s size: 10000 + frostfsid: + lifetime: 1m + size: 10000 ``` | Parameter | Type | Default value | Description | @@ -434,6 +437,7 @@ cache: | `accessbox` | [Accessbox cache config](#accessbox-subsection) | `lifetime: 10m`
`size: 100` | Cache which stores access box with tokens by its address. | | `accesscontrol` | [Cache config](#cache-subsection) | `lifetime: 1m`
`size: 100000` | Cache which stores owner to cache operation mapping. | | `morph_policy` | [Cache config](#cache-subsection) | `lifetime: 1m`
`size: 10000` | Cache which stores list of policy chains. | +| `frostfsid` | [Cache config](#cache-subsection) | `lifetime: 1m`
`size: 10000` | Cache which stores FrostfsID subject info. | #### `cache` subsection