forked from TrueCloudLab/frostfs-s3-gw
[#266] Support per namespace placement policies configuration
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
0db6cd6727
commit
28c6bb4cb8
18 changed files with 307 additions and 52 deletions
|
@ -40,6 +40,7 @@ This document outlines major changes between releases.
|
||||||
- Add new `logger.destination` config param (#236)
|
- Add new `logger.destination` config param (#236)
|
||||||
- Add `X-Amz-Content-Sha256` header validation (#218)
|
- Add `X-Amz-Content-Sha256` header validation (#218)
|
||||||
- Support frostfsid contract. See `frostfsid` config section (#260)
|
- Support frostfsid contract. See `frostfsid` config section (#260)
|
||||||
|
- Support per namespace placement policies configuration (see `namespaces.config` config param) (#266)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Update prometheus to v1.15.0 (#94)
|
- Update prometheus to v1.15.0 (#94)
|
||||||
|
|
|
@ -31,10 +31,10 @@ type (
|
||||||
|
|
||||||
// Config contains data which handler needs to keep.
|
// Config contains data which handler needs to keep.
|
||||||
Config interface {
|
Config interface {
|
||||||
DefaultPlacementPolicy() netmap.PlacementPolicy
|
DefaultPlacementPolicy(namespace string) netmap.PlacementPolicy
|
||||||
PlacementPolicy(string) (netmap.PlacementPolicy, bool)
|
PlacementPolicy(namespace, constraint string) (netmap.PlacementPolicy, bool)
|
||||||
CopiesNumbers(string) ([]uint32, bool)
|
CopiesNumbers(namespace, constraint string) ([]uint32, bool)
|
||||||
DefaultCopiesNumbers() []uint32
|
DefaultCopiesNumbers(namespace string) []uint32
|
||||||
NewXMLDecoder(io.Reader) *xml.Decoder
|
NewXMLDecoder(io.Reader) *xml.Decoder
|
||||||
DefaultMaxAge() int
|
DefaultMaxAge() int
|
||||||
NotificatorEnabled() bool
|
NotificatorEnabled() bool
|
||||||
|
@ -74,7 +74,7 @@ func New(log *zap.Logger, obj layer.Client, notificator Notificator, cfg Config)
|
||||||
// 1) array of copies numbers sent in request's header has the highest priority.
|
// 1) array of copies numbers sent in request's header has the highest priority.
|
||||||
// 2) array of copies numbers with corresponding location constraint provided in the config file.
|
// 2) array of copies numbers with corresponding location constraint provided in the config file.
|
||||||
// 3) default copies number from the config file wrapped into array.
|
// 3) default copies number from the config file wrapped into array.
|
||||||
func (h *handler) pickCopiesNumbers(metadata map[string]string, locationConstraint string) ([]uint32, error) {
|
func (h *handler) pickCopiesNumbers(metadata map[string]string, namespace, locationConstraint string) ([]uint32, error) {
|
||||||
copiesNumbersStr, ok := metadata[layer.AttributeFrostfsCopiesNumber]
|
copiesNumbersStr, ok := metadata[layer.AttributeFrostfsCopiesNumber]
|
||||||
if ok {
|
if ok {
|
||||||
result, err := parseCopiesNumbers(copiesNumbersStr)
|
result, err := parseCopiesNumbers(copiesNumbersStr)
|
||||||
|
@ -84,12 +84,12 @@ func (h *handler) pickCopiesNumbers(metadata map[string]string, locationConstrai
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
copiesNumbers, ok := h.cfg.CopiesNumbers(locationConstraint)
|
copiesNumbers, ok := h.cfg.CopiesNumbers(namespace, locationConstraint)
|
||||||
if ok {
|
if ok {
|
||||||
return copiesNumbers, nil
|
return copiesNumbers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.cfg.DefaultCopiesNumbers(), nil
|
return h.cfg.DefaultCopiesNumbers(namespace), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCopiesNumbers(copiesNumbersStr string) ([]uint32, error) {
|
func parseCopiesNumbers(copiesNumbersStr string) ([]uint32, error) {
|
||||||
|
|
|
@ -26,7 +26,7 @@ func TestCopiesNumberPicker(t *testing.T) {
|
||||||
metadata["somekey1"] = "5, 6, 7"
|
metadata["somekey1"] = "5, 6, 7"
|
||||||
expectedCopiesNumbers := []uint32{1}
|
expectedCopiesNumbers := []uint32{1}
|
||||||
|
|
||||||
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, locationConstraint2)
|
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, "", locationConstraint2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
||||||
})
|
})
|
||||||
|
@ -35,7 +35,7 @@ func TestCopiesNumberPicker(t *testing.T) {
|
||||||
metadata["somekey2"] = "6, 7, 8"
|
metadata["somekey2"] = "6, 7, 8"
|
||||||
expectedCopiesNumbers := []uint32{2, 3, 4}
|
expectedCopiesNumbers := []uint32{2, 3, 4}
|
||||||
|
|
||||||
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, locationConstraint1)
|
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, "", locationConstraint1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
||||||
})
|
})
|
||||||
|
@ -44,7 +44,7 @@ func TestCopiesNumberPicker(t *testing.T) {
|
||||||
metadata["frostfs-copies-number"] = "7, 8, 9"
|
metadata["frostfs-copies-number"] = "7, 8, 9"
|
||||||
expectedCopiesNumbers := []uint32{7, 8, 9}
|
expectedCopiesNumbers := []uint32{7, 8, 9}
|
||||||
|
|
||||||
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, locationConstraint2)
|
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, "", locationConstraint2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
||||||
})
|
})
|
||||||
|
@ -53,7 +53,7 @@ func TestCopiesNumberPicker(t *testing.T) {
|
||||||
metadata["frostfs-copies-number"] = "7,8,9"
|
metadata["frostfs-copies-number"] = "7,8,9"
|
||||||
expectedCopiesNumbers := []uint32{7, 8, 9}
|
expectedCopiesNumbers := []uint32{7, 8, 9}
|
||||||
|
|
||||||
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, locationConstraint2)
|
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, "", locationConstraint2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
||||||
})
|
})
|
||||||
|
@ -62,7 +62,7 @@ func TestCopiesNumberPicker(t *testing.T) {
|
||||||
metadata["frostfs-copies-number"] = "11, 12, 13, "
|
metadata["frostfs-copies-number"] = "11, 12, 13, "
|
||||||
expectedCopiesNumbers := []uint32{11, 12, 13}
|
expectedCopiesNumbers := []uint32{11, 12, 13}
|
||||||
|
|
||||||
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, locationConstraint2)
|
actualCopiesNumbers, err := h.pickCopiesNumbers(metadata, "", locationConstraint2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
require.Equal(t, expectedCopiesNumbers, actualCopiesNumbers)
|
||||||
})
|
})
|
||||||
|
|
|
@ -204,7 +204,7 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
DstEncryption: dstEncryptionParams,
|
DstEncryption: dstEncryptionParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, dstBktInfo.LocationConstraint)
|
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, reqInfo.Namespace, dstBktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -55,7 +55,7 @@ func (h *handler) PutBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
NewDecoder: h.cfg.NewXMLDecoder,
|
NewDecoder: h.cfg.NewXMLDecoder,
|
||||||
}
|
}
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), bktInfo.LocationConstraint)
|
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -67,20 +67,20 @@ type configMock struct {
|
||||||
md5Enabled bool
|
md5Enabled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configMock) DefaultPlacementPolicy() netmap.PlacementPolicy {
|
func (c *configMock) DefaultPlacementPolicy(_ string) netmap.PlacementPolicy {
|
||||||
return c.defaultPolicy
|
return c.defaultPolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configMock) PlacementPolicy(string) (netmap.PlacementPolicy, bool) {
|
func (c *configMock) PlacementPolicy(_, _ string) (netmap.PlacementPolicy, bool) {
|
||||||
return netmap.PlacementPolicy{}, false
|
return netmap.PlacementPolicy{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configMock) CopiesNumbers(locationConstraint string) ([]uint32, bool) {
|
func (c *configMock) CopiesNumbers(_, locationConstraint string) ([]uint32, bool) {
|
||||||
result, ok := c.copiesNumbers[locationConstraint]
|
result, ok := c.copiesNumbers[locationConstraint]
|
||||||
return result, ok
|
return result, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *configMock) DefaultCopiesNumbers() []uint32 {
|
func (c *configMock) DefaultCopiesNumbers(_ string) []uint32 {
|
||||||
return c.defaultCopiesNumbers
|
return c.defaultCopiesNumbers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), bktInfo.LocationConstraint)
|
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
@ -229,7 +229,7 @@ func (h *handler) PutObjectRetentionHandler(w http.ResponseWriter, r *http.Reque
|
||||||
NewLock: lock,
|
NewLock: lock,
|
||||||
}
|
}
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), bktInfo.LocationConstraint)
|
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -157,7 +157,7 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
p.Header[api.ContentLanguage] = contentLanguage
|
p.Header[api.ContentLanguage] = contentLanguage
|
||||||
}
|
}
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(p.Header, bktInfo.LocationConstraint)
|
p.CopiesNumbers, err = h.pickCopiesNumbers(p.Header, reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err, additional...)
|
h.logAndSendError(w, "invalid copies number", reqInfo, err, additional...)
|
||||||
return
|
return
|
||||||
|
|
|
@ -115,7 +115,7 @@ func (h *handler) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Re
|
||||||
Configuration: conf,
|
Configuration: conf,
|
||||||
}
|
}
|
||||||
|
|
||||||
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), bktInfo.LocationConstraint)
|
p.CopiesNumbers, err = h.pickCopiesNumbers(parseMetadata(r), reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -248,7 +248,7 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ContentSHA256Hash: r.Header.Get(api.AmzContentSha256),
|
ContentSHA256Hash: r.Header.Get(api.AmzContentSha256),
|
||||||
}
|
}
|
||||||
|
|
||||||
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, bktInfo.LocationConstraint)
|
params.CopiesNumbers, err = h.pickCopiesNumbers(metadata, reqInfo.Namespace, bktInfo.LocationConstraint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
h.logAndSendError(w, "invalid copies number", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
@ -800,7 +800,7 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.setPolicy(p, createParams.LocationConstraint, policies); err != nil {
|
if err = h.setPolicy(p, reqInfo.Namespace, createParams.LocationConstraint, policies); err != nil {
|
||||||
h.logAndSendError(w, "couldn't set placement policy", reqInfo, err)
|
h.logAndSendError(w, "couldn't set placement policy", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -830,8 +830,8 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
middleware.WriteSuccessResponseHeadersOnly(w)
|
middleware.WriteSuccessResponseHeadersOnly(w)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h handler) setPolicy(prm *layer.CreateBucketParams, locationConstraint string, userPolicies []*accessbox.ContainerPolicy) error {
|
func (h handler) setPolicy(prm *layer.CreateBucketParams, namespace, locationConstraint string, userPolicies []*accessbox.ContainerPolicy) error {
|
||||||
prm.Policy = h.cfg.DefaultPlacementPolicy()
|
prm.Policy = h.cfg.DefaultPlacementPolicy(namespace)
|
||||||
prm.LocationConstraint = locationConstraint
|
prm.LocationConstraint = locationConstraint
|
||||||
|
|
||||||
if locationConstraint == "" {
|
if locationConstraint == "" {
|
||||||
|
@ -845,7 +845,7 @@ func (h handler) setPolicy(prm *layer.CreateBucketParams, locationConstraint str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if policy, ok := h.cfg.PlacementPolicy(locationConstraint); ok {
|
if policy, ok := h.cfg.PlacementPolicy(namespace, locationConstraint); ok {
|
||||||
prm.Policy = policy
|
prm.Policy = policy
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,10 +83,7 @@ type (
|
||||||
isResolveListAllow bool // True if ResolveZoneList contains allowed zones
|
isResolveListAllow bool // True if ResolveZoneList contains allowed zones
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
defaultPolicy netmap.PlacementPolicy
|
namespaces Namespaces
|
||||||
regionMap map[string]netmap.PlacementPolicy
|
|
||||||
copiesNumbers map[string][]uint32
|
|
||||||
defaultCopiesNumbers []uint32
|
|
||||||
defaultXMLNS bool
|
defaultXMLNS bool
|
||||||
bypassContentEncodingInChunks bool
|
bypassContentEncodingInChunks bool
|
||||||
clientCut bool
|
clientCut bool
|
||||||
|
@ -187,6 +184,8 @@ func newAppSettings(log *Logger, v *viper.Viper) *appSettings {
|
||||||
defaultXMLNS: v.GetBool(cfgKludgeUseDefaultXMLNS),
|
defaultXMLNS: v.GetBool(cfgKludgeUseDefaultXMLNS),
|
||||||
defaultMaxAge: fetchDefaultMaxAge(v, log.logger),
|
defaultMaxAge: fetchDefaultMaxAge(v, log.logger),
|
||||||
notificatorEnabled: v.GetBool(cfgEnableNATS),
|
notificatorEnabled: v.GetBool(cfgEnableNATS),
|
||||||
|
defaultNamespaces: fetchDefaultNamespaces(log.logger, v),
|
||||||
|
namespaceHeader: v.GetString(cfgResolveNamespaceHeader),
|
||||||
}
|
}
|
||||||
|
|
||||||
settings.resolveZoneList = v.GetStringSlice(cfgResolveBucketAllow)
|
settings.resolveZoneList = v.GetStringSlice(cfgResolveBucketAllow)
|
||||||
|
@ -200,8 +199,6 @@ func newAppSettings(log *Logger, v *viper.Viper) *appSettings {
|
||||||
settings.initPlacementPolicy(log.logger, v)
|
settings.initPlacementPolicy(log.logger, v)
|
||||||
settings.setBufferMaxSizeForPut(v.GetUint64(cfgBufferMaxSizeForPut))
|
settings.setBufferMaxSizeForPut(v.GetUint64(cfgBufferMaxSizeForPut))
|
||||||
settings.setMD5Enabled(v.GetBool(cfgMD5Enabled))
|
settings.setMD5Enabled(v.GetBool(cfgMD5Enabled))
|
||||||
settings.setNamespaceHeader(v.GetString(cfgResolveNamespaceHeader))
|
|
||||||
settings.setDefaultNamespaces(v.GetStringSlice(cfgKludgeDefaultNamespaces))
|
|
||||||
|
|
||||||
return settings
|
return settings
|
||||||
}
|
}
|
||||||
|
@ -243,46 +240,40 @@ func (s *appSettings) setBufferMaxSizeForPut(size uint64) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) initPlacementPolicy(l *zap.Logger, v *viper.Viper) {
|
func (s *appSettings) initPlacementPolicy(l *zap.Logger, v *viper.Viper) {
|
||||||
defaultPolicy := fetchDefaultPolicy(l, v)
|
nsConfig := fetchNamespacesConfig(l, v)
|
||||||
regionMap := fetchRegionMappingPolicies(l, v)
|
|
||||||
defaultCopies := fetchDefaultCopiesNumbers(l, v)
|
|
||||||
copiesNumbers := fetchCopiesNumbers(l, v)
|
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
s.defaultPolicy = defaultPolicy
|
s.namespaces = nsConfig.Namespaces
|
||||||
s.regionMap = regionMap
|
|
||||||
s.defaultCopiesNumbers = defaultCopies
|
|
||||||
s.copiesNumbers = copiesNumbers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) DefaultPlacementPolicy() netmap.PlacementPolicy {
|
func (s *appSettings) DefaultPlacementPolicy(namespace string) netmap.PlacementPolicy {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
return s.defaultPolicy
|
return s.namespaces[namespace].LocationConstraints[defaultConstraintName]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) PlacementPolicy(name string) (netmap.PlacementPolicy, bool) {
|
func (s *appSettings) PlacementPolicy(namespace, constraint string) (netmap.PlacementPolicy, bool) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
policy, ok := s.regionMap[name]
|
policy, ok := s.namespaces[namespace].LocationConstraints[constraint]
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
|
|
||||||
return policy, ok
|
return policy, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) CopiesNumbers(locationConstraint string) ([]uint32, bool) {
|
func (s *appSettings) CopiesNumbers(namespace, constraint string) ([]uint32, bool) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
copiesNumbers, ok := s.copiesNumbers[locationConstraint]
|
copiesNumbers, ok := s.namespaces[namespace].CopiesNumbers[constraint]
|
||||||
s.mu.RUnlock()
|
s.mu.RUnlock()
|
||||||
|
|
||||||
return copiesNumbers, ok
|
return copiesNumbers, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) DefaultCopiesNumbers() []uint32 {
|
func (s *appSettings) DefaultCopiesNumbers(namespace string) []uint32 {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
return s.defaultCopiesNumbers
|
return s.namespaces[namespace].CopiesNumbers[defaultConstraintName]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *appSettings) NewXMLDecoder(r io.Reader) *xml.Decoder {
|
func (s *appSettings) NewXMLDecoder(r io.Reader) *xml.Decoder {
|
||||||
|
@ -687,6 +678,7 @@ func (a *App) updateSettings() {
|
||||||
a.settings.logLevel.SetLevel(lvl)
|
a.settings.logLevel.SetLevel(lvl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a.settings.setNamespaceHeader(a.cfg.GetString(cfgResolveNamespaceHeader)) // should be updated before placement policies
|
||||||
a.settings.initPlacementPolicy(a.log, a.cfg)
|
a.settings.initPlacementPolicy(a.log, a.cfg)
|
||||||
|
|
||||||
a.settings.useDefaultXMLNamespace(a.cfg.GetBool(cfgKludgeUseDefaultXMLNS))
|
a.settings.useDefaultXMLNamespace(a.cfg.GetBool(cfgKludgeUseDefaultXMLNS))
|
||||||
|
@ -694,7 +686,6 @@ func (a *App) updateSettings() {
|
||||||
a.settings.setClientCut(a.cfg.GetBool(cfgClientCut))
|
a.settings.setClientCut(a.cfg.GetBool(cfgClientCut))
|
||||||
a.settings.setBufferMaxSizeForPut(a.cfg.GetUint64(cfgBufferMaxSizeForPut))
|
a.settings.setBufferMaxSizeForPut(a.cfg.GetUint64(cfgBufferMaxSizeForPut))
|
||||||
a.settings.setMD5Enabled(a.cfg.GetBool(cfgMD5Enabled))
|
a.settings.setMD5Enabled(a.cfg.GetBool(cfgMD5Enabled))
|
||||||
a.settings.setNamespaceHeader(a.cfg.GetString(cfgResolveNamespaceHeader))
|
|
||||||
a.settings.setDefaultNamespaces(a.cfg.GetStringSlice(cfgKludgeDefaultNamespaces))
|
a.settings.setDefaultNamespaces(a.cfg.GetStringSlice(cfgKludgeDefaultNamespaces))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,14 @@ const (
|
||||||
defaultIdleTimeout = 30 * time.Second
|
defaultIdleTimeout = 30 * time.Second
|
||||||
|
|
||||||
defaultNamespaceHeader = "X-Frostfs-Namespace"
|
defaultNamespaceHeader = "X-Frostfs-Namespace"
|
||||||
|
|
||||||
|
defaultConstraintName = "default"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultCopiesNumbers = []uint32{0}
|
var (
|
||||||
|
defaultCopiesNumbers = []uint32{0}
|
||||||
|
defaultDefaultNamespaces = []string{"", "root"}
|
||||||
|
)
|
||||||
|
|
||||||
const ( // Settings.
|
const ( // Settings.
|
||||||
// Logger.
|
// Logger.
|
||||||
|
@ -153,6 +158,9 @@ const ( // Settings.
|
||||||
cfgWebWriteTimeout = "web.write_timeout"
|
cfgWebWriteTimeout = "web.write_timeout"
|
||||||
cfgWebIdleTimeout = "web.idle_timeout"
|
cfgWebIdleTimeout = "web.idle_timeout"
|
||||||
|
|
||||||
|
// Namespaces.
|
||||||
|
cfgNamespacesConfig = "namespaces.config"
|
||||||
|
|
||||||
// Command line args.
|
// Command line args.
|
||||||
cmdHelp = "help"
|
cmdHelp = "help"
|
||||||
cmdVersion = "version"
|
cmdVersion = "version"
|
||||||
|
@ -450,6 +458,81 @@ func fetchCopiesNumbers(l *zap.Logger, v *viper.Viper) map[string][]uint32 {
|
||||||
return copiesNums
|
return copiesNums
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func fetchDefaultNamespaces(l *zap.Logger, v *viper.Viper) []string {
|
||||||
|
defaultNamespaces := v.GetStringSlice(cfgKludgeDefaultNamespaces)
|
||||||
|
if len(defaultNamespaces) == 0 {
|
||||||
|
defaultNamespaces = defaultDefaultNamespaces
|
||||||
|
l.Warn(logs.DefaultNamespacesCannotBeEmpty, zap.Strings("namespaces", defaultNamespaces))
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultNamespaces
|
||||||
|
}
|
||||||
|
|
||||||
|
func fetchNamespacesConfig(l *zap.Logger, v *viper.Viper) NamespacesConfig {
|
||||||
|
defaultNSRegionMap := fetchRegionMappingPolicies(l, v)
|
||||||
|
defaultNSRegionMap[defaultConstraintName] = fetchDefaultPolicy(l, v)
|
||||||
|
|
||||||
|
defaultNSCopiesNumbers := fetchCopiesNumbers(l, v)
|
||||||
|
defaultNSCopiesNumbers[defaultConstraintName] = fetchDefaultCopiesNumbers(l, v)
|
||||||
|
|
||||||
|
defaultNSValue := Namespace{
|
||||||
|
LocationConstraints: defaultNSRegionMap,
|
||||||
|
CopiesNumbers: defaultNSCopiesNumbers,
|
||||||
|
}
|
||||||
|
|
||||||
|
nsConfig, err := readNamespacesConfig(v.GetString(cfgNamespacesConfig))
|
||||||
|
if err != nil {
|
||||||
|
l.Warn(logs.FailedToParseNamespacesConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultNamespacesNames := fetchDefaultNamespaces(l, v)
|
||||||
|
|
||||||
|
var overrideDefaults []Namespace
|
||||||
|
for _, name := range defaultNamespacesNames {
|
||||||
|
if ns, ok := nsConfig.Namespaces[name]; ok {
|
||||||
|
overrideDefaults = append(overrideDefaults, ns)
|
||||||
|
delete(nsConfig.Namespaces, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(overrideDefaults) > 0 {
|
||||||
|
l.Warn(logs.DefaultNamespaceConfigValuesBeOverwritten)
|
||||||
|
defaultNSValue.LocationConstraints = overrideDefaults[0].LocationConstraints
|
||||||
|
defaultNSValue.CopiesNumbers = overrideDefaults[0].CopiesNumbers
|
||||||
|
if len(overrideDefaults) > 1 {
|
||||||
|
l.Warn(logs.MultipleDefaultOverridesFound, zap.String("name", overrideDefaults[0].Name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range defaultNamespacesNames {
|
||||||
|
nsConfig.Namespaces[name] = Namespace{
|
||||||
|
Name: name,
|
||||||
|
LocationConstraints: defaultNSValue.LocationConstraints,
|
||||||
|
CopiesNumbers: defaultNSValue.CopiesNumbers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNamespacesConfig(filepath string) (NamespacesConfig, error) {
|
||||||
|
if filepath == "" {
|
||||||
|
return NamespacesConfig{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := os.ReadFile(filepath)
|
||||||
|
if err != nil {
|
||||||
|
return NamespacesConfig{}, fmt.Errorf("failed to read namespace config '%s': %w", filepath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var nsConfig NamespacesConfig
|
||||||
|
if err = json.Unmarshal(data, &nsConfig); err != nil {
|
||||||
|
return NamespacesConfig{}, fmt.Errorf("failed to parse namespace config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nsConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
func fetchPeers(l *zap.Logger, v *viper.Viper) []pool.NodeParam {
|
func fetchPeers(l *zap.Logger, v *viper.Viper) []pool.NodeParam {
|
||||||
var nodes []pool.NodeParam
|
var nodes []pool.NodeParam
|
||||||
for i := 0; ; i++ {
|
for i := 0; ; i++ {
|
||||||
|
@ -564,7 +647,7 @@ func newSettings() *viper.Viper {
|
||||||
// kludge
|
// kludge
|
||||||
v.SetDefault(cfgKludgeUseDefaultXMLNS, false)
|
v.SetDefault(cfgKludgeUseDefaultXMLNS, false)
|
||||||
v.SetDefault(cfgKludgeBypassContentEncodingCheckInChunks, false)
|
v.SetDefault(cfgKludgeBypassContentEncodingCheckInChunks, false)
|
||||||
v.SetDefault(cfgKludgeDefaultNamespaces, []string{"", "root"})
|
v.SetDefault(cfgKludgeDefaultNamespaces, defaultDefaultNamespaces)
|
||||||
|
|
||||||
// web
|
// web
|
||||||
v.SetDefault(cfgWebReadHeaderTimeout, defaultReadHeaderTimeout)
|
v.SetDefault(cfgWebReadHeaderTimeout, defaultReadHeaderTimeout)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
||||||
|
@ -93,3 +94,53 @@ func TestDefaultNamespace(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNamespacesMarshaling(t *testing.T) {
|
||||||
|
dataJSON := `
|
||||||
|
{
|
||||||
|
"namespaces": {
|
||||||
|
"kapusta": {
|
||||||
|
"location_constraints": {
|
||||||
|
"default": "REP 3",
|
||||||
|
"load-1-1": "REP 1 CBF 1 SELECT 1 FROM *"
|
||||||
|
},
|
||||||
|
"copies_numbers": {
|
||||||
|
"default": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"load-1-1": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"location_constraints": {
|
||||||
|
"default": "REP 3",
|
||||||
|
"test": "{\"replicas\":[{\"count\":1,\"selector\":\"\"}],\"containerBackupFactor\":1,\"selectors\":[{\"name\":\"\",\"count\":1,\"clause\":\"CLAUSE_UNSPECIFIED\",\"attribute\":\"\",\"filter\":\"Color\"}],\"filters\":[{\"name\":\"Color\",\"key\":\"Color\",\"op\":\"EQ\",\"value\":\"Red\",\"filters\":[]}],\"unique\":false}"
|
||||||
|
},
|
||||||
|
"copies_numbers": {
|
||||||
|
"default": [
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"load-1-1": [
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var nsConfig NamespacesConfig
|
||||||
|
err := json.Unmarshal([]byte(dataJSON), &nsConfig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
data, err := json.Marshal(nsConfig)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var nsConfig2 NamespacesConfig
|
||||||
|
err = json.Unmarshal(data, &nsConfig2)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, nsConfig, nsConfig2)
|
||||||
|
}
|
||||||
|
|
79
cmd/s3-gw/namespaces.go
Normal file
79
cmd/s3-gw/namespaces.go
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NamespacesConfig struct {
|
||||||
|
Namespaces Namespaces `json:"namespaces"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Namespaces map[string]Namespace
|
||||||
|
|
||||||
|
type Namespace struct {
|
||||||
|
Name string `json:"-"`
|
||||||
|
LocationConstraints LocationConstraints `json:"location_constraints"`
|
||||||
|
CopiesNumbers map[string][]uint32 `json:"copies_numbers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LocationConstraints map[string]netmap.PlacementPolicy
|
||||||
|
|
||||||
|
func (c *Namespaces) UnmarshalJSON(data []byte) error {
|
||||||
|
namespaces := make(map[string]Namespace)
|
||||||
|
if err := json.Unmarshal(data, &namespaces); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, namespace := range namespaces {
|
||||||
|
namespace.Name = name
|
||||||
|
namespaces[name] = namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = namespaces
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *LocationConstraints) UnmarshalJSON(data []byte) error {
|
||||||
|
m := make(map[string]string)
|
||||||
|
if err := json.Unmarshal(data, &m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*c = make(LocationConstraints, len(m))
|
||||||
|
for region, policy := range m {
|
||||||
|
var pp netmap.PlacementPolicy
|
||||||
|
if err := pp.DecodeString(policy); err == nil {
|
||||||
|
(*c)[region] = pp
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := pp.UnmarshalJSON([]byte(policy)); err == nil {
|
||||||
|
(*c)[region] = pp
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("failed to parse location contraint '%s': '%s'", region, policy)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c LocationConstraints) MarshalJSON() ([]byte, error) {
|
||||||
|
m := make(map[string]string, len(c))
|
||||||
|
|
||||||
|
for region, policy := range c {
|
||||||
|
var sb strings.Builder
|
||||||
|
if err := policy.WriteStringTo(&sb); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m[region] = sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(m)
|
||||||
|
}
|
|
@ -183,3 +183,7 @@ S3_GW_WEB_IDLE_TIMEOUT=30s
|
||||||
S3_GW_FROSTFSID_ENABLED=true
|
S3_GW_FROSTFSID_ENABLED=true
|
||||||
# FrostfsID contract hash (LE) or name in NNS.
|
# FrostfsID contract hash (LE) or name in NNS.
|
||||||
S3_GW_FROSTFSID_CONTRACT=frostfsid.frostfs
|
S3_GW_FROSTFSID_CONTRACT=frostfsid.frostfs
|
||||||
|
|
||||||
|
# Namespaces configuration
|
||||||
|
S3_GW_NAMESPACES_CONFIG=namespaces.json
|
||||||
|
|
||||||
|
|
|
@ -216,3 +216,6 @@ frostfsid:
|
||||||
enabled: true
|
enabled: true
|
||||||
# FrostfsID contract hash (LE) or name in NNS.
|
# FrostfsID contract hash (LE) or name in NNS.
|
||||||
contract: frostfsid.frostfs
|
contract: frostfsid.frostfs
|
||||||
|
|
||||||
|
namespaces:
|
||||||
|
config: namespaces.json
|
||||||
|
|
|
@ -189,6 +189,7 @@ There are some custom types used for brevity:
|
||||||
| `features` | [Features configuration](#features-section) |
|
| `features` | [Features configuration](#features-section) |
|
||||||
| `web` | [Web server configuration](#web-section) |
|
| `web` | [Web server configuration](#web-section) |
|
||||||
| `frostfsid` | [FrostfsID configuration](#frostfsid-section) |
|
| `frostfsid` | [FrostfsID configuration](#frostfsid-section) |
|
||||||
|
| `namespaces` | [Namespaces configuration](#namespaces-section) |
|
||||||
|
|
||||||
### General section
|
### General section
|
||||||
|
|
||||||
|
@ -615,3 +616,41 @@ frostfsid:
|
||||||
|------------|----------|---------------|-------------------|----------------------------------------------------------------------------------------|
|
|------------|----------|---------------|-------------------|----------------------------------------------------------------------------------------|
|
||||||
| `enabled` | `bool` | no | true | Enables check that allow requests only users that is registered in FrostfsID contract. |
|
| `enabled` | `bool` | no | true | Enables check that allow requests only users that is registered in FrostfsID contract. |
|
||||||
| `contract` | `string` | no | frostfsid.frostfs | FrostfsID contract hash (LE) or name in NNS. |
|
| `contract` | `string` | no | frostfsid.frostfs | FrostfsID contract hash (LE) or name in NNS. |
|
||||||
|
|
||||||
|
# `namespaces` section
|
||||||
|
|
||||||
|
Namespaces configuration.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
namespaces:
|
||||||
|
config: namespace.json
|
||||||
|
```
|
||||||
|
|
||||||
|
| Parameter | Type | SIGHUP reload | Default value | Description |
|
||||||
|
|-----------|----------|---------------|---------------|-----------------------------------------------------|
|
||||||
|
| `config` | `string` | yes | | Path to json file with config value for namespaces. |
|
||||||
|
|
||||||
|
## `namespaces.config` subsection
|
||||||
|
|
||||||
|
Example of `namespaces.json`.
|
||||||
|
Note that config values from `namespaces.json` can override config values for default namespaces
|
||||||
|
(value for which are fetched from regular config value e.g. [placement-policy](#placement_policy-section)).
|
||||||
|
To override config values for default namespaces use namespace names that are provided in `kludge.default_namespaces`.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"namespaces": {
|
||||||
|
"namespace1": {
|
||||||
|
"location_constraints": {
|
||||||
|
"default": "REP 3",
|
||||||
|
"test": "{\"replicas\":[{\"count\":1,\"selector\":\"\"}],\"containerBackupFactor\":0,\"selectors\":[],\"filters\":[],\"unique\":false}"
|
||||||
|
},
|
||||||
|
"copies_numbers": {
|
||||||
|
"default": [ 0 ],
|
||||||
|
"test": [ 1 ]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,10 @@ const (
|
||||||
FailedToParseLocationConstraint = "failed to parse location constraint, it cannot be used" // Warn in cmd/s3-gw/app_settings.go
|
FailedToParseLocationConstraint = "failed to parse location constraint, it cannot be used" // Warn in cmd/s3-gw/app_settings.go
|
||||||
FailedToParseDefaultCopiesNumbers = "failed to parse 'default' copies numbers, default one will be used" // Warn in cmd/s3-gw/app_settings.go
|
FailedToParseDefaultCopiesNumbers = "failed to parse 'default' copies numbers, default one will be used" // Warn in cmd/s3-gw/app_settings.go
|
||||||
FailedToParseCopiesNumbers = "failed to parse copies numbers, skip" // Warn in cmd/s3-gw/app_settings.go
|
FailedToParseCopiesNumbers = "failed to parse copies numbers, skip" // Warn in cmd/s3-gw/app_settings.go
|
||||||
|
DefaultNamespacesCannotBeEmpty = "default namespaces cannot be empty, defaults will be used" // Warn in cmd/s3-gw/app_settings.go
|
||||||
|
FailedToParseNamespacesConfig = "failed to unmarshal namespaces config" // Warn in cmd/s3-gw/app_settings.go
|
||||||
|
DefaultNamespaceConfigValuesBeOverwritten = "default namespace config value be overwritten by values from 'namespaces.config'" // Warn in cmd/s3-gw/app_settings.go
|
||||||
|
MultipleDefaultOverridesFound = "multiple default overrides found, only one will be used" // Warn in cmd/s3-gw/app_settings.go
|
||||||
FailedToParseDefaultDefaultLocationConstraint = "failed to parse default 'default' location constraint" // Fatal in cmd/s3-gw/app_settings.go
|
FailedToParseDefaultDefaultLocationConstraint = "failed to parse default 'default' location constraint" // Fatal in cmd/s3-gw/app_settings.go
|
||||||
ConstraintAdded = "constraint added" // Info in ../../cmd/s3-gw/app_settings.go
|
ConstraintAdded = "constraint added" // Info in ../../cmd/s3-gw/app_settings.go
|
||||||
SkipEmptyAddress = "skip, empty address" // Warn in ../../cmd/s3-gw/app_settings.go
|
SkipEmptyAddress = "skip, empty address" // Warn in ../../cmd/s3-gw/app_settings.go
|
||||||
|
|
Loading…
Reference in a new issue