Skip to content

Commit

Permalink
initial implementation of supporting FQDN address type for EndpointSlice
Browse files Browse the repository at this point in the history
Signed-off-by: sh2 <shawnhxh@outlook.com>
  • Loading branch information
shawnh2 committed Oct 31, 2023
1 parent d65ab59 commit 391eeb7
Show file tree
Hide file tree
Showing 5 changed files with 355 additions and 25 deletions.
2 changes: 1 addition & 1 deletion internal/gatewayapi/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (r *Resources) GetSecret(namespace, name string) *v1.Secret {
}

func (r *Resources) GetEndpointSlicesForBackend(svcNamespace, svcName string, backendKind string) []*discoveryv1.EndpointSlice {
endpointSlices := []*discoveryv1.EndpointSlice{}
var endpointSlices []*discoveryv1.EndpointSlice
for _, endpointSlice := range r.EndpointSlices {
var backendSelectorLabel string
switch backendKind {
Expand Down
9 changes: 5 additions & 4 deletions internal/gatewayapi/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"time"

corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/discovery/v1"
discoveryv1 "k8s.io/api/discovery/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
gwapiv1a1 "sigs.k8s.io/gateway-api/apis/v1alpha2"
Expand Down Expand Up @@ -1042,7 +1042,7 @@ func (t *Translator) processDestination(backendRef gwapiv1.BackendRef,
endpointSlices := resources.GetEndpointSlicesForBackend(backendNamespace, string(backendRef.Name), KindDerefOr(backendRef.Kind, KindService))
endpoints = getIREndpointsFromEndpointSlice(endpointSlices, servicePort.Name, servicePort.Protocol)
} else {
// Fall back to Service CluserIP routing
// Fall back to Service ClusterIP routing
ep := ir.NewDestEndpoint(
service.Spec.ClusterIP,
uint32(*backendRef.Port))
Expand Down Expand Up @@ -1127,8 +1127,8 @@ func (t *Translator) processAllowedListenersForParentRefs(routeContext RouteCont
return relevantRoute
}

func getIREndpointsFromEndpointSlice(endpointSlices []*v1.EndpointSlice, portName string, portProtocol corev1.Protocol) []*ir.DestinationEndpoint {
endpoints := []*ir.DestinationEndpoint{}
func getIREndpointsFromEndpointSlice(endpointSlices []*discoveryv1.EndpointSlice, portName string, portProtocol corev1.Protocol) []*ir.DestinationEndpoint {
var endpoints []*ir.DestinationEndpoint
for _, endpointSlice := range endpointSlices {
for _, endpoint := range endpointSlice.Endpoints {
for _, endpointPort := range endpointSlice.Ports {
Expand All @@ -1141,6 +1141,7 @@ func getIREndpointsFromEndpointSlice(endpointSlices []*v1.EndpointSlice, portNam
ep := ir.NewDestEndpoint(
address,
uint32(*endpointPort.Port))
ep.SetHostAddressType(endpointSlice.AddressType)
endpoints = append(endpoints, ep)
}
}
Expand Down
33 changes: 23 additions & 10 deletions internal/ir/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ import (

"github.com/tetratelabs/multierror"
"golang.org/x/exp/slices"

discoveryv1 "k8s.io/api/discovery/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/validation"

egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
"github.com/envoyproxy/gateway/api/v1alpha1/validation"
egv1a1validation "github.com/envoyproxy/gateway/api/v1alpha1/validation"
)

var (
Expand All @@ -32,7 +33,7 @@ var (
ErrHTTPRouteNameEmpty = errors.New("field Name must be specified")
ErrHTTPRouteHostnameEmpty = errors.New("field Hostname must be specified")
ErrDestinationNameEmpty = errors.New("field Name must be specified")
ErrDestEndpointHostInvalid = errors.New("field Address must be a valid IP address")
ErrDestEndpointHostInvalid = errors.New("field Address must be a valid IP or FQDN address")
ErrDestEndpointPortInvalid = errors.New("field Port specified is invalid")
ErrStringMatchConditionInvalid = errors.New("only one of the Exact, Prefix, SafeRegex or Distinct fields must be set")
ErrStringMatchNameIsEmpty = errors.New("field Name must be specified")
Expand Down Expand Up @@ -436,7 +437,7 @@ func (h HTTPRoute) Validate() error {
func (j *JWT) validate() error {
var errs error

if err := validation.ValidateJWTProvider(j.Providers); err != nil {
if err := egv1a1validation.ValidateJWTProvider(j.Providers); err != nil {
errs = multierror.Append(errs, err)
}

Expand Down Expand Up @@ -466,7 +467,6 @@ func (r RouteDestination) Validate() error {
}

return errs

}

// DestinationSetting holds the settings associated with the destination
Expand All @@ -488,7 +488,6 @@ func (d DestinationSetting) Validate() error {
}

return errs

}

// DestinationEndpoint holds the endpoint details associated with the destination
Expand All @@ -498,22 +497,36 @@ type DestinationEndpoint struct {
Host string `json:"host" yaml:"host"`
// Port on the service to forward the request to.
Port uint32 `json:"port" yaml:"port"`
// Type specifies the type of Host address.
Type discoveryv1.AddressType `json:"type" yaml:"type"`
}

// Validate the fields within the DestinationEndpoint structure
func (d DestinationEndpoint) Validate() error {
var errs error
// Only support IP hosts for now
if ip := net.ParseIP(d.Host); ip == nil {
errs = multierror.Append(errs, ErrDestEndpointHostInvalid)
switch d.Type {
case discoveryv1.AddressTypeFQDN:
if err := validation.IsDNS1123Subdomain(d.Host); err != nil {
errs = multierror.Append(errs, ErrDestEndpointHostInvalid)
}
default:
if ip := net.ParseIP(d.Host); ip == nil {
errs = multierror.Append(errs, ErrDestEndpointHostInvalid)
}
}

if d.Port == 0 {
errs = multierror.Append(errs, ErrDestEndpointPortInvalid)
}

return errs
}

// SetHostAddressType sets the host address type of DestinationEndpoint.
func (d *DestinationEndpoint) SetHostAddressType(hostType discoveryv1.AddressType) {
d.Type = hostType
}

// NewDestEndpoint creates a new DestinationEndpoint.
func NewDestEndpoint(host string, port uint32) *DestinationEndpoint {
return &DestinationEndpoint{
Expand All @@ -530,7 +543,7 @@ type AddHeader struct {
Append bool `json:"append" yaml:"append"`
}

// / Validate the fields within the AddHeader structure
// Validate the fields within the AddHeader structure
func (h AddHeader) Validate() error {
var errs error
if h.Name == "" {
Expand Down
65 changes: 55 additions & 10 deletions internal/xds/translator/translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
matcherv3 "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
resourcev3 "github.com/envoyproxy/go-control-plane/pkg/resource/v3"
"github.com/tetratelabs/multierror"
discoveryv1 "k8s.io/api/discovery/v1"

extensionTypes "github.com/envoyproxy/gateway/internal/extension/types"
"github.com/envoyproxy/gateway/internal/ir"
Expand Down Expand Up @@ -211,15 +212,11 @@ func (t *Translator) processHTTPListenerXdsTranslation(tCtx *types.ResourceVersi
vHost.Routes = append(vHost.Routes, xdsRoute)

if httpRoute.Destination != nil {
if err := addXdsCluster(tCtx, &xdsClusterArgs{
name: httpRoute.Destination.Name,
settings: httpRoute.Destination.Settings,
tSocket: nil,
protocol: protocol,
endpointType: Static,
loadBalancer: httpRoute.LoadBalancer,
}); err != nil && !errors.Is(err, ErrXdsClusterExists) {
return err
clusterArgs := splitEndpointsByHostAddressType(httpRoute.Destination, httpRoute.LoadBalancer, protocol)
for _, clusterArg := range clusterArgs {
if err := addXdsCluster(tCtx, clusterArg); err != nil && !errors.Is(err, ErrXdsClusterExists) {
return err
}
}
}

Expand Down Expand Up @@ -368,7 +365,7 @@ func findXdsListener(tCtx *types.ResourceVersionTable, name string) *listenerv3.
return nil
}

// findXdsRouteConfig finds an xds route with the name and returns nil if there is no match.
// findXdsRouteConfig finds a xds route with the name and returns nil if there is no match.
func findXdsRouteConfig(tCtx *types.ResourceVersionTable, name string) *routev3.RouteConfiguration {
if tCtx == nil || tCtx.XdsResources == nil || tCtx.XdsResources[resourcev3.RouteType] == nil {
return nil
Expand Down Expand Up @@ -461,5 +458,53 @@ const (
const (
DefaultEndpointType EndpointType = iota
Static
DNS
EDS
)

// splitEndpointsByHostAddressType splits FQDN host address type DestinationEndpoints from DestinationSettings.
func splitEndpointsByHostAddressType(rd *ir.RouteDestination, lb *ir.LoadBalancer, protocol ProtocolType) []*xdsClusterArgs {
// Group DestinationSettings by the type of endpoint.
groups := make(map[EndpointType][]*ir.DestinationSetting)
groupOrders := []EndpointType{Static, DNS}

for _, ds := range rd.Settings {
var fqdn, ips []*ir.DestinationEndpoint
for _, ep := range ds.Endpoints {
if ep.Type == discoveryv1.AddressTypeFQDN {
fqdn = append(fqdn, ep)
} else {
ips = append(ips, ep)
}
}
if len(fqdn) > 0 {
groups[DNS] = append(groups[DNS], &ir.DestinationSetting{
Weight: ds.Weight,
Endpoints: fqdn,
})
}
if len(ips) > 0 {
groups[Static] = append(groups[Static], &ir.DestinationSetting{
Weight: ds.Weight,
Endpoints: ips,
})
}
}

clusterArgs := make([]*xdsClusterArgs, 0, len(groups))
for _, endpointType := range groupOrders {
if len(groups[endpointType]) == 0 {
continue
}

clusterArgs = append(clusterArgs, &xdsClusterArgs{
name: fmt.Sprintf("%s-%d", rd.Name, endpointType),
settings: groups[endpointType],
tSocket: nil,
protocol: protocol,
endpointType: endpointType,
loadBalancer: lb,
})
}
return clusterArgs
}
Loading

0 comments on commit 391eeb7

Please sign in to comment.