diff --git a/CHANGELOG.md b/CHANGELOG.md index 55858ffcd..6e64a1537 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ - (Feature) (ML) Allow to use PlatformStorage - (Maintenance) Bump Go Image to 1.22.11 - (Feature) Split Helm and KClient +- (Bugfix) Fix ArangoRoute Target switch in case of temporary error ## [1.2.43](https://github.com/arangodb/kube-arangodb/tree/1.2.43) (2024-10-14) - (Feature) ArangoRoute CRD diff --git a/pkg/handlers/networking/route/handler.go b/pkg/handlers/networking/route/handler.go index bd6009a8b..3dc289045 100644 --- a/pkg/handlers/networking/route/handler.go +++ b/pkg/handlers/networking/route/handler.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// Copyright 2024-2025 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ package route import ( "context" - apiErrors "k8s.io/apimachinery/pkg/api/errors" meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -34,6 +33,7 @@ import ( "github.com/arangodb/kube-arangodb/pkg/operatorV2/event" "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors" ) var logger = logging.Global().RegisterAndGetLogger("networking-route-operator", logging.Info) @@ -56,7 +56,7 @@ func (h *handler) Handle(ctx context.Context, item operation.Item) error { object, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.client.NetworkingV1alpha1().ArangoRoutes(item.Namespace).Get, item.Name, meta.GetOptions{}) if err != nil { - if apiErrors.IsNotFound(err) { + if kerrors.IsNotFound(err) { return nil } diff --git a/pkg/handlers/networking/route/handler_deployment.go b/pkg/handlers/networking/route/handler_deployment.go index 86f7faa06..622770b5c 100644 --- a/pkg/handlers/networking/route/handler_deployment.go +++ b/pkg/handlers/networking/route/handler_deployment.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// Copyright 2024-2025 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -23,7 +23,6 @@ package route import ( "context" - apiErrors "k8s.io/apimachinery/pkg/api/errors" meta "k8s.io/apimachinery/pkg/apis/meta/v1" api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1" @@ -32,6 +31,7 @@ import ( operator "github.com/arangodb/kube-arangodb/pkg/operatorV2" "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors" ) func (h *handler) HandleArangoDeployment(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus) (bool, error) { @@ -43,7 +43,7 @@ func (h *handler) HandleArangoDeployment(ctx context.Context, item operation.Ite deployment, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.client.DatabaseV1().ArangoDeployments(item.Namespace).Get, name, meta.GetOptions{}) if err != nil { - if apiErrors.IsNotFound(err) { + if kerrors.IsNotFound(err) { // Condition for Found should be set to false if util.Or( status.Conditions.Update(networkingApi.DeploymentFoundCondition, false, "ArangoDeployment not found", "ArangoDeployment not found"), diff --git a/pkg/handlers/networking/route/handler_destination.go b/pkg/handlers/networking/route/handler_destination.go index f6232c2f2..8c644d5e0 100644 --- a/pkg/handlers/networking/route/handler_destination.go +++ b/pkg/handlers/networking/route/handler_destination.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// Copyright 2024-2025 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -48,6 +48,11 @@ func (h *handler) HandleArangoDestination(ctx context.Context, item operation.It func (h *handler) HandleArangoDestinationWithTargets(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, depl *api.ArangoDeployment) (*operator.Condition, bool, error) { c, changed, err := h.HandleArangoDestination(ctx, item, extension, status, depl) + + if operator.IsTemporary(err) { + return nil, false, err + } + if c == nil && !c.Status && status.Target != nil { status.Target = nil changed = true diff --git a/pkg/handlers/networking/route/handler_destination_endpoints.go b/pkg/handlers/networking/route/handler_destination_endpoints.go index c4a94fffb..edf616d94 100644 --- a/pkg/handlers/networking/route/handler_destination_endpoints.go +++ b/pkg/handlers/networking/route/handler_destination_endpoints.go @@ -25,6 +25,7 @@ import ( "fmt" core "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" meta "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/intstr" @@ -48,7 +49,7 @@ func (h *handler) HandleArangoDestinationEndpoints(ctx context.Context, item ope s, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.kubeClient.CoreV1().Services(endpoints.GetNamespace(extension)).Get, endpoints.GetName(), meta.GetOptions{}) if err != nil { - if api.IsNotFound(err) { + if kerrors.IsNotFound(err) { return &operator.Condition{ Status: false, Reason: "Destination Not Found", @@ -56,11 +57,7 @@ func (h *handler) HandleArangoDestinationEndpoints(ctx context.Context, item ope }, false, nil } - return &operator.Condition{ - Status: false, - Reason: "Destination Not Found", - Message: fmt.Sprintf("Unknown error for service `%s/%s`: %s", endpoints.GetNamespace(extension), endpoints.GetName(), err.Error()), - }, false, nil + return nil, false, operator.Temporary(err, "Unable to get service") } if !endpoints.Equals(s) { @@ -73,7 +70,7 @@ func (h *handler) HandleArangoDestinationEndpoints(ctx context.Context, item ope e, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.kubeClient.CoreV1().Endpoints(endpoints.GetNamespace(extension)).Get, endpoints.GetName(), meta.GetOptions{}) if err != nil { - if api.IsNotFound(err) { + if kerrors.IsNotFound(err) { return &operator.Condition{ Status: false, Reason: "Destination Not Found", @@ -81,11 +78,7 @@ func (h *handler) HandleArangoDestinationEndpoints(ctx context.Context, item ope }, false, nil } - return &operator.Condition{ - Status: false, - Reason: "Destination Not Found", - Message: fmt.Sprintf("Unknown error for endpoints `%s/%s`: %s", endpoints.GetNamespace(extension), endpoints.GetName(), err.Error()), - }, false, nil + return nil, false, operator.Temporary(err, "Unable to get endpoints") } // Discover port name - empty names are allowed diff --git a/pkg/handlers/networking/route/handler_destination_service.go b/pkg/handlers/networking/route/handler_destination_service.go index 5ccb1a3b3..eac85d043 100644 --- a/pkg/handlers/networking/route/handler_destination_service.go +++ b/pkg/handlers/networking/route/handler_destination_service.go @@ -33,6 +33,7 @@ import ( operator "github.com/arangodb/kube-arangodb/pkg/operatorV2" "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation" "github.com/arangodb/kube-arangodb/pkg/util" + "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors" ) func (h *handler) HandleArangoDestinationService(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, deployment *api.ArangoDeployment, dest *networkingApi.ArangoRouteSpecDestination, svc *networkingApi.ArangoRouteSpecDestinationService) (*operator.Condition, bool, error) { @@ -48,7 +49,7 @@ func (h *handler) HandleArangoDestinationService(ctx context.Context, item opera s, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.kubeClient.CoreV1().Services(svc.GetNamespace(extension)).Get, svc.GetName(), meta.GetOptions{}) if err != nil { - if api.IsNotFound(err) { + if kerrors.IsNotFound(err) { return &operator.Condition{ Status: false, Reason: "Destination Not Found", @@ -56,11 +57,7 @@ func (h *handler) HandleArangoDestinationService(ctx context.Context, item opera }, false, nil } - return &operator.Condition{ - Status: false, - Reason: "Destination Not Found", - Message: fmt.Sprintf("Unknown error for service `%s/%s`: %s", svc.GetNamespace(extension), svc.GetName(), err.Error()), - }, false, nil + return nil, false, operator.Temporary(err, "Unable to get service") } if !svc.Equals(s) { diff --git a/pkg/handlers/networking/route/handler_destination_service_test.go b/pkg/handlers/networking/route/handler_destination_service_test.go index 0b960be69..dcab7a8bb 100644 --- a/pkg/handlers/networking/route/handler_destination_service_test.go +++ b/pkg/handlers/networking/route/handler_destination_service_test.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// Copyright 2024-2025 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -72,7 +72,7 @@ func Test_Handler_Destination_Service_Missing(t *testing.T) { c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition) require.True(t, ok) require.EqualValues(t, c.Reason, "Destination Not Found") - require.EqualValues(t, c.Message, "Unknown error for service `fake/deployment`: services \"deployment\" not found") + require.EqualValues(t, c.Message, "Service `fake/deployment` Not found") } func Test_Handler_Destination_Service_Valid(t *testing.T) { diff --git a/pkg/operatorV2/errors_reconcile.go b/pkg/operatorV2/errors_reconcile.go index 966c5ef64..2888415e6 100644 --- a/pkg/operatorV2/errors_reconcile.go +++ b/pkg/operatorV2/errors_reconcile.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// Copyright 2023-2025 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,7 +20,11 @@ package operator -import "fmt" +import ( + "fmt" + + "github.com/arangodb/kube-arangodb/pkg/util/errors" +) func Reconcile(msg string, args ...interface{}) error { return reconcile{ @@ -37,11 +41,7 @@ func (r reconcile) Error() string { } func IsReconcile(err error) bool { - if err == nil { - return false - } - - if _, ok := err.(reconcile); ok { + if _, ok := errors.ExtractCause[reconcile](err); ok { return true } diff --git a/pkg/operatorV2/errors_stop.go b/pkg/operatorV2/errors_stop.go index 35da7d698..d71f90bb7 100644 --- a/pkg/operatorV2/errors_stop.go +++ b/pkg/operatorV2/errors_stop.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2023 ArangoDB GmbH, Cologne, Germany +// Copyright 2023-2025 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,7 +20,11 @@ package operator -import "fmt" +import ( + "fmt" + + "github.com/arangodb/kube-arangodb/pkg/util/errors" +) func Stop(msg string, args ...interface{}) error { return stop{ @@ -37,11 +41,7 @@ func (r stop) Error() string { } func IsStop(err error) bool { - if err == nil { - return false - } - - if _, ok := err.(stop); ok { + if _, ok := errors.ExtractCause[stop](err); ok { return true } diff --git a/pkg/operatorV2/errors_temporary.go b/pkg/operatorV2/errors_temporary.go new file mode 100644 index 000000000..67614d2c5 --- /dev/null +++ b/pkg/operatorV2/errors_temporary.go @@ -0,0 +1,61 @@ +// +// DISCLAIMER +// +// Copyright 2023-2025 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package operator + +import ( + "github.com/arangodb/kube-arangodb/pkg/util/errors" +) + +func Temporary(cause error, msg string, args ...interface{}) error { + if cause == nil { + return temporary{ + cause: errors.Errorf(msg, args...), + } + } + + return temporary{ + cause: errors.Wrapf(cause, msg, args...), + } +} + +type temporary struct { + cause error +} + +func (t temporary) Error() string { + return t.cause.Error() +} + +func (t temporary) Temporary() bool { + return true +} + +func (t temporary) Cause() error { + return t.cause +} + +func IsTemporary(err error) bool { + if _, ok := errors.ExtractCause[temporary](err); ok { + return true + } + + return false +} diff --git a/pkg/util/errors/errors.go b/pkg/util/errors/errors.go index a0917b264..a8dc28dca 100644 --- a/pkg/util/errors/errors.go +++ b/pkg/util/errors/errors.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2016-2024 ArangoDB GmbH, Cologne, Germany +// Copyright 2016-2025 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -55,7 +55,7 @@ func ExtractCause[T error](err error) (T, bool) { var d T if err == nil { - return d, true + return d, false } var v T @@ -70,24 +70,6 @@ func ExtractCause[T error](err error) (T, bool) { return d, false } -func ExtractCauseHelper[T error](err error, extr func(err error) (T, bool)) (T, bool) { - var d T - - if err == nil { - return d, true - } - - if v, ok := extr(err); ok { - return v, true - } - - if err := CauseWithNil(err); err != nil { - return ExtractCauseHelper[T](err, extr) - } - - return d, false -} - func New(message string) error { return errors.New(message) } diff --git a/pkg/util/errors/errors_test.go b/pkg/util/errors/errors_test.go new file mode 100644 index 000000000..c6e01b87b --- /dev/null +++ b/pkg/util/errors/errors_test.go @@ -0,0 +1,52 @@ +// +// DISCLAIMER +// +// Copyright 2025 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package errors + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type testCauseError string + +func (t testCauseError) Error() string { + return string(t) + +} + +func Test_Causer(t *testing.T) { + var err error = testCauseError("tete") + + v, ok := ExtractCause[testCauseError](err) + require.True(t, ok) + require.EqualValues(t, "tete", v) + + err = WithMessage(err, "msg") + + v, ok = ExtractCause[testCauseError](err) + require.True(t, ok) + require.EqualValues(t, "tete", v) + + v, ok = ExtractCause[testCauseError](Errorf("TEST")) + require.False(t, ok) + require.EqualValues(t, "", v) +} diff --git a/pkg/util/errors/grpc.go b/pkg/util/errors/grpc.go index 94b1315f1..942ba608a 100644 --- a/pkg/util/errors/grpc.go +++ b/pkg/util/errors/grpc.go @@ -1,7 +1,7 @@ // // DISCLAIMER // -// Copyright 2024 ArangoDB GmbH, Cologne, Germany +// Copyright 2024-2025 ArangoDB GmbH, Cologne, Germany // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -21,8 +21,6 @@ package errors import ( - "errors" - "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) @@ -33,14 +31,7 @@ type grpcError interface { } func GRPCStatus(err error) (*status.Status, bool) { - v, ok := ExtractCauseHelper[grpcError](err, func(err error) (grpcError, bool) { - var gs grpcError - if errors.As(err, &gs) { - return gs, true - } - - return nil, false - }) + v, ok := ExtractCause[grpcError](err) if !ok { return status.New(codes.Unknown, err.Error()), false diff --git a/pkg/util/errors/grpc_test.go b/pkg/util/errors/grpc_test.go new file mode 100644 index 000000000..9cbdc5fba --- /dev/null +++ b/pkg/util/errors/grpc_test.go @@ -0,0 +1,41 @@ +// +// DISCLAIMER +// +// Copyright 2025 ArangoDB GmbH, Cologne, Germany +// +// 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. +// +// Copyright holder is ArangoDB GmbH, Cologne, Germany +// + +package errors + +import ( + "testing" + + "github.com/stretchr/testify/require" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +func Test_GRPC(t *testing.T) { + v, ok := ExtractCause[grpcError](status.Error(codes.InvalidArgument, "path missing")) + require.True(t, ok) + require.EqualValues(t, codes.InvalidArgument, v.GRPCStatus().Code()) + + v, ok = ExtractCause[grpcError](WithStack(status.Error(codes.InvalidArgument, "path missing"))) + require.True(t, ok) + require.EqualValues(t, codes.InvalidArgument, v.GRPCStatus().Code()) + + require.EqualValues(t, codes.InvalidArgument, GRPCCode(status.Error(codes.InvalidArgument, "path missing"))) +}