chain-reactor 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +12 -0
- data/CHANGELOG.rdoc +10 -0
- data/Gemfile +9 -7
- data/README.rdoc +8 -1
- data/lib/chain-reactor/controller.rb +5 -0
- data/lib/chain-reactor/reaction.rb +1 -1
- data/lib/chain-reactor/version.rb +1 -1
- data/test/helpers.rb +7 -1
- data/test/test_chain_reactor_options.rb +13 -12
- data/test/test_chain_reactor_start.rb +75 -60
- data/test/test_chainfile_parser.rb +1 -1
- data/test/test_reactor.rb +6 -6
- metadata +4 -2
data/.travis.yml
ADDED
data/CHANGELOG.rdoc
ADDED
data/Gemfile
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
# Specify your gem's dependencies in chain-reactor.gemspec
|
4
3
|
gemspec
|
5
4
|
|
6
|
-
gem '
|
7
|
-
gem '
|
8
|
-
gem '
|
9
|
-
gem '
|
5
|
+
gem 'rake', '~> 0.8.7'
|
6
|
+
gem 'main', '~> 5.1.1'
|
7
|
+
gem 'json', '~> 1.7.5'
|
8
|
+
gem 'dante', '~> 0.1.5'
|
9
|
+
gem 'log4r', '~> 1.1.10'
|
10
|
+
gem 'rdoc', '~> 3.12'
|
10
11
|
|
11
|
-
gem 'xml-simple', :require => false
|
12
|
+
gem 'xml-simple', '~> 1.1.2', :require => false
|
12
13
|
|
13
14
|
group :test do
|
14
|
-
gem '
|
15
|
+
gem 'test-unit', '~> 2.5.3'
|
16
|
+
gem 'mocha', '~> 0.13.1'
|
15
17
|
end
|
data/README.rdoc
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
= Chain Reactor
|
2
2
|
|
3
|
+
{<img src="https://travis-ci.org/joonty/chain-reactor.png?branch=master" alt="Build Status" />}[https://travis-ci.org/joonty/chain-reactor]
|
4
|
+
{<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/joonty/chain-reactor]
|
5
|
+
|
3
6
|
Chain Reactor is a simple server that waits for incoming connections, then kicks off some code when one is made. Clients are locked down by IP address, so you will only recieve connections from known clients. Also, data can be passed via JSON, XML, or anything you like.
|
4
7
|
|
5
8
|
The "reactions" are written in ruby code, like this:
|
@@ -31,7 +34,9 @@ This can be installed through ruby gems:
|
|
31
34
|
|
32
35
|
== Usage
|
33
36
|
|
34
|
-
|
37
|
+
Chain reactor comes with two command line tools, <tt>chain-reactor</tt> (the server) and <tt>chain-reactor-client</tt> (I think you can guess).
|
38
|
+
|
39
|
+
A chain file is required to run the server, as this specifies which clients to accept and what to do. Fortunately, it's easy to create a template:
|
35
40
|
|
36
41
|
$ chain-reactor template > Chainfile.rb
|
37
42
|
|
@@ -45,6 +50,8 @@ The --ontop option stops it from running as a daemon, making it easier to see wh
|
|
45
50
|
|
46
51
|
Follow the instructions by adding key/value pair data to send to the server, then watch it react!
|
47
52
|
|
53
|
+
For more information on configuration, take a look at the wiki.
|
54
|
+
|
48
55
|
== Contributing
|
49
56
|
|
50
57
|
1. Fork it
|
@@ -20,6 +20,11 @@ module ChainReactor
|
|
20
20
|
#
|
21
21
|
# Uses dante as the daemonizer.
|
22
22
|
def start
|
23
|
+
if not @config.on_top? and RUBY_PLATFORM == 'java'
|
24
|
+
@log.warn { "fork() is not implemented in JRuby, chain-reactor will run on top instead of daemonizing" }
|
25
|
+
@config.on_top = true
|
26
|
+
end
|
27
|
+
|
23
28
|
# Change output format for logging to file if daemonizing
|
24
29
|
unless @config.on_top?
|
25
30
|
@log.outputters.first.formatter = ChainReactor::PatternFormatter.new(:pattern => "[%l] %d :: %m")
|
@@ -24,7 +24,7 @@ module ChainReactor
|
|
24
24
|
attr_accessor :options
|
25
25
|
|
26
26
|
# Create a new reaction, with the options and code block to run.
|
27
|
-
def initialize(options
|
27
|
+
def initialize(options,block,logger)
|
28
28
|
@options = { :parser => :json, :required_keys => [], :keys_to_sym => true }.merge(options)
|
29
29
|
@block = block
|
30
30
|
@log = logger
|
data/test/helpers.rb
CHANGED
@@ -14,6 +14,12 @@ module ChainReactor
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
# KeyError doesn't exist in ruby 1.8
|
18
|
+
if !defined? ::KeyError
|
19
|
+
class ::KeyError < ::IndexError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
17
23
|
class Params
|
18
24
|
def initialize(hash_data)
|
19
25
|
@hash = hash_data
|
@@ -26,7 +32,7 @@ module ChainReactor
|
|
26
32
|
def fetch(key,&block)
|
27
33
|
begin
|
28
34
|
@hash.fetch(key,&block)
|
29
|
-
rescue KeyError => e
|
35
|
+
rescue ::KeyError,::IndexError => e
|
30
36
|
raise ::Main::Parameter::NoneSuch, key
|
31
37
|
end
|
32
38
|
end
|
@@ -10,12 +10,18 @@ class TestChainReactorOptions < Test::Unit::TestCase
|
|
10
10
|
Open3.popen3("#{bin_path} #{arg_string}")
|
11
11
|
end
|
12
12
|
|
13
|
+
# Thread can be nil, if ruby 1.8 is used
|
14
|
+
def assert_exit_status(thread,expected_status)
|
15
|
+
unless thread.nil?
|
16
|
+
assert_equal expected_status, thread.value.exitstatus
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
13
20
|
def test_help_returns_help_in_stdout
|
14
21
|
out = exec_with('--help')
|
15
22
|
help = out[1].read
|
16
|
-
status = out[3].value
|
17
23
|
|
18
|
-
|
24
|
+
assert_exit_status(out[3],1)
|
19
25
|
assert_match(/NAME\s*chain\-reactor/, help)
|
20
26
|
assert_match(/SYNOPSIS\s*chain\-reactor \(start|stop|template\) chainfile\.rb/, help)
|
21
27
|
end
|
@@ -23,9 +29,8 @@ class TestChainReactorOptions < Test::Unit::TestCase
|
|
23
29
|
def test_no_args_returns_help_in_stdout_and_fails
|
24
30
|
out = exec_with('')
|
25
31
|
help = out[1].read
|
26
|
-
status = out[3].value
|
27
32
|
|
28
|
-
|
33
|
+
assert_exit_status(out[3],1)
|
29
34
|
assert_match(/NAME\s*chain\-reactor/, help)
|
30
35
|
assert_match(/SYNOPSIS\s*chain\-reactor \(start|stop|template\) chainfile\.rb/, help)
|
31
36
|
end
|
@@ -33,9 +38,8 @@ class TestChainReactorOptions < Test::Unit::TestCase
|
|
33
38
|
def test_invalid_mode_returns_help_and_fails
|
34
39
|
out = exec_with('ssaduhdui')
|
35
40
|
help = out[1].read
|
36
|
-
status = out[3].value
|
37
41
|
|
38
|
-
|
42
|
+
assert_exit_status(out[3],1)
|
39
43
|
assert_match(/NAME\s*chain\-reactor/, help)
|
40
44
|
assert_match(/SYNOPSIS\s*chain\-reactor \(start|stop|template\) chainfile\.rb/, help)
|
41
45
|
end
|
@@ -43,27 +47,24 @@ class TestChainReactorOptions < Test::Unit::TestCase
|
|
43
47
|
def test_template_returns_chainfile_in_stdout
|
44
48
|
out = exec_with('template')
|
45
49
|
template = out[1].read
|
46
|
-
status = out[3].value
|
47
50
|
|
48
|
-
|
51
|
+
assert_exit_status(out[3],0)
|
49
52
|
assert_match(/react_to\(/, template)
|
50
53
|
end
|
51
54
|
|
52
55
|
def test_start_without_chainfile_fails
|
53
56
|
out = exec_with('start')
|
54
57
|
error = out[2].read
|
55
|
-
status = out[3].value
|
56
58
|
|
57
|
-
|
59
|
+
assert_exit_status(out[3],1)
|
58
60
|
assert_match(/chain-reactor: A valid chainfile must be supplied/, error)
|
59
61
|
end
|
60
62
|
|
61
63
|
def test_stop_without_chainfile_fails
|
62
64
|
out = exec_with('stop')
|
63
65
|
error = out[2].read
|
64
|
-
status = out[3].value
|
65
66
|
|
66
|
-
|
67
|
+
assert_exit_status(out[3],1)
|
67
68
|
assert_match(/chain-reactor: A valid chainfile must be supplied/, error)
|
68
69
|
end
|
69
70
|
end
|
@@ -1,88 +1,103 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
require 'rubygems'
|
3
3
|
require 'open3'
|
4
|
-
require 'sys/proctable'
|
5
4
|
require 'client'
|
6
5
|
|
7
6
|
# Test case for the options of the chain reactor executable.
|
8
7
|
class TestChainReactorStart < Test::Unit::TestCase
|
9
|
-
include Sys
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
9
|
+
if RUBY_PLATFORM != 'java'
|
10
|
+
def setup
|
11
|
+
@test_path = File.dirname(__FILE__)
|
12
|
+
@bin_path = @test_path + '/../bin/chain-reactor'
|
13
|
+
@chainfile_path = @test_path + '/chainfile.test'
|
14
|
+
@pid_path = @test_path + '/chain-reactor.pid'
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
def teardown
|
18
|
+
_,_,_,t = stop_chain_reactor
|
19
|
+
if t.nil?
|
20
|
+
# Ruby 1.8
|
21
|
+
sleep 2
|
22
|
+
else
|
23
|
+
# Ruby 1.9+
|
24
|
+
t.value
|
25
|
+
end
|
26
|
+
|
27
|
+
$stdout = STDOUT
|
28
|
+
end
|
24
29
|
|
25
|
-
|
26
|
-
|
27
|
-
|
30
|
+
def start_chain_reactor(arg_string)
|
31
|
+
Open3.popen3("#{@bin_path} start #{@chainfile_path} #{arg_string} --pidfile #{@pid_path}")
|
32
|
+
end
|
28
33
|
|
29
|
-
|
30
|
-
|
31
|
-
|
34
|
+
def stop_chain_reactor
|
35
|
+
Open3.popen3("#{@bin_path} stop #{@chainfile_path} --pidfile #{@pid_path}")
|
36
|
+
end
|
32
37
|
|
33
|
-
|
34
|
-
|
35
|
-
|
38
|
+
def test_start_daemon
|
39
|
+
if RUBY_VERSION.to_f < 1.9
|
40
|
+
# Thread is not available
|
41
|
+
return
|
42
|
+
end
|
43
|
+
_, stdout, _, thread = start_chain_reactor('')
|
44
|
+
output = stdout.read
|
36
45
|
|
37
|
-
|
38
|
-
|
39
|
-
|
46
|
+
assert_match(/Registered 1 reactions/,output)
|
47
|
+
assert_match(/Starting daemon, PID file => #{@pid_path}/,output)
|
48
|
+
assert_match(/Daemon has started successfully/,output)
|
40
49
|
|
41
|
-
|
42
|
-
|
50
|
+
# Wait for thread to exit
|
51
|
+
thread.value
|
43
52
|
|
44
|
-
|
45
|
-
|
46
|
-
prc = ProcTable.ps(pid)
|
53
|
+
# Thread stopped as it's daemonized
|
54
|
+
assert_equal true, thread.stop?
|
47
55
|
|
48
|
-
|
49
|
-
|
50
|
-
end
|
56
|
+
assert File.file? @pid_path
|
57
|
+
pid = File.new(@pid_path).read.to_i
|
51
58
|
|
52
|
-
|
53
|
-
|
54
|
-
output = stdout.read
|
55
|
-
assert_match(/Daemon has started successfully/,output)
|
59
|
+
ps_output = `ps p #{pid} --no-heading`
|
60
|
+
assert_not_equal "", ps_output
|
56
61
|
|
57
|
-
|
58
|
-
$stdout = File.new('/dev/null','w')
|
59
|
-
client = ChainReactor::Client.new('127.0.0.1',20000)
|
60
|
-
client.send_as_json({:hello => :world})
|
62
|
+
assert_match(/chain\-reactor start/,ps_output)
|
61
63
|
end
|
62
|
-
end
|
63
64
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
65
|
+
def test_start_daemon_and_communicate_with_client
|
66
|
+
_, stdout, stderr, _ = start_chain_reactor('')
|
67
|
+
output = stdout.read
|
68
|
+
assert_match(/Daemon has started successfully/,output)
|
68
69
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
70
|
+
assert_nothing_raised ChainReactor::ClientError do
|
71
|
+
$stdout = File.new('/dev/null','w')
|
72
|
+
client = ChainReactor::Client.new('127.0.0.1',1987)
|
73
|
+
client.send_as_json({:hello => :world})
|
74
|
+
end
|
73
75
|
end
|
74
|
-
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
77
|
+
def test_start_daemon_with_port_override_and_communicate_with_client
|
78
|
+
_, stdout, _, _ = start_chain_reactor('--port 20100')
|
79
|
+
output = stdout.read
|
80
|
+
assert_match(/Daemon has started successfully/,output)
|
80
81
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
assert_nothing_raised ChainReactor::ClientError do
|
83
|
+
$stdout = File.new('/dev/null','w')
|
84
|
+
client = ChainReactor::Client.new('127.0.0.1',20100)
|
85
|
+
client.send({:hello => :world})
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_start_daemon_with_address_override_and_communicate_with_client
|
90
|
+
_, stdout, _, _ = start_chain_reactor('--address 0.0.0.0')
|
91
|
+
output = stdout.read
|
92
|
+
assert_match(/Daemon has started successfully/,output)
|
93
|
+
|
94
|
+
assert_nothing_raised ChainReactor::ClientError do
|
95
|
+
$stdout = File.new('/dev/null','w')
|
96
|
+
client = ChainReactor::Client.new('0.0.0.0',1987)
|
97
|
+
client.send({:hello => :world})
|
98
|
+
end
|
85
99
|
end
|
86
100
|
end
|
101
|
+
|
87
102
|
end
|
88
103
|
|
@@ -50,7 +50,7 @@ class TestChainfileParser < Test::Unit::TestCase
|
|
50
50
|
|
51
51
|
def test_single_reaction_with_options_is_added_to_reactor
|
52
52
|
chain = <<-chain
|
53
|
-
react_to('192.168.0.1', parser
|
53
|
+
react_to('192.168.0.1', :parser => :dummy, :required_keys => [:hello,:world]) { |data| puts data.inspect }
|
54
54
|
chain
|
55
55
|
|
56
56
|
parser = ChainReactor::ChainfileParser.new(File.new(chain),
|
data/test/test_reactor.rb
CHANGED
@@ -15,21 +15,21 @@ class TestReactor < Test::Unit::TestCase
|
|
15
15
|
|
16
16
|
def test_adding_reaction_makes_address_allowable
|
17
17
|
reactor = ChainReactor::Reactor.new get_logger
|
18
|
-
reactor.add(['127.0.0.1'],{parser
|
18
|
+
reactor.add(['127.0.0.1'],{:parser => :dummy},Proc.new {})
|
19
19
|
assert reactor.address_allowed? '127.0.0.1'
|
20
20
|
end
|
21
21
|
|
22
22
|
def test_react_raises_error
|
23
23
|
reactor = ChainReactor::Reactor.new get_logger
|
24
24
|
assert_raises RuntimeError, 'Address is not allowed' do
|
25
|
-
reactor.react('127.0.0.1',{parser
|
25
|
+
reactor.react('127.0.0.1',{:parser => :dummy})
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
def test_react_calls_block
|
30
30
|
reactor = ChainReactor::Reactor.new get_logger
|
31
31
|
block = Proc.new { |d| 'block has been called' }
|
32
|
-
reactor.add(['127.0.0.1'],{parser
|
32
|
+
reactor.add(['127.0.0.1'],{:parser => :dummy},block)
|
33
33
|
reactions = reactor.reactions_for('127.0.0.1')
|
34
34
|
reactor.react('127.0.0.1','This is a string')
|
35
35
|
assert_equal 'block has been called', reactions[0].previous_result
|
@@ -41,8 +41,8 @@ class TestReactor < Test::Unit::TestCase
|
|
41
41
|
block1 = Proc.new { |d| 'block1' }
|
42
42
|
block2 = Proc.new { |d| 'block2' }
|
43
43
|
|
44
|
-
reactor.add(['127.0.0.1'],{parser
|
45
|
-
reactor.add(['127.0.0.1'],{parser
|
44
|
+
reactor.add(['127.0.0.1'],{:parser => :dummy},block1)
|
45
|
+
reactor.add(['127.0.0.1'],{:parser => :dummy},block2)
|
46
46
|
|
47
47
|
reactor.react('127.0.0.1','This is a string')
|
48
48
|
|
@@ -54,7 +54,7 @@ class TestReactor < Test::Unit::TestCase
|
|
54
54
|
def test_react_catches_exceptions
|
55
55
|
reactor = ChainReactor::Reactor.new get_logger
|
56
56
|
block = Proc.new { |d| raise 'Block has been called' }
|
57
|
-
reactor.add(['127.0.0.1'],{parser
|
57
|
+
reactor.add(['127.0.0.1'],{:parser => :dummy},block)
|
58
58
|
reactor.reactions_for('127.0.0.1')
|
59
59
|
assert_nothing_raised do
|
60
60
|
reactor.react('127.0.0.1','This is a string')
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: chain-reactor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-21 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Trigger events across networks using TCP/IP sockets
|
15
15
|
email:
|
@@ -21,6 +21,8 @@ extensions: []
|
|
21
21
|
extra_rdoc_files: []
|
22
22
|
files:
|
23
23
|
- .gitignore
|
24
|
+
- .travis.yml
|
25
|
+
- CHANGELOG.rdoc
|
24
26
|
- Gemfile
|
25
27
|
- LICENSE.txt
|
26
28
|
- README.rdoc
|