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

golang filter: do not clear route cache in ·HeaderMap.Set` by default. #33229

Merged
merged 3 commits into from
Apr 1, 2024
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
3 changes: 3 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ minor_behavior_changes:
change: |
Delta SDS removals will no longer result in a "Missing SDS resources" NACK
and instead will be ignored.
- area: golang
change: |
Not implicitly clearing route cache in ``HeaderMap.Set``, introduce a new API ``ClearRouteCache`` to do it.

bug_fixes:
# *Changes expected to improve the state of the world and are unlikely to have negative effects*
Expand Down
1 change: 1 addition & 0 deletions contrib/golang/common/go/api/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ typedef enum { // NOLINT(modernize-use-using)
CAPISerializationFailure = -8,
} CAPIStatus;

CAPIStatus envoyGoFilterHttpClearRouteCache(void* r);
CAPIStatus envoyGoFilterHttpContinue(void* r, int status);
CAPIStatus envoyGoFilterHttpSendLocalReply(void* r, int response_code, void* body_text_data,
int body_text_len, void* headers, int headers_num,
Expand Down
1 change: 1 addition & 0 deletions contrib/golang/common/go/api/capi.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package api
import "unsafe"

type HttpCAPI interface {
ClearRouteCache(r unsafe.Pointer)
HttpContinue(r unsafe.Pointer, status uint64)
HttpSendLocalReply(r unsafe.Pointer, responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string)

Expand Down
3 changes: 3 additions & 0 deletions contrib/golang/common/go/api/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ type StreamFilterCallbacks interface {

type FilterCallbacks interface {
StreamFilterCallbacks
// 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()
doujiang24 marked this conversation as resolved.
Show resolved Hide resolved
// Continue or SendLocalReply should be last API invoked, no more code after them.
Continue(StatusType)
SendLocalReply(responseCode int, bodyText string, headers map[string][]string, grpcStatus int64, details string)
Expand Down
13 changes: 13 additions & 0 deletions contrib/golang/common/go/api/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,19 @@ type HeaderMap interface {

// Set key-value pair in header map, the previous pair will be replaced if exists.
// It may not take affects immediately in the Envoy thread side when it's invoked in a Go thread.
// This won't refresh route cache, please invoke ClearRouteCache if needed.
Set(key, value string)

// Add value for given key.
// Multiple headers with the same key may be added with this function.
// Use Set for setting a single header for the given key.
// It may not take affects immediately in the Envoy thread side when it's invoked in a Go thread.
// This won't refresh route cache, please invoke ClearRouteCache if needed.
Add(key, value string)

// Del delete pair of specified key
// It may not take affects immediately in the Envoy thread side when it's invoked in a Go thread.
// This won't refresh route cache, please invoke ClearRouteCache if needed.
Del(key string)

// Range calls f sequentially for each key and value present in the map.
Expand All @@ -136,6 +139,16 @@ type RequestHeaderMap interface {
Method() string
Host() string
Path() string
// SetMethod set method in header map
// This won't refresh route cache, please invoke ClearRouteCache if needed.
SetMethod(method string)
// SetHost set host in header map
// This won't refresh route cache, please invoke ClearRouteCache if needed.
SetHost(host string)
// SetPath set path in header map
// This won't refresh route cache, please invoke ClearRouteCache if needed.
SetPath(path string)
// Note: Scheme is the downstream protocol, we'd better not override it.
}

type RequestTrailerMap interface {
Expand Down
5 changes: 5 additions & 0 deletions contrib/golang/filters/http/source/cgo.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ envoyGoConfigHandlerWrapper(void* c, std::function<CAPIStatus(std::shared_ptr<Fi
return CAPIStatus::CAPIFilterIsGone;
}

CAPIStatus envoyGoFilterHttpClearRouteCache(void* r) {
return envoyGoFilterHandlerWrapper(
r, [](std::shared_ptr<Filter>& filter) -> CAPIStatus { return filter->clearRouteCache(); });
}

CAPIStatus envoyGoFilterHttpContinue(void* r, int status) {
return envoyGoFilterHandlerWrapper(r, [status](std::shared_ptr<Filter>& filter) -> CAPIStatus {
return filter->continueStatus(static_cast<GolangStatus>(status));
Expand Down
5 changes: 5 additions & 0 deletions contrib/golang/filters/http/source/go/pkg/http/capi_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ func capiStatusToErr(status C.CAPIStatus) error {
return errors.New("unknown status")
}

func (c *httpCApiImpl) ClearRouteCache(r unsafe.Pointer) {
res := C.envoyGoFilterHttpClearRouteCache(r)
handleCApiStatus(res)
}

func (c *httpCApiImpl) HttpContinue(r unsafe.Pointer, status uint64) {
res := C.envoyGoFilterHttpContinue(r, C.int(status))
handleCApiStatus(res)
Expand Down
4 changes: 4 additions & 0 deletions contrib/golang/filters/http/source/go/pkg/http/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ func (r *httpRequest) RecoverPanic() {
}
}

func (r *httpRequest) ClearRouteCache() {
cAPI.ClearRouteCache(unsafe.Pointer(r.req))
}

func (r *httpRequest) Continue(status api.StatusType) {
if status == api.LocalReply {
fmt.Printf("warning: LocalReply status is useless after sendLocalReply, ignoring")
Expand Down
12 changes: 12 additions & 0 deletions contrib/golang/filters/http/source/go/pkg/http/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ func (h *requestHeaderMapImpl) Host() string {
return v
}

func (h *requestHeaderMapImpl) SetMethod(method string) {
doujiang24 marked this conversation as resolved.
Show resolved Hide resolved
h.Set(":method", method)
}

func (h *requestHeaderMapImpl) SetPath(path string) {
h.Set(":path", path)
}

func (h *requestHeaderMapImpl) SetHost(host string) {
h.Set(":authority", host)
}

// api.ResponseHeaderMap
type responseHeaderMapImpl struct {
requestOrResponseHeaderMapImpl
Expand Down
29 changes: 16 additions & 13 deletions contrib/golang/filters/http/source/golang_filter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,6 @@ namespace Extensions {
namespace HttpFilters {
namespace Golang {

void Filter::onHeadersModified() {
// Any changes to request headers can affect how the request is going to be
// routed. If we are changing the headers we also need to clear the route
// cache.
decoding_state_.getFilterCallbacks()->downstreamCallbacks()->clearRouteCache();
}

Http::LocalErrorStatus Filter::onLocalReply(const LocalReplyData& data) {
auto& state = getProcessorState();
ASSERT(state.isThreadSafe());
Expand Down Expand Up @@ -402,6 +395,22 @@ bool Filter::doTrailer(ProcessorState& state, Http::HeaderMap& trailers) {

/*** APIs for go call C ***/

CAPIStatus Filter::clearRouteCache() {
Thread::LockGuard lock(mutex_);
if (has_destroyed_) {
ENVOY_LOG(debug, "golang filter has been destroyed");
return CAPIStatus::CAPIFilterIsDestroy;
}
auto& state = getProcessorState();
if (!state.isProcessingInGo()) {
ENVOY_LOG(debug, "golang filter is not processing Go");
return CAPIStatus::CAPINotInGo;
}
ENVOY_LOG(debug, "golang filter clearing route cache");
decoding_state_.getFilterCallbacks()->downstreamCallbacks()->clearRouteCache();
return CAPIStatus::CAPIOK;
}

void Filter::continueEncodeLocalReply(ProcessorState& state) {
ENVOY_LOG(debug,
"golang filter continue encodeHeader(local reply from other filters) after return from "
Expand Down Expand Up @@ -702,8 +711,6 @@ CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, hea
default:
RELEASE_ASSERT(false, absl::StrCat("unknown header action: ", act));
}

onHeadersModified();
} else {
// should deep copy the string_view before post to dipatcher callback.
auto key_str = std::string(key);
Expand All @@ -728,8 +735,6 @@ CAPIStatus Filter::setHeader(absl::string_view key, absl::string_view value, hea
default:
RELEASE_ASSERT(false, absl::StrCat("unknown header action: ", act));
}

onHeadersModified();
} else {
ENVOY_LOG(debug, "golang filter has gone or destroyed in setHeader");
}
Expand Down Expand Up @@ -759,7 +764,6 @@ CAPIStatus Filter::removeHeader(absl::string_view key) {
if (state.isThreadSafe()) {
// it's safe to write header in the safe thread.
headers_->remove(Http::LowerCaseString(key));
onHeadersModified();
} else {
// should deep copy the string_view before post to dipatcher callback.
auto key_str = std::string(key);
Expand All @@ -772,7 +776,6 @@ CAPIStatus Filter::removeHeader(absl::string_view key) {
if (!weak_ptr.expired() && !hasDestroyed()) {
Thread::LockGuard lock(mutex_);
headers_->remove(Http::LowerCaseString(key_str));
onHeadersModified();
} else {
ENVOY_LOG(debug, "golang filter has gone or destroyed in removeHeader");
}
Expand Down
3 changes: 1 addition & 2 deletions contrib/golang/filters/http/source/golang_filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ class Filter : public Http::StreamFilter,

void onStreamComplete() override {}

CAPIStatus clearRouteCache();
CAPIStatus continueStatus(GolangStatus status);

CAPIStatus sendLocalReply(Http::Code response_code, std::string body_text,
Expand Down Expand Up @@ -264,8 +265,6 @@ class Filter : public Http::StreamFilter,
void continueStatusInternal(GolangStatus status);
void continueData(ProcessorState& state);

void onHeadersModified();

void sendLocalReplyInternal(Http::Code response_code, absl::string_view body_text,
std::function<void(Http::ResponseHeaderMap& headers)> modify_headers,
Grpc::Status::GrpcStatus grpc_status, absl::string_view details);
Expand Down
39 changes: 39 additions & 0 deletions contrib/golang/filters/http/test/golang_integration_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,35 @@ name: golang
cleanup();
}

void testRouteCache(std::string path, bool clear) {
initializeBasicFilter(BASIC);

codec_client_ = makeHttpConnection(makeClientConnection(lookupPort("http")));
Http::TestRequestHeaderMapImpl request_headers{
{":method", "POST"}, {":path", path}, {":scheme", "http"}, {":authority", "test.com"}};

auto encoder_decoder = codec_client_->startRequest(request_headers, true);
auto response = std::move(encoder_decoder.second);

// no route found after clearing
if (!clear) {
waitForNextUpstreamRequest();
Http::TestResponseHeaderMapImpl response_headers{{":status", "200"}};
upstream_request_->encodeHeaders(response_headers, true);
}

ASSERT_TRUE(response->waitForEndStream());

// check resp status
if (clear) {
EXPECT_EQ("404", response->headers().getStatusValue());
} else {
EXPECT_EQ("200", response->headers().getStatusValue());
}

cleanup();
}

void testSendLocalReply(std::string path, std::string phase) {
initializeBasicFilter(BASIC);

Expand Down Expand Up @@ -1107,6 +1136,16 @@ TEST_P(GolangIntegrationTest, RouteConfig_Route) {
testRouteConfig("test.com", "/route-config-test", false, "baz");
}

// Set new path without clear route cache, will get 200 response status
TEST_P(GolangIntegrationTest, RouteCache_noClear) {
testRouteCache("/test?newPath=/not-found-path", false);
}

// Set new path with clear route cache, will get 404 response status
TEST_P(GolangIntegrationTest, RouteCache_Clear) {
testRouteCache("/test?newPath=/not-found-path&clearRoute=1", true);
}

// Out of range in decode header phase
TEST_P(GolangIntegrationTest, PanicRecover_DecodeHeader) {
testPanicRecover("/test?panic=decode-header", "decode-header");
Expand Down
11 changes: 11 additions & 0 deletions contrib/golang/filters/http/test/test_data/basic/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type filter struct {
databuffer string // return api.Stop
panic string // hit panic in which phase
badapi bool // bad api call
newPath string // set new path
clearRoute bool // clear route cache
}

func parseQuery(path string) url.Values {
Expand Down Expand Up @@ -76,6 +78,8 @@ func (f *filter) initRequest(header api.RequestHeaderMap) {
f.localreplay = f.query_params.Get("localreply")
f.panic = f.query_params.Get("panic")
f.badapi = f.query_params.Get("badapi") != ""
f.newPath = f.query_params.Get("newPath")
f.clearRoute = f.query_params.Get("clearRoute") != ""
}

func (f *filter) fail(msg string, a ...any) api.StatusType {
Expand Down Expand Up @@ -227,6 +231,13 @@ func (f *filter) decodeHeaders(header api.RequestHeaderMap, endStream bool) api.
if f.panic == "decode-header" {
badcode()
}

if f.newPath != "" {
header.SetPath(f.newPath)
}
if f.clearRoute {
f.callbacks.ClearRouteCache()
}
return api.Continue
}

Expand Down