robot-controller 1.0.2 → 2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|