satellite/metabase: add unique unversioned constraint for tests

While the index shouldn't be necessary as long as our implementation is
correct, it still provides some additional checks for mistakes in the
implementation.

Change-Id: I7ed71ac99a979e375d7f94c8898e6f83ac623cb6
This commit is contained in:
Egon Elbre 2023-10-16 14:20:51 +03:00 committed by Storj Robot
parent 45fdc64300
commit b2d2a8a744
3 changed files with 70 additions and 8 deletions

View File

@ -36,6 +36,8 @@ type Config struct {
// TODO remove this flag when server-side copy implementation will be finished // TODO remove this flag when server-side copy implementation will be finished
ServerSideCopy bool ServerSideCopy bool
ServerSideCopyDisabled bool ServerSideCopyDisabled bool
TestingUniqueUnversioned bool
} }
// DB implements a database for storing objects and segments. // DB implements a database for storing objects and segments.
@ -306,18 +308,18 @@ func (db *DB) TestMigrateToLatest(ctx context.Context) error {
bucket_name BYTEA NOT NULL, bucket_name BYTEA NOT NULL,
object_key BYTEA NOT NULL, object_key BYTEA NOT NULL,
stream_id BYTEA NOT NULL, stream_id BYTEA NOT NULL,
created_at TIMESTAMPTZ NOT NULL default now(), created_at TIMESTAMPTZ NOT NULL default now(),
expires_at TIMESTAMPTZ, expires_at TIMESTAMPTZ,
encrypted_metadata_nonce BYTEA default NULL, encrypted_metadata_nonce BYTEA default NULL,
encrypted_metadata BYTEA default NULL, encrypted_metadata BYTEA default NULL,
encrypted_metadata_encrypted_key BYTEA default NULL, encrypted_metadata_encrypted_key BYTEA default NULL,
encryption INT8 NOT NULL default 0, encryption INT8 NOT NULL default 0,
zombie_deletion_deadline TIMESTAMPTZ default now() + '1 day', zombie_deletion_deadline TIMESTAMPTZ default now() + '1 day',
PRIMARY KEY (project_id, bucket_name, object_key, stream_id) PRIMARY KEY (project_id, bucket_name, object_key, stream_id)
)`, )`,
` `
@ -341,6 +343,19 @@ func (db *DB) TestMigrateToLatest(ctx context.Context) error {
}, },
}, },
} }
if db.config.TestingUniqueUnversioned {
// This is only part of testing, because we do not want to affect the production performance.
migration.Steps = append(migration.Steps, &migrate.Step{
DB: &db.db,
Description: "Constraint for ensuring our metabase correctness.",
Version: 18,
Action: migrate.SQL{
`CREATE UNIQUE INDEX objects_one_unversioned_per_location ON objects (project_id, bucket_name, object_key) WHERE status IN ` + statusesUnversioned + `;`,
},
})
}
return migration.Run(ctx, db.log.Named("migrate")) return migration.Run(ctx, db.log.Named("migrate"))
} }

View File

@ -4,12 +4,15 @@
package metabase_test package metabase_test
import ( import (
"strconv"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"storj.io/common/testcontext" "storj.io/common/testcontext"
"storj.io/common/testrand"
"storj.io/private/dbutil/pgutil/pgerrcode"
"storj.io/storj/satellite/metabase" "storj.io/storj/satellite/metabase"
"storj.io/storj/satellite/metabase/metabasetest" "storj.io/storj/satellite/metabase/metabasetest"
) )
@ -22,3 +25,44 @@ func TestNow(t *testing.T) {
require.WithinDuration(t, sysnow, now, 5*time.Second) require.WithinDuration(t, sysnow, now, 5*time.Second)
}) })
} }
func TestDisallowDoubleUnversioned(t *testing.T) {
metabasetest.Run(t, func(ctx *testcontext.Context, t *testing.T, db *metabase.DB) {
// This checks that TestingUniqueUnversioned=true indeed works as needed.
objStream := metabasetest.RandObjectStream()
obj := metabasetest.CreateObject(ctx, t, db, objStream, 0)
internaldb := db.UnderlyingTagSQL()
_, err := internaldb.Exec(ctx, `
INSERT INTO objects (
project_id, bucket_name, object_key, version, stream_id,
status
) VALUES (
$1, $2, $3, $4, $5,
`+strconv.Itoa(int(metabase.CommittedUnversioned))+`
)
`, obj.ProjectID, []byte(obj.BucketName), obj.ObjectKey, obj.Version+1, testrand.UUID(),
)
require.True(t, pgerrcode.IsConstraintViolation(err))
require.ErrorContains(t, err, "objects_one_unversioned_per_location")
_, err = internaldb.Exec(ctx, `
INSERT INTO objects (
project_id, bucket_name, object_key, version, stream_id,
status
) VALUES (
$1, $2, $3, $4, $5,
`+strconv.Itoa(int(metabase.DeleteMarkerUnversioned))+`
)
`, obj.ProjectID, []byte(obj.BucketName), obj.ObjectKey, obj.Version+1, testrand.UUID(),
)
require.True(t, pgerrcode.IsConstraintViolation(err))
require.ErrorContains(t, err, "objects_one_unversioned_per_location")
metabasetest.Verify{
Objects: []metabase.RawObject{
metabase.RawObject(obj),
},
}.Check(ctx, t, db)
})
}

View File

@ -84,11 +84,14 @@ func Run(t *testing.T, fn func(ctx *testcontext.Context, t *testing.T, db *metab
) )
RunWithConfig(t, metabase.Config{ RunWithConfig(t, metabase.Config{
ApplicationName: "satellite-metabase-test", ApplicationName: "satellite-metabase-test",
MinPartSize: config.MinPartSize, MinPartSize: config.MinPartSize,
MaxNumberOfParts: config.MaxNumberOfParts, MaxNumberOfParts: config.MaxNumberOfParts,
ServerSideCopy: config.ServerSideCopy, ServerSideCopy: config.ServerSideCopy,
ServerSideCopyDisabled: config.ServerSideCopyDisabled, ServerSideCopyDisabled: config.ServerSideCopyDisabled,
TestingUniqueUnversioned: true,
}, fn) }, fn)
} }