143 lines
4.3 KiB
Go
143 lines
4.3 KiB
Go
package proxmox
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestTLSAndTaskLifecycle(t *testing.T) {
|
|
polls := 0
|
|
ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("Authorization") != "PVEAPIToken=vdi@pve!console=secret" {
|
|
t.Error("missing PVE token")
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
switch {
|
|
case strings.HasSuffix(r.URL.Path, "/status/start"):
|
|
if r.Method != "POST" {
|
|
t.Error("action should POST")
|
|
}
|
|
w.Write([]byte(`{"data":"UPID:node:123"}`))
|
|
case strings.HasSuffix(r.URL.Path, "/status/current"):
|
|
w.Write([]byte(`{"data":{"status":"running"}}`))
|
|
case strings.Contains(r.URL.Path, "/tasks/"):
|
|
polls++
|
|
if polls < 2 {
|
|
w.Write([]byte(`{"data":{"status":"running"}}`))
|
|
} else {
|
|
w.Write([]byte(`{"data":{"status":"stopped","exitstatus":"OK"}}`))
|
|
}
|
|
default:
|
|
w.WriteHeader(404)
|
|
}
|
|
}))
|
|
defer ts.Close()
|
|
c, err := New(ts.URL, "vdi@pve!console", "secret", "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err = c.Action(context.Background(), "node", 201, "start"); err == nil {
|
|
t.Fatal("untrusted PVE certificate must not be accepted")
|
|
}
|
|
c.HTTP = ts.Client()
|
|
upid, err := c.Action(context.Background(), "node", 201, "start")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = c.Wait(context.Background(), "node", upid, time.Millisecond); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if polls != 2 {
|
|
t.Fatalf("expected task polling; got %d", polls)
|
|
}
|
|
if _, err = c.Action(context.Background(), "node", 201, "restore"); err == nil {
|
|
t.Fatal("restore must never map to hard reset")
|
|
}
|
|
}
|
|
|
|
func TestObservationRetriesDoNotReplayPowerCommands(t *testing.T) {
|
|
for _, endpoint := range []string{"task", "power"} {
|
|
t.Run(endpoint, func(t *testing.T) {
|
|
reads, writes := 0, 0
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == "POST" {
|
|
writes++
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
reads++
|
|
if reads < 3 {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
fmtBody := `{"data":{"status":"running"}}`
|
|
if endpoint == "task" {
|
|
fmtBody = `{"data":{"status":"stopped","exitstatus":"OK"}}`
|
|
}
|
|
w.Write([]byte(fmtBody))
|
|
}))
|
|
defer ts.Close()
|
|
c := &Client{BaseURL: ts.URL, HTTP: ts.Client()}
|
|
var err error
|
|
if endpoint == "task" {
|
|
err = c.Wait(context.Background(), "node", "UPID:one", time.Millisecond)
|
|
} else {
|
|
_, err = c.Power(context.Background(), "node", 100)
|
|
}
|
|
if err != nil || reads != 3 {
|
|
t.Fatal("observation did not recover", reads, err)
|
|
}
|
|
if _, err = c.Action(context.Background(), "node", 100, "start"); err == nil || writes != 1 {
|
|
t.Fatal("command response loss must not replay POST", writes, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestObservationRetriesAreBoundedAndRespectCancellation(t *testing.T) {
|
|
for _, status := range []int{http.StatusForbidden, http.StatusServiceUnavailable} {
|
|
calls := 0
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls++
|
|
w.WriteHeader(status)
|
|
}))
|
|
c := &Client{BaseURL: ts.URL, HTTP: ts.Client()}
|
|
_, err := c.Power(context.Background(), "node", 100)
|
|
want := 1
|
|
if status == http.StatusServiceUnavailable {
|
|
want = 3
|
|
}
|
|
if err == nil || calls != want {
|
|
t.Fatal("incorrect retry limit", calls, err)
|
|
}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
if _, err = c.Power(ctx, "node", 100); !errors.Is(err, context.Canceled) || calls != want {
|
|
t.Fatal("cancellation ignored", calls, err)
|
|
}
|
|
ts.Close()
|
|
}
|
|
}
|
|
func TestFailedPVEJobIsNotSuccess(t *testing.T) {
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte(`{"data":{"status":"stopped","exitstatus":"ERROR: disk full"}}`))
|
|
}))
|
|
defer ts.Close()
|
|
c := &Client{BaseURL: ts.URL, HTTP: ts.Client()}
|
|
if err := c.Wait(context.Background(), "node", "task", time.Millisecond); err == nil {
|
|
t.Fatal("failed PVE job reported as success")
|
|
}
|
|
}
|
|
func TestRejectInsecureOrigin(t *testing.T) {
|
|
for _, base := range []string{"http://pve:8006", "https://user:password@pve:8006", "https://pve:8006/other"} {
|
|
if _, err := New(base, "id", "secret", ""); err == nil {
|
|
t.Fatalf("accepted %s", base)
|
|
}
|
|
}
|
|
}
|