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/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/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)