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