apisonator 2.100.0

Sign up to get free protection for your applications and to get access to all the features.
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'