From c50a16a5e3fdb8fd8054e97d92a9a57f7cd4a953 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Mon, 9 Aug 2021 17:29:44 +0300 Subject: [PATCH] [#122] Add enabling versioning New handlers: PutBucketVersioning, GetBucketVersioning Signed-off-by: Denis Kirillov --- api/handler/list.go | 21 -------- api/handler/not_support.go | 4 -- api/handler/response.go | 7 +++ api/handler/versioning.go | 64 +++++++++++++++++++++++ api/layer/layer.go | 102 +++++++++++++++++++++++++++++++++---- 5 files changed, 163 insertions(+), 35 deletions(-) create mode 100644 api/handler/versioning.go diff --git a/api/handler/list.go b/api/handler/list.go index f226d5d..3fe83a0 100644 --- a/api/handler/list.go +++ b/api/handler/list.go @@ -9,13 +9,6 @@ import ( "github.com/nspcc-dev/neofs-s3-gw/api" ) -// VersioningConfiguration contains VersioningConfiguration XML representation. -type VersioningConfiguration struct { - XMLName xml.Name `xml:"VersioningConfiguration"` - Text string `xml:",chardata"` - Xmlns string `xml:"xmlns,attr"` -} - // ListMultipartUploadsResult contains ListMultipartUploadsResult XML representation. type ListMultipartUploadsResult struct { XMLName xml.Name `xml:"ListMultipartUploadsResult"` @@ -62,20 +55,6 @@ func (h *handler) ListBucketsHandler(w http.ResponseWriter, r *http.Request) { } } -// GetBucketVersioningHandler implements bucket versioning getter handler. -func (h *handler) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) { - var ( - reqInfo = api.GetReqInfo(r.Context()) - res = new(VersioningConfiguration) - ) - - res.Xmlns = "http://s3.amazonaws.com/doc/2006-03-01/" - - if err := api.EncodeToResponse(w, res); err != nil { - h.logAndSendError(w, "something went wrong", reqInfo, err) - } -} - // ListMultipartUploadsHandler implements multipart uploads listing handler. func (h *handler) ListMultipartUploadsHandler(w http.ResponseWriter, r *http.Request) { var ( diff --git a/api/handler/not_support.go b/api/handler/not_support.go index 6556eaf..019fe5b 100644 --- a/api/handler/not_support.go +++ b/api/handler/not_support.go @@ -23,10 +23,6 @@ func (h *handler) PutBucketTaggingHandler(w http.ResponseWriter, r *http.Request h.logAndSendError(w, "not supported", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported)) } -func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) { - h.logAndSendError(w, "not supported", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported)) -} - func (h *handler) PutBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { h.logAndSendError(w, "not supported", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported)) } diff --git a/api/handler/response.go b/api/handler/response.go index 90f1fda..7d34489 100644 --- a/api/handler/response.go +++ b/api/handler/response.go @@ -164,6 +164,13 @@ type ListObjectsVersionsResponse struct { CommonPrefixes []CommonPrefix `xml:"CommonPrefixes"` } +// VersioningConfiguration contains VersioningConfiguration XML representation. +type VersioningConfiguration struct { + XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ VersioningConfiguration"` + Status string `xml:"Status"` + MfaDelete string `xml:"MfaDelete,omitempty"` +} + // MarshalXML - StringMap marshals into XML. func (s StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { tokens := []xml.Token{start} diff --git a/api/handler/versioning.go b/api/handler/versioning.go new file mode 100644 index 0000000..3fc7068 --- /dev/null +++ b/api/handler/versioning.go @@ -0,0 +1,64 @@ +package handler + +import ( + "encoding/xml" + "net/http" + "strconv" + + "github.com/nspcc-dev/neofs-s3-gw/api" + "github.com/nspcc-dev/neofs-s3-gw/api/layer" + "go.uber.org/zap" +) + +func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) { + reqInfo := api.GetReqInfo(r.Context()) + + configuration := new(VersioningConfiguration) + if err := xml.NewDecoder(r.Body).Decode(configuration); err != nil { + h.logAndSendError(w, "couldn't decode versioning configuration", reqInfo, api.GetAPIError(api.ErrIllegalVersioningConfigurationException)) + return + } + + p := &layer.PutVersioningParams{ + Bucket: reqInfo.BucketName, + VersioningEnabled: configuration.Status == "Enabled", + } + + if _, err := h.obj.PutBucketVersioning(r.Context(), p); err != nil { + h.logAndSendError(w, "couldn't put update versioning settings", reqInfo, err) + } +} + +// GetBucketVersioningHandler implements bucket versioning getter handler. +func (h *handler) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) { + reqInfo := api.GetReqInfo(r.Context()) + + objInfo, err := h.obj.GetBucketVersioning(r.Context(), reqInfo.BucketName) + if err != nil { + h.log.Warn("couldn't get version settings object: default version settings will be used", + zap.String("request_id", reqInfo.RequestID), + zap.String("method", reqInfo.API), + zap.String("object_name", reqInfo.ObjectName), + zap.Error(err)) + } + + if err = api.EncodeToResponse(w, formVersioningConfiguration(objInfo)); err != nil { + h.logAndSendError(w, "something went wrong", reqInfo, err) + } +} + +func formVersioningConfiguration(inf *layer.ObjectInfo) *VersioningConfiguration { + res := &VersioningConfiguration{Status: "Suspended"} + + if inf == nil { + return res + } + + enabled, ok := inf.Headers["S3-Settings-Versioning-enabled"] + if ok { + if parsed, err := strconv.ParseBool(enabled); err == nil && parsed { + res.Status = "Enabled" + } + } + return res +} diff --git a/api/layer/layer.go b/api/layer/layer.go index fd7d058..2ae3cc5 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -7,6 +7,7 @@ import ( "io" "net/url" "sort" + "strconv" "time" "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" @@ -71,6 +72,12 @@ type ( Header map[string]string } + // PutVersioningParams stores object copy request parameters. + PutVersioningParams struct { + Bucket string + VersioningEnabled bool + } + // CopyObjectParams stores object copy request parameters. CopyObjectParams struct { SrcBucket string @@ -117,6 +124,9 @@ type ( Client interface { NeoFS + PutBucketVersioning(ctx context.Context, p *PutVersioningParams) (*ObjectInfo, error) + GetBucketVersioning(ctx context.Context, name string) (*ObjectInfo, error) + ListBuckets(ctx context.Context) ([]*BucketInfo, error) GetBucketInfo(ctx context.Context, name string) (*BucketInfo, error) GetBucketACL(ctx context.Context, name string) (*BucketACL, error) @@ -142,6 +152,7 @@ type ( const ( unversionedObjectVersionID = "null" + bktVersionSettingsObject = ".s3-versioning-settings" ) // NewLayer creates instance of layer. It checks credentials @@ -303,17 +314,18 @@ func (n *layer) checkObject(ctx context.Context, cid *cid.ID, filename string) e // GetObjectInfo returns meta information about the object. func (n *layer) GetObjectInfo(ctx context.Context, bucketName, filename string) (*ObjectInfo, error) { - var ( - err error - oid *object.ID - bkt *BucketInfo - meta *object.Object - ) - - if bkt, err = n.GetBucketInfo(ctx, bucketName); err != nil { + bkt, err := n.GetBucketInfo(ctx, bucketName) + if err != nil { n.log.Error("could not fetch bucket info", zap.Error(err)) return nil, err - } else if oid, err = n.objectFindID(ctx, &findParams{cid: bkt.CID, val: filename}); err != nil { + } + + return n.getObjectInfo(ctx, bkt, filename) +} + +func (n *layer) getObjectInfo(ctx context.Context, bkt *BucketInfo, objectName string) (*ObjectInfo, error) { + oid, err := n.objectFindID(ctx, &findParams{cid: bkt.CID, val: objectName}) + if err != nil { n.log.Error("could not find object id", zap.Error(err)) return nil, err } @@ -325,7 +337,7 @@ func (n *layer) GetObjectInfo(ctx context.Context, bucketName, filename string) /* todo: now we get an address via request to NeoFS and try to find the object with the address in cache but it will be resolved after implementation of local cache with nicenames and address of objects for get/head requests */ - meta = n.objCache.Get(addr) + meta := n.objCache.Get(addr) if meta == nil { meta, err = n.objectHead(ctx, addr) if err != nil { @@ -508,3 +520,73 @@ func (n *layer) ListObjectVersions(ctx context.Context, p *ListObjectVersionsPar } return &res, nil } + +func (n *layer) PutBucketVersioning(ctx context.Context, p *PutVersioningParams) (*ObjectInfo, error) { + bucketInfo, err := n.GetBucketInfo(ctx, p.Bucket) + if err != nil { + return nil, err + } + + objectInfo, err := n.getObjectInfo(ctx, bucketInfo, bktVersionSettingsObject) + if err != nil { + n.log.Warn("couldn't get bucket version settings object, new one will be created", + zap.String("bucket_name", bucketInfo.Name), + zap.Stringer("cid", bucketInfo.CID), + zap.String("object_name", bktVersionSettingsObject), + zap.Error(err)) + } + + attributes := make([]*object.Attribute, 0, 3) + + filename := object.NewAttribute() + filename.SetKey(object.AttributeFileName) + filename.SetValue(bktVersionSettingsObject) + + createdAt := object.NewAttribute() + createdAt.SetKey(object.AttributeTimestamp) + createdAt.SetValue(strconv.FormatInt(time.Now().UTC().Unix(), 10)) + + versioningIgnore := object.NewAttribute() + versioningIgnore.SetKey("S3-Versions-ignore") + versioningIgnore.SetValue(strconv.FormatBool(true)) + + settingsVersioningEnabled := object.NewAttribute() + settingsVersioningEnabled.SetKey("S3-Settings-Versioning-enabled") + settingsVersioningEnabled.SetValue(strconv.FormatBool(p.VersioningEnabled)) + + attributes = append(attributes, filename, createdAt, versioningIgnore, settingsVersioningEnabled) + + raw := object.NewRaw() + raw.SetOwnerID(bucketInfo.Owner) + raw.SetContainerID(bucketInfo.CID) + raw.SetAttributes(attributes...) + + ops := new(client.PutObjectParams).WithObject(raw.Object()) + oid, err := n.pool.PutObject(ctx, ops, n.BearerOpt(ctx)) + if err != nil { + return nil, err + } + + addr := object.NewAddress() + addr.SetObjectID(oid) + addr.SetContainerID(bucketInfo.CID) + meta, err := n.objectHead(ctx, addr) + if err != nil { + return nil, err + } + + if objectInfo != nil { + addr := object.NewAddress() + addr.SetObjectID(objectInfo.ID()) + addr.SetContainerID(bucketInfo.CID) + if err = n.objectDelete(ctx, addr); err != nil { + return nil, err + } + } + + return objectInfoFromMeta(bucketInfo, meta, "", ""), nil +} + +func (n *layer) GetBucketVersioning(ctx context.Context, bucketName string) (*ObjectInfo, error) { + return n.GetObjectInfo(ctx, bucketName, bktVersionSettingsObject) +}