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-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 struct { Policy PlacementPolicy XMLDecoder XMLDecoderProvider DefaultMaxAge int NotificatorEnabled bool ResolveZoneList []string IsResolveListAllow bool // True if ResolveZoneList contains allowed zones CompleteMultipartKeepalive time.Duration } PlacementPolicy interface { DefaultPlacementPolicy() netmap.PlacementPolicy PlacementPolicy(string) (netmap.PlacementPolicy, bool) CopiesNumbers(string) ([]uint32, bool) DefaultCopiesNumbers() []uint32 } XMLDecoderProvider interface { NewCompleteMultipartDecoder(io.Reader) *xml.Decoder } ) const ( // DefaultPolicy is a default policy of placing containers in FrostFS if it's not set at the request. DefaultPolicy = "REP 3" // DefaultCopiesNumber is a default number of object copies that is enough to consider put successful if it's not set in config. DefaultCopiesNumber uint32 = 0 ) 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("notificator is disabled, s3 won't produce notification events") } 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.Policy.CopiesNumbers(locationConstraint) if ok { return copiesNumbers, nil } return h.cfg.Policy.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 }