From 680c78d3683822c9922f4f4f4dc02561c7ed01f3 Mon Sep 17 00:00:00 2001 From: Wang Yan Date: Fri, 2 Jun 2023 17:33:09 +0800 Subject: [PATCH] add more details in gc history (#18779) Show more infors in the gc history, like the sweep size and how many blobs and manifests were removed by GC. Signed-off-by: Wang Yan --- src/controller/gc/callback.go | 45 ++++++++++++++++++- src/controller/gc/callback_test.go | 44 ++++++++++++++++++ .../job/impl/gc/garbage_collection.go | 28 +++++++++++- .../job/impl/gc/garbage_collection_test.go | 10 +++++ 4 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 src/controller/gc/callback_test.go diff --git a/src/controller/gc/callback.go b/src/controller/gc/callback.go index d8f33321c..04ef73d83 100644 --- a/src/controller/gc/callback.go +++ b/src/controller/gc/callback.go @@ -35,7 +35,11 @@ func init() { } if err := task.RegisterTaskStatusChangePostFunc(job.GarbageCollectionVendorType, gcTaskStatusChange); err != nil { - log.Fatalf("failed to register the task status change post for the gc job, error %v", err) + log.Fatalf("failed to register the task status change post for the garbage collection job, error %v", err) + } + + if err := task.RegisterCheckInProcessor(job.GarbageCollectionVendorType, gcCheckIn); err != nil { + log.Fatalf("failed to register the checkin processor for the garbage collection job, error %v", err) } } @@ -60,3 +64,42 @@ func gcTaskStatusChange(ctx context.Context, taskID int64, status string) error return nil } + +func gcCheckIn(ctx context.Context, t *task.Task, sc *job.StatusChange) error { + taskID := t.ID + status := t.Status + + log.Infof("received garbage collection task status update event: task-%d, status-%s", taskID, status) + if sc.CheckIn != "" { + var gcObj struct { + SweepSize int64 `json:"freed_space"` + Blobs int64 `json:"purged_blobs"` + Manifests int64 `json:"purged_manifests"` + } + if err := json.Unmarshal([]byte(sc.CheckIn), &gcObj); err != nil { + log.Errorf("failed to resolve checkin of garbage collection task %d: %v", taskID, err) + + return err + } + t, err := task.Mgr.Get(ctx, taskID) + if err != nil { + return err + } + + e, err := task.ExecMgr.Get(ctx, t.ExecutionID) + if err != nil { + return err + } + + e.ExtraAttrs["freed_space"] = gcObj.SweepSize + e.ExtraAttrs["purged_blobs"] = gcObj.Blobs + e.ExtraAttrs["purged_manifests"] = gcObj.Manifests + + err = task.ExecMgr.UpdateExtraAttrs(ctx, e.ID, e.ExtraAttrs) + if err != nil { + log.G(ctx).WithField("error", err).Errorf("failed to update of garbage collection task %d", taskID) + return err + } + } + return nil +} diff --git a/src/controller/gc/callback_test.go b/src/controller/gc/callback_test.go new file mode 100644 index 000000000..5f7194070 --- /dev/null +++ b/src/controller/gc/callback_test.go @@ -0,0 +1,44 @@ +package gc + +import ( + "context" + "testing" + + "github.com/goharbor/harbor/src/jobservice/job" + "github.com/goharbor/harbor/src/pkg/task" + "github.com/goharbor/harbor/src/testing/mock" + tasktesting "github.com/goharbor/harbor/src/testing/pkg/task" + "github.com/stretchr/testify/suite" +) + +type callbackTestSuite struct { + suite.Suite + execMgr *tasktesting.ExecutionManager + taskMgr *tasktesting.Manager +} + +func (c *callbackTestSuite) SetupTest() { + c.execMgr = &tasktesting.ExecutionManager{} + c.taskMgr = &tasktesting.Manager{} +} + +func (c *callbackTestSuite) TestCheckIn() { + t := &task.Task{ + ID: 1, + Status: "Success", + } + + sc := &job.StatusChange{ + CheckIn: "", + } + + c.taskMgr.On("Get", mock.Anything, int64(1)).Return(&task.Task{ID: 1, ExecutionID: 1}, nil) + c.execMgr.On("Get", mock.Anything, mock.Anything).Return(&task.Execution{ID: 1}, nil) + c.execMgr.On("UpdateExtraAttrs", mock.Anything, mock.Anything, mock.Anything).Return(nil) + + gcCheckIn(context.Background(), t, sc) +} + +func TestCallBackTestSuite(t *testing.T) { + suite.Run(t, &callbackTestSuite{}) +} diff --git a/src/jobservice/job/impl/gc/garbage_collection.go b/src/jobservice/job/impl/gc/garbage_collection.go index aba2990f0..615f9e6c7 100644 --- a/src/jobservice/job/impl/gc/garbage_collection.go +++ b/src/jobservice/job/impl/gc/garbage_collection.go @@ -15,6 +15,7 @@ package gc import ( + "encoding/json" "os" "time" @@ -255,8 +256,8 @@ func (gc *GarbageCollector) mark(ctx job.Context) error { func (gc *GarbageCollector) sweep(ctx job.Context) error { gc.logger = ctx.GetLogger() sweepSize := int64(0) - blobCnt := 0 - mfCnt := 0 + blobCnt := int64(0) + mfCnt := int64(0) total := len(gc.deleteSet) for i, blob := range gc.deleteSet { if gc.shouldStop(ctx) { @@ -413,6 +414,11 @@ func (gc *GarbageCollector) sweep(ctx job.Context) error { } gc.logger.Infof("%d blobs and %d manifests are actually deleted", blobCnt, mfCnt) gc.logger.Infof("The GC job actual frees up %d MB space.", sweepSize/1024/1024) + + if err := saveGCRes(ctx, sweepSize, blobCnt, mfCnt); err != nil { + gc.logger.Errorf("failed to save the garbage collection results, errMsg=%v", err) + } + return nil } @@ -647,3 +653,21 @@ func (gc *GarbageCollector) shouldStop(ctx job.Context) bool { } return false } + +func saveGCRes(ctx job.Context, sweepSize, blobs, manifests int64) error { + gcObj := struct { + SweepSize int64 `json:"freed_space"` + Blobs int64 `json:"purged_blobs"` + Manifests int64 `json:"purged_manifests"` + }{ + SweepSize: sweepSize, + Blobs: blobs, + Manifests: manifests, + } + c, err := json.Marshal(gcObj) + if err != nil { + return err + } + _ = ctx.Checkin(string(c)) + return nil +} diff --git a/src/jobservice/job/impl/gc/garbage_collection_test.go b/src/jobservice/job/impl/gc/garbage_collection_test.go index e92b73ce1..3c78be423 100644 --- a/src/jobservice/job/impl/gc/garbage_collection_test.go +++ b/src/jobservice/job/impl/gc/garbage_collection_test.go @@ -217,6 +217,7 @@ func (suite *gcTestSuite) TestRun() { ctx.On("GetLogger").Return(logger) ctx.On("OPCommand").Return(job.NilCommand, true) mock.OnAnything(ctx, "Get").Return("core url", true) + mock.OnAnything(ctx, "Checkin").Return(nil) suite.artifactCtl.On("List").Return([]*artifact.Artifact{ { @@ -357,6 +358,7 @@ func (suite *gcTestSuite) TestSweep() { logger := &mockjobservice.MockJobLogger{} ctx.On("GetLogger").Return(logger) ctx.On("OPCommand").Return(job.NilCommand, false) + mock.OnAnything(ctx, "Checkin").Return(nil) mock.OnAnything(suite.blobMgr, "UpdateBlobStatus").Return(int64(1), nil) mock.OnAnything(suite.blobMgr, "Delete").Return(nil) @@ -378,6 +380,14 @@ func (suite *gcTestSuite) TestSweep() { suite.Nil(gc.sweep(ctx)) } +func (suite *gcTestSuite) TestSaveRes() { + ctx := &mockjobservice.MockJobContext{} + logger := &mockjobservice.MockJobLogger{} + ctx.On("GetLogger").Return(logger) + mock.OnAnything(ctx, "Checkin").Return(nil) + suite.Nil(saveGCRes(ctx, 123456, 100, 100)) +} + func TestGCTestSuite(t *testing.T) { t.Setenv("UTTEST", "true") suite.Run(t, &gcTestSuite{})