robot-controller 1.0.2 → 2.0.beta1
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/.gitignore +0 -2
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +48 -0
- data/README.md +44 -1
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/bin/controller +66 -14
- data/config/environments/robots_development.yml +3 -0
- data/config/robots.yml +3 -0
- data/example/lib/tasks/environment.rake +1 -1
- data/lib/robot-controller.rb +3 -2
- data/lib/robot-controller/bluepill.rb +12 -8
- data/lib/robot-controller/robots.rb +78 -81
- data/lib/robot-controller/tasks.rb +1 -1
- data/lib/robot-controller/verify.rb +209 -0
- data/lib/tasks/doc.rake +11 -12
- data/robot-controller.gemspec +13 -14
- data/spec/spec_helper.rb +2 -2
- data/spec/unit/robots_spec.rb +56 -59
- data/spec/unit/verify_spec.rb +144 -0
- metadata +14 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2f8958d56c26a4a2139a77a4c17a496ea96fe47
|
4
|
+
data.tar.gz: 03a8f5d1f0390f46772fc9e18d80fa8223e964b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd0432d25bf71cab0e6bfa18a5dd3e67210d9d27772b0f86e4f334c34d114d2aff590184192293d93656ae130db9a2b8b7b2e6d1b8f6ffdba97bb84f049d9284
|
7
|
+
data.tar.gz: 5da783211f9b8c77138d1cdb0d0bfbac1c29818d4f2caeda54981032f4701baca9197286d91eec276e7a7395ee94095b442b29cbfcc1d931d2721a72c21a4fda
|
data/.gitignore
CHANGED
data/.rubocop.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# This configuration was generated by `rubocop --auto-gen-config`
|
2
|
+
# on 2015-06-23 11:24:30 -0700 using RuboCop version 0.31.0.
|
3
|
+
# The point is for the user to remove these configuration records
|
4
|
+
# one by one as the offenses are removed from the code base.
|
5
|
+
# Note that changes in the inspected code, or installation of new
|
6
|
+
# versions of RuboCop, may require this file to be generated again.
|
7
|
+
|
8
|
+
# Offense count: 1
|
9
|
+
Metrics/AbcSize:
|
10
|
+
Max: 37
|
11
|
+
|
12
|
+
# Offense count: 1
|
13
|
+
Metrics/CyclomaticComplexity:
|
14
|
+
Max: 9
|
15
|
+
|
16
|
+
# Offense count: 17
|
17
|
+
# Configuration parameters: AllowURI, URISchemes.
|
18
|
+
Metrics/LineLength:
|
19
|
+
Max: 130
|
20
|
+
|
21
|
+
# Offense count: 1
|
22
|
+
# Configuration parameters: CountComments.
|
23
|
+
Metrics/MethodLength:
|
24
|
+
Max: 22
|
25
|
+
|
26
|
+
# Offense count: 1
|
27
|
+
Metrics/PerceivedComplexity:
|
28
|
+
Max: 9
|
29
|
+
|
30
|
+
# Offense count: 1
|
31
|
+
# Configuration parameters: Exclude.
|
32
|
+
Style/FileName:
|
33
|
+
Enabled: false
|
34
|
+
|
35
|
+
# Offense count: 1
|
36
|
+
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
37
|
+
Style/FormatString:
|
38
|
+
Enabled: false
|
39
|
+
|
40
|
+
# Offense count: 5
|
41
|
+
# Cop supports --auto-correct.
|
42
|
+
Style/NumericLiterals:
|
43
|
+
MinDigits: 6
|
44
|
+
|
45
|
+
# Offense count: 1
|
46
|
+
# Configuration parameters: Methods.
|
47
|
+
Style/SingleLineBlockParams:
|
48
|
+
Enabled: false
|
data/README.md
CHANGED
@@ -30,12 +30,14 @@ controller, then add:
|
|
30
30
|
|
31
31
|
Usage: controller ( boot | quit )
|
32
32
|
controller ( start | status | stop | restart | log ) [worker]
|
33
|
+
controller verify [--verbose]
|
33
34
|
controller [--help]
|
34
35
|
|
35
36
|
Example:
|
36
37
|
% controller boot # start bluepilld and jobs
|
37
38
|
% controller status # check on status of jobs
|
38
|
-
% controller
|
39
|
+
% controller verify # verify robots are running as configured
|
40
|
+
% controller log 1_dor_accessionWF_descriptive-metadata # view log for worker
|
39
41
|
% controller stop # stop jobs
|
40
42
|
% controller quit # stop bluepilld
|
41
43
|
|
@@ -48,3 +50,44 @@ controller, then add:
|
|
48
50
|
|
49
51
|
* `v1.0.0`: Initial version
|
50
52
|
* `v1.0.1`: Add 'rake' as dependency
|
53
|
+
|
54
|
+
### `verify` command
|
55
|
+
|
56
|
+
You can run the `verify` command with an optional `--verbose` to print out
|
57
|
+
details about whether the robots processes are running as configured.
|
58
|
+
|
59
|
+
When no errors are detected, the output looks like so:
|
60
|
+
|
61
|
+
% bundle exec controller verify
|
62
|
+
OK
|
63
|
+
|
64
|
+
% bundle exec controller verify --verbose
|
65
|
+
OK robot1 is up
|
66
|
+
OK robot2 is up
|
67
|
+
OK robot3 is not enabled
|
68
|
+
OK robot4 is not enabled
|
69
|
+
|
70
|
+
If `robot2` were down and `robot3` were up, the output would look like so:
|
71
|
+
|
72
|
+
% bundle exec controller verify
|
73
|
+
ERROR robot2 is down (0 out of 3 processes running)
|
74
|
+
ERROR robot3 is not enabled but 1 process is running
|
75
|
+
|
76
|
+
% bundle exec controller verify --verbose
|
77
|
+
OK robot1 is up
|
78
|
+
ERROR robot2 is down (0 out of 3 processes running)
|
79
|
+
ERROR robot3 is not enabled but 1 process is running
|
80
|
+
OK robot4 is not enabled
|
81
|
+
|
82
|
+
The various states are determined as follows:
|
83
|
+
|
84
|
+
- If the robot is enabled:
|
85
|
+
- `OK`: all N processes are running
|
86
|
+
- `ERROR`: not all N processes are running
|
87
|
+
- If the robot is NOT enabled:
|
88
|
+
- `OK`: no processes are running
|
89
|
+
- `ERROR`: 1 or more processes are running
|
90
|
+
- If the robot is unknown by the suite:
|
91
|
+
- `ERROR`: always
|
92
|
+
|
93
|
+
NOTE: The queues on which the robots are running are NOT verified.
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.beta1
|
data/bin/controller
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
require 'yaml'
|
3
|
+
require 'robot-controller'
|
2
4
|
|
3
5
|
if ARGV.size == 0
|
4
6
|
puts '
|
5
7
|
Usage: controller ( boot | quit )
|
6
8
|
controller ( start | status | stop | restart | log ) [worker]
|
9
|
+
controller verify [--verbose]
|
7
10
|
controller [--help]
|
8
11
|
|
9
12
|
Example:
|
10
13
|
% controller boot # start bluepilld and jobs
|
11
14
|
% controller status # check on status of jobs
|
12
15
|
% controller log dor_accessionWF_descriptive-metadata # view log for worker
|
16
|
+
% controller verify # verify robots are running as configured
|
13
17
|
% controller stop # stop jobs
|
14
18
|
% controller quit # stop bluepilld
|
15
19
|
|
@@ -18,37 +22,85 @@ Environment:
|
|
18
22
|
BLUEPILL_LOGFILE - output log (default: log/bluepill.log)
|
19
23
|
ROBOT_ENVIRONMENT - (default: development)
|
20
24
|
'
|
21
|
-
exit
|
25
|
+
exit(-1)
|
22
26
|
end
|
23
27
|
|
24
28
|
ENV['ROBOT_ENVIRONMENT'] ||= 'development'
|
25
29
|
ENV['BLUEPILL_BASE_DIR'] ||= File.expand_path('run/bluepill')
|
26
|
-
ENV['BLUEPILL_LOGFILE']
|
30
|
+
ENV['BLUEPILL_LOGFILE'] ||= File.expand_path('log/bluepill.log')
|
27
31
|
|
28
|
-
unless File.directory?('config')
|
29
|
-
|
30
|
-
|
31
|
-
raise "Run from root directory"
|
32
|
-
end
|
32
|
+
fail 'bluepill requires config directory' unless File.directory?('config')
|
33
|
+
fail 'bluepill requires run directory' unless File.directory?(File.dirname(ENV['BLUEPILL_BASE_DIR']))
|
34
|
+
fail 'bluepill requires log directory' unless File.directory?(File.dirname(ENV['BLUEPILL_LOGFILE']))
|
33
35
|
|
34
36
|
cmd = 'bluepill'
|
35
37
|
cmd << ' --no-privileged'
|
36
38
|
cmd << " --base-dir #{ENV['BLUEPILL_BASE_DIR']}"
|
37
39
|
cmd << " --logfile #{ENV['BLUEPILL_LOGFILE']}"
|
38
40
|
|
39
|
-
|
41
|
+
case ARGV[0].downcase
|
42
|
+
when 'boot'
|
40
43
|
fn = 'config/bluepill.rb' # allow override
|
41
|
-
unless File.file?(fn)
|
42
|
-
require 'robot-controller'
|
43
|
-
fn = RobotController.bluepill_config
|
44
|
-
end
|
44
|
+
fn = RobotController.bluepill_config unless File.file?(fn)
|
45
45
|
if File.file?(fn)
|
46
|
-
puts "Loading #{fn}"
|
46
|
+
# puts "Loading #{fn}"
|
47
47
|
exec "#{cmd} load #{fn}"
|
48
48
|
# NOTREACHED
|
49
49
|
end
|
50
50
|
puts "ERROR: Cannot find bluepill configuration file for #{ENV['ROBOT_ENVIRONMENT']}"
|
51
|
-
exit
|
51
|
+
exit(-1)
|
52
|
+
when 'verify'
|
53
|
+
verbose = (ARGV[1] == '--verbose')
|
54
|
+
|
55
|
+
# load list of all possible robots
|
56
|
+
robots = YAML.load_file('config/robots.yml')
|
57
|
+
fail ArgumentError unless robots.is_a? Array
|
58
|
+
|
59
|
+
# determine how many processes should be running for each robot
|
60
|
+
running = {}
|
61
|
+
robots.each do |robot|
|
62
|
+
running[robot] = 0
|
63
|
+
end
|
64
|
+
RobotController::Parser.load("robots_#{ENV['ROBOT_ENVIRONMENT']}.yml").each do |h|
|
65
|
+
running[h[:robot]] = h[:n]
|
66
|
+
end
|
67
|
+
|
68
|
+
# verify that all robots running are known to the config/robots.yml
|
69
|
+
running.each_key do |robot|
|
70
|
+
if !robots.include?(robot)
|
71
|
+
puts "ERROR: '#{robot}' robot is unknown to the suite. Check config/robots.yml"
|
72
|
+
running.delete(robot)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# verify suite
|
77
|
+
verify = RobotController::Verify.new(running)
|
78
|
+
begin
|
79
|
+
statuses = verify.verify
|
80
|
+
rescue => e
|
81
|
+
puts "ERROR: Cannot run verification for any robots. #{e.class}: #{e.message}"
|
82
|
+
exit(-1)
|
83
|
+
end
|
84
|
+
|
85
|
+
# print output for each status
|
86
|
+
ok = true
|
87
|
+
puts 'ERROR: No robots to verify?' if statuses.size == 0
|
88
|
+
statuses.each_pair do |robot, status|
|
89
|
+
case status[:state]
|
90
|
+
when :up
|
91
|
+
puts "OK: #{robot} is up (#{status[:running]} running)" if verbose
|
92
|
+
when :not_enabled
|
93
|
+
puts "OK: #{robot} is not enabled (#{status[:running]} running)" if verbose
|
94
|
+
when :down
|
95
|
+
ok = false
|
96
|
+
puts "ERROR: #{robot} is down (#{status[:running]} of #{running[robot]} running)"
|
97
|
+
else
|
98
|
+
ok = false
|
99
|
+
puts "ERROR: #{robot} cannot be verified (state=#{status[:state]})"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
puts 'OK' if ok && !verbose && statuses.size > 0
|
103
|
+
exit(0)
|
52
104
|
else
|
53
105
|
exec "#{cmd} #{ARGV.join(' ')}"
|
54
106
|
# NOTREACHED
|
data/config/robots.yml
ADDED
data/lib/robot-controller.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
# Monitors and controls running workflow robots off of priority queues and within a cluster.
|
2
|
-
|
3
2
|
module RobotController
|
4
3
|
# e.g., `1.2.3`
|
5
4
|
VERSION = File.read(File.join(File.dirname(__FILE__), '..', 'VERSION')).strip
|
@@ -8,4 +7,6 @@ module RobotController
|
|
8
7
|
File.join(File.dirname(__FILE__), 'robot-controller', 'bluepill.rb')
|
9
8
|
end
|
10
9
|
|
11
|
-
|
10
|
+
autoload :Verify, 'robot-controller/verify'
|
11
|
+
autoload :Parser, 'robot-controller/robots'
|
12
|
+
end
|
@@ -1,8 +1,10 @@
|
|
1
|
+
require 'robot-controller'
|
2
|
+
|
3
|
+
# current directory
|
1
4
|
WORKDIR = Dir.pwd
|
2
5
|
|
3
|
-
|
4
|
-
|
5
|
-
ROBOTS = RobotConfigParser.new.load("robots_#{robot_environment}.yml")
|
6
|
+
# setup robots configuration
|
7
|
+
ROBOTS = RobotController::Parser.new.load("robots_#{ENV['ROBOT_ENVIRONMENT'] || 'development'}.yml")
|
6
8
|
#
|
7
9
|
# Expect ROBOTS = [
|
8
10
|
# {:robot => 'x', :queues => ['a', 'b'], :n => 1}
|
@@ -12,12 +14,12 @@ ROBOTS = RobotConfigParser.new.load("robots_#{robot_environment}.yml")
|
|
12
14
|
|
13
15
|
# set application name to parent directory name
|
14
16
|
Bluepill.application File.basename(File.dirname(File.dirname(WORKDIR))),
|
15
|
-
|
17
|
+
log_file: "#{WORKDIR}/log/bluepill.log" do |app|
|
16
18
|
app.working_dir = WORKDIR
|
17
|
-
ROBOTS.each_index do |i|
|
19
|
+
ROBOTS.each_index do |i|
|
18
20
|
ROBOTS[i][:n].to_i.times do |j|
|
19
21
|
# prefix process name with index number to prevent duplicate process names
|
20
|
-
prefix = sprintf(
|
22
|
+
prefix = sprintf('robot%02d_%02d', i + 1, j + 1)
|
21
23
|
app.process("#{prefix}_#{ROBOTS[i][:robot]}") do |process|
|
22
24
|
puts "Creating robot #{process.name}"
|
23
25
|
|
@@ -26,19 +28,21 @@ Bluepill.application File.basename(File.dirname(File.dirname(WORKDIR))),
|
|
26
28
|
|
27
29
|
# use environment for these resque variables
|
28
30
|
process.environment = {
|
31
|
+
'TERM_CHILD' => '1', # TERM, KILL, USR1 sent to worker process if running
|
32
|
+
'RESQUE_TERM_TIMEOUT' => '10.0', # seconds to wait before sending KILL after TERM
|
29
33
|
'QUEUES' => queues,
|
30
34
|
'ROBOT_ENVIRONMENT' => robot_environment,
|
31
35
|
'INTERVAL' => '5'
|
32
36
|
}
|
33
37
|
process.environment['VERBOSE'] = 'yes' if ENV['ROBOT_VERBOSE'] == 'yes'
|
34
38
|
process.environment['VVERBOSE'] = 'yes' if ENV['ROBOT_VVERBOSE'] == 'yes'
|
35
|
-
|
39
|
+
|
36
40
|
# process configuration
|
37
41
|
process.group = robot_environment
|
38
42
|
process.stdout = process.stderr = "#{WORKDIR}/log/#{ROBOTS[i][:robot]}.log"
|
39
43
|
|
40
44
|
# spawn worker processes using robot-controller
|
41
|
-
process.start_command =
|
45
|
+
process.start_command = 'rake environment resque:work'
|
42
46
|
|
43
47
|
# we use bluepill to daemonize the resque workers rather than using
|
44
48
|
# resque's BACKGROUND flag
|
@@ -1,96 +1,93 @@
|
|
1
1
|
require 'yaml'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
#
|
4
|
+
module RobotController
|
5
|
+
#
|
6
|
+
class Parser
|
7
|
+
ROBOT_INSTANCE_MAX = 16
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
raise RuntimeError, "TooManyInstances: #{n} > #{ROBOT_INSTANCE_MAX}"
|
13
|
-
end
|
14
|
-
n = 1 if n < 1
|
15
|
-
n
|
16
|
-
end
|
9
|
+
class << self
|
10
|
+
# main entry point
|
11
|
+
def load(robots_fn, dir = 'config/environments', host = nil)
|
12
|
+
# Validate parameters
|
13
|
+
robots_fn = File.join(dir, robots_fn) if dir
|
14
|
+
fail "FileNotFound: #{robots_fn}" unless File.file?(robots_fn)
|
17
15
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
# parse_lanes('*') == ['*']
|
23
|
-
# parse_lanes('1') == ['1']
|
24
|
-
# parse_lanes('A') == ['A']
|
25
|
-
# parse_lanes('A , B') == ['A', 'B']
|
26
|
-
# parse_lanes('A,B,C') == ['A','B','C']
|
27
|
-
# parse_lanes('A-C,E') == ['A-C', 'E']
|
28
|
-
def parse_lanes(lanes_spec)
|
29
|
-
return ['default'] if lanes_spec.split(/,/).collect {|l| l.strip}.join('') == ''
|
30
|
-
lanes_spec.split(/,/).collect {|l| l.strip }.uniq
|
31
|
-
end
|
16
|
+
# read the YAML file
|
17
|
+
# puts "Loading #{robots_fn}"
|
18
|
+
robots = YAML.load_file(robots_fn)
|
19
|
+
# puts robots
|
32
20
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
queues = []
|
37
|
-
parse_lanes(lanes).each do |i|
|
38
|
-
queues << [robot, i].join('_')
|
39
|
-
end
|
40
|
-
queues
|
41
|
-
end
|
21
|
+
# determine current host
|
22
|
+
host = `hostname -s`.strip unless host
|
23
|
+
# puts host
|
42
24
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
robots_fn = File.join(dir, robots_fn) if dir
|
47
|
-
unless File.file?(robots_fn)
|
48
|
-
raise RuntimeError, "FileNotFound: #{robots_fn}"
|
49
|
-
end
|
50
|
-
|
51
|
-
# read the YAML file
|
52
|
-
puts "Loading #{robots_fn}"
|
53
|
-
robots = YAML.load_file(robots_fn)
|
54
|
-
# puts robots
|
55
|
-
|
56
|
-
# determine current host
|
57
|
-
host = `hostname -s`.strip unless host
|
58
|
-
# puts host
|
25
|
+
# host = 'sul-robots1-dev' # XXX
|
26
|
+
fail "HostMismatch: #{host} not defined in #{robots_fn}" unless robots.include?(host) || robots.include?('*')
|
27
|
+
host = '*' unless robots.include?(host)
|
59
28
|
|
60
|
-
|
61
|
-
|
62
|
-
raise RuntimeError, "HostMismatch: #{host} not defined in #{robots_fn}"
|
63
|
-
end
|
29
|
+
parse_yaml(robots[host])
|
30
|
+
end
|
64
31
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
if
|
71
|
-
|
72
|
-
|
32
|
+
# parse_instances(1) == 1
|
33
|
+
# parse_instances(16) == 16
|
34
|
+
# parse_instances(0) == 1
|
35
|
+
# parse_instances(99) => RuntimeError
|
36
|
+
def parse_instances(n)
|
37
|
+
fail "TooManyInstances: #{n} > #{ROBOT_INSTANCE_MAX}" if n > ROBOT_INSTANCE_MAX
|
38
|
+
n = 1 if n < 1
|
39
|
+
n
|
73
40
|
end
|
74
|
-
|
75
|
-
#
|
76
|
-
|
77
|
-
|
41
|
+
|
42
|
+
# parse_lanes('') == ['default']
|
43
|
+
# parse_lanes(' ') == ['default']
|
44
|
+
# parse_lanes(' , ') == ['default']
|
45
|
+
# parse_lanes(' , ,') == ['default']
|
46
|
+
# parse_lanes('*') == ['*']
|
47
|
+
# parse_lanes('1') == ['1']
|
48
|
+
# parse_lanes('A') == ['A']
|
49
|
+
# parse_lanes('A , B') == ['A', 'B']
|
50
|
+
# parse_lanes('A,B,C') == ['A','B','C']
|
51
|
+
# parse_lanes('A-C,E') == ['A-C', 'E']
|
52
|
+
def parse_lanes(lanes_spec)
|
53
|
+
return ['default'] if lanes_spec.split(/,/).collect(&:strip).join('') == ''
|
54
|
+
lanes_spec.split(/,/).collect(&:strip).uniq
|
78
55
|
end
|
79
|
-
|
80
|
-
|
56
|
+
|
57
|
+
# build_queues('z','A') => ['z_A']
|
58
|
+
# build_queues('z','A,C') => ['z_A', 'z_C']
|
59
|
+
def build_queues(robot, lanes)
|
60
|
+
queues = []
|
61
|
+
parse_lanes(lanes).each do |i|
|
62
|
+
queues << [robot, i].join('_')
|
63
|
+
end
|
64
|
+
queues
|
81
65
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
66
|
+
|
67
|
+
def parse_yaml(robots)
|
68
|
+
# parse YAML lines for host where i is robot[:lane[:instances]]
|
69
|
+
r = []
|
70
|
+
robots.each do |i|
|
71
|
+
robot = i.split(/:/).collect(&:strip)
|
72
|
+
robot.each do |j|
|
73
|
+
fail "SyntaxError: #{i}" if j.strip == ''
|
74
|
+
end
|
75
|
+
|
76
|
+
# add defaults
|
77
|
+
robot << 'default' if robot.size == 1
|
78
|
+
robot << '1' if robot.size == 2
|
79
|
+
|
80
|
+
# build queues for robot instances
|
81
|
+
fail "SyntaxError: #{i}" unless robot.size == 3
|
82
|
+
robot[2] = parse_instances(robot[2].to_i)
|
83
|
+
# puts robot.join(' : ')
|
84
|
+
queues = build_queues(robot[0], robot[1])
|
85
|
+
# puts queues
|
86
|
+
|
87
|
+
r << { robot: robot[0], queues: queues, n: robot[2] }
|
88
|
+
end
|
89
|
+
r
|
86
90
|
end
|
87
|
-
robot[2] = parse_instances(robot[2].to_i)
|
88
|
-
# puts robot.join(' : ')
|
89
|
-
queues = build_queues(robot[0], robot[1])
|
90
|
-
# puts queues
|
91
|
-
|
92
|
-
r << {:robot => robot[0], :queues => queues, :n => robot[2] }
|
93
91
|
end
|
94
|
-
r
|
95
92
|
end
|
96
93
|
end
|
@@ -1 +1 @@
|
|
1
|
-
require 'resque/tasks'
|
1
|
+
require 'resque/tasks'
|
@@ -0,0 +1,209 @@
|
|
1
|
+
# verification class
|
2
|
+
module RobotController
|
3
|
+
# Usage:
|
4
|
+
# RobotController::Verify.new('robot1' => 1, 'robot2' => 2, 'robot3' => 0)
|
5
|
+
# => {
|
6
|
+
# 'robot1': { state: :up, running: 1 },
|
7
|
+
# 'robot2': { state: :down, running: 0 },
|
8
|
+
# 'robot3': { state: :not_enabled, running: 0 }
|
9
|
+
# }
|
10
|
+
# ----
|
11
|
+
#
|
12
|
+
# When no errors are detected, the output looks like so:
|
13
|
+
# % bundle exec controller verify
|
14
|
+
# OK
|
15
|
+
#
|
16
|
+
# % bundle exec controller verify --verbose
|
17
|
+
# OK robot1 is up
|
18
|
+
# OK robot2 is up
|
19
|
+
# OK robot3 is not enabled
|
20
|
+
# OK robot4 is not enabled
|
21
|
+
#
|
22
|
+
# If robot2 were down and robot3 were up, the output would look like so:
|
23
|
+
#
|
24
|
+
# % bundle exec controller verify
|
25
|
+
# ERROR robot2 is down (0 out of 3 processes running)
|
26
|
+
# ERROR robot3 is not enabled but 1 process is running
|
27
|
+
#
|
28
|
+
# % bundle exec controller verify --verbose
|
29
|
+
# OK robot1 is up
|
30
|
+
# ERROR robot2 is down (0 out of 3 processes running)
|
31
|
+
# ERROR robot3 is not enabled but 1 process is running
|
32
|
+
# OK robot4 is not enabled
|
33
|
+
#
|
34
|
+
# The various states are determined as follows:
|
35
|
+
#
|
36
|
+
# If the robot is enabled:
|
37
|
+
# OK: all N processes are running
|
38
|
+
# ERROR: not all N processes are running
|
39
|
+
# If the robot is NOT enabled:
|
40
|
+
# OK: no processes are running
|
41
|
+
# ERROR: 1 or more processes are running
|
42
|
+
# If the robot is unknown by the suite:
|
43
|
+
# ERROR: always
|
44
|
+
class Verify
|
45
|
+
attr_reader :robots
|
46
|
+
|
47
|
+
# @param [Hash] nprocesses expected number of processes for all robots
|
48
|
+
def initialize(nprocesses)
|
49
|
+
fail ArgumentError if nprocesses.nil? || !nprocesses.is_a?(Hash)
|
50
|
+
fail ArgumentError, 'Empty argument' if nprocesses.size == 0
|
51
|
+
@running = nprocesses
|
52
|
+
@robots = @running.each_key.to_a
|
53
|
+
@status = nil
|
54
|
+
end
|
55
|
+
|
56
|
+
# @param [Boolean] reload forces a reload of status information
|
57
|
+
# @return [Hash<Hash>] status of all robots
|
58
|
+
# {
|
59
|
+
# 'robot1' : {
|
60
|
+
# state: :up,
|
61
|
+
# running: 2
|
62
|
+
# },
|
63
|
+
# 'robot2' : {
|
64
|
+
# state: :down,
|
65
|
+
# running: 0
|
66
|
+
# },
|
67
|
+
# 'robot3' : {
|
68
|
+
# state: :not_enabled,
|
69
|
+
# running: 0
|
70
|
+
# }
|
71
|
+
# }
|
72
|
+
def verify(reload = true)
|
73
|
+
@status = nil if reload
|
74
|
+
r = {}
|
75
|
+
robots.each do |robot|
|
76
|
+
r[robot] = robot_status(robot)
|
77
|
+
end
|
78
|
+
r
|
79
|
+
end
|
80
|
+
|
81
|
+
# @param [String] robot name
|
82
|
+
# @return [Integer] number of running processes expected otherwise nil
|
83
|
+
def running(robot)
|
84
|
+
@running[robot]
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
# @param [String] robot name
|
90
|
+
# @return [Hash] { state: :up | :down | :not_enabled, running: n }
|
91
|
+
def robot_status(robot)
|
92
|
+
if status[robot].nil?
|
93
|
+
fail "ERROR: No status information for #{robot}"
|
94
|
+
else
|
95
|
+
status[robot]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# @return [Hash] status
|
101
|
+
# {
|
102
|
+
# 'robot1': {
|
103
|
+
# state: :up,
|
104
|
+
# running: 1
|
105
|
+
# },
|
106
|
+
# 'robot2': ...,
|
107
|
+
# 'robot3': ...
|
108
|
+
# }
|
109
|
+
def status
|
110
|
+
if @status.nil?
|
111
|
+
# run controller_status to get all robot states
|
112
|
+
states = self.class.parse_status_output(controller_status)
|
113
|
+
fail 'No output from controller status' unless states.size > 0
|
114
|
+
|
115
|
+
# convert states into status metrics for all robots with state
|
116
|
+
@status = {}
|
117
|
+
robots.each do |robot|
|
118
|
+
matches = states.select { |state| state[:robot] == robot }
|
119
|
+
@status[robot] = self.class.consolidate_states_into_status(matches)
|
120
|
+
end
|
121
|
+
|
122
|
+
# cross-check against all robots
|
123
|
+
robots.each do |robot|
|
124
|
+
if @status[robot].nil?
|
125
|
+
@status[robot] = {
|
126
|
+
state: (running(robot) == 0 ? :not_enabled : :unknown),
|
127
|
+
running: 0
|
128
|
+
}
|
129
|
+
elsif @status[robot][:running] != running(robot)
|
130
|
+
@status[robot][:state] = :down
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
@status
|
135
|
+
end
|
136
|
+
|
137
|
+
# Runs 'bundle exec controller status' and returns/yields output
|
138
|
+
# @yield [Array[String]] output
|
139
|
+
def controller_status
|
140
|
+
IO.popen('bundle exec controller status 2>&1').readlines.map(&:strip)
|
141
|
+
end
|
142
|
+
|
143
|
+
# -- Class methods --
|
144
|
+
class << self
|
145
|
+
#
|
146
|
+
# @param [Array<String>] output from bundle exec controller status
|
147
|
+
# @return [Array<Hash>] status
|
148
|
+
# [
|
149
|
+
# {
|
150
|
+
# robot: 'robot123',
|
151
|
+
# nth: 1
|
152
|
+
# pid: 123
|
153
|
+
# state: :down | :up
|
154
|
+
# },
|
155
|
+
# robot: 'robot456',
|
156
|
+
# nth: 1
|
157
|
+
# pid: 456
|
158
|
+
# state: :down | :up
|
159
|
+
# }
|
160
|
+
# ]
|
161
|
+
def parse_status_output(output)
|
162
|
+
output.inject([]) { |a, e| a << parse_status_line(e) }.compact
|
163
|
+
end
|
164
|
+
|
165
|
+
#
|
166
|
+
# @param [String] line as bluepill outputs them...
|
167
|
+
# robot01_01_dor_gisAssemblyWF_assign-placenames(pid:29481): up
|
168
|
+
# robot02_01_dor_gisAssemblyWF_author-data(pid:29697): down
|
169
|
+
# robot03_01_dor_gisAssemblyWF_author-metadata(pid:29512): unmonitored
|
170
|
+
#
|
171
|
+
# @return [Hash] status {
|
172
|
+
# robot: 'robot123',
|
173
|
+
# nth: 1
|
174
|
+
# pid: 123
|
175
|
+
# state: :down | :up
|
176
|
+
# }
|
177
|
+
def parse_status_line(line)
|
178
|
+
if line =~ /^robot\d\d_(\d\d)_(.+)\(pid:(\d+)\):\s+(.+)$/
|
179
|
+
return {
|
180
|
+
nth: Regexp.last_match[1].to_i,
|
181
|
+
robot: Regexp.last_match[2].to_s,
|
182
|
+
pid: Regexp.last_match[3].to_i,
|
183
|
+
state: (Regexp.last_match[4].to_s == 'up') ? :up : :down
|
184
|
+
}
|
185
|
+
end
|
186
|
+
nil
|
187
|
+
end
|
188
|
+
|
189
|
+
# reduces individuals states into a single status
|
190
|
+
def consolidate_states_into_status(statuses)
|
191
|
+
if statuses.is_a?(Array) && statuses.size > 0
|
192
|
+
# XXX: assumes all statuses are for the same robot
|
193
|
+
running = 0
|
194
|
+
state = :up
|
195
|
+
statuses.each do |status|
|
196
|
+
running += 1 if status[:state] == :up
|
197
|
+
state = :down unless status[:state] == :up
|
198
|
+
end
|
199
|
+
{
|
200
|
+
state: state,
|
201
|
+
running: running
|
202
|
+
}
|
203
|
+
else
|
204
|
+
fail 'No information from bundle exec controller status'
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
data/lib/tasks/doc.rake
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
desc
|
2
|
-
task :
|
1
|
+
desc 'Generate RDoc'
|
2
|
+
task doc: ['doc:generate']
|
3
3
|
|
4
4
|
namespace :doc do
|
5
5
|
project_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
|
@@ -10,27 +10,26 @@ namespace :doc do
|
|
10
10
|
require 'yard/rake/yardoc_task'
|
11
11
|
|
12
12
|
YARD::Rake::YardocTask.new(:generate) do |yt|
|
13
|
-
yt.files = Dir.glob(File.join(project_root, 'lib', '*.rb')) +
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
yt.files = Dir.glob(File.join(project_root, 'lib', '*.rb')) +
|
14
|
+
Dir.glob(File.join(project_root, 'lib', '**', '*.rb')) +
|
15
|
+
[File.join(project_root, 'README.rdoc')]
|
16
|
+
|
17
17
|
yt.options = ['--output-dir', doc_destination, '--readme', 'README.md']
|
18
18
|
end
|
19
19
|
rescue LoadError
|
20
|
-
desc
|
20
|
+
desc 'Generate YARD Documentation'
|
21
21
|
task :generate do
|
22
|
-
abort
|
22
|
+
abort 'Please install the YARD gem to generate rdoc.'
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
desc
|
26
|
+
desc 'Remove generated documenation'
|
27
27
|
task :clean do
|
28
|
-
rm_r doc_destination if File.
|
28
|
+
rm_r doc_destination if File.exist?(doc_destination)
|
29
29
|
end
|
30
|
-
|
31
30
|
end
|
32
31
|
|
33
|
-
desc
|
32
|
+
desc 'Build Yard documentation'
|
34
33
|
task :yard do
|
35
34
|
YARD::Rake::YardocTask.new do |t|
|
36
35
|
t.files = ['lib/**/*.rb', 'bin/**/*.rb']
|
data/robot-controller.gemspec
CHANGED
@@ -1,36 +1,35 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
lib = File.expand_path('../lib/', __FILE__)
|
3
|
-
|
3
|
+
$LOAD_PATH.unshift lib unless $LOAD_PATH.include?(lib)
|
4
4
|
|
5
5
|
require 'robot-controller'
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
|
-
s.name =
|
8
|
+
s.name = 'robot-controller'
|
9
9
|
s.version = RobotController::VERSION
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
|
-
s.authors = [
|
12
|
-
s.email = [
|
13
|
-
s.homepage =
|
14
|
-
s.summary =
|
11
|
+
s.authors = ['Darren Hardy']
|
12
|
+
s.email = ['drh@stanford.edu']
|
13
|
+
s.homepage = 'http://github.com/sul-dlss/robot-controller'
|
14
|
+
s.summary = 'Monitors and controls running workflow robots off of priority queues and within a cluster'
|
15
15
|
s.has_rdoc = true
|
16
16
|
s.licenses = ['ALv2', 'Stanford University']
|
17
|
-
|
17
|
+
|
18
18
|
s.files = `git ls-files`.split("\n")
|
19
19
|
s.test_files = `git ls-files -- spec/*`.split("\n")
|
20
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
21
21
|
s.require_paths = ['lib']
|
22
|
-
|
23
|
-
s.required_rubygems_version =
|
24
|
-
|
25
|
-
s.add_dependency 'bluepill', '
|
22
|
+
|
23
|
+
s.required_rubygems_version = '>= 1.3.6'
|
24
|
+
|
25
|
+
s.add_dependency 'bluepill', '0.0.68' # pin bluepill to prevent nil status output regression
|
26
26
|
s.add_dependency 'resque', '~> 1.25.2'
|
27
27
|
s.add_dependency 'rake', '~> 10.3.2'
|
28
|
-
|
28
|
+
|
29
29
|
s.add_development_dependency 'awesome_print'
|
30
30
|
s.add_development_dependency 'pry'
|
31
31
|
s.add_development_dependency 'rspec'
|
32
32
|
s.add_development_dependency 'redcarpet' # provides Markdown
|
33
33
|
s.add_development_dependency 'version_bumper'
|
34
34
|
s.add_development_dependency 'yard'
|
35
|
-
|
36
35
|
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/unit/robots_spec.rb
CHANGED
@@ -1,87 +1,84 @@
|
|
1
1
|
require 'robot-controller/robots'
|
2
2
|
|
3
|
-
describe
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
}
|
3
|
+
describe RobotController::Parser do
|
4
|
+
context 'simple' do
|
5
|
+
subject do
|
6
|
+
RobotController::Parser.load('standard.yml', 'spec/fixtures', 'host1')
|
7
|
+
end
|
9
8
|
|
10
|
-
it
|
11
|
-
|
12
|
-
{:
|
13
|
-
{:
|
14
|
-
{:
|
9
|
+
it 'pass1' do
|
10
|
+
expect(subject).to eq [
|
11
|
+
{ robot: 'X', queues: ['X_default'], n: 1 },
|
12
|
+
{ robot: 'Y', queues: ['Y_B'], n: 1 },
|
13
|
+
{ robot: 'Z', queues: ['Z_C'], n: 3 }
|
15
14
|
]
|
16
|
-
end
|
15
|
+
end
|
17
16
|
end
|
18
|
-
|
19
|
-
context "expanded" do
|
20
|
-
subject {
|
21
|
-
RobotConfigParser.new.load('standard.yml', 'spec/fixtures', 'host2')
|
22
|
-
}
|
23
17
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
18
|
+
context 'expanded' do
|
19
|
+
subject do
|
20
|
+
RobotController::Parser.load('standard.yml', 'spec/fixtures', 'host2')
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'pass2' do
|
24
|
+
expect(subject).to eq [
|
25
|
+
{ robot: 'A', queues: ['A_*'], n: 1 },
|
26
|
+
{ robot: 'B', queues: %w(B_X B_Y), n: 1 },
|
27
|
+
{ robot: 'C', queues: %w(C_X C_Y C_Z), n: 5 },
|
28
|
+
{ robot: 'D', queues: ['D_default'], n: 1 }
|
30
29
|
]
|
31
|
-
end
|
30
|
+
end
|
32
31
|
end
|
33
|
-
|
34
|
-
context
|
35
|
-
subject
|
36
|
-
|
37
|
-
|
38
|
-
it
|
32
|
+
|
33
|
+
context 'parse_instances' do
|
34
|
+
subject do
|
35
|
+
RobotController::Parser
|
36
|
+
end
|
37
|
+
it 'valid inputs' do
|
39
38
|
expect(subject.parse_instances(0)).to eq 1
|
40
39
|
expect(subject.parse_instances(1)).to eq 1
|
41
40
|
expect(subject.parse_instances(16)).to eq 16
|
42
41
|
end
|
43
|
-
|
44
|
-
it
|
45
|
-
expect
|
42
|
+
|
43
|
+
it 'invalid inputs' do
|
44
|
+
expect do
|
46
45
|
subject.parse_instances(17)
|
47
|
-
|
46
|
+
end.to raise_error RuntimeError
|
48
47
|
end
|
49
48
|
end
|
50
|
-
|
51
|
-
context
|
52
|
-
subject
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
it
|
49
|
+
|
50
|
+
context 'parse_lanes' do
|
51
|
+
subject do
|
52
|
+
RobotController::Parser
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'valid inputs' do
|
57
56
|
expect(subject.parse_lanes('*')).to eq ['*']
|
58
57
|
expect(subject.parse_lanes('')).to eq ['default']
|
59
58
|
expect(subject.parse_lanes('default')).to eq ['default']
|
60
59
|
expect(subject.parse_lanes('A')).to eq ['A']
|
61
|
-
expect(subject.parse_lanes('A,B')).to eq
|
60
|
+
expect(subject.parse_lanes('A,B')).to eq %w(A B)
|
62
61
|
end
|
63
|
-
|
64
|
-
it
|
62
|
+
|
63
|
+
it 'tricky inputs' do
|
65
64
|
expect(subject.parse_lanes(' ')).to eq ['default']
|
66
65
|
expect(subject.parse_lanes(' , ')).to eq ['default']
|
67
66
|
expect(subject.parse_lanes(' ,,')).to eq ['default']
|
68
|
-
expect(subject.parse_lanes('A , B')).to eq
|
67
|
+
expect(subject.parse_lanes('A , B')).to eq %w(A B)
|
69
68
|
expect(subject.parse_lanes('A-B')).to eq ['A-B']
|
70
|
-
expect(subject.parse_lanes('A,B,A')).to eq
|
69
|
+
expect(subject.parse_lanes('A,B,A')).to eq %w(A B)
|
71
70
|
end
|
72
|
-
|
73
71
|
end
|
74
|
-
|
75
|
-
context
|
76
|
-
subject
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
it
|
81
|
-
expect(subject.build_queues('z','*')).to eq ['z_*']
|
82
|
-
expect(subject.build_queues('z','default')).to eq ['z_default']
|
83
|
-
expect(subject.build_queues('z','A,B,C')).to eq
|
72
|
+
|
73
|
+
context 'build_queues' do
|
74
|
+
subject do
|
75
|
+
RobotController::Parser
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'valid inputs' do
|
79
|
+
expect(subject.build_queues('z', '*')).to eq ['z_*']
|
80
|
+
expect(subject.build_queues('z', 'default')).to eq ['z_default']
|
81
|
+
expect(subject.build_queues('z', 'A,B,C')).to eq %w(z_A z_B z_C)
|
84
82
|
end
|
85
|
-
|
86
83
|
end
|
87
|
-
end
|
84
|
+
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require 'robot-controller'
|
2
|
+
|
3
|
+
describe RobotController::Verify do
|
4
|
+
context 'initialization' do
|
5
|
+
subject do
|
6
|
+
RobotController::Verify.new('robot1' => 1, 'robot2' => 2, 'robot3' => 0)
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'has correct robots' do
|
10
|
+
expect(subject.robots).to eq %w(robot1 robot2 robot3)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has correct enabled' do
|
14
|
+
expect(subject.running('robot1')).to eq 1
|
15
|
+
expect(subject.running('robot2')).to eq 2
|
16
|
+
expect(subject.running('robot3')).to eq 0
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'handles empty parameters' do
|
20
|
+
expect { RobotController::Verify.new }.to raise_error(ArgumentError)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'parse_status methods' do
|
25
|
+
subject do
|
26
|
+
RobotController::Verify.new('dor_gisAssemblyWF_assign-placenames' => 1)
|
27
|
+
end
|
28
|
+
it 'parse single line' do
|
29
|
+
expect(subject.class.parse_status_line('robot01_01_dor_gisAssemblyWF_assign-placenames(pid:29481): up')).to eq(
|
30
|
+
nth: 1,
|
31
|
+
pid: 29481,
|
32
|
+
robot: 'dor_gisAssemblyWF_assign-placenames',
|
33
|
+
state: :up
|
34
|
+
)
|
35
|
+
expect(subject.class.parse_status_line('robot01_02_dor_gisAssemblyWF_assign-placenames(pid:29482): starting')).to eq(
|
36
|
+
nth: 2,
|
37
|
+
pid: 29482,
|
38
|
+
robot: 'dor_gisAssemblyWF_assign-placenames',
|
39
|
+
state: :down
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'parses a single line with error' do
|
44
|
+
expect(subject.class.parse_status_line('garbage')).to be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'parse all lines' do
|
48
|
+
expect(subject.class.parse_status_output([
|
49
|
+
'robot01_01_dor_gisAssemblyWF_assign-placenames(pid:29481): starting',
|
50
|
+
'robot01_02_dor_gisAssemblyWF_assign-placenames(pid:29482): unmonitored',
|
51
|
+
'robot01_03_dor_gisAssemblyWF_assign-placenames(pid:29483): up'])).to eq(
|
52
|
+
[
|
53
|
+
{
|
54
|
+
nth: 1,
|
55
|
+
pid: 29481,
|
56
|
+
robot: 'dor_gisAssemblyWF_assign-placenames',
|
57
|
+
state: :down
|
58
|
+
}, {
|
59
|
+
nth: 2,
|
60
|
+
pid: 29482,
|
61
|
+
robot: 'dor_gisAssemblyWF_assign-placenames',
|
62
|
+
state: :down
|
63
|
+
}, {
|
64
|
+
nth: 3,
|
65
|
+
pid: 29483,
|
66
|
+
robot: 'dor_gisAssemblyWF_assign-placenames',
|
67
|
+
state: :up
|
68
|
+
}
|
69
|
+
]
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'verify method with single process' do
|
75
|
+
subject do
|
76
|
+
RobotController::Verify.new('dor_gisAssemblyWF_assign-placenames' => 1)
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'runs controller status for up' do
|
80
|
+
allow(subject).to receive(:controller_status).and_return([
|
81
|
+
'robot01_01_dor_gisAssemblyWF_assign-placenames(pid:29483): up'
|
82
|
+
])
|
83
|
+
expect(subject.verify).to eq(
|
84
|
+
'dor_gisAssemblyWF_assign-placenames' => { state: :up, running: 1 }
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'runs controller status for down' do
|
89
|
+
allow(subject).to receive(:controller_status).and_return([
|
90
|
+
'robot01_01_dor_gisAssemblyWF_assign-placenames(pid:29481): down'
|
91
|
+
])
|
92
|
+
expect(subject.verify).to eq(
|
93
|
+
'dor_gisAssemblyWF_assign-placenames' => { state: :down, running: 0 }
|
94
|
+
)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'runs controller status even when broken' do
|
98
|
+
allow(subject).to receive(:controller_status).and_return([])
|
99
|
+
expect { subject.verify }.to raise_error(RuntimeError)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'verify method with multiple processes' do
|
104
|
+
subject do
|
105
|
+
RobotController::Verify.new('dor_gisAssemblyWF_assign-placenames' => 3)
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'runs controller status for up' do
|
109
|
+
allow(subject).to receive(:controller_status).and_return([
|
110
|
+
'robot01_01_dor_gisAssemblyWF_assign-placenames(pid:29483): up',
|
111
|
+
'robot01_02_dor_gisAssemblyWF_assign-placenames(pid:29484): up',
|
112
|
+
'robot02_01_dor_gisAssemblyWF_foobar(pid:29485): up',
|
113
|
+
'robot01_02_dor_gisAssemblyWF_assign-placenames(pid:29486): up'
|
114
|
+
])
|
115
|
+
expect(subject.verify).to eq(
|
116
|
+
'dor_gisAssemblyWF_assign-placenames' => { state: :up, running: 3 }
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'runs controller status for down' do
|
121
|
+
allow(subject).to receive(:controller_status).and_return([
|
122
|
+
'robot01_01_dor_gisAssemblyWF_assign-placenames(pid:29481): starting',
|
123
|
+
'robot01_02_dor_gisAssemblyWF_assign-placenames(pid:29482): unmonitored',
|
124
|
+
'robot01_03_dor_gisAssemblyWF_assign-placenames(pid:29483): up',
|
125
|
+
'robot02_01_dor_gisAssemblyWF_foobar(pid:29484): up',
|
126
|
+
'robot02_02_dor_gisAssemblyWF_foobar(pid:29485): down'
|
127
|
+
])
|
128
|
+
expect(subject.verify).to eq(
|
129
|
+
'dor_gisAssemblyWF_assign-placenames' => { state: :down, running: 1 }
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'runs controller status for running mismatch' do
|
134
|
+
allow(subject).to receive(:controller_status).and_return([
|
135
|
+
'robot01_01_dor_gisAssemblyWF_assign-placenames(pid:29483): up',
|
136
|
+
'robot01_02_dor_gisAssemblyWF_assign-placenames(pid:29484): up',
|
137
|
+
'robot02_01_dor_gisAssemblyWF_foobar(pid:29486): up'
|
138
|
+
])
|
139
|
+
expect(subject.verify).to eq(
|
140
|
+
'dor_gisAssemblyWF_assign-placenames' => { state: :down, running: 2 }
|
141
|
+
)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: robot-controller
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.beta1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darren Hardy
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-06-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bluepill
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.0.
|
19
|
+
version: 0.0.68
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.0.
|
26
|
+
version: 0.0.68
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: resque
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -145,23 +145,29 @@ extensions: []
|
|
145
145
|
extra_rdoc_files: []
|
146
146
|
files:
|
147
147
|
- .gitignore
|
148
|
+
- .rubocop.yml
|
149
|
+
- .rubocop_todo.yml
|
148
150
|
- Gemfile
|
149
151
|
- LICENSE
|
150
152
|
- README.md
|
151
153
|
- Rakefile
|
152
154
|
- VERSION
|
153
155
|
- bin/controller
|
156
|
+
- config/environments/robots_development.yml
|
157
|
+
- config/robots.yml
|
154
158
|
- example/config/environments/robots_development.yml
|
155
159
|
- example/lib/tasks/environment.rake
|
156
160
|
- lib/robot-controller.rb
|
157
161
|
- lib/robot-controller/bluepill.rb
|
158
162
|
- lib/robot-controller/robots.rb
|
159
163
|
- lib/robot-controller/tasks.rb
|
164
|
+
- lib/robot-controller/verify.rb
|
160
165
|
- lib/tasks/doc.rake
|
161
166
|
- robot-controller.gemspec
|
162
167
|
- spec/fixtures/standard.yml
|
163
168
|
- spec/spec_helper.rb
|
164
169
|
- spec/unit/robots_spec.rb
|
170
|
+
- spec/unit/verify_spec.rb
|
165
171
|
homepage: http://github.com/sul-dlss/robot-controller
|
166
172
|
licenses:
|
167
173
|
- ALv2
|
@@ -183,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
183
189
|
version: 1.3.6
|
184
190
|
requirements: []
|
185
191
|
rubyforge_project:
|
186
|
-
rubygems_version: 2.
|
192
|
+
rubygems_version: 2.4.5
|
187
193
|
signing_key:
|
188
194
|
specification_version: 4
|
189
195
|
summary: Monitors and controls running workflow robots off of priority queues and
|
@@ -192,4 +198,5 @@ test_files:
|
|
192
198
|
- spec/fixtures/standard.yml
|
193
199
|
- spec/spec_helper.rb
|
194
200
|
- spec/unit/robots_spec.rb
|
201
|
+
- spec/unit/verify_spec.rb
|
195
202
|
has_rdoc: true
|