From b3da66cc12c3729467ff98390d72966ee56677bf Mon Sep 17 00:00:00 2001
From: ajanikow <12255597+ajanikow@users.noreply.github.com>
Date: Thu, 12 Sep 2024 14:22:10 +0000
Subject: [PATCH] [Feature] [Networking] Endpoints Destination
---
CHANGELOG.md | 1 +
docs/api/ArangoRoute.V1Alpha1.md | 76 ++-
.../v1alpha1/route_spec_destination.go | 13 +
.../route_spec_destination_authentication.go | 10 +-
.../route_spec_destination_endpoint.go | 59 ++
.../route_spec_destination_service.go | 1 -
.../v1alpha1/route_status_target.go | 5 +-
.../v1alpha1/route_status_target_type.go | 34 +
.../v1alpha1/zz_generated.deepcopy.go | 31 +
pkg/apis/shared/validate.go | 107 ++++
pkg/apis/shared/validate_test.go | 38 ++
.../networking-route.schema.generated.yaml | 29 +
pkg/deployment/cleanup.go | 6 +-
pkg/deployment/context_impl.go | 2 +-
pkg/deployment/deployment_finalizers.go | 39 +-
pkg/deployment/resources/pod_finalizers.go | 4 +-
pkg/deployment/resources/pod_inspector.go | 2 +-
pkg/deployment/resources/pvc_finalizers.go | 2 +-
pkg/deployment/resources/pvc_inspector.go | 2 +-
pkg/handlers/networking/route/handler.go | 2 +-
.../networking/route/handler_destination.go | 145 +----
.../route/handler_destination_endpoints.go | 180 ++++++
.../handler_destination_endpoints_test.go | 330 ++++++++++
.../route/handler_destination_service.go | 175 +++++
.../route/handler_destination_service_test.go | 598 ++++++++++++++++++
.../route/handler_destination_test.go | 507 +--------------
pkg/replication/finalizers.go | 30 +-
pkg/util/k8sutil/finalizers.go | 78 +--
pkg/util/k8sutil/patcher/common.go | 35 +
pkg/util/k8sutil/patcher/patcher.go | 3 +-
pkg/util/list.go | 16 +
pkg/util/tests/kubernetes.go | 44 ++
pkg/util/tests/kubernetes_test.go | 1 +
33 files changed, 1820 insertions(+), 785 deletions(-)
create mode 100644 pkg/apis/networking/v1alpha1/route_spec_destination_endpoint.go
create mode 100644 pkg/apis/networking/v1alpha1/route_status_target_type.go
create mode 100644 pkg/handlers/networking/route/handler_destination_endpoints.go
create mode 100644 pkg/handlers/networking/route/handler_destination_endpoints_test.go
create mode 100644 pkg/handlers/networking/route/handler_destination_service.go
create mode 100644 pkg/handlers/networking/route/handler_destination_service_test.go
create mode 100644 pkg/util/k8sutil/patcher/common.go
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae68b18c4..b0fbe048c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -31,6 +31,7 @@
- (Feature) (Scheduler) Add Status Conditions
- (Bugfix) Versioning Alignment
- (Feature) (Scheduler) Merge Strategy
+- (Feature) (Networking) Endpoints Destination
## [1.2.42](https://github.com/arangodb/kube-arangodb/tree/1.2.42) (2024-07-23)
- (Maintenance) Go 1.22.4 & Kubernetes 1.29.6 libraries
diff --git a/docs/api/ArangoRoute.V1Alpha1.md b/docs/api/ArangoRoute.V1Alpha1.md
index 2dc3b2302..fea374ee0 100644
--- a/docs/api/ArangoRoute.V1Alpha1.md
+++ b/docs/api/ArangoRoute.V1Alpha1.md
@@ -18,19 +18,72 @@ Deployment specifies the ArangoDeployment object name
### .spec.destination.authentication.passMode
-Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go#L28)
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go#L32)
+
+PassMode define authorization details pass mode when authorization was successful
+
+Possible Values:
+* `"override"` (default) - Generates new token for the user
+* `"pass"` - Pass token provided by the user
+* `"remove"` - Removes authorization details from the request
***
### .spec.destination.authentication.type
-Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go#L29)
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go#L37)
+
+Type of the authentication
+
+Possible Values:
+* `"optional"` (default) - Authentication is header is validated and passed to the service. In case if is unauthorized, requests is still passed
+* `"required"` - Authentication is header is validated and passed to the service. In case if is unauthorized, returns 403
+
+***
+
+### .spec.destination.endpoints.checksum
+
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/shared/v1/object.go#L61)
+
+UID keeps the information about object Checksum
+
+***
+
+### .spec.destination.endpoints.name
+
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/shared/v1/object.go#L52)
+
+Name of the object
+
+***
+
+### .spec.destination.endpoints.namespace
+
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/shared/v1/object.go#L55)
+
+Namespace of the object. Should default to the namespace of the parent object
+
+***
+
+### .spec.destination.endpoints.port
+
+Type: `intstr.IntOrString` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_endpoint.go#L36)
+
+Port defines Port or Port Name used as destination
+
+***
+
+### .spec.destination.endpoints.uid
+
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/shared/v1/object.go#L58)
+
+UID keeps the information about object UID
***
### .spec.destination.path
-Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination.go#L36)
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination.go#L39)
Path defines service path used for overrides
@@ -38,7 +91,7 @@ Path defines service path used for overrides
### .spec.destination.schema
-Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination.go#L30)
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination.go#L33)
Schema defines HTTP/S schema used for connection
@@ -70,13 +123,10 @@ Namespace of the object. Should default to the namespace of the parent object
### .spec.destination.service.port
-Type: `intstr.IntOrString` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_service.go#L36)
+Type: `intstr.IntOrString` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_spec_destination_service.go#L35)
Port defines Port or Port Name used as destination
-Links:
-* [Documentation](https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/)
-
***
### .spec.destination.service.uid
@@ -169,7 +219,7 @@ Type: `integer` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.
### .status.target.path
-Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L40)
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L43)
Path specifies request path override
@@ -181,3 +231,11 @@ Type: `boolean` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.
Insecure allows Insecure traffic
+***
+
+### .status.target.type
+
+Type: `string` [\[ref\]](https://github.com/arangodb/kube-arangodb/blob/1.2.42/pkg/apis/networking/v1alpha1/route_status_target.go#L34)
+
+Type define destination type
+
diff --git a/pkg/apis/networking/v1alpha1/route_spec_destination.go b/pkg/apis/networking/v1alpha1/route_spec_destination.go
index 055fd6cbf..1dfa0ab75 100644
--- a/pkg/apis/networking/v1alpha1/route_spec_destination.go
+++ b/pkg/apis/networking/v1alpha1/route_spec_destination.go
@@ -26,6 +26,9 @@ type ArangoRouteSpecDestination struct {
// Service defines service upstream reference
Service *ArangoRouteSpecDestinationService `json:"service,omitempty"`
+ // Endpoints defines service upstream reference - which is used to find endpoints
+ Endpoints *ArangoRouteSpecDestinationEndpoints `json:"endpoints,omitempty"`
+
// Schema defines HTTP/S schema used for connection
Schema *ArangoRouteSpecDestinationSchema `json:"schema,omitempty"`
@@ -47,6 +50,14 @@ func (a *ArangoRouteSpecDestination) GetService() *ArangoRouteSpecDestinationSer
return a.Service
}
+func (a *ArangoRouteSpecDestination) GetEndpoints() *ArangoRouteSpecDestinationEndpoints {
+ if a == nil || a.Endpoints == nil {
+ return nil
+ }
+
+ return a.Endpoints
+}
+
func (a *ArangoRouteSpecDestination) GetSchema() *ArangoRouteSpecDestinationSchema {
if a == nil || a.Schema == nil {
return nil
@@ -85,7 +96,9 @@ func (a *ArangoRouteSpecDestination) Validate() error {
}
if err := shared.WithErrors(
+ shared.ValidateExclusiveFields(a, 1, "Service", "Endpoints"),
shared.ValidateOptionalInterfacePath("service", a.Service),
+ shared.ValidateOptionalInterfacePath("endpoints", a.Endpoints),
shared.ValidateOptionalInterfacePath("schema", a.Schema),
shared.ValidateOptionalInterfacePath("tls", a.TLS),
shared.ValidateOptionalInterfacePath("authentication", a.Authentication),
diff --git a/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go b/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go
index 9ef89aba8..00ac298df 100644
--- a/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go
+++ b/pkg/apis/networking/v1alpha1/route_spec_destination_authentication.go
@@ -25,8 +25,16 @@ import (
)
type ArangoRouteSpecDestinationAuthentication struct {
+ // PassMode define authorization details pass mode when authorization was successful
+ // +doc/enum: override|Generates new token for the user
+ // +doc/enum: pass|Pass token provided by the user
+ // +doc/enum: remove|Removes authorization details from the request
PassMode *ArangoRouteSpecAuthenticationPassMode `json:"passMode,omitempty"`
- Type *ArangoRouteSpecAuthenticationType `json:"type,omitempty"`
+
+ // Type of the authentication
+ // +doc/enum: optional|Authentication is header is validated and passed to the service. In case if is unauthorized, requests is still passed
+ // +doc/enum: required|Authentication is header is validated and passed to the service. In case if is unauthorized, returns 403
+ Type *ArangoRouteSpecAuthenticationType `json:"type,omitempty"`
}
func (a *ArangoRouteSpecDestinationAuthentication) GetType() ArangoRouteSpecAuthenticationType {
diff --git a/pkg/apis/networking/v1alpha1/route_spec_destination_endpoint.go b/pkg/apis/networking/v1alpha1/route_spec_destination_endpoint.go
new file mode 100644
index 000000000..bd7d9e147
--- /dev/null
+++ b/pkg/apis/networking/v1alpha1/route_spec_destination_endpoint.go
@@ -0,0 +1,59 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 v1alpha1
+
+import (
+ "k8s.io/apimachinery/pkg/util/intstr"
+
+ shared "github.com/arangodb/kube-arangodb/pkg/apis/shared"
+ sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
+)
+
+type ArangoRouteSpecDestinationEndpoints struct {
+ // Keeps information on the service, which maps then to the endpoints
+ *sharedApi.Object `json:",inline,omitempty"`
+
+ // Port defines Port or Port Name used as destination
+ // +doc/type: intstr.IntOrString
+ Port *intstr.IntOrString `json:"port,omitempty"`
+}
+
+func (a *ArangoRouteSpecDestinationEndpoints) GetPort() *intstr.IntOrString {
+ if a == nil || a.Port == nil {
+ return nil
+ }
+
+ return a.Port
+}
+
+func (a *ArangoRouteSpecDestinationEndpoints) Validate() error {
+ if a == nil {
+ a = &ArangoRouteSpecDestinationEndpoints{}
+ }
+
+ if err := shared.WithErrors(a.Object.Validate(), shared.ValidateRequiredPath("port", a.Port, func(i intstr.IntOrString) error {
+ return nil
+ })); err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/apis/networking/v1alpha1/route_spec_destination_service.go b/pkg/apis/networking/v1alpha1/route_spec_destination_service.go
index 9b2c0d9f4..e44a4cbbd 100644
--- a/pkg/apis/networking/v1alpha1/route_spec_destination_service.go
+++ b/pkg/apis/networking/v1alpha1/route_spec_destination_service.go
@@ -32,7 +32,6 @@ type ArangoRouteSpecDestinationService struct {
// Port defines Port or Port Name used as destination
// +doc/type: intstr.IntOrString
- // +doc/link: Documentation|https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/
Port *intstr.IntOrString `json:"port,omitempty"`
}
diff --git a/pkg/apis/networking/v1alpha1/route_status_target.go b/pkg/apis/networking/v1alpha1/route_status_target.go
index 26d67b1d3..8ec626b07 100644
--- a/pkg/apis/networking/v1alpha1/route_status_target.go
+++ b/pkg/apis/networking/v1alpha1/route_status_target.go
@@ -30,6 +30,9 @@ type ArangoRouteStatusTarget struct {
// Destinations keeps target destinations
Destinations ArangoRouteStatusTargetDestinations `json:"destinations,omitempty"`
+ // Type define destination type
+ Type ArangoRouteStatusTargetType `json:"type,omitempty"`
+
// TLS Keeps target TLS Settings (if not nil, TLS is enabled)
TLS *ArangoRouteStatusTargetTLS `json:"TLS,omitempty"`
@@ -64,5 +67,5 @@ func (a *ArangoRouteStatusTarget) Hash() string {
if a == nil {
return ""
}
- return util.SHA256FromStringArray(a.Destinations.Hash(), a.TLS.Hash(), a.Path, a.Authentication.Hash())
+ return util.SHA256FromStringArray(a.Destinations.Hash(), a.Type.Hash(), a.TLS.Hash(), a.Path, a.Authentication.Hash())
}
diff --git a/pkg/apis/networking/v1alpha1/route_status_target_type.go b/pkg/apis/networking/v1alpha1/route_status_target_type.go
new file mode 100644
index 000000000..d88340012
--- /dev/null
+++ b/pkg/apis/networking/v1alpha1/route_status_target_type.go
@@ -0,0 +1,34 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 v1alpha1
+
+import "github.com/arangodb/kube-arangodb/pkg/util"
+
+type ArangoRouteStatusTargetType string
+
+func (a ArangoRouteStatusTargetType) Hash() string {
+ return util.SHA256FromString(string(a))
+}
+
+const (
+ ArangoRouteStatusTargetServiceType ArangoRouteStatusTargetType = "service"
+ ArangoRouteStatusTargetEndpointsType ArangoRouteStatusTargetType = "endpoints"
+)
diff --git a/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go
index 31e9cff51..fa67283f8 100644
--- a/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/networking/v1alpha1/zz_generated.deepcopy.go
@@ -132,6 +132,11 @@ func (in *ArangoRouteSpecDestination) DeepCopyInto(out *ArangoRouteSpecDestinati
*out = new(ArangoRouteSpecDestinationService)
(*in).DeepCopyInto(*out)
}
+ if in.Endpoints != nil {
+ in, out := &in.Endpoints, &out.Endpoints
+ *out = new(ArangoRouteSpecDestinationEndpoints)
+ (*in).DeepCopyInto(*out)
+ }
if in.Schema != nil {
in, out := &in.Schema, &out.Schema
*out = new(ArangoRouteSpecDestinationSchema)
@@ -191,6 +196,32 @@ func (in *ArangoRouteSpecDestinationAuthentication) DeepCopy() *ArangoRouteSpecD
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ArangoRouteSpecDestinationEndpoints) DeepCopyInto(out *ArangoRouteSpecDestinationEndpoints) {
+ *out = *in
+ if in.Object != nil {
+ in, out := &in.Object, &out.Object
+ *out = new(v1.Object)
+ (*in).DeepCopyInto(*out)
+ }
+ if in.Port != nil {
+ in, out := &in.Port, &out.Port
+ *out = new(intstr.IntOrString)
+ **out = **in
+ }
+ return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArangoRouteSpecDestinationEndpoints.
+func (in *ArangoRouteSpecDestinationEndpoints) DeepCopy() *ArangoRouteSpecDestinationEndpoints {
+ if in == nil {
+ return nil
+ }
+ out := new(ArangoRouteSpecDestinationEndpoints)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ArangoRouteSpecDestinationService) DeepCopyInto(out *ArangoRouteSpecDestinationService) {
*out = *in
diff --git a/pkg/apis/shared/validate.go b/pkg/apis/shared/validate.go
index 431b5f241..b9da30205 100644
--- a/pkg/apis/shared/validate.go
+++ b/pkg/apis/shared/validate.go
@@ -24,11 +24,13 @@ import (
"fmt"
"reflect"
"regexp"
+ "strings"
"github.com/google/uuid"
core "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
+ "github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
)
@@ -243,3 +245,108 @@ func ValidateServiceType(st core.ServiceType) error {
}
return errors.Errorf("Unsupported service type %s", st)
}
+
+// ValidateExclusiveFields check if fields are defined in exclusive way
+func ValidateExclusiveFields(in any, expected int, fields ...string) error {
+ v := reflect.ValueOf(in)
+ t := v.Type()
+
+ if expected > len(fields) {
+ return errors.Errorf("Expected more fields than allowed")
+ }
+
+ if t.Kind() == reflect.Pointer {
+ if !v.IsValid() {
+ return errors.Errorf("Invalid reference")
+ }
+
+ if v.IsZero() {
+ // Skip in case of zero value
+ return nil
+ }
+
+ // We got a ptr, detach type
+ return ValidateExclusiveFields(v.Elem().Interface(), expected, fields...)
+ }
+
+ if t.Kind() == reflect.Struct {
+ foundFields := map[string]bool{}
+ for _, field := range fields {
+ tf, ok := t.FieldByName(field)
+ if !ok {
+ continue
+ }
+
+ n := field
+ if tg, ok := tf.Tag.Lookup("json"); ok {
+ p := strings.Split(tg, ",")[0]
+ if p != "" {
+ n = p
+ }
+ }
+ foundFields[n] = false
+
+ vf := v.FieldByName(field)
+ if !vf.IsValid() {
+ continue
+ }
+ if vf.IsZero() {
+ // Empty or nil
+ continue
+ }
+
+ foundFields[n] = true
+ }
+
+ existing := util.Sort(util.FlattenList(util.FormatList(util.Extract(foundFields), func(a util.KV[string, bool]) []string {
+ if a.V {
+ return []string{a.K}
+ }
+
+ return nil
+ })), func(i, j string) bool {
+ return i < j
+ })
+
+ missing := util.Sort(util.FlattenList(util.FormatList(util.Extract(foundFields), func(a util.KV[string, bool]) []string {
+ if !a.V {
+ return []string{a.K}
+ }
+
+ return nil
+ })), func(i, j string) bool {
+ return i < j
+ })
+
+ all := util.SortKeys(foundFields)
+
+ if len(existing) != expected {
+ // We did not get all fields, check condition
+ if len(existing) == 0 {
+ return errors.Errorf("Elements not provided. Expected %d. Possible: %s",
+ expected,
+ strings.Join(all, ", "),
+ )
+ }
+ if len(existing) < expected {
+ return errors.Errorf("Not enough elements provided. Expected %d, got %d. Defined: %s, Additionally Possible: %s",
+ expected,
+ len(existing),
+ strings.Join(existing, ", "),
+ strings.Join(missing, ", "),
+ )
+ }
+ if len(existing) > expected {
+ return errors.Errorf("Too many elements provided. Expected %d, got %d. Defined: %s",
+ expected,
+ len(existing),
+ strings.Join(existing, ", "),
+ )
+ }
+ }
+
+ return nil
+ }
+
+ return errors.Errorf("Invalid reference")
+}
diff --git a/pkg/apis/shared/validate_test.go b/pkg/apis/shared/validate_test.go
index 31b7f6787..4a0dc4e93 100644
--- a/pkg/apis/shared/validate_test.go
+++ b/pkg/apis/shared/validate_test.go
@@ -42,3 +42,41 @@ func Test_ValidateAPIPath(t *testing.T) {
require.NoError(t, ValidateAPIPath("/api/test/2/"))
require.Error(t, ValidateAPIPath("/&/"))
}
+
+func Test_ValidateExclusiveFields(t *testing.T) {
+ type z struct {
+ A string `json:"a,omitempty"`
+ B string `json:"b,omitempty"`
+ C string `json:"c,omitempty"`
+ D string `json:"d,omitempty"`
+ }
+
+ require.EqualError(t, ValidateExclusiveFields(z{}, 1, "A"), "Elements not provided. Expected 1. Possible: a")
+
+ require.NoError(t, ValidateExclusiveFields(z{
+ A: "test",
+ }, 1, "A"))
+
+ require.EqualError(t, ValidateExclusiveFields(z{
+ A: "test",
+ }, 2, "A"), "Expected more fields than allowed")
+
+ require.EqualError(t, ValidateExclusiveFields(z{
+ A: "test",
+ }, 2, "A", "B"), "Not enough elements provided. Expected 2, got 1. Defined: a, Additionally Possible: b")
+
+ require.NoError(t, ValidateExclusiveFields(z{
+ A: "test",
+ B: "test",
+ }, 2, "A", "B"))
+
+ require.EqualError(t, ValidateExclusiveFields(z{
+ A: "test",
+ B: "test",
+ }, 1, "A", "B"), "Too many elements provided. Expected 1, got 2. Defined: a, b")
+
+ require.NoError(t, ValidateExclusiveFields(z{
+ A: "test",
+ D: "test",
+ }, 2, "A", "B", "C", "D"))
+}
diff --git a/pkg/crd/crds/networking-route.schema.generated.yaml b/pkg/crd/crds/networking-route.schema.generated.yaml
index d973e59fb..9d82639de 100644
--- a/pkg/crd/crds/networking-route.schema.generated.yaml
+++ b/pkg/crd/crds/networking-route.schema.generated.yaml
@@ -13,8 +13,37 @@ v1alpha1:
description: Authentication defines auth methods
properties:
passMode:
+ description: PassMode define authorization details pass mode when authorization was successful
+ enum:
+ - override
+ - pass
+ - remove
type: string
type:
+ description: Type of the authentication
+ enum:
+ - optional
+ - required
+ type: string
+ type: object
+ endpoints:
+ description: Endpoints defines service upstream reference - which is used to find endpoints
+ properties:
+ checksum:
+ description: UID keeps the information about object Checksum
+ type: string
+ name:
+ description: Name of the object
+ type: string
+ namespace:
+ description: Namespace of the object. Should default to the namespace of the parent object
+ type: string
+ port:
+ description: Port defines Port or Port Name used as destination
+ type: string
+ x-kubernetes-int-or-string: true
+ uid:
+ description: UID keeps the information about object UID
type: string
type: object
path:
diff --git a/pkg/deployment/cleanup.go b/pkg/deployment/cleanup.go
index f0e359bd2..e621efb31 100644
--- a/pkg/deployment/cleanup.go
+++ b/pkg/deployment/cleanup.go
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
-// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
+// Copyright 2016-2024 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.
@@ -44,7 +44,7 @@ func (d *Deployment) removePodFinalizers(ctx context.Context, cachedStatus inspe
if err := cachedStatus.Pod().V1().Iterate(func(pod *core.Pod) error {
log.Str("pod", pod.GetName()).Info("Removing Pod Finalizer")
- if count, err := k8sutil.RemovePodFinalizers(ctx, cachedStatus, d.PodsModInterface(), pod, constants.ManagedFinalizers(), true); err != nil {
+ if count, err := k8sutil.RemoveSelectedFinalizers[*core.Pod](ctx, cachedStatus.Pod().V1().Read(), cachedStatus.PodsModInterface().V1(), pod, constants.ManagedFinalizers(), true); err != nil {
log.Err(err).Warn("Failed to remove pod finalizers")
return err
} else if count > 0 {
@@ -78,7 +78,7 @@ func (d *Deployment) removePVCFinalizers(ctx context.Context, cachedStatus inspe
if err := cachedStatus.PersistentVolumeClaim().V1().Iterate(func(pvc *core.PersistentVolumeClaim) error {
log.Str("pvc", pvc.GetName()).Info("Removing PVC Finalizer")
- if count, err := k8sutil.RemovePVCFinalizers(ctx, cachedStatus, d.PersistentVolumeClaimsModInterface(), pvc, constants.ManagedFinalizers(), true); err != nil {
+ if count, err := k8sutil.RemoveSelectedFinalizers[*core.PersistentVolumeClaim](ctx, cachedStatus.PersistentVolumeClaim().V1().Read(), cachedStatus.PersistentVolumeClaimsModInterface().V1(), pvc, constants.ManagedFinalizers(), true); err != nil {
log.Err(err).Warn("Failed to remove PVC finalizers")
return err
} else if count > 0 {
diff --git a/pkg/deployment/context_impl.go b/pkg/deployment/context_impl.go
index d62659885..a2c1b0668 100644
--- a/pkg/deployment/context_impl.go
+++ b/pkg/deployment/context_impl.go
@@ -399,7 +399,7 @@ func (d *Deployment) RemovePodFinalizers(ctx context.Context, podName string) er
return errors.WithStack(err)
}
- _, err = k8sutil.RemovePodFinalizers(ctx, d.GetCachedStatus(), d.PodsModInterface(), p, p.GetFinalizers(), true)
+ _, err = k8sutil.RemoveSelectedFinalizers[*core.Pod](ctx, d.GetCachedStatus().Pod().V1().Read(), d.GetCachedStatus().PodsModInterface().V1(), p, p.GetFinalizers(), true)
if err != nil {
return errors.WithStack(err)
}
diff --git a/pkg/deployment/deployment_finalizers.go b/pkg/deployment/deployment_finalizers.go
index 45192a410..b6404b551 100644
--- a/pkg/deployment/deployment_finalizers.go
+++ b/pkg/deployment/deployment_finalizers.go
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
-// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
+// Copyright 2016-2024 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.
@@ -26,7 +26,6 @@ import (
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
- "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
@@ -90,7 +89,8 @@ func (d *Deployment) runDeploymentFinalizers(ctx context.Context, cachedStatus i
}
// Remove finalizers (if needed)
if len(removalList) > 0 {
- if err := removeDeploymentFinalizers(ctx, d.deps.Client.Arango(), updated, removalList); err != nil {
+ c := d.deps.Client.Arango().DatabaseV1().ArangoDeployments(updated.GetNamespace())
+ if _, err := k8sutil.RemoveSelectedFinalizers[*api.ArangoDeployment](ctx, c, c, updated, removalList, false); err != nil {
d.log.Err(err).Debug("Failed to update ArangoDeployment (to remove finalizers)")
return errors.WithStack(err)
}
@@ -116,36 +116,3 @@ func (d *Deployment) inspectRemoveChildFinalizers(ctx context.Context, _ *api.Ar
return retry, nil
}
-
-// removeDeploymentFinalizers removes the given finalizers from the given PVC.
-func removeDeploymentFinalizers(ctx context.Context, cli versioned.Interface,
- depl *api.ArangoDeployment, finalizers []string) error {
- depls := cli.DatabaseV1().ArangoDeployments(depl.GetNamespace())
- getFunc := func() (meta.Object, error) {
- ctxChild, cancel := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
- defer cancel()
-
- result, err := depls.Get(ctxChild, depl.GetName(), meta.GetOptions{})
- if err != nil {
- return nil, errors.WithStack(err)
- }
- return result, nil
- }
- updateFunc := func(updated meta.Object) error {
- updatedDepl := updated.(*api.ArangoDeployment)
- ctxChild, cancel := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
- defer cancel()
-
- result, err := depls.Update(ctxChild, updatedDepl, meta.UpdateOptions{})
- if err != nil {
- return errors.WithStack(err)
- }
- *depl = *result
- return nil
- }
- ignoreNotFound := false
- if _, err := k8sutil.RemoveFinalizers(finalizers, getFunc, updateFunc, ignoreNotFound); err != nil {
- return errors.WithStack(err)
- }
- return nil
-}
diff --git a/pkg/deployment/resources/pod_finalizers.go b/pkg/deployment/resources/pod_finalizers.go
index 5e8a53bc8..aef120792 100644
--- a/pkg/deployment/resources/pod_finalizers.go
+++ b/pkg/deployment/resources/pod_finalizers.go
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
-// Copyright 2016-2023 ArangoDB GmbH, Cologne, Germany
+// Copyright 2016-2024 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.
@@ -115,7 +115,7 @@ func (r *Resources) runPodFinalizers(ctx context.Context, p *core.Pod, memberSta
}
// Remove finalizers (if needed)
if len(removalList) > 0 {
- if _, err := k8sutil.RemovePodFinalizers(ctx, r.context.ACS().CurrentClusterCache(), r.context.ACS().CurrentClusterCache().PodsModInterface().V1(), p, removalList, false); err != nil {
+ if _, err := k8sutil.RemoveSelectedFinalizers[*core.Pod](ctx, r.context.ACS().CurrentClusterCache().Pod().V1().Read(), r.context.ACS().CurrentClusterCache().PodsModInterface().V1(), p, removalList, false); err != nil {
log.Err(err).Debug("Failed to update pod (to remove finalizers)")
return 0, errors.WithStack(err)
}
diff --git a/pkg/deployment/resources/pod_inspector.go b/pkg/deployment/resources/pod_inspector.go
index 1d6e2e3b8..bf85c699a 100644
--- a/pkg/deployment/resources/pod_inspector.go
+++ b/pkg/deployment/resources/pod_inspector.go
@@ -117,7 +117,7 @@ func (r *Resources) InspectPods(ctx context.Context, cachedStatus inspectorInter
// Strange, pod belongs to us, but we have no member for it.
// Remove all finalizers, so it can be removed.
log.Str("pod", pod.GetName()).Warn("Pod belongs to this deployment, but we don't know the member. Removing all finalizers")
- _, err := k8sutil.RemovePodFinalizers(ctx, r.context.ACS().CurrentClusterCache(), cachedStatus.PodsModInterface().V1(), pod, pod.GetFinalizers(), false)
+ _, err := k8sutil.RemoveSelectedFinalizers[*core.Pod](ctx, r.context.ACS().CurrentClusterCache().Pod().V1().Read(), r.context.ACS().CurrentClusterCache().PodsModInterface().V1(), pod, pod.GetFinalizers(), false)
if err != nil {
log.Str("pod", pod.GetName()).Err(err).Debug("Failed to update pod (to remove all finalizers)")
return errors.WithStack(err)
diff --git a/pkg/deployment/resources/pvc_finalizers.go b/pkg/deployment/resources/pvc_finalizers.go
index 537b6e877..8610758e1 100644
--- a/pkg/deployment/resources/pvc_finalizers.go
+++ b/pkg/deployment/resources/pvc_finalizers.go
@@ -58,7 +58,7 @@ func (r *Resources) runPVCFinalizers(ctx context.Context, p *core.PersistentVolu
}
// Remove finalizers (if needed)
if len(removalList) > 0 {
- _, err := k8sutil.RemovePVCFinalizers(ctx, r.context.ACS().CurrentClusterCache(), r.context.ACS().CurrentClusterCache().PersistentVolumeClaimsModInterface().V1(), p, removalList, false)
+ _, err := k8sutil.RemoveSelectedFinalizers[*core.PersistentVolumeClaim](ctx, r.context.ACS().CurrentClusterCache().PersistentVolumeClaim().V1().Read(), r.context.ACS().CurrentClusterCache().PersistentVolumeClaimsModInterface().V1(), p, removalList, false)
if err != nil {
log.Err(err).Debug("Failed to update PVC (to remove finalizers)")
return 0, errors.WithStack(err)
diff --git a/pkg/deployment/resources/pvc_inspector.go b/pkg/deployment/resources/pvc_inspector.go
index f1aad19e3..f369f3be2 100644
--- a/pkg/deployment/resources/pvc_inspector.go
+++ b/pkg/deployment/resources/pvc_inspector.go
@@ -77,7 +77,7 @@ func (r *Resources) InspectPVCs(ctx context.Context, cachedStatus inspectorInter
// Strange, pvc belongs to us, but we have no member for it.
// Remove all finalizers, so it can be removed.
log.Str("pvc", pvc.GetName()).Warn("PVC belongs to this deployment, but we don't know the member. Removing all finalizers")
- _, err := k8sutil.RemovePVCFinalizers(ctx, r.context.ACS().CurrentClusterCache(), cachedStatus.PersistentVolumeClaimsModInterface().V1(), pvc, pvc.GetFinalizers(), false)
+ _, err := k8sutil.RemoveSelectedFinalizers[*core.PersistentVolumeClaim](ctx, r.context.ACS().CurrentClusterCache().PersistentVolumeClaim().V1().Read(), r.context.ACS().CurrentClusterCache().PersistentVolumeClaimsModInterface().V1(), pvc, pvc.GetFinalizers(), false)
if err != nil {
log.Str("pvc", pvc.GetName()).Err(err).Debug("Failed to update PVC (to remove all finalizers)")
return errors.WithStack(err)
diff --git a/pkg/handlers/networking/route/handler.go b/pkg/handlers/networking/route/handler.go
index fc5ea603b..bd6009a8b 100644
--- a/pkg/handlers/networking/route/handler.go
+++ b/pkg/handlers/networking/route/handler.go
@@ -101,7 +101,7 @@ func (h *handler) HandleSpecValidity(ctx context.Context, item operation.Item, e
logger.Err(err).Warn("Invalid Spec on %s", item.String())
- if status.Conditions.Update(networkingApi.SpecValidCondition, false, "Spec is invalid", "Spec is invalid") {
+ if status.Conditions.Update(networkingApi.SpecValidCondition, false, "Spec is invalid", err.Error()) {
return true, operator.Stop("Invalid spec")
}
return false, operator.Stop("Invalid spec")
diff --git a/pkg/handlers/networking/route/handler_destination.go b/pkg/handlers/networking/route/handler_destination.go
index dfa1e65fd..f6232c2f2 100644
--- a/pkg/handlers/networking/route/handler_destination.go
+++ b/pkg/handlers/networking/route/handler_destination.go
@@ -22,157 +22,20 @@ package route
import (
"context"
- "fmt"
-
- core "k8s.io/api/core/v1"
- meta "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/util/intstr"
api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1"
operator "github.com/arangodb/kube-arangodb/pkg/operatorV2"
"github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
- "github.com/arangodb/kube-arangodb/pkg/util"
)
func (h *handler) HandleArangoDestination(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, deployment *api.ArangoDeployment) (*operator.Condition, bool, error) {
if dest := extension.Spec.GetDestination(); dest != nil {
if svc := dest.GetService(); svc != nil {
- port := svc.Port
-
- if port == nil {
- return &operator.Condition{
- Status: false,
- Reason: "Destination Not Found",
- Message: "Missing Port definition",
- }, false, nil
- }
-
- s, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.kubeClient.CoreV1().Services(svc.GetNamespace(extension)).Get, svc.GetName(), meta.GetOptions{})
- if err != nil {
- if api.IsNotFound(err) {
- return &operator.Condition{
- Status: false,
- Reason: "Destination Not Found",
- Message: fmt.Sprintf("Service `%s/%s` Not found", svc.GetNamespace(extension), svc.GetName()),
- }, 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
- }
-
- if !svc.Equals(s) {
- return &operator.Condition{
- Status: false,
- Reason: "Destination Not Found",
- Message: fmt.Sprintf("Service `%s/%s` Changed", svc.GetNamespace(extension), svc.GetName()),
- }, false, nil
- }
-
- var destPort int32
-
- if port.Type == intstr.Int {
- p, ok := util.PickFromList(s.Spec.Ports, func(v core.ServicePort) bool {
- return v.Port == port.IntVal
- })
- if !ok {
- return &operator.Condition{
- Status: false,
- Reason: "Destination Not Found",
- Message: fmt.Sprintf("Port `%d` not defined on Service `%s/%s`", port.IntVal, svc.GetNamespace(extension), svc.GetName()),
- }, false, nil
- }
-
- destPort = p.Port
- } else if port.Type == intstr.String && port.StrVal != "" {
- p, ok := util.PickFromList(s.Spec.Ports, func(v core.ServicePort) bool {
- return v.Name == port.StrVal
- })
- if !ok {
- return &operator.Condition{
- Status: false,
- Reason: "Destination Not Found",
- Message: fmt.Sprintf("Port `%s` not defined on Service `%s/%s`", port.StrVal, svc.GetNamespace(extension), svc.GetName()),
- }, false, nil
- }
-
- destPort = p.Port
- } else {
- return &operator.Condition{
- Status: false,
- Reason: "Destination Not Found",
- Message: "Unknown Port definition",
- }, false, nil
- }
-
- if destPort == -1 {
- return &operator.Condition{
- Status: false,
- Reason: "Destination Not Found",
- Message: fmt.Sprintf("Unable to discover port on Service `%s/%s`", svc.GetNamespace(extension), svc.GetName()),
- }, false, nil
- }
-
- var target networkingApi.ArangoRouteStatusTarget
-
- target.Path = dest.GetPath()
-
- // Render Auth Settings
-
- target.Authentication.Type = dest.GetAuthentication().GetType()
- target.Authentication.PassMode = dest.GetAuthentication().GetPassMode()
-
- if dest.Schema.Get() == networkingApi.ArangoRouteSpecDestinationSchemaHTTPS {
- target.TLS = &networkingApi.ArangoRouteStatusTargetTLS{
- Insecure: util.NewType(extension.Spec.Destination.GetTLS().GetInsecure()),
- }
- }
-
- if ip := s.Spec.ClusterIP; ip != "" {
- target.Destinations = networkingApi.ArangoRouteStatusTargetDestinations{
- networkingApi.ArangoRouteStatusTargetDestination{
- Host: ip,
- Port: destPort,
- },
- }
- } else {
- if domain := deployment.Spec.ClusterDomain; domain != nil {
- target.Destinations = networkingApi.ArangoRouteStatusTargetDestinations{
- networkingApi.ArangoRouteStatusTargetDestination{
- Host: fmt.Sprintf("%s.%s.svc.%s", s.GetName(), s.GetNamespace(), *domain),
- Port: destPort,
- },
- }
- } else {
- target.Destinations = networkingApi.ArangoRouteStatusTargetDestinations{
- networkingApi.ArangoRouteStatusTargetDestination{
- Host: fmt.Sprintf("%s.%s.svc", s.GetName(), s.GetNamespace()),
- Port: destPort,
- },
- }
- }
- }
-
- if status.Target.Hash() == target.Hash() {
- return &operator.Condition{
- Status: true,
- Reason: "Destination Found",
- Message: "Destination Found",
- Hash: target.Hash(),
- }, false, nil
- }
-
- status.Target = &target
- return &operator.Condition{
- Status: true,
- Reason: "Destination Found",
- Message: "Destination Found",
- Hash: target.Hash(),
- }, true, nil
+ return h.HandleArangoDestinationService(ctx, item, extension, status, deployment, dest, svc)
+ }
+ if endpoints := dest.GetEndpoints(); endpoints != nil {
+ return h.HandleArangoDestinationEndpoints(ctx, item, extension, status, deployment, dest, endpoints)
}
}
diff --git a/pkg/handlers/networking/route/handler_destination_endpoints.go b/pkg/handlers/networking/route/handler_destination_endpoints.go
new file mode 100644
index 000000000..c67a9a8a1
--- /dev/null
+++ b/pkg/handlers/networking/route/handler_destination_endpoints.go
@@ -0,0 +1,180 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 route
+
+import (
+ "context"
+ "fmt"
+
+ core "k8s.io/api/core/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/intstr"
+
+ api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
+ networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1"
+ operator "github.com/arangodb/kube-arangodb/pkg/operatorV2"
+ "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
+ "github.com/arangodb/kube-arangodb/pkg/util"
+)
+
+func (h *handler) HandleArangoDestinationEndpoints(ctx context.Context, item operation.Item, extension *networkingApi.ArangoRoute, status *networkingApi.ArangoRouteStatus, deployment *api.ArangoDeployment, dest *networkingApi.ArangoRouteSpecDestination, endpoints *networkingApi.ArangoRouteSpecDestinationEndpoints) (*operator.Condition, bool, error) {
+ port := endpoints.Port
+
+ if port == nil {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: "Missing Port definition",
+ }, false, nil
+ }
+
+ s, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.kubeClient.CoreV1().Services(endpoints.GetNamespace(extension)).Get, endpoints.GetName(), meta.GetOptions{})
+ if err != nil {
+ if api.IsNotFound(err) {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Service `%s/%s` Not found", endpoints.GetNamespace(extension), endpoints.GetName()),
+ }, 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
+ }
+
+ if !endpoints.Equals(s) {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Service `%s/%s` Changed", endpoints.GetNamespace(extension), endpoints.GetName()),
+ }, false, nil
+ }
+
+ e, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.kubeClient.CoreV1().Endpoints(endpoints.GetNamespace(extension)).Get, endpoints.GetName(), meta.GetOptions{})
+ if err != nil {
+ if api.IsNotFound(err) {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Endpoints `%s/%s` Not found", endpoints.GetNamespace(extension), endpoints.GetName()),
+ }, 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
+ }
+
+ // Discover port name - empty names are allowed
+ var destPortName = "N/A"
+
+ if port.Type == intstr.Int {
+ p, ok := util.PickFromList(s.Spec.Ports, func(v core.ServicePort) bool {
+ return v.Port == port.IntVal
+ })
+ if !ok {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Port `%d` not defined on Service `%s/%s`", port.IntVal, endpoints.GetNamespace(extension), endpoints.GetName()),
+ }, false, nil
+ }
+
+ destPortName = p.Name
+ } else if port.Type == intstr.String {
+ destPortName = port.StrVal
+ }
+
+ if destPortName == "N/A" {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Unable to discover port on Service `%s/%s`", endpoints.GetNamespace(extension), endpoints.GetName()),
+ }, false, nil
+ }
+
+ var target networkingApi.ArangoRouteStatusTarget
+
+ target.Path = dest.GetPath()
+ target.Type = networkingApi.ArangoRouteStatusTargetEndpointsType
+
+ // Render Auth Settings
+
+ target.Authentication.Type = dest.GetAuthentication().GetType()
+ target.Authentication.PassMode = dest.GetAuthentication().GetPassMode()
+
+ if dest.Schema.Get() == networkingApi.ArangoRouteSpecDestinationSchemaHTTPS {
+ target.TLS = &networkingApi.ArangoRouteStatusTargetTLS{
+ Insecure: util.NewType(extension.Spec.Destination.GetTLS().GetInsecure()),
+ }
+ }
+
+ for _, subset := range e.Subsets {
+ p, ok := util.PickFromList(subset.Ports, func(v core.EndpointPort) bool {
+ return v.Name == destPortName
+ })
+ if !ok {
+ continue
+ }
+
+ for _, address := range subset.Addresses {
+ target.Destinations = append(target.Destinations, networkingApi.ArangoRouteStatusTargetDestination{
+ Host: address.IP,
+ Port: p.Port,
+ })
+ }
+
+ if s.Spec.PublishNotReadyAddresses {
+ for _, address := range subset.NotReadyAddresses {
+ target.Destinations = append(target.Destinations, networkingApi.ArangoRouteStatusTargetDestination{
+ Host: address.IP,
+ Port: p.Port,
+ })
+ }
+ }
+ }
+
+ target.Destinations = util.Sort(target.Destinations, func(i, j networkingApi.ArangoRouteStatusTargetDestination) bool {
+ return i.Hash() < j.Hash()
+ })
+
+ if status.Target.Hash() == target.Hash() {
+ return &operator.Condition{
+ Status: true,
+ Reason: "Destination Found",
+ Message: "Destination Found",
+ Hash: target.Hash(),
+ }, false, nil
+ }
+
+ status.Target = &target
+ return &operator.Condition{
+ Status: true,
+ Reason: "Destination Found",
+ Message: "Destination Found",
+ Hash: target.Hash(),
+ }, true, nil
+}
diff --git a/pkg/handlers/networking/route/handler_destination_endpoints_test.go b/pkg/handlers/networking/route/handler_destination_endpoints_test.go
new file mode 100644
index 000000000..469ce724d
--- /dev/null
+++ b/pkg/handlers/networking/route/handler_destination_endpoints_test.go
@@ -0,0 +1,330 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 route
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ core "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/util/intstr"
+
+ api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
+ networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1"
+ sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
+ "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
+ "github.com/arangodb/kube-arangodb/pkg/util"
+ "github.com/arangodb/kube-arangodb/pkg/util/tests"
+)
+
+func Test_Handler_Destination_Endpoints_Valid(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Endpoints: &networkingApi.ArangoRouteSpecDestinationEndpoints{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ })
+ endpoints := tests.NewMetaObject[*core.Endpoints](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Endpoints) {
+ obj.Subsets = []core.EndpointSubset{
+ {
+ Addresses: []core.EndpointAddress{
+ {
+ IP: "127.0.0.1",
+ },
+ },
+ Ports: []core.EndpointPort{
+ {
+ Name: "",
+ Port: 10244,
+ },
+ },
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc, &endpoints)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetEndpointsType, extension.Status.Target.Type)
+
+ require.Len(t, extension.Status.Target.RenderURLs(), 1)
+ require.EqualValues(t, "http://127.0.0.1:10244/", extension.Status.Target.RenderURLs()[0])
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+}
+
+func Test_Handler_Destination_Endpoints_PortForward(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Endpoints: &networkingApi.ArangoRouteSpecDestinationEndpoints{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ })
+ endpoints := tests.NewMetaObject[*core.Endpoints](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Endpoints) {
+ obj.Subsets = []core.EndpointSubset{
+ {
+ Addresses: []core.EndpointAddress{
+ {
+ IP: "127.0.0.1",
+ },
+ },
+ Ports: []core.EndpointPort{
+ {
+ Name: "",
+ Port: 10245,
+ },
+ },
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc, &endpoints)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetEndpointsType, extension.Status.Target.Type)
+
+ require.Len(t, extension.Status.Target.RenderURLs(), 1)
+ require.EqualValues(t, "http://127.0.0.1:10245/", extension.Status.Target.RenderURLs()[0])
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+}
+
+func Test_Handler_Destination_Endpoints_MultiTargets(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Endpoints: &networkingApi.ArangoRouteSpecDestinationEndpoints{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ })
+ endpoints := tests.NewMetaObject[*core.Endpoints](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Endpoints) {
+ obj.Subsets = []core.EndpointSubset{
+ {
+ Addresses: []core.EndpointAddress{
+ {
+ IP: "127.0.0.1",
+ },
+ },
+ Ports: []core.EndpointPort{
+ {
+ Name: "",
+ Port: 10245,
+ },
+ },
+ },
+ {
+ Addresses: []core.EndpointAddress{
+ {
+ IP: "127.0.0.2",
+ },
+ },
+ Ports: []core.EndpointPort{
+ {
+ Name: "",
+ Port: 10246,
+ },
+ },
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc, &endpoints)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetEndpointsType, extension.Status.Target.Type)
+
+ require.Len(t, extension.Status.Target.RenderURLs(), 2)
+ require.EqualValues(t, "http://127.0.0.1:10245/", extension.Status.Target.RenderURLs()[0])
+ require.EqualValues(t, "http://127.0.0.2:10246/", extension.Status.Target.RenderURLs()[1])
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+}
+
+func Test_Handler_Destination_Endpoints_MultiDestinations(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Endpoints: &networkingApi.ArangoRouteSpecDestinationEndpoints{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ })
+ endpoints := tests.NewMetaObject[*core.Endpoints](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Endpoints) {
+ obj.Subsets = []core.EndpointSubset{
+ {
+ Addresses: []core.EndpointAddress{
+ {
+ IP: "127.0.0.1",
+ },
+ {
+ IP: "127.0.0.2",
+ },
+ },
+ Ports: []core.EndpointPort{
+ {
+ Name: "",
+ Port: 10245,
+ },
+ },
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc, &endpoints)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetEndpointsType, extension.Status.Target.Type)
+
+ require.Len(t, extension.Status.Target.RenderURLs(), 2)
+ require.EqualValues(t, "http://127.0.0.1:10245/", extension.Status.Target.RenderURLs()[0])
+ require.EqualValues(t, "http://127.0.0.2:10245/", extension.Status.Target.RenderURLs()[1])
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+}
diff --git a/pkg/handlers/networking/route/handler_destination_service.go b/pkg/handlers/networking/route/handler_destination_service.go
new file mode 100644
index 000000000..de56f4efb
--- /dev/null
+++ b/pkg/handlers/networking/route/handler_destination_service.go
@@ -0,0 +1,175 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 route
+
+import (
+ "context"
+ "fmt"
+
+ core "k8s.io/api/core/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/util/intstr"
+
+ api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
+ networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1"
+ operator "github.com/arangodb/kube-arangodb/pkg/operatorV2"
+ "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
+ "github.com/arangodb/kube-arangodb/pkg/util"
+)
+
+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) {
+ port := svc.Port
+
+ if port == nil {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: "Missing Port definition",
+ }, false, nil
+ }
+
+ s, err := util.WithKubernetesContextTimeoutP2A2(ctx, h.kubeClient.CoreV1().Services(svc.GetNamespace(extension)).Get, svc.GetName(), meta.GetOptions{})
+ if err != nil {
+ if api.IsNotFound(err) {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Service `%s/%s` Not found", svc.GetNamespace(extension), svc.GetName()),
+ }, 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
+ }
+
+ if !svc.Equals(s) {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Service `%s/%s` Changed", svc.GetNamespace(extension), svc.GetName()),
+ }, false, nil
+ }
+
+ var destPort int32
+
+ if port.Type == intstr.Int {
+ p, ok := util.PickFromList(s.Spec.Ports, func(v core.ServicePort) bool {
+ return v.Port == port.IntVal
+ })
+ if !ok {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Port `%d` not defined on Service `%s/%s`", port.IntVal, svc.GetNamespace(extension), svc.GetName()),
+ }, false, nil
+ }
+
+ destPort = p.Port
+ } else if port.Type == intstr.String && port.StrVal != "" {
+ p, ok := util.PickFromList(s.Spec.Ports, func(v core.ServicePort) bool {
+ return v.Name == port.StrVal
+ })
+ if !ok {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Port `%s` not defined on Service `%s/%s`", port.StrVal, svc.GetNamespace(extension), svc.GetName()),
+ }, false, nil
+ }
+
+ destPort = p.Port
+ } else {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: "Unknown Port definition",
+ }, false, nil
+ }
+
+ if destPort == -1 {
+ return &operator.Condition{
+ Status: false,
+ Reason: "Destination Not Found",
+ Message: fmt.Sprintf("Unable to discover port on Service `%s/%s`", svc.GetNamespace(extension), svc.GetName()),
+ }, false, nil
+ }
+
+ var target networkingApi.ArangoRouteStatusTarget
+
+ target.Path = dest.GetPath()
+ target.Type = networkingApi.ArangoRouteStatusTargetServiceType
+
+ // Render Auth Settings
+
+ target.Authentication.Type = dest.GetAuthentication().GetType()
+ target.Authentication.PassMode = dest.GetAuthentication().GetPassMode()
+
+ if dest.Schema.Get() == networkingApi.ArangoRouteSpecDestinationSchemaHTTPS {
+ target.TLS = &networkingApi.ArangoRouteStatusTargetTLS{
+ Insecure: util.NewType(extension.Spec.Destination.GetTLS().GetInsecure()),
+ }
+ }
+
+ if ip := s.Spec.ClusterIP; ip != "" {
+ target.Destinations = networkingApi.ArangoRouteStatusTargetDestinations{
+ networkingApi.ArangoRouteStatusTargetDestination{
+ Host: ip,
+ Port: destPort,
+ },
+ }
+ } else {
+ if domain := deployment.Spec.ClusterDomain; domain != nil {
+ target.Destinations = networkingApi.ArangoRouteStatusTargetDestinations{
+ networkingApi.ArangoRouteStatusTargetDestination{
+ Host: fmt.Sprintf("%s.%s.svc.%s", s.GetName(), s.GetNamespace(), *domain),
+ Port: destPort,
+ },
+ }
+ } else {
+ target.Destinations = networkingApi.ArangoRouteStatusTargetDestinations{
+ networkingApi.ArangoRouteStatusTargetDestination{
+ Host: fmt.Sprintf("%s.%s.svc", s.GetName(), s.GetNamespace()),
+ Port: destPort,
+ },
+ }
+ }
+ }
+
+ if status.Target.Hash() == target.Hash() {
+ return &operator.Condition{
+ Status: true,
+ Reason: "Destination Found",
+ Message: "Destination Found",
+ Hash: target.Hash(),
+ }, false, nil
+ }
+
+ status.Target = &target
+ return &operator.Condition{
+ Status: true,
+ Reason: "Destination Found",
+ Message: "Destination Found",
+ Hash: target.Hash(),
+ }, true, nil
+}
diff --git a/pkg/handlers/networking/route/handler_destination_service_test.go b/pkg/handlers/networking/route/handler_destination_service_test.go
new file mode 100644
index 000000000..c62b354f2
--- /dev/null
+++ b/pkg/handlers/networking/route/handler_destination_service_test.go
@@ -0,0 +1,598 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 route
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ core "k8s.io/api/core/v1"
+ "k8s.io/apimachinery/pkg/util/intstr"
+
+ api "github.com/arangodb/kube-arangodb/pkg/apis/deployment/v1"
+ networkingApi "github.com/arangodb/kube-arangodb/pkg/apis/networking/v1alpha1"
+ sharedApi "github.com/arangodb/kube-arangodb/pkg/apis/shared/v1"
+ "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
+ "github.com/arangodb/kube-arangodb/pkg/util"
+ "github.com/arangodb/kube-arangodb/pkg/util/tests"
+)
+
+func Test_Handler_Destination_Service_Missing(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.False(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+
+ 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")
+}
+
+func Test_Handler_Destination_Service_Valid(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetServiceType, extension.Status.Target.Type)
+
+ require.Len(t, extension.Status.Target.RenderURLs(), 1)
+ require.EqualValues(t, "http://deployment.fake.svc:10244/", extension.Status.Target.RenderURLs()[0])
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+}
+
+func Test_Handler_Destination_Service_Valid_WithIP(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ obj.Spec.ClusterIP = "127.0.0.2"
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetServiceType, extension.Status.Target.Type)
+
+ require.Len(t, extension.Status.Target.RenderURLs(), 1)
+ require.EqualValues(t, "http://127.0.0.2:10244/", extension.Status.Target.RenderURLs()[0])
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+}
+
+func Test_Handler_Destination_Service_Valid_WithPath(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ Path: util.NewType("/test/path/"),
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ obj.Spec.ClusterIP = "127.0.0.2"
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetServiceType, extension.Status.Target.Type)
+
+ require.Len(t, extension.Status.Target.RenderURLs(), 1)
+ require.EqualValues(t, "http://127.0.0.2:10244/test/path/", extension.Status.Target.RenderURLs()[0])
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+}
+
+func Test_Handler_Destination_Service_ValidName(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromString("test")),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10241,
+ Name: "test1",
+ },
+ {
+ Port: 10244,
+ Name: "test",
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetServiceType, extension.Status.Target.Type)
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+}
+
+func Test_Handler_Destination_Service_WrongPort(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10245,
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.False(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+
+ 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, "Port `10244` not defined on Service `fake/deployment`")
+}
+
+func Test_Handler_Destination_Service_WrongPortName(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromString("test")),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10245,
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.False(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+
+ 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, "Port `test` not defined on Service `fake/deployment`")
+}
+
+func Test_Handler_Destination_Service_Insecure_Default(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Testcense
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetServiceType, extension.Status.Target.Type)
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+
+ require.False(t, extension.Status.Target.TLS.IsInsecure())
+}
+
+func Test_Handler_Destination_Service_Insecure_Nil(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ TLS: &networkingApi.ArangoRouteSpecDestinationTLS{
+ Insecure: nil,
+ },
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetServiceType, extension.Status.Target.Type)
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+
+ require.False(t, extension.Status.Target.TLS.IsInsecure())
+}
+
+func Test_Handler_Destination_Service_Insecure_HTTPS_Override(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ TLS: &networkingApi.ArangoRouteSpecDestinationTLS{
+ Insecure: util.NewType(true),
+ },
+ Schema: util.NewType(networkingApi.ArangoRouteSpecDestinationSchemaHTTPS),
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetServiceType, extension.Status.Target.Type)
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+
+ require.True(t, extension.Status.Target.TLS.IsInsecure())
+}
+
+func Test_Handler_Destination_Service_Insecure_HTTP_Override(t *testing.T) {
+ // Setup
+ handler := newFakeHandler()
+
+ // Arrange
+ extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Deployment = util.NewType("deployment")
+ },
+ func(t *testing.T, obj *networkingApi.ArangoRoute) {
+ obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
+ Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Object: &sharedApi.Object{
+ Name: "deployment",
+ },
+ Port: util.NewType(intstr.FromInt32(10244)),
+ },
+ TLS: &networkingApi.ArangoRouteSpecDestinationTLS{
+ Insecure: util.NewType(true),
+ },
+ Schema: util.NewType(networkingApi.ArangoRouteSpecDestinationSchemaHTTP),
+ }
+ })
+ deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
+ svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
+ obj.Spec.Ports = []core.ServicePort{
+ {
+ Port: 10244,
+ },
+ }
+ })
+
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+
+ // Test
+ require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
+
+ // Refresh
+ refresh(t)
+
+ // Assert
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
+ require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
+ require.Equal(t, networkingApi.ArangoRouteStatusTargetServiceType, extension.Status.Target.Type)
+
+ c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.True(t, ok)
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Reason, "Destination Found")
+ require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
+
+ require.False(t, extension.Status.Target.TLS.IsInsecure())
+}
diff --git a/pkg/handlers/networking/route/handler_destination_test.go b/pkg/handlers/networking/route/handler_destination_test.go
index e53cd0f7f..a245c63c8 100644
--- a/pkg/handlers/networking/route/handler_destination_test.go
+++ b/pkg/handlers/networking/route/handler_destination_test.go
@@ -35,7 +35,7 @@ import (
"github.com/arangodb/kube-arangodb/pkg/util/tests"
)
-func Test_Handler_Destination_Service_Missing(t *testing.T) {
+func Test_Handler_MultiDest(t *testing.T) {
// Setup
handler := newFakeHandler()
@@ -46,342 +46,12 @@ func Test_Handler_Destination_Service_Missing(t *testing.T) {
},
func(t *testing.T, obj *networkingApi.ArangoRoute) {
obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
- Object: &sharedApi.Object{
- Name: "deployment",
- },
- Port: util.NewType(intstr.FromInt32(10244)),
- },
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension)
-
- // Test
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.False(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- 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")
-}
-
-func Test_Handler_Destination_Service_Valid(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
- Object: &sharedApi.Object{
- Name: "deployment",
- },
- Port: util.NewType(intstr.FromInt32(10244)),
- },
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
- svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
- obj.Spec.Ports = []core.ServicePort{
- {
- Port: 10244,
- },
- }
- })
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
-
- // Test
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- require.Len(t, extension.Status.Target.RenderURLs(), 1)
- require.EqualValues(t, "http://deployment.fake.svc:10244/", extension.Status.Target.RenderURLs()[0])
-
- c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
- require.True(t, ok)
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
-}
-
-func Test_Handler_Destination_Service_Valid_WithIP(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
- Object: &sharedApi.Object{
- Name: "deployment",
- },
- Port: util.NewType(intstr.FromInt32(10244)),
- },
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
- svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
- obj.Spec.Ports = []core.ServicePort{
- {
- Port: 10244,
- },
- }
- obj.Spec.ClusterIP = "127.0.0.2"
- })
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
-
- // Test
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- require.Len(t, extension.Status.Target.RenderURLs(), 1)
- require.EqualValues(t, "http://127.0.0.2:10244/", extension.Status.Target.RenderURLs()[0])
-
- c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
- require.True(t, ok)
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
-}
-
-func Test_Handler_Destination_Service_Valid_WithPath(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
- Object: &sharedApi.Object{
- Name: "deployment",
- },
- Port: util.NewType(intstr.FromInt32(10244)),
- },
- Path: util.NewType("/test/path/"),
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
- svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
- obj.Spec.Ports = []core.ServicePort{
- {
- Port: 10244,
- },
- }
- obj.Spec.ClusterIP = "127.0.0.2"
- })
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
-
- // Test
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- require.Len(t, extension.Status.Target.RenderURLs(), 1)
- require.EqualValues(t, "http://127.0.0.2:10244/test/path/", extension.Status.Target.RenderURLs()[0])
-
- c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
- require.True(t, ok)
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
-}
-
-func Test_Handler_Destination_Service_ValidName(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
- Object: &sharedApi.Object{
- Name: "deployment",
- },
- Port: util.NewType(intstr.FromString("test")),
- },
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
- svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
- obj.Spec.Ports = []core.ServicePort{
- {
- Port: 10241,
- Name: "test1",
- },
- {
- Port: 10244,
- Name: "test",
- },
- }
- })
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
-
- // Test
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
- require.True(t, ok)
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
-}
-
-func Test_Handler_Destination_Service_WrongPort(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
+ Endpoints: &networkingApi.ArangoRouteSpecDestinationEndpoints{
Object: &sharedApi.Object{
Name: "deployment",
},
Port: util.NewType(intstr.FromInt32(10244)),
},
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
- svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
- obj.Spec.Ports = []core.ServicePort{
- {
- Port: 10245,
- },
- }
- })
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
-
- // Test
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.False(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- 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, "Port `10244` not defined on Service `fake/deployment`")
-}
-
-func Test_Handler_Destination_Service_WrongPortName(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
- Object: &sharedApi.Object{
- Name: "deployment",
- },
- Port: util.NewType(intstr.FromString("test")),
- },
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
- svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
- obj.Spec.Ports = []core.ServicePort{
- {
- Port: 10245,
- },
- }
- })
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
-
- // Test
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.False(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.False(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- 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, "Port `test` not defined on Service `fake/deployment`")
-}
-
-func Test_Handler_Destination_Service_Insecure_Default(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
Service: &networkingApi.ArangoRouteSpecDestinationService{
Object: &sharedApi.Object{
Name: "deployment",
@@ -398,166 +68,25 @@ func Test_Handler_Destination_Service_Insecure_Default(t *testing.T) {
},
}
})
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
-
- // Testcense
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
- require.True(t, ok)
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
-
- require.False(t, extension.Status.Target.TLS.IsInsecure())
-}
-
-func Test_Handler_Destination_Service_Insecure_Nil(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
- Object: &sharedApi.Object{
- Name: "deployment",
- },
- Port: util.NewType(intstr.FromInt32(10244)),
- },
- TLS: &networkingApi.ArangoRouteSpecDestinationTLS{
- Insecure: nil,
- },
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
- svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
- obj.Spec.Ports = []core.ServicePort{
+ endpoints := tests.NewMetaObject[*core.Endpoints](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Endpoints) {
+ obj.Subsets = []core.EndpointSubset{
{
- Port: 10244,
- },
- }
- })
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
-
- // Test
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
- require.True(t, ok)
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
-
- require.False(t, extension.Status.Target.TLS.IsInsecure())
-}
-
-func Test_Handler_Destination_Service_Insecure_HTTPS_Override(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
- Object: &sharedApi.Object{
- Name: "deployment",
+ Addresses: []core.EndpointAddress{
+ {
+ IP: "127.0.0.1",
},
- Port: util.NewType(intstr.FromInt32(10244)),
},
- TLS: &networkingApi.ArangoRouteSpecDestinationTLS{
- Insecure: util.NewType(true),
- },
- Schema: util.NewType(networkingApi.ArangoRouteSpecDestinationSchemaHTTPS),
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
- svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
- obj.Spec.Ports = []core.ServicePort{
- {
- Port: 10244,
- },
- }
- })
-
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
-
- // Test
- require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
-
- // Refresh
- refresh(t)
-
- // Assert
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
- require.True(t, ok)
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
-
- require.True(t, extension.Status.Target.TLS.IsInsecure())
-}
-
-func Test_Handler_Destination_Service_Insecure_HTTP_Override(t *testing.T) {
- // Setup
- handler := newFakeHandler()
-
- // Arrange
- extension := tests.NewMetaObject[*networkingApi.ArangoRoute](t, tests.FakeNamespace, "test",
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Deployment = util.NewType("deployment")
- },
- func(t *testing.T, obj *networkingApi.ArangoRoute) {
- obj.Spec.Destination = &networkingApi.ArangoRouteSpecDestination{
- Service: &networkingApi.ArangoRouteSpecDestinationService{
- Object: &sharedApi.Object{
- Name: "deployment",
+ Ports: []core.EndpointPort{
+ {
+ Name: "",
+ Port: 10244,
},
- Port: util.NewType(intstr.FromInt32(10244)),
- },
- TLS: &networkingApi.ArangoRouteSpecDestinationTLS{
- Insecure: util.NewType(true),
},
- Schema: util.NewType(networkingApi.ArangoRouteSpecDestinationSchemaHTTP),
- }
- })
- deployment := tests.NewMetaObject[*api.ArangoDeployment](t, tests.FakeNamespace, "deployment")
- svc := tests.NewMetaObject[*core.Service](t, tests.FakeNamespace, "deployment", func(t *testing.T, obj *core.Service) {
- obj.Spec.Ports = []core.ServicePort{
- {
- Port: 10244,
},
}
})
- refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc)
+ refresh := tests.CreateObjects(t, handler.kubeClient, handler.client, &deployment, &extension, &svc, &endpoints)
// Test
require.NoError(t, tests.Handle(handler, tests.NewItem(t, operation.Update, extension)))
@@ -566,14 +95,8 @@ func Test_Handler_Destination_Service_Insecure_HTTP_Override(t *testing.T) {
refresh(t)
// Assert
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.DestinationValidCondition))
- require.True(t, extension.Status.Conditions.IsTrue(networkingApi.ReadyCondition))
-
- c, ok := extension.Status.Conditions.Get(networkingApi.DestinationValidCondition)
+ require.False(t, extension.Status.Conditions.IsTrue(networkingApi.SpecValidCondition))
+ c, ok := extension.Status.Conditions.Get(networkingApi.SpecValidCondition)
require.True(t, ok)
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Reason, "Destination Found")
- require.EqualValues(t, c.Hash, extension.Status.Target.Hash())
-
- require.False(t, extension.Status.Target.TLS.IsInsecure())
+ require.EqualValues(t, "Received 1 errors: spec.destination: Too many elements provided. Expected 1, got 2. Defined: endpoints, service", c.Message)
}
diff --git a/pkg/replication/finalizers.go b/pkg/replication/finalizers.go
index 028075a0b..618617fb4 100644
--- a/pkg/replication/finalizers.go
+++ b/pkg/replication/finalizers.go
@@ -32,7 +32,6 @@ import (
"github.com/arangodb/go-driver"
api "github.com/arangodb/kube-arangodb/pkg/apis/replication/v1"
- "github.com/arangodb/kube-arangodb/pkg/generated/clientset/versioned"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/constants"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
@@ -85,7 +84,9 @@ func (dr *DeploymentReplication) runFinalizers(ctx context.Context, p *api.Arang
}
removalList := []string{constants.FinalizerDeplReplStopSync}
- if err := removeDeploymentReplicationFinalizers(dr.deps.Client.Arango(), p, removalList, false); err != nil {
+ c := dr.deps.Client.Arango().ReplicationV1().ArangoDeploymentReplications(p.GetNamespace())
+
+ if _, err := k8sutil.RemoveSelectedFinalizers[*api.ArangoDeploymentReplication](ctx, c, c, p, removalList, false); err != nil {
return true, errors.WithMessage(err, "Failed to update deployment replication (to remove finalizers)")
}
@@ -232,31 +233,6 @@ func (dr *DeploymentReplication) inspectFinalizerDeplReplStopSync(ctx context.Co
}
-// removeDeploymentReplicationFinalizers removes the given finalizers from the given DeploymentReplication.
-func removeDeploymentReplicationFinalizers(crcli versioned.Interface, p *api.ArangoDeploymentReplication, finalizers []string, ignoreNotFound bool) error {
- repls := crcli.ReplicationV1().ArangoDeploymentReplications(p.GetNamespace())
- getFunc := func() (meta.Object, error) {
- result, err := repls.Get(context.Background(), p.GetName(), meta.GetOptions{})
- if err != nil {
- return nil, errors.WithStack(err)
- }
- return result, nil
- }
- updateFunc := func(updated meta.Object) error {
- updatedRepl := updated.(*api.ArangoDeploymentReplication)
- result, err := repls.Update(context.Background(), updatedRepl, meta.UpdateOptions{})
- if err != nil {
- return errors.WithStack(err)
- }
- *p = *result
- return nil
- }
- if _, err := k8sutil.RemoveFinalizers(finalizers, getFunc, updateFunc, ignoreNotFound); err != nil {
- return errors.WithStack(err)
- }
- return nil
-}
-
// finalizerExists returns true if a given finalizer exists.
func finalizerExists(p *api.ArangoDeploymentReplication, finalizer string) bool {
for _, f := range p.ObjectMeta.GetFinalizers() {
diff --git a/pkg/util/k8sutil/finalizers.go b/pkg/util/k8sutil/finalizers.go
index 35100f54b..3ffa6a480 100644
--- a/pkg/util/k8sutil/finalizers.go
+++ b/pkg/util/k8sutil/finalizers.go
@@ -1,7 +1,7 @@
//
// DISCLAIMER
//
-// Copyright 2016-2022 ArangoDB GmbH, Cologne, Germany
+// Copyright 2016-2024 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.
@@ -24,95 +24,42 @@ import (
"context"
"sort"
- core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/arangodb/kube-arangodb/pkg/util/errors"
- "github.com/arangodb/kube-arangodb/pkg/util/globals"
- "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/persistentvolumeclaim"
- persistentvolumeclaimv1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/persistentvolumeclaim/v1"
- "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/pod"
- podv1 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/pod/v1"
+ "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/generic"
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
+ "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/patcher"
)
const (
maxRemoveFinalizersAttempts = 50
)
-// RemovePodFinalizers removes the given finalizers from the given pod.
-func RemovePodFinalizers(ctx context.Context, cachedStatus pod.Inspector, c podv1.ModInterface, p *core.Pod,
+// RemoveSelectedFinalizers removes the given finalizers from the given pod.
+func RemoveSelectedFinalizers[T meta.Object](ctx context.Context, getter generic.GetInterface[T], patcher generic.PatchInterface[T], p T,
finalizers []string, ignoreNotFound bool) (int, error) {
- getFunc := func() (meta.Object, error) {
- ctxChild, cancel := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
- defer cancel()
-
- result, err := cachedStatus.Pod().V1().Read().Get(ctxChild, p.GetName(), meta.GetOptions{})
- if err != nil {
- return nil, errors.WithStack(err)
- }
- return result, nil
- }
- updateFunc := func(updated meta.Object) error {
- updatedPod := updated.(*core.Pod)
- ctxChild, cancel := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
- defer cancel()
-
- result, err := c.Update(ctxChild, updatedPod, meta.UpdateOptions{})
- if err != nil {
- return errors.WithStack(err)
- }
- *p = *result
- return nil
- }
- if count, err := RemoveFinalizers(finalizers, getFunc, updateFunc, ignoreNotFound); err != nil {
+ if count, err := RemoveFinalizers(ctx, getter, patcher, p.GetName(), finalizers, ignoreNotFound); err != nil {
return 0, errors.WithStack(err)
} else {
return count, nil
}
}
-// RemovePVCFinalizers removes the given finalizers from the given PVC.
-func RemovePVCFinalizers(ctx context.Context, cachedStatus persistentvolumeclaim.Inspector, c persistentvolumeclaimv1.ModInterface,
- p *core.PersistentVolumeClaim, finalizers []string, ignoreNotFound bool) (int, error) {
- getFunc := func() (meta.Object, error) {
- ctxChild, cancel := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
- defer cancel()
-
- result, err := cachedStatus.PersistentVolumeClaim().V1().Read().Get(ctxChild, p.GetName(), meta.GetOptions{})
- if err != nil {
- return nil, errors.WithStack(err)
- }
- return result, nil
- }
- updateFunc := func(updated meta.Object) error {
- updatedPVC := updated.(*core.PersistentVolumeClaim)
- ctxChild, cancel := globals.GetGlobalTimeouts().Kubernetes().WithTimeout(ctx)
- defer cancel()
-
- result, err := c.Update(ctxChild, updatedPVC, meta.UpdateOptions{})
- if err != nil {
- return errors.WithStack(err)
- }
- *p = *result
- return nil
- }
- if count, err := RemoveFinalizers(finalizers, getFunc, updateFunc, ignoreNotFound); err != nil {
- return 0, errors.WithStack(err)
- } else {
- return count, nil
- }
+type RemoveFinalizersClient[T meta.Object] interface {
+ generic.GetInterface[T]
+ generic.PatchInterface[T]
}
// RemoveFinalizers is a helper used to remove finalizers from an object.
// The functions tries to get the object using the provided get function,
// then remove the given finalizers and update the update using the given update function.
// In case of an update conflict, the functions tries again.
-func RemoveFinalizers(finalizers []string, getFunc func() (meta.Object, error), updateFunc func(meta.Object) error, ignoreNotFound bool) (int, error) {
+func RemoveFinalizers[T meta.Object](ctx context.Context, getter generic.GetInterface[T], p generic.PatchInterface[T], name string, finalizers []string, ignoreNotFound bool) (int, error) {
attempts := 0
for {
attempts++
- obj, err := getFunc()
+ obj, err := getter.Get(ctx, name, meta.GetOptions{})
if err != nil {
if kerrors.IsNotFound(err) && ignoreNotFound {
// Object no longer found and we're allowed to ignore that.
@@ -140,8 +87,7 @@ func RemoveFinalizers(finalizers []string, getFunc func() (meta.Object, error),
}
}
if z := len(original) - len(newList); z > 0 {
- obj.SetFinalizers(newList)
- if err := updateFunc(obj); kerrors.IsConflict(err) {
+ if _, _, err := patcher.Patcher[T](ctx, p, obj, meta.PatchOptions{}, patcher.Finalizers[T](newList)); kerrors.IsConflict(err) {
if attempts > maxRemoveFinalizersAttempts {
return 0, errors.WithStack(err)
} else {
diff --git a/pkg/util/k8sutil/patcher/common.go b/pkg/util/k8sutil/patcher/common.go
new file mode 100644
index 000000000..a5cbc3b33
--- /dev/null
+++ b/pkg/util/k8sutil/patcher/common.go
@@ -0,0 +1,35 @@
+//
+// DISCLAIMER
+//
+// Copyright 2024 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 patcher
+
+import (
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/arangodb/kube-arangodb/pkg/deployment/patch"
+)
+
+func Finalizers[T meta.Object](finalizers []string) Patch[T] {
+ return func(in T) []patch.Item {
+ return []patch.Item{
+ patch.ItemReplace(patch.NewPath("metadata", "finalizers"), finalizers),
+ }
+ }
+}
diff --git a/pkg/util/k8sutil/patcher/patcher.go b/pkg/util/k8sutil/patcher/patcher.go
index 41bbd53b7..ab95ff474 100644
--- a/pkg/util/k8sutil/patcher/patcher.go
+++ b/pkg/util/k8sutil/patcher/patcher.go
@@ -30,12 +30,13 @@ import (
"github.com/arangodb/kube-arangodb/pkg/deployment/patch"
"github.com/arangodb/kube-arangodb/pkg/util"
"github.com/arangodb/kube-arangodb/pkg/util/globals"
+ "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/inspector/generic"
)
type Patch[T meta.Object] func(in T) []patch.Item
type Client[T meta.Object] interface {
- Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts meta.PatchOptions, subresources ...string) (result T, err error)
+ generic.PatchInterface[T]
}
func Patcher[T meta.Object](ctx context.Context, client Client[T], in T, opts meta.PatchOptions, functions ...Patch[T]) (T, bool, error) {
diff --git a/pkg/util/list.go b/pkg/util/list.go
index 2046e4ee8..13409bc7f 100644
--- a/pkg/util/list.go
+++ b/pkg/util/list.go
@@ -107,3 +107,19 @@ func CopyList[A any](in []A) []A {
copy(ret, in)
return ret
}
+
+func FlattenList[A any](in [][]A) []A {
+ count := 0
+
+ for _, v := range in {
+ count += len(v)
+ }
+
+ res := make([]A, 0, count)
+
+ for _, v := range in {
+ res = append(res, v...)
+ }
+
+ return res
+}
diff --git a/pkg/util/tests/kubernetes.go b/pkg/util/tests/kubernetes.go
index 5731a783a..ea5c520a1 100644
--- a/pkg/util/tests/kubernetes.go
+++ b/pkg/util/tests/kubernetes.go
@@ -143,6 +143,12 @@ func CreateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe
vl := *v
_, err := k8s.CoreV1().Services(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
require.NoError(t, err)
+ case **core.Endpoints:
+ require.NotNil(t, v)
+
+ vl := *v
+ _, err := k8s.CoreV1().Endpoints(vl.GetNamespace()).Create(context.Background(), vl, meta.CreateOptions{})
+ require.NoError(t, err)
case **core.ServiceAccount:
require.NotNil(t, v)
@@ -310,6 +316,12 @@ func UpdateObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe
vl := *v
_, err := k8s.CoreV1().Services(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{})
require.NoError(t, err)
+ case **core.Endpoints:
+ require.NotNil(t, v)
+
+ vl := *v
+ _, err := k8s.CoreV1().Endpoints(vl.GetNamespace()).Update(context.Background(), vl, meta.UpdateOptions{})
+ require.NoError(t, err)
case **core.ServiceAccount:
require.NotNil(t, v)
@@ -466,6 +478,11 @@ func DeleteObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientSe
vl := *v
require.NoError(t, k8s.CoreV1().Services(vl.GetNamespace()).Delete(context.Background(), vl.GetName(), meta.DeleteOptions{}))
+ case **core.Endpoints:
+ require.NotNil(t, v)
+
+ vl := *v
+ require.NoError(t, k8s.CoreV1().Endpoints(vl.GetNamespace()).Delete(context.Background(), vl.GetName(), meta.DeleteOptions{}))
case **core.ServiceAccount:
require.NotNil(t, v)
@@ -664,6 +681,21 @@ func RefreshObjects(t *testing.T, k8s kubernetes.Interface, arango arangoClientS
} else {
*v = vn
}
+ case **core.Endpoints:
+ require.NotNil(t, v)
+
+ vl := *v
+
+ vn, err := k8s.CoreV1().Endpoints(vl.GetNamespace()).Get(context.Background(), vl.GetName(), meta.GetOptions{})
+ if err != nil {
+ if kerrors.IsNotFound(err) {
+ *v = nil
+ } else {
+ require.NoError(t, err)
+ }
+ } else {
+ *v = vn
+ }
case **core.ServiceAccount:
require.NotNil(t, v)
@@ -994,6 +1026,12 @@ func SetMetaBasedOnType(t *testing.T, object meta.Object) {
v.SetSelfLink(fmt.Sprintf("/api/v1/services/%s/%s",
object.GetNamespace(),
object.GetName()))
+ case *core.Endpoints:
+ v.Kind = "Endpoints"
+ v.APIVersion = "v1"
+ v.SetSelfLink(fmt.Sprintf("/api/v1/endpoints/%s/%s",
+ object.GetNamespace(),
+ object.GetName()))
case *core.ServiceAccount:
v.Kind = "ServiceAccount"
v.APIVersion = "v1"
@@ -1214,6 +1252,12 @@ func GVK(t *testing.T, object meta.Object) schema.GroupVersionKind {
Version: "v1",
Kind: "Service",
}
+ case *core.Endpoints:
+ return schema.GroupVersionKind{
+ Group: "",
+ Version: "v1",
+ Kind: "Endpoints",
+ }
case *core.ServiceAccount:
return schema.GroupVersionKind{
Group: "",
diff --git a/pkg/util/tests/kubernetes_test.go b/pkg/util/tests/kubernetes_test.go
index f20f6da01..c29d42215 100644
--- a/pkg/util/tests/kubernetes_test.go
+++ b/pkg/util/tests/kubernetes_test.go
@@ -73,6 +73,7 @@ func Test_NewMetaObject(t *testing.T) {
NewMetaObjectRun[*core.ConfigMap](t)
NewMetaObjectRun[*core.ServiceAccount](t)
NewMetaObjectRun[*core.Service](t)
+ NewMetaObjectRun[*core.Endpoints](t)
NewMetaObjectRun[*apps.StatefulSet](t)
NewMetaObjectRun[*rbac.Role](t)
NewMetaObjectRun[*rbac.RoleBinding](t)