metis/pkg/service/flash_workflow_test.go

183 lines
7.4 KiB
Go

package service
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestFlashOnlyWorkflowRequiresPublishedArtifact(t *testing.T) {
kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
installKubeFactory(t, kube)
app := remoteTestApp(t, nil)
if _, err := app.Flash("titan-15", "titan-22", "/dev/sdz"); err == nil || !strings.Contains(err.Error(), "run a build first") {
t.Fatalf("expected missing artifact error, got %v", err)
}
}
func TestFlashOnlyWorkflowCompletesAndRecordsVerification(t *testing.T) {
kube := remoteWorkflowKubeServer(t, remoteKubeOptions{flashMessage: `{"dest_path":"/dev/sdz","verified":true,"verification_kind":"block-device","verification_summary":"Verified /dev/sdz; system-boot and writable are present and the boot files look correct.","boot_partition":"/dev/sdz1","root_partition":"/dev/sdz2","boot_label":"system-boot","root_label":"writable","boot_fstype":"vfat","root_fstype":"ext4","checked_files":["config.txt","cmdline.txt","boot.scr"]}`})
installKubeFactory(t, kube)
app := remoteTestApp(t, nil)
app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}
job, err := app.Flash("titan-15", "titan-22", "/dev/sdz")
if err != nil {
t.Fatalf("Flash: %v", err)
}
waitForJobState(t, app, job.ID, JobDone)
got := app.job(job.ID)
if got == nil || got.Kind != "flash" || got.Status != JobDone {
t.Fatalf("unexpected flash job: %#v", got)
}
if !strings.Contains(got.Message, "Move the card into titan-15") {
t.Fatalf("unexpected completion message: %#v", got)
}
if got.ProgressPct != 100 {
t.Fatalf("expected complete flash progress, got %#v", got)
}
events := app.recentEvents(5)
if len(events) == 0 || events[0].Kind != "image.flash" {
t.Fatalf("expected flash event, got %#v", events)
}
if verified, ok := events[0].Details["verified"].(bool); !ok || !verified {
t.Fatalf("expected verified flash details, got %#v", events[0].Details)
}
if kind, ok := events[0].Details["verification_kind"].(string); !ok || kind != "block-device" {
t.Fatalf("expected block-device verification, got %#v", events[0].Details)
}
if bootPartition, ok := events[0].Details["boot_partition"].(string); !ok || bootPartition != "/dev/sdz1" {
t.Fatalf("expected boot partition details, got %#v", events[0].Details)
}
}
func TestHandleFlashRouteStartsFlashOnlyJob(t *testing.T) {
kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
installKubeFactory(t, kube)
app := remoteTestApp(t, nil)
app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}
handler := app.Handler()
req := httptest.NewRequest(http.MethodPost, "/api/jobs/flash", strings.NewReader(`{"node":"titan-15","host":"titan-22","device":"/dev/sdz"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Auth-Request-User", "brad")
req.Header.Set("X-Auth-Request-Groups", "admin")
resp := httptest.NewRecorder()
handler.ServeHTTP(resp, req)
if resp.Code != http.StatusAccepted {
t.Fatalf("flash route response: %d %s", resp.Code, resp.Body.String())
}
var job Job
if err := json.Unmarshal(resp.Body.Bytes(), &job); err != nil {
t.Fatalf("decode flash job: %v", err)
}
waitForJobState(t, app, job.ID, JobDone)
if got := app.job(job.ID); got == nil || got.Kind != "flash" {
t.Fatalf("expected flash job, got %#v", got)
}
}
func TestFlashAndReplaceDefaultHostBranches(t *testing.T) {
kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
harbor := fakeHarborServer(t, true)
installKubeFactory(t, kube)
app := remoteTestApp(t, harbor)
app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}
flashJob, err := app.Flash("titan-15", "", hostTmpDevicePath)
if err != nil {
t.Fatalf("Flash default host: %v", err)
}
waitForJobState(t, app, flashJob.ID, JobDone)
if got := app.job(flashJob.ID); got == nil || got.Host != "titan-22" || !strings.Contains(got.Message, "host /tmp") {
t.Fatalf("unexpected flash default host job: %#v", got)
}
replaceJob, err := app.Replace("titan-15", "", hostTmpDevicePath)
if err != nil {
t.Fatalf("Replace default host: %v", err)
}
waitForJobState(t, app, replaceJob.ID, JobDone)
if got := app.job(replaceJob.ID); got == nil || got.Host != "titan-22" {
t.Fatalf("unexpected replace default host job: %#v", got)
}
}
func TestFlashRejectsDuplicateActiveNodeJob(t *testing.T) {
kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
installKubeFactory(t, kube)
app := remoteTestApp(t, nil)
app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}
app.newJob("flash", "titan-15", "titan-22", "/dev/sdz")
if _, err := app.Flash("titan-15", "titan-22", "/dev/sdz"); err == nil || !strings.Contains(err.Error(), "already has an active") {
t.Fatalf("expected flash conflict, got %v", err)
}
}
func TestFlashArtifactUpdatesProgressWhenVerificationSummaryIsMissing(t *testing.T) {
kube := remoteWorkflowKubeServer(t, remoteKubeOptions{flashMessage: `{"dest_path":"/dev/sdz","size_bytes":4096,"verified":true}`})
installKubeFactory(t, kube)
app := remoteTestApp(t, nil)
job := app.newJob("flash", "titan-15", "titan-22", "/dev/sdz")
result, err := app.flashArtifact(job.ID, "registry.example/metis/titan-15")
if err != nil {
t.Fatalf("flashArtifact: %v", err)
}
if result.SizeBytes != 4096 {
t.Fatalf("unexpected flash result: %#v", result)
}
got := app.job(job.ID)
if got == nil || got.Written != 4096 || got.Total != 4096 || !strings.Contains(got.Message, "Verified the latest image write") {
t.Fatalf("unexpected flash artifact state: %#v", got)
}
}
func TestFlashOnlyWorkflowFailsVerification(t *testing.T) {
kube := remoteWorkflowKubeServer(t, remoteKubeOptions{flashMessage: `{"dest_path":"/dev/sdz","verified":false}`})
installKubeFactory(t, kube)
app := remoteTestApp(t, nil)
app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}
job, err := app.Flash("titan-15", "titan-22", "/dev/sdz")
if err != nil {
t.Fatalf("Flash: %v", err)
}
deadline := time.Now().Add(5 * time.Second)
for time.Now().Before(deadline) {
if got := app.job(job.ID); got != nil && got.Status == JobError {
if !strings.Contains(got.Error, "verification did not succeed") {
t.Fatalf("unexpected flash verification error: %#v", got)
}
return
}
time.Sleep(10 * time.Millisecond)
}
t.Fatalf("flash job %s never failed verification: %#v", job.ID, app.job(job.ID))
}
func TestBuildFlashAndReplaceRejectInvalidInputs(t *testing.T) {
kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
installKubeFactory(t, kube)
app := remoteTestApp(t, nil)
app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}
if _, err := app.Build("missing-node"); err == nil || !strings.Contains(err.Error(), "missing-node") {
t.Fatalf("expected build inventory error, got %v", err)
}
if _, err := app.Flash("titan-15", "titan-22", "/dev/sda"); err == nil || !strings.Contains(err.Error(), "not a current flash candidate") {
t.Fatalf("expected flash device validation error, got %v", err)
}
if _, err := app.Replace("titan-15", "titan-22", "/dev/sda"); err == nil || !strings.Contains(err.Error(), "not a current flash candidate") {
t.Fatalf("expected replace device validation error, got %v", err)
}
}