Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Marketplace Gateway updates

 - Bundle updates: Add dependencies in vendor/cache
 - Marketplace gateway startup script accepts optional -c config file parameter
 - Remove a lot of unrequired dependencies (such as uuid and sqlite)
 - Change credentials and token for marketplace gateway
 - Collapsed appdirect configuration for appdirect endpoint
 - Read marketplace specific config from the same folder as markerplace_gateway.yml
 - Minor: Realign code, remove extra spaces

Change-Id: I43eed452a12feb903a8f29cd99a18c7cfb18b48d
  • Loading branch information...
commit 66eead35a5f4c494743b3bddc04ed307e89ef814 1 parent 0a9d9cb
Harshawardhan Gadgil authored

Showing 74 changed files with 370 additions and 390 deletions. Show diff stats Hide diff stats

  1. +0 4 marketplace/Gemfile
  2. +4 14 marketplace/Gemfile.lock
  3. +17 8 marketplace/bin/marketplace_gateway
  4. +2 6 marketplace/config/appdirect.yml
  5. +3 1 marketplace/config/marketplace_gateway.yml
  6. +1 1  marketplace/lib/base/common.rb
  7. +12 25 marketplace/lib/base/marketplace_async_gateway.rb
  8. +1 3 marketplace/lib/base/marketplace_base.rb
  9. +161 158 marketplace/lib/marketplaces/appdirect/appdirect_helper.rb
  10. +167 167 marketplace/lib/marketplaces/appdirect/appdirect_marketplace.rb
  11. +1 1  marketplace/spec/appdirect/appdirect_gateway_spec.rb
  12. +1 2  marketplace/spec/appdirect/spec_helper.rb
  13. BIN  marketplace/vendor/cache/addressable-2.2.8.gem
  14. BIN  marketplace/vendor/cache/bcrypt-ruby-2.1.4.gem
  15. BIN  marketplace/vendor/cache/beefcake-0.3.7.gem
  16. BIN  marketplace/vendor/cache/builder-3.0.0.gem
  17. BIN  marketplace/vendor/cache/ci_reporter-1.7.1.gem
  18. BIN  marketplace/vendor/cache/curb-0.7.18.gem
  19. BIN  marketplace/vendor/cache/daemons-1.1.9.gem
  20. BIN  marketplace/vendor/cache/data_objects-0.10.8.gem
  21. BIN  marketplace/vendor/cache/datamapper-1.1.0.gem
  22. BIN  marketplace/vendor/cache/diff-lcs-1.1.3.gem
  23. BIN  marketplace/vendor/cache/dm-aggregates-1.1.0.gem
  24. BIN  marketplace/vendor/cache/dm-constraints-1.1.0.gem
  25. BIN  marketplace/vendor/cache/dm-core-1.1.0.gem
  26. BIN  marketplace/vendor/cache/dm-migrations-1.1.0.gem
  27. BIN  marketplace/vendor/cache/dm-serializer-1.1.0.gem
  28. BIN  marketplace/vendor/cache/dm-timestamps-1.1.0.gem
  29. BIN  marketplace/vendor/cache/dm-transactions-1.1.0.gem
  30. BIN  marketplace/vendor/cache/dm-types-1.1.0.gem
  31. BIN  marketplace/vendor/cache/dm-validations-1.1.0.gem
  32. BIN  marketplace/vendor/cache/do_sqlite3-0.10.8.gem
  33. BIN  marketplace/vendor/cache/em-http-request-1.0.0.beta.3.gem
  34. BIN  marketplace/vendor/cache/em-socksify-0.1.0.gem
  35. BIN  marketplace/vendor/cache/eventmachine_httpserver-0.2.1.gem
  36. BIN  marketplace/vendor/cache/fastercsv-1.5.5.gem
  37. BIN  marketplace/vendor/cache/http_parser.rb-0.5.3.gem
  38. BIN  marketplace/vendor/cache/httpclient-2.2.7.gem
  39. BIN  marketplace/vendor/cache/json-1.4.6.gem
  40. BIN  marketplace/vendor/cache/json_pure-1.7.5.gem
  41. BIN  marketplace/vendor/cache/macaddr-1.6.1.gem
  42. BIN  marketplace/vendor/cache/membrane-0.0.2.gem
  43. BIN  marketplace/vendor/cache/mime-types-1.19.gem
  44. BIN  marketplace/vendor/cache/multi_json-1.3.6.gem
  45. BIN  marketplace/vendor/cache/multipart-post-1.1.5.gem
  46. BIN  marketplace/vendor/cache/nats-0.4.24.gem
  47. BIN  marketplace/vendor/cache/oauth-0.4.6.gem
  48. BIN  marketplace/vendor/cache/posix-spawn-0.3.6.gem
  49. BIN  marketplace/vendor/cache/rack-1.4.1.gem
  50. BIN  marketplace/vendor/cache/rack-test-0.6.1.gem
  51. BIN  marketplace/vendor/cache/rake-0.9.2.2.gem
  52. BIN  marketplace/vendor/cache/redis-3.0.1.gem
  53. BIN  marketplace/vendor/cache/redis-namespace-1.2.1.gem
  54. BIN  marketplace/vendor/cache/redisk-0.2.2.gem
  55. BIN  marketplace/vendor/cache/resque-1.22.0.gem
  56. BIN  marketplace/vendor/cache/resque-status-0.3.3.gem
  57. BIN  marketplace/vendor/cache/rspec-2.11.0.gem
  58. BIN  marketplace/vendor/cache/rspec-core-2.11.1.gem
  59. BIN  marketplace/vendor/cache/rspec-expectations-2.11.2.gem
  60. BIN  marketplace/vendor/cache/rspec-mocks-2.11.2.gem
  61. BIN  marketplace/vendor/cache/ruby-hmac-0.4.0.gem
  62. BIN  marketplace/vendor/cache/rubyzip-0.9.9.gem
  63. BIN  marketplace/vendor/cache/simplecov-0.6.4.gem
  64. BIN  marketplace/vendor/cache/simplecov-html-0.5.3.gem
  65. BIN  marketplace/vendor/cache/simplecov-rcov-0.2.3.gem
  66. BIN  marketplace/vendor/cache/sinatra-1.2.8.gem
  67. BIN  marketplace/vendor/cache/stringex-1.2.2.gem
  68. BIN  marketplace/vendor/cache/systemu-2.5.2.gem
  69. BIN  marketplace/vendor/cache/thin-1.3.1.gem
  70. BIN  marketplace/vendor/cache/tilt-1.3.3.gem
  71. BIN  marketplace/vendor/cache/uuid-2.3.5.gem
  72. BIN  marketplace/vendor/cache/uuidtools-2.1.3.gem
  73. BIN  marketplace/vendor/cache/vegas-0.1.11.gem
  74. BIN  marketplace/vendor/cache/yajl-ruby-0.8.3.gem
4 marketplace/Gemfile
@@ -3,11 +3,7 @@ source :rubygems
3 3 gem 'eventmachine', :git => 'git://github.com/cloudfoundry/eventmachine.git', :branch => 'release-0.12.11-cf'
4 4 gem "em-http-request"
5 5 gem "ruby-hmac"
6   -gem "uuidtools"
7   -gem "datamapper", "~> 1.1.0"
8 6 gem "dm-core", "= 1.1.0"
9   -gem "do_sqlite3"
10   -gem "dm-sqlite-adapter"
11 7 gem "sinatra", "~> 1.2.3"
12 8 gem "oauth"
13 9 gem "json", "~> 1.4.6"
18 marketplace/Gemfile.lock
@@ -73,10 +73,10 @@ GEM
73 73 bcrypt-ruby (2.1.4)
74 74 beefcake (0.3.7)
75 75 builder (3.0.0)
76   - ci_reporter (1.7.0)
  76 + ci_reporter (1.7.1)
77 77 builder (>= 2.1.2)
78 78 curb (0.7.18)
79   - daemons (1.1.8)
  79 + daemons (1.1.9)
80 80 data_objects (0.10.8)
81 81 addressable (~> 2.1)
82 82 datamapper (1.1.0)
@@ -96,18 +96,12 @@ GEM
96 96 dm-core (~> 1.1.0)
97 97 dm-core (1.1.0)
98 98 addressable (~> 2.2.4)
99   - dm-do-adapter (1.1.0)
100   - data_objects (~> 0.10.2)
101   - dm-core (~> 1.1.0)
102 99 dm-migrations (1.1.0)
103 100 dm-core (~> 1.1.0)
104 101 dm-serializer (1.1.0)
105 102 dm-core (~> 1.1.0)
106 103 fastercsv (~> 1.5.4)
107 104 json (~> 1.4.6)
108   - dm-sqlite-adapter (1.1.0)
109   - dm-do-adapter (~> 1.1.0)
110   - do_sqlite3 (~> 0.10.2)
111 105 dm-timestamps (1.1.0)
112 106 dm-core (~> 1.1.0)
113 107 dm-transactions (1.1.0)
@@ -135,7 +129,7 @@ GEM
135 129 http_parser.rb (0.5.3)
136 130 httpclient (2.2.7)
137 131 json (1.4.6)
138   - json_pure (1.7.4)
  132 + json_pure (1.7.5)
139 133 macaddr (1.6.1)
140 134 systemu (~> 2.5.0)
141 135 membrane (0.0.2)
@@ -175,7 +169,7 @@ GEM
175 169 rspec-core (2.11.1)
176 170 rspec-expectations (2.11.2)
177 171 diff-lcs (~> 1.1.3)
178   - rspec-mocks (2.11.1)
  172 + rspec-mocks (2.11.2)
179 173 ruby-hmac (0.4.0)
180 174 rubyzip (0.9.9)
181 175 simplecov (0.6.4)
@@ -206,10 +200,7 @@ PLATFORMS
206 200
207 201 DEPENDENCIES
208 202 ci_reporter
209   - datamapper (~> 1.1.0)
210 203 dm-core (= 1.1.0)
211   - dm-sqlite-adapter
212   - do_sqlite3
213 204 em-http-request
214 205 eventmachine!
215 206 json (~> 1.4.6)
@@ -222,7 +213,6 @@ DEPENDENCIES
222 213 simplecov
223 214 simplecov-rcov
224 215 sinatra (~> 1.2.3)
225   - uuidtools
226 216 vcap_common!
227 217 vcap_logging!
228 218 vcap_services_base!
25 marketplace/bin/marketplace_gateway
@@ -6,15 +6,13 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
6 6 require 'bundler/setup'
7 7 require 'vcap_services_base'
8 8
  9 +require 'optparse'
  10 +
9 11 $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
10 12 require 'base/marketplace_async_gateway'
11 13
12 14 class VCAP::Services::Marketplace::Gateway < VCAP::Services::Base::Gateway
13 15
14   - def default_config_file
15   - File.join(File.dirname(__FILE__), '..', 'config', 'marketplace_gateway.yml')
16   - end
17   -
18 16 def additional_options
19 17 end
20 18
@@ -23,7 +21,18 @@ class VCAP::Services::Marketplace::Gateway < VCAP::Services::Base::Gateway
23 21 end
24 22
25 23 def load_config
26   - config_file = default_config_file
  24 + config_file = File.join(File.dirname(__FILE__), '..', 'config', 'marketplace_gateway.yml')
  25 +
  26 + OptionParser.new do |opts|
  27 + opts.banner = "Usage: $0 [options]"
  28 + opts.on("-c", "--config [ARG]", "Configuration File") do |opt|
  29 + config_file = opt
  30 + end
  31 + opts.on("-h", "--help", "Help") do
  32 + puts opts
  33 + exit
  34 + end
  35 + end.parse!
27 36
28 37 begin
29 38 @config = parse_gateway_config(config_file)
@@ -33,7 +42,7 @@ class VCAP::Services::Marketplace::Gateway < VCAP::Services::Base::Gateway
33 42 end
34 43
35 44 marketplace = @config[:marketplace]
36   - marketplace_config = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'config', "#{marketplace}.yml"))
  45 + marketplace_config = YAML.load_file(File.join(File.dirname(config_file), "#{marketplace}.yml"))
37 46 marketplace_config = VCAP.symbolize_keys(marketplace_config)
38 47
39 48 @config = @config.merge(marketplace_config)
@@ -45,8 +54,8 @@ class VCAP::Services::Marketplace::Gateway < VCAP::Services::Base::Gateway
45 54 setup_pid
46 55
47 56 @config[:host] = VCAP.local_ip(@config[:ip_route])
48   - @config[:port] ||= VCAP.grab_ephemeral_port
49   - @config[:external_uri] = "#{@config[:host]}:#{@config[:port]}"
  57 + @config[:port] ||= VCAP.grab_ephemeral_port
  58 + @config[:url] = "http://#{@config[:host]}:#{@config[:port]}"
50 59
51 60 EM.run do
52 61 sg = async_gateway_class.new(@config)
8 marketplace/config/appdirect.yml
@@ -5,12 +5,8 @@ classname: "VCAP::Services::Marketplace::Appdirect::AppdirectMarketplace"
5 5 node_timeout: 15
6 6
7 7 # appdirect configuration
8   -# Not configured by default as it would require revealing
9   -# credentials to a non-local resource.
10 8 appdirect:
11   - scheme: https
12   - host: "dev3cloudfoundry.appdirect.com"
13   - port: "443"
14   - key: "k"
  9 + endpoint: "https://example.com"
  10 + key: "k"
15 11 secret: "s"
16 12
4 marketplace/config/marketplace_gateway.yml
@@ -2,7 +2,7 @@
2 2 cloud_controller_uri: api.vcap.me
3 3 ip_route: localhost
4 4 index: 0
5   -token: changebrokertoken
  5 +token: changemarketplacetoken
6 6 mbus: nats://localhost:4222
7 7 logging:
8 8 level: debug
@@ -13,3 +13,5 @@ pid: /var/vcap/sys/run/marketplace.pid
13 13 # since this value is specific to interactions with #{marketplace}
14 14
15 15 marketplace: appdirect
  16 +
  17 +acls: [ "*@example.com" ]
2  marketplace/lib/base/common.rb
... ... @@ -1,7 +1,7 @@
1 1 # Copyright (c) 2009-2011 VMware, Inc.
2 2 module VCAP
3 3 module Services
4   - module Marketplace
  4 + module Marketplace
5 5 module Common
6 6 def service_name
7 7 "MarketplaceGateway"
37 marketplace/lib/base/marketplace_async_gateway.rb
@@ -7,20 +7,18 @@ module Services
7 7 module Marketplace
8 8 class MarketplaceAsyncServiceGateway < VCAP::Services::AsynchronousServiceGateway
9 9
10   - REQ_OPTS = %w(mbus external_uri token cloud_controller_uri).map {|o| o.to_sym}
  10 + REQ_OPTS = %w(mbus url token cloud_controller_uri).map {|o| o.to_sym}
11 11
12 12 set :raise_errors, Proc.new {false}
13 13 set :show_exceptions, false
14 14
15   - attr_reader :ready_to_serve
16   -
17 15 def initialize(opts)
18   - super(opts)
  16 + super(opts)
19 17 end
20 18
21 19 def load_marketplace(opts)
22 20 marketplace_lib_path = File.join(File.dirname(__FILE__), '..', 'marketplaces', opts[:marketplace])
23   - @logger.info("Loading marketplace: #{opts[:marketplace]} from: #{marketplace_lib_path}")
  21 + @logger.info("Loading marketplace: #{opts[:marketplace]} from: #{marketplace_lib_path}")
24 22
25 23 $LOAD_PATH.unshift(marketplace_lib_path)
26 24 Dir[marketplace_lib_path + '/*.rb'].each do |file|
@@ -28,9 +26,11 @@ def load_marketplace(opts)
28 26 require f
29 27 end
30 28
31   - # HACK?? Is there a better way?
32   - eval "class VCAP::Services::Marketplace::MarketplaceClient < #{opts[:classname]} ; end"
33   - VCAP::Services::Marketplace::MarketplaceClient.new(opts)
  29 + # To minimize the amount of marketplace-specific code, the config file specifies the class that
  30 + # implements MarketplaceBase's abstract methods for this marketplace. So we need to translate the
  31 + # name of the class into the actual class object, and then create an instance of it.
  32 + klass = eval(opts[:classname])
  33 + klass.new(opts)
34 34 end
35 35
36 36 def setup(opts)
@@ -43,22 +43,13 @@ def setup(opts)
43 43 @logger = opts[:logger] || make_logger()
44 44 @token = opts[:token]
45 45 @hb_interval = opts[:heartbeat_interval] || 60
46   - @cld_ctrl_uri = http_uri(opts[:cloud_controller_uri] || default_cloud_controller_uri)
47   - @external_uri = opts[:external_uri]
  46 + @cld_ctrl_uri = http_uri(opts[:cloud_controller_uri] || "api.vcap.me")
48 47 @offering_uri = "#{@cld_ctrl_uri}/services/v1/offerings/"
49   - @router_start_channel = nil
50 48 @proxy_opts = opts[:proxy]
51 49 @handle_fetched = true # set to true in order to compatible with base asycn gateway.
52 50
53 51 @marketplace_client = load_marketplace(opts)
54 52
55   - @router_register_json = {
56   - :host => @host,
57   - :port => @port,
58   - :uris => [ @external_uri ],
59   - :tags => {:components => @marketplace_client.name},
60   - }.to_json
61   -
62 53 @catalog = {}
63 54
64 55 token_hdrs = VCAP::Services::Api::GATEWAY_TOKEN_HEADER
@@ -150,7 +141,6 @@ def on_exit(stop_event_loop=true)
150 141 # Binding a service
151 142 post "/gateway/v1/configurations/:service_id/handles" do
152 143 @logger.info("Got request_body=#{request_body}")
153   - #req = JSON.parse(request_body)
154 144 req = VCAP::Services::Api::GatewayBindRequest.decode(request_body)
155 145 @logger.info("Binding request for service=#{params['service_id']} options=#{req.inspect}")
156 146
@@ -203,7 +193,7 @@ def on_exit(stop_event_loop=true)
203 193 helpers do
204 194
205 195 def advertise_service_to_cc(offering)
206   - @logger.debug("advertise service offering to cloud_controller:#{offering.inspect}")
  196 + @logger.debug("advertise service offering #{offering.inspect} to cloud_controller: #{@offering_uri}")
207 197 return false unless offering
208 198
209 199 req = create_http_request(
@@ -213,9 +203,7 @@ def advertise_service_to_cc(offering)
213 203
214 204 f = Fiber.current
215 205 http = EM::HttpRequest.new(@offering_uri).post(req)
216   - http.callback do
217   - f.resume(http)
218   - end
  206 + http.callback { f.resume(http) }
219 207 http.errback { f.resume(http) }
220 208 Fiber.yield
221 209
@@ -259,10 +247,9 @@ def delete_offerings(label)
259 247 def fmt_error(e)
260 248 "#{e} [#{e.backtrace.join("|")}]"
261 249 end
262   -
263 250 end
  251 +
264 252 end
265 253 end
266 254 end
267 255 end
268   -
4 marketplace/lib/base/marketplace_base.rb
@@ -4,8 +4,6 @@ module Services
4 4 module Marketplace
5 5 class Base
6 6
7   - VMWARE_ACLS = ["*@vmware.com", "*@rbcon.com"]
8   -
9 7 def initialize(opts_unused)
10 8 end
11 9
@@ -16,7 +14,7 @@ def name
16 14 def get_catalog
17 15 {}
18 16 end
19   -
  17 +
20 18 def generate_cc_advertise_request(name, bsvc, active = true)
21 19 {}
22 20 end
319 marketplace/lib/marketplaces/appdirect/appdirect_helper.rb
@@ -7,200 +7,203 @@ module VCAP
7 7 module Services
8 8 module Marketplace
9 9 module Appdirect
10   - class AppdirectHelper
  10 + class AppdirectHelper
11 11
12   - include VCAP::Services::Marketplace::Appdirect
  12 + include VCAP::Services::Marketplace::Appdirect
13 13
14   - OFFERINGS_PATH = "custom/cloudfoundry/v1/offerings"
15   - SERVICES_PATH = "custom/cloudfoundry/v1/services"
  14 + OFFERINGS_PATH = "custom/cloudfoundry/v1/offerings"
  15 + SERVICES_PATH = "custom/cloudfoundry/v1/services"
16 16
17   - HEADER = {"Content-Type" => "application/json" , "Accept"=>"application/json"}
  17 + HEADER = {"Content-Type" => "application/json" , "Accept"=>"application/json"}
  18 + REQ_CONFIG = %w(endpoint key secret).map {|o| o.to_sym}
18 19
19   - def initialize(appdirect_config, logger)
20   - @logger = logger
  20 + def initialize(opts, logger)
  21 + @logger = logger
21 22
22   - raise("No appdirect config section provided in: #{appdirect_config.inspect}") unless appdirect_config[:appdirect]
  23 + raise("No appdirect config section provided in: #{opts.inspect}") unless opts[:appdirect]
23 24
24   - @scheme = appdirect_config[:appdirect][:scheme] || raise("No scheme provided")
25   - @host = appdirect_config[:appdirect][:host] || raise("No host provided")
26   - @appdirect_key = appdirect_config[:appdirect][:key] || raise("No Key Provided")
27   - @appdirect_secret = appdirect_config[:appdirect][:secret] || raise("No secret provided") unless @appdirect_secret
  25 + appdirect_config = opts[:appdirect]
28 26
29   - @consumer = OAuth::Consumer.new(@appdirect_key, @appdirect_secret)
30   - @access_token = OAuth::AccessToken.new(@consumer)
31   - end
  27 + missing_opts = REQ_CONFIG.select {|o| !appdirect_config.has_key? o}
  28 + raise ArgumentError, "Missing options: #{missing_opts.join(', ')}" unless missing_opts.empty?
32 29
33   - def get_catalog
34   - catalog = nil
35   - http = get_catalog_response
36   - if http.error.empty?
37   - if http.response_header.http_status == 200
38   - @logger.debug("Got catalog response #{http.response}")
39   - data = JSON.parse(http.response) #VCAP::Services::AppDirect::AppDirectCatalogResponse.decode(raw)
40   - catalog = {}
41   - data.each do |service|
42   - # Add checks for specific categories which determine whether the addon should be listed on cc
43   - @logger.debug("Got service '#{service["id"]}' from AppDirect")
44   - catalog[service["id"]] = service
45   - end
46   - @logger.info("Got #{catalog.keys.count} services from AppDirect")
47   - else
48   - @logger.warn("Failed to get catalog #{http.response}")
  30 + @appdirect_endpoint = appdirect_config[:endpoint]
  31 +
  32 + @consumer = OAuth::Consumer.new(appdirect_config[:key], appdirect_config[:secret])
  33 + @access_token = OAuth::AccessToken.new(@consumer)
49 34 end
50   - else
51   - @logger.warn("Failed to get catalog: #{http.error}")
52   - raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_GET_LISTING, http.response_header.status)
53   - end
54   - return catalog
55   - end
56 35
57   - def purchase_service(order)
58   - new_serv = nil
59   - # TODO: Order needs to include UUID for User, Organization, AppSpace
60   - if order
61   - body = order.to_json
62   - http = post_order(body)
63   - if http.error.empty?
64   - if http.response_header.status >= 200 and http.response_header.status < 300
65   - new_serv = JSON.parse(http.response)
66   - return new_serv
  36 + def get_catalog
  37 + catalog = nil
  38 + http = get_catalog_response
  39 + if http.error.empty?
  40 + if http.response_header.http_status == 200
  41 + @logger.debug("Got catalog response #{http.response}")
  42 + data = JSON.parse(http.response) #VCAP::Services::AppDirect::AppDirectCatalogResponse.decode(raw)
  43 + catalog = {}
  44 + data.each do |service|
  45 + # Add checks for specific categories which determine whether the addon should be listed on cc
  46 + @logger.debug("Got service '#{service["id"]}' from AppDirect")
  47 + catalog[service["id"]] = service
  48 + end
  49 + @logger.info("Got #{catalog.keys.count} services from AppDirect")
  50 + else
  51 + @logger.warn("Failed to get catalog #{http.response}")
  52 + end
67 53 else
68   - # 400 bad request
69   - # 500 if AppDirect has issues
70   - # 503 if ISV is down
71   - @logger.warn("Bad status code posting #{body} was #{http.response}")
72   - raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_PURCHASE, http.response_header.status)
  54 + @logger.warn("Failed to get catalog: #{http.error}")
  55 + raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_GET_LISTING, http.response_header.status)
73 56 end
74   - else
75   - @logger.warn("Error raised: #{http.error}")
76   - raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_PURCHASE, http.response_header.status)
  57 + return catalog
77 58 end
78   - else
79   - @logger.error("Order is required to purchase a service")
80   - end
81   - new_serv
82   - end
83 59
84   - def bind_service(order, order_id)
85   - update_serv = nil
86   - if order and order_id
87   - body = order.to_json
88   - http = post_bind_service(body, order_id)
89   -
90   - if http.error.empty?
91   - if http.response_header.status >= 200 and http.response_header.status < 300
92   - @logger.debug("Got http headers #{http.headers}")
93   - update_serv = JSON.parse(http.response)
94   - @logger.debug("Bound service #{order_id}")
  60 + def purchase_service(order)
  61 + new_serv = nil
  62 + # TODO: Order needs to include UUID for User, Organization, AppSpace
  63 + if order
  64 + body = order.to_json
  65 + http = post_order(body)
  66 + if http.error.empty?
  67 + if http.response_header.status >= 200 and http.response_header.status < 300
  68 + new_serv = JSON.parse(http.response)
  69 + return new_serv
  70 + else
  71 + # 400 bad request
  72 + # 500 if AppDirect has issues
  73 + # 503 if ISV is down
  74 + @logger.warn("Bad status code posting #{body} was #{http.response}")
  75 + raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_PURCHASE, http.response_header.status)
  76 + end
  77 + else
  78 + @logger.warn("Error raised: #{http.error}")
  79 + raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_PURCHASE, http.response_header.status)
  80 + end
95 81 else
96   - raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_BIND, http.response_header.status)
  82 + @logger.error("Order is required to purchase a service")
97 83 end
98   - else
99   - @logger.warn("Error raised: #{http.error}")
100   - raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_BIND, http.response_header.status)
  84 + new_serv
101 85 end
102   - else
103   - @logger.error("Order and Order Id are required to cancel a service")
104   - end
105   - update_serv
106   - end
107 86
108   - def unbind_service(order_id, binding_id)
109   - update_binding = false
110   - if binding_id and order_id
111   - http = delete_bind_service(order_id, binding_id)
  87 + def bind_service(order, order_id)
  88 + update_serv = nil
  89 + if order and order_id
  90 + body = order.to_json
  91 + http = post_bind_service(body, order_id)
  92 +
  93 + if http.error.empty?
  94 + if http.response_header.status >= 200 and http.response_header.status < 300
  95 + @logger.debug("Got http headers #{http.headers}")
  96 + update_serv = JSON.parse(http.response)
  97 + @logger.debug("Bound service #{order_id}")
  98 + else
  99 + raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_BIND, http.response_header.status)
  100 + end
  101 + else
  102 + @logger.warn("Error raised: #{http.error}")
  103 + raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_BIND, http.response_header.status)
  104 + end
  105 + else
  106 + @logger.error("Order and Order Id are required to cancel a service")
  107 + end
  108 + update_serv
  109 + end
112 110
113   - if http.error.empty?
114   - if http.response_header.status >= 200 and http.response_header.status < 300
115   - update_binding = true
  111 + def unbind_service(order_id, binding_id)
  112 + update_binding = false
  113 + if binding_id and order_id
  114 + http = delete_bind_service(order_id, binding_id)
  115 +
  116 + if http.error.empty?
  117 + if http.response_header.status >= 200 and http.response_header.status < 300
  118 + update_binding = true
  119 + else
  120 + @logger.warn("Invalid status code returned: #{http.response_header.status}")
  121 + raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_UNBIND, http.response_header.status)
  122 + end
  123 + else
  124 + @logger.warn("Error raised: #{http.error}")
  125 + raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_UNBIND, http.response_header.status)
  126 + end
116 127 else
117   - @logger.warn("Invalid status code returned: #{http.response_header.status}")
118   - raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_UNBIND, http.response_header.status)
  128 + @logger.error("Binding Id and Order Id are required to cancel a service")
119 129 end
120   - else
121   - @logger.warn("Error raised: #{http.error}")
122   - raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_UNBIND, http.response_header.status)
  130 + update_binding
123 131 end
124   - else
125   - @logger.error("Binding Id and Order Id are required to cancel a service")
126   - end
127   - update_binding
128   - end
129 132
130   - def cancel_service(order_id)
131   - cancel_serv = false
132   - if order_id
133   - http = delete_order(order_id)
134   - if http.response_header.status >= 200 and http.response_header.status < 300
135   - @logger.debug("Deleted #{order_id}")
136   - cancel_serv = true
137   - else
138   - @logger.warn("Invalid status code returned: #{http.response_header.status}")
139   - raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_CANCEL, http.response_header.status)
  133 + def cancel_service(order_id)
  134 + cancel_serv = false
  135 + if order_id
  136 + http = delete_order(order_id)
  137 + if http.response_header.status >= 200 and http.response_header.status < 300
  138 + @logger.debug("Deleted #{order_id}")
  139 + cancel_serv = true
  140 + else
  141 + @logger.warn("Invalid status code returned: #{http.response_header.status}")
  142 + raise AppdirectError.new(AppDirectError::APPDIRECT_ERROR_CANCEL, http.response_header.status)
  143 + end
  144 + else
  145 + @logger.error("Order Id is required to cancel a service")
  146 + end
  147 + cancel_serv
140 148 end
141   - else
142   - @logger.error("Order Id is required to cancel a service")
143   - end
144   - cancel_serv
145   - end
146 149
147   - private
148   - def get_catalog_response
149   - url = "#{@scheme}://#{@host}/api/#{OFFERINGS_PATH}"
150   - @logger.debug("About to get service listing from #{url}")
151   - f = Fiber.current
152   - http = EventMachine::HttpRequest.new(url).get(:head => HEADER)
153   - http.errback {f.resume(http)}
154   - http.callback {f.resume(http)}
  150 + private
  151 + def get_catalog_response
  152 + url = "#{@appdirect_endpoint}/api/#{OFFERINGS_PATH}"
  153 + @logger.debug("About to get service listing from #{url}")
  154 + f = Fiber.current
  155 + http = EventMachine::HttpRequest.new(url).get(:head => HEADER)
  156 + http.errback {f.resume(http)}
  157 + http.callback {f.resume(http)}
155 158
156   - return Fiber.yield
157   - end
  159 + return Fiber.yield
  160 + end
158 161
159 162
160   - def post_order(body)
161   - url = "#{@scheme}://#{@host}/api/#{SERVICES_PATH}"
162   - @logger.info("About to post #{url}")
163   - f = Fiber.current
164   - http = EventMachine::HttpRequest.new(url).post(:head => HEADER, :body => body)
165   - http.errback {f.resume(http)}
166   - http.callback {f.resume(http)}
  163 + def post_order(body)
  164 + url = "#{@appdirect_endpoint}/api/#{SERVICES_PATH}"
  165 + @logger.info("About to post #{url}")
  166 + f = Fiber.current
  167 + http = EventMachine::HttpRequest.new(url).post(:head => HEADER, :body => body)
  168 + http.errback {f.resume(http)}
  169 + http.callback {f.resume(http)}
167 170
168   - return Fiber.yield
169   - end
  171 + return Fiber.yield
  172 + end
170 173
171 174
172   - def delete_order(order_id)
173   - url = "#{@scheme}://#{@host}/api/#{SERVICES_PATH}/#{order_id}"
174   - @logger.info("About to delete #{url}")
175   - f = Fiber.current
176   - http = EventMachine::HttpRequest.new(url).delete(:head => HEADER)
177   - http.errback {f.resume(http)}
178   - http.callback {f.resume(http)}
  175 + def delete_order(order_id)
  176 + url = "#{@appdirect_endpoint}/api/#{SERVICES_PATH}/#{order_id}"
  177 + @logger.info("About to delete #{url}")
  178 + f = Fiber.current
  179 + http = EventMachine::HttpRequest.new(url).delete(:head => HEADER)
  180 + http.errback {f.resume(http)}
  181 + http.callback {f.resume(http)}
179 182
180   - return Fiber.yield
181   - end
  183 + return Fiber.yield
  184 + end
182 185
183   - def post_bind_service(body, order_id)
184   - url = "#{@scheme}://#{@host}/api/#{SERVICES_PATH}/#{order_id}/bindings"
185   - @logger.info("About to post #{url}")
186   - f = Fiber.current
187   - http = EventMachine::HttpRequest.new(url).post(:head => HEADER, :body => body)
188   - http.errback {f.resume(http)}
189   - http.callback {f.resume(http)}
  186 + def post_bind_service(body, order_id)
  187 + url = "#{@appdirect_endpoint}/api/#{SERVICES_PATH}/#{order_id}/bindings"
  188 + @logger.info("About to post #{url}")
  189 + f = Fiber.current
  190 + http = EventMachine::HttpRequest.new(url).post(:head => HEADER, :body => body)
  191 + http.errback {f.resume(http)}
  192 + http.callback {f.resume(http)}
190 193
191   - return Fiber.yield
192   - end
  194 + return Fiber.yield
  195 + end
193 196
194   - def delete_bind_service(order_id, binding_id)
195   - url = "#{@scheme}://#{@host}/api/#{SERVICES_PATH}/#{order_id}/bindings/#{binding_id}"
196   - @logger.info("About to delete binding #{url}")
197   - f = Fiber.current
198   - http = EventMachine::HttpRequest.new(url).delete(:head => HEADER)
199   - http.errback {f.resume(http)}
200   - http.callback {f.resume(http)}
  197 + def delete_bind_service(order_id, binding_id)
  198 + url = "#{@appdirect_endpoint}/api/#{SERVICES_PATH}/#{order_id}/bindings/#{binding_id}"
  199 + @logger.info("About to delete binding #{url}")
  200 + f = Fiber.current
  201 + http = EventMachine::HttpRequest.new(url).delete(:head => HEADER)
  202 + http.errback {f.resume(http)}
  203 + http.callback {f.resume(http)}
201 204
202   - return Fiber.yield
203   - end
  205 + return Fiber.yield
  206 + end
204 207
205 208 end
206 209 end
334 marketplace/lib/marketplaces/appdirect/appdirect_marketplace.rb
... ... @@ -1,7 +1,6 @@
1 1 # Copyright (c) 2009-2012 VMware, Inc.
2 2 require 'fiber'
3 3 require 'dm-types'
4   -require 'nats/client'
5 4 require 'service_error'
6 5
7 6 $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', '..', '..')
@@ -13,172 +12,173 @@ module Marketplace
13 12 module Appdirect
14 13 class AppdirectMarketplace < VCAP::Services::Marketplace::Base
15 14
16   - include VCAP::Services::Base::Error
17   - include VCAP::Services::Marketplace::Appdirect
18   -
19   - def initialize(opts)
20   - super(opts)
21   -
22   - @logger = opts[:logger]
23   - @external_uri = opts[:external_uri]
24   - @node_timeout = opts[:node_timeout]
25   - @helper = AppdirectHelper.new(opts, @logger)
26   - end
27   -
28   - def name
29   - "AppDirect"
30   - end
31   -
32   - def get_catalog
33   - @helper.get_catalog
34   - end
35   -
36   - def generate_cc_advertise_request(name, bsvc, active = true)
37   - req = {}
38   - req[:label] = "#{name}-#{bsvc["version"]}"
39   - req[:active] = active && bsvc["active"]
40   - req[:description] = bsvc["description"]
41   -
42   - req[:supported_versions] = [ bsvc["version"] ]
43   - req[:version_aliases] = { "current" => bsvc["version"] }
44   -
45   - if bsvc["developers"] and bsvc["developers"].count > 0
46   - acls = []
47   - bsvc["developers"].each do |dev|
48   - acls << dev["email"]
49   - end
50   - req[:acls] = {}
51   - req[:acls][:wildcards] = VMWARE_ACLS
52   - req[:acls][:users] = acls
53   - end
54   -
55   - req[:url] = "http://#{@external_uri}"
56   -
57   - if bsvc["plans"] and bsvc["plans"].count > 0
58   - req[:plans] = []
59   - bsvc["plans"].each do |plan|
60   - req[:plans] << plan["id"]
61   - # No plan options yet
62   - end
63   - else
64   - req[:plans] = ["default"]
65   - end
66   -
67   - # No tags coming from AppDirect yet
68   - req[:tags] = ["default"]
69   - req[:timeout] = 5 + @node_timeout
70   - req
71   - end
72   -
73   - def provision_service(request_body)
74   - request = VCAP::Services::Api::GatewayProvisionRequest.decode(request_body)
75   - @logger.info("Provision request for label=#{request.label} plan=#{request.plan}, version=#{request.version}")
76   -
77   - id,version = request.label.split("-")
78   -
79   - order = {
80   - "user" => {
81   - "uuid" => nil,
82   - "email" => request.email
83   - },
84   - "offering" => {
85   - "id" => id,
86   - "version" => request.version || version
87   - },
88   - "configuration" => {
89   - "plan" => request.plan,
90   - "name" => request.name,
91   - "options" => {}
92   - }
93   - }
94   - receipt = @helper.purchase_service(order)
95   -
96   - if receipt
97   - @logger.debug("AppDirect service provisioned #{receipt.inspect}")
98   - credentials = receipt["credentials"] || {}
99   - credentials["name"] = receipt["id"] #id of service within the 3rd party ISV
100   - #We could store more info in credentials but these will never be used by apps or users
101   - svc = {
102   - :configuration => {:plan => request.plan, :name => request.name, :options => {} },
103   - :credentials => credentials,
104   - :service_id => receipt["uuid"],
105   - }
106   - success(svc)
107   - else
108   - @logger.warn("Invalid response to provision service label=#{request.label}")
109   - raise ServiceError.new(ServiceError::INTERNAL_ERROR, "Missing request -- cannot perform operation")
110   - end
111   - rescue => e
112   - if e.instance_of? ServiceError
113   - failure(e)
114   - else
115   - @logger.debug(e.inspect)
116   - @logger.warn("Can't provision service label=#{request.label}: #{fmt_error(e)}")
117   - internal_fail
118   - end
119   - end
120   -
121   - def unprovision_service(service_id)
122   - success = @helper.cancel_service(service_id)
123   - if success
124   - @logger.info("Successfully unprovisioned service #{service_id}")
125   - else
126   - @logger.info("Failed to unprovision service #{service_id}")
127   - end
128   - return success
129   - rescue => e
130   - if e.instance_of? ServiceError
131   - failure(e)
132   - else
133   - @logger.warn("Can't unprovision service service_id=#{service_id}: #{fmt_error(e)}")
134   - internal_fail
135   - end
136   - end
137   -
138   - def bind_service_instance(service_id, request)
139   - if service_id and request
140   - order = {
141   - "options" => request.binding_options
142   - }
143   - resp = @helper.bind_service(order, service_id)
144   - @logger.debug("Got response from AppDirect: #{resp.inspect}")
145   - binding = {
146   - :configuration => {:data => {:binding_options => request.binding_options}},
147   - :credentials => resp["credentials"],
148   - :service_id => resp["uuid"], #Important this is the binding_id
149   - }
150   - @logger.debug("Generated binding for CC: #{binding.inspect}")
151   - success(binding)
152   - else
153   - @logger.warn("Can't find service label=#{label}")
154   - raise ServiceError.new(ServiceError::INTERNAL_ERROR, "Missing request or service_id -- cannot perform operation")
155   - end
156   - rescue => e
157   - if e.instance_of? ServiceError
158   - failure(e)
159   - else
160   - @logger.warn("Can't bind service service_id=#{service_id}, request=#{request}: #{fmt_error(e)}")
161   - internal_fail
162   - end
163   - end
164   -
165   - def unbind_service(service_id, binding_id)
166   - success = @helper.unbind_service(service_id, binding_id)
167   - if success
168   - @logger.info("Successfully unbound service #{service_id} and binding id #{binding_id}")
169   - else
170   - @logger.info("Failed to unbind service #{service_id} and binding id #{binding_id}")
171   - end
172   - return success
173   - rescue => e
174   - if e.instance_of? ServiceError
175   - failure(e)
176   - else
177   - @logger.warn("Can't unprovision service service_id=#{service_id}: #{fmt_error(e)}")
178   - internal_fail
179   - end
180   - end
181   -
  15 + include VCAP::Services::Base::Error
  16 + include VCAP::Services::Marketplace::Appdirect
  17 +
  18 + def initialize(opts)
  19 + super(opts)
  20 +
  21 + @logger = opts[:logger]
  22 + @url = opts[:url]
  23 + @node_timeout = opts[:node_timeout]
  24 + @acls = opts[:acls]
  25 + @helper = AppdirectHelper.new(opts, @logger)
  26 + end
  27 +
  28 + def name
  29 + "AppDirect"
  30 + end
  31 +
  32 + def get_catalog
  33 + @helper.get_catalog
  34 + end
  35 +
  36 + def generate_cc_advertise_request(name, bsvc, active = true)
  37 + req = {}
  38 + req[:label] = "#{name}-#{bsvc["version"]}"
  39 + req[:active] = active && bsvc["active"]
  40 + req[:description] = bsvc["description"]
  41 +
  42 + req[:supported_versions] = [ bsvc["version"] ]
  43 + req[:version_aliases] = { "current" => bsvc["version"] }
  44 +
  45 + if bsvc["developers"] and bsvc["developers"].count > 0
  46 + acls = []
  47 + bsvc["developers"].each do |dev|
  48 + acls << dev["email"]
  49 + end
  50 + req[:acls] = {}
  51 + req[:acls][:wildcards] = @acls
  52 + req[:acls][:users] = acls
  53 + end
  54 +
  55 + req[:url] = @url
  56 +
  57 + if bsvc["plans"] and bsvc["plans"].count > 0
  58 + req[:plans] = []
  59 + bsvc["plans"].each do |plan|
  60 + req[:plans] << plan["id"]
  61 + # No plan options yet
  62 + end
  63 + else
  64 + req[:plans] = ["default"]
  65 + end
  66 +
  67 + req[:tags] = ["default"] # No tags coming from AppDirect yet
  68 + req[:timeout] = 5 + @node_timeout
  69 + req
  70 + end
  71 +
  72 + def provision_service(request_body)
  73 + request = VCAP::Services::Api::GatewayProvisionRequest.decode(request_body)
  74 + @logger.info("Provision request for label=#{request.label} plan=#{request.plan}, version=#{request.version}")
  75 +
  76 + id,version = request.label.split("-")
  77 +
  78 + order = {
  79 + "user" => {
  80 + "uuid" => nil,
  81 + "email" => request.email
  82 + },
  83 + "offering" => {
  84 + "id" => id,
  85 + "version" => request.version || version
  86 + },
  87 + "configuration" => {
  88 + "plan" => request.plan,
  89 + "name" => request.name,
  90 + "options" => {}
  91 + }
  92 + }
  93 + receipt = @helper.purchase_service(order)
  94 +
  95 + if receipt
  96 + @logger.debug("AppDirect service provisioned #{receipt.inspect}")
  97 + credentials = receipt["credentials"] || {}
  98 + credentials["name"] = receipt["id"] #id of service within the 3rd party ISV
  99 + #We could store more info in credentials but these will never be used by apps or users
  100 + svc = {
  101 + :configuration => {:plan => request.plan, :name => request.name, :options => {} },
  102 + :credentials => credentials,
  103 + :service_id => receipt["uuid"],
  104 + }
  105 + success(svc)
  106 + else
  107 + @logger.warn("Invalid response to provision service label=#{request.label}")
  108 + raise ServiceError.new(ServiceError::INTERNAL_ERROR, "Missing request -- cannot perform operation")
  109 + end
  110 + rescue => e
  111 + if e.instance_of? ServiceError
  112 + failure(e)
  113 + else
  114 + @logger.debug(e.inspect)
  115 + @logger.warn("Can't provision service label=#{request.label}: #{fmt_error(e)}")
  116 + internal_fail
  117 + end
  118 + end
  119 +
  120 + def unprovision_service(service_id)
  121 + success = @helper.cancel_service(service_id)
  122 + if success
  123 + @logger.info("Successfully unprovisioned service #{service_id}")
  124 + else
  125 + @logger.info("Failed to unprovision service #{service_id}")
  126 + end
  127 + return success
  128 + rescue => e
  129 + if e.instance_of? ServiceError
  130 + failure(e)
  131 + else
  132 + @logger.warn("Can't unprovision service service_id=#{service_id}: #{fmt_error(e)}")
  133 + internal_fail
  134 + end
  135 + end
  136 +
  137 + def bind_service_instance(service_id, request)
  138 + if service_id and request
  139 + order = {
  140 + "options" => request.binding_options
  141 + }
  142 + resp = @helper.bind_service(order, service_id)
  143 + @logger.debug("Got response from AppDirect: #{resp.inspect}")
  144 + binding = {
  145 + :configuration => {:data => {:binding_options => request.binding_options}},
  146 + :credentials => resp["credentials"],
  147 + :service_id => resp["uuid"], #Important this is the binding_id
  148 + }
  149 + @logger.debug("Generated binding for CC: #{binding.inspect}")
  150 + success(binding)
  151 + else
  152 + @logger.warn("Can't find service label=#{label}")
  153 + raise ServiceError.new(ServiceError::INTERNAL_ERROR, "Missing request or service_id -- cannot perform operation")
  154 + end
  155 + rescue => e
  156 + if e.instance_of? ServiceError
  157 + failure(e)
  158 + else
  159 + @logger.warn("Can't bind service service_id=#{service_id}, request=#{request}: #{fmt_error(e)}")
  160 + internal_fail
  161 + end
  162 + end
  163 +
  164 + def unbind_service(service_id, binding_id)
  165 + success = @helper.unbind_service(service_id, binding_id)
  166 + begin
  167 + if success
  168 + @logger.info("Successfully unbound service #{service_id} and binding id #{binding_id}")
  169 + else
  170 + @logger.info("Failed to unbind service #{service_id} and binding id #{binding_id}")
  171 + end
  172 + return success
  173 + rescue => e
  174 + if e.instance_of? ServiceError
  175 + failure(e)
  176 + else
  177 + @logger.warn("Can't unprovision service service_id=#{service_id}: #{fmt_error(e)}")
  178 + internal_fail
  179 + end
  180 + end
  181 + end
182 182 end
183 183 end
184 184 end
2  marketplace/spec/appdirect/appdirect_gateway_spec.rb
@@ -30,7 +30,7 @@ class MarketplaceAsyncServiceGateway
30 30 puts "Initializing..."
31 31 sleep 1
32 32 end
33   -
  33 +
34 34 @app_session = Rack::Test::Session.new(Rack::MockSession.new(@gateway))
35 35
36 36 @rack_env = {
3  marketplace/spec/appdirect/spec_helper.rb
@@ -77,11 +77,10 @@ def load_config()
77 77 appdirect_config = symbolize_keys(appdirect_config)
78 78
79 79 config = config.merge(appdirect_config)
80   - config[:external_uri] =