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

xds: add HTTP filter is_optional support (gRFC A39) #4221

Merged
merged 3 commits into from
Mar 2, 2021
Merged
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
4 changes: 2 additions & 2 deletions examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad h1:EmNYJhPYy0pOFjCx2PrgtaBXmee0iUX9hLlxE1xHOJE=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.11

require (
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
github.com/golang/protobuf v1.4.2
github.com/google/go-cmp v0.5.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad h1:EmNYJhPYy0pOFjCx2PrgtaBXmee0iUX9hLlxE1xHOJE=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
Expand Down
2 changes: 1 addition & 1 deletion security/advancedtls/examples/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
2 changes: 1 addition & 1 deletion security/advancedtls/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
Expand Down
85 changes: 85 additions & 0 deletions xds/internal/client/lds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
v2httppb "github.com/envoyproxy/go-control-plane/envoy/config/filter/network/http_connection_manager/v2"
v2listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v2"
v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
anypb "github.com/golang/protobuf/ptypes/any"
Expand Down Expand Up @@ -92,6 +93,11 @@ func (s) TestUnmarshalListener_ClientSide(t *testing.T) {
Name: "customFilter",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: wrappedCustomFilterTypedStructConfig},
}
customOptionalFilter = &v3httppb.HttpFilter{
Name: "customFilter",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: customFilterConfig},
IsOptional: true,
}
customFilter2 = &v3httppb.HttpFilter{
Name: "customFilter2",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: customFilterConfig},
Expand All @@ -100,6 +106,11 @@ func (s) TestUnmarshalListener_ClientSide(t *testing.T) {
Name: "errFilter",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: errFilterConfig},
}
errOptionalFilter = &v3httppb.HttpFilter{
Name: "errFilter",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: errFilterConfig},
IsOptional: true,
}
clientOnlyCustomFilter = &v3httppb.HttpFilter{
Name: "clientOnlyCustomFilter",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: clientOnlyCustomFilterConfig},
Expand All @@ -108,6 +119,20 @@ func (s) TestUnmarshalListener_ClientSide(t *testing.T) {
Name: "serverOnlyCustomFilter",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: serverOnlyCustomFilterConfig},
}
serverOnlyOptionalCustomFilter = &v3httppb.HttpFilter{
Name: "serverOnlyOptionalCustomFilter",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: serverOnlyCustomFilterConfig},
IsOptional: true,
}
unknownFilter = &v3httppb.HttpFilter{
Name: "unknownFilter",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: unknownFilterConfig},
}
unknownOptionalFilter = &v3httppb.HttpFilter{
Name: "unknownFilter",
ConfigType: &v3httppb.HttpFilter_TypedConfig{TypedConfig: unknownFilterConfig},
IsOptional: true,
}
v3LisWithFilters = func(fs ...*v3httppb.HttpFilter) *anypb.Any {
hcm := &v3httppb.HttpConnectionManager{
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
Expand Down Expand Up @@ -342,6 +367,20 @@ func (s) TestUnmarshalListener_ClientSide(t *testing.T) {
},
},
},
{
name: "v3 with optional custom filter",
resources: []*anypb.Any{v3LisWithFilters(customOptionalFilter)},
wantUpdate: map[string]ListenerUpdate{
v3LDSTarget: {
RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
HTTPFilters: []HTTPFilter{{
Name: "customFilter",
Filter: httpFilter{},
Config: filterConfig{Cfg: customFilterConfig},
}},
},
},
},
{
name: "v3 with custom filter, fault injection disabled",
resources: []*anypb.Any{v3LisWithFilters(customFilter)},
Expand Down Expand Up @@ -378,6 +417,15 @@ func (s) TestUnmarshalListener_ClientSide(t *testing.T) {
resources: []*anypb.Any{v3LisWithFilters(serverOnlyCustomFilter)},
wantErr: true,
},
{
name: "v3 with optional server-only filter",
resources: []*anypb.Any{v3LisWithFilters(serverOnlyOptionalCustomFilter)},
wantUpdate: map[string]ListenerUpdate{
v3LDSTarget: {
RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second,
},
},
},
{
name: "v3 with client-only filter",
resources: []*anypb.Any{v3LisWithFilters(clientOnlyCustomFilter)},
Expand All @@ -397,6 +445,23 @@ func (s) TestUnmarshalListener_ClientSide(t *testing.T) {
resources: []*anypb.Any{v3LisWithFilters(errFilter)},
wantErr: true,
},
{
name: "v3 with optional err filter",
resources: []*anypb.Any{v3LisWithFilters(errOptionalFilter)},
wantErr: true,
},
{
name: "v3 with unknown filter",
resources: []*anypb.Any{v3LisWithFilters(unknownFilter)},
wantErr: true,
},
{
name: "v3 with unknown filter (optional)",
resources: []*anypb.Any{v3LisWithFilters(unknownOptionalFilter)},
wantUpdate: map[string]ListenerUpdate{
v3LDSTarget: {RouteConfigName: v3RouteConfigName, MaxStreamDuration: time.Second},
},
},
{
name: "v3 with error filter, fault injection disabled",
resources: []*anypb.Any{v3LisWithFilters(errFilter)},
Expand Down Expand Up @@ -1190,3 +1255,23 @@ func init() {
panic(err.Error())
}
}

var unknownFilterConfig = &anypb.Any{
TypeUrl: "unknown.custom.filter",
Value: []byte{1, 2, 3},
}

func wrappedOptionalFilter(name string) *anypb.Any {
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, this function is defined in lds_test, but only used in rds_test..

Copy link
Member Author

Choose a reason for hiding this comment

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

That's true but it seems to fit more in here with the other filter config stuff. I can move it if you feel strongly, LMK.

filter := &v3routepb.FilterConfig{
IsOptional: true,
Config: &anypb.Any{
TypeUrl: name,
Value: []byte{1, 2, 3},
},
}
w, err := ptypes.MarshalAny(filter)
if err != nil {
panic("error marshalling any: " + err.Error())
}
return w
}
40 changes: 40 additions & 0 deletions xds/internal/client/rds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -445,11 +445,31 @@ func (s) TestRDSGenerateRDSUpdateFromRouteConfiguration(t *testing.T) {
rc: goodRouteConfigWithFilterConfigs(map[string]*anypb.Any{"foo": wrappedCustomFilterTypedStructConfig}),
wantUpdate: goodUpdateWithFilterConfigs(map[string]httpfilter.FilterConfig{"foo": filterConfig{Override: customFilterTypedStructConfig}}),
},
{
name: "good-route-config-with-optional-http-filter-config",
rc: goodRouteConfigWithFilterConfigs(map[string]*anypb.Any{"foo": wrappedOptionalFilter("custom.filter")}),
wantUpdate: goodUpdateWithFilterConfigs(map[string]httpfilter.FilterConfig{"foo": filterConfig{Override: customFilterConfig}}),
},
{
name: "good-route-config-with-http-err-filter-config",
rc: goodRouteConfigWithFilterConfigs(map[string]*anypb.Any{"foo": errFilterConfig}),
wantError: true,
},
{
name: "good-route-config-with-http-optional-err-filter-config",
rc: goodRouteConfigWithFilterConfigs(map[string]*anypb.Any{"foo": wrappedOptionalFilter("err.custom.filter")}),
wantError: true,
},
{
name: "good-route-config-with-http-unknown-filter-config",
rc: goodRouteConfigWithFilterConfigs(map[string]*anypb.Any{"foo": unknownFilterConfig}),
wantError: true,
},
{
name: "good-route-config-with-http-optional-unknown-filter-config",
rc: goodRouteConfigWithFilterConfigs(map[string]*anypb.Any{"foo": wrappedOptionalFilter("unknown.custom.filter")}),
wantUpdate: goodUpdateWithFilterConfigs(nil),
},
{
name: "good-route-config-with-http-err-filter-config-fi-disabled",
disableFI: true,
Expand Down Expand Up @@ -897,6 +917,11 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
routes: goodRouteWithFilterConfigs(map[string]*anypb.Any{"foo": wrappedCustomFilterTypedStructConfig}),
wantRoutes: goodUpdateWithFilterConfigs(map[string]httpfilter.FilterConfig{"foo": filterConfig{Override: customFilterTypedStructConfig}}),
},
{
name: "with optional custom HTTP filter config",
routes: goodRouteWithFilterConfigs(map[string]*anypb.Any{"foo": wrappedOptionalFilter("custom.filter")}),
wantRoutes: goodUpdateWithFilterConfigs(map[string]httpfilter.FilterConfig{"foo": filterConfig{Override: customFilterConfig}}),
},
{
name: "with custom HTTP filter config, FI disabled",
disableFI: true,
Expand All @@ -908,12 +933,27 @@ func (s) TestRoutesProtoToSlice(t *testing.T) {
routes: goodRouteWithFilterConfigs(map[string]*anypb.Any{"foo": errFilterConfig}),
wantErr: true,
},
{
name: "with optional erroring custom HTTP filter config",
routes: goodRouteWithFilterConfigs(map[string]*anypb.Any{"foo": wrappedOptionalFilter("err.custom.filter")}),
wantErr: true,
},
{
name: "with erroring custom HTTP filter config, FI disabled",
disableFI: true,
routes: goodRouteWithFilterConfigs(map[string]*anypb.Any{"foo": errFilterConfig}),
wantRoutes: goodUpdateWithFilterConfigs(nil),
},
{
name: "with unknown custom HTTP filter config",
routes: goodRouteWithFilterConfigs(map[string]*anypb.Any{"foo": unknownFilterConfig}),
wantErr: true,
},
{
name: "with optional unknown custom HTTP filter config",
routes: goodRouteWithFilterConfigs(map[string]*anypb.Any{"foo": wrappedOptionalFilter("unknown.custom.filter")}),
wantRoutes: goodUpdateWithFilterConfigs(nil),
},
}

cmpOpts := []cmp.Option{
Expand Down
47 changes: 36 additions & 11 deletions xds/internal/client/xds.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,24 +133,27 @@ func processClientSideListener(lis *v3listenerpb.Listener, v2 bool) (*ListenerUp
}

func unwrapHTTPFilterConfig(config *anypb.Any) (proto.Message, string, error) {
if typeURL := config.GetTypeUrl(); typeURL != "type.googleapis.com/udpa.type.v1.TypedStruct" {
return config, typeURL, nil
}
// The real type name is inside the TypedStruct.
s := new(v1typepb.TypedStruct)
if !ptypes.Is(config, s) {
return config, config.GetTypeUrl(), nil
}
if err := ptypes.UnmarshalAny(config, s); err != nil {
return nil, "", fmt.Errorf("error unmarshalling TypedStruct filter config: %v", err)
}
return s, s.GetTypeUrl(), nil
}

func validateHTTPFilterConfig(cfg *anypb.Any, lds bool) (httpfilter.Filter, httpfilter.FilterConfig, error) {
func validateHTTPFilterConfig(cfg *anypb.Any, lds, optional bool) (httpfilter.Filter, httpfilter.FilterConfig, error) {
config, typeURL, err := unwrapHTTPFilterConfig(cfg)
if err != nil {
return nil, nil, err
}
filterBuilder := httpfilter.Get(typeURL)
if filterBuilder == nil {
if optional {
return nil, nil, nil
}
return nil, nil, fmt.Errorf("no filter implementation found for %q", typeURL)
}
parseFunc := filterBuilder.ParseFilterConfig
Expand All @@ -170,9 +173,23 @@ func processHTTPFilterOverrides(cfgs map[string]*anypb.Any) (map[string]httpfilt
}
m := make(map[string]httpfilter.FilterConfig)
for name, cfg := range cfgs {
_, config, err := validateHTTPFilterConfig(cfg, false)
optional := false
s := new(v3routepb.FilterConfig)
if ptypes.Is(cfg, s) {
if err := ptypes.UnmarshalAny(cfg, s); err != nil {
return nil, fmt.Errorf("filter override %q: error unmarshalling FilterConfig: %v", name, err)
}
cfg = s.GetConfig()
optional = s.GetIsOptional()
}

httpFilter, config, err := validateHTTPFilterConfig(cfg, false, optional)
if err != nil {
return nil, err
return nil, fmt.Errorf("filter override %q: %v", name, err)
}
if httpFilter == nil {
// Optional configs are ignored.
continue
}
m[name] = config
}
Expand All @@ -196,18 +213,26 @@ func processHTTPFilters(filters []*v3httppb.HttpFilter, server bool) ([]HTTPFilt
}
seenNames[name] = true

httpFilter, config, err := validateHTTPFilterConfig(filter.GetTypedConfig(), true)
httpFilter, config, err := validateHTTPFilterConfig(filter.GetTypedConfig(), true, filter.GetIsOptional())
if err != nil {
return nil, err
}
if httpFilter == nil {
// Optional configs are ignored.
continue
}
if server {
if _, ok := httpFilter.(httpfilter.ServerInterceptorBuilder); !ok {
return nil, fmt.Errorf("httpFilter %q not supported server-side", name)
if filter.GetIsOptional() {
continue
}
return nil, fmt.Errorf("HTTP filter %q not supported server-side", name)
}
} else {
if _, ok := httpFilter.(httpfilter.ClientInterceptorBuilder); !ok {
return nil, fmt.Errorf("httpFilter %q not supported client-side", name)
} else if _, ok := httpFilter.(httpfilter.ClientInterceptorBuilder); !ok {
if filter.GetIsOptional() {
continue
}
return nil, fmt.Errorf("HTTP filter %q not supported client-side", name)
}

// Save name/config
Expand Down