apisonator 2.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +317 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.base +65 -0
  5. data/Gemfile.lock +319 -0
  6. data/Gemfile.on_prem +1 -0
  7. data/Gemfile.on_prem.lock +297 -0
  8. data/LICENSE +202 -0
  9. data/NOTICE +15 -0
  10. data/README.md +230 -0
  11. data/Rakefile +287 -0
  12. data/apisonator.gemspec +47 -0
  13. data/app/api/api.rb +13 -0
  14. data/app/api/internal/alert_limits.rb +32 -0
  15. data/app/api/internal/application_keys.rb +49 -0
  16. data/app/api/internal/application_referrer_filters.rb +43 -0
  17. data/app/api/internal/applications.rb +77 -0
  18. data/app/api/internal/errors.rb +54 -0
  19. data/app/api/internal/events.rb +42 -0
  20. data/app/api/internal/internal.rb +104 -0
  21. data/app/api/internal/metrics.rb +40 -0
  22. data/app/api/internal/service_tokens.rb +46 -0
  23. data/app/api/internal/services.rb +58 -0
  24. data/app/api/internal/stats.rb +42 -0
  25. data/app/api/internal/usagelimits.rb +62 -0
  26. data/app/api/internal/utilization.rb +23 -0
  27. data/bin/3scale_backend +223 -0
  28. data/bin/3scale_backend_worker +26 -0
  29. data/config.ru +4 -0
  30. data/config/puma.rb +192 -0
  31. data/config/schedule.rb +9 -0
  32. data/ext/mkrf_conf.rb +64 -0
  33. data/lib/3scale/backend.rb +67 -0
  34. data/lib/3scale/backend/alert_limit.rb +56 -0
  35. data/lib/3scale/backend/alerts.rb +137 -0
  36. data/lib/3scale/backend/analytics/kinesis.rb +3 -0
  37. data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
  38. data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
  39. data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
  40. data/lib/3scale/backend/analytics/redshift.rb +3 -0
  41. data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
  42. data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
  43. data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
  44. data/lib/3scale/backend/application.rb +330 -0
  45. data/lib/3scale/backend/application_events.rb +76 -0
  46. data/lib/3scale/backend/background_job.rb +65 -0
  47. data/lib/3scale/backend/configurable.rb +20 -0
  48. data/lib/3scale/backend/configuration.rb +151 -0
  49. data/lib/3scale/backend/configuration/loader.rb +42 -0
  50. data/lib/3scale/backend/constants.rb +19 -0
  51. data/lib/3scale/backend/cors.rb +84 -0
  52. data/lib/3scale/backend/distributed_lock.rb +67 -0
  53. data/lib/3scale/backend/environment.rb +21 -0
  54. data/lib/3scale/backend/error_storage.rb +52 -0
  55. data/lib/3scale/backend/errors.rb +343 -0
  56. data/lib/3scale/backend/event_storage.rb +120 -0
  57. data/lib/3scale/backend/experiment.rb +84 -0
  58. data/lib/3scale/backend/extensions.rb +5 -0
  59. data/lib/3scale/backend/extensions/array.rb +19 -0
  60. data/lib/3scale/backend/extensions/hash.rb +26 -0
  61. data/lib/3scale/backend/extensions/nil_class.rb +13 -0
  62. data/lib/3scale/backend/extensions/redis.rb +44 -0
  63. data/lib/3scale/backend/extensions/string.rb +13 -0
  64. data/lib/3scale/backend/extensions/time.rb +110 -0
  65. data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
  66. data/lib/3scale/backend/job_fetcher.rb +122 -0
  67. data/lib/3scale/backend/listener.rb +728 -0
  68. data/lib/3scale/backend/listener_metrics.rb +99 -0
  69. data/lib/3scale/backend/logging.rb +48 -0
  70. data/lib/3scale/backend/logging/external.rb +44 -0
  71. data/lib/3scale/backend/logging/external/impl.rb +93 -0
  72. data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
  73. data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
  74. data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
  75. data/lib/3scale/backend/logging/external/resque.rb +57 -0
  76. data/lib/3scale/backend/logging/logger.rb +18 -0
  77. data/lib/3scale/backend/logging/middleware.rb +62 -0
  78. data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
  79. data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
  80. data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
  81. data/lib/3scale/backend/logging/worker.rb +107 -0
  82. data/lib/3scale/backend/manifest.rb +80 -0
  83. data/lib/3scale/backend/memoizer.rb +277 -0
  84. data/lib/3scale/backend/metric.rb +275 -0
  85. data/lib/3scale/backend/metric/collection.rb +91 -0
  86. data/lib/3scale/backend/oauth.rb +4 -0
  87. data/lib/3scale/backend/oauth/token.rb +26 -0
  88. data/lib/3scale/backend/oauth/token_key.rb +30 -0
  89. data/lib/3scale/backend/oauth/token_storage.rb +313 -0
  90. data/lib/3scale/backend/oauth/token_value.rb +25 -0
  91. data/lib/3scale/backend/period.rb +3 -0
  92. data/lib/3scale/backend/period/boundary.rb +107 -0
  93. data/lib/3scale/backend/period/cache.rb +28 -0
  94. data/lib/3scale/backend/period/period.rb +402 -0
  95. data/lib/3scale/backend/queue_storage.rb +16 -0
  96. data/lib/3scale/backend/rack.rb +49 -0
  97. data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
  98. data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
  99. data/lib/3scale/backend/rack/prometheus.rb +19 -0
  100. data/lib/3scale/backend/saas.rb +6 -0
  101. data/lib/3scale/backend/saas_analytics.rb +4 -0
  102. data/lib/3scale/backend/server.rb +30 -0
  103. data/lib/3scale/backend/server/falcon.rb +52 -0
  104. data/lib/3scale/backend/server/puma.rb +71 -0
  105. data/lib/3scale/backend/service.rb +317 -0
  106. data/lib/3scale/backend/service_token.rb +97 -0
  107. data/lib/3scale/backend/stats.rb +8 -0
  108. data/lib/3scale/backend/stats/aggregator.rb +170 -0
  109. data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
  110. data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
  111. data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
  112. data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
  113. data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
  114. data/lib/3scale/backend/stats/cleaner.rb +195 -0
  115. data/lib/3scale/backend/stats/codes_commons.rb +14 -0
  116. data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
  117. data/lib/3scale/backend/stats/key_generator.rb +73 -0
  118. data/lib/3scale/backend/stats/keys.rb +104 -0
  119. data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
  120. data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
  121. data/lib/3scale/backend/stats/period_commons.rb +34 -0
  122. data/lib/3scale/backend/stats/stats_parser.rb +141 -0
  123. data/lib/3scale/backend/stats/storage.rb +113 -0
  124. data/lib/3scale/backend/statsd.rb +14 -0
  125. data/lib/3scale/backend/storable.rb +35 -0
  126. data/lib/3scale/backend/storage.rb +40 -0
  127. data/lib/3scale/backend/storage_async.rb +4 -0
  128. data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
  129. data/lib/3scale/backend/storage_async/client.rb +205 -0
  130. data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
  131. data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
  132. data/lib/3scale/backend/storage_helpers.rb +278 -0
  133. data/lib/3scale/backend/storage_key_helpers.rb +9 -0
  134. data/lib/3scale/backend/storage_sync.rb +43 -0
  135. data/lib/3scale/backend/transaction.rb +62 -0
  136. data/lib/3scale/backend/transactor.rb +177 -0
  137. data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
  138. data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
  139. data/lib/3scale/backend/transactor/notify_job.rb +47 -0
  140. data/lib/3scale/backend/transactor/process_job.rb +33 -0
  141. data/lib/3scale/backend/transactor/report_job.rb +84 -0
  142. data/lib/3scale/backend/transactor/status.rb +236 -0
  143. data/lib/3scale/backend/transactor/usage_report.rb +182 -0
  144. data/lib/3scale/backend/usage.rb +63 -0
  145. data/lib/3scale/backend/usage_limit.rb +115 -0
  146. data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
  147. data/lib/3scale/backend/util.rb +17 -0
  148. data/lib/3scale/backend/validators.rb +26 -0
  149. data/lib/3scale/backend/validators/base.rb +36 -0
  150. data/lib/3scale/backend/validators/key.rb +17 -0
  151. data/lib/3scale/backend/validators/limits.rb +57 -0
  152. data/lib/3scale/backend/validators/oauth_key.rb +15 -0
  153. data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
  154. data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
  155. data/lib/3scale/backend/validators/referrer.rb +60 -0
  156. data/lib/3scale/backend/validators/service_state.rb +15 -0
  157. data/lib/3scale/backend/validators/state.rb +15 -0
  158. data/lib/3scale/backend/version.rb +5 -0
  159. data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
  160. data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
  161. data/lib/3scale/backend/worker.rb +87 -0
  162. data/lib/3scale/backend/worker_async.rb +88 -0
  163. data/lib/3scale/backend/worker_metrics.rb +44 -0
  164. data/lib/3scale/backend/worker_sync.rb +32 -0
  165. data/lib/3scale/bundler_shim.rb +17 -0
  166. data/lib/3scale/prometheus_server.rb +10 -0
  167. data/lib/3scale/tasks/connectivity.rake +41 -0
  168. data/lib/3scale/tasks/helpers.rb +3 -0
  169. data/lib/3scale/tasks/helpers/environment.rb +23 -0
  170. data/lib/3scale/tasks/stats.rake +131 -0
  171. data/lib/3scale/tasks/swagger.rake +46 -0
  172. data/licenses.xml +1215 -0
  173. metadata +227 -0
@@ -0,0 +1,9 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module StorageKeyHelpers
4
+ def encode_key(key)
5
+ key.to_s.tr(' ', '+')
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,43 @@
1
+ # used to provide a Redis client based on a configuration object
2
+ require 'uri'
3
+
4
+ module ThreeScale
5
+ module Backend
6
+ class StorageSync
7
+ include Configurable
8
+
9
+ class << self
10
+ # Returns a shared instance of the storage. If there is no instance yet,
11
+ # creates one first. If you want to always create a fresh instance, set
12
+ # the +reset+ parameter to true.
13
+ def instance(reset = false)
14
+ if reset || @instance.nil?
15
+ @instance = new(Storage::Helpers.config_with(configuration.redis,
16
+ options: get_options))
17
+ else
18
+ @instance
19
+ end
20
+ end
21
+
22
+ def new(options)
23
+ Redis.new options
24
+ end
25
+
26
+ private
27
+
28
+ if ThreeScale::Backend.production?
29
+ def get_options
30
+ {}
31
+ end
32
+ else
33
+ DEFAULT_SERVER = '127.0.0.1:22121'.freeze
34
+ private_constant :DEFAULT_SERVER
35
+
36
+ def get_options
37
+ { default_url: DEFAULT_SERVER }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,62 @@
1
+ module ThreeScale
2
+ module Backend
3
+ class Transaction
4
+ # We accept transactions with a timestamp ts where ts:
5
+ # now - REPORT_DEADLINE_PAST <= ts <= now + REPORT_DEADLINE_FUTURE
6
+ REPORT_DEADLINE_PAST = 24*60*60
7
+ private_constant :REPORT_DEADLINE_PAST
8
+
9
+ REPORT_DEADLINE_FUTURE = 60*60
10
+ private_constant :REPORT_DEADLINE_FUTURE
11
+
12
+ # We can define an allowed range assuming Time.now = 0
13
+ DEADLINE_RANGE = (-REPORT_DEADLINE_PAST..REPORT_DEADLINE_FUTURE).freeze
14
+ private_constant :DEADLINE_RANGE
15
+
16
+ ATTRIBUTES = [:service_id, :application_id, :timestamp,
17
+ :log, :usage, :response_code]
18
+ private_constant :ATTRIBUTES
19
+
20
+ class_eval { attr_accessor *ATTRIBUTES }
21
+
22
+ def initialize(params = {})
23
+ ATTRIBUTES.each { |attr| send("#{attr}=", (params[attr] || params[attr.to_s])) }
24
+ end
25
+
26
+ def timestamp=(value = nil)
27
+ if value.is_a?(Time)
28
+ @timestamp = value
29
+ else
30
+ @timestamp = Time.parse_to_utc(value) || Time.now.getutc
31
+ end
32
+ end
33
+
34
+
35
+ def extract_response_code
36
+ if (response_code.is_a?(String) && response_code =~ /\A\d{3}\z/) ||
37
+ (response_code.is_a?(Integer) && (100 ..999).cover?(response_code) )
38
+ response_code.to_i
39
+ else
40
+ false
41
+ end
42
+ end
43
+
44
+ # Validates if transaction timestamp is within accepted range
45
+ #
46
+ # @return [unspecified] if the timestamp is within the valid range.
47
+ # @raise [TransactionTimestampTooOld] if the timestamp is too old
48
+ # @raise [TransactionTimestampTooNew] if the timestamp is too new
49
+ def ensure_on_time!
50
+ time_diff_sec = timestamp.to_i - Time.now.to_i
51
+
52
+ unless DEADLINE_RANGE.cover?(time_diff_sec)
53
+ if time_diff_sec < 0
54
+ fail(TransactionTimestampTooOld, REPORT_DEADLINE_PAST)
55
+ else
56
+ fail(TransactionTimestampTooNew, REPORT_DEADLINE_FUTURE)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,177 @@
1
+ require '3scale/backend/transactor/notify_batcher'
2
+ require '3scale/backend/transactor/notify_job'
3
+ require '3scale/backend/transactor/process_job'
4
+ require '3scale/backend/transactor/report_job'
5
+ require '3scale/backend/transactor/usage_report'
6
+ require '3scale/backend/transactor/status'
7
+ require '3scale/backend/transactor/limit_headers'
8
+ require '3scale/backend/errors'
9
+ require '3scale/backend/validators'
10
+ require '3scale/backend/stats/keys'
11
+
12
+ module ThreeScale
13
+ module Backend
14
+ # Methods for reporting and authorizing transactions.
15
+ module Transactor
16
+ include Backend::StorageKeyHelpers
17
+ include NotifyBatcher
18
+ extend self
19
+
20
+ def report(provider_key, service_id, transactions, context_info = {})
21
+ service = Service.load_with_provider_key!(service_id, provider_key)
22
+
23
+ report_enqueue(service.id, transactions, context_info)
24
+ notify_report(provider_key, transactions.size)
25
+ end
26
+
27
+ def authorize(provider_key, params, context_info = {})
28
+ do_authorize :authorize, provider_key, params, context_info
29
+ end
30
+
31
+ def oauth_authorize(provider_key, params, context_info = {})
32
+ do_authorize :oauth_authorize, provider_key, params, context_info
33
+ end
34
+
35
+ def authrep(provider_key, params, context_info = {})
36
+ do_authrep :authrep, provider_key, params, context_info
37
+ end
38
+
39
+ def oauth_authrep(provider_key, params, context_info = {})
40
+ do_authrep :oauth_authrep, provider_key, params, context_info
41
+ end
42
+
43
+ def utilization(service_id, application_id)
44
+ application = Application.load!(service_id, application_id)
45
+ application.load_metric_names
46
+ usage = Usage.application_usage(application, Time.now.getutc)
47
+ status = Status.new(service_id: service_id,
48
+ application: application,
49
+ values: usage)
50
+ Validators::Limits.apply(status, {})
51
+ status.application_usage_reports
52
+ end
53
+
54
+ private
55
+
56
+ def validate(oauth, provider_key, report_usage, params, request_info)
57
+ service = Service.load_with_provider_key!(params[:service_id], provider_key)
58
+ # service_id cannot be taken from params since it might be missing there
59
+ service_id = service.id
60
+
61
+ app_id = params[:app_id]
62
+ # TODO: make sure params are nil if they are empty up the call stack
63
+ # Note: app_key is an exception, as it being empty is semantically
64
+ # significant.
65
+ params[:app_id] = nil if app_id && app_id.empty?
66
+
67
+ if oauth
68
+ if app_id.nil?
69
+ access_token = params[:access_token]
70
+ access_token = nil if access_token && access_token.empty?
71
+
72
+ if access_token.nil?
73
+ raise ApplicationNotFound.new nil if app_id.nil?
74
+ else
75
+ app_id = get_token_ids(access_token, service_id, app_id)
76
+ # update params, since they are checked elsewhere
77
+ params[:app_id] = app_id
78
+ end
79
+ end
80
+
81
+ validators = Validators::OAUTH_VALIDATORS
82
+ else
83
+ validators = Validators::VALIDATORS
84
+ end
85
+
86
+ params[:user_key] = nil if params[:user_key] && params[:user_key].empty?
87
+ application = Application.load_by_id_or_user_key!(service_id,
88
+ app_id,
89
+ params[:user_key])
90
+ now = Time.now.getutc
91
+ usage_values = Usage.application_usage(application, now)
92
+ extensions = request_info && request_info[:extensions] || {}
93
+ status_attrs = {
94
+ service_id: service_id,
95
+ application: application,
96
+ oauth: oauth,
97
+ usage: params[:usage],
98
+ predicted_usage: !report_usage,
99
+ values: usage_values,
100
+ # hierarchy parameter adds information in the response needed
101
+ # to derive which limits affect directly or indirectly the
102
+ # metrics for which authorization is requested.
103
+ hierarchy: extensions[:hierarchy] == '1',
104
+ flat_usage: extensions[:flat_usage] == '1'
105
+ }
106
+
107
+ application.load_metric_names
108
+
109
+ # returns a status object
110
+ apply_validators(validators, status_attrs, params)
111
+ end
112
+
113
+ def get_token_ids(token, service_id, app_id)
114
+ begin
115
+ token_aid = OAuth::Token::Storage.get_credentials(token, service_id)
116
+ rescue AccessTokenInvalid => e
117
+ # Yep, well, er. Someone specified that it is OK to have an
118
+ # invalid token if an app_id is specified. Somehow passing in
119
+ # a user_key is still not enough, though...
120
+ raise e if app_id.nil?
121
+ end
122
+
123
+ # We only take the token ids into account if we had no parameter ids
124
+ if app_id.nil?
125
+ app_id = token_aid
126
+ end
127
+
128
+ app_id
129
+ end
130
+
131
+ def do_authorize(method, provider_key, params, context_info)
132
+ notify_authorize(provider_key)
133
+ validate(method == :oauth_authorize, provider_key, false, params, context_info[:request])
134
+ end
135
+
136
+ def do_authrep(method, provider_key, params, context_info)
137
+ request_info = context_info[:request] || {}
138
+ status = begin
139
+ validate(method == :oauth_authrep, provider_key, true, params, request_info)
140
+ rescue ApplicationNotFound => e
141
+ # we still want to track these
142
+ notify_authorize(provider_key)
143
+ raise e
144
+ end
145
+
146
+ usage = params[:usage]
147
+
148
+ if (usage || params[:log]) && status.authorized?
149
+ application_id = status.application.id
150
+ report_enqueue(status.service_id, { 0 => {"app_id" => application_id, "usage" => usage, "log" => params[:log] } }, request: { extensions: request_info[:extensions] })
151
+ notify_authrep(provider_key, usage ? 1 : 0)
152
+ else
153
+ notify_authorize(provider_key)
154
+ end
155
+
156
+ status
157
+ end
158
+
159
+ # This method applies the validators in the given order. If there is one
160
+ # that fails, it stops there instead of applying all of them.
161
+ # Returns a Status instance.
162
+ def apply_validators(validators, status_attrs, params)
163
+ status = Status.new(status_attrs)
164
+ validators.all? { |validator| validator.apply(status, params) }
165
+ status
166
+ end
167
+
168
+ def report_enqueue(service_id, data, context_info)
169
+ Resque.enqueue(ReportJob, service_id, data, Time.now.getutc.to_f, context_info)
170
+ end
171
+
172
+ def storage
173
+ Storage.instance
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,54 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module Transactor
4
+ module LimitHeaders
5
+ class << self
6
+ # Note: -1 is used in the remaining of usage reports to indicate that
7
+ # there is no limit.
8
+
9
+ UNCONSTRAINED = { remaining: -1, reset: -1 }.freeze
10
+ private_constant :UNCONSTRAINED
11
+
12
+ def get(reports, now = Time.now.utc)
13
+ report = most_constrained_report reports
14
+ if report && report.remaining_same_calls != -1
15
+ {
16
+ remaining: report.remaining_same_calls,
17
+ reset: report.remaining_time(now),
18
+ 'max-value': report.max_value
19
+ }
20
+ else
21
+ UNCONSTRAINED
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ # Will return the most constrained report
28
+ def most_constrained_report(reports)
29
+ reports.min do |a, b|
30
+ compare a, b
31
+ end
32
+ end
33
+
34
+ # Warning: comparison here returns from most to least constrained
35
+ # usage report (ie. in descending order)
36
+ def compare(one, other)
37
+ other_rem = other.remaining_same_calls
38
+ one_rem = one.remaining_same_calls
39
+ if one_rem == other_rem
40
+ # Note: the line below is correctly reversing the operands
41
+ # This is done to have the larger period ordered before the
42
+ # shorter ones when the remaining hits are the same.
43
+ other.period.granularity <=> one.period.granularity
44
+ elsif other_rem == -1 || one_rem < other_rem
45
+ -1
46
+ else
47
+ 1
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,139 @@
1
+ require 'resque'
2
+ require '3scale/backend/configuration'
3
+
4
+ module ThreeScale
5
+ module Backend
6
+ module Transactor
7
+
8
+ # This module is responsible for scheduling Notify jobs. These jobs are
9
+ # used to report the usage of some metrics specified in the master
10
+ # account.
11
+ module NotifyBatcher
12
+ include Resque::Helpers
13
+ include Configurable
14
+
15
+ def notify_authorize(provider_key)
16
+ notify(provider_key, configuration.master.metrics.transactions_authorize => 1)
17
+ end
18
+
19
+ def notify_authrep(provider_key, transactions)
20
+ notify(provider_key, configuration.master.metrics.transactions_authorize => 1,
21
+ configuration.master.metrics.transactions => transactions)
22
+ end
23
+
24
+ def notify_report(provider_key, transactions)
25
+ notify(provider_key, configuration.master.metrics.transactions => transactions)
26
+ end
27
+
28
+ def key_for_notifications_batch
29
+ "notify/aggregator/batch"
30
+ end
31
+
32
+ def notify(provider_key, usage)
33
+ # batch several notifications together so that we can process just one
34
+ # job for a group of them.
35
+ notify_batch(provider_key, usage)
36
+ end
37
+
38
+ def notify_batch(provider_key, usage)
39
+ # discard seconds so that all the notifications are stored in the same
40
+ # bucket, because aggregation is done at the minute level.
41
+ tt = Time.now.getutc
42
+ tt = tt - tt.sec
43
+
44
+ encoded = Yajl::Encoder.encode({
45
+ provider_key: provider_key,
46
+ usage: usage,
47
+ time: tt.to_s
48
+ })
49
+
50
+ num_elements = storage.rpush(key_for_notifications_batch, encoded)
51
+
52
+ if (num_elements % configuration.notification_batch) == 0
53
+ # batch is full
54
+ process_batch(num_elements)
55
+ end
56
+ end
57
+
58
+ def get_batch(num_elements)
59
+ storage.pipelined do
60
+ storage.lrange(key_for_notifications_batch, 0, num_elements - 1)
61
+ storage.ltrim(key_for_notifications_batch, num_elements, -1)
62
+ end.first
63
+ end
64
+
65
+ def process_batch(num_elements)
66
+ do_batch(get_batch num_elements)
67
+ end
68
+
69
+ def do_batch(list)
70
+ all = Hash.new
71
+
72
+ list.each do |item|
73
+ obj = decode(item)
74
+
75
+ provider_key = obj['provider_key'.freeze]
76
+ time = obj['time'.freeze]
77
+ usage = obj['usage'.freeze]
78
+
79
+ if usage.nil?
80
+ obj['usage'.freeze] = {}
81
+ end
82
+
83
+ bucket_key = "#{provider_key}-" << time
84
+ bucket_obj = all[bucket_key]
85
+
86
+ if bucket_obj.nil?
87
+ all[bucket_key] = obj
88
+ else
89
+ bucket_usage = bucket_obj['usage'.freeze]
90
+
91
+ usage.each do |metric_name, value|
92
+ bucket_usage[metric_name] =
93
+ bucket_usage.fetch(metric_name, 0) + value.to_i
94
+ end
95
+ end
96
+ end
97
+
98
+ enqueue_ts = Time.now.utc.to_f
99
+
100
+ all.each do |_, v|
101
+ enqueue_notify_job(v['provider_key'.freeze],
102
+ v['usage'.freeze],
103
+ v['time'.freeze],
104
+ enqueue_ts)
105
+ end
106
+ end
107
+
108
+ private
109
+
110
+ def enqueue_notify_job(provider_key, usage, timestamp, enqueue_ts)
111
+ ::Resque.enqueue(NotifyJob,
112
+ provider_key,
113
+ usage,
114
+ timestamp,
115
+ enqueue_ts)
116
+ end
117
+
118
+ if ThreeScale::Backend.test?
119
+ module Test
120
+ def get_full_batch
121
+ storage.pipelined do
122
+ storage.lrange(key_for_notifications_batch, 0, -1)
123
+ storage.del(key_for_notifications_batch)
124
+ end.first
125
+ end
126
+
127
+ def process_full_batch
128
+ do_batch(get_full_batch)
129
+ end
130
+ end
131
+
132
+ private_constant :Test
133
+
134
+ include Test
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end