hive-runner 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +120 -0
- data/bin/hive_setup +182 -0
- data/bin/hived +35 -0
- data/bin/start_hive +120 -0
- data/lib/hive/controller/shell.rb +23 -0
- data/lib/hive/controller.rb +32 -0
- data/lib/hive/device/shell.rb +14 -0
- data/lib/hive/device.rb +88 -0
- data/lib/hive/diagnostic.rb +54 -0
- data/lib/hive/diagnostic_runner.rb +32 -0
- data/lib/hive/execution_script.rb +121 -0
- data/lib/hive/file_system.rb +103 -0
- data/lib/hive/log.rb +60 -0
- data/lib/hive/port_allocator.rb +79 -0
- data/lib/hive/register.rb +120 -0
- data/lib/hive/results.rb +20 -0
- data/lib/hive/worker/shell.rb +16 -0
- data/lib/hive/worker.rb +400 -0
- data/lib/hive.rb +118 -0
- data/scripts/hive-script-helper.sh +17 -0
- metadata +262 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0a9df8820b8d030fa96b76d253723ccae821feb1
|
4
|
+
data.tar.gz: 7c2b1f9e1f75d269652e7e996ca515a135b92349
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9e1d1ebb42336cc59d7f5f37fe98392887cadce6c23ef9787c4b4b7767e26a74f8adcfc40f23bd56eb0ea8c8b3a242a2aa5d215ac5dcd140a77fd534b70c61bb
|
7
|
+
data.tar.gz: cbc8e222372513f478cf809dc297e2b1b04511e48b00b9e498dc03d2cb5f72dca396c5cf4c57f49982b7a2697fdc59ad6c05ec388838d64e177c2cb1ad2c8c7b
|
data/README.md
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# hive-runner
|
2
|
+
|
3
|
+
Run automated jobs on devices
|
4
|
+
|
5
|
+
## Quick start
|
6
|
+
|
7
|
+
Install the hive-runner gem and set up your hive:
|
8
|
+
|
9
|
+
gem install hive-runner
|
10
|
+
hive_setup my_hive
|
11
|
+
|
12
|
+
Follow the configuration instructions and, in particular, ensure that the
|
13
|
+
`HIVE_CONFIG` variable is set.
|
14
|
+
|
15
|
+
Start the Hive daemon:
|
16
|
+
|
17
|
+
hived start
|
18
|
+
|
19
|
+
Determine the status of the Hive:
|
20
|
+
|
21
|
+
hived status
|
22
|
+
|
23
|
+
Stop the Hive:
|
24
|
+
|
25
|
+
hived stop
|
26
|
+
|
27
|
+
## Configuration file
|
28
|
+
|
29
|
+
Example config file:
|
30
|
+
|
31
|
+
test:
|
32
|
+
controllers:
|
33
|
+
shell:
|
34
|
+
max_workers: 5
|
35
|
+
name_stub: SHELL_WORKER
|
36
|
+
queues:
|
37
|
+
- bash
|
38
|
+
|
39
|
+
logging:
|
40
|
+
directory: logs
|
41
|
+
pids: pids
|
42
|
+
main_filename: hive.log
|
43
|
+
|
44
|
+
timings:
|
45
|
+
worker_loop_interval: 5
|
46
|
+
|
47
|
+
### Controllers
|
48
|
+
|
49
|
+
The `controllers` section contains details about the controllers to be
|
50
|
+
used by the hive. The name of each section indicates the controller type. Some
|
51
|
+
of the fields in each controllers section is common to all controller types
|
52
|
+
(see below) while some are defined for each specific controller type.
|
53
|
+
|
54
|
+
Fields for all controller types are:
|
55
|
+
|
56
|
+
| Field | Content |
|
57
|
+
|-------------------|-------------------------------------------|
|
58
|
+
| `max_workers` | Maximum number of workers to use |
|
59
|
+
| `port_range_size` | Number of ports to allocate to the device |
|
60
|
+
| `name_stub` | Stub for name of the worker process |
|
61
|
+
|
62
|
+
### Ports
|
63
|
+
|
64
|
+
| Field | Content |
|
65
|
+
|-----------|---------------------|
|
66
|
+
| `minimum` | Minimum port number |
|
67
|
+
| `maximum` | Maximum port number |
|
68
|
+
|
69
|
+
### Logging
|
70
|
+
|
71
|
+
| Field | Content |
|
72
|
+
|-----------------|---------------------------|
|
73
|
+
| `directory` | Log file directory |
|
74
|
+
| `pids` | PIDs directory |
|
75
|
+
| `main_filename` | Name of the main log file |
|
76
|
+
|
77
|
+
### Timings
|
78
|
+
|
79
|
+
The `worker_loop_interval` indicates the number of seconds to wait between each
|
80
|
+
poll of the job queue in the worker loop.
|
81
|
+
|
82
|
+
## Shell controller
|
83
|
+
|
84
|
+
### Configuration
|
85
|
+
|
86
|
+
The shell controller section contains the following additional field:
|
87
|
+
|
88
|
+
| Field | Content |
|
89
|
+
|-----------|-------------------------------------------|
|
90
|
+
| `queues` | Array of job queues for the shell workers |
|
91
|
+
| `workers` | Number of shell workers to run |
|
92
|
+
|
93
|
+
## Setting up a new Hive Runner
|
94
|
+
|
95
|
+
Check out the Hive Runner from Github:
|
96
|
+
|
97
|
+
# Using HTTPS
|
98
|
+
git clone https://github.com/bbc-test/hive-runner
|
99
|
+
# ... or using SSH
|
100
|
+
git clone ssh@github.com:bbc-test/hive-runner
|
101
|
+
# Ensure ruby gems are installed
|
102
|
+
cd hive-runner
|
103
|
+
bundle install
|
104
|
+
|
105
|
+
Configure the hive, either by editing the default configuration file,
|
106
|
+
`hive-runner/config/hive-runner.yml`, or creating a separate configuration
|
107
|
+
file in a separate location (recommended) and ensuring that the `HIVE_CONFIG`
|
108
|
+
environment variable is set correctly:
|
109
|
+
|
110
|
+
echo export HIVE_CONFIG=/path/to/hive-config-directory >> ~/.bashrc
|
111
|
+
|
112
|
+
See the "Configuration file" above for details.
|
113
|
+
|
114
|
+
Start the Hive Runner:
|
115
|
+
|
116
|
+
./bin/hived start
|
117
|
+
|
118
|
+
Ensure that your Hive Runner is running and that your workers have started:
|
119
|
+
|
120
|
+
./bin/hived status
|
data/bin/hive_setup
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'terminal-table'
|
6
|
+
|
7
|
+
if ARGV.length < 1
|
8
|
+
puts "Usage:"
|
9
|
+
puts
|
10
|
+
puts " hive_setup <directory>"
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
dir = File.expand_path('', ARGV[0])
|
15
|
+
if Dir.exists?(dir)
|
16
|
+
if ! Dir["#{dir}/*"].empty?
|
17
|
+
puts "Directory '#{dir}' exists and is not empty"
|
18
|
+
exit 1
|
19
|
+
end
|
20
|
+
else
|
21
|
+
if File.exists?(dir)
|
22
|
+
puts "'#{dir}' exists and is not a directory"
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
FileUtils.mkdir_p(dir)
|
26
|
+
end
|
27
|
+
|
28
|
+
def yn question
|
29
|
+
yn = 'x'
|
30
|
+
while yn !~ /^[yYnN]/
|
31
|
+
print "#{question}(y/n) "
|
32
|
+
yn = STDIN.gets.chomp
|
33
|
+
end
|
34
|
+
yn =~ /^[yY]/
|
35
|
+
end
|
36
|
+
|
37
|
+
# Choose options
|
38
|
+
opt = ''
|
39
|
+
mods = []
|
40
|
+
while opt.upcase != 'X'
|
41
|
+
table = Terminal::Table.new headings: ['Device', 'Module', 'Source']
|
42
|
+
mods.each do |mod|
|
43
|
+
table.add_row [
|
44
|
+
mod[:device],
|
45
|
+
mod[:name],
|
46
|
+
mod.has_key?(:git_account) ?
|
47
|
+
"git@github.com:#{mod[:git_account]}/#{mod[:name]}" :
|
48
|
+
"https://rubygems.org/gems/#{mod[:name]}"
|
49
|
+
]
|
50
|
+
end
|
51
|
+
|
52
|
+
puts ''
|
53
|
+
puts table
|
54
|
+
puts ''
|
55
|
+
puts '1) Add module'
|
56
|
+
puts 'X) Continue'
|
57
|
+
puts ''
|
58
|
+
print "> "
|
59
|
+
opt = STDIN.gets.chomp
|
60
|
+
|
61
|
+
case opt
|
62
|
+
when '1'
|
63
|
+
mod = {}
|
64
|
+
puts ''
|
65
|
+
print "Module name: "
|
66
|
+
mod[:device] = STDIN.gets.chomp
|
67
|
+
mod[:name] = "hive-runner-#{mod[:device]}"
|
68
|
+
puts ''
|
69
|
+
if yn "Get '#{mod[:name]}' from Github? "
|
70
|
+
print "Enter GIT account name: "
|
71
|
+
mod[:git_account] = STDIN.gets.chomp
|
72
|
+
end
|
73
|
+
|
74
|
+
puts ''
|
75
|
+
puts "Module '#{mod[:name]}'"
|
76
|
+
if mod.has_key?(:git_account)
|
77
|
+
puts " from git@github.com:#{mod[:git_account]}/#{mod[:name]}"
|
78
|
+
else
|
79
|
+
puts " from https://rubygems.org/gems/#{mod[:name]}"
|
80
|
+
end
|
81
|
+
puts ''
|
82
|
+
|
83
|
+
if yn "Correct? "
|
84
|
+
mods << mod
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
FileUtils.mkdir_p("#{dir}/config")
|
90
|
+
FileUtils.mkdir_p("#{dir}/log")
|
91
|
+
FileUtils.mkdir_p("#{dir}/pids")
|
92
|
+
FileUtils.mkdir_p("#{dir}/workspaces")
|
93
|
+
|
94
|
+
File.open("#{dir}/config/settings.yml", 'w') do |f|
|
95
|
+
f.puts "#{ENV['HIVE_ENVIRONMENT'] || 'test'}:"
|
96
|
+
f.puts ' daemon_name: HIVE'
|
97
|
+
f.puts ''
|
98
|
+
f.puts ' controllers:'
|
99
|
+
f.puts ' shell:'
|
100
|
+
f.puts ' # Number of shell workers to allocate'
|
101
|
+
f.puts ' workers: 5'
|
102
|
+
f.puts ' # Queue for each shell worker'
|
103
|
+
f.puts ' queues:'
|
104
|
+
f.puts ' - bash'
|
105
|
+
f.puts ' # Number of ports to allocate to each shell worker'
|
106
|
+
f.puts ' port_range_size: 50'
|
107
|
+
f.puts ' name_stub: SHELL_WORKER'
|
108
|
+
mods.each do |m|
|
109
|
+
f.puts " #{m[:device]}:"
|
110
|
+
f.puts ' # Number of ports to allocate to each #{m[:device]} worker'
|
111
|
+
f.puts ' port_range_size: 50'
|
112
|
+
f.puts " name_stub: #{m[:device].upcase}_WORKER"
|
113
|
+
end
|
114
|
+
f.puts ''
|
115
|
+
f.puts ' # Range of ports to be made available to workers'
|
116
|
+
f.puts ' ports:'
|
117
|
+
f.puts ' minimum: 4000'
|
118
|
+
f.puts ' maximum: 5000'
|
119
|
+
f.puts ''
|
120
|
+
f.puts ' # Logging configuration'
|
121
|
+
f.puts ' logging:'
|
122
|
+
f.puts " directory: #{dir}/log"
|
123
|
+
f.puts " pids: #{dir}/pids"
|
124
|
+
f.puts ' main_filename: hive.log'
|
125
|
+
f.puts ' main_level: INFO'
|
126
|
+
f.puts ' worker_level: INFO'
|
127
|
+
f.puts " home: #{dir}/workspaces"
|
128
|
+
f.puts ' homes_to_keep: 5'
|
129
|
+
f.puts ''
|
130
|
+
f.puts ' # Timing configuration'
|
131
|
+
f.puts ' timings:'
|
132
|
+
f.puts ' worker_loop_interval: 5'
|
133
|
+
f.puts ' controller_loop_interval: 5'
|
134
|
+
f.puts ''
|
135
|
+
f.puts ' # Configuration for various network options'
|
136
|
+
f.puts ' network:'
|
137
|
+
f.puts ' scheduler: https://scheduler'
|
138
|
+
f.puts ' devicedb: https://devicedb'
|
139
|
+
f.puts ' cert: /path/to/certificate.pem'
|
140
|
+
f.puts ' cafile: /path/to/certificate-authorities.pem'
|
141
|
+
f.puts ''
|
142
|
+
f.puts ' # Configuration for diagnostic plugins'
|
143
|
+
f.puts ' diagnostics:'
|
144
|
+
end
|
145
|
+
|
146
|
+
File.open("#{dir}/Gemfile", 'w') do |f|
|
147
|
+
f.puts "source 'https://rubygems.org/'"
|
148
|
+
f.puts ""
|
149
|
+
f.puts "gem 'hive-runner'"
|
150
|
+
mods.each do |m|
|
151
|
+
source = m.has_key?(:git_account) ? ", git: 'git@github.com:#{m[:git_account]}/#{m[:name]}'" : ''
|
152
|
+
f.puts "gem '#{m[:name]}'#{source}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
print "Executing 'bundle install' ... "
|
157
|
+
Dir.chdir dir
|
158
|
+
if system("bundle install > bundle_install.out 2>&1")
|
159
|
+
print "SUCCESS\n"
|
160
|
+
File.delete('bundle_install.out')
|
161
|
+
else
|
162
|
+
print "FAILED\n"
|
163
|
+
puts "See #{dir}/bundle_install.out for details"
|
164
|
+
exit
|
165
|
+
end
|
166
|
+
|
167
|
+
puts ''
|
168
|
+
puts 'Configuration required:'
|
169
|
+
puts
|
170
|
+
puts ' * Add to config/settings.yml'
|
171
|
+
puts ' - scheduler'
|
172
|
+
puts ' - devicedb'
|
173
|
+
puts ' - cert'
|
174
|
+
puts ' - cafile'
|
175
|
+
if mods.length > 0
|
176
|
+
puts ' * Configure these modules in config/settings.yml'
|
177
|
+
mods.each do |m|
|
178
|
+
puts " - #{m[:device]}"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
puts ' * Add to ~/.bashrc'
|
182
|
+
puts " - export HIVE_CONFIG=#{dir}/config"
|
data/bin/hived
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path(
|
5
|
+
'../../Gemfile',
|
6
|
+
Pathname.new(__FILE__).realpath
|
7
|
+
)
|
8
|
+
|
9
|
+
require 'rubygems'
|
10
|
+
require 'bundler/setup'
|
11
|
+
require 'socket'
|
12
|
+
require 'daemons'
|
13
|
+
|
14
|
+
# require 'hive' here to cause a failure if the configuration is not correct
|
15
|
+
$LOAD_PATH << File.expand_path('../../lib', __FILE__)
|
16
|
+
require 'hive'
|
17
|
+
|
18
|
+
def test(app)
|
19
|
+
puts ''
|
20
|
+
app.default_show_status
|
21
|
+
|
22
|
+
Process.kill('USR1', app.pid.pid)
|
23
|
+
puts
|
24
|
+
server = TCPSocket.open('localhost', ENV.fetch('HIVE_COMM_PORT', 9999).to_i)
|
25
|
+
while line = server.gets
|
26
|
+
puts line.chop
|
27
|
+
end
|
28
|
+
server.close
|
29
|
+
end
|
30
|
+
|
31
|
+
Daemons.run(File.expand_path('..', __FILE__) + '/start_hive',
|
32
|
+
show_status_callback: :test,
|
33
|
+
log_dir: Hive.config.logging.directory,
|
34
|
+
log_output: true,
|
35
|
+
output_logfilename: 'hived.out')
|
data/bin/start_hive
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
puts "*** Starting Hive at #{Time.now} ***"
|
4
|
+
require 'pathname'
|
5
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path(
|
6
|
+
'../../Gemfile',
|
7
|
+
Pathname.new(__FILE__).realpath
|
8
|
+
)
|
9
|
+
|
10
|
+
$LOAD_PATH << File.expand_path('../../lib', __FILE__)
|
11
|
+
|
12
|
+
require 'socket'
|
13
|
+
require 'terminal-table'
|
14
|
+
require 'hive'
|
15
|
+
|
16
|
+
$PROGRAM_NAME = Hive::DAEMON_NAME
|
17
|
+
|
18
|
+
# Version information
|
19
|
+
gems = {}
|
20
|
+
Gem::Specification.find_all_by_name(/hive-*/).each do |g|
|
21
|
+
gems[g.name] = g.version
|
22
|
+
end
|
23
|
+
|
24
|
+
# Communication with daemon launcher
|
25
|
+
comm_port = ENV.fetch('HIVE_COMM_PORT', 9999).to_i
|
26
|
+
comm = TCPServer.open(comm_port)
|
27
|
+
Signal.trap('USR1') do
|
28
|
+
client = comm.accept
|
29
|
+
client.puts " Controllers in use:"
|
30
|
+
Hive.register.controllers.each do |c|
|
31
|
+
client.puts " * #{c.class.name.split('::').last.downcase}"
|
32
|
+
end
|
33
|
+
|
34
|
+
client.puts
|
35
|
+
|
36
|
+
client.puts " Software versions:"
|
37
|
+
client.puts " * hive-runner: #{gems['hive-runner']}"
|
38
|
+
gems.except('hive-runner').sort.each do |name, version|
|
39
|
+
client.puts " * #{name}: #{version}"
|
40
|
+
end
|
41
|
+
|
42
|
+
client.puts
|
43
|
+
|
44
|
+
# Create a hash of connected device details
|
45
|
+
devices = Hive.register.devices
|
46
|
+
device_details = {}
|
47
|
+
client.puts " Total number of devices: #{devices.length}"
|
48
|
+
if devices.length > 0
|
49
|
+
devices.each do |d|
|
50
|
+
device_details[d.identity] = { worker: d.claimed? ? 'Claimed' : d.worker_pid }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
client.puts ''
|
54
|
+
|
55
|
+
# Collect up the queue information per device/worker
|
56
|
+
device_details.each do |d, details|
|
57
|
+
if pid = details[:worker]
|
58
|
+
begin
|
59
|
+
queue_file = "#{Hive.config.logging.directory}/#{pid}.queues.yml"
|
60
|
+
details[:queues] = YAML.load( File.open(queue_file) )
|
61
|
+
rescue
|
62
|
+
details[:queues] = [ "---" ]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Get a seperate hash of workspace details
|
68
|
+
workspaces_list = {}
|
69
|
+
Dir["#{Hive.config.logging.home}/*"].each do |d|
|
70
|
+
if File.directory?(d)
|
71
|
+
if File.exists?("#{d}/job_info")
|
72
|
+
File.open("#{d}/job_info") do |f|
|
73
|
+
if f.read =~ /^(\d*)\s*(\S*)$/
|
74
|
+
worker = $1 || '?'
|
75
|
+
state = $2 || '?'
|
76
|
+
if workspaces_list.has_key?(worker)
|
77
|
+
workspaces_list[worker][File.basename(d)] = state
|
78
|
+
else
|
79
|
+
workspaces_list[worker] = {File.basename(d) => state}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Build the table from all that data just collected
|
88
|
+
table = Terminal::Table.new headings: ['Device', 'Worker', 'Job', 'Status', 'Queues']
|
89
|
+
device_details.each do |d, details|
|
90
|
+
table.add_separator
|
91
|
+
jobs = states = [ '---' ]
|
92
|
+
if workspaces_list.has_key?(details[:worker].to_s)
|
93
|
+
jobs = workspaces_list[details[:worker].to_s].keys
|
94
|
+
states = jobs.map { |key| workspaces_list[details[:worker].to_s][key] }
|
95
|
+
workspaces_list.delete(details[:worker].to_s)
|
96
|
+
end
|
97
|
+
table.add_row [d, details[:worker], jobs.join("\n"), states.join("\n"), details[:queues].join("\n")]
|
98
|
+
end
|
99
|
+
workspaces_list.each do |worker, list|
|
100
|
+
table.add_separator
|
101
|
+
col1 = '---'
|
102
|
+
col2 = worker
|
103
|
+
col5 = '---'
|
104
|
+
list.each do |job, state|
|
105
|
+
table.add_row [col1, col2, job, state, col5]
|
106
|
+
col1 = col2 = ''
|
107
|
+
end
|
108
|
+
end
|
109
|
+
client.puts table
|
110
|
+
|
111
|
+
client.close
|
112
|
+
end
|
113
|
+
|
114
|
+
# Initialise
|
115
|
+
Hive.register.instantiate_controllers
|
116
|
+
|
117
|
+
# Execution loop
|
118
|
+
Hive::logger.info('*** HIVE STARTING ***')
|
119
|
+
Hive::logger.info('Starting execution loop')
|
120
|
+
Hive.register.run
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'hive/controller'
|
2
|
+
require 'hive/worker/shell'
|
3
|
+
|
4
|
+
module Hive
|
5
|
+
class Controller
|
6
|
+
# The Shell controller
|
7
|
+
class Shell < Controller
|
8
|
+
def initialize(options)
|
9
|
+
Hive.logger.debug("options: #{options.inspect}")
|
10
|
+
@workers = options['workers'] || 0
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def detect
|
15
|
+
Hive.logger.info('Creating shell devices')
|
16
|
+
(1..@workers).collect do |i|
|
17
|
+
Hive.logger.info(" Shell device #{i}")
|
18
|
+
self.create_device('id' => i)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'hive'
|
2
|
+
|
3
|
+
module Hive
|
4
|
+
# Generic hive controller class
|
5
|
+
class Controller
|
6
|
+
attr_reader :port_range_size
|
7
|
+
|
8
|
+
class DeviceDetectionFailed < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class NoPortsAvailable < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(config = {})
|
15
|
+
@config = config
|
16
|
+
@device_class = self.class.to_s.sub('Controller', 'Device')
|
17
|
+
require @device_class.downcase.gsub(/::/, '/')
|
18
|
+
Hive.logger.info("Controller '#{self.class}' created")
|
19
|
+
@port_range_size = (@config.has_key?('port_range_size') ? @config['port_range_size'] : 0)
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_device(extra_options = {})
|
23
|
+
object = Object
|
24
|
+
@device_class.split('::').each { |sub| object = object.const_get(sub) }
|
25
|
+
object.new(@config.merge(extra_options))
|
26
|
+
end
|
27
|
+
|
28
|
+
def detect
|
29
|
+
raise NotImplementedError, "'detect' method not defined for '#{self.class}'"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/hive/device.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'hive'
|
2
|
+
require 'hive/port_allocator'
|
3
|
+
|
4
|
+
module Hive
|
5
|
+
# The generic device class
|
6
|
+
class Device
|
7
|
+
attr_reader :type
|
8
|
+
attr_accessor :status
|
9
|
+
attr_accessor :port_allocator
|
10
|
+
|
11
|
+
# Initialise the device
|
12
|
+
def initialize(options)
|
13
|
+
@worker_pid = nil
|
14
|
+
@options = options
|
15
|
+
@port_allocator = options['port_allocator'] or Hive::PortAllocator.new(ports: [])
|
16
|
+
@status = @options.has_key?('status') ? @options['status'] : 'none'
|
17
|
+
@worker_class = self.class.to_s.sub('Device', 'Worker')
|
18
|
+
require @worker_class.downcase.gsub(/::/, '/')
|
19
|
+
raise ArgumentError, "Identity not set for #{self.class} device" if ! @identity
|
20
|
+
end
|
21
|
+
|
22
|
+
# Start the worker process
|
23
|
+
def start
|
24
|
+
parent_pid = Process.pid
|
25
|
+
@worker_pid = Process.fork do
|
26
|
+
object = Object
|
27
|
+
@worker_class.split('::').each { |sub| object = object.const_get(sub) }
|
28
|
+
object.new(@options.merge('parent_pid' => parent_pid, 'device_identity' => self.identity, 'port_allocator' => self.port_allocator))
|
29
|
+
end
|
30
|
+
Process.detach @worker_pid
|
31
|
+
|
32
|
+
Hive.logger.info("Worker started with pid #{@worker_pid}")
|
33
|
+
end
|
34
|
+
|
35
|
+
# Terminate the worker process
|
36
|
+
def stop
|
37
|
+
begin
|
38
|
+
count = 0
|
39
|
+
while self.running? && count < 30 do
|
40
|
+
count += 1
|
41
|
+
Hive.logger.info("Attempting to terminate process #{@worker_pid} [#{count}]")
|
42
|
+
Process.kill 'TERM', @worker_pid
|
43
|
+
sleep 30
|
44
|
+
end
|
45
|
+
Process.kill 'KILL', @worker_pid if self.running?
|
46
|
+
rescue => e
|
47
|
+
Hive.logger.info("Process had already terminated")
|
48
|
+
end
|
49
|
+
@worker_pid = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
# Test the state of the worker process
|
53
|
+
def running?
|
54
|
+
if @worker_pid
|
55
|
+
begin
|
56
|
+
Process.kill 0, @worker_pid
|
57
|
+
true
|
58
|
+
rescue Errno::ESRCH
|
59
|
+
false
|
60
|
+
end
|
61
|
+
else
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Return the worker pid, checking to see if it is running first
|
67
|
+
def worker_pid
|
68
|
+
@worker_pid = nil if ! self.running?
|
69
|
+
@worker_pid
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return true if the device is claimed
|
73
|
+
# If the device has no status set it is assumed not to be claimed
|
74
|
+
def claimed?
|
75
|
+
@status == 'claimed'
|
76
|
+
end
|
77
|
+
|
78
|
+
# Test equality with another device
|
79
|
+
def ==(other)
|
80
|
+
self.identity == other.identity
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return the unique identity of the device
|
84
|
+
def identity
|
85
|
+
"#{self.class.to_s.split('::').last}-#{@identity}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'hive'
|
2
|
+
require 'hive/results'
|
3
|
+
|
4
|
+
module Hive
|
5
|
+
class Diagnostic
|
6
|
+
|
7
|
+
class InvalidParameterError < StandardError
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :last_run
|
11
|
+
attr_reader :config, :device_api
|
12
|
+
|
13
|
+
def initialize(config, options)
|
14
|
+
@options = options
|
15
|
+
@config = config
|
16
|
+
@serial = @options['serial']
|
17
|
+
@device_api = @options['device_api']
|
18
|
+
end
|
19
|
+
|
20
|
+
def should_run?
|
21
|
+
return true if @last_run == nil
|
22
|
+
time_now = Time.new.getutc
|
23
|
+
last_run_time = @last_run.timestamp
|
24
|
+
diff = ((time_now - last_run_time)/300).round
|
25
|
+
if (diff > 2 && @last_run.passed?) || diff > 1
|
26
|
+
true
|
27
|
+
else
|
28
|
+
false
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def run
|
33
|
+
Hive.logger.info("Trying to run diagnostic '#{self.class}'")
|
34
|
+
if should_run?
|
35
|
+
result = diagnose
|
36
|
+
result = repair(result) if result.failed?
|
37
|
+
@last_run = result
|
38
|
+
else
|
39
|
+
Hive.logger.info("Diagnostic '#{self.class}' last ran less than five minutes before")
|
40
|
+
end
|
41
|
+
@last_run
|
42
|
+
end
|
43
|
+
|
44
|
+
def pass(message= {}, data = {})
|
45
|
+
Hive.logger.info(message)
|
46
|
+
Hive::Results.new("pass", message, data )
|
47
|
+
end
|
48
|
+
|
49
|
+
def fail(message ={}, data = {})
|
50
|
+
Hive.logger.info(message)
|
51
|
+
Hive::Results.new("fail", message, data)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|