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
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Value object for a cron tab entry
|
5
|
+
#
|
6
|
+
module Cron
|
7
|
+
class Tab
|
8
|
+
include StandardModel
|
9
|
+
include SearchAble
|
10
|
+
#
|
11
|
+
# Constants
|
12
|
+
#
|
13
|
+
WILDCARD = '*' unless defined? WILDCARD
|
14
|
+
TIME_UNITS = %i[min hour wday mday month].freeze unless defined? TIME_UNITS
|
15
|
+
#
|
16
|
+
# Fields
|
17
|
+
#
|
18
|
+
field :name, type: String
|
19
|
+
field :enabled, type: Boolean, default: true
|
20
|
+
field :min, type: String, default: 0
|
21
|
+
field :hour, type: String, default: 0
|
22
|
+
field :wday, type: String, default: WILDCARD
|
23
|
+
field :mday, type: String, default: WILDCARD
|
24
|
+
field :month, type: String, default: WILDCARD
|
25
|
+
field :last_run_at, type: Time
|
26
|
+
#
|
27
|
+
# Validations
|
28
|
+
#
|
29
|
+
validates :name, presence: true, uniqueness: true
|
30
|
+
validate :valid_values
|
31
|
+
|
32
|
+
#
|
33
|
+
# The method might look a bit obtuse, but basically we want to compare the time values for
|
34
|
+
# * Minute
|
35
|
+
# * Hour
|
36
|
+
# * Day of Week
|
37
|
+
# * Day of Month
|
38
|
+
# * Month
|
39
|
+
#
|
40
|
+
def time_to_run?(time)
|
41
|
+
enabled? && TIME_UNITS.collect { |unit| valid_time?(time, unit, send(unit)) }.all?
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# For completion of class, but must be implemented by child class
|
46
|
+
#
|
47
|
+
def run
|
48
|
+
set last_run_at: Time.now.utc
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
#
|
54
|
+
# Check that all values are within the range
|
55
|
+
#
|
56
|
+
def valid_values
|
57
|
+
valid_range :min, 0..59
|
58
|
+
valid_range :hour, 0..23
|
59
|
+
valid_range :month, 0..11
|
60
|
+
valid_range :wday, 0..6
|
61
|
+
valid_range :mday, 0..30
|
62
|
+
end
|
63
|
+
|
64
|
+
def valid_range(field, range)
|
65
|
+
value = send(field)
|
66
|
+
valid = case value
|
67
|
+
when WILDCARD
|
68
|
+
true
|
69
|
+
when Integer
|
70
|
+
range.include?(value)
|
71
|
+
else
|
72
|
+
if value.include?('/')
|
73
|
+
(numerator, divisor) = value.split('/')
|
74
|
+
range.include?(divisor.to_i) && numerator.eql?(WILDCARD)
|
75
|
+
elsif value.include?(',')
|
76
|
+
options = value.split(',')
|
77
|
+
options.collect { |o| range.include?(o.to_i) }.all?
|
78
|
+
else
|
79
|
+
range.include?(value.to_i)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
errors.add(field, "Invalid value, allowed range: #{range}") unless valid
|
83
|
+
end
|
84
|
+
|
85
|
+
#
|
86
|
+
# Test if the target value matches the time unit or the wild card, or the comma separated list
|
87
|
+
# 0 - matches the zero value
|
88
|
+
# * - Wildcard, any value matches
|
89
|
+
# */15 - matches any value where dividing by 15 is even, or the modulus is zero
|
90
|
+
# 5,10,15 - matches on 5, 10 and 15 values
|
91
|
+
#
|
92
|
+
def valid_time?(time, unit, target)
|
93
|
+
case target
|
94
|
+
when WILDCARD
|
95
|
+
true
|
96
|
+
when Integer
|
97
|
+
time.send(unit).eql?(target)
|
98
|
+
else
|
99
|
+
if target.include?('/')
|
100
|
+
divisor = target.split('/').last.to_i
|
101
|
+
(time.send(unit) % divisor).zero?
|
102
|
+
elsif target.include?(',')
|
103
|
+
options = target.split(',')
|
104
|
+
options.collect { |o| time.send(unit.eql?(o.to_i)) }.any?
|
105
|
+
else
|
106
|
+
time.send(unit).eql?(target.to_i)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cron
|
4
|
+
#
|
5
|
+
# Clean up the collection items that have not been updated in 30 days
|
6
|
+
#
|
7
|
+
class TrimCollection < Job
|
8
|
+
cron_tab_entry :daily
|
9
|
+
#
|
10
|
+
# Fetch each item and delete it if hasn't been updated in 30 days
|
11
|
+
#
|
12
|
+
def perform
|
13
|
+
# Rails.cache.reconnect
|
14
|
+
count = 0
|
15
|
+
total = collection.count
|
16
|
+
while count <= total
|
17
|
+
collection.limit(250).skip(count).each { |item| item.destroy if archive?(item) }
|
18
|
+
count += 250
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Test if this should be archived
|
24
|
+
#
|
25
|
+
def archive?(item)
|
26
|
+
item.updated_at < allowed_time
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Allowed time the amount of time allowed to exists before deleting
|
31
|
+
#
|
32
|
+
def allowed_time
|
33
|
+
30.days.ago
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cron
|
4
|
+
#
|
5
|
+
# Clean up Audit Logs that have not been updated in 90 days
|
6
|
+
#
|
7
|
+
class TrimCronServers < TrimCollection
|
8
|
+
#
|
9
|
+
# Fetch each Audit Log and delete it if hasn't been updated in 90 days
|
10
|
+
#
|
11
|
+
def collection
|
12
|
+
Cron::Server.all
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Check if the cron job server hasn't reported in a while
|
17
|
+
#
|
18
|
+
def archive?(item)
|
19
|
+
item.dead?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Cron
|
2
|
+
#
|
3
|
+
# Automatically update Product Version Lifecycle State to EOL If it is past it's EOL date
|
4
|
+
# Runs Daily
|
5
|
+
#
|
6
|
+
class TrimFailedDelayedJobs < TrimCollection
|
7
|
+
#
|
8
|
+
# Return the failed collection
|
9
|
+
#
|
10
|
+
def collection
|
11
|
+
Delayed::Backend::Mongoid::Job.where(:failed_at.exists => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def archive?(job)
|
15
|
+
if 'Delayed::PerformableMethod'.eql?(job.name)
|
16
|
+
|
17
|
+
data = { object: job.failed_object.inspect,
|
18
|
+
method: job.failed_method_name,
|
19
|
+
args: job.failed_args,
|
20
|
+
failed_at: job.failed_at,
|
21
|
+
attempts: job.attempts,
|
22
|
+
locked_by: job.locked_by }
|
23
|
+
SlackNotification.say(data, template: :failed_delayed_job)
|
24
|
+
true
|
25
|
+
else
|
26
|
+
false
|
27
|
+
end
|
28
|
+
rescue StandardError => error
|
29
|
+
log_error "Unable to determine if we should archive job: #{job}", error
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# If the slack API is not present, don't delete jobs or we wont know about it.
|
35
|
+
#
|
36
|
+
def self.valid_environment?
|
37
|
+
SystemConfiguration.slack_api_url.present? && Delayed::Backend::Mongoid::Job.count.positive?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Cron
|
4
|
+
#
|
5
|
+
# Trim notifications in system after 30 days.
|
6
|
+
#
|
7
|
+
# This was historically done by a database index, however with the introduction
|
8
|
+
# of adding an email attachment, and thus another collection, this needed to move
|
9
|
+
# to a daily job here so that:
|
10
|
+
# 1. the object hierarchy is deleted
|
11
|
+
# 2. The paperclip call backs are fired to remove the file from S3
|
12
|
+
#
|
13
|
+
# In case you are wondering why not make it an embedded class and allow the index
|
14
|
+
# method to still work, the answer is that it won't clean up the S3 files.
|
15
|
+
#
|
16
|
+
class TrimNotifications < TrimCollection
|
17
|
+
#
|
18
|
+
# Return the collection
|
19
|
+
#
|
20
|
+
def collection
|
21
|
+
Notification.all
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
require 'active_support/dependencies'
|
3
|
+
require 'active_support/core_ext/numeric/time'
|
4
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
5
|
+
require 'active_support/hash_with_indifferent_access'
|
6
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
7
|
+
require 'logger'
|
8
|
+
require 'benchmark'
|
9
|
+
|
10
|
+
module Cron
|
11
|
+
class Worker # rubocop:disable ClassLength
|
12
|
+
DEFAULT_LOG_LEVEL = 'info'.freeze
|
13
|
+
|
14
|
+
cattr_accessor :logger, :default_log_level
|
15
|
+
|
16
|
+
def self.reset
|
17
|
+
self.default_log_level ||= DEFAULT_LOG_LEVEL
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.reload_app?
|
21
|
+
defined?(ActionDispatch::Reloader) && Rails.application.config.cache_classes == false
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(options = {})
|
25
|
+
super()
|
26
|
+
@exit = options[:exit_on_complete].presence
|
27
|
+
end
|
28
|
+
|
29
|
+
# Every worker has a unique name which by default is the pid of the process. There are some
|
30
|
+
# advantages to overriding this with something which survives worker restarts: Workers can
|
31
|
+
# safely resume working on tasks which are locked by themselves. The worker will assume that
|
32
|
+
# it crashed before.
|
33
|
+
def name
|
34
|
+
@name ||= "CronServer:#{Socket.gethostname} pid:#{Process.pid}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def start # rubocop:disable CyclomaticComplexity, PerceivedComplexity
|
38
|
+
trap('TERM') { stop }
|
39
|
+
trap('INT') { stop }
|
40
|
+
|
41
|
+
say 'Starting cron jobs server'
|
42
|
+
|
43
|
+
self.class.lifecycle.run_callbacks(:execute, self) do
|
44
|
+
loop do
|
45
|
+
sleep Cron::Server.find_or_create_server.execute
|
46
|
+
break if stop?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
say 'Exiting cron jobs server...'
|
51
|
+
end
|
52
|
+
|
53
|
+
def stop
|
54
|
+
@exit = true
|
55
|
+
end
|
56
|
+
|
57
|
+
def stop?
|
58
|
+
!!@exit
|
59
|
+
end
|
60
|
+
|
61
|
+
def say(text, level = default_log_level)
|
62
|
+
text = "[CronServer(#{name})] #{text}"
|
63
|
+
return if logger.blank?
|
64
|
+
|
65
|
+
logger.send(level, "#{Time.now.strftime('%FT%T%z')}: #{text}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
Cron::Worker.reset
|
@@ -0,0 +1,130 @@
|
|
1
|
+
#
|
2
|
+
# Objects that can be synced to switchboard. They must implement the following methods
|
3
|
+
# * switchboard_name - the name of the object in switchboard land, apps, members, etc..
|
4
|
+
# * switchboard_payload - the payload to send to switchboard when updating or deleting
|
5
|
+
#
|
6
|
+
module SwitchboardAble
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
field :switchboard_id, type: String
|
12
|
+
after_create :switchboard_upsert
|
13
|
+
# after_update :switchboard_upsert
|
14
|
+
before_destroy :switchboard_delete
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Return the name in switchboard, must be implemented by the class
|
20
|
+
#
|
21
|
+
def switchboard_name
|
22
|
+
raise 'Method (switchboard_name) must be implemented by the concrete class'
|
23
|
+
end
|
24
|
+
|
25
|
+
#
|
26
|
+
# Return the payload in switchboard, must be implemented by the class
|
27
|
+
#
|
28
|
+
def switchboard_payload
|
29
|
+
raise 'Method (switchboard_payload) must be implemented by the concrete class'
|
30
|
+
end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Generic handler to determine if we should insert or update this object
|
34
|
+
#
|
35
|
+
def switchboard_upsert
|
36
|
+
return unless community_access?
|
37
|
+
|
38
|
+
switchboard_id.present? ? switchboard_update : switchboard_insert
|
39
|
+
end
|
40
|
+
|
41
|
+
handle_asynchronously :switchboard_upsert
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
#
|
46
|
+
# Return false if this object should not uploaded to switchboard, true otherwise
|
47
|
+
#
|
48
|
+
def community_access?
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Delete the object from switchboard
|
54
|
+
#
|
55
|
+
def switchboard_delete
|
56
|
+
return unless SystemConfiguration.switchboard_configured? && switchboard_id.present?
|
57
|
+
|
58
|
+
RestClient.delete(switchboard_url, ACCESS_TOKEN: sw_api_token) do |response, _request, _result, &block|
|
59
|
+
case response.code
|
60
|
+
when 200
|
61
|
+
App47Logger.log_debug "Switchboard deleted #{inspect}"
|
62
|
+
else
|
63
|
+
App47Logger.log_error "Unable to delete the switchboard object #{inspect}, #{response.inspect}"
|
64
|
+
response.return!(&block)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Update the object in switchboard
|
71
|
+
#
|
72
|
+
def switchboard_update
|
73
|
+
return unless SystemConfiguration.switchboard_configured? && switchboard_id.present?
|
74
|
+
|
75
|
+
RestClient.put(switchboard_url,
|
76
|
+
switchboard_payload.to_json,
|
77
|
+
ACCESS_TOKEN: sw_api_token,
|
78
|
+
content_type: 'application/json') do |response, _request, _result, &block|
|
79
|
+
case response.code
|
80
|
+
when 200
|
81
|
+
App47Logger.log_debug "Switchboard updated #{inspect}"
|
82
|
+
when 404, 302
|
83
|
+
unset :switchboard_id
|
84
|
+
else
|
85
|
+
App47Logger.log_error "Unable to update the switchboard object #{inspect}, #{response.inspect}"
|
86
|
+
response.return!(&block)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# Insert the object into switchboard
|
93
|
+
#
|
94
|
+
def switchboard_insert
|
95
|
+
return unless SystemConfiguration.switchboard_configured?
|
96
|
+
|
97
|
+
RestClient.post(switchboard_url,
|
98
|
+
switchboard_payload.to_json,
|
99
|
+
ACCESS_TOKEN: sw_api_token,
|
100
|
+
content_type: 'application/json') do |response, _request, _result, &block|
|
101
|
+
case response.code
|
102
|
+
when 200
|
103
|
+
json = JSON.parse(response.body)
|
104
|
+
set switchboard_id: json['results'][switchboard_name.singularize]['id']
|
105
|
+
else
|
106
|
+
App47Logger.log_error "Unable to insert to switchboard object #{inspect}, #{response.inspect}"
|
107
|
+
response.return!(&block)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
#
|
113
|
+
# The URL for this switchboard item
|
114
|
+
#
|
115
|
+
def switchboard_url
|
116
|
+
components = [SystemConfiguration.switchboard_base_url,
|
117
|
+
'stacks',
|
118
|
+
SystemConfiguration.switchboard_stack_id,
|
119
|
+
switchboard_name]
|
120
|
+
components << switchboard_id if switchboard_id.present?
|
121
|
+
components.join('/') + '.json'
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Return the api token
|
126
|
+
#
|
127
|
+
def sw_api_token
|
128
|
+
@sw_api_token ||= SystemConfiguration.switchboard_stack_api_token
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Delayed
|
2
|
+
module Backend
|
3
|
+
module Mongoid
|
4
|
+
#
|
5
|
+
# Extend the Mongoid delayed job to add additional methods
|
6
|
+
#
|
7
|
+
class Job
|
8
|
+
#
|
9
|
+
# Helper to describe the status of the job
|
10
|
+
#
|
11
|
+
def status_description
|
12
|
+
return 'Failed' if failed?
|
13
|
+
|
14
|
+
locked_by.present? ? locked_by : "Scheduled (#{attempts})"
|
15
|
+
end
|
16
|
+
|
17
|
+
#
|
18
|
+
# Display name for the job
|
19
|
+
#
|
20
|
+
def display_name
|
21
|
+
payload_object.job_data['job_class']
|
22
|
+
rescue StandardError
|
23
|
+
name
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Safely get the payload object
|
28
|
+
#
|
29
|
+
def job_payload
|
30
|
+
payload_object.inspect
|
31
|
+
rescue StandardError => error
|
32
|
+
"Unable to retrieve payload: #{error.message}"
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Resubmit the job for processing
|
37
|
+
#
|
38
|
+
def resubmit
|
39
|
+
set locked_at: nil, locked_by: nil, failed_at: nil, last_error: nil, run_at: Time.now.utc
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# Return the failed object out of the YAML
|
44
|
+
#
|
45
|
+
def failed_object
|
46
|
+
failed_yaml.object
|
47
|
+
rescue StandardError => error
|
48
|
+
"Unknown object: #{error.message}"
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Return the failed method out of the YAML
|
53
|
+
#
|
54
|
+
def failed_method_name
|
55
|
+
failed_yaml.method_name
|
56
|
+
rescue StandardError => error
|
57
|
+
"Unknown method_name: #{error.message}"
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
# Return the failed arguments out of the YAML
|
62
|
+
#
|
63
|
+
def failed_args
|
64
|
+
failed_yaml.args
|
65
|
+
rescue StandardError => error
|
66
|
+
"Unknown args: #{error.message}"
|
67
|
+
end
|
68
|
+
|
69
|
+
#
|
70
|
+
# Return the failed YAML
|
71
|
+
#
|
72
|
+
def failed_yaml
|
73
|
+
@failed_yaml = YAML.load(handler)
|
74
|
+
rescue StandardError
|
75
|
+
''
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/web47core.rb
CHANGED
@@ -6,8 +6,10 @@ require 'app/models/concerns/time_zone_able'
|
|
6
6
|
require 'app/models/concerns/standard_model'
|
7
7
|
require 'app/models/concerns/core_system_configuration'
|
8
8
|
require 'app/models/concerns/core_account'
|
9
|
+
require 'app/models/delayed_job'
|
9
10
|
require 'app/models/redis_configuration'
|
10
11
|
require 'app/models/notification'
|
12
|
+
require 'app/models/sms_notification'
|
11
13
|
require 'app/models/template'
|
12
14
|
require 'app/models/notification_template'
|
13
15
|
require 'app/models/email_notification'
|
@@ -15,3 +17,33 @@ require 'app/models/email_template'
|
|
15
17
|
require 'app/models/slack_notification'
|
16
18
|
require 'app/models/smtp_configuration'
|
17
19
|
require 'app/models/sms_notification'
|
20
|
+
#
|
21
|
+
# Cron
|
22
|
+
#
|
23
|
+
require 'app/jobs/application_job'
|
24
|
+
require 'app/jobs/cron/job'
|
25
|
+
require 'app/jobs/cron/tab'
|
26
|
+
require 'app/jobs/cron/job_tab'
|
27
|
+
require 'app/jobs/cron/command'
|
28
|
+
require 'app/jobs/cron/worker'
|
29
|
+
require 'app/jobs/cron/server'
|
30
|
+
require 'app/jobs/cron/trim_collection'
|
31
|
+
require 'app/jobs/cron/switchboard_sync_configuration'
|
32
|
+
require 'app/jobs/cron/switchboard_sync_models'
|
33
|
+
require 'app/jobs/cron/trim_notifications'
|
34
|
+
require 'app/jobs/cron/trim_cron_servers'
|
35
|
+
require 'app/jobs/cron/trim_failed_delayed_jobs'
|
36
|
+
|
37
|
+
class Web47core
|
38
|
+
include Singleton
|
39
|
+
attr_accessor :email_able_models, :switchboard_able_models
|
40
|
+
|
41
|
+
def initialize
|
42
|
+
@email_able_models = []
|
43
|
+
@switchboard_able_models = []
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.config
|
47
|
+
instance
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Cron
|
4
|
+
class SwitchboardSyncConfigurationTest < ActiveSupport::TestCase
|
5
|
+
setup do
|
6
|
+
@config = SystemConfiguration.configuration
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'valid_environment?' do
|
10
|
+
should 'not run' do
|
11
|
+
refute Cron::SwitchboardSyncConfiguration.valid_environment?
|
12
|
+
end
|
13
|
+
|
14
|
+
should 'run' do
|
15
|
+
@config.switchboard_stack_api_token = 'abc123'
|
16
|
+
@config.switchboard_stack_id = 'abc123'
|
17
|
+
assert @config.save
|
18
|
+
assert Cron::SwitchboardSyncConfiguration.valid_environment?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'update system configuration' do
|
23
|
+
setup do
|
24
|
+
@config.switchboard_base_url = 'https://switchboard.test.com'
|
25
|
+
@config.switchboard_stack_api_token = 'abc124w'
|
26
|
+
@config.switchboard_stack_id = 'sid'
|
27
|
+
assert @config.save
|
28
|
+
end
|
29
|
+
|
30
|
+
should 'update system configuration' do
|
31
|
+
stub = stub_request(:get, "#{SystemConfiguration.switchboard_base_url}/stacks/sid/items.json").to_return(status: 200, body: { results: { cdn_url: 'https://cdn.nowhere.com' } }.to_json)
|
32
|
+
Cron::SwitchboardSyncConfiguration.new.perform
|
33
|
+
assert_not_nil @config.reload
|
34
|
+
assert_equal 'https://cdn.nowhere.com', @config.cdn_url
|
35
|
+
assert_requested stub
|
36
|
+
end
|
37
|
+
|
38
|
+
should 'update crontab' do
|
39
|
+
App47Logger.expects(:log_debug).once
|
40
|
+
Cron::JobTab.ensure_cron_tabs
|
41
|
+
tab = Cron::JobTab.find_by name: 'switchboard_sync_models'
|
42
|
+
assert_equal '0 0 * * *', tab.to_string
|
43
|
+
stub = stub_request(:get, "#{SystemConfiguration.switchboard_base_url}/stacks/sid/items.json").to_return(status: 200, body: { results: { switchboard_sync_models_crontab: '1 2 3 4 5' } }.to_json)
|
44
|
+
Cron::SwitchboardSyncConfiguration.new.perform
|
45
|
+
assert_not_nil tab.reload
|
46
|
+
assert_equal '1 2 3 4 5', tab.to_string
|
47
|
+
assert_requested stub
|
48
|
+
end
|
49
|
+
should 'deal with unknown' do
|
50
|
+
App47Logger.expects(:log_debug).never
|
51
|
+
App47Logger.expects(:log_warn).once
|
52
|
+
Cron::JobTab.ensure_cron_tabs
|
53
|
+
tab = Cron::JobTab.find_by name: 'switchboard_sync_models'
|
54
|
+
assert_equal '0 0 * * *', tab.to_string
|
55
|
+
stub = stub_request(:get, "#{SystemConfiguration.switchboard_base_url}/stacks/sid/items.json").to_return(status: 200, body: { results: { sync_knowledge_base_job_crontab: '1 2 3 4 5' } }.to_json)
|
56
|
+
Cron::SwitchboardSyncConfiguration.new.perform
|
57
|
+
assert_nil Cron::JobTab.where(name: 'sync_knowledge_base_job_crontab').first
|
58
|
+
assert_not_nil tab.reload
|
59
|
+
assert_equal '0 0 * * *', tab.to_string
|
60
|
+
assert_requested stub
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module Cron
|
4
|
+
class TrimCronServersTest < ActiveSupport::TestCase
|
5
|
+
context 'Execute job not deleting servers' do
|
6
|
+
should 'not throw exceptions if no servers ' do
|
7
|
+
assert_nothing_raised { Cron::TrimCronServers.perform_now }
|
8
|
+
end
|
9
|
+
should 'not delete any servers due to last updated' do
|
10
|
+
10.times.each { |n| Cron::Server.create!(host_name: "ip-#{n}", pid: n.to_s).set(last_check_in_at: Time.now.utc) }
|
11
|
+
assert_equal 10, Cron::Server.all.count
|
12
|
+
assert_no_difference 'Cron::Server.all.count' do
|
13
|
+
assert_nothing_raised { Cron::TrimCronServers.perform_now }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
context 'Execute jobs deleting servers' do
|
18
|
+
should 'delete five server logs' do
|
19
|
+
5.times.each { |n| Cron::Server.create!(host_name: "ip-#{n}", pid: n.to_s).set(last_check_in_at: Time.now.utc) }
|
20
|
+
5.times.each { |n| Cron::Server.create!(host_name: "ip-#{n}", pid: (100+n).to_s).set(last_check_in_at: 1.hour.ago.utc) }
|
21
|
+
assert_equal 10, Cron::Server.all.count
|
22
|
+
assert_difference 'Cron::Server.all.count', -5 do
|
23
|
+
assert_nothing_raised { Cron::TrimCronServers.perform_now }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|