apisonator 3.3.2 → 3.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -0
- data/Gemfile.lock +2 -2
- data/Gemfile.on_prem.lock +2 -2
- data/lib/3scale/backend.rb +1 -0
- data/lib/3scale/backend/alert_limit.rb +9 -11
- data/lib/3scale/backend/alerts.rb +94 -47
- data/lib/3scale/backend/constants.rb +2 -0
- data/lib/3scale/backend/event_storage.rb +8 -11
- data/lib/3scale/backend/service.rb +3 -35
- data/lib/3scale/backend/stats/aggregator.rb +30 -28
- data/lib/3scale/backend/stats/cleaner.rb +0 -6
- data/lib/3scale/backend/transactor.rb +17 -26
- data/lib/3scale/backend/transactor/status.rb +27 -9
- data/lib/3scale/backend/transactor/usage_report.rb +1 -1
- data/lib/3scale/backend/usage.rb +10 -3
- data/lib/3scale/backend/utilization.rb +83 -0
- data/lib/3scale/backend/validators.rb +7 -0
- data/lib/3scale/backend/validators/oauth_setting.rb +1 -1
- data/lib/3scale/backend/version.rb +1 -1
- data/licenses.xml +2 -2
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bb8035c96211d326fdd9231d4f7d92303ca0821d813cc01b2e44bef5c9ec8f4b
|
|
4
|
+
data.tar.gz: 3fb050d336b0bc774281741590a7b848c63a2ef05300ede3602907fb116b48d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7fd9acbfaf34c91682cce1fe47014dfcca36895e5520970fcc1b9a7c2d583ee2c467c21c87a41328d1c7331a192b104324a403a5888a55cfc22a7835e646ee4
|
|
7
|
+
data.tar.gz: 74004a2879c25b1cbab73369f5d261e64eac2ed0ce218758d4605322361eae88058caf2f512fbe993d98e6958251ecb55305171e5f93e2178131a75a5ee8157d
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
Notable changes to Apisonator will be tracked in this document.
|
|
4
4
|
|
|
5
|
+
## 3.4.3 - 2021-06-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed bug that causes Apisonator to stop pinging Porta to fetch events. This
|
|
10
|
+
bug triggers very rarely
|
|
11
|
+
([#292](https://github.com/3scale/apisonator/pull/292)).
|
|
12
|
+
|
|
13
|
+
## 3.4.2 - 2021-06-17
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed exception raised when TTL = 0 in `UsagesChecked.mark_all_checked`
|
|
18
|
+
([#290](https://github.com/3scale/apisonator/pull/290)).
|
|
19
|
+
|
|
20
|
+
## 3.4.1 - 2021-06-16
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Introduced performance optimizations around alerts detection
|
|
25
|
+
([#287](https://github.com/3scale/apisonator/pull/287)).
|
|
26
|
+
|
|
27
|
+
## 3.4.0 - 2021-06-14
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- New extension that list the keys of an application
|
|
32
|
+
([#284](https://github.com/3scale/apisonator/pull/284)).
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- It is now possible to use OIDC in the auth and authrep endpoints
|
|
37
|
+
([#280](https://github.com/3scale/apisonator/pull/280)).
|
|
38
|
+
- Updated multi-json to 1.15.0
|
|
39
|
+
([#278](https://github.com/3scale/apisonator/pull/278)).
|
|
40
|
+
|
|
41
|
+
### Removed
|
|
42
|
+
|
|
43
|
+
- Deleted unused service attributes related with deleted end-users functionality
|
|
44
|
+
([#277](https://github.com/3scale/apisonator/pull/277)).
|
|
45
|
+
|
|
46
|
+
## 3.3.3 - 2021-03-09
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- Check if alerts can be raised before calculating utilization (perf
|
|
51
|
+
optimization) ([#275](https://github.com/3scale/apisonator/pull/275)).
|
|
52
|
+
|
|
53
|
+
### Removed
|
|
54
|
+
|
|
55
|
+
- Stop maintaining unused "current_max" key in Alerts
|
|
56
|
+
([#272](https://github.com/3scale/apisonator/pull/272)).
|
|
57
|
+
|
|
5
58
|
## 3.3.2 - 2021-02-23
|
|
6
59
|
|
|
7
60
|
### Fixed
|
data/Gemfile.lock
CHANGED
|
@@ -36,7 +36,7 @@ GIT
|
|
|
36
36
|
PATH
|
|
37
37
|
remote: .
|
|
38
38
|
specs:
|
|
39
|
-
apisonator (3.3
|
|
39
|
+
apisonator (3.4.3)
|
|
40
40
|
|
|
41
41
|
GEM
|
|
42
42
|
remote: https://rubygems.org/
|
|
@@ -137,7 +137,7 @@ GEM
|
|
|
137
137
|
mocha (1.3.0)
|
|
138
138
|
metaclass (~> 0.0.1)
|
|
139
139
|
mono_logger (1.1.0)
|
|
140
|
-
multi_json (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)
|
data/Gemfile.on_prem.lock
CHANGED
|
@@ -36,7 +36,7 @@ GIT
|
|
|
36
36
|
PATH
|
|
37
37
|
remote: .
|
|
38
38
|
specs:
|
|
39
|
-
apisonator (3.3
|
|
39
|
+
apisonator (3.4.3)
|
|
40
40
|
|
|
41
41
|
GEM
|
|
42
42
|
remote: https://rubygems.org/
|
|
@@ -126,7 +126,7 @@ GEM
|
|
|
126
126
|
mocha (1.3.0)
|
|
127
127
|
metaclass (~> 0.0.1)
|
|
128
128
|
mono_logger (1.1.0)
|
|
129
|
-
multi_json (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)
|
data/lib/3scale/backend.rb
CHANGED
|
@@ -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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
107
|
+
break if cursor.to_i == 0
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
invalidate_batch(service_id, app_ids)
|
|
73
111
|
end
|
|
74
112
|
|
|
75
|
-
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
132
|
+
def update_utilization(service_id, app_id, utilization)
|
|
133
|
+
discrete = utilization_discrete(utilization.ratio)
|
|
88
134
|
|
|
89
|
-
|
|
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 =>
|
|
151
|
+
:max_utilization => utilization.ratio,
|
|
110
152
|
:application_id => app_id,
|
|
111
153
|
:service_id => service_id,
|
|
112
|
-
:timestamp =>
|
|
113
|
-
:limit =>
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
@@ -4,7 +4,10 @@ module ThreeScale
|
|
|
4
4
|
module Backend
|
|
5
5
|
class EventStorage
|
|
6
6
|
PING_TTL = 60
|
|
7
|
-
|
|
7
|
+
private_constant :PING_TTL
|
|
8
|
+
|
|
9
|
+
EVENT_TYPES = [:first_traffic, :first_daily_traffic, :alert].freeze
|
|
10
|
+
private_constant :EVENT_TYPES
|
|
8
11
|
|
|
9
12
|
class << self
|
|
10
13
|
include StorageHelpers
|
|
@@ -84,21 +87,15 @@ module ThreeScale
|
|
|
84
87
|
URI(events_hook)
|
|
85
88
|
end
|
|
86
89
|
|
|
87
|
-
def expire_last_ping
|
|
88
|
-
storage.expire(events_ping_key, PING_TTL)
|
|
89
|
-
end
|
|
90
|
-
|
|
91
90
|
def pending_ping?
|
|
92
91
|
## the queue is not empty and more than timeout has passed
|
|
93
92
|
## since the front-end was notified
|
|
94
|
-
events_set_size,
|
|
95
|
-
|
|
96
|
-
storage.
|
|
93
|
+
events_set_size, can_ping = storage.pipelined do
|
|
94
|
+
size
|
|
95
|
+
storage.set(events_ping_key, '1'.freeze, ex: PING_TTL, nx: true)
|
|
97
96
|
end
|
|
98
97
|
|
|
99
|
-
|
|
100
|
-
expire_last_ping
|
|
101
|
-
events_set_size > 0
|
|
98
|
+
can_ping && events_set_size > 0
|
|
102
99
|
end
|
|
103
100
|
|
|
104
101
|
def decode_event(raw_event)
|
|
@@ -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
|
-
|
|
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(
|
|
62
|
+
update_alerts(transactions)
|
|
63
63
|
begin
|
|
64
64
|
ApplicationEvents.ping
|
|
65
65
|
rescue ApplicationEvents::PingFailed => e
|
|
@@ -137,33 +137,35 @@ module ThreeScale
|
|
|
137
137
|
logger.info(MAX_BUCKETS_CREATED_MSG)
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
-
def update_alerts(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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)
|
|
167
169
|
end
|
|
168
170
|
end
|
|
169
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
14
|
-
@application
|
|
15
|
-
@oauth
|
|
16
|
-
@usage
|
|
17
|
-
@predicted_usage
|
|
18
|
-
@values
|
|
19
|
-
@timestamp
|
|
20
|
-
@hierarchy_ext
|
|
21
|
-
@flat_usage_ext
|
|
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|
|
data/lib/3scale/backend/usage.rb
CHANGED
|
@@ -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(
|
|
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
|
|
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
|
data/licenses.xml
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
</dependency>
|
|
24
24
|
<dependency>
|
|
25
25
|
<packageName>apisonator</packageName>
|
|
26
|
-
<version>3.3
|
|
26
|
+
<version>3.4.3</version>
|
|
27
27
|
<licenses>
|
|
28
28
|
<license>
|
|
29
29
|
<name>Apache 2.0</name>
|
|
@@ -475,7 +475,7 @@
|
|
|
475
475
|
</dependency>
|
|
476
476
|
<dependency>
|
|
477
477
|
<packageName>multi_json</packageName>
|
|
478
|
-
<version>1.
|
|
478
|
+
<version>1.15.0</version>
|
|
479
479
|
<licenses>
|
|
480
480
|
<license>
|
|
481
481
|
<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
|
|
4
|
+
version: 3.4.3
|
|
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-
|
|
19
|
+
date: 2021-06-23 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
|