robot-controller 2.0.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b2f749801d61882786e5c4d20fed0f0d25e49330
4
- data.tar.gz: 6d33a112ceecfc0e489d6a62ac1ede016fef30ce
3
+ metadata.gz: 4aecc4e3e0802963a8bde215f896ae5cf46c1eb8
4
+ data.tar.gz: c3e49d1dfb090a7bd99d000616990b4ffcdf4b7e
5
5
  SHA512:
6
- metadata.gz: a13063c8cf25fb05dcb0dd9fab4c0060cd9b2f25c2ae5702044089f98369f112b82f40843cdc61b198a5181fa38e2f8a3131354c752ceac48c23da1c862515f7
7
- data.tar.gz: f15b215c9e5a88645ca4ca7ce351fa589f11b09ab41c6d283d91f180ddc62d299f411d777f8bc0855cf5521612149c6d616a8de5bca0850163630ca81c8eed02
6
+ metadata.gz: 13ef598d961e075c20b26919576dfaed18c850e41ecf9be31ad799b2a6cd6298f250a80e1c19b93a8e35aa5776c9c3cd4c1ebc92a6e453b337b99f5d47854c9d
7
+ data.tar.gz: 0858eef295cd386ecfd652b10ad28fd8b7486dd682ed6c9058016cc4955bab4798addd85a79e2ddf1a6ba79fa8410e655833243d1bce89ee13a6ecb0367294d3
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.rubocop_todo.yml CHANGED
@@ -1,31 +1,35 @@
1
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.
2
+ # on 2015-07-01 12:52:10 -0700 using RuboCop version 0.32.1.
3
3
  # The point is for the user to remove these configuration records
4
4
  # one by one as the offenses are removed from the code base.
5
5
  # Note that changes in the inspected code, or installation of new
6
6
  # versions of RuboCop, may require this file to be generated again.
7
7
 
8
- # Offense count: 1
8
+ # Offense count: 3
9
9
  Metrics/AbcSize:
10
- Max: 37
10
+ Max: 87
11
11
 
12
12
  # Offense count: 1
13
+ Metrics/BlockNesting:
14
+ Max: 4
15
+
16
+ # Offense count: 2
13
17
  Metrics/CyclomaticComplexity:
14
- Max: 9
18
+ Max: 23
15
19
 
16
- # Offense count: 17
20
+ # Offense count: 34
17
21
  # Configuration parameters: AllowURI, URISchemes.
18
22
  Metrics/LineLength:
19
- Max: 130
23
+ Max: 123
20
24
 
21
- # Offense count: 1
25
+ # Offense count: 5
22
26
  # Configuration parameters: CountComments.
23
27
  Metrics/MethodLength:
24
- Max: 22
28
+ Max: 88
25
29
 
26
- # Offense count: 1
30
+ # Offense count: 2
27
31
  Metrics/PerceivedComplexity:
28
- Max: 9
32
+ Max: 21
29
33
 
30
34
  # Offense count: 1
31
35
  # Configuration parameters: Exclude.
@@ -41,8 +45,3 @@ Style/FormatString:
41
45
  # Cop supports --auto-correct.
42
46
  Style/NumericLiterals:
43
47
  MinDigits: 6
44
-
45
- # Offense count: 1
46
- # Configuration parameters: Methods.
47
- Style/SingleLineBlockParams:
48
- Enabled: false
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ notifications:
2
+ email: false
3
+
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.2
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ [![Build Status](https://travis-ci.org/sul-dlss/robot-controller.svg?branch=master)](https://travis-ci.org/sul-dlss/robot-controller)
2
+ [![Dependency Status](https://gemnasium.com/sul-dlss/robot-controller.svg)](https://gemnasium.com/sul-dlss/robot-controller)
3
+
4
+
1
5
  robot-controller
2
6
  ================
3
7
 
@@ -19,7 +23,7 @@ Create the following configuration files based on the examples in `example/confi
19
23
 
20
24
  Then to use the controller to boot the robots:
21
25
 
22
- % bundle exec controller boot
26
+ bundle exec controller boot
23
27
 
24
28
  If you want to *override* the bluepill configuration but still use the
25
29
  controller (though NOT recommended), then add:
@@ -34,12 +38,12 @@ controller (though NOT recommended), then add:
34
38
  controller [--help]
35
39
 
36
40
  Example:
37
- % controller boot # start bluepilld and jobs
38
- % controller status # check on status of jobs
39
- % controller verify # verify robots are running as configured
40
- % controller log robot01_01_dor_accessionWF_descriptive-metadata # view log for worker
41
- % controller stop # stop jobs
42
- % controller quit # stop bluepilld
41
+ controller boot # start bluepilld and jobs
42
+ controller status # check on status of jobs
43
+ controller verify # verify robots are running as configured
44
+ controller log robot01_01_dor_accessionWF_descriptive-metadata # view log for worker
45
+ controller stop # stop jobs
46
+ controller quit # stop bluepilld
43
47
 
44
48
  Environment:
45
49
  BLUEPILL_BASEDIR - where bluepill stores its state (default: run/bluepill)
@@ -51,6 +55,7 @@ controller (though NOT recommended), then add:
51
55
  * `v1.0.0`: Initial version
52
56
  * `v1.0.1`: Add 'rake' as dependency
53
57
  * `v2.0.0`: Added 'verify' command
58
+ * `v2.0.1`: Added rake robots:verify and support for whenever gem
54
59
 
55
60
  ### `verify` command
56
61
 
@@ -92,4 +97,23 @@ The various states are determined as follows:
92
97
  - If the robot is unknown by the suite:
93
98
  - `ERROR`: always
94
99
 
95
- NOTE: The queues on which the robots are running are NOT verified.
100
+ NOTE: The queues on which the robots are running are NOT verified.
101
+
102
+ ### Running `verify` command via crontab
103
+
104
+ In `Capfile` add:
105
+
106
+ require 'whenever/capistrano'
107
+
108
+ In `config/deploy.rb` add:
109
+
110
+ set :whenever_identifier, ->{ "#{fetch(:application)}_#{fetch(:stage)}" }
111
+
112
+ In `config/schedule.rb` add:
113
+
114
+ every 5.minutes do
115
+ # cannot use :output with Hash/String because we don't want append behavior
116
+ set :output, proc { '> log/verify.log 2> log/cron.log' }
117
+ set :environment_variable, 'ROBOT_ENVIRONMENT'
118
+ rake 'robots:verify'
119
+ end
data/Rakefile CHANGED
@@ -1,13 +1,9 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
+ require 'rspec/core/rake_task'
3
4
  require 'version_bumper'
4
5
  require 'robot-controller/tasks'
5
6
 
6
7
  Dir.glob('lib/tasks/*.rake').each { |r| import r }
7
8
 
8
- require 'rspec/core/rake_task'
9
-
10
- desc 'Run specs'
11
- RSpec::Core::RakeTask.new(:spec)
12
-
13
- task default: [:yard, :spec]
9
+ task default: [:spec, :rubocop, :yard]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0
1
+ 2.0.1
data/bin/controller CHANGED
@@ -2,110 +2,121 @@
2
2
  require 'yaml'
3
3
  require 'robot-controller'
4
4
 
5
- if ARGV.size == 0
6
- puts '
7
- Usage: controller ( boot | quit )
8
- controller ( start | status | stop | restart | log ) [worker]
9
- controller verify [--verbose]
10
- controller [--help]
5
+ # bin/controller tool
6
+ module RobotController
7
+ #
8
+ class CLI
9
+ # meta command
10
+ def self.run(args)
11
+ if args.size == 0 || args.first == '--help'
12
+ puts '
13
+ Usage: controller ( boot | quit )
14
+ controller ( start | status | stop | restart | log ) [worker]
15
+ controller verify [--verbose]
16
+ controller [--help]
11
17
 
12
- Example:
13
- % controller boot # start bluepilld and jobs
14
- % controller status # check on status of jobs
15
- % controller log dor_accessionWF_descriptive-metadata # view log for worker
16
- % controller verify # verify robots are running as configured
17
- % controller stop # stop jobs
18
- % controller quit # stop bluepilld
18
+ Example:
19
+ % controller boot # start bluepilld and jobs
20
+ % controller status # check on status of jobs
21
+ % controller log dor_accessionWF_descriptive-metadata # view log for worker
22
+ % controller verify # verify robots are running as configured
23
+ % controller stop # stop jobs
24
+ % controller quit # stop bluepilld
19
25
 
20
- Environment:
21
- BLUEPILL_BASEDIR - where bluepill stores its state (default: run/bluepill)
22
- BLUEPILL_LOGFILE - output log (default: log/bluepill.log)
23
- ROBOT_ENVIRONMENT - (default: development)
24
- '
25
- exit(-1)
26
- end
26
+ Environment:
27
+ BLUEPILL_BASEDIR - where bluepill stores its state (default: run/bluepill)
28
+ BLUEPILL_LOGFILE - output log (default: log/bluepill.log)
29
+ ROBOT_ENVIRONMENT - (default: development)
30
+ '
31
+ exit(-1)
32
+ end
27
33
 
28
- ENV['ROBOT_ENVIRONMENT'] ||= 'development'
29
- ENV['BLUEPILL_BASE_DIR'] ||= File.expand_path('run/bluepill')
30
- ENV['BLUEPILL_LOGFILE'] ||= File.expand_path('log/bluepill.log')
34
+ ENV['ROBOT_ENVIRONMENT'] ||= 'development'
35
+ ENV['BLUEPILL_BASE_DIR'] ||= File.expand_path('run/bluepill')
36
+ ENV['BLUEPILL_LOGFILE'] ||= File.expand_path('log/bluepill.log')
31
37
 
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']))
38
+ fail 'bluepill requires config directory' unless File.directory?('config')
39
+ fail 'bluepill requires run directory' unless File.directory?(File.dirname(ENV['BLUEPILL_BASE_DIR']))
40
+ fail 'bluepill requires log directory' unless File.directory?(File.dirname(ENV['BLUEPILL_LOGFILE']))
35
41
 
36
- cmd = 'bluepill'
37
- cmd << ' --no-privileged'
38
- cmd << " --base-dir #{ENV['BLUEPILL_BASE_DIR']}"
39
- cmd << " --logfile #{ENV['BLUEPILL_LOGFILE']}"
42
+ cmd = 'bluepill'
43
+ cmd << ' --no-privileged'
44
+ cmd << " --base-dir #{ENV['BLUEPILL_BASE_DIR']}"
45
+ cmd << " --logfile #{ENV['BLUEPILL_LOGFILE']}"
40
46
 
41
- case ARGV[0].downcase
42
- when 'boot'
43
- fn = 'config/bluepill.rb' # allow override
44
- fn = RobotController.bluepill_config unless File.file?(fn)
45
- if File.file?(fn)
46
- # puts "Loading #{fn}"
47
- exec "#{cmd} load #{fn}"
48
- # NOTREACHED
49
- end
50
- puts "ERROR: Cannot find bluepill configuration file for #{ENV['ROBOT_ENVIRONMENT']}"
51
- exit(-1)
52
- when 'verify'
53
- verbose = (ARGV[1] == '--verbose')
47
+ case args.first.downcase
48
+ when 'boot'
49
+ fn = 'config/bluepill.rb' # allow override
50
+ fn = RobotController.bluepill_config unless File.file?(fn)
51
+ if File.file?(fn)
52
+ # puts "Loading #{fn}"
53
+ exec "#{cmd} load #{fn}"
54
+ # NOTREACHED
55
+ end
56
+ puts "ERROR: Cannot find bluepill configuration file for #{ENV['ROBOT_ENVIRONMENT']}"
57
+ exit(-1)
58
+ when 'verify'
59
+ verbose = (args[1] == '--verbose')
54
60
 
55
- # load list of all possible robots
56
- robots = YAML.load_file('config/robots.yml')
57
- fail ArgumentError unless robots.is_a? Array
61
+ # load list of all possible robots
62
+ robots = YAML.load_file('config/robots.yml')
63
+ fail ArgumentError unless robots.is_a? Array
58
64
 
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
65
+ # determine how many processes should be running for each robot
66
+ running = {}
67
+ robots.each do |robot|
68
+ running[robot] = 0
69
+ end
70
+ RobotController::Parser.load("robots_#{ENV['ROBOT_ENVIRONMENT']}.yml").each do |h|
71
+ running[h[:robot]] = h[:n]
72
+ end
67
73
 
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
74
+ # verify that all robots running are known to the config/robots.yml
75
+ running.each_key do |robot|
76
+ unless robots.include?(robot)
77
+ puts "ERROR: '#{robot}' robot is unknown to the suite. Check config/robots.yml"
78
+ running.delete(robot)
79
+ end
80
+ end
75
81
 
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
82
+ # verify suite
83
+ verify = RobotController::Verify.new(running)
84
+ begin
85
+ statuses = verify.verify
86
+ rescue => e
87
+ puts "ERROR: Cannot run verification for any robots. #{e.class}: #{e.message}"
88
+ exit(-1)
89
+ end
84
90
 
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
- if status[:running] == 0
94
- puts "OK: #{robot} is not enabled (0 running)" if verbose
91
+ # print output for each status
92
+ ok = true
93
+ puts 'ERROR: No robots to verify?' if statuses.size == 0
94
+ statuses.each_pair do |robot, status|
95
+ case status[:state]
96
+ when :up
97
+ puts "OK: #{robot} is up (#{status[:running]} running)" if verbose
98
+ when :not_enabled
99
+ if status[:running] == 0
100
+ puts "OK: #{robot} is not enabled (0 running)" if verbose
101
+ else
102
+ puts "ERROR: #{robot} is not enabled (but #{status[:running]} running)"
103
+ end
104
+ when :down
105
+ ok = false
106
+ puts "ERROR: #{robot} is down (#{status[:running]} of #{running[robot]} running)"
107
+ else
108
+ ok = false
109
+ puts "ERROR: #{robot} cannot be verified (state=#{status[:state]})"
110
+ end
111
+ end
112
+ puts 'OK' if ok && !verbose && statuses.size > 0
113
+ exit(0)
95
114
  else
96
- puts "ERROR: #{robot} is not enabled (but #{status[:running]} running)"
115
+ exec "#{cmd} #{args.join(' ')}"
116
+ # NOTREACHED
97
117
  end
98
- when :down
99
- ok = false
100
- puts "ERROR: #{robot} is down (#{status[:running]} of #{running[robot]} running)"
101
- else
102
- ok = false
103
- puts "ERROR: #{robot} cannot be verified (state=#{status[:state]})"
104
118
  end
105
119
  end
106
- puts 'OK' if ok && !verbose && statuses.size > 0
107
- exit(0)
108
- else
109
- exec "#{cmd} #{ARGV.join(' ')}"
110
- # NOTREACHED
111
120
  end
121
+
122
+ RobotController::CLI.run(ARGV)
@@ -0,0 +1,10 @@
1
+ # Use this file to easily define all of your cron jobs.
2
+
3
+ every 5.minutes do
4
+ # cannot use :output with Hash/String because we don't want append behavior
5
+ set :output, proc { '> log/verify.log 2> log/cron.log' }
6
+ set :environment_variable, 'ROBOT_ENVIRONMENT'
7
+ rake 'robots:verify'
8
+ end
9
+
10
+ # Learn more: http://github.com/javan/whenever
@@ -14,6 +14,7 @@
14
14
  # - a list of identiers ('A,B,C'), or
15
15
  # - an asterix (*).
16
16
  # 3. instances is a single integer.
17
+ # 4. host is the hostname or '*' for any host
17
18
  #
18
19
  # Both lane and instances are optional. Lane defaults to 'default', and
19
20
  # instances defaults to 1.
@@ -43,7 +44,7 @@
43
44
  #
44
45
  # Robot 1 (8 CPU) hosts shelving and publish only
45
46
  #
46
- sul-robots1-dev:
47
+ robot1:
47
48
  - dor_accessionWF_shelve:A # 1 robot for lane A
48
49
  - dor_accessionWF_shelve:B:3 # 3 robots for lane B
49
50
  - dor_accessionWF_shelve:C:3 # 3 robots for lane C
@@ -64,7 +65,7 @@ sul-robots1-dev:
64
65
  #
65
66
  # Robot 2 (16 CPU) hosts technical metadata creation only
66
67
  #
67
- sul-robots2-dev:
68
+ robot2:
68
69
  - dor_accessionWF_technical-metadata:A:5 # 5 robots for lane A
69
70
  - dor_accessionWF_technical-metadata:B:5 # 5 robots for lane B
70
71
  - dor_accessionWF_technical-metadata:C,D,E:5 # 5 robots for lanes C, D, and E
@@ -72,7 +73,7 @@ sul-robots2-dev:
72
73
  #
73
74
  # Robot 3 (4 CPU) hosts helper robots for all accessioning workflows
74
75
  #
75
- sul-robots3-dev:
76
+ robot3:
76
77
  - dor_accessionWF_descriptive-metadata # 1 robot for default lane
77
78
  - dor_accessionWF_rights-metadata # 1 robot for default lane
78
79
  - dor_accessionWF_content-metadata # 1 robot for default lane
@@ -4,6 +4,7 @@ require 'yaml'
4
4
  module RobotController
5
5
  #
6
6
  class Parser
7
+ # maximum number of processes a single robot can have
7
8
  ROBOT_INSTANCE_MAX = 16
8
9
 
9
10
  class << self
@@ -13,80 +14,90 @@ module RobotController
13
14
  robots_fn = File.join(dir, robots_fn) if dir
14
15
  fail "FileNotFound: #{robots_fn}" unless File.file?(robots_fn)
15
16
 
16
- # read the YAML file
17
- # puts "Loading #{robots_fn}"
17
+ # read the YAML file with the configuration of all the robots to run
18
18
  robots = YAML.load_file(robots_fn)
19
- # puts robots
20
19
 
21
- # determine current host
22
- host = `hostname -s`.strip unless host
23
- # puts host
20
+ # determine current host if not provided
21
+ host ||= `hostname -s`.strip
24
22
 
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)
23
+ # if the config lists this specific host, use it;
24
+ # else check to see if '*' is a matching host
25
+ unless robots.include?(host)
26
+ if robots.include?('*')
27
+ host = '*'
28
+ else
29
+ fail "HostMismatch: #{host} not defined in #{robots_fn}"
30
+ end
31
+ end
28
32
 
29
- parse_yaml(robots[host])
33
+ # parse the host-specific YAML configuration
34
+ parse_robots_configuration(robots[host])
30
35
  end
31
36
 
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
+ # validates that the instances value is within range, e.g.,
38
+ #
39
+ # instances_valid?(1) == 1
40
+ # instances_valid?(16) == 16
41
+ # instances_valid?(0) == 1 # out of range low, enforce minimum
42
+ # instances_valid?(99) => RuntimeError # out of range high, error out
43
+ def instances_valid?(n)
37
44
  fail "TooManyInstances: #{n} > #{ROBOT_INSTANCE_MAX}" if n > ROBOT_INSTANCE_MAX
38
- n = 1 if n < 1
39
- n
45
+ (n < 1) ? 1 : n
40
46
  end
41
47
 
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']
48
+ # parse the lane values designator using the following syntax:
49
+ #
50
+ # parse_lanes('') == ['default']
51
+ # parse_lanes(' ') == ['default']
52
+ # parse_lanes(' , ') == ['default']
53
+ # parse_lanes(' , ,') == ['default']
54
+ # parse_lanes('*') == ['*']
55
+ # parse_lanes('1') == ['1']
56
+ # parse_lanes('A') == ['A']
57
+ # parse_lanes('A , B') == ['A', 'B']
58
+ # parse_lanes('A,B,C') == ['A','B','C']
59
+ # parse_lanes('A-C,E') == ['A-C', 'E']
52
60
  def parse_lanes(lanes_spec)
53
- return ['default'] if lanes_spec.split(/,/).collect(&:strip).join('') == ''
54
- lanes_spec.split(/,/).collect(&:strip).uniq
61
+ lanes = lanes_spec.split(/,/).collect(&:strip).uniq
62
+ lanes.join('') == '' ? ['default'] : lanes
55
63
  end
56
64
 
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
65
+ # generate the queue names for all given lanes, e.g.,
66
+ #
67
+ # queue_names('z','A') => ['z_A']
68
+ # queue_names('z','A,C') => ['z_A', 'z_C']
69
+ def queue_names(robot, lanes)
70
+ parse_lanes(lanes).collect { |lane| robot + '_' + lane }
65
71
  end
66
72
 
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
73
+ # parse YAML lines for host where line is robot[:lane[:instances]]
74
+ #
75
+ # @return [Array<Hash>]
76
+ # [{
77
+ # robot: 'foo',
78
+ # queues: ['foo_default'],
79
+ # n: 2
80
+ # }, ... ]
81
+ def parse_robots_configuration(robots)
82
+ [].tap do |r|
83
+ robots.each do |line|
84
+ robot = line.split(/:/).collect(&:strip)
85
+ robot.each do |j|
86
+ fail "SyntaxError: '#{line}' is missing arguments" if j.strip == ''
87
+ end
75
88
 
76
- # add defaults
77
- robot << 'default' if robot.size == 1
78
- robot << '1' if robot.size == 2
89
+ # add defaults
90
+ robot << 'default' if robot.size == 1
91
+ robot << '1' if robot.size == 2
79
92
 
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
93
+ # build queues for robot instances
94
+ fail "SyntaxError: '#{line}' is missing arguments" unless robot.size == 3
95
+ robot[2] = instances_valid?(robot[2].to_i)
96
+ queues = queue_names(robot[0], robot[1])
86
97
 
87
- r << { robot: robot[0], queues: queues, n: robot[2] }
98
+ r << { robot: robot[0], queues: queues, n: robot[2] }
99
+ end
88
100
  end
89
- r
90
101
  end
91
102
  end
92
103
  end
@@ -1 +1,9 @@
1
1
  require 'resque/tasks'
2
+
3
+ desc 'Verify that robots are running as configured'
4
+ namespace :robots do
5
+ task :verify, :verbose do |_t, args|
6
+ args.with_defaults(verbose: nil)
7
+ system "bundle exec controller verify #{args[:verbose] ? '--verbose' : ''}"
8
+ end
9
+ end
@@ -49,7 +49,7 @@ module RobotController
49
49
  fail ArgumentError if nprocesses.nil? || !nprocesses.is_a?(Hash)
50
50
  fail ArgumentError, 'Empty argument' if nprocesses.size == 0
51
51
  @running = nprocesses
52
- @robots = @running.each_key.to_a
52
+ @robots = @running.keys
53
53
  @status = nil
54
54
  end
55
55
 
@@ -71,11 +71,7 @@ module RobotController
71
71
  # }
72
72
  def verify(reload = true)
73
73
  @status = nil if reload
74
- r = {}
75
- robots.each do |robot|
76
- r[robot] = robot_status(robot)
77
- end
78
- r
74
+ {}.tap { |hash| robots.each { |robot| hash[robot] = robot_status(robot) } }
79
75
  end
80
76
 
81
77
  # @param [String] robot name
@@ -84,8 +80,6 @@ module RobotController
84
80
  @running[robot]
85
81
  end
86
82
 
87
- protected
88
-
89
83
  # @param [String] robot name
90
84
  # @return [Hash] { state: :up | :down | :not_enabled, running: n }
91
85
  def robot_status(robot)
@@ -96,6 +90,8 @@ module RobotController
96
90
  end
97
91
  end
98
92
 
93
+ protected
94
+
99
95
  #
100
96
  # @return [Hash] status
101
97
  # {
@@ -0,0 +1,6 @@
1
+ begin
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new(:spec)
4
+ rescue LoadError
5
+ puts 'no rspec available'
6
+ end
@@ -0,0 +1,6 @@
1
+ begin
2
+ require 'rubocop/rake_task'
3
+ RuboCop::RakeTask.new
4
+ rescue LoadError
5
+ puts 'Unable to load RuboCop.'
6
+ end
@@ -0,0 +1,16 @@
1
+ begin
2
+ require 'yard'
3
+ YARD::Rake::YardocTask.new do |t|
4
+ t.files = ['lib/**/*.rb', 'bin/controller']
5
+ t.options = ['--readme', 'README.md', '-m', 'markdown']
6
+ end
7
+
8
+ namespace :yard do
9
+ desc 'Clean up documentation'
10
+ task :clean do
11
+ FileUtils.rm_rf('doc')
12
+ end
13
+ end
14
+ rescue LoadError
15
+ abort 'Please install the YARD gem to generate doc.'
16
+ end
@@ -25,11 +25,13 @@ Gem::Specification.new do |s|
25
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
+ s.add_dependency 'whenever', '~> 0.9.2'
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
+ s.add_development_dependency 'rubocop'
34
+ s.add_development_dependency 'simplecov'
33
35
  s.add_development_dependency 'version_bumper'
34
36
  s.add_development_dependency 'yard'
35
37
  end
@@ -0,0 +1,4 @@
1
+ '*':
2
+ - M:default:1
3
+ - N:B:2
4
+ - O:C:3
@@ -6,7 +6,7 @@ describe RobotController::Parser do
6
6
  RobotController::Parser.load('standard.yml', 'spec/fixtures', 'host1')
7
7
  end
8
8
 
9
- it 'pass1' do
9
+ it 'parses correctly' do
10
10
  expect(subject).to eq [
11
11
  { robot: 'X', queues: ['X_default'], n: 1 },
12
12
  { robot: 'Y', queues: ['Y_B'], n: 1 },
@@ -20,7 +20,7 @@ describe RobotController::Parser do
20
20
  RobotController::Parser.load('standard.yml', 'spec/fixtures', 'host2')
21
21
  end
22
22
 
23
- it 'pass2' do
23
+ it 'parses correctly' do
24
24
  expect(subject).to eq [
25
25
  { robot: 'A', queues: ['A_*'], n: 1 },
26
26
  { robot: 'B', queues: %w(B_X B_Y), n: 1 },
@@ -30,19 +30,50 @@ describe RobotController::Parser do
30
30
  end
31
31
  end
32
32
 
33
- context 'parse_instances' do
33
+ context 'expanded mismatched host' do
34
+ it 'reports error' do
35
+ expect do
36
+ RobotController::Parser.load('standard.yml', 'spec/fixtures', 'host3')
37
+ end.to raise_error(RuntimeError)
38
+ end
39
+ end
40
+
41
+ context 'matcher' do
42
+ subject do
43
+ RobotController::Parser.load('matcher.yml', 'spec/fixtures', 'host3')
44
+ end
45
+
46
+ it 'parses correctly' do
47
+ expect(subject).to eq [
48
+ { robot: 'M', queues: ['M_default'], n: 1 },
49
+ { robot: 'N', queues: ['N_B'], n: 2 },
50
+ { robot: 'O', queues: ['O_C'], n: 3 }
51
+ ]
52
+ end
53
+ end
54
+
55
+ context 'file-not-found' do
56
+ it 'reports error' do
57
+ expect do
58
+ RobotController::Parser.load('nofile.yml', 'spec/fixtures', 'host3')
59
+ end.to raise_error(RuntimeError)
60
+ end
61
+ end
62
+
63
+ context 'instances_valid?' do
34
64
  subject do
35
65
  RobotController::Parser
36
66
  end
37
67
  it 'valid inputs' do
38
- expect(subject.parse_instances(0)).to eq 1
39
- expect(subject.parse_instances(1)).to eq 1
40
- expect(subject.parse_instances(16)).to eq 16
68
+ expect(subject.instances_valid?(0)).to eq 1
69
+ expect(subject.instances_valid?(1)).to eq 1
70
+ expect(subject.instances_valid?(8)).to eq 8
71
+ expect(subject.instances_valid?(16)).to eq 16
41
72
  end
42
73
 
43
74
  it 'invalid inputs' do
44
75
  expect do
45
- subject.parse_instances(17)
76
+ subject.instances_valid?(17)
46
77
  end.to raise_error RuntimeError
47
78
  end
48
79
  end
@@ -70,15 +101,15 @@ describe RobotController::Parser do
70
101
  end
71
102
  end
72
103
 
73
- context 'build_queues' do
104
+ context 'queue_names' do
74
105
  subject do
75
106
  RobotController::Parser
76
107
  end
77
108
 
78
109
  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)
110
+ expect(subject.queue_names('z', '*')).to eq ['z_*']
111
+ expect(subject.queue_names('z', 'default')).to eq ['z_default']
112
+ expect(subject.queue_names('z', 'A,B,C')).to eq %w(z_A z_B z_C)
82
113
  end
83
114
  end
84
115
  end
@@ -94,6 +94,23 @@ describe RobotController::Verify do
94
94
  )
95
95
  end
96
96
 
97
+ it 'runs controller status with errors' do
98
+ allow(subject).to receive(:controller_status).and_return([
99
+ 'robot01_01_dor_gisAssemblyWF_assign-placenamesMISMATCH(pid:29481): down'
100
+ ])
101
+ expect(subject.robot_status('dor_gisAssemblyWF_assign-placenames')).to eq(
102
+ state: :unknown, running: 0
103
+ )
104
+ # expect(subject.robot_status('dor_gisAssemblyWF_assign-placenamesMISMATCH')).to eq(
105
+ # { state: :not_enabled, running: 1 }
106
+ # )
107
+ expect { subject.robot_status('garbage') }.to raise_error(RuntimeError)
108
+ expect(subject.verify).to eq(
109
+ # 'dor_gisAssemblyWF_assign-placenamesMISMATCH' => { state: :not_enabled, running: 1 },
110
+ 'dor_gisAssemblyWF_assign-placenames' => { state: :unknown, running: 0 }
111
+ )
112
+ end
113
+
97
114
  it 'runs controller status even when broken' do
98
115
  allow(subject).to receive(:controller_status).and_return([])
99
116
  expect { subject.verify }.to raise_error(RuntimeError)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: robot-controller
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Darren Hardy
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-25 00:00:00.000000000 Z
11
+ date: 2015-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bluepill
@@ -53,7 +53,21 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: 10.3.2
55
55
  - !ruby/object:Gem::Dependency
56
- name: awesome_print
56
+ name: whenever
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - '>='
@@ -67,7 +81,7 @@ dependencies:
67
81
  - !ruby/object:Gem::Version
68
82
  version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
- name: pry
84
+ name: rspec
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - '>='
@@ -81,7 +95,7 @@ dependencies:
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
83
97
  - !ruby/object:Gem::Dependency
84
- name: rspec
98
+ name: redcarpet
85
99
  requirement: !ruby/object:Gem::Requirement
86
100
  requirements:
87
101
  - - '>='
@@ -95,7 +109,21 @@ dependencies:
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
- name: redcarpet
112
+ name: rubocop
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
99
127
  requirement: !ruby/object:Gem::Requirement
100
128
  requirements:
101
129
  - - '>='
@@ -145,8 +173,10 @@ extensions: []
145
173
  extra_rdoc_files: []
146
174
  files:
147
175
  - .gitignore
176
+ - .rspec
148
177
  - .rubocop.yml
149
178
  - .rubocop_todo.yml
179
+ - .travis.yml
150
180
  - Gemfile
151
181
  - LICENSE
152
182
  - README.md
@@ -155,6 +185,7 @@ files:
155
185
  - bin/controller
156
186
  - config/environments/robots_development.yml
157
187
  - config/robots.yml
188
+ - config/schedule_example.rb
158
189
  - example/config/environments/robots_development.yml
159
190
  - example/lib/tasks/environment.rake
160
191
  - lib/robot-controller.rb
@@ -162,8 +193,11 @@ files:
162
193
  - lib/robot-controller/robots.rb
163
194
  - lib/robot-controller/tasks.rb
164
195
  - lib/robot-controller/verify.rb
165
- - lib/tasks/doc.rake
196
+ - lib/tasks/rspec.rake
197
+ - lib/tasks/rubocop.rake
198
+ - lib/tasks/yard.rake
166
199
  - robot-controller.gemspec
200
+ - spec/fixtures/matcher.yml
167
201
  - spec/fixtures/standard.yml
168
202
  - spec/spec_helper.rb
169
203
  - spec/unit/robots_spec.rb
@@ -189,14 +223,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
189
223
  version: 1.3.6
190
224
  requirements: []
191
225
  rubyforge_project:
192
- rubygems_version: 2.4.5
226
+ rubygems_version: 2.0.14
193
227
  signing_key:
194
228
  specification_version: 4
195
229
  summary: Monitors and controls running workflow robots off of priority queues and
196
230
  within a cluster
197
231
  test_files:
232
+ - spec/fixtures/matcher.yml
198
233
  - spec/fixtures/standard.yml
199
234
  - spec/spec_helper.rb
200
235
  - spec/unit/robots_spec.rb
201
236
  - spec/unit/verify_spec.rb
202
- has_rdoc: true
data/lib/tasks/doc.rake DELETED
@@ -1,37 +0,0 @@
1
- desc 'Generate RDoc'
2
- task doc: ['doc:generate']
3
-
4
- namespace :doc do
5
- project_root = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
6
- doc_destination = File.join(project_root, 'rdoc')
7
-
8
- begin
9
- require 'yard'
10
- require 'yard/rake/yardoc_task'
11
-
12
- YARD::Rake::YardocTask.new(:generate) do |yt|
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
- yt.options = ['--output-dir', doc_destination, '--readme', 'README.md']
18
- end
19
- rescue LoadError
20
- desc 'Generate YARD Documentation'
21
- task :generate do
22
- abort 'Please install the YARD gem to generate rdoc.'
23
- end
24
- end
25
-
26
- desc 'Remove generated documenation'
27
- task :clean do
28
- rm_r doc_destination if File.exist?(doc_destination)
29
- end
30
- end
31
-
32
- desc 'Build Yard documentation'
33
- task :yard do
34
- YARD::Rake::YardocTask.new do |t|
35
- t.files = ['lib/**/*.rb', 'bin/**/*.rb']
36
- end
37
- end