mirror of
https://github.com/cloudnative-pg/plugin-barman-cloud.git
synced 2026-07-08 10:42:21 +02:00
Compare commits
2 Commits
18e0ae58d9
...
1528faebbd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1528faebbd | ||
|
|
40f7ce24ab |
@ -176,25 +176,6 @@ spec:
|
|||||||
format: int32
|
format: int32
|
||||||
minimum: 1
|
minimum: 1
|
||||||
type: integer
|
type: integer
|
||||||
restoreAdditionalCommandArgs:
|
|
||||||
description: |-
|
|
||||||
Additional arguments that can be appended to the 'barman-cloud-restore'
|
|
||||||
command-line invocation. These arguments provide flexibility to customize
|
|
||||||
the data restore process further, according to specific requirements or
|
|
||||||
configurations.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
In a scenario where specialized restore options are required, such as setting
|
|
||||||
a specific read timeout or defining custom behavior, users can use this field
|
|
||||||
to specify additional command arguments.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
It's essential to ensure that the provided arguments are valid and supported
|
|
||||||
by the 'barman-cloud-restore' command, to avoid potential errors or unintended
|
|
||||||
behavior during execution.
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
type: object
|
type: object
|
||||||
destinationPath:
|
destinationPath:
|
||||||
description: |-
|
description: |-
|
||||||
|
|||||||
@ -20,38 +20,10 @@ SPDX-License-Identifier: Apache-2.0
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
barmanRestorer "github.com/cloudnative-pg/barman-cloud/pkg/restorer"
|
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
)
|
)
|
||||||
|
|
||||||
// classifyWALRestoreError maps an error returned by the WAL
|
|
||||||
// restorer to a gRPC-coded error so the caller can tell terminal
|
|
||||||
// failures apart from transient ones via the status code.
|
|
||||||
func classifyWALRestoreError(walName string, walErr error) error {
|
|
||||||
switch {
|
|
||||||
case errors.Is(walErr, barmanRestorer.ErrWALNotFound):
|
|
||||||
return newWALNotFoundError(walName)
|
|
||||||
case errors.Is(walErr, barmanRestorer.ErrInvalidWalName):
|
|
||||||
// A malformed WAL name will never become valid on retry.
|
|
||||||
return newInvalidWALNameError(walName, walErr)
|
|
||||||
case errors.Is(walErr, barmanRestorer.ErrConnectivity),
|
|
||||||
errors.Is(walErr, barmanRestorer.ErrGeneric):
|
|
||||||
// barman-cloud exit codes 2 (connectivity) and 4
|
|
||||||
// (generic) both surface conditions that are retryable
|
|
||||||
// in practice — barman uses the "generic" bucket for
|
|
||||||
// some connection-class failures too, not just exit 2.
|
|
||||||
return newUnavailableError(walName, walErr)
|
|
||||||
default:
|
|
||||||
// Unrecognized exit codes and unexpected failures (e.g.
|
|
||||||
// the barman-cloud command could not be executed). No
|
|
||||||
// positive signal that retry would help.
|
|
||||||
return newInternalWALRestoreError(walName, walErr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrEndOfWALStreamReached is returned when end of WAL is detected in the cloud archive.
|
// ErrEndOfWALStreamReached is returned when end of WAL is detected in the cloud archive.
|
||||||
var ErrEndOfWALStreamReached = status.Errorf(codes.OutOfRange, "end of WAL reached")
|
var ErrEndOfWALStreamReached = status.Errorf(codes.OutOfRange, "end of WAL reached")
|
||||||
|
|
||||||
@ -63,18 +35,19 @@ var ErrEndOfWALStreamReached = status.Errorf(codes.OutOfRange, "end of WAL reach
|
|||||||
var ErrMissingPermissions = status.Errorf(codes.FailedPrecondition,
|
var ErrMissingPermissions = status.Errorf(codes.FailedPrecondition,
|
||||||
"no permission to download the backup credentials, retrying")
|
"no permission to download the backup credentials, retrying")
|
||||||
|
|
||||||
// newWALNotFoundError reports that the requested WAL file is not
|
// newWALNotFoundError returns an error indicating that the
|
||||||
// present in the object store. Emits codes.NotFound: this is a
|
// requested WAL file is not present in the object store.
|
||||||
// terminal condition (the file won't appear on retry).
|
// It carries a gRPC NotFound status so the operator can
|
||||||
|
// treat it as a terminal condition.
|
||||||
func newWALNotFoundError(walName string) error {
|
func newWALNotFoundError(walName string) error {
|
||||||
return status.Errorf(codes.NotFound, "wal %q not found", walName)
|
return status.Errorf(codes.NotFound, "wal %q not found", walName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newUnavailableError reports that downloading the WAL file failed
|
// newUnavailableError returns an error indicating that
|
||||||
// for a reason expected to be transient (a barman-cloud connectivity
|
// downloading the given WAL file failed for a transient
|
||||||
// blip, or a generic exit code that in practice covers retryable
|
// reason (e.g. a connectivity blip). It carries a gRPC
|
||||||
// conditions too). Emits codes.Unavailable: per gRPC conventions,
|
// Unavailable status so the operator will retry the
|
||||||
// the canonical signal for "retry may succeed".
|
// request.
|
||||||
func newUnavailableError(walName string, err error) error {
|
func newUnavailableError(walName string, err error) error {
|
||||||
return status.Errorf(
|
return status.Errorf(
|
||||||
codes.Unavailable,
|
codes.Unavailable,
|
||||||
@ -84,9 +57,10 @@ func newUnavailableError(walName string, err error) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newInvalidWALNameError reports that the requested WAL name is
|
// newInvalidWALNameError returns an error indicating that
|
||||||
// not a valid name. Emits codes.InvalidArgument: this is a
|
// the requested WAL name is not valid. It carries a gRPC
|
||||||
// terminal condition (the same name won't become valid on retry).
|
// InvalidArgument status so the operator treats it as a
|
||||||
|
// terminal condition.
|
||||||
func newInvalidWALNameError(walName string, err error) error {
|
func newInvalidWALNameError(walName string, err error) error {
|
||||||
return status.Errorf(
|
return status.Errorf(
|
||||||
codes.InvalidArgument,
|
codes.InvalidArgument,
|
||||||
@ -96,10 +70,10 @@ func newInvalidWALNameError(walName string, err error) error {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newInternalWALRestoreError reports that downloading the WAL
|
// newInternalWALRestoreError returns an error indicating
|
||||||
// file failed for an unclassified reason. Emits codes.Internal:
|
// that downloading the given WAL file failed for an
|
||||||
// we have no positive signal that retry would help, so by gRPC
|
// unclassified reason. It carries a gRPC Internal status
|
||||||
// convention this is treated as terminal.
|
// so the operator treats it as a terminal condition.
|
||||||
func newInternalWALRestoreError(walName string, err error) error {
|
func newInternalWALRestoreError(walName string, err error) error {
|
||||||
return status.Errorf(
|
return status.Errorf(
|
||||||
codes.Internal,
|
codes.Internal,
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © contributors to CloudNativePG, established as
|
|
||||||
CloudNativePG a Series of LF Projects, LLC.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
barmanRestorer "github.com/cloudnative-pg/barman-cloud/pkg/restorer"
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = Describe("classifyWALRestoreError", func() {
|
|
||||||
const walName = "000000010000000000000001"
|
|
||||||
|
|
||||||
DescribeTable(
|
|
||||||
"maps barman restorer sentinels to gRPC status codes",
|
|
||||||
func(walErr error, expectedCode codes.Code) {
|
|
||||||
got := classifyWALRestoreError(walName, walErr)
|
|
||||||
Expect(got).To(HaveOccurred())
|
|
||||||
|
|
||||||
st, ok := status.FromError(got)
|
|
||||||
Expect(ok).To(BeTrue(), "returned error must carry a gRPC status")
|
|
||||||
Expect(st.Code()).To(Equal(expectedCode))
|
|
||||||
Expect(st.Message()).To(ContainSubstring(walName))
|
|
||||||
},
|
|
||||||
Entry("ErrWALNotFound -> NotFound",
|
|
||||||
fmt.Errorf("object storage or file not found: %w", barmanRestorer.ErrWALNotFound),
|
|
||||||
codes.NotFound),
|
|
||||||
Entry("ErrInvalidWalName -> InvalidArgument",
|
|
||||||
fmt.Errorf("invalid name for a WAL file: %w", barmanRestorer.ErrInvalidWalName),
|
|
||||||
codes.InvalidArgument),
|
|
||||||
Entry("ErrConnectivity -> Unavailable",
|
|
||||||
fmt.Errorf("connectivity failure, retrying: %w", barmanRestorer.ErrConnectivity),
|
|
||||||
codes.Unavailable),
|
|
||||||
Entry("ErrGeneric -> Unavailable (barman uses exit 4 for some retryable cases too)",
|
|
||||||
fmt.Errorf("generic error: %w", barmanRestorer.ErrGeneric),
|
|
||||||
codes.Unavailable),
|
|
||||||
Entry("unknown error -> Internal",
|
|
||||||
errors.New("something we did not classify"),
|
|
||||||
codes.Internal),
|
|
||||||
)
|
|
||||||
|
|
||||||
It("matches the sentinel even through several wrapping layers", func() {
|
|
||||||
// The plugin wraps barman errors via fmt.Errorf("...: %w", ...);
|
|
||||||
// classification must keep working if more wraps appear above.
|
|
||||||
inner := fmt.Errorf("connectivity failure, retrying: %w", barmanRestorer.ErrConnectivity)
|
|
||||||
wrapped := fmt.Errorf("while restoring WAL %s: %w", walName, inner)
|
|
||||||
|
|
||||||
got := classifyWALRestoreError(walName, wrapped)
|
|
||||||
st, ok := status.FromError(got)
|
|
||||||
Expect(ok).To(BeTrue())
|
|
||||||
Expect(st.Code()).To(Equal(codes.Unavailable))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("treats ErrWALNotFound as terminal even when the error chain mentions other sentinels in its message", func() {
|
|
||||||
// Defensive: if the underlying error stringifies to something
|
|
||||||
// resembling another sentinel's message, the switch must still
|
|
||||||
// match by identity (errors.Is), not by substring.
|
|
||||||
walErr := fmt.Errorf("not found, looks like a connectivity failure: %w", barmanRestorer.ErrWALNotFound)
|
|
||||||
|
|
||||||
got := classifyWALRestoreError(walName, walErr)
|
|
||||||
st, _ := status.FromError(got)
|
|
||||||
Expect(st.Code()).To(Equal(codes.NotFound))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © contributors to CloudNativePG, established as
|
|
||||||
CloudNativePG a Series of LF Projects, LLC.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
SPDX-License-Identifier: Apache-2.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
package common
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo/v2"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCommon(t *testing.T) {
|
|
||||||
RegisterFailHandler(Fail)
|
|
||||||
RunSpecs(t, "Common Suite")
|
|
||||||
}
|
|
||||||
@ -365,7 +365,28 @@ func (w WALServiceImplementation) restoreFromBarmanObjectStore(
|
|||||||
// is the one that PostgreSQL has requested to restore.
|
// is the one that PostgreSQL has requested to restore.
|
||||||
// The failure has already been logged in walRestorer.RestoreList method
|
// The failure has already been logged in walRestorer.RestoreList method
|
||||||
if walStatus[0].Err != nil {
|
if walStatus[0].Err != nil {
|
||||||
return classifyWALRestoreError(walStatus[0].WalName, walStatus[0].Err)
|
walName := walStatus[0].WalName
|
||||||
|
walErr := walStatus[0].Err
|
||||||
|
switch {
|
||||||
|
case errors.Is(walErr, barmanRestorer.ErrWALNotFound):
|
||||||
|
// A missing WAL is a terminal condition: surface it as a
|
||||||
|
// gRPC NotFound so the operator stops retrying.
|
||||||
|
return newWALNotFoundError(walName)
|
||||||
|
case errors.Is(walErr, barmanRestorer.ErrInvalidWalName):
|
||||||
|
// A malformed WAL name will never succeed on retry.
|
||||||
|
return newInvalidWALNameError(walName, walErr)
|
||||||
|
case errors.Is(walErr, barmanRestorer.ErrConnectivity),
|
||||||
|
errors.Is(walErr, barmanRestorer.ErrGeneric):
|
||||||
|
// Connectivity and generic barman-cloud failures are
|
||||||
|
// considered transient: surface them as gRPC Unavailable
|
||||||
|
// so the operator retries the download.
|
||||||
|
return newUnavailableError(walName, walErr)
|
||||||
|
default:
|
||||||
|
// Unrecognized exit codes and unexpected failures (e.g. the
|
||||||
|
// barman-cloud command could not be executed) are surfaced
|
||||||
|
// as gRPC Internal so the operator treats them as terminal.
|
||||||
|
return newInternalWALRestoreError(walName, walErr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We skip this step if streaming connection is not available
|
// We skip this step if streaming connection is not available
|
||||||
|
|||||||
@ -175,25 +175,6 @@ spec:
|
|||||||
format: int32
|
format: int32
|
||||||
minimum: 1
|
minimum: 1
|
||||||
type: integer
|
type: integer
|
||||||
restoreAdditionalCommandArgs:
|
|
||||||
description: |-
|
|
||||||
Additional arguments that can be appended to the 'barman-cloud-restore'
|
|
||||||
command-line invocation. These arguments provide flexibility to customize
|
|
||||||
the data restore process further, according to specific requirements or
|
|
||||||
configurations.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
In a scenario where specialized restore options are required, such as setting
|
|
||||||
a specific read timeout or defining custom behavior, users can use this field
|
|
||||||
to specify additional command arguments.
|
|
||||||
|
|
||||||
Note:
|
|
||||||
It's essential to ensure that the provided arguments are valid and supported
|
|
||||||
by the 'barman-cloud-restore' command, to avoid potential errors or unintended
|
|
||||||
behavior during execution.
|
|
||||||
items:
|
|
||||||
type: string
|
|
||||||
type: array
|
|
||||||
type: object
|
type: object
|
||||||
destinationPath:
|
destinationPath:
|
||||||
description: |-
|
description: |-
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user