Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9501403
authz_v2alpha: initial commit
Apr 23, 2018
8419745
authz_v2alpha: fixed format
Apr 23, 2018
e6d6df0
authz_v2alpha: fixed format with right version
Apr 23, 2018
af3925b
authz_v2alpha: code review changes
May 7, 2018
33e7eba
Merge remote-tracking branch 'upstream/master' into authz_grpc_v2alpha
May 7, 2018
0ed4639
authz_v2alpha: added missing proto
May 7, 2018
ab5cf4c
authz_v2alpha: fixed response initialization
May 7, 2018
615b64c
authz_v2alpha: implemented http client
May 17, 2018
050f75f
authz_v2alpha: merged master
May 21, 2018
7e9cf7c
authz_v2alpha: renamed authz implementation files
May 21, 2018
09e4125
authz_v2alpha: move http_status into envoy type
May 21, 2018
eb45964
authz_v2alpha: improved ext_authz_grpc_impl tests
May 22, 2018
5bd6581
authz_v2alpha: code review changes
May 22, 2018
559c8fb
authz_v2alpha: minor formatting fixes
May 22, 2018
f717cda
authz_v2alpha: changed authz response to use base header_option and f…
Jun 4, 2018
69ca997
authz_v2alpha: changed authz response to use base header_option and f…
Jun 4, 2018
568b5c1
authz_v2alpha: added tests for common ext_authz http impl and other c…
Jun 11, 2018
fe9fa0c
authz_v2alpha: added tests
Jun 17, 2018
d6e008c
Merge remote-tracking branch 'upstream/master' into authz_grpc_v2alpha
Jun 17, 2018
dc61d76
authz_v2alpha: added comments to the test cases
Jun 17, 2018
982a210
authz_v2alpha: fixed docs and format
Jun 18, 2018
7acf696
authz_v2alpha: changed the docs and updated the release notes
Jun 19, 2018
4b3520d
authz_v2alpha: added extra line
Jun 19, 2018
dd9d091
authz_v2alpha: added code review changes
Jun 21, 2018
f1aa15f
authz_v2alpha: fixed conflict
Jun 21, 2018
c454122
authz_v2alpha: improved test coverage for ext_authz HTPP filter
Jun 22, 2018
681bc64
Merge remote-tracking branch 'upstream/master' into authz_grpc_v2alpha
Jun 22, 2018
f8155ab
Merge remote-tracking branch 'upstream/master' into authz_grpc_v2alpha
Jun 23, 2018
0250a49
authz_v2alpha: improved ext_authz HTTP filter branch test coverage
Jun 24, 2018
14889bc
authz_v2alpha: improved ext_authz HTTP client test coverage
Jun 25, 2018
e17a6fc
authz_v2alpha: fixed sanitizer error
Jun 25, 2018
a760b45
Merge remote-tracking branch 'upstream/master' into authz_grpc_v2alpha
Jun 25, 2018
e2ba046
authz_v2alpha: improved gRPC authz client test coverage
Jun 25, 2018
298a696
authz_v2alpha: improved gRPC authz client branch test coverage
Jun 25, 2018
8d05a5d
authz_v2alpha: improved http authz client branch test coverage
Jun 25, 2018
b7aaef1
authz_v2alpha: improved http authz filter test
Jun 25, 2018
6058248
authz_v2alpha: added test for unknown HTTP status code use case
Jun 27, 2018
91db962
Merge remote-tracking branch 'upstream/master' into authz_grpc_v2alpha
Jun 27, 2018
5c320bb
authz_v2alpha: fixed format
Jun 27, 2018
a5fcd62
Merge remote-tracking branch 'upstream/master' into authz_grpc_v2alpha
Jun 27, 2018
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
29 changes: 16 additions & 13 deletions api/envoy/service/auth/v2alpha/external_auth.proto
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,24 @@ message CheckRequest {
AttributeContext attributes = 1;
}

// An message that contains HTTP response attributes. This message is
// used when the authorization service needs to send custom responses to the
// downstream client or, to modify/add request headers being dispatched to the upstream.
message HttpResponse {
// Http status code.
uint32 status_code = 1 [(validate.rules).uint32 = {gte: 100, lt: 600}];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Non-actionable, but if we were to redo the APIs from the ground up, I think I would add a dedicated HttpStatusCode object with the related constraints. We've done this now in a lot of places.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sounds good. Should I place it here in a separated file like the http_uri.proto? https://github.com/gsagula/envoy/tree/e6d6df09c84a189a43762a1e6839103e8d02c51e/api/envoy/api/v2/core


// Http entity headers.
map<string, string> headers = 2;

// Http entity body.
string body = 3;
}

message CheckResponse {
// Status `OK` allows the request. Any other status indicates the request should be denied.
google.rpc.Status status = 1;

// An optional message that contains HTTP response attributes. This message is
// used when the authorization service needs to send custom responses to the
// downstream client or, to modify/add request headers being dispatched to the upstream.
message HttpResponse {
// Http status code.
uint32 status_code = 1 [(validate.rules).uint32 = {gte: 100, lt: 600}];

// Http entity headers.
map<string, string> headers = 2;

// Http entity body.
string body = 3;
}
// A field that supplies the attributes for an HTTP response.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

could u please add a comment about how this is not going to be used for TCP filters since the CheckResponse is used by both filters.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good call! I will do that.

HttpResponse http_response = 2;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The comment here needs to explain whether this affects the response to the downstream client, or upstream server.

It also needs to explain the relationship between this field and the status field. For example, can I set the status to "OK" and then also provide this field? What should Envoy do in that case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point too! I will add comments to explain the difference and when to use them.

}
1 change: 1 addition & 0 deletions source/extensions/filters/common/ext_authz/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ envoy_cc_library(
"//include/envoy/ssl:connection_interface",
"//include/envoy/upstream:cluster_manager_interface",
"//source/common/common:assert_lib",
"//source/common/buffer:buffer_lib",
"//source/common/grpc:async_client_lib",
"//source/common/http:headers_lib",
"//source/common/http:utility_lib",
Expand Down
22 changes: 20 additions & 2 deletions source/extensions/filters/common/ext_authz/ext_authz.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ enum class CheckStatus {
Denied
};

typedef std::vector<std::pair<Http::LowerCaseString, std::string>> KeyValueHeaders;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This could be refactored with TestMetadata to provide a standard type somewhere for header lists.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sorry, I'm not sure what is needed here. Would you mind to give a bit more context?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just introducing this type in a common header to dedupe it with other identical defs.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ok, I will do.


/**
* Authorization response object for a RequestCallback.
*/
struct Response {
// Call status.
CheckStatus status;
// Optional http headers attribute.
KeyValueHeaders headers;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think this could be HeaderMapPtr.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If it makes thing easier or more efficient, sure.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think both if possible to move it later. I'll need to take a closer look at the HeaderMap api though.

// Optional http body attribute.
Buffer::InstancePtr body;
// Extra http status code attribute.
uint32_t status_code;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: prefer to zero initialize this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

👍

};

typedef std::unique_ptr<Response> ResponsePtr;

/**
* Async callbacks used during check() calls.
*/
Expand All @@ -35,9 +53,9 @@ class RequestCallbacks {
virtual ~RequestCallbacks() {}

/**
* Called when a check request is complete. The resulting status is supplied.
* Called when a check request is complete. The resulting ResponsePtr is supplied.
*/
virtual void onComplete(CheckStatus status) PURE;
virtual void onComplete(ResponsePtr&& response) PURE;
};

class Client {
Expand Down
25 changes: 21 additions & 4 deletions source/extensions/filters/common/ext_authz/ext_authz_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "envoy/access_log/access_log.h"
#include "envoy/ssl/connection.h"

#include "common/buffer/buffer_impl.h"
#include "common/common/assert.h"
#include "common/grpc/async_client_impl.h"
#include "common/http/headers.h"
Expand Down Expand Up @@ -47,23 +48,39 @@ void GrpcClientImpl::check(RequestCallbacks& callbacks,

void GrpcClientImpl::onSuccess(
std::unique_ptr<envoy::service::auth::v2alpha::CheckResponse>&& response, Tracing::Span& span) {
CheckStatus status = CheckStatus::OK;

ASSERT(response->status().code() != Grpc::Status::GrpcStatus::Unknown);
ResponsePtr authz_response = std::make_unique<Response>();

if (response->has_http_response()) {
for (const auto& header : response->http_response().headers()) {
authz_response->headers.emplace_back(
std::make_pair(Http::LowerCaseString(header.first), header.second));
}
if (!response->http_response().body().empty()) {
authz_response->body = std::make_unique<Buffer::OwnedImpl>(response->http_response().body());
}
}

if (response->status().code() != Grpc::Status::GrpcStatus::Ok) {
status = CheckStatus::Denied;
span.setTag(Constants::get().TraceStatus, Constants::get().TraceUnauthz);
authz_response->status = CheckStatus::Denied;
authz_response->status_code = response->http_response().status_code();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

response may not have http_response ?

} else {
span.setTag(Constants::get().TraceStatus, Constants::get().TraceOk);
authz_response->status = CheckStatus::OK;
}

callbacks_->onComplete(status);
callbacks_->onComplete(std::move(authz_response));
callbacks_ = nullptr;
}

void GrpcClientImpl::onFailure(Grpc::Status::GrpcStatus status, const std::string&,
Tracing::Span&) {
ASSERT(status != Grpc::Status::GrpcStatus::Ok);
callbacks_->onComplete(CheckStatus::Error);
ResponsePtr response = std::make_unique<Response>();
response->status = CheckStatus::Error;
callbacks_->onComplete(std::move(response));
callbacks_ = nullptr;
}

Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/http/ext_authz/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ envoy_cc_library(
hdrs = ["ext_authz.h"],
deps = [
"//include/envoy/http:codes_interface",
"//source/common/buffer:buffer_lib",
"//source/common/common:assert_lib",
"//source/common/common:empty_string",
"//source/common/common:enum_to_int",
Expand Down
65 changes: 45 additions & 20 deletions source/extensions/filters/http/ext_authz/ext_authz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,11 @@ namespace HttpFilters {
namespace ExtAuthz {

namespace {

const Http::HeaderMap* getDeniedHeader() {
static const Http::HeaderMap* header_map = new Http::HeaderMapImpl{
{Http::Headers::get().Status, std::to_string(enumToInt(Http::Code::Forbidden))}};
return header_map;
}

} // namespace

void Filter::initiateCall(const Http::HeaderMap& headers) {
Expand All @@ -52,6 +50,7 @@ void Filter::initiateCall(const Http::HeaderMap& headers) {
}

Http::FilterHeadersStatus Filter::decodeHeaders(Http::HeaderMap& headers, bool) {
request_headers_ = &headers;
initiateCall(headers);
return state_ == State::Calling ? Http::FilterHeadersStatus::StopIteration
: Http::FilterHeadersStatus::Continue;
Expand All @@ -78,14 +77,16 @@ void Filter::onDestroy() {
}
}

void Filter::onComplete(Filters::Common::ExtAuthz::CheckStatus status) {
void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) {
ASSERT(cluster_);

state_ = State::Complete;

using Filters::Common::ExtAuthz::CheckStatus;

switch (status) {
const bool update_status_code = response->status_code >= enumToInt(Http::Code::Continue) &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not sure I follow the logic here, can you add some comments?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

may be save the specific code we do want to use and base rest of the logic off it? (in case u think that will help)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed! That's super confusing and, I definitely change it. The idea is that when the authz service defines a new status code for "reject/denied", this logic will checks whether the code is valid and not equal to 403. If that evaluates to true, set a custom Status Code header and ResponseStatInfo. I will simplify this.

response->status_code != enumToInt(Http::Code::Forbidden);

switch (response->status) {
case CheckStatus::OK:
cluster_->statsScope().counter("ext_authz.ok").inc();
break;
Expand All @@ -94,35 +95,59 @@ void Filter::onComplete(Filters::Common::ExtAuthz::CheckStatus status) {
break;
case CheckStatus::Denied:
cluster_->statsScope().counter("ext_authz.denied").inc();
Http::CodeUtility::ResponseStatInfo info{config_->scope(),
cluster_->statsScope(),
EMPTY_STRING,
enumToInt(Http::Code::Forbidden),
true,
EMPTY_STRING,
EMPTY_STRING,
EMPTY_STRING,
EMPTY_STRING,
false};
Http::CodeUtility::ResponseStatInfo info{
config_->scope(),
cluster_->statsScope(),
EMPTY_STRING,
(update_status_code ? response->status_code : enumToInt(Http::Code::Forbidden)),
true,
EMPTY_STRING,
EMPTY_STRING,
EMPTY_STRING,
EMPTY_STRING,
false};
Http::CodeUtility::chargeResponseStat(info);
break;
}

// We fail open/fail close based of filter config
// if there is an error contacting the service.
if (status == CheckStatus::Denied ||
(status == CheckStatus::Error && !config_->failureModeAllow())) {
Http::HeaderMapPtr response_headers{new Http::HeaderMapImpl(*getDeniedHeader())};
callbacks_->encodeHeaders(std::move(response_headers), true);
if (response->status == CheckStatus::Denied ||
(response->status == CheckStatus::Error && !config_->failureModeAllow())) {
Http::HeaderMapPtr response_headers;
if (update_status_code) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This logic is a bit hard to follow due to the multiple reasons we can get here combined with the above code. Comments and/or restructure would be appreciated.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

See my comment above. I will simplify this.

response_headers = std::make_unique<Http::HeaderMapImpl>(Http::HeaderMapImpl{
{Http::Headers::get().Status, std::to_string(response->status_code)}});
} else {
response_headers = std::make_unique<Http::HeaderMapImpl>(*getDeniedHeader());
}

for (const auto& header : response->headers) {
response_headers->setReferenceKey(header.first, header.second);
}

if (response->body) {
callbacks_->encodeHeaders(std::move(response_headers), false);
callbacks_->encodeData(*response->body.get(), true);
} else {
callbacks_->encodeHeaders(std::move(response_headers), true);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

may be wrapping the encoding of response headers and body into a function would help with parsing of the logic here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think so too. I will try to simplify this logic, but first I will make sure that we have tests for all the permutations.


callbacks_->requestInfo().setResponseFlag(
RequestInfo::ResponseFlag::UnauthorizedExternalService);
} else {
if (config_->failureModeAllow() && status == CheckStatus::Error) {
if (config_->failureModeAllow() && response->status == CheckStatus::Error) {
// Status is Error and yet we are allowing the request. Click a counter.
cluster_->statsScope().counter("ext_authz.failure_mode_allowed").inc();
}

// We can get completion inline, so only call continue if that isn't happening.
if (!initiating_call_) {
if (response->status == CheckStatus::OK) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The conditional check and addition of headers should be outside the check for initiating_call_.
We want to add those headers whether the callback is invoked on the stack or async.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oh, I see. Thanks!

for (const auto& header : response->headers) {
request_headers_->setReferenceKey(header.first, header.second);
}
}
callbacks_->continueDecoding();
}
}
Expand Down
3 changes: 2 additions & 1 deletion source/extensions/filters/http/ext_authz/ext_authz.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class Filter : public Http::StreamDecoderFilter,
void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override;

// ExtAuthz::RequestCallbacks
void onComplete(Filters::Common::ExtAuthz::CheckStatus status) override;
void onComplete(Filters::Common::ExtAuthz::ResponsePtr&&) override;

private:
enum class State { NotStarted, Calling, Complete };
Expand All @@ -86,6 +86,7 @@ class Filter : public Http::StreamDecoderFilter,
FilterConfigSharedPtr config_;
Filters::Common::ExtAuthz::ClientPtr client_;
Http::StreamDecoderFilterCallbacks* callbacks_{};
Http::HeaderMap* request_headers_;
State state_{State::NotStarted};
Upstream::ClusterInfoConstSharedPtr cluster_;
bool initiating_call_{};
Expand Down
12 changes: 7 additions & 5 deletions source/extensions/filters/network/ext_authz/ext_authz.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ void Filter::onEvent(Network::ConnectionEvent event) {
}
}

void Filter::onComplete(Filters::Common::ExtAuthz::CheckStatus status) {
void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) {
status_ = Status::Complete;
config_->stats().active_.dec();

switch (status) {
switch (response->status) {
case Filters::Common::ExtAuthz::CheckStatus::OK:
config_->stats().ok_.inc();
break;
Expand All @@ -73,12 +73,14 @@ void Filter::onComplete(Filters::Common::ExtAuthz::CheckStatus status) {
}

// Fail open only if configured to do so and if the check status was a error.
if (status == Filters::Common::ExtAuthz::CheckStatus::Denied ||
(status == Filters::Common::ExtAuthz::CheckStatus::Error && !config_->failureModeAllow())) {
if (response->status == Filters::Common::ExtAuthz::CheckStatus::Denied ||
(response->status == Filters::Common::ExtAuthz::CheckStatus::Error &&
!config_->failureModeAllow())) {
config_->stats().cx_closed_.inc();
filter_callbacks_->connection().close(Network::ConnectionCloseType::NoFlush);
} else {
if (config_->failureModeAllow() && status == Filters::Common::ExtAuthz::CheckStatus::Error) {
if (config_->failureModeAllow() &&
response->status == Filters::Common::ExtAuthz::CheckStatus::Error) {
// Status is Error and yet we are configured to allow traffic. Click a counter.
config_->stats().failure_mode_allowed_.inc();
}
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/filters/network/ext_authz/ext_authz.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class Filter : public Network::ReadFilter,
void onBelowWriteBufferLowWatermark() override {}

// ExtAuthz::RequestCallbacks
void onComplete(Filters::Common::ExtAuthz::CheckStatus status) override;
void onComplete(Filters::Common::ExtAuthz::ResponsePtr&&) override;

private:
enum class Status { NotStarted, Calling, Complete };
Expand Down
10 changes: 6 additions & 4 deletions test/extensions/filters/common/ext_authz/ext_authz_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ namespace ExtAuthz {

class MockRequestCallbacks : public RequestCallbacks {
public:
MOCK_METHOD1(onComplete, void(CheckStatus status));
void onComplete(ResponsePtr&& response) override { onComplete_(response); }

MOCK_METHOD1(onComplete_, void(ResponsePtr& response));
};

class ExtAuthzGrpcClientTest : public testing::Test {
Expand Down Expand Up @@ -68,7 +70,7 @@ TEST_F(ExtAuthzGrpcClientTest, BasicOK) {
auto status = response->mutable_status();
status->set_code(Grpc::Status::GrpcStatus::Ok);
EXPECT_CALL(span_, setTag("ext_authz_status", "ext_authz_ok"));
EXPECT_CALL(request_callbacks_, onComplete(CheckStatus::OK));
EXPECT_CALL(request_callbacks_, onComplete_(_));
client_.onSuccess(std::move(response), span_);
}

Expand Down Expand Up @@ -98,7 +100,7 @@ TEST_F(ExtAuthzGrpcClientTest, BasicDenied) {
auto status = response->mutable_status();
status->set_code(Grpc::Status::GrpcStatus::PermissionDenied);
EXPECT_CALL(span_, setTag("ext_authz_status", "ext_authz_unauthorized"));
EXPECT_CALL(request_callbacks_, onComplete(CheckStatus::Denied));
EXPECT_CALL(request_callbacks_, onComplete_(_));
client_.onSuccess(std::move(response), span_);
}

Expand All @@ -108,7 +110,7 @@ TEST_F(ExtAuthzGrpcClientTest, BasicError) {

client_.check(request_callbacks_, request, Tracing::NullSpan::instance());

EXPECT_CALL(request_callbacks_, onComplete(CheckStatus::Error));
EXPECT_CALL(request_callbacks_, onComplete_(_));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should be checking the Response's CheckStatus in this file.

@gsagula gsagula Apr 25, 2018

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I will address the tests after all the design changes.

client_.onFailure(Grpc::Status::Unknown, "", span_);
}

Expand Down
Loading