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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +317 -0
- data/Gemfile +11 -0
- data/Gemfile.base +65 -0
- data/Gemfile.lock +319 -0
- data/Gemfile.on_prem +1 -0
- data/Gemfile.on_prem.lock +297 -0
- data/LICENSE +202 -0
- data/NOTICE +15 -0
- data/README.md +230 -0
- data/Rakefile +287 -0
- data/apisonator.gemspec +47 -0
- data/app/api/api.rb +13 -0
- data/app/api/internal/alert_limits.rb +32 -0
- data/app/api/internal/application_keys.rb +49 -0
- data/app/api/internal/application_referrer_filters.rb +43 -0
- data/app/api/internal/applications.rb +77 -0
- data/app/api/internal/errors.rb +54 -0
- data/app/api/internal/events.rb +42 -0
- data/app/api/internal/internal.rb +104 -0
- data/app/api/internal/metrics.rb +40 -0
- data/app/api/internal/service_tokens.rb +46 -0
- data/app/api/internal/services.rb +58 -0
- data/app/api/internal/stats.rb +42 -0
- data/app/api/internal/usagelimits.rb +62 -0
- data/app/api/internal/utilization.rb +23 -0
- data/bin/3scale_backend +223 -0
- data/bin/3scale_backend_worker +26 -0
- data/config.ru +4 -0
- data/config/puma.rb +192 -0
- data/config/schedule.rb +9 -0
- data/ext/mkrf_conf.rb +64 -0
- data/lib/3scale/backend.rb +67 -0
- data/lib/3scale/backend/alert_limit.rb +56 -0
- data/lib/3scale/backend/alerts.rb +137 -0
- data/lib/3scale/backend/analytics/kinesis.rb +3 -0
- data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
- data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
- data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
- data/lib/3scale/backend/analytics/redshift.rb +3 -0
- data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
- data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
- data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
- data/lib/3scale/backend/application.rb +330 -0
- data/lib/3scale/backend/application_events.rb +76 -0
- data/lib/3scale/backend/background_job.rb +65 -0
- data/lib/3scale/backend/configurable.rb +20 -0
- data/lib/3scale/backend/configuration.rb +151 -0
- data/lib/3scale/backend/configuration/loader.rb +42 -0
- data/lib/3scale/backend/constants.rb +19 -0
- data/lib/3scale/backend/cors.rb +84 -0
- data/lib/3scale/backend/distributed_lock.rb +67 -0
- data/lib/3scale/backend/environment.rb +21 -0
- data/lib/3scale/backend/error_storage.rb +52 -0
- data/lib/3scale/backend/errors.rb +343 -0
- data/lib/3scale/backend/event_storage.rb +120 -0
- data/lib/3scale/backend/experiment.rb +84 -0
- data/lib/3scale/backend/extensions.rb +5 -0
- data/lib/3scale/backend/extensions/array.rb +19 -0
- data/lib/3scale/backend/extensions/hash.rb +26 -0
- data/lib/3scale/backend/extensions/nil_class.rb +13 -0
- data/lib/3scale/backend/extensions/redis.rb +44 -0
- data/lib/3scale/backend/extensions/string.rb +13 -0
- data/lib/3scale/backend/extensions/time.rb +110 -0
- data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
- data/lib/3scale/backend/job_fetcher.rb +122 -0
- data/lib/3scale/backend/listener.rb +728 -0
- data/lib/3scale/backend/listener_metrics.rb +99 -0
- data/lib/3scale/backend/logging.rb +48 -0
- data/lib/3scale/backend/logging/external.rb +44 -0
- data/lib/3scale/backend/logging/external/impl.rb +93 -0
- data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
- data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
- data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
- data/lib/3scale/backend/logging/external/resque.rb +57 -0
- data/lib/3scale/backend/logging/logger.rb +18 -0
- data/lib/3scale/backend/logging/middleware.rb +62 -0
- data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
- data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
- data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
- data/lib/3scale/backend/logging/worker.rb +107 -0
- data/lib/3scale/backend/manifest.rb +80 -0
- data/lib/3scale/backend/memoizer.rb +277 -0
- data/lib/3scale/backend/metric.rb +275 -0
- data/lib/3scale/backend/metric/collection.rb +91 -0
- data/lib/3scale/backend/oauth.rb +4 -0
- data/lib/3scale/backend/oauth/token.rb +26 -0
- data/lib/3scale/backend/oauth/token_key.rb +30 -0
- data/lib/3scale/backend/oauth/token_storage.rb +313 -0
- data/lib/3scale/backend/oauth/token_value.rb +25 -0
- data/lib/3scale/backend/period.rb +3 -0
- data/lib/3scale/backend/period/boundary.rb +107 -0
- data/lib/3scale/backend/period/cache.rb +28 -0
- data/lib/3scale/backend/period/period.rb +402 -0
- data/lib/3scale/backend/queue_storage.rb +16 -0
- data/lib/3scale/backend/rack.rb +49 -0
- data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
- data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
- data/lib/3scale/backend/rack/prometheus.rb +19 -0
- data/lib/3scale/backend/saas.rb +6 -0
- data/lib/3scale/backend/saas_analytics.rb +4 -0
- data/lib/3scale/backend/server.rb +30 -0
- data/lib/3scale/backend/server/falcon.rb +52 -0
- data/lib/3scale/backend/server/puma.rb +71 -0
- data/lib/3scale/backend/service.rb +317 -0
- data/lib/3scale/backend/service_token.rb +97 -0
- data/lib/3scale/backend/stats.rb +8 -0
- data/lib/3scale/backend/stats/aggregator.rb +170 -0
- data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
- data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
- data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
- data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
- data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
- data/lib/3scale/backend/stats/cleaner.rb +195 -0
- data/lib/3scale/backend/stats/codes_commons.rb +14 -0
- data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
- data/lib/3scale/backend/stats/key_generator.rb +73 -0
- data/lib/3scale/backend/stats/keys.rb +104 -0
- data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
- data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
- data/lib/3scale/backend/stats/period_commons.rb +34 -0
- data/lib/3scale/backend/stats/stats_parser.rb +141 -0
- data/lib/3scale/backend/stats/storage.rb +113 -0
- data/lib/3scale/backend/statsd.rb +14 -0
- data/lib/3scale/backend/storable.rb +35 -0
- data/lib/3scale/backend/storage.rb +40 -0
- data/lib/3scale/backend/storage_async.rb +4 -0
- data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
- data/lib/3scale/backend/storage_async/client.rb +205 -0
- data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
- data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
- data/lib/3scale/backend/storage_helpers.rb +278 -0
- data/lib/3scale/backend/storage_key_helpers.rb +9 -0
- data/lib/3scale/backend/storage_sync.rb +43 -0
- data/lib/3scale/backend/transaction.rb +62 -0
- data/lib/3scale/backend/transactor.rb +177 -0
- data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
- data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
- data/lib/3scale/backend/transactor/notify_job.rb +47 -0
- data/lib/3scale/backend/transactor/process_job.rb +33 -0
- data/lib/3scale/backend/transactor/report_job.rb +84 -0
- data/lib/3scale/backend/transactor/status.rb +236 -0
- data/lib/3scale/backend/transactor/usage_report.rb +182 -0
- data/lib/3scale/backend/usage.rb +63 -0
- data/lib/3scale/backend/usage_limit.rb +115 -0
- data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
- data/lib/3scale/backend/util.rb +17 -0
- data/lib/3scale/backend/validators.rb +26 -0
- data/lib/3scale/backend/validators/base.rb +36 -0
- data/lib/3scale/backend/validators/key.rb +17 -0
- data/lib/3scale/backend/validators/limits.rb +57 -0
- data/lib/3scale/backend/validators/oauth_key.rb +15 -0
- data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
- data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
- data/lib/3scale/backend/validators/referrer.rb +60 -0
- data/lib/3scale/backend/validators/service_state.rb +15 -0
- data/lib/3scale/backend/validators/state.rb +15 -0
- data/lib/3scale/backend/version.rb +5 -0
- data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
- data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
- data/lib/3scale/backend/worker.rb +87 -0
- data/lib/3scale/backend/worker_async.rb +88 -0
- data/lib/3scale/backend/worker_metrics.rb +44 -0
- data/lib/3scale/backend/worker_sync.rb +32 -0
- data/lib/3scale/bundler_shim.rb +17 -0
- data/lib/3scale/prometheus_server.rb +10 -0
- data/lib/3scale/tasks/connectivity.rake +41 -0
- data/lib/3scale/tasks/helpers.rb +3 -0
- data/lib/3scale/tasks/helpers/environment.rb +23 -0
- data/lib/3scale/tasks/stats.rake +131 -0
- data/lib/3scale/tasks/swagger.rake +46 -0
- data/licenses.xml +1215 -0
- metadata +227 -0
|
@@ -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
|