AlinsRan commented on code in PR #2814:
URL:
https://github.com/apache/apisix-ingress-controller/pull/2814#discussion_r3681629544
##########
internal/adc/translator/httproute.go:
##########
@@ -676,7 +679,9 @@ func (t *Translator) TranslateHTTPRoute(tctx
*provider.TranslateContext, httpRou
enableWebsocket, _ := t.translateBackendsToUpstreams(tctx,
rule, httpRoute, service)
- t.fillPluginsFromHTTPRouteFilters(service.Plugins,
httpRoute.GetNamespace(), rule.Filters, rule.Matches, tctx)
+ if err := t.fillPluginsFromHTTPRouteFilters(service.Plugins,
httpRoute.GetNamespace(), rule.Filters, rule.Matches, tctx); err != nil {
Review Comment:
Two consequences of aborting here worth being explicit about:
- It drops **every rule** of the HTTPRoute, not just the rule whose filter
failed. Gateway API scopes filter/backendRef failures to the requests that
would have hit them, never to the whole route.
- The failure is invisible. `httproute_controller.go` writes the status
(`Accepted=True` / `ResolvedRefs=True`) *before* calling `Provider.Update`, and
an error from `Update` only requeues — so the route reports accepted while
nothing is programmed. There is no admission gate on this path either:
`httproute_webhook.go` only does `collectWarnings` (no `adcValidator`), and
v1alpha1 `PluginConfig` has no webhook at all. So unlike the apiv2 paths, the
user gets no signal from either side.
Before this change the state was wrong but observable-by-behavior; after it,
status and data plane disagree silently. If the fail-hard shape is kept for
now, the status update needs to move after `Provider.Update` (or the error be
folded into the condition).
##########
internal/adc/translator/httproute.go:
##########
@@ -59,38 +59,41 @@ func (t *Translator) fillPluginsFromHTTPRouteFilters(
case gatewayv1.HTTPRouteFilterResponseHeaderModifier:
t.fillPluginFromHTTPResponseHeaderFilter(plugins,
filter.ResponseHeaderModifier)
case gatewayv1.HTTPRouteFilterExtensionRef:
- t.fillPluginFromExtensionRef(plugins, namespace,
filter.ExtensionRef, tctx)
+ if err := t.fillPluginFromExtensionRef(plugins,
namespace, filter.ExtensionRef, tctx); err != nil {
+ return err
+ }
case gatewayv1.HTTPRouteFilterCORS:
t.fillPluginFromHTTPCORSFilter(plugins, filter.CORS)
}
}
+ return nil
}
-func (t *Translator) fillPluginFromExtensionRef(plugins adctypes.Plugins,
namespace string, extensionRef *gatewayv1.LocalObjectReference, tctx
*provider.TranslateContext) {
+func (t *Translator) fillPluginFromExtensionRef(plugins adctypes.Plugins,
namespace string, extensionRef *gatewayv1.LocalObjectReference, tctx
*provider.TranslateContext) error {
if extensionRef == nil {
- return
+ return nil
}
if extensionRef.Kind == internaltypes.KindPluginConfig {
pluginconfig := tctx.PluginConfigs[types.NamespacedName{
Namespace: namespace,
Name: string(extensionRef.Name),
}]
if pluginconfig == nil {
- return
+ return nil
Review Comment:
Note this is the branch the spec text literally governs — "a reference to a
custom filter type cannot be resolved" — and it still skips silently: the route
is programmed with no plugins at all. The PR hardens the case the spec says
nothing about (a resolvable extension object whose own config is malformed) and
leaves this one as-is. Whatever shape is chosen for the malformed-config case
should cover this branch too. Same for `loadPluginConfigPlugins` in
`apisixroute.go`, where a missing `ApisixPluginConfig` silently drops all
plugins of the rule.
##########
internal/adc/translator/apisixroute.go:
##########
@@ -117,33 +124,41 @@ func (t *Translator) loadPluginConfigPlugins(tctx
*provider.TranslateContext, ar
pcKey := types.NamespacedName{Namespace: pcNamespace, Name:
rule.PluginConfigName}
pc, ok := tctx.ApisixPluginConfigs[pcKey]
if !ok || pc == nil {
- return
+ return nil
}
for _, plugin := range pc.Spec.Plugins {
if !plugin.Enable {
continue
}
- config := t.buildPluginConfig(plugin, pc.Namespace,
tctx.Secrets)
+ config, err := t.buildPluginConfig(plugin, pc.Namespace,
tctx.Secrets)
+ if err != nil {
+ return err
+ }
plugins[plugin.Name] = config
}
+ return nil
}
-func (t *Translator) loadRoutePlugins(tctx *provider.TranslateContext, ar
*apiv2.ApisixRoute, routePlugins []apiv2.ApisixRoutePlugin, plugins
adc.Plugins) {
+func (t *Translator) loadRoutePlugins(tctx *provider.TranslateContext, ar
*apiv2.ApisixRoute, routePlugins []apiv2.ApisixRoutePlugin, plugins
adc.Plugins) error {
for _, plugin := range routePlugins {
if !plugin.Enable {
continue
}
- config := t.buildPluginConfig(plugin, ar.Namespace,
tctx.Secrets)
+ config, err := t.buildPluginConfig(plugin, ar.Namespace,
tctx.Secrets)
+ if err != nil {
+ return err
+ }
plugins[plugin.Name] = config
}
+ return nil
}
-func (t *Translator) buildPluginConfig(plugin apiv2.ApisixRoutePlugin,
namespace string, secrets map[types.NamespacedName]*corev1.Secret)
map[string]any {
+func (t *Translator) buildPluginConfig(plugin apiv2.ApisixRoutePlugin,
namespace string, secrets map[types.NamespacedName]*corev1.Secret)
(map[string]any, error) {
config := make(map[string]any)
if len(plugin.Config.Raw) > 0 {
if err := json.Unmarshal(plugin.Config.Raw, &config); err !=
nil {
- t.Log.Error(err, "failed to unmarshal plugin config")
+ return nil, fmt.Errorf("failed to unmarshal config of
plugin %s: %w", plugin.Name, err)
Review Comment:
Consider naming the object the config came from. When the plugin lives in a
referenced `ApisixPluginConfig`, this message surfaces on the route's status
and the user cannot tell which `ApisixPluginConfig` (of possibly several) is
broken. `buildPluginConfig` already receives the namespace; passing the source
name through would make the message actionable.
##########
internal/adc/translator/httproute.go:
##########
@@ -59,38 +59,41 @@ func (t *Translator) fillPluginsFromHTTPRouteFilters(
case gatewayv1.HTTPRouteFilterResponseHeaderModifier:
t.fillPluginFromHTTPResponseHeaderFilter(plugins,
filter.ResponseHeaderModifier)
case gatewayv1.HTTPRouteFilterExtensionRef:
- t.fillPluginFromExtensionRef(plugins, namespace,
filter.ExtensionRef, tctx)
+ if err := t.fillPluginFromExtensionRef(plugins,
namespace, filter.ExtensionRef, tctx); err != nil {
+ return err
+ }
case gatewayv1.HTTPRouteFilterCORS:
t.fillPluginFromHTTPCORSFilter(plugins, filter.CORS)
}
}
+ return nil
}
-func (t *Translator) fillPluginFromExtensionRef(plugins adctypes.Plugins,
namespace string, extensionRef *gatewayv1.LocalObjectReference, tctx
*provider.TranslateContext) {
+func (t *Translator) fillPluginFromExtensionRef(plugins adctypes.Plugins,
namespace string, extensionRef *gatewayv1.LocalObjectReference, tctx
*provider.TranslateContext) error {
if extensionRef == nil {
- return
+ return nil
}
if extensionRef.Kind == internaltypes.KindPluginConfig {
pluginconfig := tctx.PluginConfigs[types.NamespacedName{
Namespace: namespace,
Name: string(extensionRef.Name),
}]
if pluginconfig == nil {
- return
+ return nil
}
for _, plugin := range pluginconfig.Spec.Plugins {
pluginName := plugin.Name
- pluginconfig := make(map[string]any)
+ config := make(map[string]any)
if len(plugin.Config.Raw) > 0 {
- if err := json.Unmarshal(plugin.Config.Raw,
&pluginconfig); err != nil {
- t.Log.Error(err, "plugin config
unmarshal failed", "plugin", plugin.Name)
- continue
+ if err := json.Unmarshal(plugin.Config.Raw,
&config); err != nil {
+ return fmt.Errorf("failed to unmarshal
config of plugin %s: %w", plugin.Name, err)
Review Comment:
This diverges from Gateway API, and in a way the codebase already has an
answer for.
The spec's rule for a custom (`ExtensionRef`) filter that cannot be applied
is that the route keeps matching and the affected requests get an error
response — it must not resolve to "the filter is not there":
> If a reference to a custom filter type cannot be resolved, the filter MUST
NOT be skipped. Instead, requests that would have been processed by that filter
MUST receive a HTTP error response.
[gateway-api v1.6.0
`httproute_types.go#L847-L849`](https://github.com/kubernetes-sigs/gateway-api/blob/v1.6.0/apis/v1/httproute_types.go#L847-L849)
([rendered](https://gateway-api.sigs.k8s.io/reference/spec/#gateway.networking.k8s.io%2fv1.HTTPRouteFilter)).
Same shape for backendRefs, where the failure semantics are explicitly
per-request and local:
[`httproute_types.go#L271-L282`](https://github.com/kubernetes-sigs/gateway-api/blob/v1.6.0/apis/v1/httproute_types.go#L271-L282).
Returning an error here means the route is never programmed, which is not
equivalent: if another route also matches, the request is served **without**
the filter — the outcome the sentence above forbids — and if nothing else
matches it is a 404, indistinguishable from "no such route". It also widens a
per-filter failure into a whole-route one (see the call site), and one bad
`PluginConfig` takes down every route referencing it.
The conformant pattern is already implemented ~40 lines below, for
backendRefs: keep the route, attach a `fault-injection` 500 —
[`httproute.go#L663-L681`](https://github.com/apache/apisix-ingress-controller/blob/dad45c50c15152fdc71e875155cad1f039060cdb/internal/adc/translator/httproute.go#L663-L681),
whose error is deliberately discarded at the call site precisely because it
has been turned into a data-plane 500. Suggest reusing that here instead of
failing translation, plus `ResolvedRefs=False` on the route. Given that, it may
be cleanest to drop `httproute.go` / `grpcroute.go` from this PR and do them
separately — the apiv2 paths don't carry these constraints.
##########
internal/adc/translator/apisixconsumer.go:
##########
@@ -101,7 +101,10 @@ func (t *Translator) TranslateApisixConsumer(tctx
*provider.TranslateContext, ac
if !plugin.Enable {
continue
}
- config := t.buildPluginConfig(plugin, ac.Namespace,
tctx.Secrets)
+ config, err := t.buildPluginConfig(plugin, ac.Namespace,
tctx.Secrets)
Review Comment:
This makes apiv2 `ApisixConsumer` fail hard while its v1alpha1 counterpart
keeps the old behavior — `consumer.go` logs and `continue`s for both the
credential config and the consumer plugins, so a malformed credential is
dropped and the consumer is published without it.
Still on the log-and-skip path after this PR:
- `consumer.go` — v1alpha1 Consumer credential config, and Consumer plugins
- `gateway.go` — GatewayProxy `plugins` and `pluginMetadata` (gateway-wide,
the same blast radius the PR description cites for `ApisixGlobalRule`)
- `policies.go` — L4RoutePolicy plugins
The failure mode differs (they `continue`, so the plugin vanishes rather
than becoming `{}`), but the user-visible outcome is the same class: the plugin
does not run and everything reconciles green. Either sweep them in the same PR
or say in the description why they are out of scope — otherwise the tree ends
up with two deliberate, opposite conventions for the same situation.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]