Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

prototype service broker implemenation

  - add API to create and delete a brokered service
  - add service_broker domain name registration
  - support load pre-defined brokered services from configuration file

Change-Id: Ieac9f1b2dde76acd48205e58691ee2e5669272db
  • Loading branch information...
commit d5053d37bb64bcf754d9502689f5692ec79fcacf 1 parent 6d9e26a
Andrew Liu andl authored
Showing with 969 additions and 30 deletions.
  1. +11 −3 base/lib/base/asynchronous_service_gateway.rb
  2. +2 −0  base/lib/base/base.rb
  3. +42 −27 base/lib/base/gateway.rb
  4. +1 −0  service_broker/.gitignore
  5. +25 −0 service_broker/Gemfile
  6. +127 −0 service_broker/Gemfile.lock
  7. +1 −0  service_broker/README
  8. +39 −0 service_broker/Rakefile
  9. +94 −0 service_broker/bin/service_broker
  10. +26 −0 service_broker/config/pre_defined_services.yml
  11. +15 −0 service_broker/config/service_broker.yml
  12. +464 −0 service_broker/lib/service_broker/async_gateway.rb
  13. +41 −0 service_broker/spec/Rakefile
  14. +51 −0 service_broker/spec/service_broker_spec.rb
  15. +30 −0 service_broker/spec/spec_helper.rb
  16. BIN  service_broker/vendor/cache/addressable-2.2.4.gem
  17. BIN  service_broker/vendor/cache/bcrypt-ruby-2.1.4.gem
  18. BIN  service_broker/vendor/cache/builder-3.0.0.gem
  19. BIN  service_broker/vendor/cache/ci_reporter-1.6.4.gem
  20. BIN  service_broker/vendor/cache/daemons-1.1.2.gem
  21. BIN  service_broker/vendor/cache/data_objects-0.10.3.gem
  22. BIN  service_broker/vendor/cache/datamapper-1.1.0.gem
  23. BIN  service_broker/vendor/cache/diff-lcs-1.1.2.gem
  24. BIN  service_broker/vendor/cache/dm-aggregates-1.1.0.gem
  25. BIN  service_broker/vendor/cache/dm-constraints-1.1.0.gem
  26. BIN  service_broker/vendor/cache/dm-core-1.1.0.gem
  27. BIN  service_broker/vendor/cache/dm-do-adapter-1.1.0.gem
  28. BIN  service_broker/vendor/cache/dm-migrations-1.1.0.gem
  29. BIN  service_broker/vendor/cache/dm-serializer-1.1.0.gem
  30. BIN  service_broker/vendor/cache/dm-sqlite-adapter-1.1.0.gem
  31. BIN  service_broker/vendor/cache/dm-timestamps-1.1.0.gem
  32. BIN  service_broker/vendor/cache/dm-transactions-1.1.0.gem
  33. BIN  service_broker/vendor/cache/dm-types-1.1.0.gem
  34. BIN  service_broker/vendor/cache/dm-validations-1.1.0.gem
  35. BIN  service_broker/vendor/cache/do_sqlite3-0.10.3.gem
  36. BIN  service_broker/vendor/cache/em-http-request-0.3.0.gem
  37. BIN  service_broker/vendor/cache/escape_utils-0.2.3.gem
  38. BIN  service_broker/vendor/cache/eventmachine-0.12.10.gem
  39. BIN  service_broker/vendor/cache/fastercsv-1.5.4.gem
  40. BIN  service_broker/vendor/cache/json-1.4.6.gem
  41. BIN  service_broker/vendor/cache/json_pure-1.5.1.gem
  42. BIN  service_broker/vendor/cache/little-plugger-1.1.2.gem
  43. BIN  service_broker/vendor/cache/logging-1.5.0.gem
  44. BIN  service_broker/vendor/cache/nats-0.4.8.gem
  45. BIN  service_broker/vendor/cache/posix-spawn-0.3.6.gem
  46. BIN  service_broker/vendor/cache/rack-1.2.2.gem
  47. BIN  service_broker/vendor/cache/rack-test-0.5.7.gem
  48. BIN  service_broker/vendor/cache/rake-0.8.7.gem
  49. BIN  service_broker/vendor/cache/rcov-0.9.9.gem
  50. BIN  service_broker/vendor/cache/rspec-2.5.0.gem
  51. BIN  service_broker/vendor/cache/rspec-core-2.5.1.gem
  52. BIN  service_broker/vendor/cache/rspec-expectations-2.5.0.gem
  53. BIN  service_broker/vendor/cache/rspec-mocks-2.5.0.gem
  54. BIN  service_broker/vendor/cache/sinatra-1.2.1.gem
  55. BIN  service_broker/vendor/cache/stringex-1.2.1.gem
  56. BIN  service_broker/vendor/cache/thin-1.2.11.gem
  57. BIN  service_broker/vendor/cache/tilt-1.2.2.gem
  58. BIN  service_broker/vendor/cache/uuidtools-2.1.2.gem
  59. BIN  service_broker/vendor/cache/vcap_common-0.99.gem
  60. BIN  service_broker/vendor/cache/vcap_logging-0.1.0.gem
  61. BIN  service_broker/vendor/cache/yajl-ruby-0.8.2.gem
14 base/lib/base/asynchronous_service_gateway.rb
View
@@ -12,6 +12,7 @@
$:.unshift(File.expand_path("../../../../../lib", __FILE__))
require 'json_message'
require 'services/api'
+require 'services/api/const'
require 'service_error'
@@ -36,6 +37,11 @@ class VCAP::Services::AsynchronousServiceGateway < Sinatra::Base
def initialize(opts)
super
+ setup(opts)
+ end
+
+ # setup the environment
+ def setup(opts)
missing_opts = REQ_OPTS.select {|o| !opts.has_key? o}
raise ArgumentError, "Missing options: #{missing_opts.join(', ')}" unless missing_opts.empty?
@service = opts[:service]
@@ -66,9 +72,11 @@ def initialize(opts)
:url => @service[:url],
:active => false
}.to_json
+
+ token_hdrs = VCAP::Services::Api::GATEWAY_TOKEN_HEADER
@cc_req_hdrs = {
- 'Content-Type' => 'application/json',
- 'X-VCAP-Service-Token' => @token,
+ 'Content-Type' => 'application/json',
+ token_hdrs => @token,
}
@proxy_opts = opts[:proxy]
@@ -329,7 +337,7 @@ def update_service_handle(handle, &cb)
def async_mode(timeout=@node_timeout)
request.env['__async_timer'] = EM.add_timer(timeout) do
- @logger.warn("Service Unavailable")
+ @logger.warn("Request timeout in #{timeout} seconds.")
error_msg = ServiceError.new(ServiceError::SERVICE_UNAVAILABLE).to_hash
err_body = error_msg['msg'].to_json()
request.env['async.callback'].call(
2  base/lib/base/base.rb
View
@@ -79,6 +79,8 @@ def shutdown()
# Subclasses VCAP::Services::Base::{Node,Provisioner} implement the
# following methods. (Note that actual service Provisioner or Node
# implementations should NOT need to touch these!)
+
+ # TODO on_connect_node should be on_connect_nats
abstract :on_connect_node
abstract :flavor # "Provisioner" or "Node"
abstract :varz_details
69 base/lib/base/gateway.rb
View
@@ -32,8 +32,7 @@ class VCAP::Services::Base::Gateway
CC_CONFIG_FILE = File.expand_path("../../../../../cloud_controller/config/cloud_controller.yml", __FILE__)
- def start
-
+ def parse_config
config_file = default_config_file
OptionParser.new do |opts|
@@ -48,55 +47,71 @@ def start
end.parse!
begin
- config = parse_gateway_config(config_file)
+ @config = parse_gateway_config(config_file)
rescue => e
puts "Couldn't read config file: #{e}"
exit
end
+ end
- VCAP::Logging.setup_from_config(config[:logging])
+ def setup_vcap_logging
+ VCAP::Logging.setup_from_config(@config[:logging])
# Use the current running binary name for logger identity name, since service gateway only has one instance now.
logger = VCAP::Logging.logger(File.basename($0))
- config[:logger] = logger
+ @config[:logger] = logger
+ end
- if config[:pid]
- pf = VCAP::PidFile.new(config[:pid])
+ def setup_pid
+ if @config[:pid]
+ pf = VCAP::PidFile.new(@config[:pid])
pf.unlink_at_exit
end
+ end
- config[:host] = VCAP.local_ip(config[:ip_route])
- config[:port] ||= VCAP.grab_ephemeral_port
- config[:service][:label] = "#{config[:service][:name]}-#{config[:service][:version]}"
- config[:service][:url] = "http://#{config[:host]}:#{config[:port]}"
- cloud_controller_uri = config[:cloud_controller_uri] || default_cloud_controller_uri
- node_timeout = config[:node_timeout] || 5
+ def start
+ parse_config
+
+ setup_vcap_logging
+
+ setup_pid
+
+ @config[:host] = VCAP.local_ip(@config[:ip_route])
+ @config[:port] ||= VCAP.grab_ephemeral_port
+ @config[:service][:label] = "#{@config[:service][:name]}-#{@config[:service][:version]}"
+ @config[:service][:url] = "http://#{@config[:host]}:#{@config[:port]}"
+ node_timeout = @config[:node_timeout] || 5
+ cloud_controller_uri = @config[:cloud_controller_uri] || default_cloud_controller_uri
# Go!
EM.run do
sp = provisioner_class.new(
- :logger => logger,
- :index => config[:index],
- :version => config[:service][:version],
- :ip_route => config[:ip_route],
- :mbus => config[:mbus],
+ :logger => @config[:logger],
+ :index => @config[:index],
+ :version => @config[:service][:version],
+ :ip_route => @config[:ip_route],
+ :mbus => @config[:mbus],
:node_timeout => node_timeout,
- :z_interval => config[:z_interval],
- :allow_over_provisioning => config[:allow_over_provisioning]
+ :z_interval => @config[:z_interval],
+ :allow_over_provisioning => @config[:allow_over_provisioning]
)
- sg = VCAP::Services::AsynchronousServiceGateway.new(
- :proxy => config[:proxy],
- :service => config[:service],
- :token => config[:token],
- :logger => logger,
+ sg = async_gateway_class.new(
+ :proxy => @config[:proxy],
+ :service => @config[:service],
+ :token => @config[:token],
+ :logger => @config[:logger],
:provisioner => sp,
:node_timeout => node_timeout,
:cloud_controller_uri => cloud_controller_uri,
- :check_orphan_interval => config[:check_orphan_interval] || -1
+ :check_orphan_interval => @config[:check_orphan_interval] || -1
)
- Thin::Server.start(config[:host], config[:port], sg)
+ Thin::Server.start(@config[:host], @config[:port], sg)
end
end
+ def async_gateway_class
+ VCAP::Services::AsynchronousServiceGateway
+ end
+
def default_cloud_controller_uri
config = YAML.load_file(CC_CONFIG_FILE)
config['external_uri'] || "api.vcap.me"
1  service_broker/.gitignore
View
@@ -0,0 +1 @@
+*.db
25 service_broker/Gemfile
View
@@ -0,0 +1,25 @@
+source "http://rubygems.org"
+
+gem "nats", '>= 0.4.8'
+gem "datamapper", ">= 0.10.2"
+gem "do_sqlite3", :require => nil
+gem "dm-sqlite-adapter"
+gem "dm-types"
+gem "eventmachine"
+gem "em-http-request"
+gem "json"
+gem "uuidtools"
+gem "thin"
+gem "sinatra"
+
+gem 'vcap_common', :require => ['vcap/common', 'vcap/component']
+gem 'vcap_logging', :require => ['vcap/logging']
+
+group :test do
+ gem "rake"
+ gem "rspec"
+ gem "rack-test"
+ gem "rcov"
+ gem "ci_reporter"
+end
+
127 service_broker/Gemfile.lock
View
@@ -0,0 +1,127 @@
+GEM
+ remote: http://rubygems.org/
+ specs:
+ addressable (2.2.4)
+ bcrypt-ruby (2.1.4)
+ builder (3.0.0)
+ ci_reporter (1.6.4)
+ builder (>= 2.1.2)
+ daemons (1.1.2)
+ data_objects (0.10.3)
+ addressable (~> 2.1)
+ datamapper (1.1.0)
+ dm-aggregates (= 1.1.0)
+ dm-constraints (= 1.1.0)
+ dm-core (= 1.1.0)
+ dm-migrations (= 1.1.0)
+ dm-serializer (= 1.1.0)
+ dm-timestamps (= 1.1.0)
+ dm-transactions (= 1.1.0)
+ dm-types (= 1.1.0)
+ dm-validations (= 1.1.0)
+ diff-lcs (1.1.2)
+ dm-aggregates (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-constraints (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-core (1.1.0)
+ addressable (~> 2.2.4)
+ dm-do-adapter (1.1.0)
+ data_objects (~> 0.10.2)
+ dm-core (~> 1.1.0)
+ dm-migrations (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-serializer (1.1.0)
+ dm-core (~> 1.1.0)
+ fastercsv (~> 1.5.4)
+ json (~> 1.4.6)
+ dm-sqlite-adapter (1.1.0)
+ dm-do-adapter (~> 1.1.0)
+ do_sqlite3 (~> 0.10.2)
+ dm-timestamps (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-transactions (1.1.0)
+ dm-core (~> 1.1.0)
+ dm-types (1.1.0)
+ bcrypt-ruby (~> 2.1.4)
+ dm-core (~> 1.1.0)
+ fastercsv (~> 1.5.4)
+ json (~> 1.4.6)
+ stringex (~> 1.2.0)
+ uuidtools (~> 2.1.2)
+ dm-validations (1.1.0)
+ dm-core (~> 1.1.0)
+ do_sqlite3 (0.10.3)
+ data_objects (= 0.10.3)
+ em-http-request (0.3.0)
+ addressable (>= 2.0.0)
+ escape_utils
+ eventmachine (>= 0.12.9)
+ escape_utils (0.2.3)
+ eventmachine (0.12.10)
+ fastercsv (1.5.4)
+ json (1.4.6)
+ json_pure (1.5.1)
+ little-plugger (1.1.2)
+ logging (1.5.0)
+ little-plugger (>= 1.1.2)
+ nats (0.4.8)
+ daemons (>= 1.1.0)
+ eventmachine (>= 0.12.10)
+ json_pure (>= 1.5.1)
+ posix-spawn (0.3.6)
+ rack (1.2.2)
+ rack-test (0.5.7)
+ rack (>= 1.0)
+ rake (0.8.7)
+ rcov (0.9.9)
+ rspec (2.5.0)
+ rspec-core (~> 2.5.0)
+ rspec-expectations (~> 2.5.0)
+ rspec-mocks (~> 2.5.0)
+ rspec-core (2.5.1)
+ rspec-expectations (2.5.0)
+ diff-lcs (~> 1.1.2)
+ rspec-mocks (2.5.0)
+ sinatra (1.2.1)
+ rack (~> 1.1)
+ tilt (< 2.0, >= 1.2.2)
+ stringex (1.2.1)
+ thin (1.2.11)
+ daemons (>= 1.0.9)
+ eventmachine (>= 0.12.6)
+ rack (>= 1.0.0)
+ tilt (1.2.2)
+ uuidtools (2.1.2)
+ vcap_common (0.99)
+ eventmachine (~> 0.12.10)
+ logging (>= 1.5.0)
+ nats
+ posix-spawn
+ thin
+ yajl-ruby
+ vcap_logging (0.1.0)
+ yajl-ruby (0.8.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ ci_reporter
+ datamapper (>= 0.10.2)
+ dm-sqlite-adapter
+ dm-types
+ do_sqlite3
+ em-http-request
+ eventmachine
+ json
+ nats (>= 0.4.8)
+ rack-test
+ rake
+ rcov
+ rspec
+ sinatra
+ thin
+ uuidtools
+ vcap_common
+ vcap_logging
1  service_broker/README
View
@@ -0,0 +1 @@
+Cloud Foundry Service Broker enables consumption of legacy, non-CF services by CF apps using standard Cloud Foundry APIs and bindings.
39 service_broker/Rakefile
View
@@ -0,0 +1,39 @@
+require 'rake'
+
+desc "Run specs"
+task "spec" => ["bundler:install:test", "test:spec"]
+
+desc "Run specs using RCov"
+task "spec:rcov" => ["bundler:install:test", "test:spec:rcov"]
+
+namespace "bundler" do
+ desc "Install gems"
+ task "install" do
+ sh("bundle install")
+ end
+
+ desc "Install gems for test"
+ task "install:test" do
+ sh("bundle install --without development production")
+ end
+
+ desc "Install gems for production"
+ task "install:production" do
+ sh("bundle install --without development test")
+ end
+
+ desc "Install gems for development"
+ task "install:development" do
+ sh("bundle install --without test production")
+ end
+end
+
+namespace "test" do
+ task "spec" do |t|
+ sh("cd spec && ../../base/bin/nats-util start && rake spec && ../../base/bin/nats-util stop")
+ end
+
+ task "spec:rcov" do |t|
+ sh("cd spec && rake spec:rcov")
+ end
+end
94 service_broker/bin/service_broker
View
@@ -0,0 +1,94 @@
+#!/usr/bin/env ruby
+# Copyright (c) 2009-2011 VMware, Inc.
+#
+ENV["BUNDLE_GEMFILE"] ||= File.expand_path('../../Gemfile', __FILE__)
+
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', 'base', 'lib')
+require 'base/gateway'
+
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
+require 'service_broker/async_gateway'
+
+class VCAP::Services::ServiceBroker::Gateway < VCAP::Services::Base::Gateway
+
+ def parse_config
+ config_file = default_config_file
+ pre_defined_services = default_pre_defined_services_config_file
+
+ OptionParser.new do |opts|
+ opts.banner = "Usage: $0 [options]"
+ opts.on("-c", "--config [ARG]", "Configuration File") do |opt|
+ config_file = opt
+ end
+ opts.on("-p", "--pre-defined [ARG]", "Pre-defined Services Configration File") do |opt|
+ pre_defined_services = opt
+ end
+ opts.on("-h", "--help", "Help") do
+ puts opts
+ exit
+ end
+ end.parse!
+
+ begin
+ @config = parse_gateway_config(config_file)
+ services = parse_pre_defined_services_config(pre_defined_services)
+ @config.merge!(services) if services
+ rescue => e
+ puts "Couldn't read config file: #{e}"
+ exit
+ end
+ end
+
+ def parse_pre_defined_services_config(config_file)
+ config = YAML.load_file(config_file)
+ config = VCAP.symbolize_keys(config)
+ config
+ end
+
+ def async_gateway_class
+ VCAP::Services::ServiceBroker::AsynchronousServiceGateway
+ end
+
+ def default_config_file
+ File.join(File.dirname(__FILE__), '..', 'config', 'service_broker.yml')
+ end
+
+ def default_pre_defined_services_config_file
+ File.join(File.dirname(__FILE__), '..', 'config', 'pre_defined_services.yml')
+ end
+
+ def start
+ parse_config
+
+ setup_vcap_logging
+
+ setup_pid
+
+ @config[:host] = VCAP.local_ip(@config[:ip_route])
+ @config[:port] ||= VCAP.grab_ephemeral_port
+ cloud_controller_uri = @config[:cloud_controller_uri] || default_cloud_controller_uri
+
+ # Go!
+ EM.run do
+ sg = async_gateway_class.new(
+ :proxy => @config[:proxy],
+ :token => @config[:token],
+ :node_timeout => @config[:node_timeout] || 5,
+ :cloud_controller_uri => cloud_controller_uri,
+ :service => @config[:service],
+ :host => @config[:host],
+ :port => @config[:port],
+ :mbus => @config[:mbus],
+ :local_db => @config[:local_db],
+ :logger => @config[:logger],
+ :index => @config[:index],
+ :ip_route => @config[:ip_route],
+ :external_uri => @config[:external_uri],
+ :z_interval => @config[:z_interval],
+ )
+ Thin::Server.start(@config[:host], @config[:port], sg)
+ end
+ end
+end
+
+VCAP::Services::ServiceBroker::Gateway.new.start
26 service_broker/config/pre_defined_services.yml
View
@@ -0,0 +1,26 @@
+---
+# pre-defined services
+# Example: advertise brokered service xyz_hr_db
+#service:
+# name: xyz_hr_db
+# description: XYZ HR Database
+# version: "2.0"
+# options:
+# - name: rw
+# acls:
+# users: [xyzhr_admin@xyzcorp.com]
+# wildcards: []
+# credentials:
+# conn: postgresql://xyzhr:secret@db.xyzcorp.com:5432/xyzpeople_na
+# - name: ro
+# acls:
+# users: [xyzhr_dev@xyzcorp.com]
+# wildcards: []
+# credentials:
+# conn: postgresql://xyzhr_ro:secret2@db.xyzcorp.com:5432/xyzpeople_na
+# - name: rocontacts
+# acls:
+# users: []
+# wildcards: ["*@xyzcorp.com"]
+# credentials:
+# conn: postgresql://xyzhr_rocontacts:secret3@db.xyzcorp.com:5432/xyzpeople_na
15 service_broker/config/service_broker.yml
View
@@ -0,0 +1,15 @@
+---
+#cloud_controller_uri: api.vcap.me
+external_uri: service-broker.vcap.me
+ip_route: localhost
+#proxy:
+# host: proxy
+# port: 8080
+# keepalive: true
+index: 0
+token: "0xdeadbeef"
+logging:
+ level: debug
+mbus: nats://localhost:4222
+pid: /var/vcap/sys/run/service_broker.pid
+local_db: sqlite3:/var/vcap/services/broker/service_broker.db
464 service_broker/lib/service_broker/async_gateway.rb
View
@@ -0,0 +1,464 @@
+# Copyright (c) 2009-2011 VMware, Inc.
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', '..', 'base', 'lib')
+require 'base/asynchronous_service_gateway'
+require 'fiber'
+require 'datamapper'
+require 'dm-types'
+require 'nats/client'
+require 'uuidtools'
+
+module VCAP
+ module Services
+ module ServiceBroker
+ end
+ end
+end
+
+class VCAP::Services::ServiceBroker::AsynchronousServiceGateway < VCAP::Services::AsynchronousServiceGateway
+
+ class BrokeredService
+ include DataMapper::Resource
+ # Custom table name
+ storage_names[:default] = "brokered_services"
+
+ property :label, String, :key => true
+ property :name, String, :required => true
+ property :version, String, :required => true
+ property :credentials, Json, :required => true
+ property :acls, Json, :required => true
+ end
+
+ REQ_OPTS = %w(mbus external_uri token cloud_controller_uri).map {|o| o.to_sym}
+ API_VERSION = "poc"
+
+ set :raise_errors, Proc.new {false}
+ set :show_exceptions, false
+
+ def initialize(opts)
+ super(opts)
+ end
+
+ def setup(opts)
+ missing_opts = REQ_OPTS.select {|o| !opts.has_key? o}
+ raise ArgumentError, "Missing options: #{missing_opts.join(', ')}" unless missing_opts.empty?
+
+ @host = opts[:host]
+ @port = opts[:port]
+ @node_timeout = opts[:node_timeout]
+ @logger = opts[:logger] || make_logger()
+ @token = opts[:token]
+ @hb_interval = opts[:heartbeat_interval] || 60
+ @cld_ctrl_uri = http_uri(opts[:cloud_controller_uri])
+ @external_uri = opts[:external_uri]
+ @offering_uri = "#{@cld_ctrl_uri}/services/v1/offerings/"
+ @service_list_uri = "#{@cld_ctrl_uri}/brokered_services/poc/offerings"
+ @router_start_channel = nil
+ @proxy_opts = opts[:proxy]
+ @ready_to_serve = false
+ @handle_fetched = true # set to true in order to compatible with base asycn gateway.
+ @router_register_json = {
+ :host => @host,
+ :port => @port,
+ :uris => [ @external_uri ],
+ :tags => {:components => "ServiceBroker"},
+ }.to_json
+
+ token_hdrs = VCAP::Services::Api::GATEWAY_TOKEN_HEADER
+ @cc_req_hdrs = {
+ 'Content-Type' => 'application/json',
+ token_hdrs => @token,
+ }
+
+ DataMapper.setup(:default, opts[:local_db])
+ DataMapper::auto_upgrade!
+
+ Kernel.at_exit do
+ if EM.reactor_running?
+ on_exit(false)
+ else
+ EM.run { on_exit }
+ end
+ end
+
+ ##### Start up
+ f = Fiber.new do
+ begin
+ start_nats(opts[:mbus])
+ # get all brokered service offerings
+ fetch_brokered_services
+ # active services in local database
+ advertise_saved_services
+ # active predefined offerings
+ advertise_pre_defined_services(opts[:service]) if opts[:service]
+ # Ready to serve
+ @logger.info("Service broker is ready to serve incoming request.")
+ @ready_to_serve = true
+ rescue => e
+ @logger.fatal("Error when start up: #{fmt_error(e)}")
+ end
+ end
+ f.resume
+ end
+
+ # Validate the incoming request
+ before do
+ unless @ready_to_serve
+ error_msg = ServiceError.new(ServiceError::SERVICE_UNAVAILABLE).to_hash
+ abort_request(error_msg)
+ end
+ end
+
+ error [JsonMessage::ValidationError, JsonMessage::ParseError] do
+ error_msg = ServiceError.new(ServiceError::MALFORMATTED_REQ).to_hash
+ abort_request(error_msg)
+ end
+
+ not_found do
+ error_msg = ServiceError.new(ServiceError::NOT_FOUND, request.path_info).to_hash
+ abort_request(error_msg)
+ end
+
+ def start_nats(uri)
+ f = Fiber.current
+ @nats = NATS.connect(:uri => uri) do
+ on_connect_nats;
+ f.resume
+ end
+ Fiber.yield
+ end
+
+ def on_connect_nats()
+ @logger.info("Register service broker uri : #{@router_register_json}")
+ @nats.publish('router.register', @router_register_json)
+ @router_start_channel = @nats.subscribe('router.start') { @nats.publish('router.register', @router_register_json)}
+ end
+
+ def fetch_brokered_services
+ f = Fiber.current
+ req = create_http_request(
+ :head => @cc_req_hdrs
+ )
+
+ f = Fiber.current
+ http = EM::HttpRequest.new(@service_list_uri).get(req)
+ http.callback { f.resume(http) }
+ http.errback { f.resume(http) }
+ Fiber.yield
+
+ if http.error.empty?
+ if http.response_header.status == 200
+ # For V1, we can't get enough information such as services credentials from CC.
+ # If CC return a service label that not known by SB, we simply print it out rather than serve it.
+ resp = VCAP::Services::Api::ListBrokeredServicesResponse.decode(http.response)
+ resp.brokered_services.each {|bsvc| @logger.info("Fetch brokered service from CC: label=#{bsvc["label"]}")}
+ return true
+ else
+ @logger.warn("Failed to fetch brokered services, status=#{http.response_header.status}")
+ end
+ else
+ @logger.warn("Failed to fetch brokered services: #{http.error}")
+ end
+ nil
+ rescue => e
+ @logger.warn("Failed to fetch brokered services: #{fmt_error(e)}")
+ end
+
+ def advertise_saved_services(active=true)
+ BrokeredService.all.each do |bsvc|
+ req = {}
+ req[:label] = bsvc.label
+ req[:active] = active
+ req[:acls] = bsvc.acls
+ req[:url] = "http://#{@external_uri}"
+ req[:plans] = ["default"]
+ req[:tags] = ["default"]
+ advertise_brokered_service_to_cc(req)
+ end
+ end
+
+ def advertise_pre_defined_services(services)
+ services[:label] = "#{services[:name]}-#{services[:version]}"
+ %w(name version).each {|key| services.delete(key.to_sym)}
+ req = VCAP::Services::Api::BrokeredServiceOfferingRequest.new(services)
+ advertise_brokered_service(req)
+ rescue => e
+ @logger.warn("Failed to advertise pre-defined services #{services.inspect}: #{e}")
+ end
+
+ def stop_nats()
+ @nats.unsubscribe(@router_start_channel) if @router_start_channel
+ @logger.debug("Unregister uri: #{@router_register_json}")
+ @nats.publish("router.unregister", @router_register_json)
+ @nats.close
+ end
+
+ def on_exit(stop_event_loop=true)
+ @ready_to_serve = false
+ Fiber.new {
+ advertise_saved_services(false)
+ stop_nats
+ EM.stop if stop_event_loop
+ }.resume
+ end
+
+ #################### Handlers ###################
+
+ # Advertise or modify a brokered service offerings
+ post "/service-broker/#{API_VERSION}/offerings" do
+ req = VCAP::Services::Api::BrokeredServiceOfferingRequest.decode(request_body)
+ @logger.debug("Advertise brokered service for label=#{req.label}")
+
+ Fiber.new {
+ msg = advertise_brokered_service(req)
+ if msg['success']
+ async_reply
+ else
+ async_reply_error(msg['response'])
+ end
+ }.resume
+ async_mode
+ end
+
+ # Delete a brokered service offerings
+ delete "/service-broker/#{API_VERSION}/offerings/:label" do
+ label = params[:label]
+ @logger.debug("Delete brokered service for label=#{label}")
+
+ Fiber.new {
+ msg = delete_brokered_service(label)
+ if msg['success']
+ async_reply
+ else
+ async_reply_error(msg['response'])
+ end
+ }.resume
+ async_mode
+ end
+
+ # Provision a brokered service
+ post "/gateway/v1/configurations" do
+ req = VCAP::Services::Api::GatewayProvisionRequest.decode(request_body)
+ @logger.info("Provision request for label=#{req.label} plan=#{req.plan}")
+
+ Fiber.new {
+ msg = provision_brokered_service(req)
+ if msg['success']
+ async_reply(VCAP::Services::Api::GatewayProvisionResponse.new(msg['response']).encode)
+ else
+ async_reply_error(msg['response'])
+ end
+ }.resume
+ async_mode
+ end
+
+ # Binding a brokered service
+ post "/gateway/v1/configurations/:service_id/handles" do
+ req = VCAP::Services::Api::GatewayBindRequest.decode(request_body)
+ @logger.info("Binding request for service=#{params['service_id']} options=#{req.binding_options}")
+
+ Fiber.new {
+ msg = bind_brokered_service_instance(req.label, req.service_id, req.binding_options)
+ if msg['success']
+ async_reply(VCAP::Services::Api::GatewayBindResponse.new(msg['response']).encode)
+ else
+ async_reply_error(msg['response'])
+ end
+ }.resume
+ async_mode
+ end
+
+ # Unprovisions a brokered service instance
+ delete "/gateway/v1/configurations/:service_id" do
+ @logger.debug("Unprovision request for service_id=#{params['service_id']}")
+ # simply return ok
+ "{}"
+ end
+
+ # Unbinds a brokered service instance
+ delete "/gateway/v1/configurations/:service_id/handles/:handle_id" do
+ @logger.info("Unbind request for service_id=#{params['service_id']} handle_id=#{params['handle_id']}")
+ # simply return ok
+ "{}"
+ end
+
+ ################## Helpers ###################
+ #
+ helpers do
+
+ def advertise_brokered_service(request)
+ @logger.debug("Advertise a brokerd service: #{request.inspect}")
+ label = request.label
+ des = request.description
+ options = request.options
+
+ options.each do |opt|
+ opt = VCAP.symbolize_keys(opt)
+ svc = {}
+ name, version = VCAP::Services::Api::Util.parse_label(label)
+ svc[:label] = "#{name}_#{opt[:name]}-#{version}"
+ svc[:active] = true
+ svc[:description] = "#{des} (option '#{opt[:name]}')"
+ # Add required fields
+ svc[:acls] = opt[:acls]
+ svc[:url] = "http://#{@external_uri}"
+ svc[:plans] = ["default"]
+ svc[:tags] = ["default"]
+
+ # update or create local database entry
+ bsvc = BrokeredService.get(svc[:label])
+ if bsvc.nil?
+ bsvc = BrokeredService.new
+ bsvc.label = svc[:label]
+ bsvc.version = version
+ bsvc.name = name
+ end
+ bsvc.credentials = opt[:credentials]
+ bsvc.acls = opt[:acls]
+ result = advertise_brokered_service_to_cc(svc)
+ if result
+ if not bsvc.save
+ @logger.error("Can't save entry to local database: #{bsvc.errors.inspect}")
+ end
+ end
+ end
+ success()
+ rescue => e
+ if e.instance_of? ServiceError
+ return failure(e)
+ else
+ @logger.warn("Can't advertise brokered service label=#{request.label}: #{fmt_error(e)}")
+ return internal_fail
+ end
+ end
+
+ def delete_brokered_service(label)
+ @logger.debug("Delete brokerd service: label=#{label}")
+ name, version = VCAP::Services::Api::Util.parse_label(label)
+ # Fetch all labels with given name
+ bsvcs = BrokeredService.all(:name => name, :version => version)
+
+ raise ServiceError.new(ServiceError::NOT_FOUND, "label #{label}") if bsvcs.empty?
+ bsvcs.each do |bsvc|
+ # TODO what if we got 404 error when delete a service?
+ result = delete_offerings(bsvc.label)
+ if not result
+ next
+ end
+ if not bsvc.destroy
+ @logger.error("Can't delete brokered service from local database: #{bsvc.errors.inspect}")
+ end
+ end
+ success()
+ rescue => e
+ if e.instance_of? ServiceError
+ return failure(e)
+ else
+ @logger.warn("Can't delete brokered service label=#{label}: #{fmt_error(e)}")
+ return internal_fail
+ end
+ end
+
+ def advertise_brokered_service_to_cc(offering)
+ @logger.debug("advertise service offering to cloud_controller:#{offering.inspect}")
+ return false unless offering
+
+ req = create_http_request(
+ :head => @cc_req_hdrs,
+ :body => Yajl::Encoder.encode(offering),
+ )
+
+ f = Fiber.current
+ http = EM::HttpRequest.new(@offering_uri).post(req)
+ http.callback { f.resume(http) }
+ http.errback { f.resume(http) }
+ Fiber.yield
+
+ if http.error.empty?
+ if http.response_header.status == 200
+ @logger.info("Successfully advertise offerings #{offering.inspect}")
+ return true
+ else
+ @logger.warn("Failed advertise offerings:#{offering.inspect}, status=#{http.response_header.status}")
+ end
+ else
+ @logger.warn("Failed advertise offerings:#{offering.inspect}: #{http.error}")
+ end
+ return false
+ end
+
+ def delete_offerings(label)
+ return false unless label
+
+ req = create_http_request(:head => @cc_req_hdrs)
+ uri = URI.join(@offering_uri, label)
+ f = Fiber.current
+ http = EM::HttpRequest.new(uri).delete(req)
+ http.callback { f.resume(http) }
+ http.errback { f.resume(http) }
+ Fiber.yield
+
+ if http.error.empty?
+ if http.response_header.status == 200
+ @logger.info("Successfully delete offerings label=#{label}")
+ return true
+ else
+ @logger.warn("Failed delete offerings label=#{label}, status=#{http.response_header.status}")
+ end
+ else
+ @logger.warn("Failed delete offerings label=#{label}: #{http.error}")
+ end
+ return false
+ end
+
+ def provision_brokered_service(request)
+ bsvc = BrokeredService.get(request.label)
+ if bsvc
+ svc = {
+ :data => {:plan => request.plan},
+ :credentials => bsvc.credentials,
+ :service_id => UUIDTools::UUID.random_create.to_s,
+ }
+ @logger.debug("Brokered service provisioned #{svc.inspect}")
+ success(svc)
+ else
+ @logger.warn("Can't find service label=#{request.label}")
+ raise ServiceError.new(ServiceError::NOT_FOUND, req.label)
+ end
+ rescue => e
+ if e.instance_of? ServiceError
+ failure(e)
+ else
+ @logger.warn("Can't provision service label=#{request.label}: #{fmt_error(e)}")
+ internal_fail
+ end
+ end
+
+ def bind_brokered_service_instance(label, instance_id, binding_options, bind_handle=nil)
+ bsvc = BrokeredService.get(label)
+ if bsvc
+ binding = {
+ :configuration => {:data => {:binding_options => binding_options}},
+ :credentials => bsvc.credentials,
+ :service_id => UUIDTools::UUID.random_create.to_s,
+ }
+ @logger.debug("Generate new service binding: #{binding.inspect}")
+ success(binding)
+ else
+ @logger.warn("Can't find service label=#{label}")
+ raise ServiceError.new(ServiceError::NOT_FOUND, label)
+ end
+ rescue => e
+ if e.instance_of? ServiceError
+ failure(e)
+ else
+ @logger.warn("Can't bind service label=#{label}, id=#{instance_id}: #{fmt_error(e)}")
+ internal_fail
+ end
+ end
+
+ def fmt_error(e)
+ "#{e} [#{e.backtrace.join("|")}]"
+ end
+ end
+
+end
41 service_broker/spec/Rakefile
View
@@ -0,0 +1,41 @@
+require 'rake'
+require 'tempfile'
+
+require 'rubygems'
+require 'bundler/setup'
+Bundler.require(:default, :test)
+
+require 'rspec'
+require 'rspec/core/rake_task'
+require 'ci/reporter/rake/rspec'
+
+coverage_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_coverage"))
+reports_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_reports"))
+dump_file = File.join(Dir.tmpdir, "service_broker.rcov")
+ignore_pattern = 'spec,[.]bundle,[/]gems[/]'
+
+ENV['CI_REPORTS'] = reports_dir
+
+desc "Run specs using RCov"
+task "spec:rcov" => ["ci:setup:rspec", "spec:rcov_internal", "convert_rcov_to_clover"]
+
+RSpec::Core::RakeTask.new do |t|
+ t.pattern = "**/*_spec.rb"
+ t.rspec_opts = ["--format", "documentation", "--colour"]
+end
+
+desc "Run specs using RCov (internal, use spec:rcov instead)"
+RSpec::Core::RakeTask.new("spec:rcov_internal") do |t|
+ FileUtils.rm_rf(dump_file)
+ t.pattern = "**/*_spec.rb"
+ t.rspec_opts = ["--format", "progress", "--colour"]
+ t.rcov = true
+ t.rcov_opts = ['--aggregate', dump_file, '--exclude', ignore_pattern, '--output', coverage_dir]
+end
+
+task "convert_rcov_to_clover" do |t|
+ analyzer = File.join(File.dirname(__FILE__), "..", "..", "..", "tests", "common", "rcov_analyzer.rb")
+ clover_output = File.join(coverage_dir, "clover.xml")
+ sh("ruby #{analyzer} #{dump_file} #{ignore_pattern} > #{clover_output}")
+ FileUtils.rm_rf(dump_file)
+end
51 service_broker/spec/service_broker_spec.rb
View
@@ -0,0 +1,51 @@
+# Copyright (c) 2009-2011 VMware, Inc.
+$:.unshift(File.dirname(__FILE__))
+$:.unshift(File.join(File.dirname(__FILE__),'../../base/lib/base'))
+require 'spec_helper'
+require 'service_broker/async_gateway'
+
+module VCAP
+ module Services
+ module ServiceBroker
+ class AsynchronousServiceGateway
+ attr_reader :logger
+ end
+ end
+ end
+end
+
+
+describe "Service Broker" do
+ include Rack::Test::Methods
+
+ def app
+ @gw = VCAP::Services::ServiceBroker::AsynchronousServiceGateway.new(@config)
+ end
+
+ before :all do
+ @config = load_config
+ @rack_env = {
+ "CONTENT_TYPE" => Rack::Mime.mime_type('.json'),
+ "HTTP_X_VCAP_SERVICE_TOKEN" => @config[:token],
+ }
+ @api_version = "poc"
+ end
+
+ it "should return bad request if request type is not json " do
+ EM.run do
+ get "/", params = {}, rack_env = {}
+ last_response.status.should == 400
+ EM.stop
+ end
+ end
+
+ it "should return unauthorize error with mismatch token " do
+ EM.run do
+ @rack_env["HTTP_X_VCAP_SERVICE_TOKEN"] = "foobar"
+ get "/", params = {}, rack_env = @rack_env
+ last_response.status.should == 401
+ EM.stop
+ end
+ end
+
+end
30 service_broker/spec/spec_helper.rb
View
@@ -0,0 +1,30 @@
+# Copyright (c) 2009-2011 VMware, Inc.
+$:.unshift File.join(File.dirname(__FILE__), '..')
+$:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
+
+require 'rubygems'
+require 'rspec'
+require 'rack/test'
+require 'json'
+require 'logger'
+require 'yaml'
+
+$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', '..')
+require 'vcap/common'
+
+def load_config()
+ config_file = File.join(File.dirname(__FILE__), '..', 'config', 'service_broker.yml')
+ config = YAML.load_file(config_file)
+ config = VCAP.symbolize_keys(config)
+ config[:host] = "localhost"
+ config[:port] ||= VCAP.grab_ephemeral_port
+ config[:cloud_controller_uri] = "api.vcap.me"
+ config[:logger] = make_logger()
+ config
+end
+
+def make_logger()
+ logger = Logger.new(STDOUT)
+ logger.level = Logger::ERROR
+ logger
+end
BIN  service_broker/vendor/cache/addressable-2.2.4.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/bcrypt-ruby-2.1.4.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/builder-3.0.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/ci_reporter-1.6.4.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/daemons-1.1.2.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/data_objects-0.10.3.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/datamapper-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/diff-lcs-1.1.2.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-aggregates-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-constraints-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-core-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-do-adapter-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-migrations-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-serializer-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-sqlite-adapter-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-timestamps-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-transactions-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-types-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/dm-validations-1.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/do_sqlite3-0.10.3.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/em-http-request-0.3.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/escape_utils-0.2.3.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/eventmachine-0.12.10.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/fastercsv-1.5.4.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/json-1.4.6.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/json_pure-1.5.1.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/little-plugger-1.1.2.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/logging-1.5.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/nats-0.4.8.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/posix-spawn-0.3.6.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/rack-1.2.2.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/rack-test-0.5.7.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/rake-0.8.7.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/rcov-0.9.9.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/rspec-2.5.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/rspec-core-2.5.1.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/rspec-expectations-2.5.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/rspec-mocks-2.5.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/sinatra-1.2.1.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/stringex-1.2.1.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/thin-1.2.11.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/tilt-1.2.2.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/uuidtools-2.1.2.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/vcap_common-0.99.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/vcap_logging-0.1.0.gem
View
Binary file not shown
BIN  service_broker/vendor/cache/yajl-ruby-0.8.2.gem
View
Binary file not shown
Please sign in to comment.
Something went wrong with that request. Please try again.