Browse files

Services release R5

Merge branch 'services-r5'

Change-Id: I8447924d6180a708680d8fd18c38dc19d50dfe80
  • Loading branch information...
1 parent 6dbbbbf commit d95ddd5af1444edc4aeba7fae4d4f4c417d7b0cc @kushmerick kushmerick committed Sep 26, 2011
View
1 AUTHORS
@@ -10,6 +10,7 @@
---
- Adam C. Greenfield { email: adam.greenfield@gmail.com }
- Ananthan Srinivasan { email: asrinivasan@vmware.com, name: AB}
+- Andrew Liu { email: aliu@vmware.com}
- Christian Dupuis { email: cdupuis@vmware.com}
- Derek Collison { email: dcollison@vmware.com }
- Ezra Zygmuntowicz { email: ez@vmware.com }
View
4 bin/services/service_broker
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+# Copyright (c) 2009-2011 VMware, Inc.
+
+exec(File.expand_path("../../../services/service_broker/bin/service_broker", __FILE__), *ARGV)
View
40 cloud_controller/app/controllers/services_controller.rb
@@ -7,7 +7,7 @@ class ServicesController < ApplicationController
include ServicesHelper
before_filter :validate_content_type
- before_filter :require_service_auth_token, :only => [:create, :delete, :update_handle, :list_handles]
+ before_filter :require_service_auth_token, :only => [:create, :delete, :update_handle, :list_handles, :list_brokered_services]
before_filter :require_user, :only => [:provision, :bind, :bind_external, :unbind, :unprovision]
rescue_from(JsonMessage::Error) {|e| render :status => 400, :json => {:errors => e.to_s}}
@@ -37,14 +37,25 @@ def create
# :acls, but to avoid breaking anything as a side efffect, we do
# this only for :acls.
attrs[:acls] = nil unless attrs.has_key?(:acls)
+
+ # Similar to acls, do the same for timeout
+ attrs[:timeout] = nil unless attrs.has_key?(:timeout)
+
svc.update_attributes!(attrs)
else
# Service doesn't exist yet. This can only happen for builtin services since service providers must
# register with us to get a token.
+ # or, it's a brokered service
svc = Service.new(req.extract)
- raise CloudError.new(CloudError::FORBIDDEN) unless svc.is_builtin? && svc.verify_auth_token(@service_auth_token)
- svc.token = @service_auth_token
- svc.save!
+ if AppConfig[:service_broker] and @service_auth_token == AppConfig[:service_broker][:token] and !svc.is_builtin?
+ attrs = req.extract.dup
+ attrs[:token] = @service_auth_token
+ svc.update_attributes!(attrs)
+ else
+ raise CloudError.new(CloudError::FORBIDDEN) unless svc.is_builtin? && svc.verify_auth_token(@service_auth_token)
+ svc.token = @service_auth_token
+ svc.save!
+ end
end
render :json => {}
@@ -107,6 +118,25 @@ def list_handles
render :json => {:handles => handles}
end
+ # List brokered services
+ def list_brokered_services
+ if AppConfig[:service_broker].nil? or @service_auth_token != AppConfig[:service_broker][:token]
+ raise CloudError.new(CloudError::FORBIDDEN)
+ end
+
+ svcs = Service.all
+ brokered_svcs = svcs.select {|svc| ! svc.is_builtin? }
+ result = []
+ brokered_svcs.each do |svc|
+ result << {
+ :label => svc.label,
+ :description => svc.description,
+ :acls => svc.acls,
+ }
+ end
+ render :json => {:brokered_services => result}
+ end
+
# Unregister a service offering with the CC
#
def delete
@@ -122,7 +152,7 @@ def delete
# Asks the gateway to provision an instance of the requested service
#
def provision
- req = VCAP::Services::Api::ProvisionRequest.decode(request_body)
+ req = VCAP::Services::Api::CloudControllerProvisionRequest.decode(request_body)
svc = Service.find_by_label(req.label)
raise CloudError.new(CloudError::SERVICE_NOT_FOUND) unless svc && svc.visible_to_user?(user)
View
6 cloud_controller/app/models/app.rb
@@ -190,6 +190,7 @@ def diff_configs(binding_names)
def bind_to_config(cfg, binding_options={})
svc = cfg.service
+ user = cfg.user
# The ordering here is odd, but important; it allows us to repair our internal
# state to match that of the gateway. The description following each operation
@@ -220,13 +221,14 @@ def bind_to_config(cfg, binding_options={})
req = VCAP::Services::Api::GatewayBindRequest.new(
:service_id => cfg.name,
:label => svc.label,
+ :email => user.email,
:binding_options => binding_options
)
if EM.reactor_running?
# yields
endpoint = "#{svc.url}/gateway/v1/configurations/#{req.service_id}/handles"
- http = VCAP::Services::Api::AsyncHttpRequest.fibered(endpoint, svc.token, :post, req)
+ http = VCAP::Services::Api::AsyncHttpRequest.fibered(endpoint, svc.token, :post, svc.timeout, req)
if !http.error.empty?
raise "Error sending bind request #{req.extract.inspect} to gateway #{svc.url}: #{http.error}"
elsif http.response_header.status != 200
@@ -300,7 +302,7 @@ def unbind_from_config(cfg)
begin
if EM.reactor_running?
endpoint = "#{svc.url}/gateway/v1/configurations/#{req.service_id}/handles/#{req.handle_id}"
- http = VCAP::Services::Api::AsyncHttpRequest.new(endpoint, svc.token, :delete, req)
+ http = VCAP::Services::Api::AsyncHttpRequest.new(endpoint, svc.token, :delete, svc.timeout, req)
http.callback do
if http.response_header.status != 200
CloudController.logger.error("Error sending unbind request #{req.extract.to_json} non 200 response from gateway #{svc.url}: #{http.response_header.status} #{http.response}")
View
2 cloud_controller/app/models/service.rb
@@ -18,7 +18,7 @@ class Service < ActiveRecord::Base
serialize :binding_options
serialize :acls
- attr_accessible :label, :token, :url, :description, :info_url, :tags, :plans, :plan_options, :binding_options, :active, :acls
+ attr_accessible :label, :token, :url, :description, :info_url, :tags, :plans, :plan_options, :binding_options, :active, :acls, :timeout
def self.active_services
where("active = ?", true)
View
9 cloud_controller/app/models/service_config.rb
@@ -32,23 +32,24 @@ def self.provision(service, user, cfg_alias, plan, plan_option)
# will lack the handle).
begin
- req = VCAP::Services::Api::ProvisionRequest.new(
+ req = VCAP::Services::Api::GatewayProvisionRequest.new(
:label => service.label,
:name => cfg_alias,
+ :email => user.email,
:plan => plan,
:plan_option => plan_option
)
if EM.reactor_running?
# yields
endpoint = "#{service.url}/gateway/v1/configurations"
- http = VCAP::Services::Api::AsyncHttpRequest.fibered(endpoint, service.token, :post, req)
+ http = VCAP::Services::Api::AsyncHttpRequest.fibered(endpoint, service.token, :post, service.timeout, req)
if !http.error.empty?
raise "Error sending provision request for #{req.extract.to_json} to gateway #{service.url}: #{http.error}"
elsif http.response_header.status != 200
raise "Error sending provision request for #{req.extract.to_json}: non 200 response from gateway #{service.url}: #{http.response_header.status} #{http.response}"
end
- config = VCAP::Services::Api::ProvisionResponse.decode(http.response)
+ config = VCAP::Services::Api::GatewayProvisionResponse.decode(http.response)
else
uri = URI.parse(service.url)
gw = VCAP::Services::Api::ServiceGatewayClient.new(uri.host, service.token, uri.port)
@@ -94,7 +95,7 @@ def unprovision
begin
if EM.reactor_running?
endpoint = "#{svc.url}/gateway/v1/configurations/#{cfg_name}"
- http = VCAP::Services::Api::AsyncHttpRequest.new(endpoint, service.token, :delete)
+ http = VCAP::Services::Api::AsyncHttpRequest.new(endpoint, service.token, :delete, service.timeout)
http.callback do
if http.response_header.status != 200
CloudController.logger.error("Error unprovisioning #{cfg_name}, non 200 response from gateway #{svc.url}: #{http.response_header.status} #{http.response}")
View
22 cloud_controller/config/appconfig.rb
@@ -150,6 +150,28 @@
end
end
+# Service broker config
+if AppConfig[:service_broker]
+ unless AppConfig[:service_broker].kind_of? Hash
+ klass = AppConfig[:service_broker].class
+ $stderr.puts "FATAL: Service broker config is invalid. Expected Hash, got #{klass}."
+ exit 1
+ end
+
+ unless AppConfig[:service_broker].has_key? :token
+ $stderr.puts "FATAL: Service broker require token key"
+ exit 1
+ end
+
+ token = AppConfig[:service_broker][:token]
+ unless (token.kind_of? String) || (token.kind_of? Integer)
+ $stderr.puts "FATAL: Token must be string or integer, #{token.class} given."
+ exit 1
+ end
+
+ AppConfig[:service_broker][:token] = token.to_s
+end
+
c = OpenSSL::Cipher::Cipher.new('blowfish')
pw_len = AppConfig[:keys][:password].length
if pw_len < c.key_len
View
5 cloud_controller/config/cloud_controller.yml
@@ -131,6 +131,10 @@ builtin_services:
neo4j:
token: "0xdeadbeef"
+# Service broker
+service_broker:
+ token: "0xdeadbeef"
+
# used for /info/runtimes endpoint (served unfiltered as JSON)
runtimes:
ruby18:
@@ -150,3 +154,4 @@ runtimes:
version: ".* 5.8.3"
python26:
version: 2.6.5
+
View
2 cloud_controller/config/routes.rb
@@ -43,6 +43,8 @@
post 'services/v1/binding_tokens' => 'binding_tokens#create', :as => :binding_token_create
get 'services/v1/binding_tokens/:binding_token' => 'binding_tokens#get', :as => :binding_token_get, :binding_token => /[^\/]+/
delete 'services/v1/binding_tokens/:binding_token' => 'binding_tokens#delete', :as => :binding_token_delete, :binding_token => /[^\/]+/
+ # Brokered Services
+ get 'brokered_services/poc/offerings' => 'services#list_brokered_services', :as => :service_list_brokered_services
# Legacy services implementation (for old vmc)
get 'services' => 'legacy_services#list', :as => :legacy_service_list
View
9 cloud_controller/db/migrate/20110818080550_add_timeout_to_services.rb
@@ -0,0 +1,9 @@
+class AddTimeoutToServices < ActiveRecord::Migration
+ def self.up
+ add_column :services, :timeout, :integer
+ end
+
+ def self.down
+ remove_column :services, :timeout
+ end
+end
View
3 cloud_controller/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20110707170643) do
+ActiveRecord::Schema.define(:version => 20110818080550) do
create_table "app_collaborations", :force => true do |t|
t.integer "app_id"
@@ -126,6 +126,7 @@
t.boolean "active", :default => true
t.datetime "created_at"
t.datetime "updated_at"
+ t.integer "timeout"
end
add_index "services", ["name", "version"], :name => "index_services_on_name_and_version", :unique => true
View
2 cloud_controller/script/service_lifecycle_tester.rb
@@ -50,7 +50,7 @@ def assert_resp_ok(resp)
#################### Provision ####################
puts "Provisioning..."
-req = VCAP::Services::Api::ProvisionRequest.new(
+req = VCAP::Services::Api::CloudControllerProvisionRequest.new(
:label => "#{svc}-#{svcs[svc]}",
:name => 'foobar',
:plan => 'free'
View
107 cloud_controller/spec/controllers/services_controller_spec.rb
@@ -66,6 +66,28 @@ def unbind_instance(service_id, handle_id, binding_options)
response.status.should == 200
end
+ it 'should create service offerings for brokered service' do
+ request.env['HTTP_X_VCAP_SERVICE_TOKEN'] = 'broker'
+ AppConfig[:service_broker] = {:token => 'broker'}
+ post_msg :create do
+ VCAP::Services::Api::ServiceOfferingRequest.new(
+ :label => 'foo-bar',
+ :url => 'http://localhost:56789')
+ end
+ response.status.should == 200
+ end
+
+ it 'should not create brokered service offerings if token mismatch' do
+ request.env['HTTP_X_VCAP_SERVICE_TOKEN'] = 'foobar'
+ AppConfig[:service_broker] = {:token => 'broker'}
+ post_msg :create do
+ VCAP::Services::Api::ServiceOfferingRequest.new(
+ :label => 'foo-bar',
+ :url => 'http://localhost:56789')
+ end
+ response.status.should == 403
+ end
+
it 'should not create service offerings if not builtin' do
post_msg :create do
VCAP::Services::Api::ServiceOfferingRequest.new(
@@ -94,12 +116,43 @@ def unbind_instance(service_id, handle_id, binding_options)
:url => 'http://www.google.com',
:acls => acls,
:plans => ['foo'],
+ :timeout => 20,
:description => 'foobar')
end
response.status.should == 200
svc = Service.find_by_label('foo-bar')
svc.should_not be_nil
svc.description.should == 'foobar'
+ svc.timeout.should == 20
+ end
+
+
+ it 'should support reverting existing offerings to nil' do
+ acls = {
+ 'users' => ['foo@bar.com'],
+ 'wildcards' => ['*@foo.com'],
+ }
+ svc = Service.create(
+ :label => 'foo-bar',
+ :url => 'http://www.google.com',
+ :token => 'foobar',
+ :acls => acls,
+ :timeout => 20,
+ :plans => ['foo'])
+ svc.should be_valid
+
+ post_msg :create do
+ VCAP::Services::Api::ServiceOfferingRequest.new(
+ :label => 'foo-bar',
+ :url => 'http://www.google.com',
+ :plans => ['foo'],
+ :description => 'foobar')
+ end
+ response.status.should == 200
+ svc = Service.find_by_label('foo-bar')
+ svc.should_not be_nil
+ svc.timeout.should be_nil
+ svc.acls.should be_nil
end
it 'should return not authorized on token mismatch for non builtin services' do
@@ -201,6 +254,52 @@ def unbind_instance(service_id, handle_id, binding_options)
end
end
+ describe '#list_brokered_services' do
+ before :each do
+ request.env['HTTP_X_VCAP_SERVICE_TOKEN'] = 'broker'
+ AppConfig[:service_broker] = {:token => 'broker'}
+ end
+
+ it "should return not authorized on token mismatch" do
+ request.env['HTTP_X_VCAP_SERVICE_TOKEN'] = 'foobar'
+ get :list_brokered_services
+ response.status.should == 403
+ end
+
+ it "should not list builtin services" do
+ AppConfig[:builtin_services] = {
+ :foo => {:token=>"foobar"}
+ }
+ svc = Service.new
+ svc.label = "foo-1.0"
+ svc.url = "http://localhost:56789"
+ svc.token = 'foobar'
+ svc.save
+ svc.should be_valid
+
+ get :list_brokered_services
+ response.status.should == 200
+ Yajl::Parser.parse(response.body)['brokered_services'].should be_empty
+ end
+
+ it "should list brokered services" do
+ AppConfig[:builtin_services] = {
+ :foo => {:token=>"foobar"}
+ }
+
+ svc = Service.new
+ svc.label = "brokered-1.0"
+ svc.url = "http://localhost:56789"
+ svc.token = 'brokered'
+ svc.save
+ svc.should be_valid
+
+ get :list_brokered_services
+ response.status.should == 200
+ Yajl::Parser.parse(response.body)['brokered_services'].size.should == 1
+ end
+ end
+
describe '#update_handle' do
before :each do
svc = Service.new
@@ -303,7 +402,7 @@ def unbind_instance(service_id, handle_id, binding_options)
it 'should return not found for unknown services' do
post_msg :provision do
- VCAP::Services::Api::ProvisionRequest.new(
+ VCAP::Services::Api::CloudControllerProvisionRequest.new(
:label => 'bar-foo',
:name => 'foo',
:plan => 'free')
@@ -316,7 +415,7 @@ def unbind_instance(service_id, handle_id, binding_options)
shim.stubs(:provision_service).returns({:data => {}, :service_id => 'foo', :credentials => {}})
gw_pid = start_gateway(@svc, shim)
post_msg :provision do
- VCAP::Services::Api::ProvisionRequest.new(
+ VCAP::Services::Api::CloudControllerProvisionRequest.new(
:label => 'foo-bar',
:name => 'foo',
:plan => 'free')
@@ -331,15 +430,15 @@ def unbind_instance(service_id, handle_id, binding_options)
gw_pid = start_gateway(@svc, shim)
post_msg :provision do
- VCAP::Services::Api::ProvisionRequest.new(
+ VCAP::Services::Api::CloudControllerProvisionRequest.new(
:label => 'foo-bar',
:name => 'foo',
:plan => 'free')
end
response.status.should == 200
post_msg :provision do
- VCAP::Services::Api::ProvisionRequest.new(
+ VCAP::Services::Api::CloudControllerProvisionRequest.new(
:label => 'foo-bar',
:name => 'foo',
:plan => 'free')
View
4 cloud_controller/spec/support/synchronous_service_gateway.rb
@@ -52,7 +52,7 @@ def initialize(opts)
# Provisions an instance of the service
#
post '/gateway/v1/configurations' do
- req = VCAP::Services::Api::ProvisionRequest.decode(request_body)
+ req = VCAP::Services::Api::GatewayProvisionRequest.decode(request_body)
@logger.debug("Provision request for label=#{req.label} plan=#{req.plan}")
@@ -64,7 +64,7 @@ def initialize(opts)
svc = @provisioner.provision_service(version, req.plan)
if svc
- VCAP::Services::Api::ProvisionResponse.new(svc).encode
+ VCAP::Services::Api::GatewayProvisionResponse.new(svc).encode
else
SERVICE_UNAVAILABLE
end
View
12 lib/services/api/async_requests.rb
@@ -15,7 +15,7 @@ module Api
module VCAP::Services::Api
class AsyncHttpRequest
class << self
- def new(url, token, verb, msg=VCAP::Services::Api::EMPTY_REQUEST)
+ def new(url, token, verb, timeout, msg=VCAP::Services::Api::EMPTY_REQUEST)
req = {
:head => {
@@ -24,11 +24,15 @@ def new(url, token, verb, msg=VCAP::Services::Api::EMPTY_REQUEST)
},
:body => msg.encode,
}
- EM::HttpRequest.new(url).send(verb.to_sym, req)
+ if timeout
+ EM::HttpRequest.new(url, :inactivity_timeout => timeout).send(verb.to_sym, req)
+ else
+ EM::HttpRequest.new(url).send(verb.to_sym, req)
+ end
end
- def fibered(url, token, verb, msg=VCAP::Services::Api::EMPTY_REQUEST)
- req = new(url, token, verb, msg)
+ def fibered(url, token, verb, timeout, msg=VCAP::Services::Api::EMPTY_REQUEST)
+ req = new(url, token, verb, timeout, msg)
f = Fiber.current
req.callback { f.resume(req) }
req.errback { f.resume(req) }
View
4 lib/services/api/clients/service_gateway_client.rb
@@ -34,9 +34,9 @@ def initialize(host, token, port=80)
end
def provision(args)
- msg = VCAP::Services::Api::ProvisionRequest.new(args)
+ msg = VCAP::Services::Api::GatewayProvisionRequest.new(args)
resp = perform_request(Net::HTTP::Post, '/gateway/v1/configurations', msg)
- VCAP::Services::Api::ProvisionResponse.decode(resp.body)
+ VCAP::Services::Api::GatewayProvisionResponse.decode(resp.body)
end
def unprovision(args)
View
26 lib/services/api/messages.rb
@@ -28,6 +28,14 @@ class ServiceOfferingRequest < JsonMessage
optional :binding_options
optional :acls, {'users' => [String], 'wildcards' => [String]}
optional :active
+ optional :timeout, Integer
+ end
+
+ class BrokeredServiceOfferingRequest < JsonMessage
+ required :label, SERVICE_LABEL_REGEX
+ required :options, [{"name" => String, "acls" => {"users" => [String], "wildcards" => [String] } , "credentials" => Hash}]
+ #required :options, [::JsonSchema::WILDCARD]
+ optional :description, String
end
class HandleUpdateRequest < JsonMessage
@@ -40,19 +48,32 @@ class ListHandlesResponse < JsonMessage
required :handles, [::JsonSchema::WILDCARD]
end
+ class ListBrokeredServicesResponse < JsonMessage
+ required :brokered_services, [{"label" => String, "description" => String, "acls" => {"users" => [String], "wildcards" => [String]}}]
+ end
+
#
# Provision a service instance
# NB: Unprovision takes all args in the url
#
- class ProvisionRequest < JsonMessage
+ class CloudControllerProvisionRequest < JsonMessage
+ required :label, SERVICE_LABEL_REGEX
+ required :name, String
+ required :plan, String
+
+ optional :plan_option
+ end
+
+ class GatewayProvisionRequest < JsonMessage
required :label, SERVICE_LABEL_REGEX
required :name, String
required :plan, String
+ required :email, String
optional :plan_option
end
- class ProvisionResponse < JsonMessage
+ class GatewayProvisionResponse < JsonMessage
required :service_id, String
required :data
required :credentials
@@ -70,6 +91,7 @@ class CloudControllerBindRequest < JsonMessage
class GatewayBindRequest < JsonMessage
required :service_id, String
required :label, String
+ required :email, String
required :binding_options
end

0 comments on commit d95ddd5

Please sign in to comment.