apisonator 3.3.1 → 3.4.1

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: a812d330bdcb770421ed926c478c8a8b48087ba33ad28ab80317a48db935f432
4
- data.tar.gz: 5bc8b40c69fbf5a6a680b71692dc66433746b053c0dbf3dcfe6e3d4aacd656af
3
+ metadata.gz: 78b328c64420fa58f4480eb39b555856dd9a032ba78eed32f084767988cea59d
4
+ data.tar.gz: de454b45677bc1c133eeda34c8fb4ebfe56253c4b4de851f1d192652c5baf999
5
5
  SHA512:
6
- metadata.gz: 807c4d8cfc932e600780f4ba97fa5cfa3afcaca04ad0e27395e92fca31407d96e360412a290566c78955e5cf12895a5f4a70e589474de045bf3f0f7cf21311af
7
- data.tar.gz: 17bf0d7c783ee36b78a885a441846edeaf8906185ab183c91ffc7f0c7f7c13e45b4237c0c7adb1a228794b3f64d4a0729f6eb8360a59910021d65a0b99c1edc1
6
+ metadata.gz: bf7613f456c9810217adfbdd5f345b86aa59d9abdccd44d308d846e7f271849d484601900348a4dcd4a16437d9e0376b7927e3675374a9be7a2af723c2820626
7
+ data.tar.gz: deb0823a0648f55bf24c24da6727afa4e62d2d5d92eeeee5545b9f6c8af38b2105bc8bb8caf3f7b28c187e020080b9b9f0b43721a42dfece2d6963d698a23e9a
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.1 - 2021-06-16
6
+
7
+ ### Changed
8
+
9
+ - Introduced performance optimizations around alerts detection
10
+ ([#287](https://github.com/3scale/apisonator/pull/287)).
11
+
12
+ ## 3.4.0 - 2021-06-14
13
+
14
+ ### Added
15
+
16
+ - New extension that list the keys of an application
17
+ ([#284](https://github.com/3scale/apisonator/pull/284)).
18
+
19
+ ### Changed
20
+
21
+ - It is now possible to use OIDC in the auth and authrep endpoints
22
+ ([#280](https://github.com/3scale/apisonator/pull/280)).
23
+ - Updated multi-json to 1.15.0
24
+ ([#278](https://github.com/3scale/apisonator/pull/278)).
25
+
26
+ ### Removed
27
+
28
+ - Deleted unused service attributes related with deleted end-users functionality
29
+ ([#277](https://github.com/3scale/apisonator/pull/277)).
30
+
31
+ ## 3.3.3 - 2021-03-09
32
+
33
+ ### Changed
34
+
35
+ - Check if alerts can be raised before calculating utilization (perf
36
+ optimization) ([#275](https://github.com/3scale/apisonator/pull/275)).
37
+
38
+ ### Removed
39
+
40
+ - Stop maintaining unused "current_max" key in Alerts
41
+ ([#272](https://github.com/3scale/apisonator/pull/272)).
42
+
43
+ ## 3.3.2 - 2021-02-23
44
+
45
+ ### Fixed
46
+
47
+ - Fixed nil exception in `Aggregator.process`
48
+ ([#269](https://github.com/3scale/apisonator/pull/269)).
49
+
50
+ ### Changed
51
+
52
+ - Updated to Ruby 2.7 in Docker images
53
+ ([#265](https://github.com/3scale/apisonator/pull/265)) and
54
+ ([#266](https://github.com/3scale/apisonator/pull/266)).
55
+ - Updated pry to 0.14.0 and pry-doc to 1.1.0
56
+ ([#267](https://github.com/3scale/apisonator/pull/267)).
57
+ - Updated Docker image to be based on RHEL UBI 8
58
+ ([#268](https://github.com/3scale/apisonator/pull/268)).
59
+
60
+ ### Removed
61
+
62
+ - Removed redundant prometheus config params
63
+ (`listener_prometheus_metrics.enabled` and `listener_prometheus_metrics.port`)
64
+ ([#270](https://github.com/3scale/apisonator/pull/270)).
65
+
66
+ ## 3.3.1.1 - 2021-02-12
67
+
68
+ ### Changed
69
+
70
+ - Updated our Puma fork to v4.3.7
71
+ ([#261](https://github.com/3scale/apisonator/pull/261)).
72
+
5
73
  ## 3.3.1 - 2021-02-11
6
74
 
7
75
  ### Fixed
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
 
@@ -46,7 +46,7 @@ group :development, :test do
46
46
  end
47
47
 
48
48
  # Default server by platform
49
- gem 'puma', git: 'https://github.com/3scale/puma', ref: 'b034371406690d3e6c2a9301c4a48bd721f3efc3'
49
+ gem 'puma', git: 'https://github.com/3scale/puma', branch: '3scale-4.3.7'
50
50
  # gems required by the runner
51
51
  gem 'gli', '~> 2.16.1', require: nil
52
52
  # Workers
data/Gemfile.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  GIT
2
2
  remote: https://github.com/3scale/puma
3
- revision: b034371406690d3e6c2a9301c4a48bd721f3efc3
4
- ref: b034371406690d3e6c2a9301c4a48bd721f3efc3
3
+ revision: c0601d08695839b8ffd0f380e91c3b91c1e8b754
4
+ branch: 3scale-4.3.7
5
5
  specs:
6
- puma (2.15.3)
6
+ puma (4.3.7)
7
+ nio4r (~> 2.0)
7
8
 
8
9
  GIT
9
10
  remote: https://github.com/3scale/redis-rb
@@ -35,7 +36,7 @@ GIT
35
36
  PATH
36
37
  remote: .
37
38
  specs:
38
- apisonator (3.3.1)
39
+ apisonator (3.4.1)
39
40
 
40
41
  GEM
41
42
  remote: https://rubygems.org/
@@ -95,7 +96,7 @@ GEM
95
96
  chronic (0.10.2)
96
97
  codeclimate-test-reporter (0.6.0)
97
98
  simplecov (>= 0.7.1, < 1.0.0)
98
- coderay (1.1.2)
99
+ coderay (1.1.3)
99
100
  concurrent-ruby (1.1.6)
100
101
  console (1.8.2)
101
102
  daemons (1.2.4)
@@ -130,13 +131,13 @@ GEM
130
131
  localhost (1.1.6)
131
132
  mapping (1.1.1)
132
133
  metaclass (0.0.4)
133
- method_source (0.9.0)
134
+ method_source (1.0.0)
134
135
  mini_portile2 (2.4.0)
135
136
  minitest (5.14.1)
136
137
  mocha (1.3.0)
137
138
  metaclass (~> 0.0.1)
138
139
  mono_logger (1.1.0)
139
- multi_json (1.13.1)
140
+ multi_json (1.15.0)
140
141
  mustache (1.0.5)
141
142
  mustermann (1.0.2)
142
143
  net-scp (1.2.1)
@@ -163,15 +164,15 @@ GEM
163
164
  protocol-hpack (~> 1.4)
164
165
  protocol-http (~> 0.15)
165
166
  protocol-redis (0.5.0)
166
- pry (0.11.3)
167
- coderay (~> 1.1.0)
168
- method_source (~> 0.9.0)
167
+ pry (0.14.0)
168
+ coderay (~> 1.1)
169
+ method_source (~> 1.0)
169
170
  pry-byebug (3.5.1)
170
171
  byebug (~> 9.1)
171
172
  pry (~> 0.10)
172
- pry-doc (0.11.1)
173
- pry (~> 0.9)
174
- yard (~> 0.9)
173
+ pry-doc (1.1.0)
174
+ pry (~> 0.11)
175
+ yard (~> 0.9.11)
175
176
  rack (2.1.4)
176
177
  rack-protection (2.0.3)
177
178
  rack
@@ -259,7 +260,7 @@ GEM
259
260
  prometheus-client (~> 1.0)
260
261
  yabeda (~> 0.5)
261
262
  yajl-ruby (1.3.1)
262
- yard (0.9.20)
263
+ yard (0.9.26)
263
264
 
264
265
  PLATFORMS
265
266
  ruby
@@ -283,9 +284,9 @@ DEPENDENCIES
283
284
  nokogiri (~> 1.10.8)
284
285
  pg (= 0.20.0)
285
286
  pkg-config (~> 1.1.7)
286
- pry (~> 0.11.3)
287
+ pry (~> 0.14)
287
288
  pry-byebug (~> 3.5.1)
288
- pry-doc (~> 0.11.1)
289
+ pry-doc (~> 1.1)
289
290
  puma!
290
291
  rack (~> 2.1.4)
291
292
  rack-test (= 0.8.2)
data/Gemfile.on_prem.lock CHANGED
@@ -1,9 +1,10 @@
1
1
  GIT
2
2
  remote: https://github.com/3scale/puma
3
- revision: b034371406690d3e6c2a9301c4a48bd721f3efc3
4
- ref: b034371406690d3e6c2a9301c4a48bd721f3efc3
3
+ revision: c0601d08695839b8ffd0f380e91c3b91c1e8b754
4
+ branch: 3scale-4.3.7
5
5
  specs:
6
- puma (2.15.3)
6
+ puma (4.3.7)
7
+ nio4r (~> 2.0)
7
8
 
8
9
  GIT
9
10
  remote: https://github.com/3scale/redis-rb
@@ -35,7 +36,7 @@ GIT
35
36
  PATH
36
37
  remote: .
37
38
  specs:
38
- apisonator (3.3.1)
39
+ apisonator (3.4.1)
39
40
 
40
41
  GEM
41
42
  remote: https://rubygems.org/
@@ -85,7 +86,7 @@ GEM
85
86
  byebug (9.1.0)
86
87
  codeclimate-test-reporter (0.6.0)
87
88
  simplecov (>= 0.7.1, < 1.0.0)
88
- coderay (1.1.2)
89
+ coderay (1.1.3)
89
90
  concurrent-ruby (1.1.6)
90
91
  console (1.8.2)
91
92
  daemons (1.2.4)
@@ -119,13 +120,13 @@ GEM
119
120
  localhost (1.1.6)
120
121
  mapping (1.1.1)
121
122
  metaclass (0.0.4)
122
- method_source (0.9.0)
123
+ method_source (1.0.0)
123
124
  mini_portile2 (2.4.0)
124
125
  minitest (5.14.1)
125
126
  mocha (1.3.0)
126
127
  metaclass (~> 0.0.1)
127
128
  mono_logger (1.1.0)
128
- multi_json (1.13.1)
129
+ multi_json (1.15.0)
129
130
  mustache (1.0.5)
130
131
  mustermann (1.0.2)
131
132
  net-scp (1.2.1)
@@ -151,15 +152,15 @@ GEM
151
152
  protocol-hpack (~> 1.4)
152
153
  protocol-http (~> 0.15)
153
154
  protocol-redis (0.5.0)
154
- pry (0.11.3)
155
- coderay (~> 1.1.0)
156
- method_source (~> 0.9.0)
155
+ pry (0.14.0)
156
+ coderay (~> 1.1)
157
+ method_source (~> 1.0)
157
158
  pry-byebug (3.5.1)
158
159
  byebug (~> 9.1)
159
160
  pry (~> 0.10)
160
- pry-doc (0.11.1)
161
- pry (~> 0.9)
162
- yard (~> 0.9)
161
+ pry-doc (1.1.0)
162
+ pry (~> 0.11)
163
+ yard (~> 0.9.11)
163
164
  rack (2.1.4)
164
165
  rack-protection (2.0.3)
165
166
  rack
@@ -243,7 +244,7 @@ GEM
243
244
  prometheus-client (~> 1.0)
244
245
  yabeda (~> 0.5)
245
246
  yajl-ruby (1.3.1)
246
- yard (0.9.20)
247
+ yard (0.9.26)
247
248
 
248
249
  PLATFORMS
249
250
  ruby
@@ -264,9 +265,9 @@ DEPENDENCIES
264
265
  mocha (~> 1.3)
265
266
  nokogiri (~> 1.10.8)
266
267
  pkg-config (~> 1.1.7)
267
- pry (~> 0.11.3)
268
+ pry (~> 0.14)
268
269
  pry-byebug (~> 3.5.1)
269
- pry-doc (~> 0.11.1)
270
+ pry-doc (~> 1.1)
270
271
  puma!
271
272
  rack (~> 2.1.4)
272
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,104 @@ 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
64
- 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
+ storage.setex(key_usage_already_checked(service_id, app_id), ttl, '1'.freeze)
84
+ Memoizer.clear(Memoizer.build_key(self, :need_to_check_all?, service_id, app_id))
85
+ end
86
+
87
+ def self.invalidate(service_id, app_id)
88
+ storage.del(key_usage_already_checked(service_id, app_id))
65
89
  end
66
90
 
67
- app_usage_reports.each(&max)
91
+ def self.invalidate_for_service(service_id)
92
+ app_ids = []
93
+ cursor = 0
68
94
 
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
95
+ loop do
96
+ cursor, ids = storage.sscan(
97
+ Application.applications_set_key(service_id), cursor, count: SCAN_SLICE
98
+ )
99
+
100
+ app_ids += ids
101
+
102
+ break if cursor.to_i == 0
103
+ end
104
+
105
+ invalidate_batch(service_id, app_ids)
73
106
  end
74
107
 
75
- [max_utilization, max_record]
108
+ def self.invalidate_batch(service_id, app_ids)
109
+ app_ids.each_slice(PIPELINED_SLICE_SIZE) do |ids|
110
+ keys = ids.map { |app_id| key_usage_already_checked(service_id, app_id) }
111
+ storage.del(keys)
112
+ end
113
+ end
114
+ private_class_method :invalidate_batch
76
115
  end
77
116
 
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
117
+ def can_raise_more_alerts?(service_id, app_id)
118
+ allowed_bins = allowed_set_for_service(service_id).sort
81
119
 
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
120
+ return false if allowed_bins.empty?
86
121
 
87
- keys = alert_keys(service_id, app_id, discrete, period_hour)
122
+ # If the bin with the highest value has already been notified, there's
123
+ # no need to notify anything else.
124
+ not notified?(service_id, app_id, allowed_bins.last)
125
+ end
88
126
 
89
- already_alerted, allowed, current_max, _ = storage.pipelined do
127
+ def update_utilization(service_id, app_id, utilization)
128
+ discrete = utilization_discrete(utilization.ratio)
129
+
130
+ keys = alert_keys(service_id, app_id, discrete)
131
+
132
+ already_alerted, allowed = storage.pipelined do
90
133
  storage.get(keys[:already_notified])
91
134
  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
135
  end
100
136
 
101
137
  if already_alerted.nil? && allowed && discrete.to_i > 0
102
- next_id, _ = storage.pipelined do
138
+ next_id, _, _ = storage.pipelined do
103
139
  storage.incr(keys[:current_id])
104
140
  storage.setex(keys[:already_notified], ALERT_TTL, "1")
141
+ UsagesChecked.invalidate(service_id, app_id)
105
142
  end
106
143
 
107
144
  alert = { :id => next_id,
108
145
  :utilization => discrete,
109
- :max_utilization => max_utilization,
146
+ :max_utilization => utilization.ratio,
110
147
  :application_id => app_id,
111
148
  :service_id => service_id,
112
- :timestamp => timestamp,
113
- :limit => formatted_limit(max_record) }
149
+ :timestamp => Time.now.utc,
150
+ :limit => utilization.to_s }
114
151
 
115
152
  Backend::EventStorage::store(:alert, alert)
116
153
  end
@@ -124,10 +161,15 @@ module ThreeScale
124
161
  end || FIRST_ALERT_BIN
125
162
  end
126
163
 
127
- def formatted_limit(record)
128
- "#{record.metric_name} per #{record.period}: "\
129
- "#{record.current_value}/#{record.max_value}"
164
+ def allowed_set_for_service(service_id)
165
+ storage.smembers(key_allowed_set(service_id)).map(&:to_i) # Redis returns strings always
166
+ end
167
+ memoize :allowed_set_for_service
168
+
169
+ def notified?(service_id, app_id, bin)
170
+ storage.get(key_already_notified(service_id, app_id, bin))
130
171
  end
172
+ memoize :notified?
131
173
 
132
174
  def storage
133
175
  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'
3
+ VERSION = '3.4.1'
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</version>
26
+ <version>3.4.1</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>
@@ -709,7 +709,7 @@
709
709
  </dependency>
710
710
  <dependency>
711
711
  <packageName>puma</packageName>
712
- <version>2.15.3</version>
712
+ <version>4.3.7</version>
713
713
  <licenses>
714
714
  <license>
715
715
  <name>New BSD</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
4
+ version: 3.4.1
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-11 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