Merge pull request #2943 from tbetbetbe/grpc-ruby-add-health-check-service

Add a health checker service implementation.
pull/2949/head
Jan Tattermusch 9 years ago
commit 723569b260
  1. 1
      src/ruby/.rspec
  2. 1
      src/ruby/.rubocop.yml
  3. 6
      src/ruby/Rakefile
  4. 2
      src/ruby/grpc.gemspec
  5. 27
      src/ruby/pb/README.md
  6. 75
      src/ruby/pb/grpc/health/checker.rb
  7. 50
      src/ruby/pb/grpc/health/v1alpha/health.proto
  8. 29
      src/ruby/pb/grpc/health/v1alpha/health.rb
  9. 39
      src/ruby/pb/grpc/health/v1alpha/health_checker.rb
  10. 28
      src/ruby/pb/grpc/health/v1alpha/health_services.rb
  11. 185
      src/ruby/spec/pb/health/checker_spec.rb

@ -1,4 +1,5 @@
-I. -I.
-Ipb
--require spec_helper --require spec_helper
--format documentation --format documentation
--color --color

@ -8,3 +8,4 @@ AllCops:
- 'bin/interop/test/**/*' - 'bin/interop/test/**/*'
- 'bin/math.rb' - 'bin/math.rb'
- 'bin/math_services.rb' - 'bin/math_services.rb'
- 'pb/grpc/health/v1alpha/*'

@ -20,7 +20,8 @@ SPEC_SUITES = [
{ id: :bidi, title: 'bidi tests', dir: %w(spec/generic), { id: :bidi, title: 'bidi tests', dir: %w(spec/generic),
tag: 'bidi' }, tag: 'bidi' },
{ id: :server, title: 'rpc server thread tests', dir: %w(spec/generic), { id: :server, title: 'rpc server thread tests', dir: %w(spec/generic),
tag: 'server' } tag: 'server' },
{ id: :pb, title: 'protobuf service tests', dir: %w(spec/pb) }
] ]
namespace :suite do namespace :suite do
SPEC_SUITES.each do |suite| SPEC_SUITES.each do |suite|
@ -50,7 +51,8 @@ task 'suite:wrapper' => [:compile, :rubocop]
task 'suite:idiomatic' => 'suite:wrapper' task 'suite:idiomatic' => 'suite:wrapper'
task 'suite:bidi' => 'suite:wrapper' task 'suite:bidi' => 'suite:wrapper'
task 'suite:server' => 'suite:wrapper' task 'suite:server' => 'suite:wrapper'
task 'suite:pb' => 'suite:server'
desc 'Compiles the gRPC extension then runs all the tests' desc 'Compiles the gRPC extension then runs all the tests'
task all: ['suite:idiomatic', 'suite:bidi', 'suite:server'] task all: ['suite:idiomatic', 'suite:bidi', 'suite:pb', 'suite:server']
task default: :all task default: :all

@ -24,7 +24,7 @@ Gem::Specification.new do |s|
%w(math noproto).each do |b| %w(math noproto).each do |b|
s.executables += ["#{b}_client.rb", "#{b}_server.rb"] s.executables += ["#{b}_client.rb", "#{b}_server.rb"]
end end
s.require_paths = %w( bin lib ) s.require_paths = %w( bin lib pb )
s.platform = Gem::Platform::RUBY s.platform = Gem::Platform::RUBY
s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1' s.add_dependency 'google-protobuf', '~> 3.0.0alpha.1.1'

@ -0,0 +1,27 @@
Protocol Buffers
================
This folder contains protocol buffers provided with gRPC ruby, and the generated
code to them.
PREREQUISITES
-------------
The code is is generated using the protoc (> 3.0.0.alpha.1) and the
grpc_ruby_plugin. These must be installed to regenerate the IDL defined
classes, but that's not necessary just to use them.
health_check/v1alpha
--------------------
This package defines the surface of a simple health check service that gRPC
servers may choose to implement, and provides an implementation for it. To
re-generate the surface.
```bash
$ # (from this directory)
$ protoc -I . grpc/health/v1alpha/health.proto \
--grpc_out=. \
--ruby_out=. \
--plugin=protoc-gen-grpc=`which grpc_ruby_plugin`
```

@ -0,0 +1,75 @@
# Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'grpc'
require 'grpc/health/v1alpha/health_services'
require 'thread'
module Grpc
# Health contains classes and modules that support providing a health check
# service.
module Health
# Checker is implementation of the schema-specified health checking service.
class Checker < V1alpha::Health::Service
StatusCodes = GRPC::Core::StatusCodes
HealthCheckResponse = V1alpha::HealthCheckResponse
# Initializes the statuses of participating services
def initialize
@statuses = {}
@status_mutex = Mutex.new # guards access to @statuses
end
# Implements the rpc IDL API method
def check(req, _call)
status = nil
@status_mutex.synchronize do
status = @statuses["#{req.host}/#{req.service}"]
end
fail GRPC::BadStatus, StatusCodes::NOT_FOUND if status.nil?
HealthCheckResponse.new(status: status)
end
# Adds the health status for a given host and service.
def add_status(host, service, status)
@status_mutex.synchronize { @statuses["#{host}/#{service}"] = status }
end
# Clears the status for the given host or service.
def clear_status(host, service)
@status_mutex.synchronize { @statuses.delete("#{host}/#{service}") }
end
# Clears alls the statuses.
def clear_all
@status_mutex.synchronize { @statuses = {} }
end
end
end
end

@ -0,0 +1,50 @@
// Copyright 2015, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
syntax = "proto3";
package grpc.health.v1alpha;
message HealthCheckRequest {
string host = 1;
string service = 2;
}
message HealthCheckResponse {
enum ServingStatus {
UNKNOWN = 0;
SERVING = 1;
NOT_SERVING = 2;
}
ServingStatus status = 1;
}
service Health {
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
}

@ -0,0 +1,29 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: grpc/health/v1alpha/health.proto
require 'google/protobuf'
Google::Protobuf::DescriptorPool.generated_pool.build do
add_message "grpc.health.v1alpha.HealthCheckRequest" do
optional :host, :string, 1
optional :service, :string, 2
end
add_message "grpc.health.v1alpha.HealthCheckResponse" do
optional :status, :enum, 1, "grpc.health.v1alpha.HealthCheckResponse.ServingStatus"
end
add_enum "grpc.health.v1alpha.HealthCheckResponse.ServingStatus" do
value :UNKNOWN, 0
value :SERVING, 1
value :NOT_SERVING, 2
end
end
module Grpc
module Health
module V1alpha
HealthCheckRequest = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1alpha.HealthCheckRequest").msgclass
HealthCheckResponse = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1alpha.HealthCheckResponse").msgclass
HealthCheckResponse::ServingStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("grpc.health.v1alpha.HealthCheckResponse.ServingStatus").enummodule
end
end
end

@ -0,0 +1,39 @@
# Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'grpc/health/v1alpha/health_services'
module Grpc
# Health contains classes and modules that support providing a health check
# service.
module Health
class Checker
end
end
end

@ -0,0 +1,28 @@
# Generated by the protocol buffer compiler. DO NOT EDIT!
# Source: grpc/health/v1alpha/health.proto for package 'grpc.health.v1alpha'
require 'grpc'
require 'grpc/health/v1alpha/health'
module Grpc
module Health
module V1alpha
module Health
# TODO: add proto service documentation here
class Service
include GRPC::GenericService
self.marshal_class_method = :encode
self.unmarshal_class_method = :decode
self.service_name = 'grpc.health.v1alpha.Health'
rpc :Check, HealthCheckRequest, HealthCheckResponse
end
Stub = Service.rpc_stub_class
end
end
end
end

@ -0,0 +1,185 @@
# Copyright 2015, Google Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'grpc'
require 'grpc/health/v1alpha/health'
require 'grpc/health/checker'
describe Grpc::Health::Checker do
StatusCodes = GRPC::Core::StatusCodes
ServingStatus = Grpc::Health::V1alpha::HealthCheckResponse::ServingStatus
HCResp = Grpc::Health::V1alpha::HealthCheckResponse
HCReq = Grpc::Health::V1alpha::HealthCheckRequest
success_tests =
[
{
desc: 'neither host or service are specified',
host: '',
service: ''
}, {
desc: 'only the host is specified',
host: 'test-fake-host',
service: ''
}, {
desc: 'the host and service are specified',
host: 'test-fake-host',
service: 'fake-service-1'
}, {
desc: 'only the service is specified',
host: '',
service: 'fake-service-2'
}
]
context 'initialization' do
it 'can be constructed with no args' do
expect(subject).to_not be(nil)
end
end
context 'method `add_status` and `check`' do
success_tests.each do |t|
it "should succeed when #{t[:desc]}" do
subject.add_status(t[:host], t[:service], ServingStatus::NOT_SERVING)
got = subject.check(HCReq.new(host: t[:host], service: t[:service]),
nil)
want = HCResp.new(status: ServingStatus::NOT_SERVING)
expect(got).to eq(want)
end
end
end
context 'method `check`' do
success_tests.each do |t|
it "should fail with NOT_FOUND when #{t[:desc]}" do
blk = proc do
subject.check(HCReq.new(host: t[:host], service: t[:service]), nil)
end
expected_msg = /#{StatusCodes::NOT_FOUND}/
expect(&blk).to raise_error GRPC::BadStatus, expected_msg
end
end
end
context 'method `clear_status`' do
success_tests.each do |t|
it "should fail after clearing status when #{t[:desc]}" do
subject.add_status(t[:host], t[:service], ServingStatus::NOT_SERVING)
got = subject.check(HCReq.new(host: t[:host], service: t[:service]),
nil)
want = HCResp.new(status: ServingStatus::NOT_SERVING)
expect(got).to eq(want)
subject.clear_status(t[:host], t[:service])
blk = proc do
subject.check(HCReq.new(host: t[:host], service: t[:service]),
nil)
end
expected_msg = /#{StatusCodes::NOT_FOUND}/
expect(&blk).to raise_error GRPC::BadStatus, expected_msg
end
end
end
context 'method `clear_all`' do
it 'should return NOT_FOUND after being invoked' do
success_tests.each do |t|
subject.add_status(t[:host], t[:service], ServingStatus::NOT_SERVING)
got = subject.check(HCReq.new(host: t[:host], service: t[:service]),
nil)
want = HCResp.new(status: ServingStatus::NOT_SERVING)
expect(got).to eq(want)
end
subject.clear_all
success_tests.each do |t|
blk = proc do
subject.check(HCReq.new(host: t[:host], service: t[:service]), nil)
end
expected_msg = /#{StatusCodes::NOT_FOUND}/
expect(&blk).to raise_error GRPC::BadStatus, expected_msg
end
end
end
describe 'running on RpcServer' do
RpcServer = GRPC::RpcServer
StatusCodes = GRPC::Core::StatusCodes
CheckerStub = Grpc::Health::Checker.rpc_stub_class
before(:each) do
@server_queue = GRPC::Core::CompletionQueue.new
server_host = '0.0.0.0:0'
@server = GRPC::Core::Server.new(@server_queue, nil)
server_port = @server.add_http2_port(server_host)
@host = "localhost:#{server_port}"
@ch = GRPC::Core::Channel.new(@host, nil)
@client_opts = { channel_override: @ch }
server_opts = {
server_override: @server,
completion_queue_override: @server_queue,
poll_period: 1
}
@srv = RpcServer.new(**server_opts)
end
after(:each) do
@srv.stop
end
it 'should receive the correct status', server: true do
@srv.handle(subject)
subject.add_status('', '', ServingStatus::NOT_SERVING)
t = Thread.new { @srv.run }
@srv.wait_till_running
stub = CheckerStub.new(@host, **@client_opts)
got = stub.check(HCReq.new)
want = HCResp.new(status: ServingStatus::NOT_SERVING)
expect(got).to eq(want)
@srv.stop
t.join
end
it 'should fail on unknown services', server: true do
@srv.handle(subject)
t = Thread.new { @srv.run }
@srv.wait_till_running
blk = proc do
stub = CheckerStub.new(@host, **@client_opts)
stub.check(HCReq.new(host: 'unknown', service: 'unknown'))
end
expected_msg = /#{StatusCodes::NOT_FOUND}/
expect(&blk).to raise_error GRPC::BadStatus, expected_msg
@srv.stop
t.join
end
end
end
Loading…
Cancel
Save