• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2015 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
15require 'spec_helper'
16
17include GRPC::Core
18
19shared_examples 'basic GRPC message delivery is OK' do
20  include GRPC::Core
21
22  context 'the test channel' do
23    it 'should have a target' do
24      expect(@ch.target).to be_a(String)
25    end
26  end
27
28  it 'unary calls work' do
29    run_services_on_server(@server, services: [EchoService]) do
30      call = @stub.an_rpc(EchoMsg.new, return_op: true)
31      expect(call.execute).to be_a(EchoMsg)
32    end
33  end
34
35  it 'unary calls work when enabling compression' do
36    run_services_on_server(@server, services: [EchoService]) do
37      long_request_str = '0' * 2000
38      md = { 'grpc-internal-encoding-request' => 'gzip' }
39      call = @stub.an_rpc(EchoMsg.new(msg: long_request_str),
40                          return_op: true,
41                          metadata: md)
42      response = call.execute
43      expect(response).to be_a(EchoMsg)
44      expect(response.msg).to eq(long_request_str)
45    end
46  end
47
48  def client_cancel_test(cancel_proc, expected_code,
49                         expected_details)
50    call = @stub.an_rpc(EchoMsg.new, return_op: true)
51    run_services_on_server(@server, services: [EchoService]) do
52      # start the call, but don't send a message yet
53      call.start_call
54      # cancel the call
55      cancel_proc.call(call)
56      # check the client's status
57      failed = false
58      begin
59        call.execute
60      rescue GRPC::BadStatus => e
61        failed = true
62        expect(e.code).to be expected_code
63        expect(e.details).to eq expected_details
64      end
65      expect(failed).to be(true)
66    end
67  end
68
69  it 'clients can cancel a call on the server' do
70    expected_code = StatusCodes::CANCELLED
71    expected_details = 'CANCELLED'
72    cancel_proc = proc { |call| call.cancel }
73    client_cancel_test(cancel_proc, expected_code, expected_details)
74  end
75
76  it 'cancel_with_status unknown status' do
77    code = StatusCodes::UNKNOWN
78    details = 'test unknown reason'
79    cancel_proc = proc { |call| call.cancel_with_status(code, details) }
80    client_cancel_test(cancel_proc, code, details)
81  end
82
83  it 'cancel_with_status unknown status' do
84    code = StatusCodes::FAILED_PRECONDITION
85    details = 'test failed precondition reason'
86    cancel_proc = proc { |call| call.cancel_with_status(code, details) }
87    client_cancel_test(cancel_proc, code, details)
88  end
89end
90
91shared_examples 'GRPC metadata delivery works OK' do
92  describe 'from client => server' do
93    before(:example) do
94      n = 7  # arbitrary number of metadata
95      diff_keys_fn = proc { |i| [format('k%d', i), format('v%d', i)] }
96      diff_keys = Hash[n.times.collect { |x| diff_keys_fn.call x }]
97      null_vals_fn = proc { |i| [format('k%d', i), format('v\0%d', i)] }
98      null_vals = Hash[n.times.collect { |x| null_vals_fn.call x }]
99      same_keys_fn = proc { |i| [format('k%d', i), [format('v%d', i)] * n] }
100      same_keys = Hash[n.times.collect { |x| same_keys_fn.call x }]
101      symbol_key = { a_key: 'a val' }
102      @valid_metadata = [diff_keys, same_keys, null_vals, symbol_key]
103      @bad_keys = []
104      @bad_keys << { Object.new => 'a value' }
105      @bad_keys << { 1 => 'a value' }
106    end
107
108    it 'raises an exception if a metadata key is invalid' do
109      @bad_keys.each do |md|
110        # NOTE: no need to run a server in this test b/c the failure
111        # happens while validating metadata to send.
112        failed = false
113        begin
114          @stub.an_rpc(EchoMsg.new, metadata: md)
115        rescue TypeError => e
116          failed = true
117          expect(e.message).to eq('grpc_rb_md_ary_fill_hash_cb: bad type for key parameter')
118        end
119        expect(failed).to be(true)
120      end
121    end
122
123    it 'sends all the metadata pairs when keys and values are valid' do
124      service = EchoService.new
125      run_services_on_server(@server, services: [service]) do
126        @valid_metadata.each_with_index do |md, i|
127          expect(@stub.an_rpc(EchoMsg.new, metadata: md)).to be_a(EchoMsg)
128          # confirm the server can receive the client metadata
129          # finish the call
130          expect(service.received_md.length).to eq(i + 1)
131          md.each do |k, v|
132            expect(service.received_md[i][k.to_s]).to eq(v)
133          end
134        end
135      end
136    end
137  end
138
139  describe 'from server => client' do
140    before(:example) do
141      n = 7  # arbitrary number of metadata
142      diff_keys_fn = proc { |i| [format('k%d', i), format('v%d', i)] }
143      diff_keys = Hash[n.times.collect { |x| diff_keys_fn.call x }]
144      null_vals_fn = proc { |i| [format('k%d', i), format('v\0%d', i)] }
145      null_vals = Hash[n.times.collect { |x| null_vals_fn.call x }]
146      same_keys_fn = proc { |i| [format('k%d', i), [format('v%d', i)] * n] }
147      same_keys = Hash[n.times.collect { |x| same_keys_fn.call x }]
148      symbol_key = { a_key: 'a val' }
149      @valid_metadata = [diff_keys, same_keys, null_vals, symbol_key]
150      @bad_keys = []
151      @bad_keys << { Object.new => 'a value' }
152      @bad_keys << { 1 => 'a value' }
153    end
154
155    it 'raises an exception if a metadata key is invalid' do
156      service = EchoService.new
157      run_services_on_server(@server, services: [service]) do
158        @bad_keys.each do |md|
159          proceed = Queue.new
160          server_exception = nil
161          service.on_call_started = proc do |call|
162            call.send_initial_metadata(md)
163          rescue TypeError => e
164            server_exception = e
165          ensure
166            proceed.push(1)
167          end
168          client_exception = nil
169          client_call = @stub.an_rpc(EchoMsg.new, return_op: true)
170          thr = Thread.new do
171            client_call.execute
172          rescue GRPC::BadStatus => e
173            client_exception = e
174          end
175          proceed.pop
176          # TODO(apolcyn): we shouldn't need this cancel here. It's
177          # only currently needed b/c the server does not seem to properly
178          # terminate the RPC if it fails to send initial metadata. That
179          # should be fixed, in which case this cancellation can be removed.
180          client_call.cancel
181          thr.join
182          p client_exception
183          expect(client_exception.nil?).to be(false)
184          expect(server_exception.nil?).to be(false)
185          expect(server_exception.message).to eq(
186            'grpc_rb_md_ary_fill_hash_cb: bad type for key parameter')
187        end
188      end
189    end
190
191    it 'sends an empty hash if no metadata is added' do
192      run_services_on_server(@server, services: [EchoService]) do
193        call = @stub.an_rpc(EchoMsg.new, return_op: true)
194        expect(call.execute).to be_a(EchoMsg)
195        expect(call.metadata).to eq({})
196      end
197    end
198
199    it 'sends all the pairs when keys and values are valid' do
200      service = EchoService.new
201      run_services_on_server(@server, services: [service]) do
202        @valid_metadata.each do |md|
203          service.on_call_started = proc do |call|
204            call.send_initial_metadata(md)
205          end
206          call = @stub.an_rpc(EchoMsg.new, return_op: true)
207          call.execute
208          replace_symbols = Hash[md.each_pair.collect { |x, y| [x.to_s, y] }]
209          expect(call.metadata).to eq(replace_symbols)
210        end
211      end
212    end
213  end
214end
215
216describe 'the http client/server' do
217  before(:example) do
218    server_host = '0.0.0.0:0'
219    @server = new_rpc_server_for_testing
220    server_port = @server.add_http2_port(server_host, :this_port_is_insecure)
221    @ch = Channel.new("0.0.0.0:#{server_port}", nil, :this_channel_is_insecure)
222    @stub = EchoStub.new(
223      "0.0.0.0:#{server_port}", nil, channel_override: @ch)
224  end
225
226  it_behaves_like 'basic GRPC message delivery is OK' do
227  end
228
229  it_behaves_like 'GRPC metadata delivery works OK' do
230  end
231end
232
233describe 'the secure http client/server' do
234  def load_test_certs
235    test_root = File.join(File.dirname(__FILE__), 'testdata')
236    files = ['ca.pem', 'server1.key', 'server1.pem']
237    files.map { |f| File.open(File.join(test_root, f)).read }
238  end
239
240  before(:example) do
241    certs = load_test_certs
242    server_host = '0.0.0.0:0'
243    server_creds = GRPC::Core::ServerCredentials.new(
244      nil, [{ private_key: certs[1], cert_chain: certs[2] }], false)
245    @server = new_rpc_server_for_testing
246    server_port = @server.add_http2_port(server_host, server_creds)
247    args = { Channel::SSL_TARGET => 'foo.test.google.fr' }
248    @ch = Channel.new(
249      "0.0.0.0:#{server_port}", args,
250      GRPC::Core::ChannelCredentials.new(certs[0], nil, nil))
251    @stub = EchoStub.new(
252      "0.0.0.0:#{server_port}", nil, channel_override: @ch)
253  end
254
255  it_behaves_like 'basic GRPC message delivery is OK' do
256  end
257
258  it_behaves_like 'GRPC metadata delivery works OK' do
259  end
260
261  it 'modifies metadata with CallCredentials' do
262    # create call creds
263    auth_proc = proc { { 'k1' => 'v1' } }
264    call_creds = GRPC::Core::CallCredentials.new(auth_proc)
265    # create arbitrary custom metadata
266    custom_md = { 'k2' => 'v2' }
267    # perform an RPC
268    echo_service = EchoService.new
269    run_services_on_server(@server, services: [echo_service]) do
270      expect(@stub.an_rpc(EchoMsg.new,
271                          credentials: call_creds,
272                          metadata: custom_md)).to be_a(EchoMsg)
273    end
274    # call creds metadata should be merged with custom MD
275    expect(echo_service.received_md.length).to eq(1)
276    expected_md = { 'k1' => 'v1', 'k2' => 'v2' }
277    expected_md.each do |k, v|
278      expect(echo_service.received_md[0][k]).to eq(v)
279    end
280  end
281
282  it 'modifies large metadata with CallCredentials' do
283    val_array = %w(
284      '00000000000000000000000000000000000000000000000000000000000000',
285      '11111111111111111111111111111111111111111111111111111111111111',
286    )
287    # create call creds
288    auth_proc = proc do
289      {
290        k2: val_array,
291        k3: '0000000000000000000000000000000000000000000000000000000000',
292        keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey4: 'v4'
293      }
294    end
295    call_creds = GRPC::Core::CallCredentials.new(auth_proc)
296    # create arbitrary custom metadata
297    custom_md = { k1: 'v1' }
298    # perform an RPC
299    echo_service = EchoService.new
300    run_services_on_server(@server, services: [echo_service]) do
301      expect(@stub.an_rpc(EchoMsg.new,
302                          credentials: call_creds,
303                          metadata: custom_md)).to be_a(EchoMsg)
304    end
305    # call creds metadata should be merged with custom MD
306    expect(echo_service.received_md.length).to eq(1)
307    expected_md = {
308      k1: 'v1',
309      k2: val_array,
310      k3: '0000000000000000000000000000000000000000000000000000000000',
311      keeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeey4: 'v4'
312    }
313    expected_md.each do |k, v|
314      expect(echo_service.received_md[0][k.to_s]).to eq(v)
315    end
316  end
317end
318