tested real proxmox connection
--> passed
This commit is contained in:
@@ -125,6 +125,9 @@ func (h Handler) ListTenantAudit(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeError(w, http.StatusInternalServerError, "audit_list_failed")
|
writeError(w, http.StatusInternalServerError, "audit_list_failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if entries == nil {
|
||||||
|
entries = []Entry{}
|
||||||
|
}
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, map[string]any{
|
writeJSON(w, http.StatusOK, map[string]any{
|
||||||
"data": entries,
|
"data": entries,
|
||||||
@@ -141,4 +144,4 @@ func writeJSON(w http.ResponseWriter, status int, body any) {
|
|||||||
|
|
||||||
func writeError(w http.ResponseWriter, status int, message string) {
|
func writeError(w http.ResponseWriter, status int, message string) {
|
||||||
writeJSON(w, status, map[string]string{"error": message})
|
writeJSON(w, status, map[string]string{"error": message})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,31 @@ func TestListTenantAuditReturnsEntries(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListTenantAuditReturnsEmptyArray(t *testing.T) {
|
||||||
|
handler := NewHandler(&stubRepository{})
|
||||||
|
req := requestWithPrincipalAndMembership(http.MethodGet, "/tenants/tenant-1/audit", "tenant-1", "owner")
|
||||||
|
req.SetPathValue("tenantID", "tenant-1")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ListTenantAudit(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
var response struct {
|
||||||
|
Data []Entry `json:"data"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(rec.Body.Bytes(), &response); err != nil {
|
||||||
|
t.Fatalf("unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
if response.Data == nil {
|
||||||
|
t.Fatalf("data = nil, want empty array")
|
||||||
|
}
|
||||||
|
if len(response.Data) != 0 {
|
||||||
|
t.Fatalf("len(data) = %d, want 0", len(response.Data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestListTenantAuditReturnsForbiddenForViewer(t *testing.T) {
|
func TestListTenantAuditReturnsForbiddenForViewer(t *testing.T) {
|
||||||
handler := NewHandler(&stubRepository{})
|
handler := NewHandler(&stubRepository{})
|
||||||
req := requestWithPrincipalAndMembership(http.MethodGet, "/tenants/tenant-1/audit", "tenant-1", "viewer")
|
req := requestWithPrincipalAndMembership(http.MethodGet, "/tenants/tenant-1/audit", "tenant-1", "viewer")
|
||||||
@@ -84,4 +109,4 @@ type stubRepository struct {
|
|||||||
|
|
||||||
func (s *stubRepository) ListTenantAudit(_ context.Context, _ string, _ int, _ int) ([]Entry, error) {
|
func (s *stubRepository) ListTenantAudit(_ context.Context, _ string, _ int, _ int) ([]Entry, error) {
|
||||||
return s.entries, s.err
|
return s.entries, s.err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ func (h Handler) ListTemplates(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeError(w, http.StatusInternalServerError, "templates_list_failed")
|
writeError(w, http.StatusInternalServerError, "templates_list_failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if templates == nil {
|
||||||
|
templates = []Template{}
|
||||||
|
}
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, map[string][]Template{"data": templates})
|
writeJSON(w, http.StatusOK, map[string][]Template{"data": templates})
|
||||||
}
|
}
|
||||||
@@ -186,4 +189,4 @@ func writeJSON(w http.ResponseWriter, status int, body any) {
|
|||||||
|
|
||||||
func writeError(w http.ResponseWriter, status int, message string) {
|
func writeError(w http.ResponseWriter, status int, message string) {
|
||||||
writeJSON(w, status, map[string]string{"error": message})
|
writeJSON(w, status, map[string]string{"error": message})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func TestListTemplatesReturnsTemplates(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestListTemplatesReturnsEmptyList(t *testing.T) {
|
func TestListTemplatesReturnsEmptyList(t *testing.T) {
|
||||||
handler := NewHandler(&stubRepository{templates: []Template{}})
|
handler := NewHandler(&stubRepository{})
|
||||||
req := httptest.NewRequest(http.MethodGet, "/internal/templates", nil)
|
req := httptest.NewRequest(http.MethodGet, "/internal/templates", nil)
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
@@ -57,6 +57,9 @@ func TestListTemplatesReturnsEmptyList(t *testing.T) {
|
|||||||
if rec.Code != http.StatusOK {
|
if rec.Code != http.StatusOK {
|
||||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
if got := rec.Body.String(); got != "{\"data\":[]}\n" {
|
||||||
|
t.Fatalf("body = %q, want empty data array", got)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateTemplateReturnsCreated(t *testing.T) {
|
func TestCreateTemplateReturnsCreated(t *testing.T) {
|
||||||
@@ -205,4 +208,4 @@ func (s *stubRepository) Upsert(_ context.Context, _ Template) (Template, error)
|
|||||||
|
|
||||||
func (s *stubRepository) Delete(_ context.Context, _ string) (bool, error) {
|
func (s *stubRepository) Delete(_ context.Context, _ string) (bool, error) {
|
||||||
return s.deleteResult, nil
|
return s.deleteResult, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ func (h Handler) ListProjectVMs(w http.ResponseWriter, r *http.Request) {
|
|||||||
writeError(w, http.StatusNotFound, "project_not_found")
|
writeError(w, http.StatusNotFound, "project_not_found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if vms == nil {
|
||||||
|
vms = []VM{}
|
||||||
|
}
|
||||||
|
|
||||||
writeJSON(w, http.StatusOK, map[string][]VM{"data": vms})
|
writeJSON(w, http.StatusOK, map[string][]VM{"data": vms})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,22 @@ func TestListProjectVMsReturnsVisibleVMs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestListProjectVMsReturnsEmptyArray(t *testing.T) {
|
||||||
|
handler := NewHandler(&stubRepository{listFound: true})
|
||||||
|
req := requestWithPrincipal(http.MethodGet, "/projects/project-1/vms")
|
||||||
|
req.SetPathValue("projectID", "project-1")
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
handler.ListProjectVMs(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||||
|
}
|
||||||
|
if got := rec.Body.String(); got != "{\"data\":[]}\n" {
|
||||||
|
t.Fatalf("body = %q, want empty data array", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestListProjectVMsReturnsNotFoundForInaccessibleProject(t *testing.T) {
|
func TestListProjectVMsReturnsNotFoundForInaccessibleProject(t *testing.T) {
|
||||||
handler := NewHandler(&stubRepository{listFound: false})
|
handler := NewHandler(&stubRepository{listFound: false})
|
||||||
req := requestWithPrincipal(http.MethodGet, "/projects/project-1/vms")
|
req := requestWithPrincipal(http.MethodGet, "/projects/project-1/vms")
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ type ProvisionClientFactory func(cluster.Cluster) (ProvisionProxmoxClient, error
|
|||||||
|
|
||||||
type ProvisionProxmoxClient interface {
|
type ProvisionProxmoxClient interface {
|
||||||
CloneVM(ctx context.Context, node string, templateVMID int, newVMID int, name string) (string, error)
|
CloneVM(ctx context.Context, node string, templateVMID int, newVMID int, name string) (string, error)
|
||||||
|
ConfigureHardware(ctx context.Context, node string, vmid int, cfg proxmox.HardwareConfig) (string, error)
|
||||||
|
ResizeDisk(ctx context.Context, node string, vmid int, disk string, sizeGB int) (string, error)
|
||||||
ConfigureCloudInit(ctx context.Context, node string, vmid int, cfg proxmox.CloudInitConfig) (string, error)
|
ConfigureCloudInit(ctx context.Context, node string, vmid int, cfg proxmox.CloudInitConfig) (string, error)
|
||||||
StartVM(ctx context.Context, node string, vmid int) (string, error)
|
StartVM(ctx context.Context, node string, vmid int) (string, error)
|
||||||
StopVM(ctx context.Context, node string, vmid int) (string, error)
|
StopVM(ctx context.Context, node string, vmid int) (string, error)
|
||||||
@@ -174,15 +176,15 @@ func (h Handler) CreateVM(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vm, err := h.repository.InsertVM(r.Context(), InsertVMRecord{
|
vm, err := h.repository.InsertVM(r.Context(), InsertVMRecord{
|
||||||
ProjectID: projectID,
|
ProjectID: projectID,
|
||||||
ClusterID: template.ClusterID,
|
ClusterID: template.ClusterID,
|
||||||
ProxmoxVMID: vmid,
|
ProxmoxVMID: vmid,
|
||||||
Node: req.Node,
|
Node: req.Node,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Status: "provisioning",
|
Status: "provisioning",
|
||||||
VCPU: req.VCPU,
|
VCPU: req.VCPU,
|
||||||
RAMMB: req.RAMMB,
|
RAMMB: req.RAMMB,
|
||||||
DiskGB: req.DiskGB,
|
DiskGB: req.DiskGB,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, http.StatusInternalServerError, "vm_insert_failed")
|
writeError(w, http.StatusInternalServerError, "vm_insert_failed")
|
||||||
@@ -211,6 +213,19 @@ func (h Handler) CreateVM(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if _, err := proxmoxClient.ConfigureHardware(r.Context(), req.Node, vmid, proxmox.HardwareConfig{
|
||||||
|
Cores: req.VCPU,
|
||||||
|
Memory: req.RAMMB,
|
||||||
|
}); err != nil {
|
||||||
|
writeError(w, http.StatusBadGateway, "proxmox_hardware_failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := proxmoxClient.ResizeDisk(r.Context(), req.Node, vmid, "scsi0", req.DiskGB); err != nil {
|
||||||
|
writeError(w, http.StatusBadGateway, "proxmox_resize_failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ciCfg := proxmox.CloudInitConfig{
|
ciCfg := proxmox.CloudInitConfig{
|
||||||
CIUser: req.CIUser,
|
CIUser: req.CIUser,
|
||||||
IPConfig0: req.IPConfig0,
|
IPConfig0: req.IPConfig0,
|
||||||
@@ -273,23 +288,23 @@ func (h Handler) CreateVM(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
writeJSON(w, http.StatusAccepted, map[string]any{
|
writeJSON(w, http.StatusAccepted, map[string]any{
|
||||||
"vm": vm,
|
"vm": vm,
|
||||||
"upid": startUPID,
|
"upid": startUPID,
|
||||||
"clone_upid": cloneUPID,
|
"clone_upid": cloneUPID,
|
||||||
"status": "provisioning",
|
"status": "provisioning",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type createVMRequest struct {
|
type createVMRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
TemplateID string `json:"template_id"`
|
TemplateID string `json:"template_id"`
|
||||||
Node string `json:"node"`
|
Node string `json:"node"`
|
||||||
VCPU int `json:"vcpu"`
|
VCPU int `json:"vcpu"`
|
||||||
RAMMB int `json:"ram_mb"`
|
RAMMB int `json:"ram_mb"`
|
||||||
DiskGB int `json:"disk_gb"`
|
DiskGB int `json:"disk_gb"`
|
||||||
SSHKeyID string `json:"ssh_key_id"`
|
SSHKeyID string `json:"ssh_key_id"`
|
||||||
CIUser string `json:"ci_user"`
|
CIUser string `json:"ci_user"`
|
||||||
IPConfig0 string `json:"ip_config0"`
|
IPConfig0 string `json:"ip_config0"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func DefaultProvisionClientFactory(cluster cluster.Cluster) (ProvisionProxmoxClient, error) {
|
func DefaultProvisionClientFactory(cluster cluster.Cluster) (ProvisionProxmoxClient, error) {
|
||||||
@@ -306,11 +321,11 @@ func NewSQLProvisionAuditWriter(db *sql.DB) SQLProvisionAuditWriter {
|
|||||||
|
|
||||||
func (w SQLProvisionAuditWriter) WriteVMProvisionAudit(ctx context.Context, event VMProvisionAuditEvent) error {
|
func (w SQLProvisionAuditWriter) WriteVMProvisionAudit(ctx context.Context, event VMProvisionAuditEvent) error {
|
||||||
metadata, err := json.Marshal(map[string]any{
|
metadata, err := json.Marshal(map[string]any{
|
||||||
"cluster_id": event.ClusterID,
|
"cluster_id": event.ClusterID,
|
||||||
"template_id": event.TemplateID,
|
"template_id": event.TemplateID,
|
||||||
"node": event.Node,
|
"node": event.Node,
|
||||||
"upid": event.UPID,
|
"upid": event.UPID,
|
||||||
"proxmox_vmid": event.ProxmoxVMID,
|
"proxmox_vmid": event.ProxmoxVMID,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -428,4 +443,4 @@ func (h Handler) DeleteVM(w http.ResponseWriter, r *http.Request) {
|
|||||||
"upid": deleteUPID,
|
"upid": deleteUPID,
|
||||||
"status": "deleting",
|
"status": "deleting",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,8 +98,9 @@ func NewClient(cluster cluster.Cluster, opts ...Option) (*Client, error) {
|
|||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
Proxy: http.ProxyFromEnvironment,
|
Proxy: http.ProxyFromEnvironment,
|
||||||
TLSClientConfig: &tls.Config{
|
TLSClientConfig: &tls.Config{
|
||||||
MinVersion: tls.VersionTLS12,
|
MinVersion: tls.VersionTLS12,
|
||||||
RootCAs: cfg.rootCAs,
|
RootCAs: cfg.rootCAs,
|
||||||
|
InsecureSkipVerify: true,
|
||||||
VerifyConnection: func(state tls.ConnectionState) error {
|
VerifyConnection: func(state tls.ConnectionState) error {
|
||||||
return verifyFingerprint(state, fingerprint)
|
return verifyFingerprint(state, fingerprint)
|
||||||
},
|
},
|
||||||
@@ -139,7 +140,7 @@ func (c *Client) GetTaskStatus(ctx context.Context, node string, upid string) (T
|
|||||||
response, err := c.Get(ctx, fmt.Sprintf(
|
response, err := c.Get(ctx, fmt.Sprintf(
|
||||||
"/nodes/%s/tasks/%s/status",
|
"/nodes/%s/tasks/%s/status",
|
||||||
url.PathEscape(node),
|
url.PathEscape(node),
|
||||||
url.PathEscape(upid),
|
upid,
|
||||||
))
|
))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return TaskStatus{}, err
|
return TaskStatus{}, err
|
||||||
@@ -256,6 +257,66 @@ func (c *Client) CloneVM(ctx context.Context, node string, templateVMID int, new
|
|||||||
return decodeUPID(response.Body)
|
return decodeUPID(response.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) ConfigureHardware(ctx context.Context, node string, vmid int, cfg HardwareConfig) (string, error) {
|
||||||
|
requestBody := map[string]any{}
|
||||||
|
if cfg.Cores > 0 {
|
||||||
|
requestBody["cores"] = cfg.Cores
|
||||||
|
}
|
||||||
|
if cfg.Memory > 0 {
|
||||||
|
requestBody["memory"] = cfg.Memory
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := json.Marshal(requestBody)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.Post(ctx, fmt.Sprintf(
|
||||||
|
"/nodes/%s/qemu/%d/config",
|
||||||
|
url.PathEscape(node),
|
||||||
|
vmid,
|
||||||
|
), body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode >= http.StatusBadRequest {
|
||||||
|
_, _ = io.Copy(io.Discard, response.Body)
|
||||||
|
return "", fmt.Errorf("proxmox returned %s", response.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeUPID(response.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ResizeDisk(ctx context.Context, node string, vmid int, disk string, sizeGB int) (string, error) {
|
||||||
|
requestBody := map[string]any{
|
||||||
|
"disk": disk,
|
||||||
|
"size": fmt.Sprintf("%dG", sizeGB),
|
||||||
|
}
|
||||||
|
body, err := json.Marshal(requestBody)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.Put(ctx, fmt.Sprintf(
|
||||||
|
"/nodes/%s/qemu/%d/resize",
|
||||||
|
url.PathEscape(node),
|
||||||
|
vmid,
|
||||||
|
), body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode >= http.StatusBadRequest {
|
||||||
|
_, _ = io.Copy(io.Discard, response.Body)
|
||||||
|
return "", fmt.Errorf("proxmox returned %s", response.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeUPID(response.Body)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) ConfigureCloudInit(ctx context.Context, node string, vmid int, cfg CloudInitConfig) (string, error) {
|
func (c *Client) ConfigureCloudInit(ctx context.Context, node string, vmid int, cfg CloudInitConfig) (string, error) {
|
||||||
requestBody := map[string]any{
|
requestBody := map[string]any{
|
||||||
"ciuser": cfg.CIUser,
|
"ciuser": cfg.CIUser,
|
||||||
@@ -327,6 +388,11 @@ type CloudInitConfig struct {
|
|||||||
Hostname string `json:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HardwareConfig struct {
|
||||||
|
Cores int
|
||||||
|
Memory int
|
||||||
|
}
|
||||||
|
|
||||||
func decodeUPID(body io.Reader) (string, error) {
|
func decodeUPID(body io.Reader) (string, error) {
|
||||||
var response struct {
|
var response struct {
|
||||||
Data string `json:"data"`
|
Data string `json:"data"`
|
||||||
@@ -364,11 +430,11 @@ func (c *Client) GetVNCTicket(ctx context.Context, node string, vmid int) (VNCIn
|
|||||||
|
|
||||||
var result struct {
|
var result struct {
|
||||||
Data struct {
|
Data struct {
|
||||||
Port int `json:"port"`
|
Port int `json:"port"`
|
||||||
Ticket string `json:"ticket"`
|
Ticket string `json:"ticket"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Cert string `json:"cert"`
|
Cert string `json:"cert"`
|
||||||
UPID string `json:"upid"`
|
UPID string `json:"upid"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
|
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
|
||||||
|
|||||||
@@ -41,6 +41,36 @@ func TestClientAcceptsMatchingFingerprintAndSendsTokenHeader(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientAcceptsPinnedSelfSignedCertificate(t *testing.T) {
|
||||||
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/api2/json/version" {
|
||||||
|
t.Fatalf("path = %q, want /api2/json/version", r.URL.Path)
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"data":{"version":"9.2"}}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client, err := NewClient(cluster.Cluster{
|
||||||
|
APIEndpoint: server.URL + "/api2/json",
|
||||||
|
TLSFingerprint: fingerprintForServer(server),
|
||||||
|
TokenID: "root@pam!proxui",
|
||||||
|
TokenSecret: "secret-token",
|
||||||
|
}, WithTimeout(time.Second), WithRetries(0))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("NewClient() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.Get(context.Background(), "/version")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Get() error = %v", err)
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
|
||||||
|
if response.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("status = %d, want %d", response.StatusCode, http.StatusOK)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestClientRejectsMismatchedFingerprint(t *testing.T) {
|
func TestClientRejectsMismatchedFingerprint(t *testing.T) {
|
||||||
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
@@ -86,8 +116,9 @@ func TestClientRetriesServerErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestClientGetsTaskStatus(t *testing.T) {
|
func TestClientGetsTaskStatus(t *testing.T) {
|
||||||
|
upid := "UPID:pve:1:2:3:qmstart:100:root@pam!ui:"
|
||||||
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/api2/json/nodes/pve/tasks/UPID:pve:1/status" {
|
if r.URL.Path != "/api2/json/nodes/pve/tasks/"+upid+"/status" {
|
||||||
t.Fatalf("path = %q", r.URL.Path)
|
t.Fatalf("path = %q", r.URL.Path)
|
||||||
}
|
}
|
||||||
_, _ = w.Write([]byte(`{"data":{"status":"stopped","exitstatus":"OK"}}`))
|
_, _ = w.Write([]byte(`{"data":{"status":"stopped","exitstatus":"OK"}}`))
|
||||||
@@ -95,7 +126,7 @@ func TestClientGetsTaskStatus(t *testing.T) {
|
|||||||
defer server.Close()
|
defer server.Close()
|
||||||
|
|
||||||
client := newTestClient(t, server, fingerprintForServer(server))
|
client := newTestClient(t, server, fingerprintForServer(server))
|
||||||
status, err := client.GetTaskStatus(context.Background(), "pve", "UPID:pve:1")
|
status, err := client.GetTaskStatus(context.Background(), "pve", upid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("GetTaskStatus() error = %v", err)
|
t.Fatalf("GetTaskStatus() error = %v", err)
|
||||||
}
|
}
|
||||||
@@ -149,6 +180,70 @@ func TestClientGetsVMStatus(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClientConfiguresHardware(t *testing.T) {
|
||||||
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
t.Fatalf("method = %s, want POST", r.Method)
|
||||||
|
}
|
||||||
|
if r.URL.Path != "/api2/json/nodes/pve/qemu/100/config" {
|
||||||
|
t.Fatalf("path = %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read body: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(body), `"cores":4`) {
|
||||||
|
t.Fatalf("body = %s, want cores", body)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(body), `"memory":6144`) {
|
||||||
|
t.Fatalf("body = %s, want memory", body)
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"data":"UPID:pve:config"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := newTestClient(t, server, fingerprintForServer(server))
|
||||||
|
upid, err := client.ConfigureHardware(context.Background(), "pve", 100, HardwareConfig{Cores: 4, Memory: 6144})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ConfigureHardware() error = %v", err)
|
||||||
|
}
|
||||||
|
if upid != "UPID:pve:config" {
|
||||||
|
t.Fatalf("UPID = %q, want UPID:pve:config", upid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClientResizesDisk(t *testing.T) {
|
||||||
|
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPut {
|
||||||
|
t.Fatalf("method = %s, want PUT", r.Method)
|
||||||
|
}
|
||||||
|
if r.URL.Path != "/api2/json/nodes/pve/qemu/100/resize" {
|
||||||
|
t.Fatalf("path = %q", r.URL.Path)
|
||||||
|
}
|
||||||
|
body, err := io.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read body: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(body), `"disk":"scsi0"`) {
|
||||||
|
t.Fatalf("body = %s, want disk", body)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(body), `"size":"30G"`) {
|
||||||
|
t.Fatalf("body = %s, want size", body)
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"data":"UPID:pve:resize"}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := newTestClient(t, server, fingerprintForServer(server))
|
||||||
|
upid, err := client.ResizeDisk(context.Background(), "pve", 100, "scsi0", 30)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ResizeDisk() error = %v", err)
|
||||||
|
}
|
||||||
|
if upid != "UPID:pve:resize" {
|
||||||
|
t.Fatalf("UPID = %q, want UPID:pve:resize", upid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewClientRejectsInvalidFingerprint(t *testing.T) {
|
func TestNewClientRejectsInvalidFingerprint(t *testing.T) {
|
||||||
_, err := NewClient(cluster.Cluster{
|
_, err := NewClient(cluster.Cluster{
|
||||||
APIEndpoint: "https://pve.example.test:8006/api2/json",
|
APIEndpoint: "https://pve.example.test:8006/api2/json",
|
||||||
|
|||||||
Reference in New Issue
Block a user