apisonator 3.3.1.1 → 3.4.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09ccae8a2719bc3717d22dcdd78c5c996cbd4774f2528b91d77c74fbbe2e51b7'
4
- data.tar.gz: 4a9f237a6d1b25f9cc763817030cbe562536a6d5ed89374326db2f99ea82971c
3
+ metadata.gz: 356ca549dce065646329337096b0c3233ea3be1fdf599b135c144f5c887e5754
4
+ data.tar.gz: 04af4be3b14ce0299bbefc968349e7f063222d9a61d10557a4ffade0a5c683db
5
5
  SHA512:
6
- metadata.gz: 2818f8b3a97b701218c28ae93339650db57a0229181ee3573a24ffe215611747c1381e9f0e643eb9dc85b81d13d43336a08252a88cd63cd7fc78ed9a182c5c83
7
- data.tar.gz: 7a9c2e389c239094e2c6f303749056d5a8ea2d216f5097e98e9a87ef5c3bcd4ea2d190c4b8277d659608f7c80f57d5d727bf65eb9330f4c08fb8777ab5a1ae81
6
+ metadata.gz: 850c97fc6f5835da57e1918881aa2f4ce0e086fb550c2bda024b98c9e1e62b829aadb1f0b45dd7b690aa0bd5b5cfb712de1d4914f39eab8caa5107e28c48f600
7
+ data.tar.gz: 0d45268818cd98bf0c8684fe1340544e230c85fdef7d05b4f37d0c1db4d963d21fc2f50455bf1226619276ec6402c3be3af9ee755b783f8ee5d2ec5c300b8e66
data/CHANGELOG.md CHANGED
@@ -2,6 +2,74 @@
2
2
 
3
3
  Notable changes to Apisonator will be tracked in this document.
4
4
 
5
+ ## 3.4.2 - 2021-06-17
6
+
7
+ ### Fixed
8
+
9
+ - Fixed exception raised when TTL = 0 in `UsagesChecked.mark_all_checked`
10
+ ([#290](https://github.com/3scale/apisonator/pull/290)).
11
+
12
+ ## 3.4.1 - 2021-06-16
13
+
14
+ ### Changed
15
+
16
+ - Introduced performance optimizations around alerts detection
17
+ ([#287](https://github.com/3scale/apisonator/pull/287)).
18
+
19
+ ## 3.4.0 - 2021-06-14
20
+
21
+ ### Added
22
+
23
+ - New extension that list the keys of an application
24
+ ([#284](https://github.com/3scale/apisonator/pull/284)).
25
+
26
+ ### Changed
27
+
28
+ - It is now possible to use OIDC in the auth and authrep endpoints
29
+ ([#280](https://github.com/3scale/apisonator/pull/280)).
30
+ - Updated multi-json to 1.15.0
31
+ ([#278](https://github.com/3scale/apisonator/pull/278)).
32
+
33
+ ### Removed
34
+
35
+ - Deleted unused service attributes related with deleted end-users functionality
36
+ ([#277](https://github.com/3scale/apisonator/pull/277)).
37
+
38
+ ## 3.3.3 - 2021-03-09
39
+
40
+ ### Changed
41
+
42
+ - Check if alerts can be raised before calculating utilization (perf
43
+ optimization) ([#275](https://github.com/3scale/apisonator/pull/275)).
44
+
45
+ ### Removed
46
+
47
+ - Stop maintaining unused "current_max" key in Alerts
48
+ ([#272](https://github.com/3scale/apisonator/pull/272)).
49
+
50
+ ## 3.3.2 - 2021-02-23
51
+
52
+ ### Fixed
53
+
54
+ - Fixed nil exception in `Aggregator.process`
55
+ ([#269](https://github.com/3scale/apisonator/pull/269)).
56
+
57
+ ### Changed
58
+
59
+ - Updated to Ruby 2.7 in Docker images
60
+ ([#265](https://github.com/3scale/apisonator/pull/265)) and
61
+ ([#266](https://github.com/3scale/apisonator/pull/266)).
62
+ - Updated pry to 0.14.0 and pry-doc to 1.1.0
63
+ ([#267](https://github.com/3scale/apisonator/pull/267)).
64
+ - Updated Docker image to be based on RHEL UBI 8
65
+ ([#268](https://github.com/3scale/apisonator/pull/268)).
66
+
67
+ ### Removed
68
+
69
+ - Removed redundant prometheus config params
70
+ (`listener_prometheus_metrics.enabled` and `listener_prometheus_metrics.port`)
71
+ ([#270](https://github.com/3scale/apisonator/pull/270)).
72
+
5
73
  ## 3.3.1.1 - 2021-02-12
6
74
 
7
75
  ### Changed
data/Gemfile.base CHANGED
@@ -36,8 +36,8 @@ end
36
36
  group :development do
37
37
  gem 'sshkit'
38
38
  gem 'source2swagger', git: 'https://github.com/3scale/source2swagger', branch: 'backend'
39
- gem 'pry', '~> 0.11.3'
40
- gem 'pry-doc', '~> 0.11.1'
39
+ gem 'pry', '~> 0.14'
40
+ gem 'pry-doc', '~> 1.1'
41
41
  gem 'license_finder', '~> 5'
42
42
  end
43
43
 
data/Gemfile.lock CHANGED
@@ -36,7 +36,7 @@ GIT
36
36
  PATH
37
37
  remote: .
38
38
  specs:
39
- apisonator (3.3.1.1)
39
+ apisonator (3.4.2)
40
40
 
41
41
  GEM
42
42
  remote: https://rubygems.org/
@@ -96,7 +96,7 @@ GEM
96
96
  chronic (0.10.2)
97
97
  codeclimate-test-reporter (0.6.0)
98
98
  simplecov (>= 0.7.1, < 1.0.0)
99
- coderay (1.1.2)
99
+ coderay (1.1.3)
100
100
  concurrent-ruby (1.1.6)
101
101
  console (1.8.2)
102
102
  daemons (1.2.4)
@@ -131,13 +131,13 @@ GEM
131
131
  localhost (1.1.6)
132
132
  mapping (1.1.1)
133
133
  metaclass (0.0.4)
134
- method_source (0.9.0)
134
+ method_source (1.0.0)
135
135
  mini_portile2 (2.4.0)
136
136
  minitest (5.14.1)
137
137
  mocha (1.3.0)
138
138
  metaclass (~> 0.0.1)
139
139
  mono_logger (1.1.0)
140
- multi_json (1.13.1)
140
+ multi_json (1.15.0)
141
141
  mustache (1.0.5)
142
142
  mustermann (1.0.2)
143
143
  net-scp (1.2.1)
@@ -164,15 +164,15 @@ GEM
164
164
  protocol-hpack (~> 1.4)
165
165
  protocol-http (~> 0.15)
166
166
  protocol-redis (0.5.0)
167
- pry (0.11.3)
168
- coderay (~> 1.1.0)
169
- method_source (~> 0.9.0)
167
+ pry (0.14.0)
168
+ coderay (~> 1.1)
169
+ method_source (~> 1.0)
170
170
  pry-byebug (3.5.1)
171
171
  byebug (~> 9.1)
172
172
  pry (~> 0.10)
173
- pry-doc (0.11.1)
174
- pry (~> 0.9)
175
- yard (~> 0.9)
173
+ pry-doc (1.1.0)
174
+ pry (~> 0.11)
175
+ yard (~> 0.9.11)
176
176
  rack (2.1.4)
177
177
  rack-protection (2.0.3)
178
178
  rack
@@ -260,7 +260,7 @@ GEM
260
260
  prometheus-client (~> 1.0)
261
261
  yabeda (~> 0.5)
262
262
  yajl-ruby (1.3.1)
263
- yard (0.9.20)
263
+ yard (0.9.26)
264
264
 
265
265
  PLATFORMS
266
266
  ruby
@@ -284,9 +284,9 @@ DEPENDENCIES
284
284
  nokogiri (~> 1.10.8)
285
285
  pg (= 0.20.0)
286
286
  pkg-config (~> 1.1.7)
287
- pry (~> 0.11.3)
287
+ pry (~> 0.14)
288
288
  pry-byebug (~> 3.5.1)
289
- pry-doc (~> 0.11.1)
289
+ pry-doc (~> 1.1)
290
290
  puma!
291
291
  rack (~> 2.1.4)
292
292
  rack-test (= 0.8.2)
data/Gemfile.on_prem.lock CHANGED
@@ -36,7 +36,7 @@ GIT
36
36
  PATH
37
37
  remote: .
38
38
  specs:
39
- apisonator (3.3.1.1)
39
+ apisonator (3.4.2)
40
40
 
41
41
  GEM
42
42
  remote: https://rubygems.org/
@@ -86,7 +86,7 @@ GEM
86
86
  byebug (9.1.0)
87
87
  codeclimate-test-reporter (0.6.0)
88
88
  simplecov (>= 0.7.1, < 1.0.0)
89
- coderay (1.1.2)
89
+ coderay (1.1.3)
90
90
  concurrent-ruby (1.1.6)
91
91
  console (1.8.2)
92
92
  daemons (1.2.4)
@@ -120,13 +120,13 @@ GEM
120
120
  localhost (1.1.6)
121
121
  mapping (1.1.1)
122
122
  metaclass (0.0.4)
123
- method_source (0.9.0)
123
+ method_source (1.0.0)
124
124
  mini_portile2 (2.4.0)
125
125
  minitest (5.14.1)
126
126
  mocha (1.3.0)
127
127
  metaclass (~> 0.0.1)
128
128
  mono_logger (1.1.0)
129
- multi_json (1.13.1)
129
+ multi_json (1.15.0)
130
130
  mustache (1.0.5)
131
131
  mustermann (1.0.2)
132
132
  net-scp (1.2.1)
@@ -152,15 +152,15 @@ GEM
152
152
  protocol-hpack (~> 1.4)
153
153
  protocol-http (~> 0.15)
154
154
  protocol-redis (0.5.0)
155
- pry (0.11.3)
156
- coderay (~> 1.1.0)
157
- method_source (~> 0.9.0)
155
+ pry (0.14.0)
156
+ coderay (~> 1.1)
157
+ method_source (~> 1.0)
158
158
  pry-byebug (3.5.1)
159
159
  byebug (~> 9.1)
160
160
  pry (~> 0.10)
161
- pry-doc (0.11.1)
162
- pry (~> 0.9)
163
- yard (~> 0.9)
161
+ pry-doc (1.1.0)
162
+ pry (~> 0.11)
163
+ yard (~> 0.9.11)
164
164
  rack (2.1.4)
165
165
  rack-protection (2.0.3)
166
166
  rack
@@ -244,7 +244,7 @@ GEM
244
244
  prometheus-client (~> 1.0)
245
245
  yabeda (~> 0.5)
246
246
  yajl-ruby (1.3.1)
247
- yard (0.9.20)
247
+ yard (0.9.26)
248
248
 
249
249
  PLATFORMS
250
250
  ruby
@@ -265,9 +265,9 @@ DEPENDENCIES
265
265
  mocha (~> 1.3)
266
266
  nokogiri (~> 1.10.8)
267
267
  pkg-config (~> 1.1.7)
268
- pry (~> 0.11.3)
268
+ pry (~> 0.14)
269
269
  pry-byebug (~> 3.5.1)
270
- pry-doc (~> 0.11.1)
270
+ pry-doc (~> 1.1)
271
271
  puma!
272
272
  rack (~> 2.1.4)
273
273
  rack-test (= 0.8.2)
@@ -44,6 +44,7 @@ require '3scale/backend/queue_storage'
44
44
  require '3scale/backend/errors'
45
45
  require '3scale/backend/stats'
46
46
  require '3scale/backend/usage_limit'
47
+ require '3scale/backend/utilization'
47
48
  require '3scale/backend/alerts'
48
49
  require '3scale/backend/event_storage'
49
50
  require '3scale/backend/worker'
@@ -1,21 +1,19 @@
1
1
  module ThreeScale
2
2
  module Backend
3
3
  class AlertLimit
4
- module KeyHelpers
5
- def key(service_id)
6
- "alerts/service_id:#{service_id}/allowed_set"
7
- end
8
- end
9
-
10
- include KeyHelpers
11
- extend KeyHelpers
4
+ include Alerts::KeyHelpers
5
+ extend Alerts::KeyHelpers
12
6
 
13
7
  include Storable
14
8
 
15
9
  attr_accessor :service_id, :value
16
10
 
17
11
  def save
18
- storage.sadd(key(service_id), value.to_i) if valid?
12
+ if valid?
13
+ res = storage.sadd(key_allowed_set(service_id), value.to_i)
14
+ Alerts::UsagesChecked.invalidate_for_service(service_id)
15
+ res
16
+ end
19
17
  end
20
18
 
21
19
  def to_hash
@@ -26,7 +24,7 @@ module ThreeScale
26
24
  end
27
25
 
28
26
  def self.load_all(service_id)
29
- values = storage.smembers(key(service_id))
27
+ values = storage.smembers(key_allowed_set(service_id))
30
28
  values.map do |value|
31
29
  new(service_id: service_id, value: value.to_i)
32
30
  end
@@ -38,7 +36,7 @@ module ThreeScale
38
36
  end
39
37
 
40
38
  def self.delete(service_id, value)
41
- storage.srem(key(service_id), value.to_i) if valid_value?(value)
39
+ storage.srem(key_allowed_set(service_id), value.to_i) if valid_value?(value)
42
40
  end
43
41
 
44
42
  def self.valid_value?(value)
@@ -6,11 +6,10 @@ module ThreeScale
6
6
 
7
7
  # The compacted hour in the params refers to the
8
8
  # TimeHacks.to_compact_s method.
9
- def alert_keys(service_id, app_id, discrete_utilization, compacted_hour_start)
9
+ def alert_keys(service_id, app_id, discrete_utilization)
10
10
  {
11
11
  already_notified: key_already_notified(service_id, app_id, discrete_utilization),
12
12
  allowed: key_allowed_set(service_id),
13
- current_max: key_current_max(service_id, app_id, compacted_hour_start),
14
13
  current_id: key_current_id
15
14
  }
16
15
  end
@@ -31,18 +30,19 @@ module ThreeScale
31
30
  "#{prefix}allowed_set"
32
31
  end
33
32
 
34
- def key_current_max(service_id, app_id, compacted_hour_start)
35
- prefix = key_prefix(service_id, app_id)
36
- "#{prefix}#{compacted_hour_start}/current_max"
37
- end
38
-
39
33
  def key_current_id
40
34
  'alerts/current_id'.freeze
41
35
  end
36
+
37
+ def key_usage_already_checked(service_id, app_id)
38
+ prefix = key_prefix(service_id, app_id)
39
+ "#{prefix}usage_already_checked"
40
+ end
42
41
  end
43
42
 
44
43
  extend self
45
44
  extend KeyHelpers
45
+ include Memoizer::Decorator
46
46
 
47
47
  ALERT_TTL = 24*3600 # 1 day (only one message per day)
48
48
  ## zero must be here and sorted, yes or yes
@@ -50,67 +50,109 @@ module ThreeScale
50
50
  FIRST_ALERT_BIN = ALERT_BINS.first
51
51
  RALERT_BINS = ALERT_BINS.reverse.freeze
52
52
 
53
- def utilization(app_usage_reports)
54
- max_utilization = -1.0
55
- max_record = nil
56
- max = proc do |item|
57
- if item.max_value > 0
58
- utilization = item.current_value / item.max_value.to_f
59
-
60
- if utilization > max_utilization
61
- max_record = item
62
- max_utilization = utilization
63
- end
53
+ # This class is useful to reduce the amount of information that we need to
54
+ # fetch from Redis to determine whether an alert should be raised.
55
+ # In summary, alerts are raised at the application level and we need to
56
+ # wait for 24h before raising a new one for the same level (ALERTS_BIN
57
+ # above).
58
+ #
59
+ # This class allows us to check all the usage limits once and then not
60
+ # check all of them again (just the ones in the report job) until:
61
+ # 1) A specific alert has expired (24h passed since it was triggered).
62
+ # 2) A new alert bin is enabled for the service.
63
+ class UsagesChecked
64
+ extend KeyHelpers
65
+ extend StorageHelpers
66
+ include Memoizer::Decorator
67
+
68
+ def self.need_to_check_all?(service_id, app_id)
69
+ !storage.exists(key_usage_already_checked(service_id, app_id))
70
+ end
71
+ memoize :need_to_check_all?
72
+
73
+ def self.mark_all_checked(service_id, app_id)
74
+ ttl = ALERT_BINS.map do |bin|
75
+ ttl = storage.ttl(key_already_notified(service_id, app_id, bin))
76
+
77
+ # Redis returns -2 if key does not exist, and -1 if it exists without
78
+ # a TTL (we know this should not happen for the alert bins).
79
+ # In those cases we should just set the TTL to the max (ALERT_TTL).
80
+ ttl >= 0 ? ttl : ALERT_TTL
81
+ end.min
82
+
83
+ # Setex fails when ttl = 0. Also, if it's 0, we don't need to mark it
84
+ # as checked, because the "already_notified" key for the bin is just
85
+ # about to expire, so we'll need to check all the usages.
86
+ if ttl > 0
87
+ storage.setex(key_usage_already_checked(service_id, app_id), ttl, '1'.freeze)
88
+ Memoizer.clear(Memoizer.build_key(self, :need_to_check_all?, service_id, app_id))
64
89
  end
65
90
  end
66
91
 
67
- app_usage_reports.each(&max)
92
+ def self.invalidate(service_id, app_id)
93
+ storage.del(key_usage_already_checked(service_id, app_id))
94
+ end
95
+
96
+ def self.invalidate_for_service(service_id)
97
+ app_ids = []
98
+ cursor = 0
99
+
100
+ loop do
101
+ cursor, ids = storage.sscan(
102
+ Application.applications_set_key(service_id), cursor, count: SCAN_SLICE
103
+ )
104
+
105
+ app_ids += ids
68
106
 
69
- if max_utilization == -1
70
- ## case that all the limits have max_value==0
71
- max_utilization = 0
72
- max_record = app_usage_reports.first
107
+ break if cursor.to_i == 0
108
+ end
109
+
110
+ invalidate_batch(service_id, app_ids)
73
111
  end
74
112
 
75
- [max_utilization, max_record]
113
+ def self.invalidate_batch(service_id, app_ids)
114
+ app_ids.each_slice(PIPELINED_SLICE_SIZE) do |ids|
115
+ keys = ids.map { |app_id| key_usage_already_checked(service_id, app_id) }
116
+ storage.del(keys)
117
+ end
118
+ end
119
+ private_class_method :invalidate_batch
76
120
  end
77
121
 
78
- def update_utilization(service_id, app_id, max_utilization, max_record, timestamp)
79
- discrete = utilization_discrete(max_utilization)
80
- max_utilization_i = (max_utilization * 100.0).round
122
+ def can_raise_more_alerts?(service_id, app_id)
123
+ allowed_bins = allowed_set_for_service(service_id).sort
124
+
125
+ return false if allowed_bins.empty?
81
126
 
82
- beginning_of_day = Period::Boundary.day_start(timestamp)
83
- period_hour = Period::Boundary.hour_start(timestamp).to_compact_s
84
- # UNIX timestamp for key expiration - add 1 day + 5 mins
85
- expire_at = (beginning_of_day + 86700).to_i
127
+ # If the bin with the highest value has already been notified, there's
128
+ # no need to notify anything else.
129
+ not notified?(service_id, app_id, allowed_bins.last)
130
+ end
86
131
 
87
- keys = alert_keys(service_id, app_id, discrete, period_hour)
132
+ def update_utilization(service_id, app_id, utilization)
133
+ discrete = utilization_discrete(utilization.ratio)
88
134
 
89
- already_alerted, allowed, current_max, _ = storage.pipelined do
135
+ keys = alert_keys(service_id, app_id, discrete)
136
+
137
+ already_alerted, allowed = storage.pipelined do
90
138
  storage.get(keys[:already_notified])
91
139
  storage.sismember(keys[:allowed], discrete)
92
- storage.get(keys[:current_max])
93
- storage.expireat(keys[:current_max], expire_at)
94
- end
95
-
96
- ## update the status of utilization
97
- if max_utilization_i > current_max.to_i
98
- storage.set(keys[:current_max], max_utilization_i)
99
140
  end
100
141
 
101
142
  if already_alerted.nil? && allowed && discrete.to_i > 0
102
- next_id, _ = storage.pipelined do
143
+ next_id, _, _ = storage.pipelined do
103
144
  storage.incr(keys[:current_id])
104
145
  storage.setex(keys[:already_notified], ALERT_TTL, "1")
146
+ UsagesChecked.invalidate(service_id, app_id)
105
147
  end
106
148
 
107
149
  alert = { :id => next_id,
108
150
  :utilization => discrete,
109
- :max_utilization => max_utilization,
151
+ :max_utilization => utilization.ratio,
110
152
  :application_id => app_id,
111
153
  :service_id => service_id,
112
- :timestamp => timestamp,
113
- :limit => formatted_limit(max_record) }
154
+ :timestamp => Time.now.utc,
155
+ :limit => utilization.to_s }
114
156
 
115
157
  Backend::EventStorage::store(:alert, alert)
116
158
  end
@@ -124,10 +166,15 @@ module ThreeScale
124
166
  end || FIRST_ALERT_BIN
125
167
  end
126
168
 
127
- def formatted_limit(record)
128
- "#{record.metric_name} per #{record.period}: "\
129
- "#{record.current_value}/#{record.max_value}"
169
+ def allowed_set_for_service(service_id)
170
+ storage.smembers(key_allowed_set(service_id)).map(&:to_i) # Redis returns strings always
171
+ end
172
+ memoize :allowed_set_for_service
173
+
174
+ def notified?(service_id, app_id, bin)
175
+ storage.get(key_already_notified(service_id, app_id, bin))
130
176
  end
177
+ memoize :notified?
131
178
 
132
179
  def storage
133
180
  Storage.instance
@@ -58,7 +58,6 @@ module ThreeScale
58
58
  config.add_section(:internal_api, :user, :password)
59
59
  config.add_section(:master, :metrics)
60
60
  config.add_section(:worker_prometheus_metrics, :enabled, :port)
61
- config.add_section(:listener_prometheus_metrics, :enabled, :port)
62
61
 
63
62
  config.add_section(
64
63
  :async_worker,
@@ -4,6 +4,8 @@ module ThreeScale
4
4
  module All
5
5
  TIME_FORMAT = '%Y-%m-%d %H:%M:%S %z'.freeze
6
6
  PIPELINED_SLICE_SIZE = 400
7
+ SLEEP_BETWEEN_SCANS = 0.01 # In seconds
8
+ SCAN_SLICE = 500
7
9
  end
8
10
 
9
11
  def self.included(base)
@@ -17,7 +17,10 @@ module ThreeScale
17
17
 
18
18
  Backend::Logging::External.setup_rack self
19
19
 
20
- if Backend.configuration.listener_prometheus_metrics.enabled
20
+ # Notice that this cannot be specified via config, it needs to be an
21
+ # ENV because the metric server is started in Puma/Falcon
22
+ # "before_fork" and the configuration is not loaded at that point.
23
+ if ENV['CONFIG_LISTENER_PROMETHEUS_METRICS_ENABLED'].to_s.downcase.freeze == 'true'.freeze
21
24
  use Rack::Prometheus
22
25
  end
23
26
 
@@ -4,16 +4,12 @@ module ThreeScale
4
4
  include Storable
5
5
 
6
6
  # list of attributes to be fetched from storage
7
- ATTRIBUTES = %i[state referrer_filters_required backend_version
8
- user_registration_required default_user_plan_id
9
- default_user_plan_name provider_key].freeze
7
+ ATTRIBUTES = %i[state referrer_filters_required backend_version provider_key].freeze
10
8
  private_constant :ATTRIBUTES
11
9
 
12
10
  attr_reader :state
13
- attr_accessor :provider_key, :id, :backend_version,
14
- :default_user_plan_id, :default_user_plan_name
15
- attr_writer :referrer_filters_required, :user_registration_required,
16
- :default_service
11
+ attr_accessor :provider_key, :id, :backend_version
12
+ attr_writer :referrer_filters_required, :default_service
17
13
 
18
14
  class << self
19
15
  include Memoizer::Decorator
@@ -104,8 +100,6 @@ module ThreeScale
104
100
  memoize :list
105
101
 
106
102
  def save!(attributes = {})
107
- massage_set_user_registration_required attributes
108
-
109
103
  new(attributes).save!
110
104
  end
111
105
 
@@ -139,26 +133,10 @@ module ThreeScale
139
133
  def massage_service_attrs(service_attrs)
140
134
  service_attrs[:referrer_filters_required] =
141
135
  service_attrs[:referrer_filters_required].to_i > 0
142
- service_attrs[:user_registration_required] =
143
- massage_get_user_registration_required(
144
- service_attrs[:user_registration_required])
145
136
 
146
137
  service_attrs
147
138
  end
148
139
 
149
- # nil => true, 1 => true, '1' => true, 0 => false, '0' => false
150
- def massage_get_user_registration_required(value)
151
- value.nil? ? true : value.to_i > 0
152
- end
153
-
154
- def massage_set_user_registration_required(attributes)
155
- if attributes[:user_registration_required].nil?
156
- val = storage.get(storage_key(attributes[:id], :user_registration_required))
157
- attributes[:user_registration_required] =
158
- (!val.nil? && val.to_i == 0) ? false : true
159
- end
160
- end
161
-
162
140
  def get_attr(id, attribute)
163
141
  storage.get(storage_key(id, attribute))
164
142
  end
@@ -195,10 +173,6 @@ module ThreeScale
195
173
  @referrer_filters_required
196
174
  end
197
175
 
198
- def user_registration_required?
199
- @user_registration_required
200
- end
201
-
202
176
  def save!
203
177
  set_as_default_if_needed
204
178
  persist
@@ -227,9 +201,6 @@ module ThreeScale
227
201
  provider_key: provider_key,
228
202
  backend_version: backend_version,
229
203
  referrer_filters_required: referrer_filters_required?,
230
- user_registration_required: user_registration_required?,
231
- default_user_plan_id: default_user_plan_id,
232
- default_user_plan_name: default_user_plan_name,
233
204
  default_service: default_service?
234
205
  }
235
206
  end
@@ -294,9 +265,6 @@ module ThreeScale
294
265
 
295
266
  def persist_attributes
296
267
  persist_attribute :referrer_filters_required, referrer_filters_required? ? 1 : 0
297
- persist_attribute :user_registration_required, user_registration_required? ? 1 : 0
298
- persist_attribute :default_user_plan_id, default_user_plan_id, true
299
- persist_attribute :default_user_plan_name, default_user_plan_name, true
300
268
  persist_attribute :backend_version, backend_version, true
301
269
  persist_attribute :provider_key, provider_key
302
270
  persist_attribute :state, state.to_s if state
@@ -59,7 +59,7 @@ module ThreeScale
59
59
  touched_apps = aggregate(transactions, current_bucket)
60
60
 
61
61
  ApplicationEvents.generate(touched_apps.values)
62
- update_alerts(touched_apps)
62
+ update_alerts(transactions)
63
63
  begin
64
64
  ApplicationEvents.ping
65
65
  rescue ApplicationEvents::PingFailed => e
@@ -137,29 +137,35 @@ module ThreeScale
137
137
  logger.info(MAX_BUCKETS_CREATED_MSG)
138
138
  end
139
139
 
140
- def update_alerts(applications)
141
- current_timestamp = Time.now.getutc
142
-
143
- applications.each do |_appid, values|
144
- service_id = values[:service_id]
145
- application = Backend::Application.load(service_id,
146
- values[:application_id])
147
-
148
- application.load_metric_names
149
- usage = Usage.application_usage(application, current_timestamp)
150
- status = Transactor::Status.new(service_id: service_id,
151
- application: application,
152
- values: usage)
153
-
154
- max_utilization, max_record = Alerts.utilization(
155
- status.application_usage_reports)
156
-
157
- if max_utilization >= 0.0
158
- Alerts.update_utilization(service_id,
159
- values[:application_id],
160
- max_utilization,
161
- max_record,
162
- current_timestamp)
140
+ def update_alerts(transactions)
141
+ transactions.group_by { |tx| tx.application_id }.each do |app_id, txs|
142
+ service_id = txs.first.service_id # All the txs of an app belong to the same service
143
+
144
+ # Finding the max utilization can be costly because it involves
145
+ # loading usage limits and current usages. That's why before that,
146
+ # we check if there are any alerts that can be raised.
147
+ next unless Alerts.can_raise_more_alerts?(service_id, app_id)
148
+
149
+ begin
150
+ max_utilization = if Alerts::UsagesChecked.need_to_check_all?(service_id, app_id)
151
+ Utilization.max_in_all_metrics(service_id, app_id).tap do
152
+ Alerts::UsagesChecked.mark_all_checked(service_id, app_id)
153
+ end
154
+ else
155
+ # metrics_ids here includes the metrics
156
+ # explicitly reported plus their parents in
157
+ # the hierarchy.
158
+ metric_ids = txs.map { |tx| tx.usage.keys }.flatten.uniq
159
+ Utilization.max_in_metrics(service_id, app_id, metric_ids)
160
+ end
161
+ rescue ApplicationNotFound
162
+ # The app could have been deleted at some point since the job
163
+ # was enqueued. No need to update alerts in that case.
164
+ next
165
+ end
166
+
167
+ if max_utilization && max_utilization.ratio > 0
168
+ Alerts.update_utilization(service_id, app_id, max_utilization)
163
169
  end
164
170
  end
165
171
  end
@@ -36,12 +36,6 @@ module ThreeScale
36
36
  KEY_SERVICES_TO_DELETE = 'set_with_services_marked_for_deletion'.freeze
37
37
  private_constant :KEY_SERVICES_TO_DELETE
38
38
 
39
- SLEEP_BETWEEN_SCANS = 0.01 # In seconds
40
- private_constant :SLEEP_BETWEEN_SCANS
41
-
42
- SCAN_SLICE = 500
43
- private_constant :SCAN_SLICE
44
-
45
39
  STATS_KEY_PREFIX = 'stats/'.freeze
46
40
  private_constant :STATS_KEY_PREFIX
47
41
 
@@ -61,6 +61,8 @@ module ThreeScale
61
61
 
62
62
  def validate(oauth, provider_key, report_usage, params, request_info)
63
63
  service = Service.load_with_provider_key!(params[:service_id], provider_key)
64
+ oidc_service = !oauth && service.backend_version == 'oauth'.freeze
65
+
64
66
  # service_id cannot be taken from params since it might be missing there
65
67
  service_id = service.id
66
68
 
@@ -70,12 +72,18 @@ module ThreeScale
70
72
  # significant.
71
73
  params[:app_id] = nil if app_id && app_id.empty?
72
74
 
73
- if oauth
74
- raise ApplicationNotFound.new nil if app_id.nil?
75
- validators = Validators::OAUTH_VALIDATORS
76
- else
77
- validators = Validators::VALIDATORS
78
- end
75
+ # While OIDC without an app_id makes little sense, we would break existing
76
+ # behaviour when calling non oauth_auth*.xml endpoints if we returned an
77
+ # error here, so only do this for oauth_auth*.xml endpoints.
78
+ raise ApplicationNotFound.new nil if oauth && app_id.nil?
79
+
80
+ validators = if oidc_service
81
+ Validators::OIDC_VALIDATORS
82
+ elsif oauth
83
+ Validators::OAUTH_VALIDATORS
84
+ else
85
+ Validators::VALIDATORS
86
+ end
79
87
 
80
88
  params[:user_key] = nil if params[:user_key] && params[:user_key].empty?
81
89
  application = Application.load_by_id_or_user_key!(service_id,
@@ -98,8 +106,9 @@ module ThreeScale
98
106
  # hierarchy parameter adds information in the response needed
99
107
  # to derive which limits affect directly or indirectly the
100
108
  # metrics for which authorization is requested.
101
- hierarchy: extensions[:hierarchy] == '1',
102
- flat_usage: extensions[:flat_usage] == '1'
109
+ hierarchy: extensions[:hierarchy] == '1'.freeze,
110
+ flat_usage: extensions[:flat_usage] == '1'.freeze,
111
+ list_app_keys: extensions[:list_app_keys] == '1'.freeze
103
112
  }
104
113
 
105
114
  application.load_metric_names
@@ -108,24 +117,6 @@ module ThreeScale
108
117
  apply_validators(validators, status_attrs, params)
109
118
  end
110
119
 
111
- def get_token_ids(token, service_id, app_id)
112
- begin
113
- token_aid = OAuth::Token::Storage.get_credentials(token, service_id)
114
- rescue AccessTokenInvalid => e
115
- # Yep, well, er. Someone specified that it is OK to have an
116
- # invalid token if an app_id is specified. Somehow passing in
117
- # a user_key is still not enough, though...
118
- raise e if app_id.nil?
119
- end
120
-
121
- # We only take the token ids into account if we had no parameter ids
122
- if app_id.nil?
123
- app_id = token_aid
124
- end
125
-
126
- app_id
127
- end
128
-
129
120
  def do_authorize(method, provider_key, params, context_info)
130
121
  notify_authorize(provider_key)
131
122
  validate(method == :oauth_authorize, provider_key, false, params, context_info[:request])
@@ -8,17 +8,23 @@ module ThreeScale
8
8
  # We only use 'redirect_uri' if a request sent such a param. See #397.
9
9
  REDIRECT_URI_FIELD = 'redirect_url'.freeze
10
10
  private_constant :REDIRECT_URI_FIELD
11
+ # Maximum number of keys to list when using the list_app_keys extension
12
+ # At the time of writing System/Porta has a limit of 5 different app_keys
13
+ # at any given moment, but this could change anytime.
14
+ LIST_APP_KEYS_MAX = 256
15
+ private_constant :LIST_APP_KEYS_MAX
11
16
 
12
17
  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]
18
+ @service_id = attributes[:service_id]
19
+ @application = attributes[:application]
20
+ @oauth = attributes[:oauth]
21
+ @usage = attributes[:usage]
22
+ @predicted_usage = attributes[:predicted_usage]
23
+ @values = filter_values(attributes[:values] || {})
24
+ @timestamp = attributes[:timestamp] || Time.now.getutc
25
+ @hierarchy_ext = attributes[:hierarchy]
26
+ @flat_usage_ext = attributes[:flat_usage]
27
+ @list_app_keys_ext = attributes[:list_app_keys]
22
28
 
23
29
  raise 'service_id not specified' if @service_id.nil?
24
30
  raise ':application is required' if @application.nil?
@@ -106,6 +112,7 @@ module ThreeScale
106
112
  add_plan_section(xml, 'plan'.freeze, plan_name)
107
113
  add_reports_section(xml, application_usage_reports)
108
114
  hierarchy_reports.concat application_usage_reports if hierarchy_reports
115
+ add_app_keys_section xml if @list_app_keys_ext
109
116
  end
110
117
 
111
118
  if hierarchy_reports
@@ -161,6 +168,17 @@ module ThreeScale
161
168
  xml << '</hierarchy>'.freeze
162
169
  end
163
170
 
171
+ def add_app_keys_section(xml)
172
+ xml << '<app_keys app="'.freeze
173
+ xml << @application.id << '" svc="'.freeze
174
+ xml << @service_id << '">'.freeze
175
+ @application.keys.take(LIST_APP_KEYS_MAX).each do |key|
176
+ xml << '<key id="'.freeze
177
+ xml << key << '"/>'.freeze
178
+ end
179
+ xml << '</app_keys>'.freeze
180
+ end
181
+
164
182
  # helper to iterate over reports and get relevant hierarchy info
165
183
  def with_report_and_hierarchy(reports)
166
184
  reports.each do |ur|
@@ -3,7 +3,7 @@ module ThreeScale
3
3
  module Transactor
4
4
  class Status
5
5
  class UsageReport
6
- attr_reader :type, :period
6
+ attr_reader :type, :usage_limit, :period
7
7
 
8
8
  def initialize(status, usage_limit)
9
9
  @status = status
@@ -3,12 +3,19 @@ module ThreeScale
3
3
  class Usage
4
4
  class << self
5
5
  def application_usage(application, timestamp)
6
- usage(application, timestamp) do |metric_id, instance_period|
6
+ usage(application.usage_limits, timestamp) do |metric_id, instance_period|
7
7
  Stats::Keys.application_usage_value_key(
8
8
  application.service_id, application.id, metric_id, instance_period)
9
9
  end
10
10
  end
11
11
 
12
+ def application_usage_for_limits(application, timestamp, usage_limits)
13
+ usage(usage_limits, timestamp) do |metric_id, instance_period|
14
+ Stats::Keys.application_usage_value_key(
15
+ application.service_id, application.id, metric_id, instance_period)
16
+ end
17
+ end
18
+
12
19
  def is_set?(usage_str)
13
20
  usage_str && usage_str[0] == '#'.freeze
14
21
  end
@@ -25,7 +32,7 @@ module ThreeScale
25
32
 
26
33
  private
27
34
 
28
- def usage(obj, timestamp)
35
+ def usage(usage_limits, timestamp)
29
36
  # The timestamp does not change, so we can generate all the
30
37
  # instantiated periods just once.
31
38
  # This is important. Without this, the code can generate many instance
@@ -33,7 +40,7 @@ module ThreeScale
33
40
  # time.
34
41
  instance_periods = Period::instance_periods_for_ts(timestamp)
35
42
 
36
- pairs = metric_period_pairs obj.usage_limits
43
+ pairs = metric_period_pairs usage_limits
37
44
  return {} if pairs.empty?
38
45
 
39
46
  keys = pairs.map do |(metric_id, period)|
@@ -0,0 +1,83 @@
1
+ module ThreeScale
2
+ module Backend
3
+ class Utilization
4
+ include Comparable
5
+
6
+ attr_reader :metric_id, :period, :max_value, :current_value
7
+
8
+ def initialize(limit, current_value)
9
+ @metric_id = limit.metric_id
10
+ @period = limit.period
11
+ @max_value = limit.value
12
+ @current_value = current_value
13
+ @encoded = encoded(limit, current_value)
14
+ end
15
+
16
+ def ratio
17
+ return 0 if max_value == 0 # Disabled metric
18
+ current_value/max_value.to_f
19
+ end
20
+
21
+ # Returns in the format needed by the Alerts class.
22
+ def to_s
23
+ @encoded
24
+ end
25
+
26
+ def <=>(other)
27
+ # Consider "disabled" the lowest ones
28
+ if ratio == 0 && other.ratio == 0
29
+ return max_value <=> other.max_value
30
+ end
31
+
32
+ ratio <=> other.ratio
33
+ end
34
+
35
+ # Note: this can return nil
36
+ def self.max_in_all_metrics(service_id, app_id)
37
+ application = Backend::Application.load!(service_id, app_id)
38
+
39
+ usage = Usage.application_usage(application, Time.now.getutc)
40
+
41
+ status = Transactor::Status.new(service_id: service_id,
42
+ application: application,
43
+ values: usage)
44
+
45
+ # Preloads all the metric names to avoid fetching them one by one when
46
+ # generating the usage reports
47
+ application.load_metric_names
48
+
49
+ max = status.application_usage_reports.map do |usage_report|
50
+ Utilization.new(usage_report.usage_limit, usage_report.current_value)
51
+ end.max
52
+
53
+ # Avoid returning a utilization for disabled metrics
54
+ max && max.max_value > 0 ? max : nil
55
+ end
56
+
57
+ # Note: this can return nil
58
+ def self.max_in_metrics(service_id, app_id, metric_ids)
59
+ application = Backend::Application.load!(service_id, app_id)
60
+
61
+ limits = UsageLimit.load_for_affecting_metrics(
62
+ service_id, application.plan_id, metric_ids
63
+ )
64
+
65
+ usage = Usage.application_usage_for_limits(application, Time.now.getutc, limits)
66
+
67
+ max = limits.map do |limit|
68
+ Utilization.new(limit, usage[limit.period][limit.metric_id])
69
+ end.max
70
+
71
+ # Avoid returning a utilization for disabled metrics
72
+ max && max.max_value > 0 ? max : nil
73
+ end
74
+
75
+ private
76
+
77
+ def encoded(limit, current_value)
78
+ metric_name = Metric.load_name(limit.service_id, limit.metric_id)
79
+ "#{metric_name} per #{limit.period}: #{current_value}/#{limit.value}"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -21,6 +21,13 @@ module ThreeScale
21
21
  OAUTH_VALIDATORS = ([Validators::OauthSetting,
22
22
  Validators::OauthKey,
23
23
  Validators::RedirectURI] + COMMON_VALIDATORS).freeze
24
+
25
+ # OIDC specific validators will only check app keys when app_key is given.
26
+ #
27
+ # No need to add OauthSetting, since we need to check that to tell
28
+ # OIDC apart from the rest when calling authrep.xml (note lack of
29
+ # the oauth_ prefix).
30
+ OIDC_VALIDATORS = ([Validators::OauthKey] + COMMON_VALIDATORS).freeze
24
31
  end
25
32
  end
26
33
  end
@@ -3,7 +3,7 @@ module ThreeScale
3
3
  module Validators
4
4
  class OauthSetting < Base
5
5
  def apply
6
- if service.backend_version == 'oauth'
6
+ if service.backend_version == 'oauth'.freeze
7
7
  succeed!
8
8
  else
9
9
  fail!(OauthNotEnabled.new)
@@ -1,5 +1,5 @@
1
1
  module ThreeScale
2
2
  module Backend
3
- VERSION = '3.3.1.1'
3
+ VERSION = '3.4.2'
4
4
  end
5
5
  end
@@ -4,7 +4,7 @@
4
4
  require_relative '../3scale/backend/listener_metrics'
5
5
 
6
6
  # Config is not loaded at this point, so read ENV instead.
7
- if ENV['CONFIG_LISTENER_PROMETHEUS_METRICS_ENABLED'].to_s == 'true'
7
+ if ENV['CONFIG_LISTENER_PROMETHEUS_METRICS_ENABLED'].to_s.downcase.freeze == 'true'.freeze
8
8
  prometheus_port = ENV['CONFIG_LISTENER_PROMETHEUS_METRICS_PORT']
9
9
  ThreeScale::Backend::ListenerMetrics.start_metrics_server(prometheus_port)
10
10
  end
data/licenses.xml CHANGED
@@ -23,7 +23,7 @@
23
23
  </dependency>
24
24
  <dependency>
25
25
  <packageName>apisonator</packageName>
26
- <version>3.3.1.1</version>
26
+ <version>3.4.2</version>
27
27
  <licenses>
28
28
  <license>
29
29
  <name>Apache 2.0</name>
@@ -233,7 +233,7 @@
233
233
  </dependency>
234
234
  <dependency>
235
235
  <packageName>coderay</packageName>
236
- <version>1.1.2</version>
236
+ <version>1.1.3</version>
237
237
  <licenses>
238
238
  <license>
239
239
  <name>MIT</name>
@@ -421,7 +421,7 @@
421
421
  </dependency>
422
422
  <dependency>
423
423
  <packageName>method_source</packageName>
424
- <version>0.9.0</version>
424
+ <version>1.0.0</version>
425
425
  <licenses>
426
426
  <license>
427
427
  <name>MIT</name>
@@ -475,7 +475,7 @@
475
475
  </dependency>
476
476
  <dependency>
477
477
  <packageName>multi_json</packageName>
478
- <version>1.13.1</version>
478
+ <version>1.15.0</version>
479
479
  <licenses>
480
480
  <license>
481
481
  <name>MIT</name>
@@ -679,7 +679,7 @@
679
679
  </dependency>
680
680
  <dependency>
681
681
  <packageName>pry</packageName>
682
- <version>0.11.3</version>
682
+ <version>0.14.0</version>
683
683
  <licenses>
684
684
  <license>
685
685
  <name>MIT</name>
@@ -699,7 +699,7 @@
699
699
  </dependency>
700
700
  <dependency>
701
701
  <packageName>pry-doc</packageName>
702
- <version>0.11.1</version>
702
+ <version>1.1.0</version>
703
703
  <licenses>
704
704
  <license>
705
705
  <name>MIT</name>
@@ -1143,7 +1143,7 @@
1143
1143
  </dependency>
1144
1144
  <dependency>
1145
1145
  <packageName>yard</packageName>
1146
- <version>0.9.20</version>
1146
+ <version>0.9.26</version>
1147
1147
  <licenses>
1148
1148
  <license>
1149
1149
  <name>MIT</name>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apisonator
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.1.1
4
+ version: 3.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Ciganek
@@ -16,7 +16,7 @@ authors:
16
16
  autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
- date: 2021-02-12 00:00:00.000000000 Z
19
+ date: 2021-06-17 00:00:00.000000000 Z
20
20
  dependencies: []
21
21
  description: This gem provides a daemon that handles authorization and reporting of
22
22
  web services managed by 3scale.
@@ -164,6 +164,7 @@ files:
164
164
  - lib/3scale/backend/usage_limit.rb
165
165
  - lib/3scale/backend/use_cases/provider_key_change_use_case.rb
166
166
  - lib/3scale/backend/util.rb
167
+ - lib/3scale/backend/utilization.rb
167
168
  - lib/3scale/backend/validators.rb
168
169
  - lib/3scale/backend/validators/base.rb
169
170
  - lib/3scale/backend/validators/key.rb
@@ -207,8 +208,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
207
208
  - !ruby/object:Gem::Version
208
209
  version: 1.3.7
209
210
  requirements: []
210
- rubyforge_project:
211
- rubygems_version: 2.7.8
211
+ rubygems_version: 3.2.10
212
212
  signing_key:
213
213
  specification_version: 4
214
214
  summary: 3scale web service management system backend