diff --git a/api/Makefile b/api/Makefile index b2863752..e5b8a443 100644 --- a/api/Makefile +++ b/api/Makefile @@ -16,7 +16,7 @@ include ../common.mk .PHONY: unit-test unit-test: - go test ${TEST_OPTION} $(shell go list ./... | \ + go test -tags ${ENVOY_API_VERSION} ${TEST_OPTION} $(shell go list ./... | \ grep -v tests/integration) # We can't specify -race to `go build` because it seems that @@ -24,7 +24,7 @@ unit-test: # the race detector will allocate memory out of 48bits address which is not allowed in x64. .PHONY: build-test-so-local build-test-so-local: - CGO_ENABLED=1 go build -tags so \ + CGO_ENABLED=1 go build -tags so,${ENVOY_API_VERSION} \ -ldflags "-B 0x$(shell head -c20 /dev/urandom|od -An -tx1|tr -d ' \n') " \ --buildmode=c-shared \ -cover -covermode=atomic -coverpkg=${PROJECT_NAME}/... \ @@ -47,10 +47,10 @@ build-test-so: integration-test: test -d /tmp/htnn_coverage && rm -rf /tmp/htnn_coverage || true $(foreach PKG, $(shell go list ./tests/integration/...), \ - go test -v ${PKG} || exit 1; \ + go test -tags ${ENVOY_API_VERSION} -v ${PKG} || exit 1; \ ) # The benchmark running time can be controlled via env var HTNN_DATA_PLANE_BENCHMARK_DURATION .PHONY: benchmark benchmark: - go test -tags benchmark -v ./tests/integration/ -run TestBenchmark + go test -tags benchmark,${ENVOY_API_VERSION} -v ./tests/integration/ -run TestBenchmark diff --git a/api/go.mod b/api/go.mod index b7d2089a..242f20f6 100644 --- a/api/go.mod +++ b/api/go.mod @@ -21,7 +21,7 @@ go 1.21 require ( github.com/agiledragon/gomonkey/v2 v2.11.0 github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa - github.com/envoyproxy/envoy v1.29.4 + github.com/envoyproxy/envoy v1.31.0 github.com/envoyproxy/go-control-plane v0.12.1-0.20240117015050-472addddff92 // version used by istio 1.21 github.com/envoyproxy/protoc-gen-validate v1.0.4 github.com/go-logr/logr v1.4.1 @@ -31,7 +31,7 @@ require ( go.uber.org/zap v1.27.0 golang.org/x/net v0.24.0 google.golang.org/grpc v1.63.2 - google.golang.org/protobuf v1.33.0 + google.golang.org/protobuf v1.34.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/api/go.sum b/api/go.sum index caad57b4..778f3637 100644 --- a/api/go.sum +++ b/api/go.sum @@ -6,8 +6,8 @@ github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/P github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/envoyproxy/envoy v1.29.4 h1:1c52LYxzA6arnSjpTfDyxCSrtztwVAnzekcGHirvq8Y= -github.com/envoyproxy/envoy v1.29.4/go.mod h1:c2OGLXJVY9BaTYPiWFRz6eUNaC+3TfqFKmkWS9brdKo= +github.com/envoyproxy/envoy v1.31.0 h1:NsTo+medzu0bMffXAjl+zKaViLOShKuIZWQnKKYq0/4= +github.com/envoyproxy/envoy v1.31.0/go.mod h1:ujBFxE543X8OePZG+FbeR9LnpBxTLu64IAU7A20EB9A= github.com/envoyproxy/go-control-plane v0.12.1-0.20240117015050-472addddff92 h1:/3bsjkhOTh0swUKDBxL1+3MrXCxrf/sEEMseiIEJg00= github.com/envoyproxy/go-control-plane v0.12.1-0.20240117015050-472addddff92/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= @@ -59,8 +59,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1: google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/api/pkg/consumer/consumer.go b/api/pkg/consumer/consumer.go index 980e8ea1..5bb5688b 100644 --- a/api/pkg/consumer/consumer.go +++ b/api/pkg/consumer/consumer.go @@ -36,16 +36,14 @@ type consumerManager struct { conf *consumerManagerConfig } -func ConsumerManagerFactory(c interface{}) capi.StreamFilterFactory { +func ConsumerManagerFactory(c interface{}, callbacks capi.FilterCallbackHandler) capi.StreamFilter { conf, ok := c.(*consumerManagerConfig) if !ok { panic(fmt.Sprintf("wrong config type: %s", reflect.TypeOf(c))) } - return func(callbacks capi.FilterCallbackHandler) capi.StreamFilter { - return &consumerManager{ - callbacks: callbacks, - conf: conf, - } + return &consumerManager{ + callbacks: callbacks, + conf: conf, } } diff --git a/api/pkg/consumer/consumer_test.go b/api/pkg/consumer/consumer_test.go index aef97156..d9141cf1 100644 --- a/api/pkg/consumer/consumer_test.go +++ b/api/pkg/consumer/consumer_test.go @@ -76,6 +76,6 @@ func TestParse(t *testing.T) { func TestConsumerManagerFactory(t *testing.T) { assert.PanicsWithValuef(t, "wrong config type: *struct {}", func() { - ConsumerManagerFactory(&struct{}{}) + ConsumerManagerFactory(&struct{}{}, nil) }, "check if the panic message contains the wrong type") } diff --git a/api/pkg/dynamicconfig/dynamicconfig.go b/api/pkg/dynamicconfig/dynamicconfig.go index 6417b66c..904eaa6e 100644 --- a/api/pkg/dynamicconfig/dynamicconfig.go +++ b/api/pkg/dynamicconfig/dynamicconfig.go @@ -41,11 +41,9 @@ type dynamicConfigFilter struct { callbacks capi.FilterCallbackHandler } -func DynamicConfigFactory(c interface{}) capi.StreamFilterFactory { - return func(callbacks capi.FilterCallbackHandler) capi.StreamFilter { - return &dynamicConfigFilter{ - callbacks: callbacks, - } +func DynamicConfigFactory(_ interface{}, callbacks capi.FilterCallbackHandler) capi.StreamFilter { + return &dynamicConfigFilter{ + callbacks: callbacks, } } diff --git a/api/pkg/filtermanager/api/api.go b/api/pkg/filtermanager/api/api.go index 2d5b6348..ccfad7fb 100644 --- a/api/pkg/filtermanager/api/api.go +++ b/api/pkg/filtermanager/api/api.go @@ -17,7 +17,6 @@ package api import ( "net/http" "net/url" - "sync/atomic" "github.com/envoyproxy/envoy/contrib/golang/common/go/api" "google.golang.org/protobuf/reflect/protoreflect" @@ -172,6 +171,8 @@ type StreamInfo interface { FilterState() FilterState // VirtualClusterName returns the name of the virtual cluster which got matched VirtualClusterName() (string, bool) + // WorkerID returns the ID of the Envoy worker thread + WorkerID() uint32 // Methods added by HTNN @@ -194,13 +195,10 @@ type Consumer interface { PluginConfig(name string) PluginConsumerConfig } -// FilterCallbackHandler provides API that is used during request processing -type FilterCallbackHandler interface { +// StreamFilterCallbacks provides API that is used during request processing +type StreamFilterCallbacks interface { // StreamInfo provides API to get/set current stream's context. StreamInfo() StreamInfo - // RecoverPanic covers panic to 500 response to avoid crashing Envoy. If you create goroutine - // in your Filter, please add `defer RecoverPanic()` to avoid crash by panic. - RecoverPanic() // GetProperty fetch Envoy attribute and return the value as a string. // The list of attributes can be found in https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes. // If the fetch succeeded, a string will be returned. @@ -212,6 +210,9 @@ type FilterCallbackHandler interface { // * ErrSerializationFailure (Currently, fetching attributes in List/Map type are unsupported) // * ErrValueNotFound GetProperty(key string) (string, error) + // ClearRouteCache clears the route cache for the current request, and filtermanager will re-fetch the route in the next filter. + // Please be careful to invoke it, since filtermanager will raise an 404 route_not_found response when failed to re-fetch a route. + ClearRouteCache() // Methods added by HTNN @@ -229,7 +230,7 @@ type FilterCallbackHandler interface { // WithLogArg injectes `key: value` as the suffix of application log created by this // callback's Log* methods. The injected log arguments are only valid in the current request. // This method can be used to inject IDs or other context information into the logs. - WithLogArg(key string, value any) FilterCallbackHandler + WithLogArg(key string, value any) StreamFilterCallbacks LogTracef(format string, v ...any) LogTrace(message string) LogDebugf(format string, v ...any) @@ -242,6 +243,31 @@ type FilterCallbackHandler interface { LogError(message string) } +// FilterProcessCallbacks is the interface for filter to process request/response in decode/encode phase. +type FilterProcessCallbacks interface { + SendLocalReply(responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string) + // RecoverPanic recover panic in defer and terminate the request by SendLocalReply with 500 status code. + RecoverPanic() + + // hide Continue() method from the user +} + +type DecoderFilterCallbacks interface { + FilterProcessCallbacks +} + +type EncoderFilterCallbacks interface { + FilterProcessCallbacks +} + +type FilterCallbackHandler interface { + StreamFilterCallbacks + // DecoderFilterCallbacks could only be used in DecodeXXX phases. + DecoderFilterCallbacks() DecoderFilterCallbacks + // EncoderFilterCallbacks could only be used in EncodeXXX phases. + EncoderFilterCallbacks() EncoderFilterCallbacks +} + // FilterFactory returns a per-request Filter which has configuration bound to it. // This function should be a pure builder and should not have any side effect. type FilterFactory func(config interface{}, callbacks FilterCallbackHandler) Filter @@ -282,102 +308,6 @@ var ( LogLevelCritical = api.Critical ) -// Drop our log optimization once https://github.com/envoyproxy/envoy/commit/591fb13817ddf1f54945186e3c6de4e0345508d2 -// is used. - -var ( - currLogLevel atomic.Int32 -) - -func GetLogLevel() LogType { - lv := currLogLevel.Load() - return LogType(lv) -} - -func LogTrace(message string) { - if GetLogLevel() > LogLevelTrace { - return - } - api.LogTrace(message) -} - -func LogDebug(message string) { - if GetLogLevel() > LogLevelDebug { - return - } - api.LogDebug(message) -} - -func LogInfo(message string) { - if GetLogLevel() > LogLevelInfo { - return - } - api.LogInfo(message) -} - -func LogWarn(message string) { - if GetLogLevel() > LogLevelWarn { - return - } - api.LogWarn(message) -} - -func LogError(message string) { - if GetLogLevel() > LogLevelError { - return - } - api.LogError(message) -} - -func LogCritical(message string) { - if GetLogLevel() > LogLevelCritical { - return - } - api.LogCritical(message) -} - -func LogTracef(format string, v ...any) { - if GetLogLevel() > LogLevelTrace { - return - } - api.LogTracef(format, v...) -} - -func LogDebugf(format string, v ...any) { - if GetLogLevel() > LogLevelDebug { - return - } - api.LogDebugf(format, v...) -} - -func LogInfof(format string, v ...any) { - if GetLogLevel() > LogLevelInfo { - return - } - api.LogInfof(format, v...) -} - -func LogWarnf(format string, v ...any) { - if GetLogLevel() > LogLevelWarn { - return - } - api.LogWarnf(format, v...) -} - -func LogErrorf(format string, v ...any) { - if GetLogLevel() > LogLevelError { - return - } - api.LogErrorf(format, v...) -} - -func LogCriticalf(format string, v ...any) { - if GetLogLevel() > LogLevelCritical { - return - } - api.LogCriticalf(format, v...) -} - var ( ErrInternalFailure = api.ErrInternalFailure ErrSerializationFailure = api.ErrSerializationFailure diff --git a/api/pkg/filtermanager/api/api_129.go b/api/pkg/filtermanager/api/api_129.go new file mode 100644 index 00000000..ddc9d57b --- /dev/null +++ b/api/pkg/filtermanager/api/api_129.go @@ -0,0 +1,116 @@ +// Copyright The HTNN 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. + +//go:build 1.29 + +package api + +import ( + "sync/atomic" + + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +var ( + currLogLevel atomic.Int32 +) + +func GetLogLevel() LogType { + lv := currLogLevel.Load() + return LogType(lv) +} + +func LogTrace(message string) { + if GetLogLevel() > LogLevelTrace { + return + } + api.LogTrace(message) +} + +func LogDebug(message string) { + if GetLogLevel() > LogLevelDebug { + return + } + api.LogDebug(message) +} + +func LogInfo(message string) { + if GetLogLevel() > LogLevelInfo { + return + } + api.LogInfo(message) +} + +func LogWarn(message string) { + if GetLogLevel() > LogLevelWarn { + return + } + api.LogWarn(message) +} + +func LogError(message string) { + if GetLogLevel() > LogLevelError { + return + } + api.LogError(message) +} + +func LogCritical(message string) { + if GetLogLevel() > LogLevelCritical { + return + } + api.LogCritical(message) +} + +func LogTracef(format string, v ...any) { + if GetLogLevel() > LogLevelTrace { + return + } + api.LogTracef(format, v...) +} + +func LogDebugf(format string, v ...any) { + if GetLogLevel() > LogLevelDebug { + return + } + api.LogDebugf(format, v...) +} + +func LogInfof(format string, v ...any) { + if GetLogLevel() > LogLevelInfo { + return + } + api.LogInfof(format, v...) +} + +func LogWarnf(format string, v ...any) { + if GetLogLevel() > LogLevelWarn { + return + } + api.LogWarnf(format, v...) +} + +func LogErrorf(format string, v ...any) { + if GetLogLevel() > LogLevelError { + return + } + api.LogErrorf(format, v...) +} + +func LogCriticalf(format string, v ...any) { + if GetLogLevel() > LogLevelCritical { + return + } + api.LogCriticalf(format, v...) +} diff --git a/api/pkg/filtermanager/api/no_so.go b/api/pkg/filtermanager/api/api_129_no_so.go similarity index 97% rename from api/pkg/filtermanager/api/no_so.go rename to api/pkg/filtermanager/api/api_129_no_so.go index f951975e..ce72dfb7 100644 --- a/api/pkg/filtermanager/api/no_so.go +++ b/api/pkg/filtermanager/api/api_129_no_so.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build !so +//go:build 1.29 && !so package api diff --git a/api/pkg/filtermanager/api/so.go b/api/pkg/filtermanager/api/api_129_so.go similarity index 98% rename from api/pkg/filtermanager/api/so.go rename to api/pkg/filtermanager/api/api_129_so.go index 2facd5ed..50ccaffc 100644 --- a/api/pkg/filtermanager/api/so.go +++ b/api/pkg/filtermanager/api/api_129_so.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build so +//go:build 1.29 && so package api diff --git a/api/pkg/filtermanager/api/api_latest.go b/api/pkg/filtermanager/api/api_latest.go new file mode 100644 index 00000000..1de2547e --- /dev/null +++ b/api/pkg/filtermanager/api/api_latest.go @@ -0,0 +1,38 @@ +// Copyright The HTNN 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. + +//go:build !1.29 + +package api + +import ( + "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +var ( + LogTrace = api.LogTrace + LogDebug = api.LogDebug + LogInfo = api.LogInfo + LogWarn = api.LogWarn + LogError = api.LogError + LogCritical = api.LogCritical + LogTracef = api.LogTracef + LogDebugf = api.LogDebugf + LogInfof = api.LogInfof + LogWarnf = api.LogWarnf + LogErrorf = api.LogErrorf + LogCriticalf = api.LogCriticalf + + GetLogLevel = api.GetLogLevel +) diff --git a/api/pkg/filtermanager/api_impl.go b/api/pkg/filtermanager/api_impl.go index 574f9a6c..24078e62 100644 --- a/api/pkg/filtermanager/api_impl.go +++ b/api/pkg/filtermanager/api_impl.go @@ -207,7 +207,7 @@ func (cb *filterManagerCallbackHandler) PluginState() api.PluginState { return cb.pluginState } -func (cb *filterManagerCallbackHandler) WithLogArg(key string, value any) api.FilterCallbackHandler { +func (cb *filterManagerCallbackHandler) WithLogArg(key string, value any) api.StreamFilterCallbacks { // As the log is embedded into the Envoy's log, it's not so necessary to use structural logging // here. So far the value is just an ID string, introduce complex processions like quoting is // overkill. diff --git a/api/pkg/filtermanager/api_impl_129.go b/api/pkg/filtermanager/api_impl_129.go new file mode 100644 index 00000000..bedec0da --- /dev/null +++ b/api/pkg/filtermanager/api_impl_129.go @@ -0,0 +1,42 @@ +// Copyright The HTNN 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. + +//go:build 1.29 + +package filtermanager + +import ( + capi "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "mosn.io/htnn/api/pkg/filtermanager/api" +) + +func (s *filterManagerStreamInfo) WorkerID() uint32 { + panic("not implemented") +} + +func (cb *filterManagerCallbackHandler) ClearRouteCache() { + panic("not implemented") +} + +func (cb *filterManagerCallbackHandler) DecoderFilterCallbacks() api.DecoderFilterCallbacks { + return cb.FilterCallbackHandler +} + +func (cb *filterManagerCallbackHandler) EncoderFilterCallbacks() api.EncoderFilterCallbacks { + return cb.FilterCallbackHandler +} + +func (cb *filterManagerCallbackHandler) Continue(st capi.StatusType, _ bool) { + cb.FilterCallbackHandler.Continue(st) +} diff --git a/api/pkg/filtermanager/api_impl_latest.go b/api/pkg/filtermanager/api_impl_latest.go new file mode 100644 index 00000000..e65ad200 --- /dev/null +++ b/api/pkg/filtermanager/api_impl_latest.go @@ -0,0 +1,38 @@ +// Copyright The HTNN 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. + +//go:build !1.29 + +package filtermanager + +import ( + capi "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "mosn.io/htnn/api/pkg/filtermanager/api" +) + +func (cb *filterManagerCallbackHandler) DecoderFilterCallbacks() api.DecoderFilterCallbacks { + return cb.FilterCallbackHandler.DecoderFilterCallbacks() +} + +func (cb *filterManagerCallbackHandler) EncoderFilterCallbacks() api.EncoderFilterCallbacks { + return cb.FilterCallbackHandler.EncoderFilterCallbacks() +} + +func (cb *filterManagerCallbackHandler) Continue(st capi.StatusType, decoding bool) { + if decoding { + cb.FilterCallbackHandler.DecoderFilterCallbacks().Continue(st) + } else { + cb.FilterCallbackHandler.EncoderFilterCallbacks().Continue(st) + } +} diff --git a/api/pkg/filtermanager/api_impl_test.go b/api/pkg/filtermanager/api_impl_test.go index c9398deb..3f1cf2d6 100644 --- a/api/pkg/filtermanager/api_impl_test.go +++ b/api/pkg/filtermanager/api_impl_test.go @@ -78,7 +78,7 @@ func TestPluginState(t *testing.T) { Factory: getPluginStateFilterFactory, }, } - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) h := http.Header{} hdr := envoy.NewRequestHeaderMap(h) m.DecodeHeaders(hdr, true) @@ -143,7 +143,7 @@ func TestAccessCacheFieldsConcurrently(t *testing.T) { for i := 0; i < n; i++ { go func(i int) { cb := envoy.NewCAPIFilterCallbackHandler() - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) h := http.Header{} hdr := envoy.NewRequestHeaderMap(h) m.DecodeHeaders(hdr, true) @@ -223,7 +223,7 @@ func TestLogWithArgs(t *testing.T) { Factory: testLogFilterFactory, }, } - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) h := http.Header{} hdr := envoy.NewRequestHeaderMap(h) m.DecodeHeaders(hdr, true) diff --git a/api/pkg/filtermanager/filtermanager.go b/api/pkg/filtermanager/filtermanager.go index 593aff3b..66107c4a 100644 --- a/api/pkg/filtermanager/filtermanager.go +++ b/api/pkg/filtermanager/filtermanager.go @@ -37,7 +37,8 @@ type filterManager struct { decodeRequestNeeded bool decodeIdx int - reqHdr api.RequestHeaderMap + reqHdr api.RequestHeaderMap // don't access it in Encode phases + contentType string encodeResponseNeeded bool encodeIdx int @@ -66,6 +67,7 @@ func (m *filterManager) Reset() { m.decodeRequestNeeded = false m.decodeIdx = -1 m.reqHdr = nil + m.contentType = "" m.encodeResponseNeeded = false m.encodeIdx = -1 @@ -127,7 +129,17 @@ func needLogExecution() bool { return api.GetLogLevel() <= api.LogLevelDebug } -func FilterManagerFactory(c interface{}) capi.StreamFilterFactory { +func FilterManagerFactory(c interface{}, cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + // the RecoverPanic requires the underline Go req to be created. However, the Go req is created + // after the FilterManagerFactory is called. So we implement our own RecoverPanic here to avoid breaking + // the creation of Go req. + defer func() { + if p := recover(); p != nil { + api.LogErrorf("panic: %v\n%s", p, debug.Stack()) + streamFilter = InternalErrorFactoryForCAPI(c, cb) + } + }() + conf, ok := c.(*filterManagerConfig) if !ok { panic(fmt.Sprintf("wrong config type: %s", reflect.TypeOf(c))) @@ -135,96 +147,86 @@ func FilterManagerFactory(c interface{}) capi.StreamFilterFactory { parsedConfig := conf.parsed - return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { - // TODO: remove this protection once we upgrade to the new Envoy version - defer func() { - if p := recover(); p != nil { - api.LogErrorf("panic: %v\n%s", p, debug.Stack()) - streamFilter = InternalErrorFactoryForCAPI(c, cb) - } - }() - - data := conf.pool.Get() - fm, ok := data.(*filterManager) - if !ok { - panic(fmt.Sprintf("unexpected type: %s", reflect.TypeOf(data))) - } + data := conf.pool.Get() + fm, ok := data.(*filterManager) + if !ok { + panic(fmt.Sprintf("unexpected type: %s", reflect.TypeOf(data))) + } - fm.callbacks.FilterCallbackHandler = cb + fm.callbacks.FilterCallbackHandler = cb - canSkipMethod := fm.canSkipMethod - if canSkipMethod == nil { - canSkipMethod = newSkipMethodsMap() - } + canSkipMethod := fm.canSkipMethod + if canSkipMethod == nil { + canSkipMethod = newSkipMethodsMap() + } - filters := make([]*model.FilterWrapper, len(parsedConfig)) - logExecution := needLogExecution() - for i, fc := range parsedConfig { - factory := fc.Factory - config := fc.ParsedConfig - f := factory(config, fm.callbacks) - // Technically, the factory might create different f for different calls. We don't support this edge case for now. - if fm.canSkipMethod == nil { - definedMethod := make(map[string]bool, len(canSkipMethod)) - for meth := range canSkipMethod { - definedMethod[meth] = false - } - for meth := range canSkipMethod { - overridden, err := reflectx.IsMethodOverridden(f, meth) - if err != nil { - api.LogErrorf("failed to check method %s in plugin %s: %v", meth, fc.Name, err) - // canSkipMethod[meth] will be false - } - canSkipMethod[meth] = canSkipMethod[meth] && !overridden - definedMethod[meth] = overridden + filters := make([]*model.FilterWrapper, len(parsedConfig)) + logExecution := needLogExecution() + for i, fc := range parsedConfig { + factory := fc.Factory + config := fc.ParsedConfig + f := factory(config, fm.callbacks) + // Technically, the factory might create different f for different calls. We don't support this edge case for now. + if fm.canSkipMethod == nil { + definedMethod := make(map[string]bool, len(canSkipMethod)) + for meth := range canSkipMethod { + definedMethod[meth] = false + } + for meth := range canSkipMethod { + overridden, err := reflectx.IsMethodOverridden(f, meth) + if err != nil { + api.LogErrorf("failed to check method %s in plugin %s: %v", meth, fc.Name, err) + // canSkipMethod[meth] will be false } + canSkipMethod[meth] = canSkipMethod[meth] && !overridden + definedMethod[meth] = overridden + } - if definedMethod["DecodeRequest"] { - if !definedMethod["DecodeHeaders"] { - api.LogErrorf("plugin %s has DecodeRequest but not DecodeHeaders. To run DecodeRequest, we need to return api.WaitAllData from DecodeHeaders", fc.Name) - } + if definedMethod["DecodeRequest"] { + if !definedMethod["DecodeHeaders"] { + api.LogErrorf("plugin %s has DecodeRequest but not DecodeHeaders. To run DecodeRequest, we need to return api.WaitAllData from DecodeHeaders", fc.Name) + } - p := pkgPlugins.LoadPluginType(fc.Name) - if p != nil { - order := p.Order() - if order.Position <= pkgPlugins.OrderPositionAuthn { - api.LogErrorf("plugin %s has DecodeRequest which is not supported because the order of plugin", fc.Name) - } + p := pkgPlugins.LoadPluginType(fc.Name) + if p != nil { + order := p.Order() + if order.Position <= pkgPlugins.OrderPositionAuthn { + api.LogErrorf("plugin %s has DecodeRequest which is not supported because the order of plugin", fc.Name) } } - if definedMethod["EncodeResponse"] && !definedMethod["EncodeHeaders"] { - api.LogErrorf("plugin %s has EncodeResponse but not EncodeHeaders. To run EncodeResponse, we need to return api.WaitAllData from EncodeHeaders", fc.Name) - } } - - if logExecution { - filters[i] = model.NewFilterWrapper(fc.Name, NewLogExecutionFilter(fc.Name, f, fm.callbacks)) - } else { - filters[i] = model.NewFilterWrapper(fc.Name, f) + if definedMethod["EncodeResponse"] && !definedMethod["EncodeHeaders"] { + api.LogErrorf("plugin %s has EncodeResponse but not EncodeHeaders. To run EncodeResponse, we need to return api.WaitAllData from EncodeHeaders", fc.Name) } + } - if fm.DebugModeEnabled() { - filters[i] = model.NewFilterWrapper(fc.Name, NewDebugFilter(fc.Name, filters[i].Filter, fm.callbacks)) - } + if logExecution { + filters[i] = model.NewFilterWrapper(fc.Name, NewLogExecutionFilter(fc.Name, f, fm.callbacks)) + } else { + filters[i] = model.NewFilterWrapper(fc.Name, f) } - if fm.canSkipMethod == nil { - fm.canSkipMethod = canSkipMethod + if fm.DebugModeEnabled() { + filters[i] = model.NewFilterWrapper(fc.Name, NewDebugFilter(fc.Name, filters[i].Filter, fm.callbacks)) } + } - // We can't cache the slice of filters as it may be changed by consumer - fm.filters = filters + if fm.canSkipMethod == nil { + fm.canSkipMethod = canSkipMethod + } - // The skip check is based on the compiled code. So if the DecodeRequest is defined, - // even it is not called, DecodeData will not be skipped. Same as EncodeResponse. - fm.canSkipDecodeHeaders = fm.canSkipMethod["DecodeHeaders"] && fm.canSkipMethod["DecodeRequest"] && fm.config.initOnce == nil - fm.canSkipDecodeData = fm.canSkipMethod["DecodeData"] && fm.canSkipMethod["DecodeRequest"] - fm.canSkipEncodeHeaders = fm.canSkipMethod["EncodeHeaders"] - fm.canSkipEncodeData = fm.canSkipMethod["EncodeData"] && fm.canSkipMethod["EncodeResponse"] - fm.canSkipOnLog = fm.canSkipMethod["OnLog"] + // We can't cache the slice of filters as it may be changed by consumer + fm.filters = filters - return fm - } + // The skip check is based on the compiled code. So if the DecodeRequest is defined, + // even it is not called, DecodeData will not be skipped. Same as EncodeResponse. + fm.canSkipDecodeHeaders = fm.canSkipMethod["DecodeHeaders"] && fm.canSkipMethod["DecodeRequest"] && fm.config.initOnce == nil + fm.canSkipDecodeData = fm.canSkipMethod["DecodeData"] && fm.canSkipMethod["DecodeRequest"] + fm.canSkipEncodeHeaders = fm.canSkipMethod["EncodeHeaders"] + fm.canSkipEncodeData = fm.canSkipMethod["EncodeData"] && fm.canSkipMethod["EncodeResponse"] + fm.canSkipOnLog = fm.canSkipMethod["OnLog"] + + return fm } func (m *filterManager) recordLocalReplyPluginName(name string) { @@ -254,7 +256,7 @@ func (m *filterManager) handleAction(res api.ResultAction, phase phase, filter * switch v := res.(type) { case *api.LocalResponse: m.recordLocalReplyPluginName(filter.Name) - m.localReply(v) + m.localReply(v, phase < phaseEncodeHeaders) return true default: api.LogErrorf("unknown result action: %+v", v) @@ -266,7 +268,7 @@ type jsonReply struct { Msg string `json:"msg"` } -func (m *filterManager) localReply(v *api.LocalResponse) { +func (m *filterManager) localReply(v *api.LocalResponse, decoding bool) { var hdr map[string][]string if v.Header != nil { hdr = map[string][]string(v.Header) @@ -294,8 +296,10 @@ func (m *filterManager) localReply(v *api.LocalResponse) { isJSON = true } } else { - ct, ok = m.reqHdr.Get("content-type") - if !ok || ct == "application/json" { + // use the Content-Type header passed by the client, not the header + // provided by the gateway if have. + ct = m.contentType + if ct == "" || ct == "application/json" { isJSON = true } } @@ -310,14 +314,22 @@ func (m *filterManager) localReply(v *api.LocalResponse) { hdr["Content-Type"] = []string{"application/json"} } } - m.callbacks.SendLocalReply(v.Code, msg, hdr, 0, "") + + var cb api.FilterProcessCallbacks + if decoding { + cb = m.callbacks.DecoderFilterCallbacks() + } else { + cb = m.callbacks.EncoderFilterCallbacks() + } + cb.SendLocalReply(v.Code, msg, hdr, 0, "") } func (m *filterManager) DecodeHeaders(headers capi.RequestHeaderMap, endStream bool) capi.StatusType { + m.contentType, _ = headers.Get("content-type") + // Ensure the headers are cached on the Go side. // FIXME: remove this once we support OnLog phase headers in Envoy Go. if m.DebugModeEnabled() { - headers.Get("test") headers := &filterManagerRequestHeaderMap{ RequestHeaderMap: headers, } @@ -332,7 +344,7 @@ func (m *filterManager) DecodeHeaders(headers capi.RequestHeaderMap, endStream b go func() { defer m.MarkRunningInGoThread(false) - defer m.callbacks.RecoverPanic() + defer m.callbacks.DecoderFilterCallbacks().RecoverPanic() var res api.ResultAction m.config.InitOnce() @@ -341,7 +353,7 @@ func (m *filterManager) DecodeHeaders(headers capi.RequestHeaderMap, endStream b m.recordLocalReplyPluginName(m.config.initFailedPluginName) m.localReply(&api.LocalResponse{ Code: 500, - }) + }, true) return } @@ -370,7 +382,7 @@ func (m *filterManager) DecodeHeaders(headers capi.RequestHeaderMap, endStream b m.localReply(&api.LocalResponse{ Code: 401, Msg: "consumer not found", - }) + }, true) return } @@ -484,7 +496,7 @@ func (m *filterManager) DecodeHeaders(headers capi.RequestHeaderMap, endStream b m.decodeIdx = i // some filters, like authorization with request body, need to // have a whole body before passing to the next filter - m.callbacks.Continue(capi.StopAndBuffer) + m.callbacks.Continue(capi.StopAndBuffer, true) return } @@ -496,7 +508,7 @@ func (m *filterManager) DecodeHeaders(headers capi.RequestHeaderMap, endStream b } } - m.callbacks.Continue(capi.Continue) + m.callbacks.Continue(capi.Continue, true) }() return capi.Running @@ -511,7 +523,7 @@ func (m *filterManager) DecodeData(buf capi.BufferInstance, endStream bool) capi go func() { defer m.MarkRunningInGoThread(false) - defer m.callbacks.RecoverPanic() + defer m.callbacks.DecoderFilterCallbacks().RecoverPanic() var res api.ResultAction // We have discussed a lot about how to support processing data both streamingly and @@ -537,7 +549,7 @@ func (m *filterManager) DecodeData(buf capi.BufferInstance, endStream bool) capi return } } - m.callbacks.Continue(capi.Continue) + m.callbacks.Continue(capi.Continue, true) } else { for i := 0; i < m.decodeIdx; i++ { @@ -592,7 +604,7 @@ func (m *filterManager) DecodeData(buf capi.BufferInstance, endStream bool) capi } } - m.callbacks.Continue(capi.Continue) + m.callbacks.Continue(capi.Continue, true) } }() @@ -615,7 +627,7 @@ func (m *filterManager) EncodeHeaders(headers capi.ResponseHeaderMap, endStream go func() { defer m.MarkRunningInGoThread(false) - defer m.callbacks.RecoverPanic() + defer m.callbacks.EncoderFilterCallbacks().RecoverPanic() var res api.ResultAction m.hdrLock.Lock() @@ -633,7 +645,7 @@ func (m *filterManager) EncodeHeaders(headers capi.ResponseHeaderMap, endStream m.encodeResponseNeeded = false if !endStream { m.encodeIdx = i - m.callbacks.Continue(capi.StopAndBuffer) + m.callbacks.Continue(capi.StopAndBuffer, false) return } @@ -645,7 +657,7 @@ func (m *filterManager) EncodeHeaders(headers capi.ResponseHeaderMap, endStream } } - m.callbacks.Continue(capi.Continue) + m.callbacks.Continue(capi.Continue, false) }() return capi.Running @@ -660,7 +672,7 @@ func (m *filterManager) EncodeData(buf capi.BufferInstance, endStream bool) capi go func() { defer m.MarkRunningInGoThread(false) - defer m.callbacks.RecoverPanic() + defer m.callbacks.EncoderFilterCallbacks().RecoverPanic() var res api.ResultAction n := len(m.filters) @@ -673,7 +685,7 @@ func (m *filterManager) EncodeData(buf capi.BufferInstance, endStream bool) capi return } } - m.callbacks.Continue(capi.Continue) + m.callbacks.Continue(capi.Continue, false) } else { for i := n - 1; i > m.encodeIdx; i-- { @@ -724,7 +736,7 @@ func (m *filterManager) EncodeData(buf capi.BufferInstance, endStream bool) capi } } - m.callbacks.Continue(capi.Continue) + m.callbacks.Continue(capi.Continue, false) } }() diff --git a/api/pkg/filtermanager/filtermanager_benchmark_test.go b/api/pkg/filtermanager/filtermanager_benchmark_test.go index e680cc0a..f5eca7c7 100644 --- a/api/pkg/filtermanager/filtermanager_benchmark_test.go +++ b/api/pkg/filtermanager/filtermanager_benchmark_test.go @@ -44,7 +44,7 @@ func BenchmarkFilterManagerAllPhase(b *testing.B) { respBuf := envoy.NewBufferInstance([]byte{}) for n := 0; n < b.N; n++ { - m := FilterManagerFactory(config)(cb) + m := FilterManagerFactory(config, cb) m.DecodeHeaders(reqHdr, false) cb.WaitContinued() m.DecodeData(reqBuf, true) @@ -88,7 +88,7 @@ func BenchmarkFilterManagerRegular(b *testing.B) { reqHdr := envoy.NewRequestHeaderMap(http.Header{}) for n := 0; n < b.N; n++ { - m := FilterManagerFactory(config)(cb) + m := FilterManagerFactory(config, cb) m.DecodeHeaders(reqHdr, false) cb.WaitContinued() m.OnLog() @@ -129,7 +129,7 @@ func BenchmarkFilterManagerConsumerWithFilter(b *testing.B) { } for n := 0; n < b.N; n++ { - m := FilterManagerFactory(config)(cb) + m := FilterManagerFactory(config, cb) m.DecodeHeaders(reqHdrs[n%num], false) cb.WaitContinued() m.OnLog() diff --git a/api/pkg/filtermanager/filtermanager_test.go b/api/pkg/filtermanager/filtermanager_test.go index b7445040..0aeee79e 100644 --- a/api/pkg/filtermanager/filtermanager_test.go +++ b/api/pkg/filtermanager/filtermanager_test.go @@ -41,7 +41,7 @@ func TestPassThrough(t *testing.T) { }, } for i := 0; i < 2; i++ { - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) hdr := envoy.NewRequestHeaderMap(http.Header{}) m.DecodeHeaders(hdr, false) cb.WaitContinued() @@ -109,7 +109,7 @@ func TestLocalReplyJSON_UseReqHeader(t *testing.T) { Factory: PassThroughFactory, }, } - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) patches := gomonkey.ApplyMethodReturn(m.filters[0].Filter, "DecodeHeaders", &api.LocalResponse{ Code: 200, Msg: "msg", @@ -182,7 +182,7 @@ func TestLocalReplyJSON_UseRespHeader(t *testing.T) { Factory: PassThroughFactory, }, } - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) patches := gomonkey.ApplyMethodReturn(m.filters[0].Filter, "EncodeHeaders", &api.LocalResponse{ Code: 200, Msg: "msg", @@ -218,7 +218,7 @@ func TestLocalReplyJSON_DoNotChangeMsgIfContentTypeIsGiven(t *testing.T) { Factory: PassThroughFactory, }, } - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) patches := gomonkey.ApplyMethodReturn(m.filters[0].Filter, "DecodeHeaders", &api.LocalResponse{ Msg: "msg", Header: http.Header(map[string][]string{"Content-Type": {"text/plain"}}), @@ -280,7 +280,7 @@ func TestInitFailed(t *testing.T) { for i := 0; i < n; i++ { go func(i int) { cb := envoy.NewCAPIFilterCallbackHandler() - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) h := http.Header{} hdr := envoy.NewRequestHeaderMap(h) m.DecodeHeaders(hdr, true) @@ -303,7 +303,7 @@ func TestInitFailed(t *testing.T) { for i := 0; i < n; i++ { go func(i int) { cb := envoy.NewCAPIFilterCallbackHandler() - m := FilterManagerFactory(config2)(cb).(*filterManager) + m := FilterManagerFactory(config2, cb).(*filterManager) h := http.Header{} hdr := envoy.NewRequestHeaderMap(h) m.DecodeHeaders(hdr, true) @@ -371,7 +371,7 @@ func TestSkipMethodWhenThereAreMultiFilters(t *testing.T) { } for i := 0; i < 2; i++ { - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) assert.Equal(t, false, m.canSkipOnLog) assert.Equal(t, false, m.canSkipDecodeHeaders) assert.Equal(t, true, m.canSkipDecodeData) @@ -452,7 +452,7 @@ func TestFiltersFromConsumer(t *testing.T) { for i := 0; i < 2*n; i++ { go func(i int) { cb := envoy.NewCAPIFilterCallbackHandler() - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) assert.Equal(t, true, m.canSkipOnLog) assert.Equal(t, 2, len(m.filters)) h := http.Header{} @@ -545,7 +545,7 @@ func TestDoNotRecycleInUsedFilterManager(t *testing.T) { for i := 0; i < n; i++ { go func(i int) { cb := envoy.NewCAPIFilterCallbackHandler() - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) h := http.Header{} hdr := envoy.NewRequestHeaderMap(h) m.DecodeHeaders(hdr, true) @@ -560,7 +560,7 @@ func TestDoNotRecycleInUsedFilterManager(t *testing.T) { for i := 0; i < n; i++ { go func(i int) { cb := envoy.NewCAPIFilterCallbackHandler() - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) h := http.Header{} hdr := envoy.NewRequestHeaderMap(h) m.DecodeHeaders(hdr, false) @@ -577,7 +577,7 @@ func TestDoNotRecycleInUsedFilterManager(t *testing.T) { for i := 0; i < n; i++ { go func(i int) { cb := envoy.NewCAPIFilterCallbackHandler() - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) h := http.Header{} hdr := envoy.NewRequestHeaderMap(h) m.DecodeHeaders(hdr, true) @@ -595,7 +595,7 @@ func TestDoNotRecycleInUsedFilterManager(t *testing.T) { for i := 0; i < n; i++ { go func(i int) { cb := envoy.NewCAPIFilterCallbackHandler() - m := FilterManagerFactory(config)(cb).(*filterManager) + m := FilterManagerFactory(config, cb).(*filterManager) h := http.Header{} hdr := envoy.NewRequestHeaderMap(h) m.DecodeHeaders(hdr, true) diff --git a/api/pkg/filtermanager/internal_error.go b/api/pkg/filtermanager/internal_error.go index fe78392e..72f52cc0 100644 --- a/api/pkg/filtermanager/internal_error.go +++ b/api/pkg/filtermanager/internal_error.go @@ -46,16 +46,18 @@ func NewInternalErrorFactory(plugin string, err error) api.FilterFactory { type internalErrorFilterForCAPI struct { capi.PassThroughStreamFilter - callbacks capi.FilterCallbacks + callbacks api.FilterCallbackHandler } func (f *internalErrorFilterForCAPI) DecodeHeaders(headers capi.RequestHeaderMap, endStream bool) capi.StatusType { - f.callbacks.SendLocalReply(500, "", nil, 0, "") + f.callbacks.DecoderFilterCallbacks().SendLocalReply(500, "", nil, 0, "") return capi.LocalReply } func InternalErrorFactoryForCAPI(cfg interface{}, callbacks capi.FilterCallbackHandler) capi.StreamFilter { return &internalErrorFilterForCAPI{ - callbacks: callbacks, + callbacks: &filterManagerCallbackHandler{ + FilterCallbackHandler: callbacks, + }, } } diff --git a/api/plugins/tests/pkg/envoy/capi.go b/api/plugins/tests/pkg/envoy/capi.go index 8b0a2af7..cc99442d 100644 --- a/api/plugins/tests/pkg/envoy/capi.go +++ b/api/plugins/tests/pkg/envoy/capi.go @@ -36,8 +36,12 @@ func init() { capi.SetCommonCAPI(&fakeCapi{}) } +var ( + logLevel = capi.Info +) + func DisableLogInTest() { - api.SetLogLevel(capi.Critical) + logLevel = capi.Critical } func logInGo(level capi.LogType, message string) { @@ -51,7 +55,7 @@ func (a *fakeCapi) Log(level capi.LogType, message string) { } func (a *fakeCapi) LogLevel() capi.LogType { - return 0 + return logLevel } type HeaderMap struct { @@ -70,6 +74,10 @@ func (i *HeaderMap) Get(key string) (string, bool) { return v, true } +func (i *HeaderMap) GetAllHeaders() map[string][]string { + return i.Header.Clone() +} + func (i *HeaderMap) Values(key string) []string { return i.Header.Values(key) } @@ -144,6 +152,18 @@ func (i *RequestHeaderMap) Path() string { return path } +func (i *RequestHeaderMap) SetMethod(method string) { + i.Set(":method", method) +} + +func (i *RequestHeaderMap) SetPath(path string) { + i.Set(":path", path) +} + +func (i *RequestHeaderMap) SetHost(host string) { + i.Set(":authority", host) +} + func (i *RequestHeaderMap) URL() *url.URL { path := i.Path() u, _ := url.ParseRequestURI(path) @@ -499,6 +519,9 @@ func (i *filterCallbackHandler) GetProperty(key string) (string, error) { return "", nil } +func (i *filterCallbackHandler) ClearRouteCache() { +} + func (i *filterCallbackHandler) LookupConsumer(_, _ string) (api.Consumer, bool) { return nil, false } @@ -518,7 +541,7 @@ func (i *filterCallbackHandler) PluginState() api.PluginState { return i.pluginState } -func (i *filterCallbackHandler) WithLogArg(key string, value any) api.FilterCallbackHandler { +func (i *filterCallbackHandler) WithLogArg(key string, value any) api.StreamFilterCallbacks { return i } @@ -552,6 +575,16 @@ func (i *filterCallbackHandler) LogErrorf(format string, v ...any) { func (i *filterCallbackHandler) LogError(message string) { } +func (i *filterCallbackHandler) DecoderFilterCallbacks() api.DecoderFilterCallbacks { + // we don't distinguish between decoder and encoder filter callbacks in the test helper + return i +} + +func (i *filterCallbackHandler) EncoderFilterCallbacks() api.EncoderFilterCallbacks { + // we don't distinguish between decoder and encoder filter callbacks in the test helper + return i +} + var _ api.FilterCallbackHandler = (*filterCallbackHandler)(nil) type capiFilterCallbackHandler struct { diff --git a/api/plugins/tests/pkg/envoy/capi_129.go b/api/plugins/tests/pkg/envoy/capi_129.go new file mode 100644 index 00000000..29bb1337 --- /dev/null +++ b/api/plugins/tests/pkg/envoy/capi_129.go @@ -0,0 +1,17 @@ +// Copyright The HTNN 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. + +//go:build 1.29 && !so + +package envoy diff --git a/api/plugins/tests/pkg/envoy/capi_latest.go b/api/plugins/tests/pkg/envoy/capi_latest.go new file mode 100644 index 00000000..80c9a466 --- /dev/null +++ b/api/plugins/tests/pkg/envoy/capi_latest.go @@ -0,0 +1,29 @@ +// Copyright The HTNN 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. + +//go:build !1.29 && !so + +package envoy + +import ( + capi "github.com/envoyproxy/envoy/contrib/golang/common/go/api" +) + +func (cb *capiFilterCallbackHandler) DecoderFilterCallbacks() capi.DecoderFilterCallbacks { + return cb.filterCallbackHandler.DecoderFilterCallbacks().(*filterCallbackHandler) +} + +func (cb *capiFilterCallbackHandler) EncoderFilterCallbacks() capi.EncoderFilterCallbacks { + return cb.filterCallbackHandler.EncoderFilterCallbacks().(*filterCallbackHandler) +} diff --git a/api/tests/integration/libgolang/main.go b/api/tests/integration/libgolang/main.go index 4b278ca2..72de4345 100644 --- a/api/tests/integration/libgolang/main.go +++ b/api/tests/integration/libgolang/main.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build so +//go:build so && !1.29 package main @@ -27,9 +27,9 @@ import ( ) func init() { - http.RegisterHttpFilterConfigFactoryAndParser("fm", filtermanager.FilterManagerFactory, &filtermanager.FilterManagerConfigParser{}) - http.RegisterHttpFilterConfigFactoryAndParser("cm", consumer.ConsumerManagerFactory, &consumer.ConsumerManagerConfigParser{}) - http.RegisterHttpFilterConfigFactoryAndParser("dc", dynamicconfig.DynamicConfigFactory, &dynamicconfig.DynamicConfigParser{}) + http.RegisterHttpFilterFactoryAndConfigParser("fm", filtermanager.FilterManagerFactory, &filtermanager.FilterManagerConfigParser{}) + http.RegisterHttpFilterFactoryAndConfigParser("cm", consumer.ConsumerManagerFactory, &consumer.ConsumerManagerConfigParser{}) + http.RegisterHttpFilterFactoryAndConfigParser("dc", dynamicconfig.DynamicConfigFactory, &dynamicconfig.DynamicConfigParser{}) } func main() {} diff --git a/api/tests/integration/libgolang/main_129.go b/api/tests/integration/libgolang/main_129.go new file mode 100644 index 00000000..ba8a8124 --- /dev/null +++ b/api/tests/integration/libgolang/main_129.go @@ -0,0 +1,54 @@ +// Copyright The HTNN 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. + +//go:build so && 1.29 + +package main + +import ( + capi "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" + + "mosn.io/htnn/api/pkg/consumer" + "mosn.io/htnn/api/pkg/dynamicconfig" + "mosn.io/htnn/api/pkg/filtermanager" + _ "mosn.io/htnn/api/plugins/tests/integration/dataplane" // for utility plugins provided in the test framework + _ "mosn.io/htnn/api/tests/integration" // for plugins used in the test +) + +var ( + filterManagerFactoryWrapper = func(c interface{}) capi.StreamFilterFactory { + return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + return filtermanager.FilterManagerFactory(c, cb) + } + } + consumerManagerFactoryWrapper = func(c interface{}) capi.StreamFilterFactory { + return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + return consumer.ConsumerManagerFactory(c, cb) + } + } + dynamicConfigFactoryWrapper = func(c interface{}) capi.StreamFilterFactory { + return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + return dynamicconfig.DynamicConfigFactory(c, cb) + } + } +) + +func init() { + http.RegisterHttpFilterConfigFactoryAndParser("fm", filterManagerFactoryWrapper, &filtermanager.FilterManagerConfigParser{}) + http.RegisterHttpFilterConfigFactoryAndParser("cm", consumerManagerFactoryWrapper, &consumer.ConsumerManagerConfigParser{}) + http.RegisterHttpFilterConfigFactoryAndParser("dc", dynamicConfigFactoryWrapper, &dynamicconfig.DynamicConfigParser{}) +} + +func main() {} diff --git a/api/tests/integration/test_plugins.go b/api/tests/integration/test_plugins.go index 4b515cae..c7208a64 100644 --- a/api/tests/integration/test_plugins.go +++ b/api/tests/integration/test_plugins.go @@ -180,16 +180,20 @@ type localReplyFilter struct { callbacks api.FilterCallbackHandler config *Config - reqHdr api.RequestHeaderMap + + reqHdr api.RequestHeaderMap + runFilters []string } -func (f *localReplyFilter) NewLocalResponse(reply string) *api.LocalResponse { +func (f *localReplyFilter) NewLocalResponse(reply string, decoding bool) *api.LocalResponse { hdr := http.Header{} hdr.Set("local", reply) - runFilters := f.reqHdr.Values("run") - if len(runFilters) > 0 { - hdr.Set("order", strings.Join(runFilters, "|")) + if decoding { + f.runFilters = f.reqHdr.Values("run") + } + if len(f.runFilters) > 0 { + hdr.Set("order", strings.Join(f.runFilters, "|")) } msg := "ok" @@ -202,8 +206,9 @@ func (f *localReplyFilter) NewLocalResponse(reply string) *api.LocalResponse { func (f *localReplyFilter) DecodeRequest(headers api.RequestHeaderMap, buf api.BufferInstance, trailer api.RequestTrailerMap) api.ResultAction { api.LogInfof("traceback: %s", string(debug.Stack())) f.reqHdr = headers + f.runFilters = headers.Values("run") if f.config.Decode { - return f.NewLocalResponse("reply") + return f.NewLocalResponse("reply", true) } return api.Continue } @@ -212,7 +217,7 @@ func (f *localReplyFilter) EncodeResponse(headers api.ResponseHeaderMap, buf api api.LogInfof("traceback: %s", string(debug.Stack())) if f.config.Encode { r, _ := headers.Get("echo-from") - return f.NewLocalResponse(r) + return f.NewLocalResponse(r, false) } return api.Continue } @@ -223,8 +228,9 @@ func (f *localReplyFilter) DecodeHeaders(headers api.RequestHeaderMap, endStream return api.WaitAllData } f.reqHdr = headers + f.runFilters = headers.Values("run") if f.config.Decode && f.config.Headers { - return f.NewLocalResponse("reply") + return f.NewLocalResponse("reply", true) } return api.Continue } @@ -232,7 +238,7 @@ func (f *localReplyFilter) DecodeHeaders(headers api.RequestHeaderMap, endStream func (f *localReplyFilter) DecodeData(data api.BufferInstance, endStream bool) api.ResultAction { api.LogInfof("traceback: %s", string(debug.Stack())) if f.config.Decode && f.config.Data { - return f.NewLocalResponse("reply") + return f.NewLocalResponse("reply", true) } return api.Continue } @@ -244,7 +250,7 @@ func (f *localReplyFilter) EncodeHeaders(headers api.ResponseHeaderMap, endStrea } if f.config.Encode && f.config.Headers { r, _ := headers.Get("echo-from") - return f.NewLocalResponse(r) + return f.NewLocalResponse(r, false) } return api.Continue } @@ -252,7 +258,7 @@ func (f *localReplyFilter) EncodeHeaders(headers api.ResponseHeaderMap, endStrea func (f *localReplyFilter) EncodeData(data api.BufferInstance, endStream bool) api.ResultAction { api.LogInfof("traceback: %s", string(debug.Stack())) if f.config.Encode && f.config.Data { - return f.NewLocalResponse("reply") + return f.NewLocalResponse("reply", false) } return api.Continue } diff --git a/common.mk b/common.mk index 6ac92a1d..28bfad57 100644 --- a/common.mk +++ b/common.mk @@ -28,8 +28,10 @@ PROJECT_NAME = mosn.io/htnn DOCKER_MIRROR = m.daocloud.io/ # Both images use glibc 2.31. Ensure libc in the images match each other. BUILD_IMAGE ?= $(DOCKER_MIRROR)docker.io/library/golang:1.21-bullseye +ENVOY_API_VERSION ?= 1.29 # We don't use istio/proxyv2 because it is not designed to be run separately (need to work around permission issue). -PROXY_IMAGE ?= $(DOCKER_MIRROR)docker.io/envoyproxy/envoy:contrib-v1.29.5 +PROXY_IMAGE_VERSION ?= 1.29.5 +PROXY_IMAGE ?= $(DOCKER_MIRROR)docker.io/envoyproxy/envoy:contrib-v$(PROXY_IMAGE_VERSION) # We may need to use timestamp if we need to update the image in one PR REAL_DEV_TOOLS_IMAGE ?= ghcr.io/mosn/htnn-dev-tools:2024-07-12 DEV_TOOLS_IMAGE ?= $(DOCKER_MIRROR)$(REAL_DEV_TOOLS_IMAGE) diff --git a/patch/switch-envoy-go-version.sh b/patch/switch-envoy-go-version.sh new file mode 100755 index 00000000..b9a3d245 --- /dev/null +++ b/patch/switch-envoy-go-version.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# Copyright The HTNN 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. + + +set -euo pipefail + +envoy_version=$1 + +# Check if arguments were provided +if [ -z "$envoy_version" ]; then + echo "Usage: $0 " + exit 1 +fi + +if [[ "$envoy_version" =~ ^1\.29\.? ]]; then + # patch version is trivial as so far we don't have any patch version created for Envoy Go + envoy_version="1.29.5" +fi + +append_if_need() { + search_string=$1 + target_file=$2 + # Search for the string in the file + if ! grep -q "$search_string" "$target_file"; then + # Append the string to the file + echo "$search_string" >> "$target_file" + echo "String '$search_string' appended to $target_file" + else + echo "String '$search_string' already exists in $target_file" + fi +} + +# append to go.mod is easier than maintaining a go.mod file for -modfile flag +append_if_need "replace github.com/envoyproxy/envoy => github.com/envoyproxy/envoy v$envoy_version" api/go.mod +append_if_need "replace github.com/envoyproxy/envoy => github.com/envoyproxy/envoy v$envoy_version" plugins/go.mod diff --git a/plugins/Makefile b/plugins/Makefile index f11514d5..8adf6c26 100644 --- a/plugins/Makefile +++ b/plugins/Makefile @@ -18,7 +18,7 @@ PROJECT_ROOT := $(PWD)/.. .PHONY: unit-test unit-test: - go test ${TEST_OPTION} $(shell go list ./... | \ + go test -tags ${ENVOY_API_VERSION} ${TEST_OPTION} $(shell go list ./... | \ grep -v tests/integration) # We can't specify -race to `go build` because it seems that @@ -26,7 +26,7 @@ unit-test: # the race detector will allocate memory out of 48bits address which is not allowed in x64. .PHONY: build-test-so-local build-test-so-local: - CGO_ENABLED=1 go build -tags so \ + CGO_ENABLED=1 go build -tags so,${ENVOY_API_VERSION} \ -ldflags "-B 0x$(shell head -c20 /dev/urandom|od -An -tx1|tr -d ' \n')" \ --buildmode=c-shared \ -cover -covermode=atomic -coverpkg=${PROJECT_NAME}/... \ @@ -55,12 +55,12 @@ stop-service: integration-test: test -d /tmp/htnn_coverage && rm -rf /tmp/htnn_coverage || true $(foreach PKG, $(shell go list ./tests/integration/...), \ - go test -v ${PKG} || exit 1; \ + go test -tags ${ENVOY_API_VERSION} -v ${PKG} || exit 1; \ ) .PHONY: build-so-local build-so-local: - CGO_ENABLED=1 go build -tags so \ + CGO_ENABLED=1 go build -tags so,${ENVOY_API_VERSION} \ -ldflags "-B 0x$(shell head -c20 /dev/urandom|od -An -tx1|tr -d ' \n')" \ --buildmode=c-shared \ -v -o ${TARGET_SO} \ diff --git a/plugins/cmd/libgolang/main.go b/plugins/cmd/libgolang/main.go index 32a7465b..c83a2a2f 100644 --- a/plugins/cmd/libgolang/main.go +++ b/plugins/cmd/libgolang/main.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build so +//go:build so && !1.29 package main @@ -26,9 +26,9 @@ import ( ) func init() { - http.RegisterHttpFilterConfigFactoryAndParser("fm", filtermanager.FilterManagerFactory, &filtermanager.FilterManagerConfigParser{}) - http.RegisterHttpFilterConfigFactoryAndParser("cm", consumer.ConsumerManagerFactory, &consumer.ConsumerManagerConfigParser{}) - http.RegisterHttpFilterConfigFactoryAndParser("dc", dynamicconfig.DynamicConfigFactory, &dynamicconfig.DynamicConfigParser{}) + http.RegisterHttpFilterFactoryAndConfigParser("fm", filtermanager.FilterManagerFactory, &filtermanager.FilterManagerConfigParser{}) + http.RegisterHttpFilterFactoryAndConfigParser("cm", consumer.ConsumerManagerFactory, &consumer.ConsumerManagerConfigParser{}) + http.RegisterHttpFilterFactoryAndConfigParser("dc", dynamicconfig.DynamicConfigFactory, &dynamicconfig.DynamicConfigParser{}) } func main() {} diff --git a/plugins/cmd/libgolang/main_129.go b/plugins/cmd/libgolang/main_129.go new file mode 100644 index 00000000..d59ea502 --- /dev/null +++ b/plugins/cmd/libgolang/main_129.go @@ -0,0 +1,53 @@ +// Copyright The HTNN 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. + +//go:build so && 1.29 + +package main + +import ( + capi "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" + + "mosn.io/htnn/api/pkg/consumer" + "mosn.io/htnn/api/pkg/dynamicconfig" + "mosn.io/htnn/api/pkg/filtermanager" + _ "mosn.io/htnn/plugins" +) + +var ( + filterManagerFactoryWrapper = func(c interface{}) capi.StreamFilterFactory { + return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + return filtermanager.FilterManagerFactory(c, cb) + } + } + consumerManagerFactoryWrapper = func(c interface{}) capi.StreamFilterFactory { + return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + return consumer.ConsumerManagerFactory(c, cb) + } + } + dynamicConfigFactoryWrapper = func(c interface{}) capi.StreamFilterFactory { + return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + return dynamicconfig.DynamicConfigFactory(c, cb) + } + } +) + +func init() { + http.RegisterHttpFilterConfigFactoryAndParser("fm", filterManagerFactoryWrapper, &filtermanager.FilterManagerConfigParser{}) + http.RegisterHttpFilterConfigFactoryAndParser("cm", consumerManagerFactoryWrapper, &consumer.ConsumerManagerConfigParser{}) + http.RegisterHttpFilterConfigFactoryAndParser("dc", dynamicConfigFactoryWrapper, &dynamicconfig.DynamicConfigParser{}) +} + +func main() {} diff --git a/plugins/pkg/request/request.go b/plugins/pkg/request/request.go index 75fa2838..62121811 100644 --- a/plugins/pkg/request/request.go +++ b/plugins/pkg/request/request.go @@ -15,7 +15,7 @@ package request import ( - "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "mosn.io/htnn/api/pkg/filtermanager/api" ) // GetHeaders returns a plain map represents the headers. The returned headers won't diff --git a/plugins/tests/integration/libgolang/main.go b/plugins/tests/integration/libgolang/main.go index 064a275b..1af2a126 100644 --- a/plugins/tests/integration/libgolang/main.go +++ b/plugins/tests/integration/libgolang/main.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//go:build so +//go:build so && !1.29 package main @@ -27,9 +27,9 @@ import ( ) func init() { - http.RegisterHttpFilterConfigFactoryAndParser("fm", filtermanager.FilterManagerFactory, &filtermanager.FilterManagerConfigParser{}) - http.RegisterHttpFilterConfigFactoryAndParser("cm", consumer.ConsumerManagerFactory, &consumer.ConsumerManagerConfigParser{}) - http.RegisterHttpFilterConfigFactoryAndParser("dc", dynamicconfig.DynamicConfigFactory, &dynamicconfig.DynamicConfigParser{}) + http.RegisterHttpFilterFactoryAndConfigParser("fm", filtermanager.FilterManagerFactory, &filtermanager.FilterManagerConfigParser{}) + http.RegisterHttpFilterFactoryAndConfigParser("cm", consumer.ConsumerManagerFactory, &consumer.ConsumerManagerConfigParser{}) + http.RegisterHttpFilterFactoryAndConfigParser("dc", dynamicconfig.DynamicConfigFactory, &dynamicconfig.DynamicConfigParser{}) } func main() {} diff --git a/plugins/tests/integration/libgolang/main_129.go b/plugins/tests/integration/libgolang/main_129.go new file mode 100644 index 00000000..7579acb1 --- /dev/null +++ b/plugins/tests/integration/libgolang/main_129.go @@ -0,0 +1,54 @@ +// Copyright The HTNN 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. + +//go:build so && 1.29 + +package main + +import ( + capi "github.com/envoyproxy/envoy/contrib/golang/common/go/api" + "github.com/envoyproxy/envoy/contrib/golang/filters/http/source/go/pkg/http" + + "mosn.io/htnn/api/pkg/consumer" + "mosn.io/htnn/api/pkg/dynamicconfig" + "mosn.io/htnn/api/pkg/filtermanager" + _ "mosn.io/htnn/api/plugins/tests/integration/dataplane" // for utility plugins provided in the test framework + _ "mosn.io/htnn/plugins" +) + +var ( + filterManagerFactoryWrapper = func(c interface{}) capi.StreamFilterFactory { + return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + return filtermanager.FilterManagerFactory(c, cb) + } + } + consumerManagerFactoryWrapper = func(c interface{}) capi.StreamFilterFactory { + return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + return consumer.ConsumerManagerFactory(c, cb) + } + } + dynamicConfigFactoryWrapper = func(c interface{}) capi.StreamFilterFactory { + return func(cb capi.FilterCallbackHandler) (streamFilter capi.StreamFilter) { + return dynamicconfig.DynamicConfigFactory(c, cb) + } + } +) + +func init() { + http.RegisterHttpFilterConfigFactoryAndParser("fm", filterManagerFactoryWrapper, &filtermanager.FilterManagerConfigParser{}) + http.RegisterHttpFilterConfigFactoryAndParser("cm", consumerManagerFactoryWrapper, &consumer.ConsumerManagerConfigParser{}) + http.RegisterHttpFilterConfigFactoryAndParser("dc", dynamicConfigFactoryWrapper, &dynamicconfig.DynamicConfigParser{}) +} + +func main() {}