Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Direct Response #3977

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions api/v1alpha1/virtualbackend_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright Envoy Gateway Authors
// SPDX-License-Identifier: Apache-2.0
// The full text of the Apache license is available in the LICENSE file at
// the root of the repo.

package v1alpha1

import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

const (
// KindVirtualBackend is the name of the VirtualBackend kind.
KindVirtualBackend = "VirtualBackend"
)

// +kubebuilder:validation:Minimum=100
// +kubebuilder:validation:Maximum=599
type StatusCode uint32

// +kubebuilder:validation:Pattern=`^([\w-]+)$`
type ResponseHeader string

// +kubebuilder:object:root=true
// +kubebuilder:resource:categories=envoy-gateway,shortName=vb
//
// VirtualBackend defines the configuration for direct response.
type VirtualBackend struct {
Copy link
Member

@zirain zirain Aug 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@guydc do you think it's a good idea to support this in Backend?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the very late response. IMHO, the best direction here is to support this through a custom filter, as outlined here: #4120

metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// Spec defines desired state of VirtualBackend.
Spec VirtualBackendSpec `json:"spec"`
}

// +kubebuilder:validation:XValidation:rule="has(self.statusCode)"
//
// VirtualBackendSpec defines direct response configuration.
type VirtualBackendSpec struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add some CEL validations here, and also document what the defaults are if nothing is provided.

For consistency with other EG APIs, if the value is optional:

  • It should be a pointer
  • The JSON tag should specify "omitempty"
  • There should be some documentation around what is used if the value was not specified.

This is especially important for the StatusCode field - there might not be a body but the direct response should always include a StatusCode - it shouldn't be possible to leave this empty.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@liorokman done.

// +optional
//
// Body contains data which gateway returns in direct response.
Body *[]byte `json:"body,omitempty" yaml:"body,omitempty"`

// +kubebuilder:default=200
//
// StatusCode defines HTTP response status code of direct response. Default value is 200.
StatusCode StatusCode `json:"statusCode" yaml:"statusCode"`

// +optional
//
// ResponseHeaders defines Header:Value map of additional headers to response.
ResponseHeaders map[ResponseHeader]string `json:"responseHeaders,omitempty" yaml:"responseHeaders,omitempty"`
}
57 changes: 57 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.15.0
name: virtualbackends.gateway.envoyproxy.io
spec:
group: gateway.envoyproxy.io
names:
categories:
- envoy-gateway
kind: VirtualBackend
listKind: VirtualBackendList
plural: virtualbackends
shortNames:
- vb
singular: virtualbackend
scope: Namespaced
versions:
- name: v1alpha1
schema:
openAPIV3Schema:
description: VirtualBackend defines the configuration for direct response.
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: Spec defines desired state of VirtualBackend.
properties:
body:
description: Body contains data which gateway returns in direct response.
format: byte
type: string
responseHeaders:
additionalProperties:
type: string
description: ResponseHeaders defines Header:Value map of additional
headers to response.
type: object
statusCode:
default: 200
description: StatusCode defines HTTP response status code of direct
response. Default value is 200.
format: int32
maximum: 599
minimum: 100
type: integer
required:
- statusCode
type: object
x-kubernetes-validations:
- rule: has(self.statusCode)
required:
- spec
type: object
served: true
storage: true
12 changes: 12 additions & 0 deletions internal/gatewayapi/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ type Resources struct {
EnvoyExtensionPolicies []*egv1a1.EnvoyExtensionPolicy `json:"envoyExtensionPolicies,omitempty" yaml:"envoyExtensionPolicies,omitempty"`
ExtensionServerPolicies []unstructured.Unstructured `json:"extensionServerPolicies,omitempty" yaml:"extensionServerPolicies,omitempty"`
Backends []*egv1a1.Backend `json:"backends,omitempty" yaml:"backends,omitempty"`
VirtualBackends []*egv1a1.VirtualBackend `json:"virtualBackends,omitempty" yaml:"virtualBackends,omitempty"`
}

func NewResources() *Resources {
Expand All @@ -86,6 +87,7 @@ func NewResources() *Resources {
EnvoyExtensionPolicies: []*egv1a1.EnvoyExtensionPolicy{},
ExtensionServerPolicies: []unstructured.Unstructured{},
Backends: []*egv1a1.Backend{},
VirtualBackends: []*egv1a1.VirtualBackend{},
}
}

Expand Down Expand Up @@ -139,6 +141,16 @@ func (r *Resources) GetBackend(namespace, name string) *egv1a1.Backend {
return nil
}

func (r *Resources) GetVirtualBackend(namespace, name string) *egv1a1.VirtualBackend {
for _, vb := range r.VirtualBackends {
if vb.Namespace == namespace && vb.Name == name {
return vb
}
}

return nil
}

func (r *Resources) GetSecret(namespace, name string) *corev1.Secret {
for _, secret := range r.Secrets {
if secret.Namespace == namespace && secret.Name == name {
Expand Down
23 changes: 22 additions & 1 deletion internal/gatewayapi/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,27 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
if !t.IsEnvoyServiceRouting(envoyProxy) && ds != nil && len(ds.Endpoints) > 0 && ds.AddressType != nil {
dstAddrTypeMap[*ds.AddressType]++
}

if backendRef.Kind != nil && *backendRef.Kind == egv1a1.KindVirtualBackend {
vb := resources.GetVirtualBackend(NamespaceDerefOr(backendRef.Namespace, httpRoute.Namespace), string(backendRef.Name))
for _, route := range ruleRoutes {
directResponse := &ir.DirectResponse{
Body: string(*vb.Spec.Body),
StatusCode: uint32(vb.Spec.StatusCode),
}

route.DirectResponse = directResponse
for header, value := range vb.Spec.ResponseHeaders {
responseHeader := ir.AddHeader{
Name: string(header),
Value: value,
Append: false,
}
route.AddResponseHeaders = append(route.AddResponseHeaders, responseHeader)
}
}
}

if ds == nil {
continue
}
Expand Down Expand Up @@ -237,7 +258,7 @@ func (t *Translator) processHTTPRouteRules(httpRoute *HTTPRouteContext, parentRe
// If the route has no valid backends then just use a direct response and don't fuss with weighted responses
for _, ruleRoute := range ruleRoutes {
noValidBackends := ruleRoute.Destination == nil || ruleRoute.Destination.ToBackendWeights().Valid == 0
if noValidBackends && ruleRoute.Redirect == nil {
if noValidBackends && ruleRoute.Redirect == nil && ruleRoute.DirectResponse == nil {
ruleRoute.DirectResponse = &ir.DirectResponse{
StatusCode: 500,
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
gateways:
- apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
namespace: envoy-gateway
name: gateway-1
spec:
gatewayClassName: envoy-gateway-class
listeners:
- name: http
protocol: HTTP
port: 80
hostname: "*.envoyproxy.io"
allowedRoutes:
namespaces:
from: All
httpRoutes:
- apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
namespace: default
name: httproute-1
spec:
hostnames:
- gateway.envoyproxy.io
parentRefs:
- namespace: envoy-gateway
name: gateway-1
sectionName: http
rules:
- matches:
- path:
value: "/"
backendRefs:
- kind: VirtualBackend
name: virtual-backend-1
virtualBackends:
- apiVersion: gateway.envoyproxy.io/v1alpha1
kind: VirtualBackend
metadata:
namespace: default
name: virtual-backend-1
spec:
body: eyJlcnIiOiAiU29tZSBlcnJvciJ9
statusCode: 501
Loading