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,277 @@
1
+ raise "Memoizer is not thread safe" if ThreeScale::Backend::Manifest.thread_safe?
2
+
3
+ module ThreeScale
4
+ module Backend
5
+ class Memoizer
6
+ EXPIRE = 60
7
+ PURGE = 60
8
+ MAX_ENTRIES = 10000
9
+ ACTIVE = true
10
+ private_constant :EXPIRE, :PURGE, :MAX_ENTRIES, :ACTIVE
11
+
12
+ def self.reset!
13
+ # Initialize class instance variables
14
+ # Note: we would be better off pre-allocating the Hash size
15
+ # ie. rb_hash_new_with_size(MAX_ENTRIES)
16
+ @memoizer_cache = Hash.new
17
+ @memoizer_purge_time = Time.now.getutc.to_i + PURGE
18
+ @memoizer_stats_count = 0
19
+ @memoizer_stats_hits = 0
20
+ end
21
+
22
+ reset!
23
+
24
+ # Key management:
25
+ #
26
+ # When automatically memoizing using the decorator, you should
27
+ # NEVER assume a specific key format. Here are some helpers to
28
+ # let you build keys to clear or getting them.
29
+ #
30
+ # You should only use build_keys_for_class and build_key
31
+ #
32
+
33
+ private
34
+
35
+ def self.build_class_key(klass)
36
+ classkey = klass.to_s
37
+ # This can receive both objects and classes, so check if we can
38
+ # call singleton_class? before actually doing so.
39
+ if klass.respond_to? :singleton_class? and klass.singleton_class?
40
+ # obtain class from Ruby's metaclass notation
41
+ classkey.split(':'.freeze).delete_if do |k|
42
+ k[0] == '#'.freeze
43
+ end.join(':'.freeze).split('>'.freeze).first
44
+ else
45
+ classkey
46
+ end
47
+ end
48
+
49
+ def self.build_method_key(classkey, methodname)
50
+ classkey + '.'.freeze + methodname
51
+ end
52
+
53
+ def self.build_args_key(methodkey, *args)
54
+ if args.empty?
55
+ methodkey
56
+ else
57
+ methodkey + '-'.freeze + args.join('-'.freeze)
58
+ end
59
+ end
60
+
61
+ # A method to inspect in debugging mode the contents of the cache
62
+ def self.cache
63
+ raise 'Memoizer.cache only in development!' unless ThreeScale::Backend.development?
64
+ @memoizer_cache
65
+ end
66
+
67
+ public
68
+
69
+ # Generate a key for the given class, method and args
70
+ def self.build_key(klass, method, *args)
71
+ key = build_class_key klass
72
+ key = build_method_key key, method.to_s
73
+ build_args_key key, *args
74
+ end
75
+
76
+ # Pass in the class or object that receives the memoized
77
+ # methods, and a hash containing the methods as keys and
78
+ # an array of their arguments in order.
79
+ def self.build_keys_for_class(klass, methods_n_args)
80
+ classkey = build_class_key klass
81
+ methods_n_args.map do |method, args|
82
+ key = build_method_key(classkey, method.to_s)
83
+ build_args_key key, *args
84
+ end
85
+ end
86
+
87
+ if ACTIVE
88
+ Entry = Struct.new(:obj, :expire)
89
+ private_constant :Entry
90
+
91
+ def self.fetch(key)
92
+ @memoizer_stats_count = @memoizer_stats_count + 1
93
+
94
+ cached = @memoizer_cache[key]
95
+
96
+ now = Time.now.getutc.to_i
97
+ purge(now) if now > @memoizer_purge_time
98
+
99
+ if cached && now <= cached.expire
100
+ @memoizer_stats_hits = @memoizer_stats_hits + 1
101
+ cached
102
+ end
103
+ end
104
+
105
+ def self.memoize(key, obj)
106
+ @memoizer_cache[key] = Entry.new(obj, Time.now.getutc.to_i + EXPIRE)
107
+ obj
108
+ end
109
+ else
110
+ def self.fetch(_key)
111
+ nil
112
+ end
113
+
114
+ def self.memoize(_key, obj)
115
+ obj
116
+ end
117
+ end
118
+
119
+ def self.get(key)
120
+ entry = @memoizer_cache[key]
121
+ entry.obj if entry
122
+ end
123
+
124
+ def self.memoized?(key)
125
+ !!(fetch key)
126
+ end
127
+
128
+ def self.clear(keys)
129
+ Array(keys).each do |key|
130
+ @memoizer_cache.delete key
131
+ end
132
+ end
133
+
134
+ def self.purge(time)
135
+ @memoizer_purge_time = time + PURGE
136
+
137
+ @memoizer_cache.delete_if do |_, entry|
138
+ time > entry.expire
139
+ end
140
+
141
+ ##safety, should never reach this unless massive concurrency
142
+ reset! if @memoizer_cache.size > MAX_ENTRIES
143
+ end
144
+
145
+ def self.stats
146
+ {
147
+ size: @memoizer_cache.size,
148
+ count: @memoizer_stats_count,
149
+ hits: @memoizer_stats_hits,
150
+ }
151
+ end
152
+
153
+ def self.memoize_block(key, &block)
154
+ entry = fetch key
155
+ if entry.nil?
156
+ Memoizer.memoize(key, yield)
157
+ else
158
+ entry.obj
159
+ end
160
+ end
161
+
162
+ # Decorator allows a class or module to include it and get
163
+ # memoize :method1, :method2, ...
164
+ # using keys "#{classname}.#{methodname}-#{arg1}-#{arg2}-..."
165
+ module Decorator
166
+ def self.included(base)
167
+ base.extend(ClassMethods)
168
+ end
169
+
170
+ module ClassMethods
171
+ module Helpers
172
+ module_function
173
+
174
+ def memoize_instance_method(m, klass)
175
+ method_name = m.name
176
+ method_s = method_name.to_s
177
+ klass.send :define_method, method_name do |*args|
178
+ key = Memoizer.build_method_key self.to_s, method_s
179
+ key = Memoizer.build_args_key key, *args
180
+ Memoizer.memoize_block(key) do
181
+ m.bind(self).call(*args)
182
+ end
183
+ end
184
+ end
185
+ private :memoize_instance_method
186
+
187
+ def memoize_class_method(m, partialkey, klass)
188
+ klass.define_singleton_method(m.name) do |*args|
189
+ key = Memoizer.build_args_key partialkey, *args
190
+ Memoizer.memoize_block(key) do
191
+ m.call(*args)
192
+ end
193
+ end
194
+ end
195
+ private :memoize_class_method
196
+
197
+ # helper to go down one level from the current class context
198
+ # ie. the reverse of singleton_class: from metaclass to class
199
+ def get_instance_class(klass)
200
+ return klass unless klass.singleton_class?
201
+ # workaround Ruby's lack of the inverse of singleton_class
202
+ base_s = klass.to_s.split(':').delete_if { |k| k.start_with? '#' }.
203
+ join(':').split('>').first
204
+ iklass = Kernel.const_get(base_s)
205
+ # got the root class, now go up a level shy of self
206
+ iklass = iklass.singleton_class while iklass.singleton_class != klass
207
+ iklass
208
+ end
209
+ private :get_instance_class
210
+ end
211
+
212
+ # memoize :method, :other_method, ...
213
+ #
214
+ # Decorate the methods passed in memoizing their results based
215
+ # on the parameters they receive using a key with the form:
216
+ # (ClassName|Instance).methodname[-param1[-param2[-...]]]
217
+ #
218
+ # You can call this from a class on instance or class methods, and
219
+ # from a metaclass on instance methods, which actually are class
220
+ # methods.
221
+ #
222
+ # CAVEAT: if you have an instance method named exactly as an existing
223
+ # class method you either memoize the instance method BEFORE defining
224
+ # the class method or use memoize_i on the instance method.
225
+ #
226
+ # WARNING: do NOT use this memoize method on frequently called instance
227
+ # methods since you'll have a noticeable overhead from the necessity
228
+ # to bind the method to the object.
229
+ #
230
+ def memoize(*methods)
231
+ classkey = Memoizer.build_class_key self
232
+ # get the base class of self so that we get rid of metaclasses
233
+ klass = Helpers.get_instance_class self
234
+ # make sure klass points to the klass that self is a metaclass of
235
+ # in case we're being invoked from a metaclass
236
+ methods.each do |m|
237
+ # For each method, first search for a class method, which is the
238
+ # common case. If not found, then look for an instance method.
239
+ #
240
+ begin
241
+ key = Memoizer.build_method_key(classkey, m.to_s)
242
+ original_method = klass.method m
243
+ raise NameError unless original_method.owner == klass.singleton_class
244
+ Helpers.memoize_class_method original_method, key, klass
245
+ rescue NameError
246
+ # If we cannot find a class method, try an instance method
247
+ # before bailing out.
248
+ memoize_i m
249
+ end
250
+ end
251
+ end
252
+
253
+ # memoize_i :method, :other_method
254
+ #
255
+ # Forces memoization of methods which are instance methods in
256
+ # the current context level. This can be used to, for example,
257
+ # override the default look up when we have two methods, class
258
+ # and instance, which are named the same and we want to memoize
259
+ # both or only the instance level one.
260
+ def memoize_i(*methods)
261
+ methods.each do |m|
262
+ # We don't support calling this from a metaclass, because
263
+ # we have not built a correct key. The user should use memoize
264
+ # instead of this, which will already work with the instance
265
+ # methods within a metaclass, that is, the class' class methods.
266
+ raise NameError if singleton_class?
267
+ original_method = instance_method m
268
+ raise NameError unless original_method.owner == self
269
+ Helpers.memoize_instance_method original_method, self
270
+ end
271
+ end
272
+ end
273
+
274
+ end
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,275 @@
1
+ require 'set'
2
+
3
+ module ThreeScale
4
+ module Backend
5
+ class Metric
6
+ module KeyHelpers
7
+ def key(service_id, id, attribute)
8
+ encode_key("metric/service_id:#{service_id}/id:#{id}/#{attribute}")
9
+ end
10
+
11
+ def id_key(service_id, name)
12
+ encode_key("metric/service_id:#{service_id}/name:#{name}/id")
13
+ end
14
+
15
+ def id_set_key(service_id)
16
+ encode_key("metrics/service_id:#{service_id}/ids")
17
+ end
18
+ end
19
+
20
+ include KeyHelpers
21
+ extend KeyHelpers
22
+
23
+ include Storable
24
+
25
+ attr_accessor :service_id, :id, :parent_id, :name
26
+ attr_writer :children
27
+
28
+ def save
29
+ old_name = self.class.load_name(service_id, id)
30
+ storage.pipelined do
31
+ save_attributes
32
+ save_to_list
33
+ remove_reverse_mapping(service_id, old_name) if old_name != name
34
+ end
35
+
36
+ # can't include this in the pipeline since it is a potentially
37
+ # large number of commands.
38
+ save_children
39
+
40
+ self.class.clear_cache(service_id, id, name)
41
+ end
42
+
43
+ def children
44
+ @children ||= []
45
+ end
46
+
47
+ def to_hash
48
+ {
49
+ service_id: service_id,
50
+ id: id,
51
+ parent_id: parent_id,
52
+ name: name
53
+ }
54
+ end
55
+
56
+ def update(attributes)
57
+ attributes.each do |attr, val|
58
+ public_send("#{attr}=", val)
59
+ end
60
+ self
61
+ end
62
+
63
+ class << self
64
+ include Memoizer::Decorator
65
+
66
+ def attribute_names
67
+ %i[service_id id parent_id name children].freeze
68
+ end
69
+
70
+ def load(service_id, id)
71
+ name, parent_id = storage.mget(key(service_id, id, :name),
72
+ key(service_id, id, :parent_id))
73
+
74
+ name && new(id: id.to_s,
75
+ service_id: service_id.to_s,
76
+ name: name,
77
+ parent_id: parent_id)
78
+ end
79
+
80
+ def load_all(service_id)
81
+ Collection.new(service_id)
82
+ end
83
+ memoize :load_all
84
+
85
+ def load_id(service_id, name)
86
+ storage.get(id_key(service_id, name))
87
+ end
88
+ memoize :load_id
89
+
90
+ def load_all_ids(service_id)
91
+ # smembers is guaranteed to return an array of strings, even if empty
92
+ storage.smembers(id_set_key(service_id))
93
+ end
94
+ memoize :load_all_ids
95
+
96
+ def load_name(service_id, id)
97
+ storage.get(key(service_id, id, :name))
98
+ end
99
+ memoize :load_name
100
+
101
+ def load_all_names(service_id, ids)
102
+ if ids.nil? || ids.empty?
103
+ {}
104
+ else
105
+ name_keys = ids.map { |id| key(service_id, id, :name) }
106
+ Hash[ids.zip(storage.mget(name_keys))]
107
+ end
108
+ end
109
+ memoize :load_all_names
110
+
111
+ def load_parent_id(service_id, id)
112
+ storage.get(key(service_id, id, :parent_id))
113
+ end
114
+ memoize :load_parent_id
115
+
116
+ def save(attributes)
117
+ metrics = new(attributes)
118
+ metrics.save
119
+ metrics
120
+ end
121
+
122
+ # Returns a hash where the keys can only be parent metric ids (as
123
+ # Strings) and their values are arrays of children.
124
+ #
125
+ # The as_names optional parameter (default: true) returns metric names
126
+ # instead of metric ids when true.
127
+ def hierarchy(service_id, as_names = true)
128
+ h_ids = hierarchy_ids(service_id)
129
+ return h_ids unless as_names
130
+
131
+ metric_ids = Set.new(h_ids.keys + h_ids.values.flatten)
132
+ return {} if metric_ids.empty?
133
+
134
+ res = {}
135
+ metric_names = load_all_names(service_id, metric_ids)
136
+
137
+ h_ids.each do |m_id, c_ids|
138
+ m_name = metric_names[m_id]
139
+ res[m_name] = c_ids.map do |c_id|
140
+ metric_names[c_id]
141
+ end
142
+ end
143
+
144
+ res
145
+ end
146
+ memoize :hierarchy
147
+
148
+ def children(service_id, id)
149
+ hierarchy(service_id, false)[id.to_s]
150
+ end
151
+
152
+ # Returns the "descendants" of a metric, that is, its children,
153
+ # grandchildren, etc. in the metric hierarchy of the given service.
154
+ # In other words, the "descendants" of a metric are its children plus
155
+ # the descendants of each of them.
156
+ def descendants(service_id, metric_name)
157
+ metrics_hierarchy = hierarchy(service_id)
158
+ children = metrics_hierarchy[metric_name] || []
159
+
160
+ children.reduce(children) do |acc, child|
161
+ acc + descendants(service_id, child)
162
+ end
163
+ end
164
+ memoize :descendants
165
+
166
+ # Returns the "ascendants" of a metric, that is, its parent,
167
+ # grandparent, etc. The "ascendants" of a metric are its parent plus
168
+ # the ascendants of the parent.
169
+ def ascendants(service_id, metric_name)
170
+ parents_of_metric = parents(service_id, [metric_name])
171
+
172
+ parents_of_metric.reduce(parents_of_metric) do |acc, parent|
173
+ acc + ascendants(service_id, parent)
174
+ end
175
+ end
176
+ memoize :ascendants
177
+
178
+ # Given an array of metrics, returns an array without duplicates that
179
+ # includes the names of the metrics that are parent of at least one of
180
+ # the given metrics.
181
+ def parents(service_id, metric_names)
182
+ parents = []
183
+
184
+ metric_names.each do |name|
185
+ parent_id = load_parent_id service_id, load_id(service_id, name)
186
+ if parent_id
187
+ parents << load_name(service_id, parent_id)
188
+ end
189
+ end
190
+
191
+ parents.uniq
192
+ end
193
+
194
+ def delete(service_id, id)
195
+ name = load_name(service_id, id)
196
+ return false unless name and not name.empty?
197
+ clear_cache(service_id, id, name)
198
+
199
+ storage.pipelined do
200
+ storage.srem(id_set_key(service_id), id)
201
+
202
+ storage.del(key(service_id, id, :name),
203
+ key(service_id, id, :parent_id),
204
+ id_key(service_id, name))
205
+ end
206
+
207
+ true
208
+ end
209
+
210
+ def clear_cache(service_id, id, name)
211
+ metric_ids = load_all_ids(service_id)
212
+ Memoizer.clear(Memoizer.build_keys_for_class(self,
213
+ load_all: [service_id],
214
+ load_all_names: [service_id, metric_ids],
215
+ load_name: [service_id, id],
216
+ load_id: [service_id, name],
217
+ load_all_ids: [service_id]))
218
+ end
219
+
220
+ private
221
+
222
+ def hierarchy_ids(service_id)
223
+ ids = load_all_ids(service_id)
224
+ parent_ids_keys = ids.map { |id| key(service_id, id, :parent_id) }
225
+
226
+ parent_ids = storage.pipelined do
227
+ parent_ids_keys.each_slice(PIPELINED_SLICE_SIZE).map do |slice|
228
+ storage.mget(slice)
229
+ end
230
+ end.flatten
231
+
232
+ parent_child_rels = parent_ids.zip(ids)
233
+
234
+ parent_child_rels.inject({}) do |acc, (parent_id, child_id)|
235
+ if parent_id # nil if child_id has no parent
236
+ acc[parent_id] ||= []
237
+ acc[parent_id] << child_id
238
+ end
239
+ acc
240
+ end
241
+ end
242
+ end
243
+
244
+ private
245
+
246
+ def remove_reverse_mapping(service_id, name)
247
+ storage.del id_key(service_id, name)
248
+ end
249
+
250
+ def save_attributes
251
+ storage.set(id_key(service_id, name), id)
252
+ storage.set(key(service_id, id, :name), name)
253
+ storage.set(key(service_id, id, :parent_id), parent_id) if parent_id
254
+ end
255
+
256
+ def save_to_list
257
+ storage.sadd(id_set_key(service_id), id)
258
+ end
259
+
260
+ def save_children
261
+ children.each do |child|
262
+ child.service_id = service_id
263
+ child.parent_id = id
264
+ child.save
265
+ end
266
+ end
267
+
268
+ end
269
+ end
270
+ end
271
+
272
+ # this is required by our code, but it is nested inside class Metric
273
+ # require'ing it here ensures we always reopen the class instead of
274
+ # defining it.
275
+ require '3scale/backend/metric/collection'