OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/env ruby |
| 2 |
| 3 # Copyright 2015, Google Inc. |
| 4 # All rights reserved. |
| 5 # |
| 6 # Redistribution and use in source and binary forms, with or without |
| 7 # modification, are permitted provided that the following conditions are |
| 8 # met: |
| 9 # |
| 10 # * Redistributions of source code must retain the above copyright |
| 11 # notice, this list of conditions and the following disclaimer. |
| 12 # * Redistributions in binary form must reproduce the above |
| 13 # copyright notice, this list of conditions and the following disclaimer |
| 14 # in the documentation and/or other materials provided with the |
| 15 # distribution. |
| 16 # * Neither the name of Google Inc. nor the names of its |
| 17 # contributors may be used to endorse or promote products derived from |
| 18 # this software without specific prior written permission. |
| 19 # |
| 20 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| 21 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| 22 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| 23 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| 24 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 25 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 26 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 29 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 30 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 |
| 32 # interop_server is a Testing app that runs a gRPC interop testing server. |
| 33 # |
| 34 # It helps validate interoperation b/w gRPC in different environments |
| 35 # |
| 36 # Helps validate interoperation b/w different gRPC implementations. |
| 37 # |
| 38 # Usage: $ path/to/interop_server.rb --port |
| 39 |
| 40 this_dir = File.expand_path(File.dirname(__FILE__)) |
| 41 lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib') |
| 42 pb_dir = File.dirname(File.dirname(this_dir)) |
| 43 $LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) |
| 44 $LOAD_PATH.unshift(pb_dir) unless $LOAD_PATH.include?(pb_dir) |
| 45 $LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) |
| 46 |
| 47 require 'forwardable' |
| 48 require 'logger' |
| 49 require 'optparse' |
| 50 |
| 51 require 'grpc' |
| 52 |
| 53 require 'test/proto/empty' |
| 54 require 'test/proto/messages' |
| 55 require 'test/proto/test_services' |
| 56 |
| 57 # DebugIsTruncated extends the default Logger to truncate debug messages |
| 58 class DebugIsTruncated < Logger |
| 59 def debug(s) |
| 60 super(truncate(s, 1024)) |
| 61 end |
| 62 |
| 63 # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer t
han <tt>length</tt>: |
| 64 # |
| 65 # 'Once upon a time in a world far far away'.truncate(27) |
| 66 # # => "Once upon a time in a wo..." |
| 67 # |
| 68 # Pass a string or regexp <tt>:separator</tt> to truncate +text+ at a natural
break: |
| 69 # |
| 70 # 'Once upon a time in a world far far away'.truncate(27, separator: ' ') |
| 71 # # => "Once upon a time in a..." |
| 72 # |
| 73 # 'Once upon a time in a world far far away'.truncate(27, separator: /\s/) |
| 74 # # => "Once upon a time in a..." |
| 75 # |
| 76 # The last characters will be replaced with the <tt>:omission</tt> string (def
aults to "...") |
| 77 # for a total length not exceeding <tt>length</tt>: |
| 78 # |
| 79 # 'And they found that many people were sleeping better.'.truncate(25, omiss
ion: '... (continued)') |
| 80 # # => "And they f... (continued)" |
| 81 def truncate(s, truncate_at, options = {}) |
| 82 return s unless s.length > truncate_at |
| 83 omission = options[:omission] || '...' |
| 84 with_extra_room = truncate_at - omission.length |
| 85 stop = \ |
| 86 if options[:separator] |
| 87 rindex(options[:separator], with_extra_room) || with_extra_room |
| 88 else |
| 89 with_extra_room |
| 90 end |
| 91 "#{s[0, stop]}#{omission}" |
| 92 end |
| 93 end |
| 94 |
| 95 # RubyLogger defines a logger for gRPC based on the standard ruby logger. |
| 96 module RubyLogger |
| 97 def logger |
| 98 LOGGER |
| 99 end |
| 100 |
| 101 LOGGER = DebugIsTruncated.new(STDOUT) |
| 102 LOGGER.level = Logger::WARN |
| 103 end |
| 104 |
| 105 # GRPC is the general RPC module |
| 106 module GRPC |
| 107 # Inject the noop #logger if no module-level logger method has been injected. |
| 108 extend RubyLogger |
| 109 end |
| 110 |
| 111 # loads the certificates by the test server. |
| 112 def load_test_certs |
| 113 this_dir = File.expand_path(File.dirname(__FILE__)) |
| 114 data_dir = File.join(File.dirname(File.dirname(this_dir)), 'spec/testdata') |
| 115 files = ['ca.pem', 'server1.key', 'server1.pem'] |
| 116 files.map { |f| File.open(File.join(data_dir, f)).read } |
| 117 end |
| 118 |
| 119 # creates a ServerCredentials from the test certificates. |
| 120 def test_server_creds |
| 121 certs = load_test_certs |
| 122 GRPC::Core::ServerCredentials.new( |
| 123 nil, [{private_key: certs[1], cert_chain: certs[2]}], false) |
| 124 end |
| 125 |
| 126 # produces a string of null chars (\0) of length l. |
| 127 def nulls(l) |
| 128 fail 'requires #{l} to be +ve' if l < 0 |
| 129 [].pack('x' * l).force_encoding('ascii-8bit') |
| 130 end |
| 131 |
| 132 # A EnumeratorQueue wraps a Queue yielding the items added to it via each_item. |
| 133 class EnumeratorQueue |
| 134 extend Forwardable |
| 135 def_delegators :@q, :push |
| 136 |
| 137 def initialize(sentinel) |
| 138 @q = Queue.new |
| 139 @sentinel = sentinel |
| 140 end |
| 141 |
| 142 def each_item |
| 143 return enum_for(:each_item) unless block_given? |
| 144 loop do |
| 145 r = @q.pop |
| 146 break if r.equal?(@sentinel) |
| 147 fail r if r.is_a? Exception |
| 148 yield r |
| 149 end |
| 150 end |
| 151 end |
| 152 |
| 153 # A runnable implementation of the schema-specified testing service, with each |
| 154 # service method implemented as required by the interop testing spec. |
| 155 class TestTarget < Grpc::Testing::TestService::Service |
| 156 include Grpc::Testing |
| 157 include Grpc::Testing::PayloadType |
| 158 |
| 159 def empty_call(_empty, _call) |
| 160 Empty.new |
| 161 end |
| 162 |
| 163 def unary_call(simple_req, _call) |
| 164 req_size = simple_req.response_size |
| 165 SimpleResponse.new(payload: Payload.new(type: :COMPRESSABLE, |
| 166 body: nulls(req_size))) |
| 167 end |
| 168 |
| 169 def streaming_input_call(call) |
| 170 sizes = call.each_remote_read.map { |x| x.payload.body.length } |
| 171 sum = sizes.inject(0) { |s, x| s + x } |
| 172 StreamingInputCallResponse.new(aggregated_payload_size: sum) |
| 173 end |
| 174 |
| 175 def streaming_output_call(req, _call) |
| 176 cls = StreamingOutputCallResponse |
| 177 req.response_parameters.map do |p| |
| 178 cls.new(payload: Payload.new(type: req.response_type, |
| 179 body: nulls(p.size))) |
| 180 end |
| 181 end |
| 182 |
| 183 def full_duplex_call(reqs) |
| 184 # reqs is a lazy Enumerator of the requests sent by the client. |
| 185 q = EnumeratorQueue.new(self) |
| 186 cls = StreamingOutputCallResponse |
| 187 Thread.new do |
| 188 begin |
| 189 GRPC.logger.info('interop-server: started receiving') |
| 190 reqs.each do |req| |
| 191 resp_size = req.response_parameters[0].size |
| 192 GRPC.logger.info("read a req, response size is #{resp_size}") |
| 193 resp = cls.new(payload: Payload.new(type: req.response_type, |
| 194 body: nulls(resp_size))) |
| 195 q.push(resp) |
| 196 end |
| 197 GRPC.logger.info('interop-server: finished receiving') |
| 198 q.push(self) |
| 199 rescue StandardError => e |
| 200 GRPC.logger.info('interop-server: failed') |
| 201 GRPC.logger.warn(e) |
| 202 q.push(e) # share the exception with the enumerator |
| 203 end |
| 204 end |
| 205 q.each_item |
| 206 end |
| 207 |
| 208 def half_duplex_call(reqs) |
| 209 # TODO: update with unique behaviour of the half_duplex_call if that's |
| 210 # ever required by any of the tests. |
| 211 full_duplex_call(reqs) |
| 212 end |
| 213 end |
| 214 |
| 215 # validates the the command line options, returning them as a Hash. |
| 216 def parse_options |
| 217 options = { |
| 218 'port' => nil, |
| 219 'secure' => false |
| 220 } |
| 221 OptionParser.new do |opts| |
| 222 opts.banner = 'Usage: --port port' |
| 223 opts.on('--port PORT', 'server port') do |v| |
| 224 options['port'] = v |
| 225 end |
| 226 opts.on('--use_tls USE_TLS', ['false', 'true'], |
| 227 'require a secure connection?') do |v| |
| 228 options['secure'] = v == 'true' |
| 229 end |
| 230 end.parse! |
| 231 |
| 232 if options['port'].nil? |
| 233 fail(OptionParser::MissingArgument, 'please specify --port') |
| 234 end |
| 235 options |
| 236 end |
| 237 |
| 238 def main |
| 239 opts = parse_options |
| 240 host = "0.0.0.0:#{opts['port']}" |
| 241 s = GRPC::RpcServer.new |
| 242 if opts['secure'] |
| 243 s.add_http2_port(host, test_server_creds) |
| 244 GRPC.logger.info("... running securely on #{host}") |
| 245 else |
| 246 s.add_http2_port(host, :this_port_is_insecure) |
| 247 GRPC.logger.info("... running insecurely on #{host}") |
| 248 end |
| 249 s.handle(TestTarget) |
| 250 s.run_till_terminated |
| 251 end |
| 252 |
| 253 main |
OLD | NEW |