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,65 @@
1
+ module ThreeScale
2
+ module Backend
3
+
4
+ class BackgroundJob
5
+ include Configurable
6
+
7
+ EMPTY_HOOKS = [].freeze
8
+ Error = Class.new StandardError
9
+
10
+ class << self
11
+ def perform(*args)
12
+ perform_wrapper(args)
13
+ end
14
+
15
+ def perform_logged(*_args)
16
+ raise "This should be overloaded."
17
+ end
18
+
19
+ # Disable hooks to improve performance. Profiling tests show that some
20
+ # significant CPU resources were consumed sorting and filtering these.
21
+ def hooks
22
+ EMPTY_HOOKS
23
+ end
24
+
25
+ private
26
+
27
+ def enqueue_time(args)
28
+ args.last or raise('Enqueue time not specified')
29
+ end
30
+
31
+ def perform_wrapper(args)
32
+ start_time = Time.now.getutc
33
+ status_ok, message = perform_logged(*args)
34
+ stats_mem = Memoizer.stats
35
+ end_time = Time.now.getutc
36
+
37
+ raise Error, 'No job message given' unless message
38
+ prefix = log_class_name + ' ' + message
39
+
40
+ if status_ok
41
+ Worker.logger.info(prefix +
42
+ " #{(end_time - start_time).round(5)}" +
43
+ " #{(end_time.to_f - enqueue_time(args)).round(5)}"+
44
+ " #{stats_mem[:size]} #{stats_mem[:count]} #{stats_mem[:hits]}")
45
+
46
+ if configuration.worker_prometheus_metrics.enabled
47
+ update_prometheus_metrics(end_time - start_time)
48
+ end
49
+ else
50
+ Worker.logger.error("#{log_class_name} " + message)
51
+ end
52
+ end
53
+
54
+ def log_class_name
55
+ self.name.split('::').last
56
+ end
57
+
58
+ def update_prometheus_metrics(runtime)
59
+ WorkerMetrics.increase_job_count(log_class_name)
60
+ WorkerMetrics.report_runtime(log_class_name, runtime)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,20 @@
1
+ require '3scale/backend/configuration'
2
+
3
+ module ThreeScale
4
+ module Backend
5
+ # Include this into any class to provide convenient access to the configuration.
6
+ module Configurable
7
+ def self.included(base)
8
+ base.extend(self)
9
+ end
10
+
11
+ def configuration
12
+ ThreeScale::Backend.configuration
13
+ end
14
+
15
+ def configuration=(cfg)
16
+ ThreeScale::Backend.configuration=(cfg)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,151 @@
1
+ require '3scale/backend/configuration/loader'
2
+ require '3scale/backend/environment'
3
+ require '3scale/backend/configurable'
4
+
5
+ module ThreeScale
6
+ module Backend
7
+ class << self
8
+ attr_accessor :configuration
9
+
10
+ def configure
11
+ yield configuration
12
+ end
13
+
14
+ private
15
+
16
+ def parse_int(value, default)
17
+ case value
18
+ when "", nil, false then default
19
+ else Integer(value)
20
+ end
21
+ end
22
+ end
23
+
24
+ NOTIFICATION_BATCH_DEFAULT = 10000
25
+ private_constant :NOTIFICATION_BATCH_DEFAULT
26
+
27
+ CONFIG_MASTER_METRICS_TRANSACTIONS_DEFAULT = "transactions".freeze
28
+ private_constant :CONFIG_MASTER_METRICS_TRANSACTIONS_DEFAULT
29
+ CONFIG_MASTER_METRICS_TRANSACTIONS_AUTHORIZE_DEFAULT = "transactions/authorize".freeze
30
+ private_constant :CONFIG_MASTER_METRICS_TRANSACTIONS_AUTHORIZE_DEFAULT
31
+
32
+ CONFIG_DELETE_STATS_BATCH_SIZE = 50
33
+ private_constant :CONFIG_DELETE_STATS_BATCH_SIZE
34
+ CONFIG_DELETE_STATS_PARTITION_BATCH_SIZE = 1000
35
+ private_constant :CONFIG_DELETE_STATS_PARTITION_BATCH_SIZE
36
+
37
+ @configuration = Configuration::Loader.new
38
+
39
+ # assign @configuration first, since code can depend on the attr_reader
40
+ @configuration.tap do |config|
41
+ # To distinguish between SaaS and on-premises mode.
42
+ config.saas = true
43
+
44
+ config.request_loggers = [:text]
45
+ config.workers_logger_formatter = :text
46
+
47
+ # Add configuration sections
48
+ config.add_section(:queues, :master_name, :sentinels, :role,
49
+ :connect_timeout, :read_timeout, :write_timeout)
50
+ config.add_section(:redis, :url, :proxy, :sentinels, :role,
51
+ :connect_timeout, :read_timeout, :write_timeout,
52
+ :async)
53
+ config.add_section(:analytics_redis, :server,
54
+ :connect_timeout, :read_timeout, :write_timeout)
55
+ config.add_section(:hoptoad, :service, :api_key)
56
+ config.add_section(:stats, :bucket_size, :delete_batch_size, :delete_partition_batch_size)
57
+ config.add_section(:redshift, :host, :port, :dbname, :user, :password)
58
+ config.add_section(:statsd, :host, :port)
59
+ config.add_section(:internal_api, :user, :password)
60
+ config.add_section(:oauth, :max_token_size)
61
+ config.add_section(:master, :metrics)
62
+ config.add_section(:worker_prometheus_metrics, :enabled, :port)
63
+ config.add_section(:listener_prometheus_metrics, :enabled, :port)
64
+
65
+ config.add_section(
66
+ :async_worker,
67
+
68
+ # Max number of jobs in the reactor
69
+ :max_concurrent_jobs,
70
+ # Max number of jobs in memory pending to be added to the reactor
71
+ :max_pending_jobs,
72
+ # Seconds to wait before fetching more jobs when the number of jobs
73
+ # in memory has reached max_pending_jobs.
74
+ :seconds_before_fetching_more
75
+ )
76
+
77
+ # Configure nested fields
78
+ master_metrics = [:transactions, :transactions_authorize]
79
+ config.master.metrics = Struct.new(*master_metrics).new
80
+
81
+ # Default config
82
+ config.master_service_id = 1
83
+
84
+ # This setting controls whether the listener can create event buckets in
85
+ # Redis. We do not want all the listeners creating buckets yet, as we do
86
+ # not know exactly the rate at which we can send events to Kinesis
87
+ # without problems.
88
+ # By default, we will allow creating buckets in any environment that is
89
+ # not 'production'.
90
+ # Notice that in order to create buckets, you also need to execute this
91
+ # rake task: stats:buckets:enable
92
+ config.can_create_event_buckets = !production?
93
+
94
+ config.legacy_referrer_filters = false
95
+
96
+ # Load configuration from a file.
97
+ config.load!([
98
+ '/etc/3scale_backend.conf',
99
+ '~/.3scale_backend.conf',
100
+ ENV['CONFIG_FILE']
101
+ ].compact)
102
+
103
+ ## this means that there will be a NotifyJob for every X notifications (this is
104
+ ## the call to master)
105
+ config.notification_batch = parse_int(config.notification_batch,
106
+ NOTIFICATION_BATCH_DEFAULT)
107
+
108
+ # Assign default values to some configuration values
109
+ # that might been set in the config file but their
110
+ # environment variables not, or not have been set
111
+ # but should always have at least a default value.
112
+ # Also make sure that what we have is a String, just in case
113
+ # a type of data different than a String has been
114
+ # assigned to this configuration parameter
115
+ config.master.metrics.transactions = config.master.metrics.transactions.to_s
116
+ if config.master.metrics.transactions.empty?
117
+ config.master.metrics.transactions = CONFIG_MASTER_METRICS_TRANSACTIONS_DEFAULT
118
+ end
119
+ config.master.metrics.transactions_authorize = config.master.metrics.transactions_authorize.to_s
120
+ if config.master.metrics.transactions_authorize.empty?
121
+ config.master.metrics.transactions_authorize = CONFIG_MASTER_METRICS_TRANSACTIONS_AUTHORIZE_DEFAULT
122
+ end
123
+
124
+ # can_create_event_buckets is just for our SaaS analytics system.
125
+ # If SaaS has been set to false, we need to disable buckets too.
126
+ config.can_create_event_buckets = false unless config.saas
127
+
128
+ config.stats.delete_batch_size = parse_int(config.stats.delete_batch_size,
129
+ CONFIG_DELETE_STATS_BATCH_SIZE)
130
+
131
+ config.stats.delete_partition_batch_size = parse_int(config.stats.delete_partition_batch_size,
132
+ CONFIG_DELETE_STATS_PARTITION_BATCH_SIZE)
133
+
134
+ # often we don't have a log_file setting - generate it here from
135
+ # the log_path setting.
136
+ log_file = config.log_file
137
+ if !log_file || log_file.empty?
138
+ log_path = config.log_path
139
+ config.log_file = if log_path && !log_path.empty?
140
+ if File.stat(log_path).ftype == 'directory'
141
+ "#{log_path}/backend_logger.log"
142
+ else
143
+ log_path
144
+ end
145
+ else
146
+ ENV['CONFIG_LOG_FILE'] || STDOUT
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,42 @@
1
+ require 'ostruct'
2
+
3
+ module ThreeScale
4
+ module Backend
5
+ module Configuration
6
+ class Loader < OpenStruct
7
+ Error = Class.new StandardError
8
+ NoConfigFiles = Class.new Error
9
+
10
+ # Add configuration section with the given fields.
11
+ #
12
+ # == Example
13
+ #
14
+ # # Define section like this
15
+ # loader = Loader.new
16
+ # loader.add_section(:bacons, :type, :amount)
17
+ #
18
+ # # Configure default values like this
19
+ # loader.bacons.type = :chunky
20
+ # loader.bacons.amount = 'a lot'
21
+ #
22
+ # # Load the configuration from an array of files
23
+ # loader.load!(files)
24
+ #
25
+ # # Use like this
26
+ # loader.bacons.type # :chunky
27
+ #
28
+ def add_section(name, *fields)
29
+ send("#{name}=", Struct.new(*fields).new)
30
+ end
31
+
32
+ # Load configuration from a set of files
33
+ def load!(files)
34
+ raise NoConfigFiles if !files || files.empty?
35
+ files.each do |path|
36
+ load path if File.readable?(File.expand_path(path))
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,19 @@
1
+ module ThreeScale
2
+ module Backend
3
+ module Constants
4
+ module All
5
+ TIME_FORMAT = '%Y-%m-%d %H:%M:%S %z'.freeze
6
+ PIPELINED_SLICE_SIZE = 400
7
+ end
8
+
9
+ def self.included(base)
10
+ All.constants.each do |k|
11
+ base.const_set(k, All.const_get(k))
12
+ base.private_constant k
13
+ end
14
+ end
15
+ end
16
+
17
+ include Constants
18
+ end
19
+ end
@@ -0,0 +1,84 @@
1
+ # CORS support
2
+ #
3
+ # Please see references:
4
+ #
5
+ # https://www.w3.org/TR/cors/
6
+ # https://code.google.com/archive/p/html5security/wikis/CrossOriginRequestSecurity.wiki
7
+ #
8
+ module ThreeScale
9
+ module Backend
10
+ module CORS
11
+ def self.stringify_consts(*consts)
12
+ consts.each do |k|
13
+ val = const_get k
14
+ val = val.respond_to?(:join) ? val.join(', ') : val.to_s
15
+ k_s = "#{k}_S".to_sym
16
+ const_set(k_s, val.freeze)
17
+ private_constant k_s
18
+ end
19
+ end
20
+ private_class_method :stringify_consts
21
+
22
+ MAX_AGE = 86400
23
+ private_constant :MAX_AGE
24
+
25
+ ALLOW_ORIGIN = '*'.freeze
26
+ private_constant :ALLOW_ORIGIN
27
+
28
+ ALLOW_METHODS = [
29
+ 'GET'.freeze,
30
+ 'POST'.freeze,
31
+ 'PATCH'.freeze,
32
+ 'PUT'.freeze,
33
+ 'DELETE'.freeze
34
+ ].freeze
35
+ private_constant :ALLOW_METHODS
36
+
37
+ ALLOW_HEADERS = [
38
+ 'Authorization'.freeze,
39
+ 'Accept-Encoding'.freeze,
40
+ 'Content-Type'.freeze,
41
+ 'Cache-Control'.freeze,
42
+ 'Accept'.freeze,
43
+ 'If-Match'.freeze,
44
+ 'If-Modified-Since'.freeze,
45
+ 'If-None-Match'.freeze,
46
+ 'If-Unmodified-Since'.freeze,
47
+ 'X-Requested-With'.freeze,
48
+ 'X-HTTP-Method-Override'.freeze,
49
+ '3scale-options'.freeze,
50
+ ].freeze
51
+ private_constant :ALLOW_HEADERS
52
+
53
+ EXPOSE_HEADERS = [
54
+ 'ETag'.freeze,
55
+ 'Link'.freeze,
56
+ '3scale-rejection-reason'.freeze,
57
+ ].freeze
58
+ private_constant :EXPOSE_HEADERS
59
+
60
+ stringify_consts :MAX_AGE, :ALLOW_METHODS, :ALLOW_HEADERS, :EXPOSE_HEADERS
61
+
62
+ HEADERS = {
63
+ 'Access-Control-Allow-Origin'.freeze => ALLOW_ORIGIN,
64
+ 'Access-Control-Expose-Headers'.freeze => EXPOSE_HEADERS_S,
65
+ }.freeze
66
+ private_constant :HEADERS
67
+
68
+ OPTIONS_HEADERS = {
69
+ 'Access-Control-Max-Age'.freeze => MAX_AGE_S,
70
+ 'Access-Control-Allow-Methods'.freeze => ALLOW_METHODS_S,
71
+ 'Access-Control-Allow-Headers'.freeze => ALLOW_HEADERS_S,
72
+ }.merge(HEADERS).freeze
73
+ private_constant :OPTIONS_HEADERS
74
+
75
+ def self.headers
76
+ HEADERS
77
+ end
78
+
79
+ def self.options_headers
80
+ OPTIONS_HEADERS
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,67 @@
1
+ module ThreeScale
2
+ module Backend
3
+
4
+ # This class uses Redis to implement a distributed lock.
5
+ #
6
+ # To implement the distributed lock, we use the Redis operation 'set nx'.
7
+ # The locking algorithm is detailed here: http://redis.io/topics/distlock
8
+ # Basically, every time that we want to use the lock, we generate a random
9
+ # number and set a key in Redis with that random number if its current
10
+ # value is null. If we could set the value, it means that we could get the
11
+ # lock. To release it, we just need to set to delete the same key.
12
+ #
13
+ # The implementation used in this class has some limitations.
14
+ # This is limited to a single Redis instance (through Twemproxy), and if
15
+ # the master goes off the mutual exclusion basically does not exist
16
+ # anymore. But there is another thing that breaks the mutual exclusion:
17
+ # whatever we do within the lock critical section is racing against the
18
+ # TTL, and we cannot guarantee that the section will be finished within the
19
+ # limit of the TTL. It can be the case that even if we actually locked
20
+ # Redis, the TTL would have expired before we got the response (think about
21
+ # really bad network conditions or scheduling issues in the computer that
22
+ # is running the critical section). So the lock acts more as an "advisory"
23
+ # lock than a real lock: whatever we execute inside the critical section is
24
+ # "probably going to be with mutual exclusion, but no guarantees".
25
+ #
26
+ # Possible ways to minimize the window of this race condition:
27
+ # 1) Do all the work and just lock for committing.
28
+ # 2) Use large values as TTLs as much as possible.
29
+ class DistributedLock
30
+ MAX_RANDOM = 1_000_000_000
31
+ private_constant :MAX_RANDOM
32
+
33
+ def initialize(resource, ttl, storage)
34
+ @resource = resource
35
+ @ttl = ttl
36
+ @storage = storage
37
+ @random = Random.new
38
+ end
39
+
40
+ # Returns key to unlock if the lock is acquired. Nil otherwise.
41
+ def lock
42
+ key = lock_key
43
+ storage.set(lock_storage_key, key, nx: true, ex: ttl) ? key : nil
44
+ end
45
+
46
+ def unlock
47
+ storage.del(lock_storage_key)
48
+ end
49
+
50
+ def current_lock_key
51
+ storage.get(lock_storage_key)
52
+ end
53
+
54
+ private
55
+
56
+ attr_reader :resource, :ttl, :storage, :random
57
+
58
+ def lock_key
59
+ random.rand(MAX_RANDOM).to_s
60
+ end
61
+
62
+ def lock_storage_key
63
+ "#{resource.downcase}:lock".freeze
64
+ end
65
+ end
66
+ end
67
+ end