wires-cluster 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +2 -0
- data/lib/wires/cluster.rb +78 -0
- data/lib/wires/cluster/json.rb +99 -0
- data/lib/wires/cluster/udp.rb +115 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 52d376216621589ebc6244e42f99969ca1762986
|
4
|
+
data.tar.gz: b55d81e07aebad4eaf6e31e0440cffe8596aec4d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7456c1ac652e1dcd87e0768c90d09f531cf023a608846274421083376be8367136259cdb29e4aee9387609e0a8fcbd187e7444a077d48f4c208b3ab7de6f16e0
|
7
|
+
data.tar.gz: 23a1faf0eaab0db74c549c6df870ce64e79dcb8c83966f85279c62107f9b02eb07f09c88728fcebae0a98d161f6f0a7211b19f5596a52236f65da32266fa1d74
|
data/LICENSE
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
|
2
|
+
require 'wires'
|
3
|
+
require "#{File.dirname __FILE__}/cluster/udp"
|
4
|
+
require "#{File.dirname __FILE__}/cluster/json"
|
5
|
+
|
6
|
+
|
7
|
+
module Wires
|
8
|
+
module Cluster
|
9
|
+
PORT = 4567
|
10
|
+
GROUP = "224.0.1.33"
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
# Block indefinitely while receiving messages
|
15
|
+
def listen!
|
16
|
+
listen
|
17
|
+
@rx_thread.join
|
18
|
+
end
|
19
|
+
|
20
|
+
# Start the UDP receiving thread (or stop it, if action==:stop)
|
21
|
+
def listen(action=:start)
|
22
|
+
case action
|
23
|
+
when :start
|
24
|
+
@rx ||= UDP::RX.new GROUP, PORT
|
25
|
+
raise IOError, "Probably firewalled..." unless @rx.test!
|
26
|
+
@rx_thread ||= Thread.new { _rx_loop }
|
27
|
+
when :stop
|
28
|
+
@rx_thread.kill; @rx_thread = nil
|
29
|
+
@rx.close; @rx = nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Enable UDP sending of fired events (or disable, if action==:stop)
|
34
|
+
def spout(action=:start)
|
35
|
+
case action
|
36
|
+
when :start
|
37
|
+
@tx ||= UDP::TX.new GROUP, PORT
|
38
|
+
@tx_proc = Wires::Channel.after_fire(true) do |e,c|
|
39
|
+
@tx.puts JSON.dump [e,c] unless e.cluster_source
|
40
|
+
end
|
41
|
+
when :stop
|
42
|
+
Wires::Channel.remove_hook :@after_fire, @tx_proc
|
43
|
+
@tx.close; @tx = nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Loop through incoming messages and deploy valid firable events
|
48
|
+
def _rx_loop
|
49
|
+
ongoing = {}
|
50
|
+
loop do
|
51
|
+
msg = @rx.gets
|
52
|
+
ongoing[msg.source] ||= ''
|
53
|
+
ongoing[msg.source] += msg
|
54
|
+
|
55
|
+
begin
|
56
|
+
data = _load_json(ongoing[msg.source])
|
57
|
+
if (data[0].is_a? Event) and (data[1].is_a? Channel)
|
58
|
+
event, chan = data
|
59
|
+
event.instance_variable_set(:@cluster_source, msg.source)
|
60
|
+
Convenience::fire *data
|
61
|
+
end
|
62
|
+
ongoing[msg.source] = nil
|
63
|
+
rescue JSON::MissingTailError
|
64
|
+
rescue JSON::ParserError
|
65
|
+
ongoing[msg.source] = nil
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end; private :_rx_loop
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Wires::Event
|
77
|
+
attr_reader :cluster_source
|
78
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
|
2
|
+
# For now, must require in this order
|
3
|
+
# to avoid active_support wrecking json/add/core
|
4
|
+
require 'active_support/json'
|
5
|
+
require 'json/add/core'
|
6
|
+
|
7
|
+
# Add for reference when parsing for incomplete packets
|
8
|
+
require 'json/pure/parser'
|
9
|
+
|
10
|
+
|
11
|
+
# Define default JSON behavior for objects as UnserializableObject
|
12
|
+
class Object
|
13
|
+
def as_json(opt=nil)
|
14
|
+
UnserializableObject.new.as_json
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.json_create(data)
|
18
|
+
UnserializableObject.new
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# A placeholder object to alert the JSON receiver that
|
23
|
+
# the intended object has no defined serialization scheme
|
24
|
+
class UnserializableObject
|
25
|
+
def to_s; '<UnserializableObject>'; end
|
26
|
+
def inspect; to_s; end
|
27
|
+
|
28
|
+
def as_json(opt=nil)
|
29
|
+
{ json_class:self.class.name }
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.json_create(data)
|
33
|
+
self.new
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Define a serialization scheme for Wires Events
|
39
|
+
class Wires::Event
|
40
|
+
def as_json(*serialization_args)
|
41
|
+
{ json_class: self.class.name,
|
42
|
+
args: [*@args, **@kwargs] }
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.json_create(data)
|
46
|
+
self.new(*data['args'])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Define a serialization scheme for Wires Channels
|
51
|
+
class Wires::Channel
|
52
|
+
def as_json(*serialization_args)
|
53
|
+
{ json_class: self.class.name,
|
54
|
+
name: @name }
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.json_create(data)
|
58
|
+
self.new(data['name'])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# Add JSON-related functions to Wires::Cluster
|
64
|
+
module Wires
|
65
|
+
module Cluster
|
66
|
+
class << self
|
67
|
+
|
68
|
+
# Perform rudimentary JSON verification to make sure str is not
|
69
|
+
# a partial payload due to missing or yet-to-come packets,
|
70
|
+
# then actually load in the JSON objects and return them
|
71
|
+
def _load_json(str)
|
72
|
+
ref_parser = JSON::Pure::Parser
|
73
|
+
|
74
|
+
# Ignore json comments and strings for open/close count verification
|
75
|
+
stripped_str = str.gsub(ref_parser::IGNORE, '')
|
76
|
+
.gsub(ref_parser::STRING, '')
|
77
|
+
|
78
|
+
# Missing tail if any quote symbols remain after string purge
|
79
|
+
raise JSON::MissingTailError if stripped_str.match /(?<!\\)"/
|
80
|
+
|
81
|
+
# Make sure open/close symbol counts match
|
82
|
+
[[ref_parser::OBJECT_OPEN, ref_parser::OBJECT_CLOSE],
|
83
|
+
[ref_parser::ARRAY_OPEN, ref_parser::ARRAY_CLOSE]].each do |a, z|
|
84
|
+
case (stripped_str.scan(a).size) <=> (stripped_str.scan(z).size)
|
85
|
+
when 1; raise JSON::MissingTailError
|
86
|
+
when -1; raise JSON::MissingHeadError
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# Try to load objects from the string and return the result
|
91
|
+
JSON.load(str)
|
92
|
+
end; private :_load_json
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class JSON::MissingTailError < JSON::ParserError; end
|
99
|
+
class JSON::MissingHeadError < JSON::ParserError; end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
|
2
|
+
require 'socket'
|
3
|
+
require 'ipaddr'
|
4
|
+
|
5
|
+
module Wires
|
6
|
+
module Cluster
|
7
|
+
module UDP
|
8
|
+
|
9
|
+
class << self; attr_accessor :max_length; end
|
10
|
+
self.max_length = 1024 # default max payload length
|
11
|
+
|
12
|
+
class Xceiver
|
13
|
+
attr_reader :group, :port, :socket, :local_ip, :local_port
|
14
|
+
|
15
|
+
def self.new(*args)
|
16
|
+
if (self.class==Xceiver)
|
17
|
+
raise TypeError, "#{self.class} is an 'abstract class' only."\
|
18
|
+
" Inherit it; don't instantiate it!"; end
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(group, port, **kwargs)
|
23
|
+
@group = group
|
24
|
+
@port = port
|
25
|
+
kwargs.each_pair { |k,v| instance_variable_set("@#{k}".to_sym, v) }
|
26
|
+
open
|
27
|
+
end
|
28
|
+
|
29
|
+
def open
|
30
|
+
@socket.close if @socket
|
31
|
+
@socket = UDPSocket.new
|
32
|
+
configure
|
33
|
+
@local_ip = Socket.ip_address_list.detect{|intf| intf.ipv4_private?}
|
34
|
+
@local_port = @socket.addr[1]
|
35
|
+
return @socket
|
36
|
+
ensure
|
37
|
+
@finalizer ||= ObjectSpace.define_finalizer self, Proc.new { close }
|
38
|
+
end
|
39
|
+
|
40
|
+
def close
|
41
|
+
@socket.close if @socket
|
42
|
+
@socket = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
class TX < Xceiver
|
48
|
+
def configure
|
49
|
+
# Set up for multicasting
|
50
|
+
@socket.setsockopt Socket::IPPROTO_IP,
|
51
|
+
Socket::IP_MULTICAST_TTL,
|
52
|
+
[1].pack('i')
|
53
|
+
# Bind to any available port
|
54
|
+
@socket.bind "0.0.0.0", (@bind_port or 0)
|
55
|
+
end
|
56
|
+
|
57
|
+
def puts(m)
|
58
|
+
max = UDP.max_length
|
59
|
+
if m.size > max
|
60
|
+
self.puts m[0...max]
|
61
|
+
self.puts m[max...m.size]
|
62
|
+
else
|
63
|
+
@socket.send(m, 0, @group, @port)
|
64
|
+
m
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
class RX < Xceiver
|
71
|
+
def configure
|
72
|
+
# Add membership to the multicast group
|
73
|
+
@socket.setsockopt Socket::IPPROTO_IP,
|
74
|
+
Socket::IP_ADD_MEMBERSHIP,
|
75
|
+
IPAddr.new(@group).hton + IPAddr.new("0.0.0.0").hton
|
76
|
+
# Don't prevent future listening peers on the same machine
|
77
|
+
@socket.setsockopt(Socket::SOL_SOCKET,
|
78
|
+
Socket::SO_REUSEADDR,
|
79
|
+
[1].pack('i')) unless @selfish
|
80
|
+
# Bind the socket to the specified port or any open port on the machine
|
81
|
+
@socket.bind Socket::INADDR_ANY, @port
|
82
|
+
end
|
83
|
+
|
84
|
+
def gets
|
85
|
+
msg, addrinfo = @socket.recvfrom(UDP.max_length)
|
86
|
+
msg.instance_variable_set :@source, addrinfo[3].to_s+':'+addrinfo[1].to_s
|
87
|
+
class << msg; attr_reader :source; end
|
88
|
+
msg
|
89
|
+
end
|
90
|
+
|
91
|
+
def test!(message="#{self}.test!")
|
92
|
+
tx = UDP::TX.new @group, @port
|
93
|
+
rx = self
|
94
|
+
|
95
|
+
outer_thread = Thread.current
|
96
|
+
passed = false
|
97
|
+
thr = Thread.new do
|
98
|
+
rx.gets
|
99
|
+
passed = true
|
100
|
+
outer_thread.wakeup
|
101
|
+
end
|
102
|
+
Thread.pass
|
103
|
+
|
104
|
+
tx.puts message
|
105
|
+
sleep 1 if thr.status
|
106
|
+
thr.kill
|
107
|
+
tx.close
|
108
|
+
|
109
|
+
passed
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wires-cluster
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joe McIlvain
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-09-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: wires
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: wires-test
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: jemc-reporter
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
description: Wires extension gem for firing and receiving events between Ruby processes
|
84
|
+
with UDP multicasts.
|
85
|
+
email: joe.eli.mac@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- lib/wires/cluster/udp.rb
|
91
|
+
- lib/wires/cluster/json.rb
|
92
|
+
- lib/wires/cluster.rb
|
93
|
+
- LICENSE
|
94
|
+
homepage: https://github.com/jemc/wires-cluster/
|
95
|
+
licenses:
|
96
|
+
- 'Copyright (c) Joe McIlvain. All rights reserved '
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options: []
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project:
|
114
|
+
rubygems_version: 2.0.3
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: wires-cluster
|
118
|
+
test_files: []
|