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,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