From 72013e08ec33ee9209919704bfd0cf60c2d5d244 Mon Sep 17 00:00:00 2001
From: Angira Kekteeva <kira@nspcc.ru>
Date: Tue, 19 Jul 2022 18:58:18 +0400
Subject: [PATCH] [#569] Refactor versioning in tree service

Signed-off-by: Angira Kekteeva <kira@nspcc.ru>
---
 api/data/info.go                | 19 +++++++++++++++++--
 api/handler/delete.go           |  2 +-
 api/handler/multipart_upload.go |  2 +-
 api/handler/put.go              |  6 +++---
 api/handler/tagging.go          |  2 +-
 api/handler/versioning.go       | 19 +++++++++----------
 api/layer/layer.go              |  6 +++---
 api/layer/object.go             | 18 ++++++------------
 api/layer/system_object.go      |  3 +--
 internal/neofs/tree.go          | 15 ++++++---------
 10 files changed, 48 insertions(+), 44 deletions(-)

diff --git a/api/data/info.go b/api/data/info.go
index e4ed76c2..86c5329e 100644
--- a/api/data/info.go
+++ b/api/data/info.go
@@ -13,6 +13,10 @@ const (
 	bktSettingsObject                  = ".s3-settings"
 	bktCORSConfigurationObject         = ".s3-cors"
 	bktNotificationConfigurationObject = ".s3-notifications"
+
+	VerUnversioned = "Unversioned"
+	VerEnabled     = "Enabled"
+	VerSuspended   = "Suspended"
 )
 
 type (
@@ -45,8 +49,7 @@ type (
 
 	// BucketSettings stores settings such as versioning.
 	BucketSettings struct {
-		IsNoneStatus      bool
-		VersioningEnabled bool                     `json:"versioning_enabled"`
+		Versioning        string                   `json:"versioning"`
 		LockConfiguration *ObjectLockConfiguration `json:"lock_configuration"`
 	}
 
@@ -91,3 +94,15 @@ func (o *ObjectInfo) Address() oid.Address {
 
 	return addr
 }
+
+func (b BucketSettings) Unversioned() bool {
+	return b.Versioning == VerUnversioned
+}
+
+func (b BucketSettings) VersioningEnabled() bool {
+	return b.Versioning == VerEnabled
+}
+
+func (b BucketSettings) VersioningSuspended() bool {
+	return b.Versioning == VerSuspended
+}
diff --git a/api/handler/delete.go b/api/handler/delete.go
index 4a022871..f1bcec5c 100644
--- a/api/handler/delete.go
+++ b/api/handler/delete.go
@@ -98,7 +98,7 @@ func (h *handler) DeleteObjectHandler(w http.ResponseWriter, r *http.Request) {
 
 	var m *SendNotificationParams
 
-	if bktSettings.VersioningEnabled && len(versionID) == 0 {
+	if bktSettings.VersioningEnabled() && len(versionID) == 0 {
 		m = &SendNotificationParams{
 			Event: EventObjectRemovedDeleteMarkerCreated,
 			ObjInfo: &data.ObjectInfo{
diff --git a/api/handler/multipart_upload.go b/api/handler/multipart_upload.go
index 3b884112..6963a9c4 100644
--- a/api/handler/multipart_upload.go
+++ b/api/handler/multipart_upload.go
@@ -418,7 +418,7 @@ func (h *handler) CompleteMultipartUploadHandler(w http.ResponseWriter, r *http.
 		Key:    objInfo.Name,
 	}
 
-	if bktSettings.VersioningEnabled {
+	if bktSettings.VersioningEnabled() {
 		w.Header().Set(api.AmzVersionID, objInfo.Version())
 	}
 
diff --git a/api/handler/put.go b/api/handler/put.go
index e11eed7f..94a68e60 100644
--- a/api/handler/put.go
+++ b/api/handler/put.go
@@ -277,7 +277,7 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if settings.VersioningEnabled {
+	if settings.VersioningEnabled() {
 		w.Header().Set(api.AmzVersionID, info.Version())
 	}
 
@@ -407,7 +407,7 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) {
 
 	if settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo); err != nil {
 		h.log.Warn("couldn't get bucket versioning", zap.String("bucket name", reqInfo.BucketName), zap.Error(err))
-	} else if settings.VersioningEnabled {
+	} else if settings.VersioningEnabled() {
 		w.Header().Set(api.AmzVersionID, info.Version())
 	}
 
@@ -659,7 +659,7 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
 	if p.ObjectLockEnabled {
 		sp := &layer.PutSettingsParams{
 			BktInfo:  bktInfo,
-			Settings: &data.BucketSettings{VersioningEnabled: true},
+			Settings: &data.BucketSettings{Versioning: data.VerEnabled},
 		}
 		if err = h.obj.PutBucketSettings(r.Context(), sp); err != nil {
 			h.logAndSendError(w, "couldn't enable bucket versioning", reqInfo, err,
diff --git a/api/handler/tagging.go b/api/handler/tagging.go
index 7049b1ce..23e2724d 100644
--- a/api/handler/tagging.go
+++ b/api/handler/tagging.go
@@ -91,7 +91,7 @@ func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request
 		return
 	}
 
-	if settings.VersioningEnabled {
+	if settings.VersioningEnabled() {
 		w.Header().Set(api.AmzVersionID, versionID)
 	}
 	if err = api.EncodeToResponse(w, encodeTagging(tagSet)); err != nil {
diff --git a/api/handler/versioning.go b/api/handler/versioning.go
index 70e41fab..e04af6f7 100644
--- a/api/handler/versioning.go
+++ b/api/handler/versioning.go
@@ -33,15 +33,18 @@ func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Requ
 		return
 	}
 
-	settings.VersioningEnabled = configuration.Status == "Enabled"
-	settings.IsNoneStatus = false
+	if configuration.Status != data.VerEnabled && configuration.Status != data.VerSuspended {
+		h.logAndSendError(w, "invalid versioning configuration", reqInfo, err)
+		return
+	}
+	settings.Versioning = configuration.Status
 
 	p := &layer.PutSettingsParams{
 		BktInfo:  bktInfo,
 		Settings: settings,
 	}
 
-	if !p.Settings.VersioningEnabled && bktInfo.ObjectLockEnabled {
+	if p.Settings.VersioningSuspended() && bktInfo.ObjectLockEnabled {
 		h.logAndSendError(w, "couldn't suspend bucket versioning", reqInfo, fmt.Errorf("object lock is enabled"))
 		return
 	}
@@ -77,13 +80,9 @@ func (h *handler) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Requ
 
 func formVersioningConfiguration(settings *data.BucketSettings) *VersioningConfiguration {
 	res := &VersioningConfiguration{}
-	if settings.IsNoneStatus {
-		return res
-	}
-	if settings.VersioningEnabled {
-		res.Status = "Enabled"
-	} else {
-		res.Status = "Suspended"
+	if !settings.Unversioned() {
+		res.Status = settings.Versioning
 	}
+
 	return res
 }
diff --git a/api/layer/layer.go b/api/layer/layer.go
index d6eb70ac..e61c7893 100644
--- a/api/layer/layer.go
+++ b/api/layer/layer.go
@@ -475,7 +475,7 @@ func getRandomOID() (oid.ID, error) {
 }
 
 func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, settings *data.BucketSettings, obj *VersionedObject) *VersionedObject {
-	if len(obj.VersionID) != 0 || settings.IsNoneStatus {
+	if len(obj.VersionID) != 0 || settings.Unversioned() {
 		var nodeVersion *data.NodeVersion
 		if nodeVersion, obj.Error = n.getNodeVersionToDelete(ctx, bkt, obj); obj.Error != nil {
 			return obj
@@ -492,7 +492,7 @@ func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, settings
 
 	var newVersion *data.NodeVersion
 
-	if !settings.VersioningEnabled {
+	if settings.VersioningSuspended() {
 		obj.VersionID = UnversionedObjectVersionID
 
 		var nodeVersion *data.NodeVersion
@@ -526,7 +526,7 @@ func (n *layer) deleteObject(ctx context.Context, bkt *data.BucketInfo, settings
 			Created: time.Now(),
 			Owner:   n.Owner(ctx),
 		},
-		IsUnversioned: !settings.VersioningEnabled,
+		IsUnversioned: settings.VersioningSuspended(),
 	}
 
 	if obj.Error = n.treeService.AddVersion(ctx, bkt.CID, newVersion); obj.Error != nil {
diff --git a/api/layer/object.go b/api/layer/object.go
index d230624e..a420cc8c 100644
--- a/api/layer/object.go
+++ b/api/layer/object.go
@@ -149,10 +149,14 @@ func MimeByFileName(name string) string {
 func (n *layer) PutObject(ctx context.Context, p *PutObjectParams) (*data.ObjectInfo, error) {
 	own := n.Owner(ctx)
 
-	versioningEnabled := n.isVersioningEnabled(ctx, p.BktInfo)
+	bktSettings, err := n.GetBucketSettings(ctx, p.BktInfo)
+	if err != nil {
+		return nil, fmt.Errorf("couldn't get versioning settings object: %w", err)
+	}
+
 	newVersion := &data.NodeVersion{
 		BaseNodeVersion: data.BaseNodeVersion{FilePath: p.Object},
-		IsUnversioned:   !versioningEnabled,
+		IsUnversioned:   !bktSettings.VersioningEnabled(),
 	}
 
 	r := p.Reader
@@ -653,16 +657,6 @@ func triageExtendedObjects(allObjects []*data.ExtendedObjectInfo) (prefixes []st
 	return
 }
 
-func (n *layer) isVersioningEnabled(ctx context.Context, bktInfo *data.BucketInfo) bool {
-	settings, err := n.GetBucketSettings(ctx, bktInfo)
-	if err != nil {
-		n.log.Warn("couldn't get versioning settings object", zap.Error(err))
-		return false
-	}
-
-	return settings.VersioningEnabled
-}
-
 func (n *layer) objectInfoFromObjectsCacheOrNeoFS(ctx context.Context, bktInfo *data.BucketInfo, obj oid.ID, prefix, delimiter string) *data.ObjectInfo {
 	if objInfo := n.objCache.GetObject(newAddress(bktInfo.CID, obj)); objInfo != nil {
 		return objInfo
diff --git a/api/layer/system_object.go b/api/layer/system_object.go
index b8f6d25b..367835b7 100644
--- a/api/layer/system_object.go
+++ b/api/layer/system_object.go
@@ -189,8 +189,7 @@ func (n *layer) GetBucketSettings(ctx context.Context, bktInfo *data.BucketInfo)
 		if !errorsStd.Is(err, ErrNodeNotFound) {
 			return nil, err
 		}
-		settings = &data.BucketSettings{}
-		settings.IsNoneStatus = true
+		settings = &data.BucketSettings{Versioning: data.VerUnversioned}
 	}
 
 	if err = n.systemCache.PutSettings(systemKey, settings); err != nil {
diff --git a/internal/neofs/tree.go b/internal/neofs/tree.go
index ca221881..ad7cfd2b 100644
--- a/internal/neofs/tree.go
+++ b/internal/neofs/tree.go
@@ -47,7 +47,7 @@ type (
 )
 
 const (
-	versioningEnabledKV = "VersioningEnabled"
+	versioningKV        = "Versioning"
 	lockConfigurationKV = "LockConfiguration"
 	oidKV               = "OID"
 	fileNameKV          = "FileName"
@@ -260,18 +260,15 @@ func newPartInfo(node NodeResponse) (*data.PartInfo, error) {
 }
 
 func (c *TreeClient) GetSettingsNode(ctx context.Context, cnrID cid.ID) (*data.BucketSettings, error) {
-	keysToReturn := []string{versioningEnabledKV, lockConfigurationKV}
+	keysToReturn := []string{versioningKV, lockConfigurationKV}
 	node, err := c.getSystemNode(ctx, cnrID, []string{settingsFileName}, keysToReturn)
 	if err != nil {
 		return nil, fmt.Errorf("couldn't get node: %w", err)
 	}
 
-	settings := &data.BucketSettings{}
-
-	if versioningEnabledValue, ok := node.Get(versioningEnabledKV); ok {
-		if settings.VersioningEnabled, err = strconv.ParseBool(versioningEnabledValue); err != nil {
-			return nil, fmt.Errorf("settings node: invalid versioning: %w", err)
-		}
+	settings := &data.BucketSettings{Versioning: data.VerUnversioned}
+	if versioningValue, ok := node.Get(versioningKV); ok {
+		settings.Versioning = versioningValue
 	}
 
 	if lockConfigurationValue, ok := node.Get(lockConfigurationKV); ok {
@@ -1158,7 +1155,7 @@ func metaFromSettings(settings *data.BucketSettings) map[string]string {
 	results := make(map[string]string, 3)
 
 	results[fileNameKV] = settingsFileName
-	results[versioningEnabledKV] = strconv.FormatBool(settings.VersioningEnabled)
+	results[versioningKV] = settings.Versioning
 	results[lockConfigurationKV] = encodeLockConfiguration(settings.LockConfiguration)
 
 	return results