Skip to content

Commit

Permalink
Three-Phase Endpoint Probing (knative-extensions#672)
Browse files Browse the repository at this point in the history
* Use the net-gateway-api's status prober

* add helpers for test fixtures

* sort headers based on name

* test case - new backend triggers endpoint probes

* test case - steady state endpoints are not ready

* test case - transition to new backends

* test case - route probing succeeds - ingress is ready

* test case - no backend transition when hash is different

* test-case - update endpoint probes when ingress has new backends

* Update the status prober to work with dynamic paths

Because probes on the HTTPRoute now have unique paths
we no longer use a vanilla prober from knative/networking.

The changes make the prober less coupled to Ingress and it requires
consumers to build up a list of backends URLs to probe.

The probe target lister now merges backends and URLs with their
corresponding proxy pod IPs and ports. The alternative
would be to look up HTTPRoutes in the informer cache but theres
no guarantee this is up to date.

* re-enable update tests, run codegen

* Add a third phase to the probing

We can't just drop the probing rules and move the backends into
the main rules. This still results in 503s with Contour.

Instead we now move the newer backends into the main rules and then
once that 'succeeds' we drop the probes.

* test case - add steady state reconcile while we are transition probing

* test case - handle backend updates

* fix boilerplate

* Add test coverage for resources functions
  • Loading branch information
dprotaso authored Apr 17, 2024
1 parent 7724433 commit e72ced6
Show file tree
Hide file tree
Showing 13 changed files with 2,261 additions and 644 deletions.
4 changes: 2 additions & 2 deletions docs/test-version.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ The following Gateway API version and Ingress were tested as part of the release

| Ingress | Tested version | Unavailable features |
| ------- | ----------------------- | ------------------------------ |
| Istio | v1.21.1 | retry,httpoption,update |
| Contour | v1.28.3 | httpoption,update |
| Istio | v1.21.1 | retry,httpoption |
| Contour | v1.28.3 | httpoption |
4 changes: 2 additions & 2 deletions hack/test-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@

export GATEWAY_API_VERSION="v1.0.0"
export ISTIO_VERSION="1.21.1"
export ISTIO_UNSUPPORTED_E2E_TESTS="retry,httpoption,update"
export ISTIO_UNSUPPORTED_E2E_TESTS="retry,httpoption"
export CONTOUR_VERSION="v1.28.3"
export CONTOUR_UNSUPPORTED_E2E_TESTS="httpoption,update"
export CONTOUR_UNSUPPORTED_E2E_TESTS="httpoption"
11 changes: 4 additions & 7 deletions pkg/reconciler/ingress/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ import (
"k8s.io/client-go/tools/cache"

"knative.dev/networking/pkg/apis/networking"
"knative.dev/networking/pkg/apis/networking/v1alpha1"
ingressinformer "knative.dev/networking/pkg/client/injection/informers/networking/v1alpha1/ingress"
ingressreconciler "knative.dev/networking/pkg/client/injection/reconciler/networking/v1alpha1/ingress"
networkcfg "knative.dev/networking/pkg/config"
"knative.dev/networking/pkg/status"
endpointsinformer "knative.dev/pkg/client/injection/kube/informers/core/v1/endpoints"
"knative.dev/pkg/configmap"
"knative.dev/pkg/controller"
Expand All @@ -39,6 +37,7 @@ import (
httprouteinformer "knative.dev/net-gateway-api/pkg/client/injection/informers/apis/v1beta1/httproute"
referencegrantinformer "knative.dev/net-gateway-api/pkg/client/injection/informers/apis/v1beta1/referencegrant"
"knative.dev/net-gateway-api/pkg/reconciler/ingress/config"
"knative.dev/net-gateway-api/pkg/status"
)

const (
Expand Down Expand Up @@ -105,13 +104,11 @@ func NewController(
statusProber := status.NewProber(
logger.Named("status-manager"),
NewProbeTargetLister(logger, endpointsInformer.Lister()),
func(ing *v1alpha1.Ingress) {
logger.Debugf("Ready callback triggered for ingress: %s/%s", ing.Namespace, ing.Name)
impl.EnqueueKey(types.NamespacedName{Namespace: ing.Namespace, Name: ing.Name})
func(ing types.NamespacedName) {
logger.Debugf("Ready callback triggered for ingress: %v", ing)
impl.EnqueueKey(ing)
})
c.statusManager = statusProber
// TODO: Bring up gateway-api community to discuss about probing.
// related to https://github.com/knative-sandbox/net-gateway-api/issues/18
statusProber.Start(ctx.Done())

// Make sure trackers are deleted once the observers are removed.
Expand Down
232 changes: 232 additions & 0 deletions pkg/reconciler/ingress/fixtures_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
Copyright 2021 The Knative Authors
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.
*/

package ingress

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
"knative.dev/networking/pkg/apis/networking"
"knative.dev/networking/pkg/http/header"
gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayapi "sigs.k8s.io/gateway-api/apis/v1beta1"
)

type RuleBuilder interface {
Build() gatewayapi.HTTPRouteRule
}

type HTTPRoute struct {
Namespace string
Name string
Hostnames []string
Hostname string
Rules []RuleBuilder
StatusConditions []metav1.Condition
}

func (r HTTPRoute) Build() *gatewayapi.HTTPRoute {
hostnames := r.Hostnames

if len(hostnames) == 0 && r.Hostname == "" {
hostnames = []string{"example.com"}
}

if r.Hostname != "" {
hostnames = append(hostnames, r.Hostname)
}

route := gatewayapi.HTTPRoute{
ObjectMeta: metav1.ObjectMeta{
Name: r.Name,
Namespace: r.Namespace,
Annotations: map[string]string{
networking.IngressClassAnnotationKey: gatewayAPIIngressClassName,
},
Labels: map[string]string{
networking.VisibilityLabelKey: "",
},
OwnerReferences: []metav1.OwnerReference{{
APIVersion: "networking.internal.knative.dev/v1alpha1",
Kind: "Ingress",
Name: "name",
Controller: ptr.To(true),
BlockOwnerDeletion: ptr.To(true),
}},
},
Spec: gatewayapi.HTTPRouteSpec{
CommonRouteSpec: gatewayapi.CommonRouteSpec{
ParentRefs: []gatewayapi.ParentReference{{
Group: ptr.To[gatewayapi.Group]("gateway.networking.k8s.io"),
Kind: ptr.To[gatewayapi.Kind]("Gateway"),
Namespace: ptr.To[gatewayapi.Namespace]("istio-system"),
Name: "istio-gateway",
}},
},
},
}

for _, hostname := range hostnames {
route.Spec.Hostnames = append(
route.Spec.Hostnames,
gatewayapi.Hostname(hostname),
)
}

if route.Status.Parents == nil {
route.Status.Parents = []gatewayapi.RouteParentStatus{{}}
}

route.Status.RouteStatus.Parents[0].Conditions = append(
route.Status.RouteStatus.Parents[0].Conditions,
r.StatusConditions...,
)

for _, rule := range r.Rules {
route.Spec.Rules = append(route.Spec.Rules, rule.Build())
}

return &route
}

type EndpointProbeRule struct {
Namespace string
Name string
Hash string
Path string
Port int
Headers []string
}

func (p EndpointProbeRule) Build() gatewayapi.HTTPRouteRule {
path := p.Path
if path == "" {
path = "/"
}
rule := gatewayapi.HTTPRouteRule{
Matches: []gatewayapi.HTTPRouteMatch{{
Path: &gatewayapi.HTTPPathMatch{
Type: ptr.To(gatewayapiv1.PathMatchPathPrefix),
Value: ptr.To(path),
},
Headers: []gatewayapi.HTTPHeaderMatch{{
Type: ptr.To(gatewayapiv1.HeaderMatchExact),
Name: header.HashKey,
Value: header.HashValueOverride,
}},
}},
Filters: []gatewayapi.HTTPRouteFilter{{
Type: gatewayapiv1.HTTPRouteFilterRequestHeaderModifier,
RequestHeaderModifier: &gatewayapi.HTTPHeaderFilter{
Set: []gatewayapi.HTTPHeader{{
Name: header.HashKey,
Value: p.Hash,
}},
},
}},
BackendRefs: []gatewayapi.HTTPBackendRef{{
Filters: []gatewayapiv1.HTTPRouteFilter{{
Type: gatewayapiv1.HTTPRouteFilterRequestHeaderModifier,
RequestHeaderModifier: &gatewayapi.HTTPHeaderFilter{
Set: []gatewayapi.HTTPHeader{{
Name: "K-Serving-Namespace",
Value: p.Namespace,
}, {
Name: "K-Serving-Revision",
Value: p.Name,
}},
},
}},
BackendRef: gatewayapi.BackendRef{
Weight: ptr.To[int32](100),
BackendObjectReference: gatewayapiv1.BackendObjectReference{
Group: ptr.To[gatewayapi.Group](""),
Kind: ptr.To[gatewayapi.Kind]("Service"),
Name: gatewayapi.ObjectName(p.Name),
Port: ptr.To[gatewayapi.PortNumber](gatewayapi.PortNumber(p.Port)),
},
},
}},
}

for i := 0; i < len(p.Headers); i += 2 {
k, v := p.Headers[i], p.Headers[i+1]
rule.BackendRefs[0].Filters[0].RequestHeaderModifier.Set = append(
rule.BackendRefs[0].Filters[0].RequestHeaderModifier.Set,
gatewayapi.HTTPHeader{Name: gatewayapiv1.HTTPHeaderName(k), Value: v},
)
}

return rule
}

type NormalRule struct {
Namespace string
Name string
Path string
Port int
Headers []string
Weight int
}

func (p NormalRule) Build() gatewayapi.HTTPRouteRule {
path := p.Path
if path == "" {
path = "/"
}
rule := gatewayapi.HTTPRouteRule{
Matches: []gatewayapi.HTTPRouteMatch{{
Path: &gatewayapi.HTTPPathMatch{
Type: ptr.To(gatewayapiv1.PathMatchPathPrefix),
Value: ptr.To(path),
},
}},
BackendRefs: []gatewayapi.HTTPBackendRef{{
Filters: []gatewayapiv1.HTTPRouteFilter{{
Type: gatewayapiv1.HTTPRouteFilterRequestHeaderModifier,
RequestHeaderModifier: &gatewayapi.HTTPHeaderFilter{
Set: []gatewayapi.HTTPHeader{{
Name: "K-Serving-Namespace",
Value: p.Namespace,
}, {
Name: "K-Serving-Revision",
Value: p.Name,
}},
},
}},
BackendRef: gatewayapi.BackendRef{
BackendObjectReference: gatewayapiv1.BackendObjectReference{
Group: ptr.To[gatewayapi.Group](""),
Kind: ptr.To[gatewayapi.Kind]("Service"),
Name: gatewayapi.ObjectName(p.Name),
Port: ptr.To[gatewayapi.PortNumber](gatewayapi.PortNumber(p.Port)),
},
Weight: ptr.To[int32](int32(p.Weight)),
},
},
},
}

for i := 0; i < len(p.Headers); i += 2 {
k, v := p.Headers[i], p.Headers[i+1]
rule.BackendRefs[0].Filters[0].RequestHeaderModifier.Set = append(
rule.BackendRefs[0].Filters[0].RequestHeaderModifier.Set,
gatewayapi.HTTPHeader{Name: gatewayapiv1.HTTPHeaderName(k), Value: v},
)
}

return rule
}
Loading

0 comments on commit e72ced6

Please sign in to comment.