Permalink
Browse files

AMQP auto-reconfiguration

Change-Id: I8b79d6a2f2b0bcc0004acd26c2be9e9cc6b3e7de
  • Loading branch information...
1 parent e6b5663 commit 90ecf56a252045e45b6df67c8e5218a0138143c3 Jennifer Hickey committed Dec 1, 2011
@@ -13,11 +13,11 @@ spec = Gem::Specification.new do |s|
s.platform = Gem::Platform::RUBY
#s.extra_rdoc_files = ["README.md", "LICENSE"]
-
+
s.add_dependency "cf-runtime"
- # TODO pick the lowest version of redis we support
s.add_development_dependency "redis", "~> 2.0"
+ s.add_development_dependency "amqp", "~> 0.8"
s.add_development_dependency "rake", "~> 0.9.2"
s.add_development_dependency "rcov", "~> 0.9.10"
s.add_development_dependency "rspec", "~> 2.6.0"
@@ -20,3 +20,10 @@
else
puts "No MySQL service bound to app. Skipping auto-reconfiguration."
end
+
+if CFRuntime::CloudApp.service_props('rabbitmq')
+ puts "Loading RabbitMQ auto-reconfiguration."
+ require 'cfautoconfig/messaging/amqp_configurer'
+else
+ puts "No RabbitMQ service bound to app. Skipping auto-reconfiguration."
+end
@@ -1,8 +1,9 @@
require 'cfautoconfig/configuration_helper'
+SUPPORTED_REDIS_VERSION = '2.0'
begin
require 'redis'
require File.join(File.dirname(__FILE__), 'redis')
- if Gem::Version.new(Redis::VERSION) >= Gem::Version.new('2.0')
+ if Gem::Version.new(Redis::VERSION) >= Gem::Version.new(SUPPORTED_REDIS_VERSION)
if AutoReconfiguration::ConfigurationHelper.disabled? :redis
puts "Redis auto-reconfiguration disabled."
class Redis
@@ -24,7 +25,8 @@ class Redis
end
end
else
- puts "Auto-reconfiguration not supported for this Redis version."
+ puts "Auto-reconfiguration not supported for this Redis version. " +
+ "Found: #{Redis::VERSION}. Required: #{SUPPORTED_REDIS_VERSION} or higher."
end
rescue LoadError
puts "No Redis Library Found. Skipping auto-reconfiguration."
@@ -0,0 +1,39 @@
+require 'cfruntime/properties'
+
+module AutoReconfiguration
+ module AMQP
+ def self.included( base )
+ base.send( :alias_method, :original_connect, :connect)
+ base.send( :alias_method, :connect, :connect_with_cf )
+ end
+
+ def connect_with_cf(connection_options_or_string = {}, other_options = {}, &block)
+ service_props = CFRuntime::CloudApp.service_props('rabbitmq')
+ if(service_props.nil?)
+ puts "No RabbitMQ service bound to app. Skipping auto-reconfiguration."
+ original_connect(connection_options_or_string, other_options, &block)
+ else
+ puts "Auto-reconfiguring AMQP."
+ url_provided = false
+ case connection_options_or_string
+ when String then
+ url_provided = true
+ cfoptions = {}
+ else
+ cfoptions = connection_options_or_string
+ end
+ if service_props[:url] && url_provided
+ #If user passed in a URL and we have a URL for service, use it
+ original_connect(service_props[:url], other_options, &block)
+ else
+ cfoptions[:host] = service_props[:host]
+ cfoptions[:port] = service_props[:port]
+ cfoptions[:user] = service_props[:username]
+ cfoptions[:pass] = service_props[:password]
+ cfoptions[:vhost] = service_props[:vhost]
+ original_connect(cfoptions, other_options, &block)
+ end
+ end
+ end
+ end
+end
@@ -0,0 +1,32 @@
+require 'cfautoconfig/configuration_helper'
+SUPPORTED_AMQP_VERSION = '0.8'
+begin
+ #Require amqp here is mandatory for configurer to ensure class is loaded before applying OpenClass
+ require "amqp"
+ require File.join(File.dirname(__FILE__), 'amqp')
+ if Gem::Version.new(AMQP::VERSION) >= Gem::Version.new(SUPPORTED_AMQP_VERSION)
+ if AutoReconfiguration::ConfigurationHelper.disabled? :rabbitmq
+ puts "RabbitMQ auto-reconfiguration disabled."
+ class << AMQP
+ #Remove introduced aliases and methods.
+ #This is mostly for testing, as we don't expect this script
+ #to run twice during a single app startup
+ if method_defined?(:connect_with_cf)
+ undef_method :connect_with_cf
+ alias :connect :original_connect
+ end
+ end
+ elsif AMQP.public_methods.index :connect_with_cf
+ puts "AMQP AutoReconfiguration already included."
+ else
+ class << AMQP
+ include AutoReconfiguration::AMQP
+ end
+ end
+ else
+ puts "Auto-reconfiguration not supported for this AMQP version. " +
+ "Found: #{AMQP::VERSION}. Required: #{SUPPORTED_AMQP_VERSION} or higher."
+ end
+rescue LoadError
+ puts "No AMQP Library Found. Skipping auto-reconfiguration."
+end
@@ -8,6 +8,7 @@
ENV['VCAP_SERVICES'] = '{"redis-2.2":[{"name": "redis-1","label": "redis-2.2",' +
'"plan": "free", "credentials": {"node_id": "redis_node_8","hostname": ' +
'"10.20.30.40","port": 1234,"password": "mypass","name": "r1"}}]}'
+ ENV['DISABLE_AUTO_CONFIG'] = nil
load 'cfruntime/properties.rb'
end
@@ -38,6 +39,18 @@
redis.client.password.should == 'mypw'
end
+ it 'does not open Redis class to apply methods twice' do
+ load 'cfautoconfig/keyvalue/redis_configurer.rb'
+ #This would blow up massively (stack trace too deep) if we
+ #aliased initialize twice
+ redis = Redis.new(:host => '127.0.0.1',
+ :port => '6321',
+ :password => 'mypw')
+ redis.client.host.should == '10.20.30.40'
+ redis.client.port.should == 1234
+ redis.client.password.should == 'mypass'
+ end
+
it 'disables Redis auto-reconfig if DISABLE_AUTO_CONFIG includes redis' do
ENV['DISABLE_AUTO_CONFIG'] = "redis:mongodb"
load 'cfautoconfig/keyvalue/redis_configurer.rb'
@@ -0,0 +1,95 @@
+File.join(File.dirname(__FILE__), '../../','spec_helper')
+require 'amqp'
+require 'cfautoconfig/messaging/amqp_configurer'
+
+describe 'AutoReconfiguration::AMQP' do
+
+ before(:each) do
+ ENV['VCAP_SERVICES'] = '{"rabbitmq-2.4":[{"name": "rabbit-1","label": "rabbitmq-2.4",' +
+ '"plan": "free", "credentials": {"url": "amqp://username:password@10.20.30.40:12345/virtualHost"}}]}'
+ ENV['DISABLE_AUTO_CONFIG'] = nil
+ load 'cfruntime/properties.rb'
+ @mock_connection = mock("connection")
+ end
+
+ it 'auto-configures AMQP on connect with options and new format (srs)' do
+ mock_client = mock("client")
+ AMQP.client = mock_client
+ mock_client.should_receive(:connect).with({:host => '10.20.30.40', :port =>12345, :user=>'username',
+ :pass=>'password', :vhost=>'virtualHost'}).and_return(@mock_connection)
+ @mock_connection.should_receive(:on_open)
+ AMQP.connect({:host => '127.0.0.1', :port =>1234, :user=>'testuser',
+ :pass=>'testpass', :vhost=>'testvhost'})
+ end
+
+ it 'auto-configures AMQP on connect with options and old format' do
+ ENV['VCAP_SERVICES'] = '{"rabbitmq-2.4":[{"name": "rabbit-1","label": "rabbitmq-2.4",' +
+ '"plan": "free", "credentials": {"hostname": "10.20.30.40", "port": 12345, "user": "username",
+ "pass":"password", "vhost" : "virtualHost"}}]}'
+ load 'cfruntime/properties.rb'
+ mock_client = mock("client")
+ AMQP.client = mock_client
+ mock_client.should_receive(:connect).with({:host => '10.20.30.40', :port =>12345, :user=>'username',
+ :pass=>'password', :vhost=>'virtualHost'}).and_return(@mock_connection)
+ @mock_connection.should_receive(:on_open)
+ AMQP.connect({:host => '127.0.0.1', :port =>1234, :user=>'testuser',
+ :pass=>'testpass', :vhost=>'testvhost'})
+ end
+
+ it 'auto-configures AMQP on connect with url and new format (srs)' do
+ mock_client = mock("client")
+ AMQP.client = mock_client
+ mock_client.should_receive(:connect).with(:scheme=>"amqp", :user=>"username", :pass=>"password",
+ :host=>"10.20.30.40", :port=>12345, :ssl=>false, :vhost=>"virtualHost").and_return(@mock_connection)
+ @mock_connection.should_receive(:on_open)
+ AMQP.connect("amqp://testuser:testpass@127.0.0.1:1234/testvhost")
+ end
+
+ it 'auto-configures AMQP on connect with url and old format' do
+ ENV['VCAP_SERVICES'] = '{"rabbitmq-2.4":[{"name": "rabbit-1","label": "rabbitmq-2.4",' +
+ '"plan": "free", "credentials": {"hostname": "10.20.30.40", "port": 12345, "user": "username",
+ "pass":"password", "vhost" : "virtualHost"}}]}'
+ load 'cfruntime/properties.rb'
+ mock_client = mock("client")
+ AMQP.client = mock_client
+ mock_client.should_receive(:connect).with({:host => '10.20.30.40', :port =>12345, :user=>'username',
+ :pass=>'password', :vhost=>'virtualHost'}).and_return(@mock_connection)
+ @mock_connection.should_receive(:on_open)
+ AMQP.connect("amqp://testuser:testpass@127.0.0.1:1234/testvhost")
+ end
+
+ it 'does not auto-configure AMQP if VCAP_SERVICES not set' do
+ ENV['VCAP_SERVICES'] = nil
+ load 'cfruntime/properties.rb'
+ mock_client = mock("client")
+ AMQP.client = mock_client
+ mock_client.should_receive(:connect).with(:scheme=>"amqp", :user=>"testuser", :pass=>"testpass",
+ :host=>"127.0.0.1", :port=>1234, :ssl=>false, :vhost=>"testvhost").and_return(@mock_connection)
+ @mock_connection.should_receive(:on_open)
+ AMQP.connect("amqp://testuser:testpass@127.0.0.1:1234/testvhost")
+ end
+
+ it 'does not open AMQP class to apply methods twice' do
+ load 'cfautoconfig/messaging/amqp_configurer.rb'
+ #This would blow up massively (stack trace too deep) if we
+ #aliased the connect methods twice
+ mock_client = mock("client")
+ AMQP.client = mock_client
+ mock_client.should_receive(:connect).with(:scheme=>"amqp", :user=>"username", :pass=>"password",
+ :host=>"10.20.30.40", :port=>12345, :ssl=>false, :vhost=>"virtualHost").and_return(@mock_connection)
+ @mock_connection.should_receive(:on_open)
+ AMQP.connect("amqp://testuser:testpass@127.0.0.1:1234/testvhost")
+ end
+
+ it 'disables AMQP auto-reconfig if DISABLE_AUTO_CONFIG includes rabbitmq' do
+ ENV['DISABLE_AUTO_CONFIG'] = "redis:rabbitmq:mongodb"
+ load 'cfautoconfig/messaging/amqp_configurer.rb'
+ mock_client = mock("client")
+ AMQP.client = mock_client
+ mock_client.should_receive(:connect).with(:scheme=>"amqp", :user=>"testuser", :pass=>"testpass",
+ :host=>"127.0.0.1", :port=>1234, :ssl=>false, :vhost=>"testvhost").and_return(@mock_connection)
+ @mock_connection.should_receive(:on_open)
+ AMQP.connect("amqp://testuser:testpass@127.0.0.1:1234/testvhost")
+ end
+
+end
@@ -42,6 +42,7 @@ class CloudApp
raise ArgumentError.new("multiple segments in path of amqp URI: #{uri}") if $1.index('/')
vhost = URI.unescape($1)
end
+ serviceopts[:url] = cred['url']
else
# The "old" credentials format
user,passwd,host,port,vhost = %w(user pass hostname port vhost).map {|key|
@@ -85,6 +85,7 @@
CFRuntime::CloudApp.service_props('rabbit-test')[:port].should == 25046
CFRuntime::CloudApp.service_props('rabbit-test')[:username].should_not == nil
CFRuntime::CloudApp.service_props('rabbit-test')[:password].should_not == nil
+ CFRuntime::CloudApp.service_props('rabbit-test')[:url].should_not == nil
CFRuntime::CloudApp.service_props('rabbit-test')[:vhost].should == "testvhost"
end
end
@@ -99,6 +100,7 @@
CFRuntime::CloudApp.service_props('rabbit-test')[:port].should == 25046
CFRuntime::CloudApp.service_props('rabbit-test')[:username].should_not == nil
CFRuntime::CloudApp.service_props('rabbit-test')[:password].should_not == nil
+ CFRuntime::CloudApp.service_props('rabbit-test')[:url].should_not == nil
CFRuntime::CloudApp.service_props('rabbit-test')[:vhost].should == '/'
end
end
View
@@ -0,0 +1,9 @@
+#!/bin/bash
+if [ -x $0.local ]; then
+ $0.local "$@" || exit $?
+fi
+
+REPO_DIR=$(dirname $GIT_DIR)
+if [ -x $REPO_DIR/git/tracked_hooks/$(basename $0) ]; then
+ $REPO_DIR/git/tracked_hooks/$(basename $0) "$@" || exit $?
+fi
View
@@ -0,0 +1,28 @@
+#!/bin/bash
+HOOK_NAMES="
+applypatch-msg
+pre-applypatch
+post-applypatch
+pre-commit
+prepare-commit-msg
+commit-msg
+post-commit
+pre-rebase
+post-checkout
+post-merge
+pre-receive
+update
+post-receive
+post-update
+pre-auto-gc
+"
+
+HOOK_SRC=$(dirname $0)
+HOOK_DIR=$(git rev-parse --git-dir)/hooks || exit $?
+
+for hook in $HOOK_NAMES; do
+ if [ ! -h $HOOK_DIR/$hook -a -x $HOOK_DIR/$hook ]; then
+ mv $HOOK_DIR/$hook $HOOK_DIR/$hook.local
+ fi
+ ln -s -f ../../git/hooks-wrapper $HOOK_DIR/$hook
+done
@@ -0,0 +1,11 @@
+#!/bin/sh
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+ against=HEAD
+else
+ # Initial commit: diff against an empty tree object
+ against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+exec git diff-index --check --cached $against --

0 comments on commit 90ecf56

Please sign in to comment.