0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-07-04 04:22:59 +00:00

fix(core): improve error handling while waiting for PVE tasks to complete (#526)

This commit is contained in:
Pavel Boldyrev 2023-08-29 19:06:34 -04:00 committed by GitHub
parent 5556b17a1e
commit 6f02df4440
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 52 additions and 9 deletions

View File

@ -25,9 +25,6 @@ import (
"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 (
basePathJSONAPI = "api2/json"
)
@ -292,7 +289,7 @@ func (c *client) IsRootTicket() bool {
// validateResponseCode ensures that a response is valid.
func validateResponseCode(res *http.Response) error {
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{}
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")))
}
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

29
proxmox/api/errors.go Normal file
View 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)
}

View File

@ -282,7 +282,6 @@ func (c *Client) APIUpload(
}
err = c.Tasks().WaitForTask(ctx, *resBody.UploadID, uploadTimeout, 5)
if err != nil {
return nil, fmt.Errorf("error uploading file to datastore %s: failed waiting for upload - %w", datastoreID, err)
}

View File

@ -8,6 +8,7 @@ package tasks
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
@ -28,7 +29,7 @@ func (c *Client) GetTaskStatus(ctx context.Context, upid string) (*GetTaskStatus
resBody,
)
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 {
@ -45,10 +46,24 @@ func (c *Client) WaitForTask(ctx context.Context, upid string, timeoutSec, delay
timeStart := time.Now()
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 {
if int64(timeElapsed.Seconds())%timeDelay == 0 {
status, err := c.GetTaskStatus(ctx, upid)
if err != nil {
if isCriticalError(err) {
return err
}