3scale-3scale_ws_api_for_ruby 0.4.7

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,63 @@
1
+ = Interface for 3scale web service API monitoring system
2
+
3
+ This plugin provides interface for communication with 3scale monitoring system.
4
+
5
+ == Configuration
6
+
7
+ If you are using rails 2.1 or newer, put this into your config/environment.rb
8
+ file:
9
+
10
+ config.gem "3scale_interface"
11
+
12
+ If you are using older version, put there this instead:
13
+
14
+ require "3scale_interface"
15
+
16
+ == Usage
17
+
18
+ First, create new interface object with 3scale backed hostname and Your
19
+ private authentication key:
20
+
21
+ interface = ThreeScale::Interface.new("http://3scale.net", "a3b034...")
22
+
23
+ Because the object is stateless, you can create just one and store it globally.
24
+
25
+ Then for each request to Your service:
26
+
27
+ 1. Start the transaction with user key and (optionally) estimated resource
28
+ usage (in this example it is: 1 hit and 42000 kilobytes of storage space),
29
+
30
+ transaction = interface.start(user_key, 'hits' => 1, 'storage' => 42000)
31
+
32
+ This will return transaction data (if successful). It is a hash containing
33
+ these fields:
34
+
35
+ <tt>:id</tt>::
36
+ transaction id necessary for confirmation or cancellation of transaction
37
+ (see following steps).
38
+
39
+ <tt>:provider_verification_key</tt>::
40
+ the provider should send this key back to user so he/she can verify the
41
+ authenticity of the response.
42
+
43
+ <tt>:contract_name</tt>::
44
+ name of contract the user is signed for. This can be used to send
45
+ different response according to contract type, if that is desired.
46
+
47
+ 2. Process the request.
48
+
49
+ 2a. If the processing was successful:
50
+ Call +confirm+:
51
+
52
+ interface.confirm(transaction[:id])
53
+
54
+ Or call it with actual resource usage, if it differs from predicted one:
55
+
56
+ interface.confirm(transaction[:id], 'hits' => 1, 'storage' => 40500)
57
+
58
+ 2b. If there was some error, call +cancel+:
59
+
60
+ interface.cancel(transaction[:id])
61
+
62
+ 3. Send response back to the user together with +provider_verification_key+.
63
+
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ desc 'Default: run unit tests.'
7
+ task :default => :test
8
+
9
+ desc 'Run unit tests.'
10
+ Rake::TestTask.new(:test) do |t|
11
+ t.libs << 'lib'
12
+ t.pattern = 'test/**/*_test.rb'
13
+ t.verbose = true
14
+ end
15
+
16
+ desc 'Generate documentation.'
17
+ Rake::RDocTask.new(:rdoc) do |rdoc|
18
+ rdoc.rdoc_dir = 'rdoc'
19
+ rdoc.title = '3scale interface'
20
+ rdoc.options << '--line-numbers' << '--inline-source'
21
+ rdoc.rdoc_files.include('README')
22
+ rdoc.rdoc_files.include('lib/**/*.rb')
23
+ end
24
+
25
+ spec = eval(File.read(File.dirname(__FILE__) + '/3scale_ws_api_for_ruby.gemspec'))
26
+
27
+ Rake::GemPackageTask.new(spec) do |package|
28
+ package.need_tar = true
29
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require "#{File.dirname(__FILE__)}/lib/3scale_interface"
@@ -0,0 +1,236 @@
1
+ require 'cgi'
2
+ require 'hpricot'
3
+ require 'net/http'
4
+
5
+ module ThreeScale # :nodoc:
6
+ class Error < StandardError; end
7
+
8
+ # Base class for exceptions caused by user.
9
+ class UserError < Error; end
10
+
11
+ # Exception raised when contract between user and provider is not active.
12
+ # Contract can be inactive when it is pending (requires confirmation from
13
+ # provider), suspended or canceled.
14
+ class ContractNotActive < UserError; end
15
+
16
+ # Exception raised when usage limits configured for contract are already
17
+ # exceeded.
18
+ class LimitsExceeded < UserError; end
19
+
20
+ # Exception raised when +user_key+ is not valid. This can mean that contract
21
+ # between provider and user does not exists, or the passed +user_key+ does
22
+ # not correspond to the key associated with this contract.
23
+ class UserKeyInvalid < UserError; end
24
+
25
+
26
+ # Base class for exceptions caused by provider.
27
+ class ProviderError < Error; end
28
+
29
+ # Exception raised when some metric name in provider +usage+ hash does not
30
+ # correspond to metric configured for the service.
31
+ class MetricInvalid < ProviderError; end
32
+
33
+ # Exception raised when provider authentication key is not valid. The provider
34
+ # needs to make sure that the key used is the same as the one that was
35
+ # generated for him/her when he/she published a service on 3scale.
36
+ class ProviderKeyInvalid < ProviderError; end
37
+
38
+ # Exception raised when transaction corresponding to given +transaction_id+
39
+ # does not exists. Methods +confirm+ and +cancel+ need valid transaction id
40
+ # that is obtained by preceding call to +start+.
41
+ class TransactionNotFound < ProviderError; end
42
+
43
+
44
+ # Base class for exceptions caused by 3scale backend system.
45
+ class SystemError < Error; end
46
+
47
+ # Other error.
48
+ class UnknownError < SystemError; end
49
+
50
+
51
+
52
+ # This class provides interface to 3scale monitoring system.
53
+ #
54
+ # Objects of this class are stateless and can be shared through multiple
55
+ # transactions and by multiple clients.
56
+ class Interface
57
+
58
+ # Hostname of 3scale server.
59
+ attr_accessor :host
60
+
61
+ # Key that uniquely identifies the provider. This key is known only to the
62
+ # provider and to 3scale.
63
+ attr_accessor :provider_authentication_key
64
+
65
+ # Create a 3scale interface object.
66
+ #
67
+ # == Arguments
68
+ # +host+:: Hostname of 3scale backend server.
69
+ # +provider_authentication_key+:: Unique key that identifies this provider.
70
+ def initialize(host = nil, provider_authentication_key = nil)
71
+ @host = host
72
+ @provider_authentication_key = provider_authentication_key
73
+ end
74
+
75
+
76
+ # Starts a transaction. This can be used also to report estimated resource
77
+ # usage of the request.
78
+ #
79
+ # == Arguments
80
+ # +user_key+:: Key that uniquely identifies an user of the service.
81
+ # +usage+::
82
+ # A hash of metric names/values pairs that contains predicted resource
83
+ # usage of this request.
84
+ #
85
+ # For example, if this request is going to take 10MB of storage space,
86
+ # then this parameter could contain {'storage' => 10}. The values may be
87
+ # only approximate or they can be missing altogether. In these cases, the
88
+ # real values must be reported using method +confirm+.
89
+ #
90
+ # == Return values
91
+ # A hash containing there keys:
92
+ # <tt>:id</tt>::
93
+ # Transaction id. This is required for confirmation/cancellation of the
94
+ # transaction later.
95
+ # <tt>:provider_verification_key</tt>::
96
+ # This key should be sent back to the user so he/she can use it to verify
97
+ # the authenticity of the provider.
98
+ # <tt>:contract_name</tt>::
99
+ # This is name of the contract the user is singed for. This information
100
+ # can be used to serve different responses according to contract types,
101
+ # if that is desirable.
102
+ #
103
+ # == Exceptions
104
+ #
105
+ # ThreeScale::UserKeyInvalid:: +user_key+ is not valid
106
+ # ThreeScale::ProviderKeyInvalid:: +provider_authentication_key+ is not valid
107
+ # ThreeScale::MetricInvalid:: +usage+ contains invalid metrics
108
+ # ThreeScale::ContractNotActive:: contract is not active
109
+ # ThreeScale::LimitsExceeded:: usage limits are exceeded
110
+ # ThreeScale::UnknownError:: some other unexpected error
111
+ #
112
+ def start(user_key, usage = {})
113
+ uri = URI.parse("#{host}/transactions.xml")
114
+ params = {
115
+ 'user_key' => prepare_key(user_key),
116
+ 'provider_key' => provider_authentication_key
117
+ }
118
+ params.merge!(encode_params(usage, 'usage'))
119
+ response = Net::HTTP.post_form(uri, params)
120
+
121
+ if response.is_a?(Net::HTTPSuccess)
122
+ element = Hpricot::XML(response.body).at('transaction')
123
+ [:id, :provider_verification_key, :contract_name].inject({}) do |memo, key|
124
+ memo[key] = element.at(key).inner_text if element.at(key)
125
+ memo
126
+ end
127
+ else
128
+ handle_error(response.body)
129
+ end
130
+ end
131
+
132
+ # Confirms a transaction.
133
+ #
134
+ # == Arguments
135
+ #
136
+ # +transaction_id+::
137
+ # A transaction id obtained from previous call to +start+.
138
+ # +usage+::
139
+ # A hash of metric names/values pairs containing actual resource usage
140
+ # of this request. This parameter is required only if no usage information
141
+ # was passed to method +start+ for this transaction, or if it was only
142
+ # approximate.
143
+ #
144
+ # == Return values
145
+ #
146
+ # If there were no exceptions raised, returns true.
147
+ #
148
+ # == Exceptions
149
+ #
150
+ # ThreeScale::TransactionNotFound:: transactions does not exits
151
+ # ThreeScale::ProviderKeyInvalid:: +provider_authentication_key+ is not valid
152
+ # ThreeScale::MetricInvalid:: +usage+ contains invalid metrics
153
+ # ThreeScale::UnknownError:: some other unexpected error
154
+ #
155
+ def confirm(transaction_id, usage = {})
156
+ uri = URI.parse("#{host}/transactions/#{CGI.escape(transaction_id.to_s)}/confirm.xml")
157
+ params = {
158
+ 'provider_key' => provider_authentication_key
159
+ }
160
+ params.merge!(encode_params(usage, 'usage'))
161
+
162
+ response = Net::HTTP.post_form(uri, params)
163
+ response.is_a?(Net::HTTPSuccess) ? true : handle_error(response.body)
164
+ end
165
+
166
+ # Cancels a transaction.
167
+ #
168
+ # Use this if request processing failed. Any estimated resource usage
169
+ # reported by preceding call to +start+ will be deleted. You don't have to
170
+ # call this if call to +start+ itself failed.
171
+ #
172
+ # == Arguments
173
+ #
174
+ # +transaction_id+::
175
+ # A transaction id obtained from previous call to +start+.
176
+ #
177
+ # == Return values
178
+ #
179
+ # If there were no exceptions raised, returns true.
180
+ #
181
+ # == Exceptions
182
+ #
183
+ # ThreeScale::TransactionNotFound:: transactions does not exits
184
+ # ThreeScale::ProviderKeyInvalid:: +provider_authentication_key+ is not valid
185
+ # ThreeScale::UnknownError:: some other unexpected error
186
+ #
187
+ def cancel(transaction_id)
188
+ uri = URI.parse("#{host}/transactions/#{CGI.escape(transaction_id.to_s)}.xml" +
189
+ "?provider_key=#{CGI.escape(provider_authentication_key)}")
190
+
191
+ response = Net::HTTP.start(uri.host, uri.port) do |http|
192
+ http.delete("#{uri.path}?#{uri.query}")
193
+ end
194
+
195
+ response.is_a?(Net::HTTPSuccess) ? true : handle_error(response.body)
196
+ end
197
+
198
+ KEY_PREFIX = '3scale-' # :nodoc:
199
+
200
+ # This can be used to quickly distinguish between keys used with 3scale
201
+ # system and any other keys the provider might use. Returns true if the key
202
+ # is for 3scale system.
203
+ def system_key?(key)
204
+ # Key should start with prefix
205
+ key.index(KEY_PREFIX) == 0
206
+ end
207
+
208
+ private
209
+
210
+ # Encode hash into form suitable for sending it as params of HTTP request.
211
+ def encode_params(params, prefix)
212
+ params.inject({}) do |memo, (key, value)|
213
+ memo["#{prefix}[#{CGI.escape(key)}]"] = CGI.escape(value.to_s)
214
+ memo
215
+ end
216
+ end
217
+
218
+ def prepare_key(key)
219
+ system_key?(key) ? key[KEY_PREFIX.length..-1] : key
220
+ end
221
+
222
+ CODES_TO_EXCEPTIONS = {
223
+ 'user.exceeded_limits' => LimitsExceeded,
224
+ 'user.invalid_key' => UserKeyInvalid,
225
+ 'user.inactive_contract' => ContractNotActive,
226
+ 'provider.invalid_key' => ProviderKeyInvalid,
227
+ 'provider.invalid_metric' => MetricInvalid,
228
+ 'provider.invalid_transaction_id' => TransactionNotFound} # :nodoc:
229
+
230
+ def handle_error(response)
231
+ element = Hpricot::XML(response).at('error')
232
+ raise UnknownError unless element
233
+ raise CODES_TO_EXCEPTIONS[element[:id]] || UnknownError, element.inner_text
234
+ end
235
+ end
236
+ end
@@ -0,0 +1 @@
1
+ require "#{File.dirname(__FILE__)}/3scale/interface"
@@ -0,0 +1,218 @@
1
+ require 'rubygems'
2
+ require 'activesupport'
3
+ require 'fake_web'
4
+ require 'mocha'
5
+ require 'test/unit'
6
+
7
+ require "#{File.dirname(__FILE__)}/../lib/3scale/interface"
8
+
9
+ class InterfaceTest < Test::Unit::TestCase
10
+ def setup
11
+ @interface = ThreeScale::Interface.new('http://3scale.net', 'some_key')
12
+ end
13
+
14
+ def test_start_should_raise_exception_on_invalid_user_key
15
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
16
+ :status => ['403', 'Forbidden'],
17
+ :string => stub_error('user.invalid_key'))
18
+
19
+ assert_raise ThreeScale::UserKeyInvalid do
20
+ @interface.start('invalid_key')
21
+ end
22
+ end
23
+
24
+ def test_start_should_raise_exception_on_invalid_provider_key
25
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
26
+ :status => ['403', 'Forbidden'],
27
+ :string => stub_error('provider.invalid_key'))
28
+
29
+ assert_raise ThreeScale::ProviderKeyInvalid do
30
+ @interface.start('valid_key')
31
+ end
32
+ end
33
+
34
+ def test_start_should_raise_exception_on_inactive_contract
35
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
36
+ :status => ['403', 'Forbidden'],
37
+ :string => stub_error('user.inactive_contract'))
38
+
39
+ assert_raise ThreeScale::ContractNotActive do
40
+ @interface.start('valid_key', 'clicks' => 1)
41
+ end
42
+ end
43
+
44
+ def test_start_should_raise_exception_on_invalid_metric
45
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
46
+ :status => ['400', 'Bad Request'],
47
+ :string => stub_error('provider.invalid_metric'))
48
+
49
+ assert_raise ThreeScale::MetricInvalid do
50
+ @interface.start('valid_key', 'clicks' => 1)
51
+ end
52
+ end
53
+
54
+ def test_start_should_raise_exception_on_exceeded_limits
55
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
56
+ :status => ['403', 'Forbidden'],
57
+ :string => stub_error('user.exceeded_limits'))
58
+
59
+ assert_raise ThreeScale::LimitsExceeded do
60
+ @interface.start('valid_key', 'clicks' => 1)
61
+ end
62
+ end
63
+
64
+ def test_start_should_raise_exception_on_unexpected_error
65
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
66
+ :status => ['500', 'Internal Server Error'])
67
+
68
+ assert_raise ThreeScale::UnknownError do
69
+ @interface.start('valid_key', 'clicks' => 1)
70
+ end
71
+ end
72
+
73
+ def test_start_should_send_usage_data
74
+ Net::HTTP.expects(:post_form).
75
+ with(anything, has_entries('usage[hits]' => '1')).
76
+ returns(stub_response)
77
+
78
+ @interface.start('valid_key', 'hits' => 1)
79
+ end
80
+
81
+ def test_start_should_return_transaction_data_on_success
82
+ FakeWeb.register_uri('http://3scale.net/transactions.xml',
83
+ :status => ['200', 'OK'],
84
+ :string => {:id => '42', :provider_verification_key => 'some_key',
85
+ :contract_name => 'ultimate'}.to_xml(:root => 'transaction',
86
+ :dasherize => false))
87
+
88
+ result = @interface.start('valid_key', {'clicks' => 1})
89
+
90
+ assert_equal '42', result[:id]
91
+ assert_equal 'some_key', result[:provider_verification_key]
92
+ assert_equal 'ultimate', result[:contract_name]
93
+ end
94
+
95
+ def test_start_should_strip_3scale_prefix_from_user_key_before_sending
96
+ Net::HTTP.expects(:post_form).with(anything,
97
+ has_entries('user_key' => 'foo')).returns(stub_response)
98
+
99
+ @interface.start('3scale-foo')
100
+ end
101
+
102
+ def test_start_should_leave_user_key_unchanged_if_it_does_not_contain_3scale_prefix
103
+ Net::HTTP.expects(:post_form).with(anything,
104
+ has_entries('user_key' => 'foo')).returns(stub_response)
105
+
106
+ @interface.start('foo')
107
+ end
108
+
109
+ def test_confirm_should_raise_exception_on_invalid_transaction
110
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
111
+ :status => ['404', 'Not Found'],
112
+ :string => stub_error('provider.invalid_transaction_id'))
113
+
114
+ assert_raise ThreeScale::TransactionNotFound do
115
+ @interface.confirm(42)
116
+ end
117
+ end
118
+
119
+ def test_confirm_should_raise_exception_on_invalid_provider_key
120
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
121
+ :status => ['403', 'Forbidden'],
122
+ :string => stub_error('provider.invalid_key'))
123
+
124
+ assert_raise ThreeScale::ProviderKeyInvalid do
125
+ @interface.confirm(42)
126
+ end
127
+ end
128
+
129
+ def test_confirm_should_raise_exception_on_invalid_metric
130
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
131
+ :status => ['400', 'Bad Request'],
132
+ :string => stub_error('provider.invalid_metric'))
133
+
134
+ assert_raise ThreeScale::MetricInvalid do
135
+ @interface.confirm(42, 'clicks' => 1)
136
+ end
137
+ end
138
+
139
+ def test_confirm_should_raise_exception_on_unexpected_error
140
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
141
+ :status => ['500', 'Internal Server Error'])
142
+
143
+ assert_raise ThreeScale::UnknownError do
144
+ @interface.confirm(42)
145
+ end
146
+ end
147
+
148
+ def test_confirm_should_return_true_on_success
149
+ FakeWeb.register_uri('http://3scale.net/transactions/42/confirm.xml',
150
+ :status => ['200', 'OK'])
151
+
152
+ result = @interface.confirm(42, 'clicks' => 1)
153
+ assert_equal true, result
154
+ end
155
+
156
+ def test_confirm_should_send_usage_data
157
+ Net::HTTP.expects(:post_form).
158
+ with(anything, has_entries('usage[hits]' => '1')).
159
+ returns(stub_response)
160
+
161
+ @interface.confirm(42, 'hits' => 1)
162
+ end
163
+
164
+ def test_cancel_should_raise_exception_on_invalid_transaction
165
+ FakeWeb.register_uri('http://3scale.net/transactions/42.xml?provider_key=some_key',
166
+ :status => ['404', 'Not Found'],
167
+ :string => stub_error('provider.invalid_transaction_id'))
168
+
169
+ assert_raise ThreeScale::TransactionNotFound do
170
+ @interface.cancel(42)
171
+ end
172
+ end
173
+
174
+ def test_cancel_should_raise_exception_on_invalid_provider_key
175
+ FakeWeb.register_uri('http://3scale.net/transactions/42.xml?provider_key=some_key',
176
+ :status => ['403', 'Forbidden'],
177
+ :string => stub_error('provider.invalid_key'))
178
+
179
+ assert_raise ThreeScale::ProviderKeyInvalid do
180
+ @interface.cancel(42)
181
+ end
182
+ end
183
+
184
+ def test_cancel_should_raise_exception_on_unexpected_error
185
+ FakeWeb.register_uri('http://3scale.net/transactions/42.xml?provider_key=some_key',
186
+ :status => ['500', 'Internal Server Error'])
187
+
188
+ assert_raise ThreeScale::UnknownError do
189
+ @interface.cancel(42)
190
+ end
191
+ end
192
+
193
+ def test_cancel_should_return_true_on_success
194
+ FakeWeb.register_uri('http://3scale.net/transactions/42.xml?provider_key=some_key',
195
+ :status => ['200', 'OK'])
196
+
197
+ result = @interface.cancel(42)
198
+ assert_equal true, result
199
+ end
200
+
201
+ def test_should_identify_3scale_keys
202
+ assert @interface.system_key?('3scale-foo')
203
+ assert !@interface.system_key?('foo')
204
+ end
205
+
206
+ private
207
+
208
+ def stub_error(id)
209
+ "<error id=\"#{id}\">blah blah</error>"
210
+ end
211
+
212
+ def stub_response
213
+ response = stub
214
+ response.stubs(:is_a?).with(Net::HTTPSuccess).returns(true)
215
+ response.stubs(:body).returns('<transaction></transaction>')
216
+ response
217
+ end
218
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: 3scale-3scale_ws_api_for_ruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.7
5
+ platform: ruby
6
+ authors:
7
+ - "Adam Cig\xC3\xA1nek"
8
+ - Josep M. Pujol
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2008-10-27 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: hpricot
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.6.161
24
+ version:
25
+ description:
26
+ email: adam@3scale.net
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - init.rb
35
+ - lib/3scale/interface.rb
36
+ - lib/3scale_interface.rb
37
+ - README
38
+ - Rakefile
39
+ - test/interface_test.rb
40
+ has_rdoc: true
41
+ homepage: http://www.3scale.net
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: "0"
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.2.0
63
+ signing_key:
64
+ specification_version: 2
65
+ summary: 3scale web service management API for Ruby.
66
+ test_files:
67
+ - test/interface_test.rb