Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CubeMaster/pkg/templatecenter/job_dto.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ func jobModelToInfo(ctx context.Context, record *models.TemplateImageJob) (*type
return info, nil
}

// cloneTemplateImageJobInfo returns an independent copy of src. When
// TemplateImageJobInfo, Res, or RootfsArtifactInfo gain new pointer/slice/map
// fields, update the deep-copy branches here accordingly.
func cloneTemplateImageJobInfo(src *types.TemplateImageJobInfo) *types.TemplateImageJobInfo {
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
if src == nil {
return nil
}
dst := *src
dst.RedoScope = append([]string(nil), src.RedoScope...)
if src.Artifact != nil {
artifact := *src.Artifact
dst.Artifact = &artifact
}
if src.Template != nil {
template := *src.Template
if src.Template.Ret != nil {
ret := *src.Template.Ret
template.Ret = &ret
}
dst.Template = &template
}
return &dst
}

func artifactModelToInfo(record *models.RootfsArtifact) *types.RootfsArtifactInfo {
return &types.RootfsArtifactInfo{
ArtifactID: record.ArtifactID,
Expand Down
32 changes: 32 additions & 0 deletions CubeMaster/pkg/templatecenter/job_dto_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) 2026 Tencent Inc.
// SPDX-License-Identifier: Apache-2.0
//

package templatecenter

import (
"testing"

sandboxtypes "github.com/tencentcloud/CubeSandbox/CubeMaster/pkg/service/sandbox/types"
)

func TestCloneTemplateImageJobInfo(t *testing.T) {
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
if cloneTemplateImageJobInfo(nil) != nil {
t.Fatal("nil input should return nil")
}

src := &sandboxtypes.TemplateImageJobInfo{
JobID: "job-1",
TemplateID: "tpl-1",
Status: JobStatusPending,
RedoScope: []string{"phase-a"},
}
dst := cloneTemplateImageJobInfo(src)
if dst == nil || dst == src {
t.Fatal("expected independent clone")
}
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
dst.RedoScope[0] = "changed"
if src.RedoScope[0] == "changed" {
t.Fatal("RedoScope should be deep-copied")
}
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
}
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
19 changes: 11 additions & 8 deletions CubeMaster/pkg/templatecenter/snapshot_ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,16 +708,13 @@ func runSnapshotDeleteJob(ctx context.Context, jobID, snapshotID string) error {
if err := runReplicaCleanup(ctx, snapshotID, locators); err != nil {
return failSnapshotDeleteJob(ctx, jobID, snapshotID, err)
}
if err := deleteSnapshotMetadataOnly(ctx, snapshotID); err != nil {
if err := runMetadataCleanup(ctx, snapshotID); err != nil {
return failSnapshotDeleteJob(ctx, jobID, snapshotID, err)
}
invalidateTemplateCaches(snapshotID)
if err := updateTemplateImageJob(ctx, jobID, map[string]any{
"status": JobStatusReady,
"phase": JobPhaseReady,
"progress": 100,
"result_json": `{"deleted":true}`,
}); err != nil {
// Mirror template delete: drop job rows so ListTemplates does not
// resurrect the snapshot from orphan job fallback entries.
if err := runTemplateJobCleanup(ctx, snapshotID); err != nil {
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
return err
}
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
success = true
Expand Down Expand Up @@ -1274,7 +1271,13 @@ func executeSnapshotDeleteJob(ctx context.Context, info *sandboxtypes.TemplateIm
if err := runSnapshotDeleteJob(jobCtx, info.JobID, snapshotID); err != nil {
return nil, err
}
return finalizeSnapshotJobByID(ctx, info.JobID)
// runSnapshotDeleteJob removes job rows on success; return a terminal
// READY view without re-querying the deleted job record.
result := cloneTemplateImageJobInfo(info)
result.Status = JobStatusReady
result.Phase = JobPhaseReady
result.Progress = 100
return result, nil
}

func resolveExistingSnapshotJobByID(ctx context.Context, jobID string) (*sandboxtypes.TemplateImageJobInfo, error) {
Expand Down
93 changes: 93 additions & 0 deletions CubeMaster/pkg/templatecenter/snapshot_ops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,96 @@ func TestDeleteSnapshotDoesNotBlockWhenRuntimeRefsExist(t *testing.T) {
t.Fatalf("DeleteSnapshot error = %q, runtime-ref guard should no longer reject", err.Error())
}
}

func TestRunSnapshotDeleteJobCleansTemplateJobs(t *testing.T) {
origReplicaCleanup := runReplicaCleanup
origMetadataCleanup := runMetadataCleanup
origJobCleanup := runTemplateJobCleanup
t.Cleanup(func() {
runReplicaCleanup = origReplicaCleanup
runMetadataCleanup = origMetadataCleanup
runTemplateJobCleanup = origJobCleanup
})

patches := gomonkey.NewPatches()
defer patches.Reset()

jobsCleaned := false
patches.ApplyFunc(updateTemplateImageJob, func(ctx context.Context, jobID string, fields map[string]any) error {
return nil
})
patches.ApplyFunc(discoverTemplateCleanupTargets, func(ctx context.Context, templateID, instanceType string) (*templateCleanupTargets, error) {
return &templateCleanupTargets{}, nil
})
patches.ApplyFunc(snapshotDeleteLocators, func(targets *templateCleanupTargets) ([]templateCleanupLocator, error) {
return nil, nil
})
patches.ApplyFunc(invalidateTemplateCaches, func(templateID string) {})
runReplicaCleanup = func(ctx context.Context, templateID string, locators []templateCleanupLocator) error {
return nil
}
runMetadataCleanup = func(ctx context.Context, templateID string) error {
return nil
}
runTemplateJobCleanup = func(ctx context.Context, templateID string) error {
if templateID != "snap-del" {
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
t.Fatalf("runTemplateJobCleanup templateID = %q, want snap-del", templateID)
}
jobsCleaned = true
return nil
}

if err := runSnapshotDeleteJob(context.Background(), "job-del", "snap-del"); err != nil {
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
t.Fatalf("runSnapshotDeleteJob returned error: %v", err)
}
if !jobsCleaned {
t.Fatal("expected runTemplateJobCleanup to be called")
}
}

func TestExecuteSnapshotDeleteJobReturnsReadyWithoutJobLookup(t *testing.T) {
patches := gomonkey.NewPatches()
defer patches.Reset()

patches.ApplyFunc(claimSnapshotJobExecution, func(ctx context.Context, jobID, phase string, progress int32) (bool, error) {
return true, nil
})
patches.ApplyFunc(runSnapshotDeleteJob, func(ctx context.Context, jobID, snapshotID string) error {
return nil
})
getJobInfoCallCount := 0
patches.ApplyFunc(GetTemplateImageJobInfo, func(ctx context.Context, jobID string) (*sandboxtypes.TemplateImageJobInfo, error) {
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
getJobInfoCallCount++
return nil, nil
})

info, err := executeSnapshotDeleteJob(context.Background(), &sandboxtypes.TemplateImageJobInfo{
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
JobID: "job-del",
TemplateID: "snap-del",
Status: JobStatusPending,
}, "snap-del")
if err != nil {
t.Fatalf("executeSnapshotDeleteJob returned error: %v", err)
}
if info == nil {
t.Fatal("expected non-nil job info")
}
if info.JobID != "job-del" {
t.Fatalf("jobID = %q, want %q", info.JobID, "job-del")
}
if info.TemplateID != "snap-del" {
t.Fatalf("templateID = %q, want %q", info.TemplateID, "snap-del")
}
if info.Status != JobStatusReady {
Comment thread
xiaojunxiang2023 marked this conversation as resolved.
t.Fatalf("status = %q, want %q", info.Status, JobStatusReady)
}
if info.Phase != JobPhaseReady {
t.Fatalf("phase = %q, want %q", info.Phase, JobPhaseReady)
}
if info.Progress != 100 {
t.Fatalf("progress = %d, want 100", info.Progress)
}
if getJobInfoCallCount != 0 {
t.Fatalf("GetTemplateImageJobInfo called %d time(s), want 0", getJobInfoCallCount)
}
}
Loading