apisonator 2.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +317 -0
  3. data/Gemfile +11 -0
  4. data/Gemfile.base +65 -0
  5. data/Gemfile.lock +319 -0
  6. data/Gemfile.on_prem +1 -0
  7. data/Gemfile.on_prem.lock +297 -0
  8. data/LICENSE +202 -0
  9. data/NOTICE +15 -0
  10. data/README.md +230 -0
  11. data/Rakefile +287 -0
  12. data/apisonator.gemspec +47 -0
  13. data/app/api/api.rb +13 -0
  14. data/app/api/internal/alert_limits.rb +32 -0
  15. data/app/api/internal/application_keys.rb +49 -0
  16. data/app/api/internal/application_referrer_filters.rb +43 -0
  17. data/app/api/internal/applications.rb +77 -0
  18. data/app/api/internal/errors.rb +54 -0
  19. data/app/api/internal/events.rb +42 -0
  20. data/app/api/internal/internal.rb +104 -0
  21. data/app/api/internal/metrics.rb +40 -0
  22. data/app/api/internal/service_tokens.rb +46 -0
  23. data/app/api/internal/services.rb +58 -0
  24. data/app/api/internal/stats.rb +42 -0
  25. data/app/api/internal/usagelimits.rb +62 -0
  26. data/app/api/internal/utilization.rb +23 -0
  27. data/bin/3scale_backend +223 -0
  28. data/bin/3scale_backend_worker +26 -0
  29. data/config.ru +4 -0
  30. data/config/puma.rb +192 -0
  31. data/config/schedule.rb +9 -0
  32. data/ext/mkrf_conf.rb +64 -0
  33. data/lib/3scale/backend.rb +67 -0
  34. data/lib/3scale/backend/alert_limit.rb +56 -0
  35. data/lib/3scale/backend/alerts.rb +137 -0
  36. data/lib/3scale/backend/analytics/kinesis.rb +3 -0
  37. data/lib/3scale/backend/analytics/kinesis/adapter.rb +180 -0
  38. data/lib/3scale/backend/analytics/kinesis/exporter.rb +86 -0
  39. data/lib/3scale/backend/analytics/kinesis/job.rb +135 -0
  40. data/lib/3scale/backend/analytics/redshift.rb +3 -0
  41. data/lib/3scale/backend/analytics/redshift/adapter.rb +367 -0
  42. data/lib/3scale/backend/analytics/redshift/importer.rb +83 -0
  43. data/lib/3scale/backend/analytics/redshift/job.rb +33 -0
  44. data/lib/3scale/backend/application.rb +330 -0
  45. data/lib/3scale/backend/application_events.rb +76 -0
  46. data/lib/3scale/backend/background_job.rb +65 -0
  47. data/lib/3scale/backend/configurable.rb +20 -0
  48. data/lib/3scale/backend/configuration.rb +151 -0
  49. data/lib/3scale/backend/configuration/loader.rb +42 -0
  50. data/lib/3scale/backend/constants.rb +19 -0
  51. data/lib/3scale/backend/cors.rb +84 -0
  52. data/lib/3scale/backend/distributed_lock.rb +67 -0
  53. data/lib/3scale/backend/environment.rb +21 -0
  54. data/lib/3scale/backend/error_storage.rb +52 -0
  55. data/lib/3scale/backend/errors.rb +343 -0
  56. data/lib/3scale/backend/event_storage.rb +120 -0
  57. data/lib/3scale/backend/experiment.rb +84 -0
  58. data/lib/3scale/backend/extensions.rb +5 -0
  59. data/lib/3scale/backend/extensions/array.rb +19 -0
  60. data/lib/3scale/backend/extensions/hash.rb +26 -0
  61. data/lib/3scale/backend/extensions/nil_class.rb +13 -0
  62. data/lib/3scale/backend/extensions/redis.rb +44 -0
  63. data/lib/3scale/backend/extensions/string.rb +13 -0
  64. data/lib/3scale/backend/extensions/time.rb +110 -0
  65. data/lib/3scale/backend/failed_jobs_scheduler.rb +141 -0
  66. data/lib/3scale/backend/job_fetcher.rb +122 -0
  67. data/lib/3scale/backend/listener.rb +728 -0
  68. data/lib/3scale/backend/listener_metrics.rb +99 -0
  69. data/lib/3scale/backend/logging.rb +48 -0
  70. data/lib/3scale/backend/logging/external.rb +44 -0
  71. data/lib/3scale/backend/logging/external/impl.rb +93 -0
  72. data/lib/3scale/backend/logging/external/impl/airbrake.rb +66 -0
  73. data/lib/3scale/backend/logging/external/impl/bugsnag.rb +69 -0
  74. data/lib/3scale/backend/logging/external/impl/default.rb +18 -0
  75. data/lib/3scale/backend/logging/external/resque.rb +57 -0
  76. data/lib/3scale/backend/logging/logger.rb +18 -0
  77. data/lib/3scale/backend/logging/middleware.rb +62 -0
  78. data/lib/3scale/backend/logging/middleware/json_writer.rb +21 -0
  79. data/lib/3scale/backend/logging/middleware/text_writer.rb +60 -0
  80. data/lib/3scale/backend/logging/middleware/writer.rb +143 -0
  81. data/lib/3scale/backend/logging/worker.rb +107 -0
  82. data/lib/3scale/backend/manifest.rb +80 -0
  83. data/lib/3scale/backend/memoizer.rb +277 -0
  84. data/lib/3scale/backend/metric.rb +275 -0
  85. data/lib/3scale/backend/metric/collection.rb +91 -0
  86. data/lib/3scale/backend/oauth.rb +4 -0
  87. data/lib/3scale/backend/oauth/token.rb +26 -0
  88. data/lib/3scale/backend/oauth/token_key.rb +30 -0
  89. data/lib/3scale/backend/oauth/token_storage.rb +313 -0
  90. data/lib/3scale/backend/oauth/token_value.rb +25 -0
  91. data/lib/3scale/backend/period.rb +3 -0
  92. data/lib/3scale/backend/period/boundary.rb +107 -0
  93. data/lib/3scale/backend/period/cache.rb +28 -0
  94. data/lib/3scale/backend/period/period.rb +402 -0
  95. data/lib/3scale/backend/queue_storage.rb +16 -0
  96. data/lib/3scale/backend/rack.rb +49 -0
  97. data/lib/3scale/backend/rack/exception_catcher.rb +136 -0
  98. data/lib/3scale/backend/rack/internal_error_catcher.rb +23 -0
  99. data/lib/3scale/backend/rack/prometheus.rb +19 -0
  100. data/lib/3scale/backend/saas.rb +6 -0
  101. data/lib/3scale/backend/saas_analytics.rb +4 -0
  102. data/lib/3scale/backend/server.rb +30 -0
  103. data/lib/3scale/backend/server/falcon.rb +52 -0
  104. data/lib/3scale/backend/server/puma.rb +71 -0
  105. data/lib/3scale/backend/service.rb +317 -0
  106. data/lib/3scale/backend/service_token.rb +97 -0
  107. data/lib/3scale/backend/stats.rb +8 -0
  108. data/lib/3scale/backend/stats/aggregator.rb +170 -0
  109. data/lib/3scale/backend/stats/aggregators/base.rb +72 -0
  110. data/lib/3scale/backend/stats/aggregators/response_code.rb +58 -0
  111. data/lib/3scale/backend/stats/aggregators/usage.rb +34 -0
  112. data/lib/3scale/backend/stats/bucket_reader.rb +135 -0
  113. data/lib/3scale/backend/stats/bucket_storage.rb +108 -0
  114. data/lib/3scale/backend/stats/cleaner.rb +195 -0
  115. data/lib/3scale/backend/stats/codes_commons.rb +14 -0
  116. data/lib/3scale/backend/stats/delete_job_def.rb +60 -0
  117. data/lib/3scale/backend/stats/key_generator.rb +73 -0
  118. data/lib/3scale/backend/stats/keys.rb +104 -0
  119. data/lib/3scale/backend/stats/partition_eraser_job.rb +58 -0
  120. data/lib/3scale/backend/stats/partition_generator_job.rb +46 -0
  121. data/lib/3scale/backend/stats/period_commons.rb +34 -0
  122. data/lib/3scale/backend/stats/stats_parser.rb +141 -0
  123. data/lib/3scale/backend/stats/storage.rb +113 -0
  124. data/lib/3scale/backend/statsd.rb +14 -0
  125. data/lib/3scale/backend/storable.rb +35 -0
  126. data/lib/3scale/backend/storage.rb +40 -0
  127. data/lib/3scale/backend/storage_async.rb +4 -0
  128. data/lib/3scale/backend/storage_async/async_redis.rb +21 -0
  129. data/lib/3scale/backend/storage_async/client.rb +205 -0
  130. data/lib/3scale/backend/storage_async/pipeline.rb +79 -0
  131. data/lib/3scale/backend/storage_async/resque_extensions.rb +30 -0
  132. data/lib/3scale/backend/storage_helpers.rb +278 -0
  133. data/lib/3scale/backend/storage_key_helpers.rb +9 -0
  134. data/lib/3scale/backend/storage_sync.rb +43 -0
  135. data/lib/3scale/backend/transaction.rb +62 -0
  136. data/lib/3scale/backend/transactor.rb +177 -0
  137. data/lib/3scale/backend/transactor/limit_headers.rb +54 -0
  138. data/lib/3scale/backend/transactor/notify_batcher.rb +139 -0
  139. data/lib/3scale/backend/transactor/notify_job.rb +47 -0
  140. data/lib/3scale/backend/transactor/process_job.rb +33 -0
  141. data/lib/3scale/backend/transactor/report_job.rb +84 -0
  142. data/lib/3scale/backend/transactor/status.rb +236 -0
  143. data/lib/3scale/backend/transactor/usage_report.rb +182 -0
  144. data/lib/3scale/backend/usage.rb +63 -0
  145. data/lib/3scale/backend/usage_limit.rb +115 -0
  146. data/lib/3scale/backend/use_cases/provider_key_change_use_case.rb +60 -0
  147. data/lib/3scale/backend/util.rb +17 -0
  148. data/lib/3scale/backend/validators.rb +26 -0
  149. data/lib/3scale/backend/validators/base.rb +36 -0
  150. data/lib/3scale/backend/validators/key.rb +17 -0
  151. data/lib/3scale/backend/validators/limits.rb +57 -0
  152. data/lib/3scale/backend/validators/oauth_key.rb +15 -0
  153. data/lib/3scale/backend/validators/oauth_setting.rb +15 -0
  154. data/lib/3scale/backend/validators/redirect_uri.rb +33 -0
  155. data/lib/3scale/backend/validators/referrer.rb +60 -0
  156. data/lib/3scale/backend/validators/service_state.rb +15 -0
  157. data/lib/3scale/backend/validators/state.rb +15 -0
  158. data/lib/3scale/backend/version.rb +5 -0
  159. data/lib/3scale/backend/views/oauth_access_tokens.builder +14 -0
  160. data/lib/3scale/backend/views/oauth_app_id_by_token.builder +4 -0
  161. data/lib/3scale/backend/worker.rb +87 -0
  162. data/lib/3scale/backend/worker_async.rb +88 -0
  163. data/lib/3scale/backend/worker_metrics.rb +44 -0
  164. data/lib/3scale/backend/worker_sync.rb +32 -0
  165. data/lib/3scale/bundler_shim.rb +17 -0
  166. data/lib/3scale/prometheus_server.rb +10 -0
  167. data/lib/3scale/tasks/connectivity.rake +41 -0
  168. data/lib/3scale/tasks/helpers.rb +3 -0
  169. data/lib/3scale/tasks/helpers/environment.rb +23 -0
  170. data/lib/3scale/tasks/stats.rake +131 -0
  171. data/lib/3scale/tasks/swagger.rake +46 -0
  172. data/licenses.xml +1215 -0
  173. metadata +227 -0
@@ -0,0 +1,108 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module Stats
4
+
5
+ # This class manages the buckets where we are storing stats keys.
6
+ # The way those buckets work is as follows: we are creating a bucket
7
+ # every few seconds (10 by default now), and in each of those buckets,
8
+ # we store all the stats keys that have changed in that bucket creation
9
+ # interval.
10
+ # The values of the keys that are stored in the buckets can be retrieved
11
+ # with a normal call to redis.
12
+ class BucketStorage
13
+ # If we have not read buckets for a long time, we might deal with lots
14
+ # of keys in the union operation. This is why we define a constant that
15
+ # limits the number of buckets that we send to the union op.
16
+ #
17
+ # Currently, we are running a Kinesis job every 2 min and the buckets
18
+ # are being created every 10s. We could set the constant to 12
19
+ # (120/10 = 12), but to be sure that we will call union just once on
20
+ # each job, we are going to set it to 15.
21
+ MAX_BUCKETS_REDIS_UNION = 15
22
+ private_constant :MAX_BUCKETS_REDIS_UNION
23
+
24
+ attr_reader :storage
25
+
26
+ def initialize(storage)
27
+ @storage = storage
28
+ end
29
+
30
+ # For each of the buckets in the range, deletes it from the set, and
31
+ # also deletes its contents.
32
+ def delete_range(last_bucket)
33
+ buckets = storage.zrangebyscore(Keys.changed_keys_key, 0, last_bucket)
34
+
35
+ storage.pipelined do
36
+ buckets.each { |bucket| delete_bucket_content(bucket) }
37
+ storage.zremrangebyscore(Keys.changed_keys_key, 0, last_bucket)
38
+ end
39
+ end
40
+
41
+ def delete_all_buckets_and_keys(options = {})
42
+ Storage.disable!
43
+
44
+ buckets.each do |bucket|
45
+ keys = storage.smembers(Keys.changed_keys_bucket_key(bucket))
46
+ unless options[:silent]
47
+ puts "Deleting bucket: #{bucket}, containing #{keys.size} keys"
48
+ end
49
+ storage.del(Keys.changed_keys_bucket_key(bucket))
50
+ end
51
+ storage.del(Keys.changed_keys_key)
52
+ end
53
+
54
+ def buckets(first: '-inf', last: '+inf')
55
+ storage.zrangebyscore(Keys.changed_keys_key, first, last)
56
+ end
57
+
58
+ def pending_buckets_size
59
+ storage.zcard(Keys.changed_keys_key)
60
+ end
61
+
62
+ # Puts keys in a bucket. The bucket is created if it does not exist.
63
+ # We could have decided to only fill the bucket if it existed, but that
64
+ # would affect performance, because we would need to get all the
65
+ # existing buckets to check if the given one exists in every call.
66
+ def put_in_bucket(event_keys, bucket)
67
+ storage.pipelined do
68
+ storage.zadd(Keys.changed_keys_key, bucket, bucket)
69
+ storage.sadd(Keys.changed_keys_bucket_key(bucket), event_keys)
70
+ end
71
+ end
72
+
73
+ def content(buckets)
74
+ # Values are stored as strings in Redis, but we want integers.
75
+ # There are some values that can be nil. This happens when the key
76
+ # has a TTL and we read it once it has expired. Right now, event keys
77
+ # with granularity = 'minute' expire after 180 s. We might need to
78
+ # increase that to make sure that we do not miss any values.
79
+
80
+ buckets.each_slice(MAX_BUCKETS_REDIS_UNION).inject([]) do |res, buckets_slice|
81
+ bucket_keys = buckets_slice.map do |bucket|
82
+ Keys.changed_keys_bucket_key(bucket)
83
+ end
84
+ (res + storage.sunion(bucket_keys))
85
+ end.uniq
86
+ end
87
+
88
+ def pending_keys_by_bucket
89
+ bucket_keys = buckets.map do |bucket|
90
+ Keys.changed_keys_bucket_key(bucket)
91
+ end
92
+
93
+ cardinalities = storage.pipelined do
94
+ bucket_keys.map { |bucket_key| storage.scard(bucket_key) }
95
+ end
96
+
97
+ Hash[buckets.zip(cardinalities)]
98
+ end
99
+
100
+ private
101
+
102
+ def delete_bucket_content(bucket)
103
+ storage.del(Keys.changed_keys_bucket_key(bucket))
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,195 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module Stats
4
+ class Cleaner
5
+ # Design notes:
6
+ # Apisonator does not store in any Redis structure the stats keys
7
+ # associated with a service. Doing so would imply:
8
+ # - Performance hit when reporting. After updating a stats key, it
9
+ # would need to be included in a set, which would increase the number
10
+ # of operations in Redis.
11
+ # - More space in Redis. To maintain the sets mentioned in the point
12
+ # above.
13
+ # - A data migration would be needed to create those sets from the
14
+ # existing stats keys.
15
+ #
16
+ # In order to avoid those costs, this class is implemented in a way that
17
+ # does not need to keep an updated list of all the stats keys for every
18
+ # service. Services marked for deletion are stored in a Redis set, and
19
+ # then, a script, periodic cron, etc. is responsible for calling
20
+ # delete!(). That method scans the whole database and deletes all the
21
+ # stats keys that belong to those services marked to be deleted.
22
+ # The downside of this method is that it requires direct access to the
23
+ # redis servers. Going through a proxy like Twemproxy does not work,
24
+ # because it does not support the "SCAN" command.
25
+ #
26
+ # In the past we tried an alternative approach. When we received a
27
+ # request to delete the stats of a service, we generated all the
28
+ # possible stats keys that could exist for it. That approach was not
29
+ # efficient because it ended up generating many keys that didn't exist
30
+ # and thus, unnecessary delete calls to Redis. That approach is also
31
+ # more complex and error prone. You can find the details here:
32
+ # https://github.com/3scale/apisonator/issues/90
33
+
34
+ include Storable
35
+
36
+ KEY_SERVICES_TO_DELETE = 'set_with_services_marked_for_deletion'.freeze
37
+ private_constant :KEY_SERVICES_TO_DELETE
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
+ STATS_KEY_PREFIX = 'stats/'.freeze
46
+ private_constant :STATS_KEY_PREFIX
47
+
48
+ class << self
49
+ include Logging
50
+ def mark_service_to_be_deleted(service_id)
51
+ storage.sadd(KEY_SERVICES_TO_DELETE, service_id)
52
+ end
53
+
54
+ # Deletes all the stats for the services that have been marked for
55
+ # deletion.
56
+ #
57
+ # This method receives a collection of instantiated Redis clients.
58
+ # Those clients need to connect to Redis servers directly. They cannot
59
+ # connect to a proxy like Twemproxy. The reason is that this function
60
+ # needs to scan the database using the "SCAN" command, which is not
61
+ # supported by Twemproxy.
62
+ #
63
+ # The services marked as deletion will be marked as done only when
64
+ # this function finishes deleting the keys from all the Redis servers.
65
+ # This means that if the function raises in the middle of the
66
+ # execution, those services will be retried in the next call.
67
+ #
68
+ # Note 1: keys deleted cannot be restored.
69
+ # Note 2: this method can take a long time to finish as it needs to
70
+ # scan all the keys in several DBs.
71
+ #
72
+ # @param [Array] redis_conns Instantiated Redis clients.
73
+ # @param [IO] log_deleted_keys IO where to write the logs. Defaults to
74
+ # nil (logs nothing).
75
+ def delete!(redis_conns, log_deleted_keys: nil)
76
+ services = services_to_delete
77
+ logger.info("Going to delete the stats keys for these services: #{services.to_a}")
78
+
79
+ unless services.empty?
80
+ delete_successful = true
81
+ redis_conns.each do |redis_conn|
82
+ begin
83
+ delete_keys(redis_conn, services, log_deleted_keys)
84
+ # If it's a connection error, mark as failed and continue
85
+ # cleaning other shards. If it's another kind of error, it
86
+ # could be a bug, so better re-raise.
87
+ rescue Redis::BaseConnectionError, Errno::ECONNREFUSED, Errno::EPIPE => e
88
+ logger.error("Error while deleting stats of server #{redis_conn}: #{e}")
89
+ delete_successful = false
90
+ rescue Redis::CommandError => e
91
+ # Redis::CommandError from redis-rb can be raised for multiple
92
+ # reasons, so we need to check the error message to distinguish
93
+ # connection errors from the rest.
94
+ if e.message == 'ERR Connection timed out'.freeze
95
+ logger.error("Error while deleting stats of server #{redis_conn}: #{e}")
96
+ delete_successful = false
97
+ else
98
+ raise e
99
+ end
100
+ end
101
+ end
102
+
103
+ remove_services_from_delete_set(services) if delete_successful
104
+ end
105
+
106
+ logger.info("Finished deleting the stats keys for these services: #{services.to_a}")
107
+ end
108
+
109
+ private
110
+
111
+ # Returns a set with the services included in the
112
+ # SET_WITH_SERVICES_MARKED_FOR_DELETION Redis set.
113
+ def services_to_delete
114
+ res = []
115
+ cursor = 0
116
+
117
+ loop do
118
+ cursor, services = storage.sscan(
119
+ KEY_SERVICES_TO_DELETE, cursor, count: SCAN_SLICE
120
+ )
121
+
122
+ res += services
123
+
124
+ break if cursor.to_i == 0
125
+
126
+ sleep(SLEEP_BETWEEN_SCANS)
127
+ end
128
+
129
+ res.to_set
130
+ end
131
+
132
+ def delete_keys(redis_conn, services, log_deleted_keys)
133
+ cursor = 0
134
+
135
+ loop do
136
+ cursor, keys = redis_conn.scan(cursor, count: SCAN_SLICE)
137
+
138
+ to_delete = keys.select { |key| delete_key?(key, services) }
139
+
140
+ unless to_delete.empty?
141
+ if log_deleted_keys
142
+ values = redis_conn.mget(*(to_delete.to_a))
143
+ to_delete.each_with_index do |k, i|
144
+ log_deleted_keys.puts "#{k} #{values[i]}"
145
+ end
146
+ end
147
+
148
+ redis_conn.del(to_delete)
149
+ end
150
+
151
+ break if cursor.to_i == 0
152
+
153
+ sleep(SLEEP_BETWEEN_SCANS)
154
+ end
155
+ end
156
+
157
+ def remove_services_from_delete_set(services)
158
+ storage.pipelined do
159
+ services.each do |service|
160
+ storage.srem(KEY_SERVICES_TO_DELETE, service)
161
+ end
162
+ end
163
+ end
164
+
165
+ def delete_key?(key, services_to_delete)
166
+ return false unless is_stats_key?(key)
167
+
168
+ service_in_key = service_from_stats_key(key)
169
+ service_in_key && services_to_delete.include?(service_in_key)
170
+ end
171
+
172
+ def is_stats_key?(key)
173
+ # A key that starts with STATS_KEY_PREFIX is a stats key except if it
174
+ # follows this pattern: /STATS_KEY_PREFIX{service:.*}\/cinstances/. That's a
175
+ # type of key used only for the "first traffic" event
176
+ # (ApplicationEvents.first_traffic).
177
+ key.start_with?(STATS_KEY_PREFIX) && !key.match(/cinstances/)
178
+ end
179
+
180
+ # Returns nil when there's not a service encoded in the key or when
181
+ # the stats key has an invalid format.
182
+ def service_from_stats_key(stats_key)
183
+ StatsParser.parse(stats_key, nil)[:service]
184
+ rescue StatsParser::StatsKeyValueInvalid
185
+ # This could happen with legacy stats keys. For example, a long time
186
+ # ago some stats keys had a "city" and a "country" encoded, but
187
+ # always empty. That format has not been used in a long time. We'll
188
+ # simply ignore those keys.
189
+ nil
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,14 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module Stats
4
+ module CodesCommons
5
+ TRACKED_CODES = [200, 404, 403, 500, 503].freeze
6
+ TRACKED_CODE_GROUPS = ['2XX'.freeze, '4XX'.freeze, '5XX'.freeze].freeze
7
+
8
+ def self.get_http_code_group(http_code)
9
+ "#{http_code / 100}XX"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,60 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module Stats
4
+ class DeleteJobDef
5
+ ATTRIBUTES = %i[service_id applications metrics from to context_info].freeze
6
+ private_constant :ATTRIBUTES
7
+ attr_reader(*ATTRIBUTES)
8
+
9
+ def self.attribute_names
10
+ ATTRIBUTES
11
+ end
12
+
13
+ def initialize(params = {})
14
+ ATTRIBUTES.each do |key|
15
+ instance_variable_set("@#{key}".to_sym, params[key]) unless params[key].nil?
16
+ end
17
+ validate
18
+ end
19
+
20
+ def run_async
21
+ Resque.enqueue(PartitionGeneratorJob, Time.now.getutc.to_f, service_id, applications,
22
+ metrics, from, to, context_info)
23
+ end
24
+
25
+ def to_json
26
+ to_hash.to_json
27
+ end
28
+
29
+ def to_hash
30
+ Hash[ATTRIBUTES.collect { |key| [key, send(key)] }]
31
+ end
32
+
33
+ private
34
+
35
+ def validate
36
+ # from and to valid epoch times
37
+ raise_validation_error('from field not integer') unless from.is_a? Integer
38
+ raise_validation_error('from field is zero') if from.zero?
39
+ raise_validation_error('to field not integer') unless to.is_a? Integer
40
+ raise_validation_error('to field is zero') if to.zero?
41
+ raise_validation_error('from < to fields') if Time.at(to) < Time.at(from)
42
+ # application is array
43
+ raise_validation_error('applications field') unless applications.is_a? Array
44
+ raise_validation_error('applications values') unless applications.all? do |x|
45
+ x.is_a?(String) || x.is_a?(Integer)
46
+ end
47
+ # metrics is array
48
+ raise_validation_error('metrics field') unless metrics.is_a? Array
49
+ raise_validation_error('metrics values') unless metrics.all? do |x|
50
+ x.is_a?(String) || x.is_a?(Integer)
51
+ end
52
+ end
53
+
54
+ def raise_validation_error(msg)
55
+ raise DeleteServiceStatsValidationError.new(service_id, msg)
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,73 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module Stats
4
+ class KeyGenerator
5
+ attr_reader :service_id, :applications, :metrics, :from, :to
6
+
7
+ def initialize(service_id:, applications: [], metrics: [], from:, to:, **)
8
+ @service_id = service_id
9
+ @applications = applications
10
+ @metrics = metrics
11
+ @from = from
12
+ @to = to
13
+ end
14
+
15
+ def keys
16
+ response_code_service_keys +
17
+ response_code_application_keys +
18
+ usage_service_keys +
19
+ usage_application_keys
20
+ end
21
+
22
+ private
23
+
24
+ def periods(granularities)
25
+ granularities.flat_map do |granularity|
26
+ (Period[granularity].new(Time.at(from))..Period[granularity].new(Time.at(to))).to_a
27
+ end
28
+ end
29
+
30
+ def response_codes
31
+ CodesCommons::TRACKED_CODES + CodesCommons::TRACKED_CODE_GROUPS
32
+ end
33
+
34
+ def response_code_service_keys
35
+ periods(PeriodCommons::PERMANENT_SERVICE_GRANULARITIES).flat_map do |period|
36
+ response_codes.flat_map do |response_code|
37
+ Keys.service_response_code_value_key(service_id, response_code, period)
38
+ end
39
+ end
40
+ end
41
+
42
+ def response_code_application_keys
43
+ periods(PeriodCommons::PERMANENT_EXPANDED_GRANULARITIES).flat_map do |period|
44
+ response_codes.flat_map do |response_code|
45
+ applications.flat_map do |application|
46
+ Keys.application_response_code_value_key(service_id, application,
47
+ response_code, period)
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ def usage_service_keys
54
+ periods(PeriodCommons::PERMANENT_SERVICE_GRANULARITIES).flat_map do |period|
55
+ metrics.flat_map do |metric|
56
+ Keys.service_usage_value_key(service_id, metric, period)
57
+ end
58
+ end
59
+ end
60
+
61
+ def usage_application_keys
62
+ periods(PeriodCommons::PERMANENT_EXPANDED_GRANULARITIES).flat_map do |period|
63
+ metrics.flat_map do |metric|
64
+ applications.flat_map do |application|
65
+ Keys.application_usage_value_key(service_id, application, metric, period)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end