web47core 0.0.10 → 0.1.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -1
- data/bin/cron_server +6 -0
- data/lib/app/jobs/application_job.rb +23 -0
- data/lib/app/jobs/cron/command.rb +79 -0
- data/lib/app/jobs/cron/job.rb +31 -0
- data/lib/app/jobs/cron/job_tab.rb +126 -0
- data/lib/app/jobs/cron/server.rb +285 -0
- data/lib/app/jobs/cron/switchboard_sync_configuration.rb +66 -0
- data/lib/app/jobs/cron/switchboard_sync_models.rb +25 -0
- data/lib/app/jobs/cron/tab.rb +111 -0
- data/lib/app/jobs/cron/trim_collection.rb +36 -0
- data/lib/app/jobs/cron/trim_cron_servers.rb +22 -0
- data/lib/app/jobs/cron/trim_failed_delayed_jobs.rb +40 -0
- data/lib/app/jobs/cron/trim_notifications.rb +24 -0
- data/lib/app/jobs/cron/worker.rb +70 -0
- data/lib/app/models/concerns/switchboard_able.rb +130 -0
- data/lib/app/models/delayed_job.rb +80 -0
- data/lib/templates/slack/failed_delayed_job.liquid +7 -0
- data/lib/web47core.rb +32 -0
- data/test/jobs/cron/switchboard_sync_configuration_test.rb +64 -0
- data/test/jobs/cron/trim_cron_servers_test.rb +28 -0
- data/test/jobs/cron/trim_failed_delayed_jobs_test.rb +71 -0
- data/test/models/job_cron_tab_test.rb +25 -0
- data/test/models/web47core_test.rb +18 -0
- data/web47core.gemspec +4 -1
- metadata +47 -5
- /data/test/models/{app47_logger_test.rb → concerns/app47_logger_test.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b3949ac1edae0d03528d687d4eb39024813fc8fa98325d0407a982800c68ff77
|
4
|
+
data.tar.gz: 250122ca58e430a2a366f3adebba47c7c83e17061a5946690fb8010ccbcc293c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 74c4529df3ec5931a3b7e1d9f53123253dca0beddf26612db997e08d5ce1950897e3306cf5d10ff56d0be1ee581e077915d9d7a8b141765fcce018b5ee4b856b
|
7
|
+
data.tar.gz: 4b9b67895fba934193b72badc6a4da20d7dce717dd53169152a0445a581278564a027ea42e4b56c3a1f0b97417160e090632ba0f4aabe6a52159c0d26221ed06
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
web47core (0.0
|
4
|
+
web47core (0.1.0)
|
5
5
|
activesupport (~> 5.0)
|
6
6
|
aws-sdk-ec2 (> 1.140, <= 1.160)
|
7
|
+
daemons
|
7
8
|
delayed_job_mongoid (~> 2.3)
|
8
9
|
email_format
|
9
10
|
haml
|
@@ -92,6 +93,7 @@ GEM
|
|
92
93
|
crack (0.4.3)
|
93
94
|
safe_yaml (~> 1.0.0)
|
94
95
|
crass (1.0.6)
|
96
|
+
daemons (1.3.1)
|
95
97
|
database_cleaner (1.8.3)
|
96
98
|
delayed_job (4.1.8)
|
97
99
|
activesupport (>= 3.0, < 6.1)
|
data/bin/cron_server
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Base application job that all jobs extend from
|
5
|
+
#
|
6
|
+
class ApplicationJob < ActiveJob::Base
|
7
|
+
include App47Logger
|
8
|
+
|
9
|
+
#
|
10
|
+
# If this job should run in this current environment, defaults to true
|
11
|
+
#
|
12
|
+
def self.valid_environments
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
|
16
|
+
#
|
17
|
+
# Is this job in a valid environment
|
18
|
+
#
|
19
|
+
def self.valid_environment?
|
20
|
+
my_environments = valid_environments
|
21
|
+
(my_environments.empty? || my_environments.include?(Rails.env))
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
unless ENV['RAILS_ENV'] == 'test'
|
2
|
+
begin
|
3
|
+
require 'daemons'
|
4
|
+
rescue LoadError
|
5
|
+
raise "You need to add gem 'daemons' to your Gemfile if you wish to use it."
|
6
|
+
end
|
7
|
+
end
|
8
|
+
require 'fileutils'
|
9
|
+
require 'optparse'
|
10
|
+
require 'pathname'
|
11
|
+
|
12
|
+
module Cron
|
13
|
+
class Command # rubocop:disable ClassLength
|
14
|
+
|
15
|
+
DIR_PWD = Pathname.new Dir.pwd
|
16
|
+
|
17
|
+
def initialize(args) # rubocop:disable MethodLength
|
18
|
+
@options = { pid_dir: "#{root}/tmp/pids", log_dir: "#{root}/log", monitor: false }
|
19
|
+
|
20
|
+
opts = OptionParser.new do |opt|
|
21
|
+
opt.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options] start|stop|restart|run"
|
22
|
+
|
23
|
+
opt.on('-h', '--help', 'Show this message') do
|
24
|
+
puts opt
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
opt.on('-e', '--environment=NAME', 'Specifies the environment to run this delayed jobs under (test/development/production).') do |_e|
|
28
|
+
STDERR.puts 'The -e/--environment option has been deprecated and has no effect. Use RAILS_ENV and see http://github.com/collectiveidea/delayed_job/issues/7'
|
29
|
+
end
|
30
|
+
opt.on('--pid-dir=DIR', 'Specifies an alternate directory in which to store the process ids.') do |dir|
|
31
|
+
@options[:pid_dir] = dir
|
32
|
+
end
|
33
|
+
opt.on('--log-dir=DIR', 'Specifies an alternate directory in which to store the delayed_job log.') do |dir|
|
34
|
+
@options[:log_dir] = dir
|
35
|
+
end
|
36
|
+
opt.on('-m', '--monitor', 'Start monitor process.') do
|
37
|
+
@options[:monitor] = true
|
38
|
+
end
|
39
|
+
opt.on('--exit-on-complete', 'Exit when no more jobs are available to run. This will exit if all jobs are scheduled to run in the future.') do
|
40
|
+
@options[:exit_on_complete] = true
|
41
|
+
end
|
42
|
+
opt.on('--daemon-options a, b, c', Array, 'options to be passed through to daemons gem') do |daemon_options|
|
43
|
+
@daemon_options = daemon_options
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@args = opts.parse!(args) + (@daemon_options || [])
|
47
|
+
end
|
48
|
+
|
49
|
+
def daemonize # rubocop:disable PerceivedComplexity
|
50
|
+
dir = @options[:pid_dir]
|
51
|
+
FileUtils.mkdir_p(dir) unless File.exist?(dir)
|
52
|
+
|
53
|
+
Cron::JobTab.ensure_cron_tabs
|
54
|
+
Cron::Worker.logger ||= Logger.new(File.join(@options[:log_dir], 'cron.log'))
|
55
|
+
Daemons.run_proc('cron_server',
|
56
|
+
dir: options[:pid_dir],
|
57
|
+
dir_mode: :normal,
|
58
|
+
monitor: @options[:monitor],
|
59
|
+
ARGV: @args) do
|
60
|
+
run @options
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def run(options = {})
|
65
|
+
Dir.chdir(root)
|
66
|
+
|
67
|
+
Cron::Worker.new(options).start
|
68
|
+
rescue StandardError => error
|
69
|
+
App47Logger.log_error 'Unable to start Cron Server', error
|
70
|
+
exit 1
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def root
|
76
|
+
@root ||= defined?(::Rails.root) ? ::Rails.root : DIR_PWD
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cron
|
4
|
+
#
|
5
|
+
# Base class for WeeklyJobs.
|
6
|
+
#
|
7
|
+
# Support running only on certain days as defined by day_of_week method
|
8
|
+
#
|
9
|
+
class Job < ApplicationJob
|
10
|
+
cattr_accessor :cron_tab
|
11
|
+
#
|
12
|
+
# Method to set the cron_tab
|
13
|
+
#
|
14
|
+
def self.cron_tab_entry(entry)
|
15
|
+
self.cron_tab = entry
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Sends support an email if something goes awry
|
20
|
+
#
|
21
|
+
def send_support_email(error, event)
|
22
|
+
email = EmailNotification.new
|
23
|
+
email.to = 'support@app47.com'
|
24
|
+
params = { current_date: I18n.l(Time.zone.today, format: :medium),
|
25
|
+
event: event,
|
26
|
+
error: error }
|
27
|
+
email.from_template('support_ticket_notification', params)
|
28
|
+
email.send_notification
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Value object for a cron tab entry
|
5
|
+
#
|
6
|
+
module Cron
|
7
|
+
class JobTab < Tab
|
8
|
+
unless defined? FRAMEWORK_CLASSES
|
9
|
+
FRAMEWORK_CLASSES = %w[job trim_collection command server tab job_tab worker].freeze
|
10
|
+
end
|
11
|
+
cattr_accessor :jobs
|
12
|
+
#
|
13
|
+
# Validations
|
14
|
+
#
|
15
|
+
validates :name, presence: true, uniqueness: true
|
16
|
+
validate :valid_name
|
17
|
+
|
18
|
+
class << self
|
19
|
+
#
|
20
|
+
# Pull form system configuration
|
21
|
+
#
|
22
|
+
def from_string(name, value)
|
23
|
+
tab = JobTab.find_or_initialize_by name: name
|
24
|
+
return unless tab.valid?
|
25
|
+
|
26
|
+
tab.update_from_string(value)
|
27
|
+
tab
|
28
|
+
end
|
29
|
+
|
30
|
+
def ensure_cron_tabs
|
31
|
+
self.jobs = []
|
32
|
+
Cron.constants.each do |job|
|
33
|
+
job_name = job.to_s.underscore
|
34
|
+
next if FRAMEWORK_CLASSES.include?(job_name) ||
|
35
|
+
job_name.end_with?('_test') ||
|
36
|
+
job_name.start_with?('base_')
|
37
|
+
|
38
|
+
jobs << job_name
|
39
|
+
klass = "cron/#{job_name}".camelize.constantize
|
40
|
+
tab = JobTab.find_or_initialize_by name: job_name
|
41
|
+
next if tab.persisted?
|
42
|
+
|
43
|
+
configure_cron_tab(tab, klass.cron_tab)
|
44
|
+
end
|
45
|
+
purge_cron_tabs
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def configure_cron_tab(tab, type)
|
52
|
+
case type
|
53
|
+
when :always
|
54
|
+
tab.update! min: WILDCARD, hour: WILDCARD
|
55
|
+
when :hourly
|
56
|
+
tab.update! min: 0, hour: WILDCARD
|
57
|
+
when :daily
|
58
|
+
tab.update! min: 0, hour: 0
|
59
|
+
when :weekly
|
60
|
+
tab.update! min: 0, hour: 0, wday: 0
|
61
|
+
when :monthly
|
62
|
+
tab.update! min: 0, hour: 0, mday: 0
|
63
|
+
else
|
64
|
+
tab.update_from_string(type)
|
65
|
+
tab.save!
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def purge_cron_tabs
|
70
|
+
JobTab.all.each do |tab|
|
71
|
+
tab.destroy unless jobs.include?(tab.name)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# Update our values based on the value
|
78
|
+
#
|
79
|
+
def update_from_string(value)
|
80
|
+
values = value.split(' ')
|
81
|
+
self.min = values[0]
|
82
|
+
self.hour = values[1]
|
83
|
+
self.mday = values[2]
|
84
|
+
self.month = values[3]
|
85
|
+
self.wday = values[4]
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Run this job cron tab
|
90
|
+
#
|
91
|
+
def run
|
92
|
+
return unless valid_environment?
|
93
|
+
|
94
|
+
cron_job_class.perform_later
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# Is this enabled and a valid environment
|
100
|
+
#
|
101
|
+
def valid_environment?
|
102
|
+
enabled? && cron_job_class.valid_environment?
|
103
|
+
end
|
104
|
+
|
105
|
+
#
|
106
|
+
# Return the class associated with this job cron tab
|
107
|
+
#
|
108
|
+
def cron_job_class
|
109
|
+
@cron_job_class ||= "cron/#{name}".camelize.constantize
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# Convert back to a standard string value
|
114
|
+
#
|
115
|
+
def to_string
|
116
|
+
[min, hour, mday, month, wday].join(' ')
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Test the name is the list of jobs discovered
|
121
|
+
#
|
122
|
+
def valid_name
|
123
|
+
errors.add(:name, 'Invalid Tab') unless jobs.include?(name)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
#
|
2
|
+
# Handle the coordination of which server should be running the cron jobs
|
3
|
+
#
|
4
|
+
module Cron
|
5
|
+
class Server
|
6
|
+
include StandardModel
|
7
|
+
#
|
8
|
+
# Constants
|
9
|
+
#
|
10
|
+
STATE_PRIMARY = 'primary'.freeze unless defined? STATE_PRIMARY
|
11
|
+
STATE_SECONDARY = 'secondary'.freeze unless defined? STATE_SECONDARY
|
12
|
+
ALL_STATES = [STATE_PRIMARY, STATE_SECONDARY].freeze unless defined? ALL_STATES
|
13
|
+
#
|
14
|
+
# Fields
|
15
|
+
#
|
16
|
+
field :host_name, type: String
|
17
|
+
field :pid, type: Integer
|
18
|
+
field :desired_server_count, type: Integer, default: 0
|
19
|
+
field :current_server_count, type: Integer, default: 0
|
20
|
+
field :last_check_in_at, type: Time, default: Time.now.utc
|
21
|
+
field :state, type: String, default: STATE_SECONDARY
|
22
|
+
#
|
23
|
+
# Validations
|
24
|
+
#
|
25
|
+
validates :host_name, presence: true
|
26
|
+
validates :pid, presence: true
|
27
|
+
validates :last_check_in_at, presence: true
|
28
|
+
validates :state, inclusion: { in: ALL_STATES }
|
29
|
+
validate :high_lander
|
30
|
+
#
|
31
|
+
# Go through the logic once a minute
|
32
|
+
#
|
33
|
+
def execute
|
34
|
+
if primary?
|
35
|
+
run_cron_jobs
|
36
|
+
else
|
37
|
+
primary = Cron::Server.where(state: STATE_PRIMARY).first
|
38
|
+
if primary.blank? || primary.dead?
|
39
|
+
become_primary
|
40
|
+
run_cron_jobs
|
41
|
+
end
|
42
|
+
end
|
43
|
+
time_to_next_run
|
44
|
+
rescue StandardError => error
|
45
|
+
App47Logger.log_error 'Unable to run cron jobs', error
|
46
|
+
time_to_next_run
|
47
|
+
ensure
|
48
|
+
check_in
|
49
|
+
end
|
50
|
+
|
51
|
+
def run_cron_jobs
|
52
|
+
run_jobs
|
53
|
+
check_auto_scale
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Run all cron tab jobs
|
58
|
+
#
|
59
|
+
def run_jobs
|
60
|
+
now = Time.now.utc
|
61
|
+
CronTab.all.each { |tab| tab.run if tab.time_to_run?(now) }
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Determine the next minute to run,
|
66
|
+
#
|
67
|
+
def time_to_next_run
|
68
|
+
60 - Time.now.utc.to_i % 60
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Find a record for this server
|
73
|
+
#
|
74
|
+
def self.find_or_create_server
|
75
|
+
Cron::Server.find_or_create_by!(host_name: Socket.gethostname, pid: Process.pid)
|
76
|
+
end
|
77
|
+
|
78
|
+
#
|
79
|
+
# Find a the current master
|
80
|
+
#
|
81
|
+
def self.primary_server
|
82
|
+
Cron::Server.where(state: STATE_PRIMARY).first
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Warm up a server on the next evaluation
|
87
|
+
#
|
88
|
+
def self.warm_up_server
|
89
|
+
return unless SystemConfiguration.auto_scaling_configured?
|
90
|
+
|
91
|
+
primary_server.auto_scale([primary_server.desired_server_count + 1, 10].min)
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Become primary, making others secondary
|
96
|
+
#
|
97
|
+
def become_primary
|
98
|
+
Cron::Server.each(&:become_secondary)
|
99
|
+
# sleep a small amount of time to randomize a new primary
|
100
|
+
sleep rand(1..15)
|
101
|
+
# Check to see if another node already became primary
|
102
|
+
primary = Cron::Server.primary_server
|
103
|
+
return if primary.present? && primary.alive?
|
104
|
+
|
105
|
+
# no one else is in, so become primary
|
106
|
+
update_attributes! state: STATE_PRIMARY, last_check_in_at: Time.now.utc
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# Become secondary node
|
111
|
+
#
|
112
|
+
def become_secondary(user = nil)
|
113
|
+
if user.present?
|
114
|
+
update_attributes_and_log! user, state: STATE_SECONDARY
|
115
|
+
else
|
116
|
+
update_attributes! state: STATE_SECONDARY
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Am I the primary server
|
122
|
+
#
|
123
|
+
def primary?
|
124
|
+
alive? && STATE_PRIMARY.eql?(state)
|
125
|
+
end
|
126
|
+
|
127
|
+
#
|
128
|
+
# Am I a secondary server
|
129
|
+
#
|
130
|
+
def secondary?
|
131
|
+
STATE_SECONDARY.eql?(state)
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Return true if I've reported in the last two minutes
|
136
|
+
#
|
137
|
+
def alive?
|
138
|
+
last_check_in_at >= 90.seconds.ago.utc
|
139
|
+
end
|
140
|
+
|
141
|
+
#
|
142
|
+
# Is the server dead, meaning is it not reporting within the last two minutes
|
143
|
+
#
|
144
|
+
def dead?
|
145
|
+
!alive?
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
# Perform a check in for the server
|
150
|
+
#
|
151
|
+
def check_in
|
152
|
+
set last_check_in_at: Time.now.utc
|
153
|
+
end
|
154
|
+
|
155
|
+
#
|
156
|
+
# Auto scale environment
|
157
|
+
#
|
158
|
+
def check_auto_scale
|
159
|
+
return unless SystemConfiguration.auto_scaling_configured?
|
160
|
+
|
161
|
+
if delayed_jobs_count.eql?(0)
|
162
|
+
handle_zero_job_count
|
163
|
+
else
|
164
|
+
handle_auto_scale_jobs
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
#
|
169
|
+
# Returns the AWS AutoScaling Client
|
170
|
+
#
|
171
|
+
def client
|
172
|
+
credentials = { access_key_id: sys_config.access_key_id,
|
173
|
+
secret_access_key: sys_config.secret_access_key,
|
174
|
+
region: sys_config.region }
|
175
|
+
@client ||= Aws::AutoScaling::Client.new(credentials)
|
176
|
+
end
|
177
|
+
|
178
|
+
def sys_config
|
179
|
+
@sys_config ||= SystemConfiguration.configuration
|
180
|
+
end
|
181
|
+
|
182
|
+
#
|
183
|
+
# Returns the AutoScalingGroup associated with the account
|
184
|
+
#
|
185
|
+
def auto_scaling_group
|
186
|
+
filter = { auto_scaling_group_names: [sys_config.auto_scaling_group_name] }
|
187
|
+
@auto_scaling_group ||= client.describe_auto_scaling_groups(filter).auto_scaling_groups.first
|
188
|
+
end
|
189
|
+
|
190
|
+
#
|
191
|
+
# Returns a count of the Delayed Jobs in queue that have not failed
|
192
|
+
#
|
193
|
+
def delayed_jobs_count
|
194
|
+
@delayed_jobs_count ||= Delayed::Backend::Mongoid::Job.where(failed_at: nil).read(mode: :primary).count
|
195
|
+
end
|
196
|
+
|
197
|
+
#
|
198
|
+
# Returns the current value of 'desired capacity' for the AutoScalingGroup
|
199
|
+
#
|
200
|
+
def current_desired_capacity
|
201
|
+
current = auto_scaling_group.desired_capacity
|
202
|
+
set current_server_count: current
|
203
|
+
current
|
204
|
+
rescue StandardError
|
205
|
+
0
|
206
|
+
end
|
207
|
+
|
208
|
+
#
|
209
|
+
# Calls the 'auto_scale' method with a 'desired_count' of 0 unless the capacity is already at 0
|
210
|
+
#
|
211
|
+
def handle_zero_job_count
|
212
|
+
return if current_desired_capacity.eql?(0)
|
213
|
+
|
214
|
+
auto_scale
|
215
|
+
end
|
216
|
+
|
217
|
+
#
|
218
|
+
# Calls the 'auto_scale' method with a variable 'desired_count' based on how many jobs are running
|
219
|
+
# We don't need any more workers if the job count is less than 1,000
|
220
|
+
#
|
221
|
+
def handle_auto_scale_jobs
|
222
|
+
return if delayed_jobs_count < 50
|
223
|
+
|
224
|
+
case delayed_jobs_count
|
225
|
+
when 50..250
|
226
|
+
auto_scale(1)
|
227
|
+
when 251..500
|
228
|
+
auto_scale(2)
|
229
|
+
when 501..1_000
|
230
|
+
auto_scale(3)
|
231
|
+
when 1_001..2_000
|
232
|
+
auto_scale(4)
|
233
|
+
when 2_001..3_999
|
234
|
+
auto_scale(4)
|
235
|
+
when 4_000..7_999
|
236
|
+
auto_scale(5)
|
237
|
+
when 8_000..10_999
|
238
|
+
auto_scale(5)
|
239
|
+
when 11_000..13_999
|
240
|
+
auto_scale(6)
|
241
|
+
when 14_000..17_999
|
242
|
+
auto_scale(6)
|
243
|
+
else
|
244
|
+
auto_scale(7)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# Sets the desired and minimum number of EC2 instances to run
|
250
|
+
#
|
251
|
+
def auto_scale(desired_count = 0)
|
252
|
+
set desired_server_count: desired_count
|
253
|
+
# Make sure we don't remove any workers with assigned jobs by accident
|
254
|
+
return if desired_count.positive? && desired_count <= current_desired_capacity
|
255
|
+
|
256
|
+
client.update_auto_scaling_group(auto_scaling_group_name: sys_config.auto_scaling_group_name,
|
257
|
+
min_size: desired_count,
|
258
|
+
desired_capacity: desired_count)
|
259
|
+
end
|
260
|
+
|
261
|
+
#
|
262
|
+
# Look to make sure there is only one primary
|
263
|
+
#
|
264
|
+
def high_lander
|
265
|
+
return if secondary? # Don't need to check if not primary
|
266
|
+
|
267
|
+
primary = Cron::Server.where(state: STATE_PRIMARY).first
|
268
|
+
errors.add(:state, 'there can only be one primary') unless primary.blank? || primary.eql?(self)
|
269
|
+
end
|
270
|
+
|
271
|
+
#
|
272
|
+
# Returns the count of active servers
|
273
|
+
#
|
274
|
+
def active_count
|
275
|
+
current_server_count
|
276
|
+
end
|
277
|
+
|
278
|
+
#
|
279
|
+
# Returns the count of inactive servers
|
280
|
+
#
|
281
|
+
def inactive_count
|
282
|
+
desired_server_count
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
#
|
2
|
+
# Run in cron
|
3
|
+
#
|
4
|
+
module Cron
|
5
|
+
#
|
6
|
+
# Sync configuration with switchboard
|
7
|
+
#
|
8
|
+
class SwitchboardSyncConfiguration < Job
|
9
|
+
cron_tab_entry :hourly
|
10
|
+
|
11
|
+
#
|
12
|
+
# Only run in environments where switchboard is configured
|
13
|
+
#
|
14
|
+
def self.valid_environment?
|
15
|
+
SystemConfiguration.switchboard_configured?
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Cycle through all configuration keys
|
20
|
+
#
|
21
|
+
def perform
|
22
|
+
Rails.cache.reconnect
|
23
|
+
RestClient.get(switchboard_url,
|
24
|
+
ACCESS_TOKEN: SystemConfiguration.switchboard_stack_api_token,
|
25
|
+
content_type: 'application/json') do |response, _request, _result, &block|
|
26
|
+
case response.code
|
27
|
+
when 200
|
28
|
+
json = JSON.parse(response.body)
|
29
|
+
config = SystemConfiguration.configuration
|
30
|
+
json['results'].each { |key, value| update_config(config, key, value) }
|
31
|
+
config.save!
|
32
|
+
else
|
33
|
+
App47Logger.log_error "Unable to fetch switchboard config, #{response.inspect}"
|
34
|
+
response.return!(&block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# First see if it's updateable against system config, then see if it's in JobCronTab
|
41
|
+
#
|
42
|
+
def update_config(config, key, value)
|
43
|
+
config.send("#{key}=", value) if config.respond_to?("#{key}=")
|
44
|
+
return unless key.end_with?('_crontab')
|
45
|
+
|
46
|
+
name = key.chomp('_crontab')
|
47
|
+
tab = Cron::JobTab.from_string(name, value)
|
48
|
+
if tab.present? && tab.valid?
|
49
|
+
tab.save
|
50
|
+
App47Logger.log_debug "Crontab #{name} updated with #{value}"
|
51
|
+
else
|
52
|
+
App47Logger.log_warn "Unable to update crontab #{name} updated with #{value}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Generate the switchboard URL
|
58
|
+
#
|
59
|
+
def switchboard_url
|
60
|
+
[SystemConfiguration.switchboard_base_url,
|
61
|
+
'stacks',
|
62
|
+
SystemConfiguration.switchboard_stack_id,
|
63
|
+
'items.json'].join('/')
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#
|
2
|
+
# Run in cron
|
3
|
+
#
|
4
|
+
module Cron
|
5
|
+
#
|
6
|
+
# Cycle through all members and tell them to sync with with switchboard
|
7
|
+
#
|
8
|
+
class SwitchboardSyncModels <Job
|
9
|
+
cron_tab_entry :daily
|
10
|
+
|
11
|
+
#
|
12
|
+
# Only run in environments where switchboard is configured
|
13
|
+
#
|
14
|
+
def self.valid_environment?
|
15
|
+
SystemConfiguration.switchboard_configured? && Web47core.config.switchboard_able_models.present?
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Cycle through the collection and perform an upsert on it
|
20
|
+
#
|
21
|
+
def perform
|
22
|
+
Web47core.config.switchboard_able_models.each { |model| model.each(&:switchboard_upsert) }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|