Skip to content

Commit ee9d28a

Browse files
authored
instrumentation/grpc: Testing for gRPC Client Interceptor (#896)
1 parent 2a952b3 commit ee9d28a

File tree

10 files changed

+775
-8
lines changed

10 files changed

+775
-8
lines changed

ext/opentelemetry-ext-grpc/CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Add status code to gRPC client spans
6+
([896](https://github.com/open-telemetry/opentelemetry-python/pull/896))
7+
58
## 0.8b0
69

710
Released 2020-05-27

ext/opentelemetry-ext-grpc/setup.cfg

+1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ install_requires =
4747
test =
4848
opentelemetry-test == 0.11.dev0
4949
opentelemetry-sdk == 0.11.dev0
50+
protobuf == 3.12.2
5051

5152
[options.packages.find]
5253
where = src

ext/opentelemetry-ext-grpc/src/opentelemetry/ext/grpc/_client.py

+33-7
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import grpc
2626

2727
from opentelemetry import propagators, trace
28+
from opentelemetry.trace.status import Status, StatusCanonicalCode
2829

2930
from . import grpcext
3031
from ._utilities import RpcInfo
@@ -33,14 +34,16 @@
3334
class _GuardedSpan:
3435
def __init__(self, span):
3536
self.span = span
37+
self.generated_span = None
3638
self._engaged = True
3739

3840
def __enter__(self):
39-
self.span.__enter__()
41+
self.generated_span = self.span.__enter__()
4042
return self
4143

4244
def __exit__(self, *args, **kwargs):
4345
if self._engaged:
46+
self.generated_span = None
4447
return self.span.__exit__(*args, **kwargs)
4548
return False
4649

@@ -122,7 +125,15 @@ def intercept_unary(self, request, metadata, client_info, invoker):
122125
timeout=client_info.timeout,
123126
request=request,
124127
)
125-
result = invoker(request, metadata)
128+
129+
try:
130+
result = invoker(request, metadata)
131+
except grpc.RpcError as exc:
132+
guarded_span.generated_span.set_status(
133+
Status(StatusCanonicalCode(exc.code().value[0]))
134+
)
135+
raise
136+
126137
return self._trace_result(guarded_span, rpc_info, result)
127138

128139
# For RPCs that stream responses, the result can be a generator. To record
@@ -136,7 +147,7 @@ def _intercept_server_stream(
136147
else:
137148
mutable_metadata = OrderedDict(metadata)
138149

139-
with self._start_span(client_info.full_method):
150+
with self._start_span(client_info.full_method) as span:
140151
_inject_span_context(mutable_metadata)
141152
metadata = tuple(mutable_metadata.items())
142153
rpc_info = RpcInfo(
@@ -146,9 +157,16 @@ def _intercept_server_stream(
146157
)
147158
if client_info.is_client_stream:
148159
rpc_info.request = request_or_iterator
149-
result = invoker(request_or_iterator, metadata)
150-
for response in result:
151-
yield response
160+
161+
try:
162+
result = invoker(request_or_iterator, metadata)
163+
for response in result:
164+
yield response
165+
except grpc.RpcError as exc:
166+
span.set_status(
167+
Status(StatusCanonicalCode(exc.code().value[0]))
168+
)
169+
raise
152170

153171
def intercept_stream(
154172
self, request_or_iterator, metadata, client_info, invoker
@@ -172,5 +190,13 @@ def intercept_stream(
172190
timeout=client_info.timeout,
173191
request=request_or_iterator,
174192
)
175-
result = invoker(request_or_iterator, metadata)
193+
194+
try:
195+
result = invoker(request_or_iterator, metadata)
196+
except grpc.RpcError as exc:
197+
guarded_span.generated_span.set_status(
198+
Status(StatusCanonicalCode(exc.code().value[0]))
199+
)
200+
raise
201+
176202
return self._trace_result(guarded_span, rpc_info, result)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from .protobuf.test_server_pb2 import Request
16+
17+
CLIENT_ID = 1
18+
19+
20+
def simple_method(stub, error=False):
21+
request = Request(
22+
client_id=CLIENT_ID, request_data="error" if error else "data"
23+
)
24+
stub.SimpleMethod(request)
25+
26+
27+
def client_streaming_method(stub, error=False):
28+
# create a generator
29+
def request_messages():
30+
for _ in range(5):
31+
request = Request(
32+
client_id=CLIENT_ID, request_data="error" if error else "data"
33+
)
34+
yield request
35+
36+
stub.ClientStreamingMethod(request_messages())
37+
38+
39+
def server_streaming_method(stub, error=False):
40+
request = Request(
41+
client_id=CLIENT_ID, request_data="error" if error else "data"
42+
)
43+
response_iterator = stub.ServerStreamingMethod(request)
44+
list(response_iterator)
45+
46+
47+
def bidirectional_streaming_method(stub, error=False):
48+
def request_messages():
49+
for _ in range(5):
50+
request = Request(
51+
client_id=CLIENT_ID, request_data="error" if error else "data"
52+
)
53+
yield request
54+
55+
response_iterator = stub.BidirectionalStreamingMethod(request_messages())
56+
57+
list(response_iterator)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Copyright The OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from concurrent import futures
16+
17+
import grpc
18+
19+
from .protobuf import test_server_pb2, test_server_pb2_grpc
20+
21+
SERVER_ID = 1
22+
23+
24+
class TestServer(test_server_pb2_grpc.GRPCTestServerServicer):
25+
def SimpleMethod(self, request, context):
26+
if request.request_data == "error":
27+
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
28+
return test_server_pb2.Response()
29+
response = test_server_pb2.Response(
30+
server_id=SERVER_ID, response_data="data"
31+
)
32+
return response
33+
34+
def ClientStreamingMethod(self, request_iterator, context):
35+
data = list(request_iterator)
36+
if data[0].request_data == "error":
37+
context.set_code(grpc.StatusCode.INVALID_ARGUMENT)
38+
return test_server_pb2.Response()
39+
response = test_server_pb2.Response(
40+
server_id=SERVER_ID, response_data="data"
41+
)
42+
return response
43+
44+
def ServerStreamingMethod(self, request, context):
45+
if request.request_data == "error":
46+
47+
context.abort(
48+
code=grpc.StatusCode.INVALID_ARGUMENT,
49+
details="server stream error",
50+
)
51+
return test_server_pb2.Response()
52+
53+
# create a generator
54+
def response_messages():
55+
for _ in range(5):
56+
response = test_server_pb2.Response(
57+
server_id=SERVER_ID, response_data="data"
58+
)
59+
yield response
60+
61+
return response_messages()
62+
63+
def BidirectionalStreamingMethod(self, request_iterator, context):
64+
data = list(request_iterator)
65+
if data[0].request_data == "error":
66+
context.abort(
67+
code=grpc.StatusCode.INVALID_ARGUMENT,
68+
details="bidirectional error",
69+
)
70+
return
71+
72+
for _ in range(5):
73+
yield test_server_pb2.Response(
74+
server_id=SERVER_ID, response_data="data"
75+
)
76+
77+
78+
def create_test_server(port):
79+
server = grpc.server(futures.ThreadPoolExecutor(max_workers=1))
80+
81+
test_server_pb2_grpc.add_GRPCTestServerServicer_to_server(
82+
TestServer(), server
83+
)
84+
85+
server.add_insecure_port("localhost:{}".format(port))
86+
87+
return server
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright 2019 gRPC authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
syntax = "proto3";
15+
16+
message Request {
17+
int64 client_id = 1;
18+
string request_data = 2;
19+
}
20+
21+
message Response {
22+
int64 server_id = 1;
23+
string response_data = 2;
24+
}
25+
26+
service GRPCTestServer {
27+
rpc SimpleMethod (Request) returns (Response);
28+
29+
rpc ClientStreamingMethod (stream Request) returns (Response);
30+
31+
rpc ServerStreamingMethod (Request) returns (stream Response);
32+
33+
rpc BidirectionalStreamingMethod (stream Request) returns (stream Response);
34+
}

0 commit comments

Comments
 (0)