package retention import ( "context" "os" "path/filepath" "testing" core "git.yuntuo.hk/k2safe/orbit/model" ) type idleArchiveStore struct { RuntimeStore scheduled int } func (s *idleArchiveStore) ScheduleArchive(context.Context, core.ID) error { s.scheduled++; return nil } func (s *idleArchiveStore) ClaimArchive(context.Context, core.ID) (*ManagedJob, error) { return nil, nil } func TestArchiveVolumeIdentityLockAndInitializationRecovery(t *testing.T) { store := &idleArchiveStore{} id, _ := core.NewID() runtime := &ArchiveRuntime{Directory: filepath.Join(t.TempDir(), "volume"), StorageID: id, MaxBytes: 8 << 20, Store: store} if err := runtime.Init(); err != nil { t.Fatal(err) } root, lock, err := openJob(runtime.Directory, false) if err != nil { t.Fatal(err) } // 其他节点正在执行时,新节点仍可读取不可变卷身份并正常启动。 if err = runtime.Init(); err != nil { t.Fatal("another node could not initialize", err) } other := *runtime other.StorageID, _ = core.NewID() if err = other.Init(); err == nil { t.Fatal("wrong volume accepted while busy") } if err = runtime.Tick(context.Background()); err != nil || store.scheduled != 0 { t.Fatal("busy volume scheduled work", err) } lock.Close() root.Close() if err = runtime.Tick(context.Background()); err != nil || store.scheduled != 1 { t.Fatal("unlocked volume did not schedule", err) } if err = other.Init(); err == nil { t.Fatal("wrong volume accepted") } // 首次身份文件原子发布前崩溃,只允许丢弃本工具的安全临时文件。 fresh := *runtime fresh.Directory = filepath.Join(t.TempDir(), "interrupted") if err = os.Mkdir(fresh.Directory, 0700); err != nil { t.Fatal(err) } if err = os.WriteFile(filepath.Join(fresh.Directory, ".write.tmp"), []byte(id), 0600); err != nil { t.Fatal(err) } if err = fresh.Init(); err != nil { t.Fatal("volume init did not recover", err) } } func TestArchiveVolumeScanCountsAllJobsAndRejectsLinks(t *testing.T) { dir := t.TempDir() job := "job" if err := os.Mkdir(filepath.Join(dir, job), 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, "other"), []byte("volume"), 0600); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(dir, job, "batch"), []byte("job"), 0600); err != nil { t.Fatal(err) } root, err := os.OpenRoot(dir) if err != nil { t.Fatal(err) } defer root.Close() count := 0 total, current, err := archiveVolumeUsage(context.Background(), root, ".", job, &count) if err != nil || total != 9 || current != 3 || count != 3 { t.Fatal(total, current, count, err) } if err = os.Symlink("other", filepath.Join(dir, "unexpected")); err != nil { t.Fatal(err) } count = 0 if _, _, err = archiveVolumeUsage(context.Background(), root, ".", job, &count); err == nil { t.Fatal("volume symlink accepted") } }