grpc 1.31.1-x86-linux → 1.35.0.pre1-x86-linux

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of grpc might be problematic. Click here for more details.

@@ -110,6 +110,8 @@ module Grpc
110
110
 
111
111
  # Gets the backend distribution for RPCs sent by a test client.
112
112
  rpc :GetClientStats, ::Grpc::Testing::LoadBalancerStatsRequest, ::Grpc::Testing::LoadBalancerStatsResponse
113
+ # Gets the accumulated stats for RPCs sent by a test client.
114
+ rpc :GetClientAccumulatedStats, ::Grpc::Testing::LoadBalancerAccumulatedStatsRequest, ::Grpc::Testing::LoadBalancerAccumulatedStatsResponse
113
115
  end
114
116
 
115
117
  Stub = Service.rpc_stub_class
@@ -128,6 +130,22 @@ module Grpc
128
130
  rpc :SetNotServing, ::Grpc::Testing::Empty, ::Grpc::Testing::Empty
129
131
  end
130
132
 
133
+ Stub = Service.rpc_stub_class
134
+ end
135
+ module XdsUpdateClientConfigureService
136
+ # A service to dynamically update the configuration of an xDS test client.
137
+ class Service
138
+
139
+ include GRPC::GenericService
140
+
141
+ self.marshal_class_method = :encode
142
+ self.unmarshal_class_method = :decode
143
+ self.service_name = 'grpc.testing.XdsUpdateClientConfigureService'
144
+
145
+ # Update the tes client's configuration.
146
+ rpc :Configure, ::Grpc::Testing::ClientConfigureRequest, ::Grpc::Testing::ClientConfigureResponse
147
+ end
148
+
131
149
  Stub = Service.rpc_stub_class
132
150
  end
133
151
  end
@@ -39,11 +39,38 @@ require_relative '../src/proto/grpc/testing/empty_pb'
39
39
  require_relative '../src/proto/grpc/testing/messages_pb'
40
40
  require_relative '../src/proto/grpc/testing/test_services_pb'
41
41
 
42
+ class RpcConfig
43
+ def init(rpcs_to_send, metadata_to_send)
44
+ @rpcs_to_send = rpcs_to_send
45
+ @metadata_to_send = metadata_to_send
46
+ end
47
+ def rpcs_to_send
48
+ @rpcs_to_send
49
+ end
50
+ def metadata_to_send
51
+ @metadata_to_send
52
+ end
53
+ end
54
+
55
+ # Some global constant mappings
56
+ $RPC_MAP = {
57
+ 'UnaryCall' => :UNARY_CALL,
58
+ 'EmptyCall' => :EMPTY_CALL,
59
+ }
60
+
42
61
  # Some global variables to be shared by server and client
43
62
  $watchers = Array.new
44
63
  $watchers_mutex = Mutex.new
45
64
  $watchers_cv = ConditionVariable.new
46
65
  $shutdown = false
66
+ # These can be configured by the test runner dynamically
67
+ $rpc_config = RpcConfig.new
68
+ $rpc_config.init([:UNARY_CALL], {})
69
+ # These stats are shared across threads
70
+ $accumulated_stats_mu = Mutex.new
71
+ $num_rpcs_started_by_method = {}
72
+ $num_rpcs_succeeded_by_method = {}
73
+ $num_rpcs_failed_by_method = {}
47
74
 
48
75
  # RubyLogger defines a logger for gRPC based on the standard ruby logger.
49
76
  module RubyLogger
@@ -71,6 +98,31 @@ def create_stub(opts)
71
98
  )
72
99
  end
73
100
 
101
+ class ConfigureTarget < Grpc::Testing::XdsUpdateClientConfigureService::Service
102
+ include Grpc::Testing
103
+
104
+ def configure(req, _call)
105
+ rpcs_to_send = req['types'];
106
+ metadata_to_send = {}
107
+ req['metadata'].each do |m|
108
+ rpc = m.type
109
+ if !metadata_to_send.key?(rpc)
110
+ metadata_to_send[rpc] = {}
111
+ end
112
+ metadata_key = m.key
113
+ metadata_value = m.value
114
+ metadata_to_send[rpc][metadata_key] = metadata_value
115
+ end
116
+ GRPC.logger.info("Configuring new rpcs_to_send and metadata_to_send...")
117
+ GRPC.logger.info(rpcs_to_send)
118
+ GRPC.logger.info(metadata_to_send)
119
+ new_rpc_config = RpcConfig.new
120
+ new_rpc_config.init(rpcs_to_send, metadata_to_send)
121
+ $rpc_config = new_rpc_config
122
+ ClientConfigureResponse.new();
123
+ end
124
+ end
125
+
74
126
  # This implements LoadBalancerStatsService required by the test runner
75
127
  class TestTarget < Grpc::Testing::LoadBalancerStatsService::Service
76
128
  include Grpc::Testing
@@ -81,6 +133,7 @@ class TestTarget < Grpc::Testing::LoadBalancerStatsService::Service
81
133
  watcher = {}
82
134
  $watchers_mutex.synchronize do
83
135
  watcher = {
136
+ "rpcs_by_method" => Hash.new(),
84
137
  "rpcs_by_peer" => Hash.new(0),
85
138
  "rpcs_needed" => req['num_rpcs'],
86
139
  "no_remote_peer" => 0
@@ -95,60 +148,165 @@ class TestTarget < Grpc::Testing::LoadBalancerStatsService::Service
95
148
  end
96
149
  $watchers.delete_at($watchers.index(watcher))
97
150
  end
151
+ # convert results into proper proto object
152
+ rpcs_by_method = {}
153
+ watcher['rpcs_by_method'].each do |rpc_name, rpcs_by_peer|
154
+ rpcs_by_method[rpc_name] = LoadBalancerStatsResponse::RpcsByPeer.new(
155
+ rpcs_by_peer: rpcs_by_peer
156
+ )
157
+ end
98
158
  LoadBalancerStatsResponse.new(
159
+ rpcs_by_method: rpcs_by_method,
99
160
  rpcs_by_peer: watcher['rpcs_by_peer'],
100
161
  num_failures: watcher['no_remote_peer'] + watcher['rpcs_needed']
101
162
  );
102
163
  end
164
+
165
+ def get_client_accumulated_stats(req, _call)
166
+ $accumulated_stats_mu.synchronize do
167
+ LoadBalancerAccumulatedStatsResponse.new(
168
+ num_rpcs_started_by_method: $num_rpcs_started_by_method,
169
+ num_rpcs_succeeded_by_method: $num_rpcs_succeeded_by_method,
170
+ num_rpcs_failed_by_method: $num_rpcs_failed_by_method
171
+ )
172
+ end
173
+ end
174
+ end
175
+
176
+ # execute 1 RPC and return remote hostname
177
+ def execute_rpc(op, fail_on_failed_rpcs, rpc_stats_key)
178
+ remote_peer = ""
179
+ begin
180
+ op.execute
181
+ if op.metadata.key?('hostname')
182
+ remote_peer = op.metadata['hostname']
183
+ end
184
+ rescue GRPC::BadStatus => e
185
+ if fail_on_failed_rpcs
186
+ raise e
187
+ end
188
+ end
189
+ $accumulated_stats_mu.synchronize do
190
+ if remote_peer.empty?
191
+ $num_rpcs_failed_by_method[rpc_stats_key] += 1
192
+ else
193
+ $num_rpcs_succeeded_by_method[rpc_stats_key] += 1
194
+ end
195
+ end
196
+ remote_peer
197
+ end
198
+
199
+ def execute_rpc_in_thread(op, rpc_stats_key)
200
+ Thread.new {
201
+ begin
202
+ op.execute
203
+ # The following should _not_ happen with the current spec
204
+ # because we are only executing RPCs in a thread if we expect it
205
+ # to be kept open, or deadline_exceeded, or dropped by the load
206
+ # balancing policy. These RPCs should not complete successfully.
207
+ # Doing this for consistency
208
+ $accumulated_stats_mu.synchronize do
209
+ $num_rpcs_succeeded_by_method[rpc_stats_key] += 1
210
+ end
211
+ rescue GRPC::BadStatus => e
212
+ # Normal execution arrives here,
213
+ # either because of deadline_exceeded or "call dropped by load
214
+ # balancing policy"
215
+ $accumulated_stats_mu.synchronize do
216
+ $num_rpcs_failed_by_method[rpc_stats_key] += 1
217
+ end
218
+ end
219
+ }
103
220
  end
104
221
 
105
222
  # send 1 rpc every 1/qps second
106
223
  def run_test_loop(stub, target_seconds_between_rpcs, fail_on_failed_rpcs)
107
224
  include Grpc::Testing
108
- req = SimpleRequest.new()
225
+ simple_req = SimpleRequest.new()
226
+ empty_req = Empty.new()
109
227
  target_next_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
228
+ # Some RPCs are meant to be "kept open". Since Ruby does not have an
229
+ # async API, we are executing those RPCs in a thread so that they don't
230
+ # block.
231
+ keep_open_threads = Array.new
110
232
  while !$shutdown
111
233
  now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
112
234
  sleep_seconds = target_next_start - now
113
235
  if sleep_seconds < 0
114
236
  target_next_start = now + target_seconds_between_rpcs
115
- GRPC.logger.info(
116
- "ruby xds: warning, rpc takes too long to finish. " \
117
- "Deficit = %.1fms. " \
118
- "If you consistently see this, the qps is too high." \
119
- % [(sleep_seconds * 1000).abs().round(1)])
120
237
  else
121
238
  target_next_start += target_seconds_between_rpcs
122
239
  sleep(sleep_seconds)
123
240
  end
124
- begin
125
- deadline = GRPC::Core::TimeConsts::from_relative_time(30) # 30 seconds
126
- resp = stub.unary_call(req, deadline: deadline)
127
- remote_peer = resp.hostname
128
- rescue GRPC::BadStatus => e
129
- remote_peer = ""
130
- GRPC.logger.info("ruby xds: rpc failed:|#{e.message}|, " \
131
- "this may or may not be expected")
132
- if fail_on_failed_rpcs
133
- raise e
241
+ deadline = GRPC::Core::TimeConsts::from_relative_time(30) # 30 seconds
242
+ results = {}
243
+ $rpc_config.rpcs_to_send.each do |rpc|
244
+ # rpc is in the form of :UNARY_CALL or :EMPTY_CALL here
245
+ metadata = $rpc_config.metadata_to_send.key?(rpc) ?
246
+ $rpc_config.metadata_to_send[rpc] : {}
247
+ $accumulated_stats_mu.synchronize do
248
+ $num_rpcs_started_by_method[rpc.to_s] += 1
249
+ num_started = $num_rpcs_started_by_method[rpc.to_s]
250
+ if num_started % 100 == 0
251
+ GRPC.logger.info("Started #{num_started} of #{rpc}")
252
+ end
253
+ end
254
+ if rpc == :UNARY_CALL
255
+ op = stub.unary_call(simple_req,
256
+ metadata: metadata,
257
+ deadline: deadline,
258
+ return_op: true)
259
+ elsif rpc == :EMPTY_CALL
260
+ op = stub.empty_call(empty_req,
261
+ metadata: metadata,
262
+ deadline: deadline,
263
+ return_op: true)
264
+ else
265
+ raise "Unsupported rpc #{rpc}"
266
+ end
267
+ rpc_stats_key = rpc.to_s
268
+ if metadata.key?('rpc-behavior') and
269
+ (metadata['rpc-behavior'] == 'keep-open')
270
+ num_open_threads = keep_open_threads.size
271
+ if num_open_threads % 50 == 0
272
+ GRPC.logger.info("number of keep_open_threads = #{num_open_threads}")
273
+ end
274
+ keep_open_threads << execute_rpc_in_thread(op, rpc_stats_key)
275
+ else
276
+ results[rpc] = execute_rpc(op, fail_on_failed_rpcs, rpc_stats_key)
134
277
  end
135
278
  end
136
279
  $watchers_mutex.synchronize do
137
280
  $watchers.each do |watcher|
281
+ # this is counted once when each group of all rpcs_to_send were done
138
282
  watcher['rpcs_needed'] -= 1
139
- if remote_peer.strip.empty?
140
- watcher['no_remote_peer'] += 1
141
- else
142
- watcher['rpcs_by_peer'][remote_peer] += 1
283
+ results.each do |rpc_name, remote_peer|
284
+ # These stats expect rpc_name to be in the form of
285
+ # UnaryCall or EmptyCall, not the underscore-case all-caps form
286
+ rpc_name = $RPC_MAP.invert()[rpc_name]
287
+ if remote_peer.strip.empty?
288
+ # error is counted per individual RPC
289
+ watcher['no_remote_peer'] += 1
290
+ else
291
+ if not watcher['rpcs_by_method'].key?(rpc_name)
292
+ watcher['rpcs_by_method'][rpc_name] = Hash.new(0)
293
+ end
294
+ # increment the remote hostname distribution histogram
295
+ # both by overall, and broken down per RPC
296
+ watcher['rpcs_by_method'][rpc_name][remote_peer] += 1
297
+ watcher['rpcs_by_peer'][remote_peer] += 1
298
+ end
143
299
  end
144
300
  end
145
301
  $watchers_cv.broadcast
146
302
  end
147
303
  end
304
+ keep_open_threads.each { |thd| thd.join }
148
305
  end
149
306
 
150
307
  # Args is used to hold the command line info.
151
308
  Args = Struct.new(:fail_on_failed_rpcs, :num_channels,
309
+ :rpc, :metadata,
152
310
  :server, :stats_port, :qps)
153
311
 
154
312
  # validates the command line options, returning them as a Hash.
@@ -156,6 +314,8 @@ def parse_args
156
314
  args = Args.new
157
315
  args['fail_on_failed_rpcs'] = false
158
316
  args['num_channels'] = 1
317
+ args['rpc'] = 'UnaryCall'
318
+ args['metadata'] = ''
159
319
  OptionParser.new do |opts|
160
320
  opts.on('--fail_on_failed_rpcs BOOL', ['false', 'true']) do |v|
161
321
  args['fail_on_failed_rpcs'] = v == 'true'
@@ -163,6 +323,12 @@ def parse_args
163
323
  opts.on('--num_channels CHANNELS', 'number of channels') do |v|
164
324
  args['num_channels'] = v.to_i
165
325
  end
326
+ opts.on('--rpc RPCS_TO_SEND', 'list of RPCs to send') do |v|
327
+ args['rpc'] = v
328
+ end
329
+ opts.on('--metadata METADATA_TO_SEND', 'metadata to send per RPC') do |v|
330
+ args['metadata'] = v
331
+ end
166
332
  opts.on('--server SERVER_HOST', 'server hostname') do |v|
167
333
  GRPC.logger.info("ruby xds: server address is #{v}")
168
334
  args['server'] = v
@@ -187,14 +353,57 @@ def main
187
353
  s = GRPC::RpcServer.new
188
354
  s.add_http2_port(host, :this_port_is_insecure)
189
355
  s.handle(TestTarget)
356
+ s.handle(ConfigureTarget)
190
357
  server_thread = Thread.new {
191
358
  # run the server until the main test runner terminates this process
192
359
  s.run_till_terminated_or_interrupted(['TERM'])
193
360
  }
194
361
 
195
- # The client just sends unary rpcs continuously in a regular interval
362
+ # Initialize stats
363
+ $RPC_MAP.values.each do |rpc|
364
+ $num_rpcs_started_by_method[rpc.to_s] = 0
365
+ $num_rpcs_succeeded_by_method[rpc.to_s] = 0
366
+ $num_rpcs_failed_by_method[rpc.to_s] = 0
367
+ end
368
+
369
+ # The client just sends rpcs continuously in a regular interval
196
370
  stub = create_stub(opts)
197
371
  target_seconds_between_rpcs = (1.0 / opts['qps'].to_f)
372
+ # Convert 'metadata' input in the form of
373
+ # rpc1:k1:v1,rpc2:k2:v2,rpc1:k3:v3
374
+ # into
375
+ # {
376
+ # 'rpc1' => {
377
+ # 'k1' => 'v1',
378
+ # 'k3' => 'v3',
379
+ # },
380
+ # 'rpc2' => {
381
+ # 'k2' => 'v2'
382
+ # },
383
+ # }
384
+ rpcs_to_send = []
385
+ metadata_to_send = {}
386
+ if opts['metadata']
387
+ metadata_entries = opts['metadata'].split(',')
388
+ metadata_entries.each do |e|
389
+ (rpc_name, metadata_key, metadata_value) = e.split(':')
390
+ rpc_name = $RPC_MAP[rpc_name]
391
+ # initialize if we haven't seen this rpc_name yet
392
+ if !metadata_to_send.key?(rpc_name)
393
+ metadata_to_send[rpc_name] = {}
394
+ end
395
+ metadata_to_send[rpc_name][metadata_key] = metadata_value
396
+ end
397
+ end
398
+ if opts['rpc']
399
+ rpcs_to_send = opts['rpc'].split(',')
400
+ end
401
+ if rpcs_to_send.size > 0
402
+ rpcs_to_send.map! { |rpc| $RPC_MAP[rpc] }
403
+ new_rpc_config = RpcConfig.new
404
+ new_rpc_config.init(rpcs_to_send, metadata_to_send)
405
+ $rpc_config = new_rpc_config
406
+ end
198
407
  client_threads = Array.new
199
408
  opts['num_channels'].times {
200
409
  client_threads << Thread.new {
@@ -50,6 +50,16 @@ describe GRPC::Core::ChannelCredentials do
50
50
  blk = proc { ChannelCredentials.new(nil) }
51
51
  expect(&blk).not_to raise_error
52
52
  end
53
+
54
+ it 'fails gracefully with constructed with a nil private key' do
55
+ blk = proc { GRPC::Core::ChannelCredentials.new(nil, nil, '') }
56
+ expect(&blk).to raise_error
57
+ end
58
+
59
+ it 'fails gracefully with constructed with a nil cert chain' do
60
+ blk = proc { GRPC::Core::ChannelCredentials.new(nil, '', nil) }
61
+ expect(&blk).to raise_error
62
+ end
53
63
  end
54
64
 
55
65
  describe '#compose' do
@@ -43,6 +43,16 @@ describe GRPC::ActiveCall do
43
43
  @server = new_core_server_for_testing(nil)
44
44
  server_port = @server.add_http2_port(host, :this_port_is_insecure)
45
45
  @server.start
46
+ @received_rpcs_queue = Queue.new
47
+ @server_thread = Thread.new do
48
+ begin
49
+ received_rpc = @server.request_call
50
+ rescue GRPC::Core::CallError, StandardError => e
51
+ # enqueue the exception in this case as a way to indicate the error
52
+ received_rpc = e
53
+ end
54
+ @received_rpcs_queue.push(received_rpc)
55
+ end
46
56
  @ch = GRPC::Core::Channel.new("0.0.0.0:#{server_port}", nil,
47
57
  :this_channel_is_insecure)
48
58
  end
@@ -50,6 +60,7 @@ describe GRPC::ActiveCall do
50
60
  after(:each) do
51
61
  @server.shutdown_and_notify(deadline)
52
62
  @server.close
63
+ @server_thread.join
53
64
  end
54
65
 
55
66
  describe 'restricted view methods' do
@@ -105,7 +116,7 @@ describe GRPC::ActiveCall do
105
116
  client_call.remote_send(msg)
106
117
 
107
118
  # check that server rpc new was received
108
- recvd_rpc = @server.request_call
119
+ recvd_rpc = @received_rpcs_queue.pop
109
120
  expect(recvd_rpc).to_not eq nil
110
121
  recvd_call = recvd_rpc.call
111
122
 
@@ -130,7 +141,7 @@ describe GRPC::ActiveCall do
130
141
  client_call.remote_send(msg)
131
142
 
132
143
  # confirm that the message was marshalled
133
- recvd_rpc = @server.request_call
144
+ recvd_rpc = @received_rpcs_queue.pop
134
145
  recvd_call = recvd_rpc.call
135
146
  server_ops = {
136
147
  CallOps::SEND_INITIAL_METADATA => nil
@@ -160,7 +171,7 @@ describe GRPC::ActiveCall do
160
171
  call.run_batch(CallOps::SEND_CLOSE_FROM_CLIENT => nil) if f == 1
161
172
 
162
173
  # confirm that the message was marshalled
163
- recvd_rpc = @server.request_call
174
+ recvd_rpc = @received_rpcs_queue.pop
164
175
  recvd_call = recvd_rpc.call
165
176
  server_ops = {
166
177
  CallOps::SEND_INITIAL_METADATA => nil
@@ -321,7 +332,7 @@ describe GRPC::ActiveCall do
321
332
  call = make_test_call
322
333
  metadata = { k1: 'v1', k2: 'v2' }
323
334
  ActiveCall.client_invoke(call, metadata)
324
- recvd_rpc = @server.request_call
335
+ recvd_rpc = @received_rpcs_queue.pop
325
336
  recvd_call = recvd_rpc.call
326
337
  expect(recvd_call).to_not be_nil
327
338
  expect(recvd_rpc.metadata).to_not be_nil
@@ -339,7 +350,7 @@ describe GRPC::ActiveCall do
339
350
  call = make_test_call
340
351
  ActiveCall.client_invoke(call)
341
352
 
342
- recvd_rpc = @server.request_call
353
+ recvd_rpc = @received_rpcs_queue.pop
343
354
  server_call = ActiveCall.new(
344
355
  recvd_rpc.call,
345
356
  @pass_through,
@@ -405,7 +416,7 @@ describe GRPC::ActiveCall do
405
416
  client_call = make_test_call
406
417
  ActiveCall.client_invoke(client_call)
407
418
 
408
- recvd_rpc = @server.request_call
419
+ recvd_rpc = @received_rpcs_queue.pop
409
420
  recvd_call = recvd_rpc.call
410
421
 
411
422
  server_call = ActiveCall.new(
@@ -575,7 +586,7 @@ describe GRPC::ActiveCall do
575
586
  @client_call = make_test_call
576
587
  @client_call.run_batch(CallOps::SEND_INITIAL_METADATA => {})
577
588
 
578
- recvd_rpc = @server.request_call
589
+ recvd_rpc = @received_rpcs_queue.pop
579
590
  recvd_call = recvd_rpc.call
580
591
  @server_call = ActiveCall.new(
581
592
  recvd_call,
@@ -654,7 +665,7 @@ describe GRPC::ActiveCall do
654
665
  end
655
666
 
656
667
  def expect_server_to_be_invoked(**kw)
657
- recvd_rpc = @server.request_call
668
+ recvd_rpc = @received_rpcs_queue.pop
658
669
  expect(recvd_rpc).to_not eq nil
659
670
  recvd_call = recvd_rpc.call
660
671
  recvd_call.run_batch(CallOps::SEND_INITIAL_METADATA => kw)