mirror of
https://github.com/goharbor/harbor.git
synced 2025-02-19 05:11:30 +01:00
fixes 13976 (#15047)
Fixes #13976 for the quota exceed case, gc will print the untagged blobs for dry-run Signed-off-by: Wang Yan <wangyan@vmware.com>
This commit is contained in:
parent
0867a6bfd6
commit
8a0cd99473
@ -189,14 +189,15 @@ func (gc *GarbageCollector) mark(ctx job.Context) error {
|
|||||||
|
|
||||||
// get gc candidates, and set the repositories.
|
// get gc candidates, and set the repositories.
|
||||||
// AS the reference count is calculated by joining table project_blob and blob, here needs to call removeUntaggedBlobs to remove these non-used blobs from table project_blob firstly.
|
// AS the reference count is calculated by joining table project_blob and blob, here needs to call removeUntaggedBlobs to remove these non-used blobs from table project_blob firstly.
|
||||||
if !gc.dryRun {
|
untaggedBlobs := gc.markOrSweepUntaggedBlobs(ctx)
|
||||||
gc.removeUntaggedBlobs(ctx)
|
|
||||||
}
|
|
||||||
blobs, err := gc.blobMgr.UselessBlobs(ctx.SystemContext(), gc.timeWindowHours)
|
blobs, err := gc.blobMgr.UselessBlobs(ctx.SystemContext(), gc.timeWindowHours)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gc.logger.Errorf("failed to get gc candidate: %v", err)
|
gc.logger.Errorf("failed to get gc candidate: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if len(untaggedBlobs) != 0 {
|
||||||
|
blobs = append(blobs, untaggedBlobs...)
|
||||||
|
}
|
||||||
if len(blobs) == 0 {
|
if len(blobs) == 0 {
|
||||||
gc.logger.Info("no need to execute GC as there is no non referenced artifacts.")
|
gc.logger.Info("no need to execute GC as there is no non referenced artifacts.")
|
||||||
return nil
|
return nil
|
||||||
@ -415,8 +416,11 @@ func (gc *GarbageCollector) deletedArt(ctx job.Context) (map[string][]model.Arti
|
|||||||
return artMap, nil
|
return artMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// clean the untagged blobs in each project, these blobs are not referenced by any manifest and will be cleaned by GC
|
// mark or sweep the untagged blobs in each project, these blobs are not referenced by any manifest and will be cleaned by GC
|
||||||
func (gc *GarbageCollector) removeUntaggedBlobs(ctx job.Context) {
|
// * dry-run, find and return the untagged blobs
|
||||||
|
// * non dry-run, remove the reference of the untagged blobs
|
||||||
|
func (gc *GarbageCollector) markOrSweepUntaggedBlobs(ctx job.Context) []*blob_models.Blob {
|
||||||
|
var untaggedBlobs []*blob_models.Blob
|
||||||
for result := range project.ListAll(ctx.SystemContext(), 50, nil, project.Metadata(false)) {
|
for result := range project.ListAll(ctx.SystemContext(), 50, nil, project.Metadata(false)) {
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
gc.logger.Errorf("remove untagged blobs for all projects got error: %v", result.Error)
|
gc.logger.Errorf("remove untagged blobs for all projects got error: %v", result.Error)
|
||||||
@ -451,9 +455,18 @@ func (gc *GarbageCollector) removeUntaggedBlobs(ctx job.Context) {
|
|||||||
gc.logger.Errorf("failed to get blobs of project, %v", err)
|
gc.logger.Errorf("failed to get blobs of project, %v", err)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if err := gc.blobMgr.CleanupAssociationsForProject(ctx.SystemContext(), p.ProjectID, blobs); err != nil {
|
if gc.dryRun {
|
||||||
gc.logger.Errorf("failed to clean untagged blobs of project, %v", err)
|
unassociated, err := gc.blobMgr.FindBlobsShouldUnassociatedWithProject(ctx.SystemContext(), p.ProjectID, blobs)
|
||||||
break
|
if err != nil {
|
||||||
|
gc.logger.Errorf("failed to find untagged blobs of project, %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
untaggedBlobs = append(untaggedBlobs, unassociated...)
|
||||||
|
} else {
|
||||||
|
if err := gc.blobMgr.CleanupAssociationsForProject(ctx.SystemContext(), p.ProjectID, blobs); err != nil {
|
||||||
|
gc.logger.Errorf("failed to clean untagged blobs of project, %v", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(blobs) < ps {
|
if len(blobs) < ps {
|
||||||
break
|
break
|
||||||
@ -461,6 +474,7 @@ func (gc *GarbageCollector) removeUntaggedBlobs(ctx job.Context) {
|
|||||||
lastBlobID = blobs[len(blobs)-1].ID
|
lastBlobID = blobs[len(blobs)-1].ID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return untaggedBlobs
|
||||||
}
|
}
|
||||||
|
|
||||||
// markDeleteFailed set the blob status to StatusDeleteFailed
|
// markDeleteFailed set the blob status to StatusDeleteFailed
|
||||||
|
@ -139,7 +139,7 @@ func (suite *gcTestSuite) TestRemoveUntaggedBlobs() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suite.NotPanics(func() {
|
suite.NotPanics(func() {
|
||||||
gc.removeUntaggedBlobs(ctx)
|
gc.markOrSweepUntaggedBlobs(ctx)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,9 @@ type Manager interface {
|
|||||||
// CleanupAssociationsForProject remove unneeded associations between blobs and project
|
// CleanupAssociationsForProject remove unneeded associations between blobs and project
|
||||||
CleanupAssociationsForProject(ctx context.Context, projectID int64, blobs []*Blob) error
|
CleanupAssociationsForProject(ctx context.Context, projectID int64, blobs []*Blob) error
|
||||||
|
|
||||||
|
// FindBlobsShouldUnassociatedWithProject filter the blobs which should not be associated with the project
|
||||||
|
FindBlobsShouldUnassociatedWithProject(ctx context.Context, projectID int64, blobs []*models.Blob) ([]*models.Blob, error)
|
||||||
|
|
||||||
// Get get blob by digest
|
// Get get blob by digest
|
||||||
Get(ctx context.Context, digest string) (*Blob, error)
|
Get(ctx context.Context, digest string) (*Blob, error)
|
||||||
|
|
||||||
@ -114,6 +117,10 @@ func (m *manager) CleanupAssociationsForProject(ctx context.Context, projectID i
|
|||||||
return m.dao.DeleteProjectBlob(ctx, projectID, blobIDs...)
|
return m.dao.DeleteProjectBlob(ctx, projectID, blobIDs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *manager) FindBlobsShouldUnassociatedWithProject(ctx context.Context, projectID int64, blobs []*models.Blob) ([]*models.Blob, error) {
|
||||||
|
return m.dao.FindBlobsShouldUnassociatedWithProject(ctx, projectID, blobs)
|
||||||
|
}
|
||||||
|
|
||||||
func (m *manager) Get(ctx context.Context, digest string) (*Blob, error) {
|
func (m *manager) Get(ctx context.Context, digest string) (*Blob, error) {
|
||||||
return m.dao.GetBlobByDigest(ctx, digest)
|
return m.dao.GetBlobByDigest(ctx, digest)
|
||||||
}
|
}
|
||||||
|
@ -192,6 +192,67 @@ func (suite *ManagerTestSuite) TestCleanupAssociationsForProject() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (suite *ManagerTestSuite) TestFindBlobsShouldUnassociatedWithProject() {
|
||||||
|
ctx := suite.Context()
|
||||||
|
|
||||||
|
suite.WithProject(func(projectID int64, projectName string) {
|
||||||
|
artifact1 := suite.DigestString()
|
||||||
|
artifact2 := suite.DigestString()
|
||||||
|
|
||||||
|
sql := `INSERT INTO artifact ("type", media_type, manifest_media_type, digest, project_id, repository_id, repository_name) VALUES ('image', 'media_type', 'manifest_media_type', ?, ?, ?, 'library/hello-world')`
|
||||||
|
suite.ExecSQL(sql, artifact1, projectID, 11)
|
||||||
|
suite.ExecSQL(sql, artifact2, projectID, 11)
|
||||||
|
|
||||||
|
defer suite.ExecSQL(`DELETE FROM artifact WHERE project_id = ?`, projectID)
|
||||||
|
|
||||||
|
digest1 := suite.DigestString()
|
||||||
|
digest2 := suite.DigestString()
|
||||||
|
digest3 := suite.DigestString()
|
||||||
|
digest4 := suite.DigestString()
|
||||||
|
digest5 := suite.DigestString()
|
||||||
|
|
||||||
|
var ol q.OrList
|
||||||
|
blobDigests := []string{digest1, digest2, digest3, digest4, digest5}
|
||||||
|
for _, digest := range blobDigests {
|
||||||
|
blobID, err := Mgr.Create(ctx, digest, "", 100)
|
||||||
|
if suite.Nil(err) {
|
||||||
|
Mgr.AssociateWithProject(ctx, blobID, projectID)
|
||||||
|
}
|
||||||
|
ol.Values = append(ol.Values, digest)
|
||||||
|
}
|
||||||
|
|
||||||
|
blobs, err := Mgr.List(ctx, q.New(q.KeyWords{"digest": &ol}))
|
||||||
|
suite.Nil(err)
|
||||||
|
suite.Len(blobs, 5)
|
||||||
|
|
||||||
|
for _, digest := range []string{digest1, digest2, digest3} {
|
||||||
|
Mgr.AssociateWithArtifact(ctx, digest, artifact1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, digest := range blobDigests {
|
||||||
|
Mgr.AssociateWithArtifact(ctx, digest, artifact2)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
results, err := Mgr.FindBlobsShouldUnassociatedWithProject(ctx, projectID, blobs)
|
||||||
|
suite.Nil(err)
|
||||||
|
suite.Len(results, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
suite.ExecSQL(`DELETE FROM artifact WHERE digest = ?`, artifact2)
|
||||||
|
|
||||||
|
{
|
||||||
|
results, err := Mgr.FindBlobsShouldUnassociatedWithProject(ctx, projectID, blobs)
|
||||||
|
suite.Nil(err)
|
||||||
|
if suite.Len(results, 2) {
|
||||||
|
suite.Contains([]string{results[0].Digest, results[1].Digest}, digest4)
|
||||||
|
suite.Contains([]string{results[0].Digest, results[1].Digest}, digest5)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (suite *ManagerTestSuite) TestGet() {
|
func (suite *ManagerTestSuite) TestGet() {
|
||||||
ctx := suite.Context()
|
ctx := suite.Context()
|
||||||
|
|
||||||
|
@ -163,6 +163,29 @@ func (_m *Manager) Delete(ctx context.Context, id int64) error {
|
|||||||
return r0
|
return r0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindBlobsShouldUnassociatedWithProject provides a mock function with given fields: ctx, projectID, blobs
|
||||||
|
func (_m *Manager) FindBlobsShouldUnassociatedWithProject(ctx context.Context, projectID int64, blobs []*models.Blob) ([]*models.Blob, error) {
|
||||||
|
ret := _m.Called(ctx, projectID, blobs)
|
||||||
|
|
||||||
|
var r0 []*models.Blob
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int64, []*models.Blob) []*models.Blob); ok {
|
||||||
|
r0 = rf(ctx, projectID, blobs)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]*models.Blob)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, int64, []*models.Blob) error); ok {
|
||||||
|
r1 = rf(ctx, projectID, blobs)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
// Get provides a mock function with given fields: ctx, digest
|
// Get provides a mock function with given fields: ctx, digest
|
||||||
func (_m *Manager) Get(ctx context.Context, digest string) (*models.Blob, error) {
|
func (_m *Manager) Get(ctx context.Context, digest string) (*models.Blob, error) {
|
||||||
ret := _m.Called(ctx, digest)
|
ret := _m.Called(ctx, digest)
|
||||||
|
Loading…
Reference in New Issue
Block a user