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