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,47 @@
|
|
|
1
|
+
module ThreeScale
|
|
2
|
+
module Backend
|
|
3
|
+
module Transactor
|
|
4
|
+
#
|
|
5
|
+
# Job for notifying about backend calls.
|
|
6
|
+
class NotifyJob < BackgroundJob
|
|
7
|
+
extend Configurable
|
|
8
|
+
@queue = :main
|
|
9
|
+
|
|
10
|
+
InvalidMasterServiceId = Class.new(ThreeScale::Backend::Error)
|
|
11
|
+
|
|
12
|
+
class << self
|
|
13
|
+
def perform_logged(provider_key, usage, timestamp, _enqueue_time)
|
|
14
|
+
application_id = Application.load_id_by_key(master_service_id, provider_key)
|
|
15
|
+
|
|
16
|
+
if application_id && Application.exists?(master_service_id, application_id)
|
|
17
|
+
master_metrics = Metric.load_all(master_service_id)
|
|
18
|
+
|
|
19
|
+
ProcessJob.perform([{
|
|
20
|
+
service_id: master_service_id,
|
|
21
|
+
application_id: application_id,
|
|
22
|
+
timestamp: timestamp,
|
|
23
|
+
usage: master_metrics.process_usage(usage)
|
|
24
|
+
}])
|
|
25
|
+
end
|
|
26
|
+
[true, "#{provider_key} #{application_id || '--'}"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def master_service_id
|
|
32
|
+
value = configuration.master_service_id
|
|
33
|
+
|
|
34
|
+
unless value
|
|
35
|
+
raise InvalidMasterServiceId,
|
|
36
|
+
"Can't find master service id. Make sure the \"master_service_id\" "\
|
|
37
|
+
'configuration value is set correctly'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
value.to_s
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
require '3scale/backend/transaction'
|
|
2
|
+
|
|
3
|
+
module ThreeScale
|
|
4
|
+
module Backend
|
|
5
|
+
module Transactor
|
|
6
|
+
# Job for processing (aggregating and archiving) transactions.
|
|
7
|
+
|
|
8
|
+
## WARNING: This is not a resque job, the .perform is called by another
|
|
9
|
+
## job, either Report or NotifyJob it's meant to be like this in case we
|
|
10
|
+
## want to detach it further
|
|
11
|
+
class ProcessJob
|
|
12
|
+
|
|
13
|
+
class << self
|
|
14
|
+
def perform(transactions)
|
|
15
|
+
transactions = preprocess(transactions)
|
|
16
|
+
Stats::Aggregator.process(transactions)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def preprocess(transactions)
|
|
22
|
+
transactions.map do |transaction_attrs|
|
|
23
|
+
transaction = Transaction.new(transaction_attrs)
|
|
24
|
+
transaction.ensure_on_time!
|
|
25
|
+
transaction
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module ThreeScale
|
|
2
|
+
module Backend
|
|
3
|
+
module Transactor
|
|
4
|
+
|
|
5
|
+
# Job for reporting transactions.
|
|
6
|
+
class ReportJob < BackgroundJob
|
|
7
|
+
@queue = :priority
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
def perform_logged(service_id, raw_transactions, _enqueue_time, context_info = {})
|
|
11
|
+
context_info ||= {} # avoid nils potentially existing in older versions
|
|
12
|
+
request_info = context_info['request'.freeze] || {}
|
|
13
|
+
|
|
14
|
+
transactions = parse_transactions(service_id, raw_transactions, request_info)
|
|
15
|
+
ProcessJob.perform(transactions) if !transactions.nil? && transactions.size > 0
|
|
16
|
+
|
|
17
|
+
# Last field was logs.size. Set it to 0 until we modify our parsers.
|
|
18
|
+
[true, "#{service_id} #{transactions.size} 0"]
|
|
19
|
+
rescue Error => error
|
|
20
|
+
ErrorStorage.store(service_id, error, context_info)
|
|
21
|
+
[false, "#{service_id} #{error}"]
|
|
22
|
+
rescue Exception => error
|
|
23
|
+
if error.class == ArgumentError &&
|
|
24
|
+
error.message == "invalid byte sequence in UTF-8"
|
|
25
|
+
|
|
26
|
+
ErrorStorage.store(service_id, NotValidData.new, context_info)
|
|
27
|
+
[false, "#{service_id} #{error}"]
|
|
28
|
+
else
|
|
29
|
+
raise error
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def enqueue_time(args)
|
|
36
|
+
args[2]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parse_transactions(service_id, raw_transactions, request_info)
|
|
40
|
+
transactions = []
|
|
41
|
+
exts = request_info['extensions'.freeze]&.symbolize_names || {}
|
|
42
|
+
|
|
43
|
+
group_by_application_id(service_id, raw_transactions) do |app_id, group|
|
|
44
|
+
group.each do |raw_transaction|
|
|
45
|
+
|
|
46
|
+
transaction = compose_transaction(service_id, app_id, raw_transaction, exts)
|
|
47
|
+
log = raw_transaction['log']
|
|
48
|
+
|
|
49
|
+
transaction[:response_code] = log['code'] if transaction && log
|
|
50
|
+
|
|
51
|
+
transactions << transaction if transaction
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
transactions
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def group_by_application_id(service_id, transactions, &block)
|
|
59
|
+
return [] if transactions.empty?
|
|
60
|
+
transactions = transactions.values if transactions.respond_to?(:values)
|
|
61
|
+
transactions.group_by do |transaction|
|
|
62
|
+
Application.extract_id!(service_id, transaction['app_id'], transaction['user_key'], transaction['access_token'])
|
|
63
|
+
end.each(&block)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def compose_transaction(service_id, app_id, raw_transaction, extensions)
|
|
67
|
+
usage = raw_transaction['usage']
|
|
68
|
+
|
|
69
|
+
if usage && !usage.empty?
|
|
70
|
+
metrics = Metric.load_all(service_id)
|
|
71
|
+
{
|
|
72
|
+
service_id: service_id,
|
|
73
|
+
application_id: app_id,
|
|
74
|
+
timestamp: raw_transaction['timestamp'],
|
|
75
|
+
usage: metrics.process_usage(usage,
|
|
76
|
+
extensions[:flat_usage] == '1'),
|
|
77
|
+
}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module ThreeScale
|
|
4
|
+
module Backend
|
|
5
|
+
module Transactor
|
|
6
|
+
class Status
|
|
7
|
+
# This is the default field we respond with when using OAuth redirects
|
|
8
|
+
# We only use 'redirect_uri' if a request sent such a param. See #397.
|
|
9
|
+
REDIRECT_URI_FIELD = 'redirect_url'.freeze
|
|
10
|
+
private_constant :REDIRECT_URI_FIELD
|
|
11
|
+
|
|
12
|
+
def initialize(attributes)
|
|
13
|
+
@service_id = attributes[:service_id]
|
|
14
|
+
@application = attributes[:application]
|
|
15
|
+
@oauth = attributes[:oauth]
|
|
16
|
+
@usage = attributes[:usage]
|
|
17
|
+
@predicted_usage = attributes[:predicted_usage]
|
|
18
|
+
@values = filter_values(attributes[:values] || {})
|
|
19
|
+
@timestamp = attributes[:timestamp] || Time.now.getutc
|
|
20
|
+
@hierarchy_ext = attributes[:hierarchy]
|
|
21
|
+
@flat_usage_ext = attributes[:flat_usage]
|
|
22
|
+
|
|
23
|
+
raise 'service_id not specified' if @service_id.nil?
|
|
24
|
+
raise ':application is required' if @application.nil?
|
|
25
|
+
|
|
26
|
+
@redirect_uri_field = REDIRECT_URI_FIELD
|
|
27
|
+
@authorized = true
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
attr_reader :service_id
|
|
31
|
+
attr_reader :application
|
|
32
|
+
attr_reader :oauth
|
|
33
|
+
attr_accessor :redirect_uri_field
|
|
34
|
+
attr_accessor :values
|
|
35
|
+
|
|
36
|
+
def flat_usage
|
|
37
|
+
@flat_usage_ext
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reject!(error)
|
|
41
|
+
@authorized = false
|
|
42
|
+
@rejection_reason_code ||= error.code
|
|
43
|
+
@rejection_reason_text ||= error.message
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
attr_reader :timestamp
|
|
47
|
+
attr_reader :rejection_reason_code
|
|
48
|
+
attr_reader :rejection_reason_text
|
|
49
|
+
|
|
50
|
+
# Returns the usage to be reported in an authrep request.
|
|
51
|
+
def usage
|
|
52
|
+
@predicted_usage ? nil : @usage
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the predicted usage of an authorize request.
|
|
56
|
+
def predicted_usage
|
|
57
|
+
@predicted_usage ? @usage : nil
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the actual usage. If there isn't one, returns the predicted
|
|
61
|
+
# usage. If there isn't an actual or predicted usage, returns nil.
|
|
62
|
+
def actual_or_predicted_usage
|
|
63
|
+
usage || predicted_usage
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def authorized?
|
|
67
|
+
@authorized
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def plan_name
|
|
71
|
+
@application.plan_name unless @application.nil?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def application_usage_reports
|
|
75
|
+
@usage_report ||= load_usage_reports @application
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def value_for_usage_limit(usage_limit)
|
|
79
|
+
values = @values[usage_limit.period]
|
|
80
|
+
values && values[usage_limit.metric_id] || 0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def value_for_application_usage_limit(usage_limit)
|
|
84
|
+
values = @values[usage_limit.period]
|
|
85
|
+
values && values[usage_limit.metric_id] || 0
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# provides a hierarchy hash with metrics as symbolic names
|
|
89
|
+
def hierarchy
|
|
90
|
+
@hierarchy ||= Metric.hierarchy service_id
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def limit_headers(now = Time.now.utc)
|
|
94
|
+
# maybe filter by exceeded reports if not authorized
|
|
95
|
+
LimitHeaders.get(reports_to_calculate_limit_headers, now)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def to_xml(options = {})
|
|
99
|
+
xml = ''
|
|
100
|
+
xml << '<?xml version="1.0" encoding="UTF-8"?>'.freeze unless options[:skip_instruct]
|
|
101
|
+
xml << '<status>'.freeze
|
|
102
|
+
|
|
103
|
+
add_authorize_section(xml)
|
|
104
|
+
|
|
105
|
+
if oauth
|
|
106
|
+
add_application_section(xml)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
hierarchy_reports = [] if @hierarchy_ext
|
|
110
|
+
if !@application.nil?
|
|
111
|
+
add_plan_section(xml, 'plan'.freeze, plan_name)
|
|
112
|
+
add_reports_section(xml, application_usage_reports)
|
|
113
|
+
hierarchy_reports.concat application_usage_reports if hierarchy_reports
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
if hierarchy_reports
|
|
117
|
+
add_hierarchy_section(xml, hierarchy_reports)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
xml << '</status>'.freeze
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
private
|
|
124
|
+
|
|
125
|
+
# Returns the app usage reports needed to construct the limit headers.
|
|
126
|
+
# If the status does not have a 'usage', this method returns all the
|
|
127
|
+
# usage reports. Otherwise, it returns the reports associated with the
|
|
128
|
+
# metrics present in the 'usage' and their parents.
|
|
129
|
+
def reports_to_calculate_limit_headers
|
|
130
|
+
all_reports = application_usage_reports
|
|
131
|
+
|
|
132
|
+
return all_reports if (@usage.nil? || @usage.empty?)
|
|
133
|
+
|
|
134
|
+
metric_names_in_usage = @usage.keys
|
|
135
|
+
metrics = metric_names_in_usage | ascendants_names(metric_names_in_usage)
|
|
136
|
+
all_reports.select { |report| metrics.include?(report.metric_name) }
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def ascendants_names(metric_names)
|
|
140
|
+
metric_names.flat_map do |metric_name|
|
|
141
|
+
Metric.ascendants(@service_id, metric_name)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# make sure the keys are Periods
|
|
146
|
+
def filter_values(values)
|
|
147
|
+
return nil if values.nil?
|
|
148
|
+
values.inject({}) do |acc, (k, v)|
|
|
149
|
+
key = begin
|
|
150
|
+
Period[k]
|
|
151
|
+
rescue Period::Unknown
|
|
152
|
+
k
|
|
153
|
+
end
|
|
154
|
+
acc[key] = v
|
|
155
|
+
acc
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def add_hierarchy_section(xml, reports)
|
|
160
|
+
xml << '<hierarchy>'.freeze
|
|
161
|
+
with_report_and_hierarchy(reports) do |ur, children|
|
|
162
|
+
xml << '<metric name="'.freeze
|
|
163
|
+
xml << ur.metric_name << '" children="'.freeze
|
|
164
|
+
xml << (children ? children.join(' '.freeze) : '') << '"/>'.freeze
|
|
165
|
+
end
|
|
166
|
+
xml << '</hierarchy>'.freeze
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# helper to iterate over reports and get relevant hierarchy info
|
|
170
|
+
def with_report_and_hierarchy(reports)
|
|
171
|
+
reports.each do |ur|
|
|
172
|
+
yield ur, hierarchy[ur.metric_name]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def add_plan_section(xml, tag, plan_name)
|
|
177
|
+
xml << "<#{tag}>"
|
|
178
|
+
xml << plan_name.to_s.encode(xml: :text) << "</#{tag}>"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def add_authorize_section(xml)
|
|
182
|
+
if authorized?
|
|
183
|
+
xml << '<authorized>true</authorized>'.freeze
|
|
184
|
+
else
|
|
185
|
+
xml << '<authorized>false</authorized><reason>'.freeze
|
|
186
|
+
xml << rejection_reason_text
|
|
187
|
+
xml << '</reason>'.freeze
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def add_application_section(xml)
|
|
192
|
+
redirect_uri = application.redirect_url
|
|
193
|
+
xml << '<application>' \
|
|
194
|
+
"<id>#{application.id}</id>" \
|
|
195
|
+
"<key>#{application.keys.first}</key>" \
|
|
196
|
+
"<#{@redirect_uri_field}>#{redirect_uri}</#{@redirect_uri_field}>" \
|
|
197
|
+
'</application>'
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def load_usage_reports(application)
|
|
201
|
+
# We might have usage limits that apply to metrics that no longer
|
|
202
|
+
# exist. In that case, the usage limit refers to a metric ID that no
|
|
203
|
+
# longer has a name associated to it. When that happens, we do not
|
|
204
|
+
# want to take into account that usage limit.
|
|
205
|
+
# This might happen, for example, when a Backend client decides to delete
|
|
206
|
+
# a metric and all the associated usage limits, but the operation fails
|
|
207
|
+
# for some of the usage limits.
|
|
208
|
+
|
|
209
|
+
reports = []
|
|
210
|
+
if !application.nil?
|
|
211
|
+
application.usage_limits.each do |usage_limit|
|
|
212
|
+
if application.metric_name(usage_limit.metric_id)
|
|
213
|
+
reports << UsageReport.new(self, usage_limit)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
reports
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def add_reports_section(xml, reports)
|
|
221
|
+
unless reports.empty?
|
|
222
|
+
xml_node = 'usage_reports>'.freeze
|
|
223
|
+
xml << '<'.freeze
|
|
224
|
+
xml << xml_node
|
|
225
|
+
reports.each do |report|
|
|
226
|
+
xml << report.to_xml
|
|
227
|
+
end
|
|
228
|
+
xml << '</'.freeze
|
|
229
|
+
xml << xml_node
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
module ThreeScale
|
|
2
|
+
module Backend
|
|
3
|
+
module Transactor
|
|
4
|
+
class Status
|
|
5
|
+
class UsageReport
|
|
6
|
+
attr_reader :type, :period
|
|
7
|
+
|
|
8
|
+
def initialize(status, usage_limit)
|
|
9
|
+
@status = status
|
|
10
|
+
@usage_limit = usage_limit
|
|
11
|
+
@period = usage_limit.period.new(status.timestamp)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def metric_name
|
|
15
|
+
@metric_name ||= @status.application.metric_name(metric_id)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def metric_id
|
|
19
|
+
@usage_limit.metric_id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def max_value
|
|
23
|
+
@usage_limit.value
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def current_value
|
|
27
|
+
@current_value ||= @status.value_for_usage_limit(@usage_limit)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns -1 if the period is eternity. Otherwise, returns the time
|
|
31
|
+
# remaining until the end of the period in seconds.
|
|
32
|
+
def remaining_time(from = Time.now)
|
|
33
|
+
if period.granularity == Period::Granularity::Eternity
|
|
34
|
+
-1
|
|
35
|
+
else
|
|
36
|
+
(period.finish - from).ceil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns the number of identical calls that can be made before
|
|
41
|
+
# violating the limits defined in the usage report.
|
|
42
|
+
#
|
|
43
|
+
# Authrep (with actual usage): suppose that we have a metric with a
|
|
44
|
+
# daily limit of 10, a current usage of 0, and a given usage of 2.
|
|
45
|
+
# After taking into account the given usage, the number of identical
|
|
46
|
+
# calls that could be performed is (10-2)/2 = 4.
|
|
47
|
+
#
|
|
48
|
+
# Authorize (with predicted usage): suppose that we have a metric
|
|
49
|
+
# with a daily limit of 10, a current usage of 0, and a given usage
|
|
50
|
+
# of 2. This time, the given usage is not taken into account, as it
|
|
51
|
+
# is predicted, not to be reported. The number of identical calls
|
|
52
|
+
# that could be performed is 10/2 = 5.
|
|
53
|
+
#
|
|
54
|
+
# Returns -1 when there is not a limit in the number of calls.
|
|
55
|
+
def remaining_same_calls
|
|
56
|
+
return 0 if remaining <= 0
|
|
57
|
+
|
|
58
|
+
usage = compute_usage
|
|
59
|
+
usage > 0 ? remaining/usage : -1
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def usage
|
|
63
|
+
@status.usage
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def exceeded?
|
|
67
|
+
current_value > max_value
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def authorized?
|
|
71
|
+
@status.authorized?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def inspect
|
|
75
|
+
"#<#{self.class.name} " \
|
|
76
|
+
"type=#{type} " \
|
|
77
|
+
"period=#{period} " \
|
|
78
|
+
"metric_name=#{metric_name} " \
|
|
79
|
+
"max_value=#{max_value} " \
|
|
80
|
+
"current_value=#{current_value}>"
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def to_h
|
|
84
|
+
{ period: period,
|
|
85
|
+
metric_name: metric_name,
|
|
86
|
+
max_value: max_value,
|
|
87
|
+
current_value: current_value }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def to_xml
|
|
91
|
+
xml = String.new
|
|
92
|
+
# Node header
|
|
93
|
+
add_head(xml)
|
|
94
|
+
# Node content
|
|
95
|
+
add_period(xml) if period != Period[:eternity]
|
|
96
|
+
add_values(xml)
|
|
97
|
+
# Node closing
|
|
98
|
+
add_tail(xml)
|
|
99
|
+
xml
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
private
|
|
103
|
+
|
|
104
|
+
def hierarchy
|
|
105
|
+
@status.hierarchy
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def add_head(xml)
|
|
109
|
+
xml << '<usage_report metric="'.freeze
|
|
110
|
+
xml << metric_name.to_s << '" period="'.freeze
|
|
111
|
+
xml << period.to_s << '"'.freeze
|
|
112
|
+
xml << (exceeded? ? ' exceeded="true">'.freeze : '>'.freeze)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def add_period(xml)
|
|
116
|
+
xml << '<period_start>'.freeze
|
|
117
|
+
xml << period.start.strftime(TIME_FORMAT) << '</period_start>'.freeze
|
|
118
|
+
xml << '<period_end>'.freeze
|
|
119
|
+
xml << period.finish.strftime(TIME_FORMAT) << '</period_end>'.freeze
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def add_values(xml)
|
|
123
|
+
xml << '<max_value>'.freeze
|
|
124
|
+
xml << max_value.to_s << '</max_value><current_value>'.freeze
|
|
125
|
+
xml << compute_current_value.to_s
|
|
126
|
+
xml << '</current_value>'
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def add_tail(xml)
|
|
130
|
+
xml << '</usage_report>'.freeze
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def remaining
|
|
134
|
+
# The remaining could be negative for several reasons:
|
|
135
|
+
# 1) We allow reports that do not check limits.
|
|
136
|
+
# 2) The reports included in authreps are async.
|
|
137
|
+
# 3) A usage passed by param in an authrep can go over the limits.
|
|
138
|
+
# However, a negative remaining does not make much sense. It's
|
|
139
|
+
# better to return just 0.
|
|
140
|
+
[max_value - compute_current_value, 0].max
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def compute_usage
|
|
144
|
+
usage = @status.usage || @status.predicted_usage
|
|
145
|
+
|
|
146
|
+
return 0 unless usage
|
|
147
|
+
|
|
148
|
+
this_usage = usage[metric_name] || 0
|
|
149
|
+
res = Usage.get_from(this_usage)
|
|
150
|
+
|
|
151
|
+
add_descendants_usage(usage, res)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# helper to compute the current usage value after applying a possibly
|
|
155
|
+
# non-existent usage (or possibly unauthorized state)
|
|
156
|
+
def compute_current_value
|
|
157
|
+
# If not authorized or nothing to add, we just report the current
|
|
158
|
+
# value from the data store.
|
|
159
|
+
if authorized? && usage
|
|
160
|
+
this_usage = usage[metric_name] || 0
|
|
161
|
+
# this is an auth/authrep request and therefore we should sum the usage
|
|
162
|
+
computed_usage = Usage.get_from this_usage, current_value
|
|
163
|
+
# children can alter the resulting current value
|
|
164
|
+
add_descendants_usage(usage, computed_usage)
|
|
165
|
+
else
|
|
166
|
+
current_value
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def add_descendants_usage(usages, parent_usage)
|
|
171
|
+
descendants = Metric.descendants(@status.service_id, metric_name)
|
|
172
|
+
|
|
173
|
+
descendants.reduce(parent_usage) do |acc, descendant|
|
|
174
|
+
descendant_usage = usages[descendant]
|
|
175
|
+
Usage.get_from descendant_usage, acc
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|