mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-09 15:25:01 +00:00
fix(core): improve error handling while waiting for PVE tasks to complete (#526)
This commit is contained in:
parent
5556b17a1e
commit
6f02df4440
@ -25,9 +25,6 @@ import (
|
|||||||
"github.com/bpg/terraform-provider-proxmox/utils"
|
"github.com/bpg/terraform-provider-proxmox/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNoDataObjectInResponse is returned when the server does not include a data object in the response.
|
|
||||||
var ErrNoDataObjectInResponse = errors.New("the server did not include a data object in the response")
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
basePathJSONAPI = "api2/json"
|
basePathJSONAPI = "api2/json"
|
||||||
)
|
)
|
||||||
@ -292,7 +289,7 @@ func (c *client) IsRootTicket() bool {
|
|||||||
// validateResponseCode ensures that a response is valid.
|
// validateResponseCode ensures that a response is valid.
|
||||||
func validateResponseCode(res *http.Response) error {
|
func validateResponseCode(res *http.Response) error {
|
||||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||||
status := strings.TrimPrefix(res.Status, fmt.Sprintf("%d ", res.StatusCode))
|
msg := strings.TrimPrefix(res.Status, fmt.Sprintf("%d ", res.StatusCode))
|
||||||
|
|
||||||
errRes := &ErrorResponseBody{}
|
errRes := &ErrorResponseBody{}
|
||||||
err := json.NewDecoder(res.Body).Decode(errRes)
|
err := json.NewDecoder(res.Body).Decode(errRes)
|
||||||
@ -304,10 +301,13 @@ func validateResponseCode(res *http.Response) error {
|
|||||||
errList = append(errList, fmt.Sprintf("%s: %s", k, strings.TrimRight(v, "\n\r")))
|
errList = append(errList, fmt.Sprintf("%s: %s", k, strings.TrimRight(v, "\n\r")))
|
||||||
}
|
}
|
||||||
|
|
||||||
status = fmt.Sprintf("%s (%s)", status, strings.Join(errList, " - "))
|
msg = fmt.Sprintf("%s (%s)", msg, strings.Join(errList, " - "))
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("received an HTTP %d response - Reason: %s", res.StatusCode, status)
|
return &HTTPError{
|
||||||
|
Code: res.StatusCode,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
29
proxmox/api/errors.go
Normal file
29
proxmox/api/errors.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package api
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// Error is a sentinel error type for API errors.
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
func (err Error) Error() string {
|
||||||
|
return string(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrNoDataObjectInResponse is returned when the server does not include a data object in the response.
|
||||||
|
const ErrNoDataObjectInResponse Error = "the server did not include a data object in the response"
|
||||||
|
|
||||||
|
// HTTPError is a generic error type for HTTP errors.
|
||||||
|
type HTTPError struct {
|
||||||
|
Code int
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err *HTTPError) Error() string {
|
||||||
|
return fmt.Sprintf("received an HTTP %d response - Reason: %s", err.Code, err.Message)
|
||||||
|
}
|
@ -282,7 +282,6 @@ func (c *Client) APIUpload(
|
|||||||
}
|
}
|
||||||
|
|
||||||
err = c.Tasks().WaitForTask(ctx, *resBody.UploadID, uploadTimeout, 5)
|
err = c.Tasks().WaitForTask(ctx, *resBody.UploadID, uploadTimeout, 5)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error uploading file to datastore %s: failed waiting for upload - %w", datastoreID, err)
|
return nil, fmt.Errorf("error uploading file to datastore %s: failed waiting for upload - %w", datastoreID, err)
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ package tasks
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -28,7 +29,7 @@ func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatus
|
|||||||
resBody,
|
resBody,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error retrievinf task status: %w", err)
|
return nil, fmt.Errorf("error retrieving task status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if resBody.Data == nil {
|
if resBody.Data == nil {
|
||||||
@ -45,10 +46,24 @@ func (c *Client) WaitForTask(ctx context.Context, upid string, timeoutSec, delay
|
|||||||
timeStart := time.Now()
|
timeStart := time.Now()
|
||||||
timeElapsed := timeStart.Sub(timeStart)
|
timeElapsed := timeStart.Sub(timeStart)
|
||||||
|
|
||||||
|
isCriticalError := func(err error) bool {
|
||||||
|
var target *api.HTTPError
|
||||||
|
if errors.As(err, &target) {
|
||||||
|
if target.Code != http.StatusBadRequest {
|
||||||
|
// this is a special case to account for eventual consistency
|
||||||
|
// when creating a task -- the task may not be available via status API
|
||||||
|
// immediately after creation
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err != nil
|
||||||
|
}
|
||||||
|
|
||||||
for timeElapsed.Seconds() < timeMax {
|
for timeElapsed.Seconds() < timeMax {
|
||||||
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
if int64(timeElapsed.Seconds())%timeDelay == 0 {
|
||||||
status, err := c.GetTaskStatus(ctx, upid)
|
status, err := c.GetTaskStatus(ctx, upid)
|
||||||
if err != nil {
|
if isCriticalError(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user