zuora_connect 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 (106) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/Rakefile +38 -0
  4. data/app/assets/javascripts/hallway_wrapper/after.js +15 -0
  5. data/app/assets/javascripts/hallway_wrapper/before.js +2 -0
  6. data/app/assets/javascripts/zuora_connect/api/v1/app_instance.js +2 -0
  7. data/app/assets/javascripts/zuora_connect/application.js +13 -0
  8. data/app/assets/stylesheets/zuora_connect/api/v1/app_instance.css +4 -0
  9. data/app/assets/stylesheets/zuora_connect/application.css +15 -0
  10. data/app/controllers/zuora_connect/admin/tenant_controller.rb +11 -0
  11. data/app/controllers/zuora_connect/api/v1/app_instance_controller.rb +58 -0
  12. data/app/controllers/zuora_connect/application_controller.rb +8 -0
  13. data/app/controllers/zuora_connect/static_controller.rb +58 -0
  14. data/app/helpers/zuora_connect/api/v1/app_instance_helper.rb +4 -0
  15. data/app/helpers/zuora_connect/application_helper.rb +5 -0
  16. data/app/models/zuora_connect/app_instance.rb +5 -0
  17. data/app/models/zuora_connect/app_instance_base.rb +952 -0
  18. data/app/models/zuora_connect/login.rb +36 -0
  19. data/app/models/zuora_connect/telegraf.rb +93 -0
  20. data/app/views/layouts/zuora_connect/application.html.erb +14 -0
  21. data/app/views/sql/refresh_aggregate_table.txt +85 -0
  22. data/app/views/zuora_connect/static/invalid_app_instance_error.html.erb +65 -0
  23. data/app/views/zuora_connect/static/invalid_launch_request.html +65 -0
  24. data/app/views/zuora_connect/static/launch.html.erb +80 -0
  25. data/app/views/zuora_connect/static/session_error.html.erb +63 -0
  26. data/config/initializers/apartment.rb +95 -0
  27. data/config/initializers/aws.rb +2 -0
  28. data/config/initializers/elastic_apm.rb +25 -0
  29. data/config/initializers/object_method_hooks.rb +27 -0
  30. data/config/initializers/postgresql_adapter.rb +32 -0
  31. data/config/initializers/prometheus.rb +40 -0
  32. data/config/initializers/redis.rb +13 -0
  33. data/config/initializers/resque.rb +22 -0
  34. data/config/initializers/to_bool.rb +24 -0
  35. data/config/initializers/unicorn.rb +9 -0
  36. data/config/routes.rb +16 -0
  37. data/db/migrate/20100718151733_create_connect_app_instances.rb +9 -0
  38. data/db/migrate/20101024162319_add_tokens_to_app_instance.rb +6 -0
  39. data/db/migrate/20101024220705_add_token_to_app_instance.rb +5 -0
  40. data/db/migrate/20110131211919_add_sessions_table.rb +13 -0
  41. data/db/migrate/20110411200303_add_expiration_to_app_instance.rb +5 -0
  42. data/db/migrate/20110413191512_add_new_api_token.rb +5 -0
  43. data/db/migrate/20110503003602_add_catalog_data_to_app_instance.rb +6 -0
  44. data/db/migrate/20110503003603_add_catalog_mappings_to_app_instance.rb +5 -0
  45. data/db/migrate/20110503003604_catalog_default.rb +5 -0
  46. data/db/migrate/20180301052853_add_catalog_attempted_at.rb +5 -0
  47. data/db/migrate/20181206162339_add_fields_to_instance.rb +5 -0
  48. data/lib/metrics/influx/point_value.rb +79 -0
  49. data/lib/metrics/net.rb +218 -0
  50. data/lib/middleware/metrics_middleware.rb +134 -0
  51. data/lib/resque/additions.rb +53 -0
  52. data/lib/resque/dynamic_queues.rb +222 -0
  53. data/lib/resque/plugins/custom_logger.rb +46 -0
  54. data/lib/resque/self_lookup.rb +19 -0
  55. data/lib/resque/silence_done.rb +71 -0
  56. data/lib/tasks/zuora_connect_tasks.rake +24 -0
  57. data/lib/zuora_connect.rb +42 -0
  58. data/lib/zuora_connect/configuration.rb +53 -0
  59. data/lib/zuora_connect/controllers/helpers.rb +261 -0
  60. data/lib/zuora_connect/engine.rb +34 -0
  61. data/lib/zuora_connect/exceptions.rb +67 -0
  62. data/lib/zuora_connect/railtie.rb +63 -0
  63. data/lib/zuora_connect/version.rb +3 -0
  64. data/lib/zuora_connect/views/helpers.rb +9 -0
  65. data/test/controllers/zuora_connect/api/v1/app_instance_controller_test.rb +13 -0
  66. data/test/dummy/README.rdoc +28 -0
  67. data/test/dummy/Rakefile +6 -0
  68. data/test/dummy/app/assets/javascripts/application.js +13 -0
  69. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  70. data/test/dummy/app/controllers/application_controller.rb +5 -0
  71. data/test/dummy/app/helpers/application_helper.rb +2 -0
  72. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  73. data/test/dummy/bin/bundle +3 -0
  74. data/test/dummy/bin/rails +4 -0
  75. data/test/dummy/bin/rake +4 -0
  76. data/test/dummy/bin/setup +29 -0
  77. data/test/dummy/config.ru +4 -0
  78. data/test/dummy/config/application.rb +26 -0
  79. data/test/dummy/config/boot.rb +5 -0
  80. data/test/dummy/config/database.yml +25 -0
  81. data/test/dummy/config/environment.rb +5 -0
  82. data/test/dummy/config/environments/development.rb +41 -0
  83. data/test/dummy/config/environments/production.rb +79 -0
  84. data/test/dummy/config/environments/test.rb +42 -0
  85. data/test/dummy/config/initializers/assets.rb +11 -0
  86. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  87. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  88. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  89. data/test/dummy/config/initializers/inflections.rb +16 -0
  90. data/test/dummy/config/initializers/mime_types.rb +4 -0
  91. data/test/dummy/config/initializers/session_store.rb +3 -0
  92. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  93. data/test/dummy/config/locales/en.yml +23 -0
  94. data/test/dummy/config/routes.rb +4 -0
  95. data/test/dummy/config/secrets.yml +22 -0
  96. data/test/dummy/public/404.html +67 -0
  97. data/test/dummy/public/422.html +67 -0
  98. data/test/dummy/public/500.html +66 -0
  99. data/test/dummy/public/favicon.ico +0 -0
  100. data/test/fixtures/zuora_connect/app_instances.yml +11 -0
  101. data/test/integration/navigation_test.rb +8 -0
  102. data/test/lib/generators/zuora_connect/datatable_generator_test.rb +16 -0
  103. data/test/models/zuora_connect/app_instance_test.rb +9 -0
  104. data/test/test_helper.rb +21 -0
  105. data/test/zuora_connect_test.rb +7 -0
  106. metadata +443 -0
@@ -0,0 +1,218 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'set'
4
+
5
+ # Usage:
6
+ #
7
+ # require 'http_logger'
8
+ #
9
+ # == Setup logger
10
+ #
11
+ # HttpLogger.logger = Logger.new('/tmp/all.log')
12
+ # HttpLogger.log_headers = true
13
+ #
14
+ # == Do request
15
+ #
16
+ # res = Net::HTTP.start(url.host, url.port) { |http|
17
+ # http.request(req)
18
+ # }
19
+ # ...
20
+ #
21
+ # == View the log
22
+ #
23
+ # cat /tmp/all.log
24
+ class HttpLogger
25
+ class << self
26
+ attr_accessor :collapse_body_limit
27
+ attr_accessor :log_headers
28
+ attr_accessor :log_request_body
29
+ attr_accessor :log_response_body
30
+ attr_accessor :logger
31
+ attr_accessor :colorize
32
+ attr_accessor :ignore
33
+ attr_accessor :level
34
+ end
35
+
36
+ self.log_headers = false
37
+ self.log_request_body = true
38
+ self.log_response_body = true
39
+ self.colorize = true
40
+ self.collapse_body_limit = 5000
41
+ self.ignore = []
42
+ self.level = :debug
43
+
44
+ def self.perform(*args, &block)
45
+ instance.perform(*args, &block)
46
+ end
47
+
48
+ def self.instance
49
+ @instance ||= HttpLogger.new
50
+ end
51
+
52
+ def self.deprecate_config(option)
53
+ warn "Net::HTTP.#{option} is deprecated. Use HttpLogger.#{option} instead."
54
+ end
55
+
56
+ def perform(http, request, request_body)
57
+ start_time = Time.now
58
+ tags = {}
59
+ response = yield
60
+ rescue => ex
61
+ tags = tags.merge({error_type: ex.class})
62
+ raise
63
+ ensure
64
+ values = {response_time: ((Time.now - start_time)*1000).round(2)}
65
+ if require_logging?(http, request)
66
+ tags = tags.merge({endpoint: http.address, method: request.method})
67
+ log_request_url(http, request, start_time)
68
+ log_request_body(request)
69
+ log_request_headers(request)
70
+ if defined?(response) && response
71
+ tags = tags.merge({status: response.code.to_i})
72
+ log_response_code(response)
73
+ log_response_headers(response)
74
+ log_response_body(response.body)
75
+ end
76
+ ZuoraConnect::AppInstance.write_to_telegraf(direction: :outbound, tags: tags, values: values)
77
+ end
78
+ end
79
+
80
+ protected
81
+
82
+ def log_request_url(http, request, start_time)
83
+ ofset = Time.now - start_time
84
+ log("HTTP #{request.method} (%0.2fms)" % (ofset * 1000), request_url(http, request))
85
+ end
86
+
87
+ def request_url(http, request)
88
+ URI.decode("http#{"s" if http.use_ssl?}://#{http.address}:#{http.port}#{request.path}")
89
+ end
90
+
91
+ def log_request_headers(request)
92
+ if self.class.log_headers
93
+ request.each_capitalized { |k,v| log("HTTP request header", "#{k}: #{v}") }
94
+ end
95
+ end
96
+
97
+ HTTP_METHODS_WITH_BODY = Set.new(%w(POST PUT GET PATCH))
98
+
99
+ def log_request_body(request)
100
+ if self.class.log_request_body
101
+ if HTTP_METHODS_WITH_BODY.include?(request.method)
102
+ if (body = request.body) && !body.empty?
103
+ log("Request body", truncate_body(body))
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ def log_response_code(response)
110
+ log("Response status", "#{response.class} (#{response.code})")
111
+ end
112
+
113
+ def log_response_headers(response)
114
+ if self.class.log_headers
115
+ response.each_capitalized { |k,v| log("HTTP response header", "#{k}: #{v}") }
116
+ end
117
+ end
118
+
119
+ def log_response_body(body)
120
+ if self.class.log_response_body
121
+ if body.is_a?(Net::ReadAdapter)
122
+ log("Response body", "<impossible to log>")
123
+ else
124
+ if body && !body.empty?
125
+ log("Response body", truncate_body(body))
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ def require_logging?(http, request)
132
+ self.logger && !ignored?(http, request) && (http.started? || fakeweb?(http, request))
133
+ end
134
+
135
+ def ignored?(http, request)
136
+ url = request_url(http, request)
137
+ self.class.ignore.any? do |pattern|
138
+ url =~ pattern
139
+ end
140
+ end
141
+
142
+ def fakeweb?(http, request)
143
+ return false unless defined?(::FakeWeb)
144
+ uri = ::FakeWeb::Utility.request_uri_as_string(http, request)
145
+ method = request.method.downcase.to_sym
146
+ ::FakeWeb.registered_uri?(method, uri)
147
+ end
148
+
149
+ def truncate_body(body)
150
+ if collapse_body_limit && collapse_body_limit > 0 && body && body.size >= collapse_body_limit
151
+ body_piece_size = collapse_body_limit / 2
152
+ body[0..body_piece_size] +
153
+ "\n\n<some data truncated>\n\n" +
154
+ body[(body.size - body_piece_size)..body.size]
155
+ else
156
+ body
157
+ end
158
+ end
159
+
160
+ def log(message, dump)
161
+ self.logger.send(self.class.level, format_log_entry(message, dump)) if Rails.env.to_sym == :development
162
+ end
163
+
164
+ def format_log_entry(message, dump = nil)
165
+ if self.class.colorize
166
+ message_color, dump_color = "4;32;1", "0;1"
167
+ log_entry = " \e[#{message_color}m#{message}\e[0m "
168
+ log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
169
+ log_entry
170
+ else
171
+ "%s %s" % [message, dump]
172
+ end
173
+ end
174
+
175
+ def logger
176
+ self.class.logger
177
+ end
178
+
179
+ def collapse_body_limit
180
+ self.class.collapse_body_limit
181
+ end
182
+ end
183
+
184
+ class Net::HTTP
185
+
186
+ def self.log_headers=(value)
187
+ HttpLogger.deprecate_config("log_headers")
188
+ HttpLogger.log_headers = value
189
+ end
190
+
191
+ def self.colorize=(value)
192
+ HttpLogger.deprecate_config("colorize")
193
+ HttpLogger.colorize = value
194
+ end
195
+
196
+ def self.logger=(value)
197
+ HttpLogger.deprecate_config("logger")
198
+ HttpLogger.logger = value
199
+ end
200
+
201
+
202
+ alias_method :request_without_logging, :request
203
+
204
+ def request(request, body = nil, &block)
205
+ HttpLogger.perform(self, request, body) do
206
+ request_without_logging(request, body, &block)
207
+ end
208
+ end
209
+ end
210
+
211
+ if defined?(Rails)
212
+ if defined?(ActiveSupport) && ActiveSupport.respond_to?(:on_load)
213
+ # Rails3
214
+ ActiveSupport.on_load(:after_initialize) do
215
+ HttpLogger.logger = Rails.logger unless HttpLogger.logger
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,134 @@
1
+ module Middleware
2
+ require 'uri'
3
+
4
+ # Object of this class is passed to the ActiveSupport::Notification hook
5
+ class PageRequest
6
+
7
+ # This method is triggered when a non error page is loaded (not 404)
8
+ def call(name, started, finished, unique_id, payload)
9
+ # If the url contains any css or JavaScript files then do not collect metrics for them
10
+ return nil if ["css", "assets", "jpg", "png", "jpeg", "ico"].any? { |word| payload[:path].include?(word) }
11
+
12
+ # Getting the endpoint and the content_type
13
+ content_hash = {:html => "text/html", :js => "application/javascript", :json => "application/json", :csv => "text/csv"}
14
+ content_type = content_hash.key?(payload[:format]) ? content_hash[payload[:format]] : payload[:format]
15
+ content_type = content_type.to_s.gsub('text/javascript', 'application/javascript')
16
+
17
+ # payloads with 500 requests do not have status as it is not set by the controller
18
+ # https://github.com/rails/rails/issues/33335
19
+ #status_code = payload[:status] ? payload[:status] : payload[:exception_object].present? ? 500 : ""
20
+ if payload[:exception].present?
21
+ status_code, exception = [500, payload[:exception].first]
22
+ else
23
+ status_code, exception = [payload[:status], nil]
24
+ end
25
+
26
+ tags = {method: payload[:method], status: status_code, error_type: exception, content_type: content_type, controller: payload[:controller], action: payload[:action]}.compact
27
+
28
+ values = {view_time: payload[:view_runtime], db_time: payload[:db_runtime], response_time: ((finished-started)*1000)}.compact
29
+ values = values.map{ |k,v| [k,v.round(2)]}.to_h
30
+
31
+ ZuoraConnect::AppInstanceBase.write_to_telegraf(direction: :inbound, tags: tags, values: values)
32
+ end
33
+ end
34
+
35
+ class MetricsMiddleware
36
+
37
+ require "zuora_connect/version"
38
+ require "zuora_api/version"
39
+
40
+ def initialize(app)
41
+ @app = app
42
+ end
43
+
44
+ def call(env)
45
+ @bad_headers = ["HTTP_X_FORWARDED_FOR", "HTTP_X_FORWARDED_HOST", "HTTP_X_FORWARDED_PORT", "HTTP_X_FORWARDED_PROTO", "HTTP_X_FORWARDED_SCHEME", "HTTP_X_FORWARDED_SSL"]
46
+ if !ActionDispatch::Request::HTTP_METHODS.include?(env["REQUEST_METHOD"].upcase)
47
+ [405, {"Content-Type" => "text/plain"}, ["Method Not Allowed"]]
48
+ else
49
+ if (env['HTTP_ZUORA_LAYOUT_FETCH_TEMPLATE_ID'].present?)
50
+ Thread.current[:isHallway] = "/#{env['HTTP_ZUORA_LAYOUT_FETCH_TEMPLATE_ID']}"
51
+ env['PATH_INFO'] = env['PATH_INFO'].gsub(Thread.current[:isHallway], '')
52
+ env['REQUEST_URI'] = env['REQUEST_URI'].gsub(Thread.current[:isHallway], '')
53
+ env['REQUEST_PATH'] = env['REQUEST_PATH'].gsub(Thread.current[:isHallway], '')
54
+
55
+ #We need the forwarded host header to identify location of tenant
56
+ @bad_headers.delete('HTTP_X_FORWARDED_HOST')
57
+ else
58
+ Thread.current[:isHallway] = nil
59
+ end
60
+
61
+ #Remove bad headers
62
+ @bad_headers.each { |header| env.delete(header) }
63
+
64
+ #Thread.current[:appinstance] = nil
65
+ start_time = Time.now
66
+ begin
67
+ @status, @headers, @response = @app.call(env)
68
+ ensure
69
+
70
+ # If the url contains any CSS or JavaScript files then do not collect metrics for them
71
+ if ["css", "assets", "jpg", "png", "jpeg", "ico"].any? { |word| env['PATH_INFO'].include?(word) } || /.*\.js$/.match(env['PATH_INFO'])
72
+ tags = {status: @status, controller: 'ActionController', action: 'Assets', app_instance: 0}
73
+ values = {response_time: ((Time.now - start_time)*1000).round(2) }
74
+ ZuoraConnect::AppInstanceBase.write_to_telegraf(direction: 'request-inbound-assets', tags: tags, values: values)
75
+ end
76
+
77
+ if defined? Prometheus
78
+ #Prometheus Stuff
79
+ if env['PATH_INFO'] == '/connect/internal/metrics'
80
+
81
+ #Do something before each scrape
82
+ if defined? Resque.redis
83
+ begin
84
+
85
+ Resque.redis.ping
86
+
87
+ Prometheus::REDIS_CONNECTION.set({connection:'redis',name: ZuoraConnect::Telegraf.app_name},1)
88
+ Prometheus::FINISHED_JOBS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:processed])
89
+ Prometheus::PENDING_JOBS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:pending])
90
+ Prometheus::ACTIVE_WORKERS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:working])
91
+ Prometheus::WORKERS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:workers])
92
+ Prometheus::FAILED_JOBS.set({type:'resque',name: ZuoraConnect::Telegraf.app_name},Resque.info[:failed])
93
+
94
+ rescue Redis::CannotConnectError
95
+ Prometheus::REDIS_CONNECTION.set({connection:'redis',name: ZuoraConnect::Telegraf.app_name},0)
96
+ end
97
+
98
+ if ZuoraConnect.configuration.custom_prometheus_update_block != nil
99
+ ZuoraConnect.configuration.custom_prometheus_update_block.call()
100
+ end
101
+ end
102
+
103
+ end
104
+ end
105
+
106
+ # Uncomment following block of code for handling engine requests/requests without controller
107
+ # else
108
+ # # Handling requests which do not have controllers (engines)
109
+ if env["SCRIPT_NAME"].present?
110
+ controller_path = "#{env['SCRIPT_NAME'][1..-1]}"
111
+ controller_path = controller_path.sub("/", "::")
112
+ request_path = "#{controller_path}#UnknownAction"
113
+ else
114
+ # Writing to telegraf: Handle 404
115
+ if [404, 500].include?(@status)
116
+ content_type = @headers['Content-Type'].split(';')[0] if @headers['Content-Type']
117
+ content_type = content_type.gsub('text/javascript', 'application/javascript')
118
+ tags = {status: @status, content_type: content_type}
119
+
120
+ tags = tags.merge({controller: 'ActionController'})
121
+ tags = tags.merge({action: 'RoutingError' }) if @status == 404
122
+
123
+ values = {response_time: ((Time.now - start_time)*1000).round(2) }
124
+
125
+ ZuoraConnect::AppInstanceBase.write_to_telegraf(direction: :inbound, tags: tags, values: values)
126
+ end
127
+ end
128
+ Thread.current[:inbound_metric] = nil
129
+ end
130
+ [@status, @headers, @response]
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,53 @@
1
+ module Resque
2
+ module Additions
3
+ def dequeue_from(queue, klass, *args)
4
+ ####### ------ Resque Job --------
5
+ # Perform before_dequeue hooks. Don't perform dequeue if any hook returns false
6
+ before_hooks = Plugin.before_dequeue_hooks(klass).collect do |hook|
7
+ klass.send(hook, *args)
8
+ end
9
+ return if before_hooks.any? { |result| result == false }
10
+
11
+ destroyed = Job.destroy(queue, klass, *args)
12
+
13
+ Plugin.after_dequeue_hooks(klass).each do |hook|
14
+ klass.send(hook, *args)
15
+ end
16
+
17
+ destroyed
18
+ end
19
+
20
+ ####### ------ Resque Delayed Job --------
21
+ # Returns delayed jobs schedule timestamp for +klass+, +args+.
22
+ def scheduled_at_with_queue(queue, klass, *args)
23
+ search = encode(job_to_hash_with_queue(queue,klass, args))
24
+ redis.smembers("timestamps:#{search}").map do |key|
25
+ key.tr('delayed:', '').to_i
26
+ end
27
+ end
28
+
29
+ # Given an encoded item, remove it from the delayed_queue
30
+ def remove_delayed_with_queue(queue, klass, *args)
31
+ search = encode(job_to_hash_with_queue(queue,klass, args))
32
+ remove_delayed_job(search)
33
+ end
34
+
35
+ #Given a timestamp and job (klass + args) it removes all instances and
36
+ # returns the count of jobs removed.
37
+ #
38
+ # O(N) where N is the number of jobs scheduled to fire at the given
39
+ # timestamp
40
+ def remove_delayed_job_with_queue_from_timestamp(timestamp, queue, klass, *args)
41
+ return 0 if Resque.inline?
42
+
43
+ key = "delayed:#{timestamp.to_i}"
44
+ encoded_job = encode(job_to_hash_with_queue(queue, klass, args))
45
+
46
+ redis.srem("timestamps:#{encoded_job}", key)
47
+ count = redis.lrem(key, 0, encoded_job)
48
+ clean_up_timestamp(key, timestamp)
49
+
50
+ count
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,222 @@
1
+ module Resque
2
+ module DynamicQueues
3
+ def filter_busy_queues qs
4
+ busy_queues = Resque::Worker.working.map { |worker| worker.job["queue"] }.compact
5
+ Array(qs.dup).compact - busy_queues
6
+ end
7
+
8
+ def rotated_queues
9
+ @n ||= 0
10
+ @n += 1
11
+ rot_queues = queues # since we rely on the resque-dynamic-queues plugin, this is all the queues, expanded out
12
+ if rot_queues.size > 0
13
+ @n = @n % rot_queues.size
14
+ rot_queues.rotate(@n)
15
+ else
16
+ rot_queues
17
+ end
18
+ end
19
+
20
+ def queue_depth queuename
21
+ busy_queues = Resque::Worker.working.map { |worker| worker.job["queue"] }.compact
22
+ # find the queuename, count it.
23
+ busy_queues.select {|q| q == queuename }.size
24
+ end
25
+
26
+ def get_categorized_queues(queue_list)
27
+ priority_map = {"Synchronous" => 0, "High" => 1, "Medium" => 2, "Low" => 3}
28
+ categorized_queues = {}
29
+ for queue in queue_list.uniq
30
+ priority = queue.split("_")[1]
31
+ priority = "Medium" if !["Synchronous", "High", "Medium", "Low"].include?(priority)
32
+ categorized_queues[priority] ||= []
33
+ categorized_queues[priority].push(queue)
34
+ end
35
+ return categorized_queues.transform_keys{ |key| priority_map[key.to_s]}.sort
36
+ end
37
+
38
+ DEFAULT_QUEUE_DEPTH = 0
39
+ def should_work_on_queue? queuename
40
+ return true if @queues.include? '*' # workers with QUEUES=* are special and are not subject to queue depth setting
41
+ max = DEFAULT_QUEUE_DEPTH
42
+ unless ENV["RESQUE_QUEUE_DEPTH"].nil? || ENV["RESQUE_QUEUE_DEPTH"] == ""
43
+ max = ENV["RESQUE_QUEUE_DEPTH"].to_i
44
+ end
45
+ return true if max == 0 # 0 means no limiting
46
+ cur_depth = queue_depth(queuename)
47
+ log! "queue #{queuename} depth = #{cur_depth} max = #{max}"
48
+ return true if cur_depth < max
49
+ false
50
+ end
51
+
52
+ def get_grouped_queues
53
+ self.queues.sort.group_by{|u| /(\d{1,20})_.*/.match(u) ? /(\d{1,20})_.*/.match(u).captures.first : nil}
54
+ end
55
+
56
+ def reserve_with_round_robin
57
+ grouped_queues = self.get_grouped_queues
58
+
59
+ #Instance queue grouping
60
+ if !grouped_queues.keys.include?(nil) && grouped_queues.keys.size > 0
61
+ if ZuoraConnect.configuration.blpop_queue
62
+ @job_in_progress = get_restricted_job
63
+ return @job_in_progress if @job_in_progress.present?
64
+ return @job_in_progress = get_queued_job(grouped_queues)
65
+ else
66
+ @n ||= 0
67
+ @n += 1
68
+ @n = @n % grouped_queues.keys.size
69
+ grouped_queues.keys.rotate(@n).each do |key|
70
+ self.get_categorized_queues(grouped_queues[key]).each do |key, queues|
71
+ queues.each do |queue|
72
+ log! "Checking #{queue}"
73
+ if should_work_on_queue?(queue) && @job_in_progress = Resque::Job.reserve(queue)
74
+ log! "Found job on #{queue}"
75
+ return @job_in_progress
76
+ end
77
+ end
78
+ end
79
+ @n += 1 # Start the next search at the queue after the one from which we pick a job.
80
+ end
81
+ nil
82
+ end
83
+ else
84
+ return reserve_without_round_robin
85
+ end
86
+
87
+ rescue Exception => e
88
+ log "Error reserving job: #{e.inspect}"
89
+ log e.backtrace.join("\n")
90
+ raise e
91
+ end
92
+
93
+ def create_job(queue, payload)
94
+ return unless payload
95
+ Resque::Job.new(queue, payload)
96
+ end
97
+
98
+ def get_next_job(grouped_queues)
99
+ @n ||= 1
100
+ queue_index = {}
101
+ grouped_queues.each_with_index do |(key, queue_list), index|
102
+ queue_list.each do |queue|
103
+ queue_index[queue] = index
104
+ end
105
+ end
106
+
107
+ grouped_queues = grouped_queues.values.rotate(@n).map{|queue_list| get_categorized_queues(queue_list).to_h.values.flatten}.flatten.delete_if{|queue| !should_work_on_queue?(queue)}.map{|queue| "queue:#{queue}"}
108
+ queue, payload = Resque.redis.blpop(grouped_queues, :timeout => (ENV["BLPOP_TIMEOUT"].to_i || 30))
109
+ return nil if queue.blank?
110
+
111
+ queue = queue.split("queue:")[1]
112
+ @n = queue_index[queue] + 1
113
+ return create_job(queue, Resque.decode(payload))
114
+ end
115
+
116
+ def get_restricted_job
117
+ Resque::Plugins::ConcurrentRestrictionJob.next_runnable_job_random
118
+ end
119
+
120
+ def get_queued_job(grouped_queues)
121
+ if defined?(Resque::Plugins::ConcurrentRestriction)
122
+ # Bounded retry
123
+ 1.upto(Resque::Plugins::ConcurrentRestriction.reserve_queued_job_attempts) do |i|
124
+ resque_job = get_next_job(grouped_queues)
125
+
126
+ # Short-curcuit if a job was not found
127
+ return if resque_job.nil?
128
+
129
+ # If there is a job on regular queues, then only run it if its not restricted
130
+ job_class = resque_job.payload_class
131
+ job_args = resque_job.args
132
+
133
+ # Return to work on job if not a restricted job
134
+ return resque_job unless job_class.is_a?(Resque::Plugins::ConcurrentRestriction)
135
+
136
+ # Keep trying if job is restricted. If job is runnable, we keep the lock until
137
+ # done_working
138
+ return resque_job unless job_class.stash_if_restricted(resque_job)
139
+ end
140
+
141
+ # Safety net, here in case we hit the upper bound and there are still queued items
142
+ return nil
143
+ else
144
+ return get_next_job(grouped_queues)
145
+ end
146
+ end
147
+
148
+ # Returns a list of queues to use when searching for a job.
149
+ #
150
+ # A splat ("*") means you want every queue (in alpha order) - this
151
+ # can be useful for dynamically adding new queues.
152
+ #
153
+ # The splat can also be used as a wildcard within a queue name,
154
+ # e.g. "*high*", and negation can be indicated with a prefix of "!"
155
+ #
156
+ # An @key can be used to dynamically look up the queue list for key from redis.
157
+ # If no key is supplied, it defaults to the worker's hostname, and wildcards
158
+ # and negations can be used inside this dynamic queue list. Set the queue
159
+ # list for a key with Resque.set_dynamic_queue(key, ["q1", "q2"]
160
+ #
161
+ def queues_with_dynamic
162
+ queue_names = @queues.dup
163
+
164
+ return queues_without_dynamic if queue_names.grep(/(^!)|(^@)|(\*)/).size == 0
165
+
166
+ real_queues = Resque.queues
167
+ matched_queues = []
168
+
169
+ #Remove Queues under Api Limits
170
+ Redis.current.zremrangebyscore("APILimits", "0", "(#{Time.now.to_i}")
171
+ api_limit_instances = Redis.current.zrange("APILimits", 0, -1).map {|key| key.to_i if key.match(/^\d*$/)}.compact
172
+ real_queues = real_queues.select {|key| key if !api_limit_instances.include?((key.match(/^(\d*)_.*/) || [])[1].to_i)} ## 2
173
+
174
+ #Queue Pausing
175
+ Resque.redis.zremrangebyscore("PauseQueue", "0", "(#{Time.now.to_i}")
176
+ paused_instances = Resque.redis.zrange("PauseQueue", 0, -1).map {|key| key.split("__")[0].to_i if key.match(/^\d*__.*/)}.compact
177
+ real_queues = real_queues.select {|key| key if !paused_instances.include?((key.match(/^(\d*)_.*/) || [])[1].to_i)}
178
+
179
+ while q = queue_names.shift
180
+ q = q.to_s
181
+
182
+ if q =~ /^(!)?@(.*)/
183
+ key = $2.strip
184
+ key = hostname if key.size == 0
185
+
186
+ add_queues = Resque.get_dynamic_queue(key)
187
+ add_queues.map! { |q| q.gsub!(/^!/, '') || q.gsub!(/^/, '!') } if $1
188
+
189
+ queue_names.concat(add_queues)
190
+ next
191
+ end
192
+
193
+ if q =~ /^!/
194
+ negated = true
195
+ q = q[1..-1]
196
+ end
197
+
198
+ patstr = q.gsub(/\*/, '.*')
199
+ pattern = /^#{patstr}$/
200
+ if negated
201
+ matched_queues -= matched_queues.grep(pattern)
202
+ else
203
+ matches = real_queues.grep(/^#{pattern}$/)
204
+ matches = [q] if matches.size == 0 && q == patstr
205
+ matched_queues.concat(matches.sort)
206
+ end
207
+ end
208
+
209
+ return matched_queues.uniq
210
+ end
211
+
212
+
213
+ def self.included(receiver)
214
+ receiver.class_eval do
215
+ alias queues_without_dynamic queues
216
+ alias queues queues_with_dynamic
217
+ alias reserve_without_round_robin reserve
218
+ alias reserve reserve_with_round_robin
219
+ end
220
+ end
221
+ end
222
+ end