-
Notifications
You must be signed in to change notification settings - Fork 171
ROX-17183: Migrate persistent data #1 - scanner definition #6068
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
60 commits
Select commit
Hold shift + click to select a range
7edd1aa
Add Blob store to Postgres
b77920a
Add utilities to pass transactions through contexts
7d4d493
dont forget the proto file
46c4217
Merge branch 'master' into cgorman-tx-context
c-du 9ae1ca7
Merge branch 'cgorman-tx-context' into cgorman-blob-store
c-du 92d1c0e
stage
c-du d40b4ec
stage
c-du 4384c9a
Add delete
c-du aa2197e
Merge branch 'master' into cgorman-tx-context
c-du fe09a82
Merge branch 'cong/tx' into cong/blobstore
c-du 8d2a24b
regen
c-du 8df6ccc
Merge branch 'master' into cong/blobstore
c-du 96cfee9
Address review comments
c-du 13c3efd
no blob
c-du c962711
Merge branch 'cong/blobstore' into cong/vuldef
c-du f9d3511
stage
c-du e1129f4
stage
c-du ee06bc3
Merge branch 'master' into cong/vuldef
c-du 44e45b5
stage
c-du c892183
cherry-pick
c-du c0434f2
Merge branch 'master' into cong/vuldef
c-du 007feb1
Use blobstore for scanner definitions
c-du 79142a5
Merge branch 'master' of github.com:stackrox/stackrox
c-du 864cca8
Merge branch 'master' into cong/vuldef
c-du 1e36fc3
stage
c-du 1312343
first break it
c-du 59bb9eb
Merge branch 'master' into cong/vuldef
c-du e57c2f7
security
c-du f254423
revert sneak
c-du b2f6f6b
revert sneak
c-du 3920356
fix
c-du 595d12b
stage
c-du 6b52d7c
stage
c-du 375ad88
stage
c-du 436ecc0
stage
c-du 9a50c29
stage
c-du aa19928
stage
c-du 853ed61
stage
c-du 4aebc48
stage
c-du 921254e
stage
c-du 28b45d1
stage
c-du c9c7c4e
Migrate persistent data #1 - scanner definition
c-du 5886e46
Merge branch 'master' into cong/vuldef
c-du 6ad546b
Merge branch 'cong/vuldef' into cong/vul-test
c-du c30a3b0
Merge branch 'cong/vul-test' into cong/vul-migrate
c-du bea7011
merge and style
c-du d37f371
small review first
c-du 84b4536
Merge branch 'cong/vuldef' into cong/vul-migrate
c-du 5a803d0
Resolve review comments
c-du dc5d829
Merge branch 'cong/vuldef' into cong/vul-migrate
c-du f7190ff
add migration
c-du 09b16a5
review comments
c-du c0346c0
close
c-du 60e8a15
review
c-du 0799e65
size and minor change
c-du 9c6812d
Merge branch 'cong/vuldef' into cong/vul-migrate
c-du 8fe29a9
Merge branch 'master' into cong/vuldef
c-du 89b1199
Merge branch 'cong/vuldef' into cong/vul-migrate
c-du 2bb5551
Resolve review comment
c-du e40addf
Merge branch 'master' into cong/vul-migrate
c-du File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
125 changes: 125 additions & 0 deletions
125
migrator/migrations/m_180_to_m_181_move_to_blobstore/migration.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,125 @@ | ||
| package m180tom181 | ||
|
|
||
| import ( | ||
| "context" | ||
| "database/sql" | ||
| "os" | ||
|
|
||
| timestamp "github.com/gogo/protobuf/types" | ||
| "github.com/pkg/errors" | ||
| "github.com/stackrox/rox/generated/storage" | ||
| "github.com/stackrox/rox/migrator/migrations" | ||
| "github.com/stackrox/rox/migrator/migrations/m_180_to_m_181_move_to_blobstore/schema" | ||
| "github.com/stackrox/rox/migrator/types" | ||
| "github.com/stackrox/rox/pkg/logging" | ||
| "github.com/stackrox/rox/pkg/postgres/gorm/largeobject" | ||
| "github.com/stackrox/rox/pkg/postgres/pgutils" | ||
| "github.com/stackrox/rox/pkg/sac" | ||
| "github.com/stackrox/rox/pkg/utils" | ||
| "gorm.io/gorm" | ||
| ) | ||
|
|
||
| const ( | ||
| scannerDefBlobName = "/offline/scanner/scanner-defs.zip" | ||
| ) | ||
|
|
||
| var ( | ||
| scannerDefPath = "/var/lib/stackrox/scannerdefinitions/scanner-defs.zip" | ||
| ) | ||
|
|
||
| var ( | ||
| migration = types.Migration{ | ||
| StartingSeqNum: 180, | ||
| VersionAfter: &storage.Version{SeqNum: 181}, | ||
| Run: func(databases *types.Databases) error { | ||
| err := moveToBlobs(databases.GormDB) | ||
| if err != nil { | ||
| return errors.Wrap(err, "moving persistent files to blobs") | ||
| } | ||
| return nil | ||
| }, | ||
| } | ||
| log = logging.LoggerForModule() | ||
| ) | ||
|
|
||
| func moveToBlobs(db *gorm.DB) (err error) { | ||
| ctx := sac.WithAllAccess(context.Background()) | ||
| db = db.WithContext(ctx).Table(schema.BlobsTableName) | ||
| pgutils.CreateTableFromModel(context.Background(), db, schema.CreateTableBlobsStmt) | ||
|
|
||
| tx := db.Begin(&sql.TxOptions{Isolation: sql.LevelRepeatableRead}) | ||
| if err = moveScannerDefinitions(tx); err != nil { | ||
| result := tx.Rollback() | ||
| if result.Error != nil { | ||
| log.Warnf("failed to rollback with error %v", result.Error) | ||
| } | ||
| return errors.Wrap(err, "failed to move scanner definition to blob store.") | ||
| } | ||
|
|
||
| return tx.Commit().Error | ||
| } | ||
|
|
||
| func moveScannerDefinitions(tx *gorm.DB) error { | ||
| fd, err := os.Open(scannerDefPath) | ||
| if os.IsNotExist(err) { | ||
| return nil | ||
| } | ||
| if err != nil { | ||
| return errors.Wrapf(err, "failed to open %s", scannerDefPath) | ||
| } | ||
| defer utils.IgnoreError(fd.Close) | ||
| stat, err := fd.Stat() | ||
| if err != nil { | ||
| return err | ||
| } | ||
| if stat.IsDir() { | ||
| return nil | ||
| } | ||
| modTime, err := timestamp.TimestampProto(stat.ModTime()) | ||
| if err != nil { | ||
| return errors.Wrapf(err, "invalid timestamp %v", stat.ModTime()) | ||
| } | ||
|
|
||
| // Prepare blob | ||
| blob := &storage.Blob{ | ||
| Name: scannerDefBlobName, | ||
| Length: stat.Size(), | ||
| LastUpdated: timestamp.TimestampNow(), | ||
| ModifiedTime: modTime, | ||
| } | ||
| los := largeobject.LargeObjects{DB: tx} | ||
|
|
||
| // Find the blob if it exists | ||
| var targets []schema.Blobs | ||
| result := tx.Limit(1).Where(&schema.Blobs{Name: scannerDefBlobName}).Find(&targets) | ||
| if result.Error != nil { | ||
| return result.Error | ||
| } | ||
|
|
||
| if len(targets) == 0 { | ||
| blob.Oid, err = los.Create() | ||
| if err != nil { | ||
| return errors.Wrap(err, "failed to create large object") | ||
| } | ||
| } else { | ||
| // Update | ||
| existingBlob, err := schema.ConvertBlobToProto(&targets[0]) | ||
| if err != nil { | ||
| return errors.Wrapf(err, "existing blob is not valid %+v", targets[0]) | ||
| } | ||
| blob.Oid = existingBlob.Oid | ||
| } | ||
| blobModel, err := schema.ConvertBlobFromProto(blob) | ||
| if err != nil { | ||
| return errors.Wrapf(err, "failed to convert blob to blob model %+v", blob) | ||
| } | ||
| tx = tx.FirstOrCreate(blobModel) | ||
| if tx.Error != nil { | ||
| return errors.Wrap(tx.Error, "failed to create blob metadata") | ||
| } | ||
| return los.Upsert(blob.Oid, fd) | ||
| } | ||
|
|
||
| func init() { | ||
| migrations.MustRegisterMigration(migration) | ||
| } | ||
98 changes: 98 additions & 0 deletions
98
migrator/migrations/m_180_to_m_181_move_to_blobstore/migration_test.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| //go:build sql_integration | ||
|
|
||
| package m180tom181 | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "crypto/rand" | ||
| "io" | ||
| "os" | ||
| "testing" | ||
|
|
||
| "github.com/stackrox/rox/migrator/migrations/m_180_to_m_181_move_to_blobstore/schema" | ||
| pghelper "github.com/stackrox/rox/migrator/migrations/postgreshelper" | ||
| "github.com/stackrox/rox/pkg/postgres/gorm/largeobject" | ||
| "github.com/stackrox/rox/pkg/postgres/pgutils" | ||
| "github.com/stretchr/testify/suite" | ||
| ) | ||
|
|
||
| type blobMigrationTestSuite struct { | ||
| suite.Suite | ||
|
|
||
| db *pghelper.TestPostgres | ||
| } | ||
|
|
||
| func TestMigration(t *testing.T) { | ||
| suite.Run(t, new(blobMigrationTestSuite)) | ||
| } | ||
|
|
||
| func (s *blobMigrationTestSuite) SetupTest() { | ||
| s.db = pghelper.ForT(s.T(), true) | ||
| } | ||
|
|
||
| func (s *blobMigrationTestSuite) TearDownTest() { | ||
| s.db.Teardown(s.T()) | ||
| } | ||
|
|
||
| func (s *blobMigrationTestSuite) TestMigration() { | ||
| // Nothing to migrate | ||
| s.Require().NoError(moveToBlobs(s.db.GetGormDB())) | ||
|
|
||
| // Prepare persistent file | ||
| size := 90000 | ||
| randomData := make([]byte, size) | ||
| _, err := rand.Read(randomData) | ||
| s.Require().NoError(err) | ||
| reader := bytes.NewBuffer(randomData) | ||
|
|
||
| file, err := os.CreateTemp("", "move-blob") | ||
| s.Require().NoError(err) | ||
| defer func() { | ||
| s.NoError(file.Close()) | ||
| s.NoError(os.Remove(file.Name())) | ||
| }() | ||
| scannerDefPath = file.Name() | ||
| n, err := io.Copy(file, reader) | ||
| s.Require().NoError(err) | ||
|
|
||
| s.Require().EqualValues(size, n) | ||
| fileInfo, err := file.Stat() | ||
| s.Require().NoError(err) | ||
|
|
||
| // Migrate | ||
| s.Require().NoError(moveToBlobs(s.db.GetGormDB())) | ||
|
|
||
| // Verify Blob | ||
| blobModel := &schema.Blobs{Name: scannerDefBlobName} | ||
| s.Require().NoError(s.db.GetGormDB().First(&blobModel).Error) | ||
|
|
||
| blob, err := schema.ConvertBlobToProto(blobModel) | ||
| s.Require().NoError(err) | ||
| s.Equal(scannerDefBlobName, blob.GetName()) | ||
| s.EqualValues(size, blob.GetLength()) | ||
|
|
||
| modTime := pgutils.NilOrTime(blob.GetModifiedTime()) | ||
| s.Equal(fileInfo.ModTime().UTC(), modTime.UTC()) | ||
|
|
||
| // Verify Data | ||
| buf := bytes.NewBuffer([]byte{}) | ||
|
|
||
| tx := s.db.GetGormDB().Begin() | ||
| s.Require().NoError(err) | ||
| los := &largeobject.LargeObjects{DB: tx} | ||
| s.Require().NoError(los.Get(blob.Oid, buf)) | ||
| s.Equal(len(randomData), buf.Len()) | ||
| s.Equal(randomData, buf.Bytes()) | ||
| s.NoError(tx.Commit().Error) | ||
|
|
||
| // Test re-entry | ||
| s.Require().NoError(moveToBlobs(s.db.GetGormDB())) | ||
| buf.Reset() | ||
| tx = s.db.GetGormDB().Begin() | ||
| los = &largeobject.LargeObjects{DB: tx} | ||
| s.Require().NoError(err) | ||
| s.Require().NoError(los.Get(blob.Oid, buf)) | ||
| s.Equal(len(randomData), buf.Len()) | ||
| s.Equal(randomData, buf.Bytes()) | ||
| s.NoError(tx.Commit().Error) | ||
| } |
35 changes: 35 additions & 0 deletions
35
migrator/migrations/m_180_to_m_181_move_to_blobstore/schema/blobs.go
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
28 changes: 28 additions & 0 deletions
28
migrator/migrations/m_180_to_m_181_move_to_blobstore/schema/convert_blobs.go
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
20 changes: 20 additions & 0 deletions
20
migrator/migrations/m_180_to_m_181_move_to_blobstore/schema/convert_blobs_test.go
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
6 changes: 6 additions & 0 deletions
6
migrator/migrations/m_180_to_m_181_move_to_blobstore/schema/gen.go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package schema | ||
|
|
||
| // TODO(ROX-17180): Remove this auto-generation at the beginning of 4.2 or at least | ||
| // before we made schema change to Blob store after first release. | ||
|
|
||
| //go:generate pg-schema-migration-helper --type=storage.Blob |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to check and distinguish in this case? Migrations only run once and we can be guaranteed that no object will exist in the database because all of this is wrapped in a txn. I think it's a fair assumption that this does not exist and that we will always need to add and create
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could happen when the first migration fails maybe at migration 182 and then we fixed the problem somehow and customer tries again. All migrations should be re-entriable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah good point, do you think it'd be generally worthwhile to write out the version after each sequence going forward? Not in this PR of course