Skip to content

Commit

Permalink
fix(core): improve error handling while waiting for PVE tasks to comp…
Browse files Browse the repository at this point in the history
…lete (#526)
  • Loading branch information
bpg authored Aug 29, 2023
1 parent 5556b17 commit 6f02df4
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 9 deletions.
12 changes: 6 additions & 6 deletions proxmox/api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down
29 changes: 29 additions & 0 deletions proxmox/api/errors.go
Original file line number Diff line number Diff line change
@@ -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)
}
1 change: 0 additions & 1 deletion proxmox/nodes/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
19 changes: 17 additions & 2 deletions proxmox/nodes/tasks/tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package tasks

import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
Expand All @@ -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 {
Expand All @@ -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
}

Expand Down

0 comments on commit 6f02df4

Please sign in to comment.