mirror of https://github.com/grpc/grpc.git
parent
7256b055f2
commit
d5d00d5c8f
1 changed files with 278 additions and 0 deletions
@ -0,0 +1,278 @@ |
||||
#!/usr/bin/env ruby |
||||
|
||||
# Copyright 2014, 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. |
||||
|
||||
# pubsub_demo demos accesses the Google PubSub API via its gRPC interface |
||||
# |
||||
# TODO: update the Usage once the usable auth gem is available |
||||
# $ SSL_CERT_FILE=<path/to/ssl/certs> \ |
||||
# path/to/pubsub_demo.rb \ |
||||
# --service_account_key_file=<path_to_service_account> \ |
||||
# [--action=<chosen_demo_action> ] |
||||
# |
||||
# There are options related to the chosen action, see #parse_args below. |
||||
# - the possible actions are given by the method names of NamedAction class |
||||
# - the default action is list_some_topics |
||||
|
||||
this_dir = File.expand_path(File.dirname(__FILE__)) |
||||
lib_dir = File.join(File.dirname(File.dirname(this_dir)), 'lib') |
||||
$LOAD_PATH.unshift(lib_dir) unless $LOAD_PATH.include?(lib_dir) |
||||
$LOAD_PATH.unshift(this_dir) unless $LOAD_PATH.include?(this_dir) |
||||
|
||||
require 'optparse' |
||||
|
||||
require 'grpc' |
||||
require 'google/protobuf' |
||||
|
||||
require 'google/protobuf/empty' |
||||
require 'tech/pubsub/proto/pubsub' |
||||
require 'tech/pubsub/proto/pubsub_services' |
||||
|
||||
# loads the certificates used to access the test server securely. |
||||
def load_prod_cert |
||||
fail 'could not find a production cert' if ENV['SSL_CERT_FILE'].nil? |
||||
p "loading prod certs from #{ENV['SSL_CERT_FILE']}" |
||||
File.open(ENV['SSL_CERT_FILE']).read |
||||
end |
||||
|
||||
# creates a SSL Credentials from the production certificates. |
||||
def ssl_creds |
||||
GRPC::Core::Credentials.new(load_prod_cert) |
||||
end |
||||
|
||||
# Builds the metadata authentication update proc. |
||||
# |
||||
# TODO: replace this once the ruby usable auth repo is available. |
||||
def auth_proc(opts) |
||||
if GRPC::Auth::GCECredentials.on_gce? |
||||
return GRPC::Auth::GCECredentials.new.updater_proc |
||||
end |
||||
fd = StringIO.new(File.read(opts.oauth_key_file)) |
||||
GRPC::Auth::ServiceAccountCredentials.new(opts.oauth_scope, fd).updater_proc |
||||
end |
||||
|
||||
# Creates a stub for accessing the publisher service. |
||||
def publisher_stub(opts) |
||||
address = "#{opts.host}:#{opts.port}" |
||||
stub_clz = Tech::Pubsub::PublisherService::Stub # shorter |
||||
logger.info("... access PublisherService at #{address}") |
||||
stub_clz.new(address, |
||||
creds: ssl_creds, update_metadata: auth_proc(opts), |
||||
GRPC::Core::Channel::SSL_TARGET => opts.host) |
||||
end |
||||
|
||||
# Creates a stub for accessing the subscriber service. |
||||
def subscriber_stub(opts) |
||||
address = "#{opts.host}:#{opts.port}" |
||||
stub_clz = Tech::Pubsub::SubscriberService::Stub # shorter |
||||
logger.info("... access SubscriberService at #{address}") |
||||
stub_clz.new(address, |
||||
creds: ssl_creds, update_metadata: auth_proc(opts), |
||||
GRPC::Core::Channel::SSL_TARGET => opts.host) |
||||
end |
||||
|
||||
# defines methods corresponding to each interop test case. |
||||
class NamedActions |
||||
include Tech::Pubsub |
||||
|
||||
# Initializes NamedActions |
||||
# |
||||
# @param pub [Stub] a stub for accessing the publisher service |
||||
# @param sub [Stub] a stub for accessing the publisher service |
||||
# @param args [Args] provides access to the command line |
||||
def initialize(pub, sub, args) |
||||
@pub = pub |
||||
@sub = sub |
||||
@args = args |
||||
end |
||||
|
||||
# Removes the test topic if it exists |
||||
def remove_topic |
||||
name = test_topic_name |
||||
p "... removing Topic #{name}" |
||||
@pub.delete_topic(DeleteTopicRequest.new(topic: name)) |
||||
p "removed Topic: #{name} OK" |
||||
rescue GRPC::BadStatus => e |
||||
p "Could not delete a topics: rpc failed with '#{e}'" |
||||
end |
||||
|
||||
# Creates a test topic |
||||
def create_topic |
||||
name = test_topic_name |
||||
p "... creating Topic #{name}" |
||||
resp = @pub.create_topic(Topic.new(name: name)) |
||||
p "created Topic: #{resp.name} OK" |
||||
rescue GRPC::BadStatus => e |
||||
p "Could not create a topics: rpc failed with '#{e}'" |
||||
end |
||||
|
||||
# Lists topics in the project |
||||
def list_some_topics |
||||
p 'Listing topics' |
||||
p '-------------_' |
||||
list_project_topics.topic.each { |t| p t.name } |
||||
rescue GRPC::BadStatus => e |
||||
p "Could not list topics: rpc failed with '#{e}'" |
||||
end |
||||
|
||||
# Checks if a topics exists in a project |
||||
def check_exists |
||||
name = test_topic_name |
||||
p "... checking for topic #{name}" |
||||
exists = topic_exists?(name) |
||||
p "#{name} is a topic" if exists |
||||
p "#{name} is not a topic" unless exists |
||||
rescue GRPC::BadStatus => e |
||||
p "Could not check for a topics: rpc failed with '#{e}'" |
||||
end |
||||
|
||||
# Publishes some messages |
||||
def random_pub_sub |
||||
topic_name, sub_name = test_topic_name, test_sub_name |
||||
create_topic_if_needed(topic_name) |
||||
@sub.create_subscription(Subscription.new(name: sub_name, |
||||
topic: topic_name)) |
||||
msg_count = rand(10..30) |
||||
msg_count.times do |x| |
||||
msg = PubsubMessage.new(data: "message #{x}") |
||||
@pub.publish(PublishRequest.new(topic: topic_name, message: msg)) |
||||
end |
||||
p "Sent #{msg_count} messages to #{topic_name}, checking for them now." |
||||
batch = @sub.pull_batch(PullBatchRequest.new(subscription: sub_name, |
||||
max_events: msg_count)) |
||||
ack_ids = batch.pull_responses.map { |x| x.ack_id } |
||||
p "Got #{ack_ids.size} messages; acknowledging them.." |
||||
@sub.acknowledge(AcknowledgeRequest.new(subscription: sub_name, |
||||
ack_id: ack_ids)) |
||||
p "Test messages were acknowledged OK, deleting the subscription" |
||||
del_req = DeleteSubscriptionRequest.new(subscription: sub_name) |
||||
@sub.delete_subscription(del_req) |
||||
rescue GRPC::BadStatus => e |
||||
p "Could not do random pub sub: rpc failed with '#{e}'" |
||||
end |
||||
|
||||
private |
||||
|
||||
# test_topic_name is the topic name to use in this test. |
||||
def test_topic_name |
||||
unless @args.topic_name.nil? |
||||
return "/topics/#{@args.project_id}/#{@args.topic_name}" |
||||
end |
||||
now_text = Time.now.utc.strftime('%Y%m%d%H%M%S%L') |
||||
"/topics/#{@args.project_id}/#{ENV['USER']}-#{now_text}" |
||||
end |
||||
|
||||
# test_sub_name is the subscription name to use in this test. |
||||
def test_sub_name |
||||
unless @args.sub_name.nil? |
||||
return "/subscriptions/#{@args.project_id}/#{@args.sub_name}" |
||||
end |
||||
now_text = Time.now.utc.strftime('%Y%m%d%H%M%S%L') |
||||
"/subscriptions/#{@args.project_id}/#{ENV['USER']}-#{now_text}" |
||||
end |
||||
|
||||
# determines if the topic name exists |
||||
def topic_exists?(name) |
||||
topics = list_project_topics.topic.map { |t| t.name } |
||||
topics.include?(name) |
||||
end |
||||
|
||||
def create_topic_if_needed(name) |
||||
return if topic_exists?(name) |
||||
@pub.create_topic(Topic.new(name: name)) |
||||
end |
||||
|
||||
def list_project_topics |
||||
q = "cloud.googleapis.com/project in (/projects/#{@args.project_id})" |
||||
@pub.list_topics(ListTopicsRequest.new(query: q)) |
||||
end |
||||
end |
||||
|
||||
# Args is used to hold the command line info. |
||||
Args = Struct.new(:host, :oauth_scope, :oauth_key_file, :port, :action, |
||||
:project_id, :topic_name, :sub_name) |
||||
|
||||
# validates the the command line options, returning them as an Arg. |
||||
def parse_args |
||||
args = Args.new('pubsub-staging.googleapis.com', |
||||
'https://www.googleapis.com/auth/pubsub', |
||||
nil, 443, 'list_some_topics', 'stoked-keyword-656') |
||||
OptionParser.new do |opts| |
||||
opts.on('--oauth_scope scope', |
||||
'Scope for OAuth tokens') { |v| args['oauth_scope'] = v } |
||||
opts.on('--server_host SERVER_HOST', 'server hostname') do |v| |
||||
args.host = v |
||||
end |
||||
opts.on('--server_port SERVER_PORT', 'server port') do |v| |
||||
args.port = v |
||||
end |
||||
opts.on('--service_account_key_file PATH', |
||||
'Path to the service account json key file') do |v| |
||||
args.oauth_key_file = v |
||||
end |
||||
|
||||
# instance_methods(false) gives only the methods defined in that class. |
||||
scenes = NamedActions.instance_methods(false).map { |t| t.to_s } |
||||
scene_list = scenes.join(',') |
||||
opts.on("--action CODE", scenes, {}, 'pick a demo action', |
||||
" (#{scene_list})") do |v| |
||||
args.action = v |
||||
end |
||||
|
||||
# Set the remaining values. |
||||
%w(project_id topic_name sub_name).each do |o| |
||||
opts.on("--#{o} VALUE", "#{o}") do |v| |
||||
args[o] = v |
||||
end |
||||
end |
||||
end.parse! |
||||
_check_args(args) |
||||
end |
||||
|
||||
def _check_args(args) |
||||
%w(host port action).each do |a| |
||||
if args[a].nil? |
||||
raise OptionParser::MissingArgument.new("please specify --#{a}") |
||||
end |
||||
end |
||||
if args['oauth_key_file'].nil? || args['oauth_scope'].nil? |
||||
fail(OptionParser::MissingArgument, |
||||
'please specify both of --service_account_key_file and --oauth_scope') |
||||
end |
||||
args |
||||
end |
||||
|
||||
def main |
||||
args = parse_args |
||||
pub, sub = publisher_stub(args), subscriber_stub(args) |
||||
NamedActions.new(pub, sub, args).method(args.action).call |
||||
end |
||||
|
||||
main |
Loading…
Reference in new issue