306 lines
8.3 KiB
Go
306 lines
8.3 KiB
Go
package updater
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"scout/internal/httpapi"
|
|
"scout/internal/registry"
|
|
"scout/pkg/client"
|
|
"scout/pkg/scoutpkg"
|
|
)
|
|
|
|
func TestPrepareActivateAndConfirmHealthy(t *testing.T) {
|
|
api := newTestClient(t)
|
|
packageData := createScoutPackage(t, "1.1.0")
|
|
if _, err := api.Upload(context.Background(), client.UploadRequest{
|
|
FileName: "demo.scout",
|
|
File: bytes.NewReader(packageData),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root := t.TempDir()
|
|
manager, err := New(Config{
|
|
Client: api,
|
|
PackageName: "demo-core",
|
|
CurrentVersion: "1.0.0",
|
|
RootDir: root,
|
|
Platform: "linux",
|
|
Architecture: "amd64",
|
|
KeepReleases: 2,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prepared, err := manager.Prepare(context.Background(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if prepared.Version != "1.1.0" {
|
|
t.Fatalf("\u51c6\u5907\u7248\u672c\u9519\u8bef: %s", prepared.Version)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(root, "current.json")); !os.IsNotExist(err) {
|
|
t.Fatal("Prepare \u4e0d\u5e94\u4fee\u6539\u5f53\u524d\u7248\u672c\u6307\u9488")
|
|
}
|
|
content, err := os.ReadFile(filepath.Join(prepared.Path, "demo"))
|
|
if err != nil || string(content) != "new binary" {
|
|
t.Fatalf("\u89e3\u538b\u7ed3\u679c\u9519\u8bef: %q, %v", content, err)
|
|
}
|
|
activated, err := manager.Activate()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if activated.Version != prepared.Version {
|
|
t.Fatalf("\u6fc0\u6d3b\u7248\u672c\u9519\u8bef: %s", activated.Version)
|
|
}
|
|
if err := manager.ConfirmHealthy(activated.Version); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
status, err := manager.Status()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status.Current == nil || status.Current.Version != "1.1.0" || !status.State.Healthy {
|
|
t.Fatalf("\u5065\u5eb7\u72b6\u6001\u9519\u8bef: %+v", status)
|
|
}
|
|
if status.Pending != nil {
|
|
t.Fatal("\u5065\u5eb7\u786e\u8ba4\u540e\u5e94\u6e05\u9664\u5f85\u6fc0\u6d3b\u6307\u9488")
|
|
}
|
|
}
|
|
|
|
func TestActivationPreservesOldReleaseAndStaleActivationRollsBack(t *testing.T) {
|
|
api := newTestClient(t)
|
|
if _, err := api.Upload(context.Background(), client.UploadRequest{
|
|
FileName: "demo.scout",
|
|
File: bytes.NewReader(createScoutPackage(t, "2.0.0")),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root := t.TempDir()
|
|
manager, err := New(Config{
|
|
Client: api,
|
|
PackageName: "demo-core",
|
|
RootDir: root,
|
|
Platform: "linux",
|
|
Architecture: "amd64",
|
|
HealthTimeout: 10 * time.Millisecond,
|
|
KeepReleases: 2,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
oldDir := filepath.Join(root, "releases", "1.0.0")
|
|
if err := os.MkdirAll(oldDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(oldDir, "demo"), []byte("old binary"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
oldPointer, err := manager.RegisterCurrent("1.0.0", oldDir, "demo")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := manager.Prepare(context.Background(), "2.0.0"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := manager.Activate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(filepath.Join(oldPointer.Path, oldPointer.Entrypoint)); err != nil {
|
|
t.Fatalf("\u6fc0\u6d3b\u540e\u65e7\u7248\u672c\u4e0d\u5e94\u88ab\u5220\u9664: %v", err)
|
|
}
|
|
recovered, err := manager.RecoverStaleActivation(time.Now().Add(time.Second))
|
|
if err != nil || !recovered {
|
|
t.Fatalf("\u8d85\u65f6\u56de\u6eda\u5931\u8d25: %t, %v", recovered, err)
|
|
}
|
|
status, err := manager.Status()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if status.Current == nil || status.Current.Version != "1.0.0" || status.State.Stage != "rolled_back" {
|
|
t.Fatalf("\u56de\u6eda\u72b6\u6001\u9519\u8bef: %+v", status)
|
|
}
|
|
}
|
|
|
|
func TestHealthyCleanupUsesConfiguredRetention(t *testing.T) {
|
|
api := newTestClient(t)
|
|
if _, err := api.Upload(context.Background(), client.UploadRequest{
|
|
FileName: "demo.scout",
|
|
File: bytes.NewReader(createScoutPackage(t, "1.1.0")),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
root := t.TempDir()
|
|
manager, err := New(Config{
|
|
Client: api,
|
|
PackageName: "demo-core",
|
|
RootDir: root,
|
|
Platform: "linux",
|
|
Architecture: "amd64",
|
|
KeepReleases: 1,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
oldDir := filepath.Join(root, "releases", "1.0.0")
|
|
if err := os.MkdirAll(oldDir, 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(oldDir, "demo"), []byte("old binary"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := manager.RegisterCurrent("1.0.0", oldDir, "demo"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prepared, err := manager.Prepare(context.Background(), "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := manager.Activate(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(oldDir); err != nil {
|
|
t.Fatalf("\u5065\u5eb7\u786e\u8ba4\u524d\u5fc5\u987b\u4fdd\u7559\u65e7\u7248\u672c: %v", err)
|
|
}
|
|
if err := manager.ConfirmHealthy(prepared.Version); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := os.Stat(oldDir); !os.IsNotExist(err) {
|
|
t.Fatalf("\u5065\u5eb7\u786e\u8ba4\u540e\u672a\u6309\u4fdd\u7559\u7b56\u7565\u6e05\u7406\u65e7\u7248\u672c: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestPrepareCleansFailedStagingAndCanRetry(t *testing.T) {
|
|
api := newTestClient(t)
|
|
invalidManifest := scoutpkg.Manifest{
|
|
SchemaVersion: 1,
|
|
Name: "demo-core",
|
|
Version: "1.1.0",
|
|
Channel: "stable",
|
|
PackageType: "full",
|
|
Targets: []scoutpkg.Target{{
|
|
Platform: "linux",
|
|
Architecture: "amd64",
|
|
Path: "targets/linux/amd64",
|
|
Entrypoint: "targets/linux/amd64/missing",
|
|
}},
|
|
}
|
|
if _, err := api.Upload(context.Background(), client.UploadRequest{
|
|
FileName: "invalid.scout",
|
|
File: bytes.NewReader(createScoutArchive(t, invalidManifest, map[string][]byte{
|
|
"targets/linux/amd64/payload": []byte("payload"),
|
|
})),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
root := t.TempDir()
|
|
manager, err := New(Config{
|
|
Client: api,
|
|
PackageName: "demo-core",
|
|
RootDir: root,
|
|
Platform: "linux",
|
|
Architecture: "amd64",
|
|
HealthTimeout: 10 * time.Millisecond,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := manager.Prepare(context.Background(), ""); err == nil {
|
|
t.Fatal("入口不存在的更新包应该准备失败")
|
|
}
|
|
staging, err := filepath.Glob(filepath.Join(root, "releases", ".staging-*"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(staging) != 0 {
|
|
t.Fatalf("失败后不应保留暂存目录: %v", staging)
|
|
}
|
|
|
|
if _, err := api.Upload(context.Background(), client.UploadRequest{
|
|
FileName: "valid.scout",
|
|
File: bytes.NewReader(createScoutPackage(t, "1.2.0")),
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
prepared, err := manager.Prepare(context.Background(), "")
|
|
if err != nil {
|
|
t.Fatalf("清理失败暂存目录后应允许重试: %v", err)
|
|
}
|
|
if prepared.Version != "1.2.0" {
|
|
t.Fatalf("重试准备的版本错误: %s", prepared.Version)
|
|
}
|
|
}
|
|
|
|
func newTestClient(t *testing.T) *client.Client {
|
|
t.Helper()
|
|
store, err := registry.New(t.TempDir())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
server := httptest.NewServer((&httpapi.Server{Registry: store, DeveloperToken: "test-token"}).Handler())
|
|
t.Cleanup(server.Close)
|
|
api, err := client.New(server.URL, client.WithDeveloperToken("test-token"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return api
|
|
}
|
|
|
|
func createScoutPackage(t *testing.T, version string) []byte {
|
|
t.Helper()
|
|
manifest := scoutpkg.Manifest{
|
|
SchemaVersion: 1,
|
|
Name: "demo-core",
|
|
Version: version,
|
|
Channel: "stable",
|
|
PackageType: "full",
|
|
Targets: []scoutpkg.Target{{
|
|
Platform: "linux",
|
|
Architecture: "amd64",
|
|
Path: "targets/linux/amd64",
|
|
Entrypoint: "targets/linux/amd64/demo",
|
|
}},
|
|
}
|
|
return createScoutArchive(t, manifest, map[string][]byte{
|
|
"targets/linux/amd64/demo": []byte("new binary"),
|
|
})
|
|
}
|
|
|
|
func createScoutArchive(t *testing.T, manifest scoutpkg.Manifest, files map[string][]byte) []byte {
|
|
t.Helper()
|
|
buffer := bytes.NewBuffer(nil)
|
|
writer := zip.NewWriter(buffer)
|
|
manifestData, err := json.Marshal(manifest)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
entry, err := writer.Create(scoutpkg.ManifestName)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := entry.Write(manifestData); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for name, content := range files {
|
|
entry, err := writer.Create(name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := entry.Write(content); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return buffer.Bytes()
|
|
}
|