package handler import ( "encoding/xml" "errors" "fmt" "io" "strconv" "strings" "time" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "go.uber.org/zap" ) type ( handler struct { log *zap.Logger obj layer.Client notificator Notificator cfg Config } Notificator interface { SendNotifications(topics map[string]string, p *SendNotificationParams) error SendTestNotification(topic, bucketName, requestID, HostID string, now time.Time) error } // Config contains data which handler needs to keep. Config interface { DefaultPlacementPolicy() netmap.PlacementPolicy PlacementPolicy(string) (netmap.PlacementPolicy, bool) CopiesNumbers(string) ([]uint32, bool) DefaultCopiesNumbers() []uint32 NewXMLDecoder(io.Reader) *xml.Decoder DefaultMaxAge() int NotificatorEnabled() bool ResolveZoneList() []string IsResolveListAllow() bool CompleteMultipartKeepalive() time.Duration BypassContentEncodingInChunks() bool MD5Enabled() bool } ) var _ api.Handler = (*handler)(nil) // New creates new api.Handler using given logger and client. func New(log *zap.Logger, obj layer.Client, notificator Notificator, cfg Config) (api.Handler, error) { switch { case obj == nil: return nil, errors.New("empty FrostFS Object Layer") case log == nil: return nil, errors.New("empty logger") } if !cfg.NotificatorEnabled() { log.Warn(logs.NotificatorIsDisabledS3WontProduceNotificationEvents) } else if notificator == nil { return nil, errors.New("empty notificator") } return &handler{ log: log, obj: obj, cfg: cfg, notificator: notificator, }, nil } // pickCopiesNumbers chooses the return values following this logic: // 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. // 3) default copies number from the config file wrapped into array. func (h *handler) pickCopiesNumbers(metadata map[string]string, locationConstraint string) ([]uint32, error) { copiesNumbersStr, ok := metadata[layer.AttributeFrostfsCopiesNumber] if ok { result, err := parseCopiesNumbers(copiesNumbersStr) if err != nil { return nil, err } return result, nil } copiesNumbers, ok := h.cfg.CopiesNumbers(locationConstraint) if ok { return copiesNumbers, nil } return h.cfg.DefaultCopiesNumbers(), nil } func parseCopiesNumbers(copiesNumbersStr string) ([]uint32, error) { var result []uint32 copiesNumbersSplit := strings.Split(copiesNumbersStr, ",") for i := range copiesNumbersSplit { item := strings.ReplaceAll(copiesNumbersSplit[i], " ", "") if len(item) == 0 { continue } copiesNumber, err := strconv.ParseUint(item, 10, 32) if err != nil { return nil, fmt.Errorf("pasrse copies number: %w", err) } result = append(result, uint32(copiesNumber)) } return result, nil }