ruby_nos 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 +15 -0
- data/.gitignore +8 -0
- data/.travis.yml +10 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +61 -0
- data/LICENSE +21 -0
- data/README.md +64 -0
- data/Rakefile +19 -0
- data/config/environment.rb +17 -0
- data/lib/initializable.rb +14 -0
- data/lib/ruby_nos.rb +64 -0
- data/lib/ruby_nos/agent.rb +127 -0
- data/lib/ruby_nos/aliasing.rb +15 -0
- data/lib/ruby_nos/cloud.rb +80 -0
- data/lib/ruby_nos/endpoint.rb +43 -0
- data/lib/ruby_nos/formatter.rb +25 -0
- data/lib/ruby_nos/list.rb +34 -0
- data/lib/ruby_nos/message.rb +47 -0
- data/lib/ruby_nos/processor.rb +133 -0
- data/lib/ruby_nos/remote_agent.rb +41 -0
- data/lib/ruby_nos/rest_api.rb +23 -0
- data/lib/ruby_nos/signature_generator.rb +22 -0
- data/lib/ruby_nos/udp_receptor.rb +47 -0
- data/lib/ruby_nos/udp_sender.rb +26 -0
- data/lib/ruby_nos/version.rb +3 -0
- data/ruby_nos.gemspec +22 -0
- data/spec/ruby_nos.spec.rb +7 -0
- data/spec/ruby_nos/agent_spec.rb +120 -0
- data/spec/ruby_nos/cloud_spec.rb +107 -0
- data/spec/ruby_nos/endpoint_spec.rb +38 -0
- data/spec/ruby_nos/formatter_spec.rb +34 -0
- data/spec/ruby_nos/list_spec.rb +54 -0
- data/spec/ruby_nos/message_spec.rb +47 -0
- data/spec/ruby_nos/processor_spec.rb +104 -0
- data/spec/ruby_nos/remote_agent_spec.rb +57 -0
- data/spec/ruby_nos/rest_api_spec.rb +23 -0
- data/spec/ruby_nos/signature_generator_spec.rb +28 -0
- data/spec/ruby_nos/udp_receptor_spec.rb +31 -0
- data/spec/ruby_nos/udp_sender_spec.rb +35 -0
- data/spec/spec_helper.rb +28 -0
- metadata +129 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Aliasing
|
|
2
|
+
|
|
3
|
+
module ClassMethods
|
|
4
|
+
def attr_alias(new_attr, original)
|
|
5
|
+
alias_method(new_attr, original) if method_defined? original
|
|
6
|
+
new_writer = "#{new_attr}="
|
|
7
|
+
original_writer = "#{original}="
|
|
8
|
+
alias_method(new_writer, original_writer) if method_defined? original_writer
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.included klass
|
|
13
|
+
klass.extend ClassMethods
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module RubyNos
|
|
2
|
+
class Cloud
|
|
3
|
+
include Initializable
|
|
4
|
+
attr_accessor :uuid, :current_agent, :list
|
|
5
|
+
|
|
6
|
+
def uuid
|
|
7
|
+
@uuid ||= RubyNos.cloud_uuid
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def list
|
|
11
|
+
@list ||= List.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def update agent_info
|
|
15
|
+
if agent_info.is_a?(Hash)
|
|
16
|
+
self.current_agent = build_remote_agent(agent_info)
|
|
17
|
+
else
|
|
18
|
+
self.current_agent = agent_info
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if list.is_on_the_list?(self.current_agent.uuid)
|
|
22
|
+
update_actual_info
|
|
23
|
+
else
|
|
24
|
+
RubyNos.logger.send(:info, "Added agent #{self.current_agent.uuid}")
|
|
25
|
+
list.add(self.current_agent)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def build_remote_agent agent_info
|
|
32
|
+
agent = RemoteAgent.new(uuid: agent_info[:agent_uuid], timestamp: (agent_info[:timestamp] || timestamp_for_list))
|
|
33
|
+
info = agent_info[:info]
|
|
34
|
+
agent.endpoints = process_endpoints(info[:endpoints]) if (info && info[:endpoints])
|
|
35
|
+
agent
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def timestamp_for_list
|
|
39
|
+
Formatter.timestamp
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def update_actual_info
|
|
43
|
+
if correct_timestamp? && !same_info?
|
|
44
|
+
prepare_agent
|
|
45
|
+
list.update(self.current_agent.uuid, self.current_agent)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def remote_agent_on_the_list
|
|
50
|
+
list.info_for(self.current_agent.uuid)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def prepare_agent
|
|
54
|
+
if self.current_agent.endpoints == []
|
|
55
|
+
self.current_agent.endpoints = remote_agent_on_the_list.endpoints if remote_agent_on_the_list.endpoints
|
|
56
|
+
end
|
|
57
|
+
if self.current_agent.rest_api == nil
|
|
58
|
+
self.current_agent.rest_api = remote_agent_on_the_list.rest_api if remote_agent_on_the_list.rest_api
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def correct_timestamp?
|
|
63
|
+
timestamp = current_agent.timestamp
|
|
64
|
+
((timestamp_for_list - RubyNos.keep_alive_time) < timestamp) && (timestamp <= timestamp_for_list)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def same_info?
|
|
68
|
+
remote_agent_on_the_list.same_endpoints?(self.current_agent) && remote_agent_on_the_list.same_api?(self.current_agent) && remote_agent_on_the_list.same_timestamp?(self.current_agent)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def process_endpoints endpoints
|
|
72
|
+
[].tap do |endpoints_info|
|
|
73
|
+
endpoints.each do |endpoint|
|
|
74
|
+
e_info = endpoint.split(",")
|
|
75
|
+
endpoints_info << Endpoint.new({type: e_info[0], port: e_info[1], host: e_info[2]})
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module RubyNos
|
|
2
|
+
class Endpoint
|
|
3
|
+
include Initializable
|
|
4
|
+
include Aliasing
|
|
5
|
+
|
|
6
|
+
attr_accessor :path, :port, :sticky, :type, :priority, :host
|
|
7
|
+
ALLOWED_TYPES = ["PUBLIC", "HEALTHCHECK", "INTERNAL", "MSNOS_HTTP", "UDP", "HTTP", "PUB", "HCK", "INT", "MHT"]
|
|
8
|
+
|
|
9
|
+
attr_alias :pa, :path
|
|
10
|
+
attr_alias :po, :port
|
|
11
|
+
attr_alias :st, :sticky
|
|
12
|
+
attr_alias :ty, :type
|
|
13
|
+
attr_alias :xp, :priority
|
|
14
|
+
attr_alias :ho, :host
|
|
15
|
+
|
|
16
|
+
def type= type
|
|
17
|
+
if ALLOWED_TYPES.include?(type)
|
|
18
|
+
@type = type
|
|
19
|
+
else
|
|
20
|
+
raise ArgumentError
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def priority
|
|
25
|
+
@priority ||= 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def sticky
|
|
29
|
+
@sticky ||= 0
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def to_hash
|
|
33
|
+
{
|
|
34
|
+
pa: self.path,
|
|
35
|
+
po: self.port,
|
|
36
|
+
st: self.sticky,
|
|
37
|
+
ty: self.type,
|
|
38
|
+
xp: self.priority,
|
|
39
|
+
ho: self.host
|
|
40
|
+
}
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
module RubyNos
|
|
4
|
+
class Formatter
|
|
5
|
+
def convert_to_uuid string_uuid
|
|
6
|
+
string_uuid.match(/(\h{8})(\h{4})(\h{4})(\h{4})(\h{12})/).captures.join("-")
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def uuid_format? uuid
|
|
10
|
+
!!uuid.match(/\h{8}-\h{4}-\h{4}-\h{4}-\h{12}/)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def uuid_to_string uuid
|
|
14
|
+
uuid.gsub("-", "")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse_message message
|
|
18
|
+
JSON.parse(message, {symbolize_names: true})
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.timestamp
|
|
22
|
+
(Time.now.to_f*1000).to_i
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module RubyNos
|
|
2
|
+
class List
|
|
3
|
+
include Initializable
|
|
4
|
+
attr_accessor :list
|
|
5
|
+
|
|
6
|
+
def list
|
|
7
|
+
@list ||= []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add element
|
|
11
|
+
list << {element.uuid => element}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def update uuid, new_element
|
|
15
|
+
list.select{|e| e[uuid]}.first[uuid] = new_element
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def eliminate uuid
|
|
19
|
+
list.delete_if{|e| e.keys.first == uuid}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def info_for uuid
|
|
23
|
+
list.select{|e| e[uuid]}.first[uuid]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def list_of_keys
|
|
27
|
+
list.map{|e| e.keys}.flatten
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def is_on_the_list? uuid
|
|
31
|
+
list_of_keys.include?(uuid)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require "securerandom"
|
|
2
|
+
require "initializable"
|
|
3
|
+
|
|
4
|
+
module RubyNos
|
|
5
|
+
class Message
|
|
6
|
+
include Aliasing
|
|
7
|
+
|
|
8
|
+
include Initializable
|
|
9
|
+
attr_accessor :version, :from, :type, :to, :hops, :reliable, :data, :id, :signature, :timestamp
|
|
10
|
+
attr_alias :v, :version
|
|
11
|
+
attr_alias :fr, :from
|
|
12
|
+
attr_alias :ty, :type
|
|
13
|
+
attr_alias :hp, :hops
|
|
14
|
+
attr_alias :rx, :reliable
|
|
15
|
+
attr_alias :dt, :data
|
|
16
|
+
attr_alias :sg, :signature
|
|
17
|
+
attr_alias :ts, :timestamp
|
|
18
|
+
|
|
19
|
+
def to_hash
|
|
20
|
+
{
|
|
21
|
+
v: self.version || "1.0",
|
|
22
|
+
ty: self.type,
|
|
23
|
+
fr: self.from,
|
|
24
|
+
to: self.to,
|
|
25
|
+
hp: self.hops || RubyNos.hops,
|
|
26
|
+
ts: self.timestamp || generate_miliseconds_timestamp,
|
|
27
|
+
rx: self.reliable || 0,
|
|
28
|
+
dt: self.data
|
|
29
|
+
}.delete_if{|key, value| value==nil || value == {}}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def serialize
|
|
33
|
+
message = to_hash
|
|
34
|
+
message.merge!({sg: signature_generator.generate_signature(message.to_s)})
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def generate_miliseconds_timestamp
|
|
40
|
+
Formatter.timestamp
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def signature_generator
|
|
44
|
+
@signature_generator ||= SignatureGenerator.new
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
module RubyNos
|
|
2
|
+
class Processor
|
|
3
|
+
|
|
4
|
+
attr_accessor :agent, :current_message
|
|
5
|
+
|
|
6
|
+
def initialize agent
|
|
7
|
+
@agent = agent
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def formatter
|
|
11
|
+
@formatter ||= Formatter.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def signature_generator
|
|
15
|
+
@signature_generator ||= SignatureGenerator.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def process_message received_message
|
|
19
|
+
formatted_message = formatter.parse_message(received_message)
|
|
20
|
+
self.current_message = Message.new(formatted_message)
|
|
21
|
+
|
|
22
|
+
unless sender_uuid == agent.uuid || !correct_signature?(formatted_message)
|
|
23
|
+
RubyNos.logger.send(:info, "#{self.current_message.type} arrives")
|
|
24
|
+
if agent_receptor? || cloud_receptor?
|
|
25
|
+
processor = message_processor.fetch(current_message.type, nil)
|
|
26
|
+
processor.call if processor
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def message_processor
|
|
34
|
+
{
|
|
35
|
+
"PIN" => lambda {process_pin_message},
|
|
36
|
+
"PON" => lambda {process_pon_message},
|
|
37
|
+
"PRS" => lambda {process_presence_message},
|
|
38
|
+
"DSC" => lambda {process_discovery_message},
|
|
39
|
+
"ENQ" => lambda {process_enquiry_message},
|
|
40
|
+
"QNE" => lambda {process_enquiry_answer_message}
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def sender_uuid
|
|
45
|
+
get_uuid(self.current_message.from)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def receptor_uuid
|
|
49
|
+
get_uuid(self.current_message.to)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def received_api
|
|
53
|
+
RestApi.new({name: self.current_message.data[:name]}).tap do |api|
|
|
54
|
+
if self.current_message.data[:apis]
|
|
55
|
+
self.current_message.data[:apis].each do |endpoint|
|
|
56
|
+
api.add_endpoint(endpoint)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def correct_signature? received_message
|
|
63
|
+
if received_message[:sg]
|
|
64
|
+
signature = received_message.delete(:sg)
|
|
65
|
+
signature_generator.valid_signature?(received_message.to_s, signature)
|
|
66
|
+
else
|
|
67
|
+
true
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def process_pin_message
|
|
72
|
+
update_cloud
|
|
73
|
+
send_response "PON"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def process_pon_message
|
|
77
|
+
update_cloud
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def process_presence_message
|
|
81
|
+
if self.current_message.data[:present] == 0
|
|
82
|
+
agent.cloud.list.eliminate(sender_uuid)
|
|
83
|
+
else
|
|
84
|
+
update_cloud
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def process_discovery_message
|
|
89
|
+
unless agent.cloud.list.is_on_the_list?(sender_uuid)
|
|
90
|
+
update_cloud
|
|
91
|
+
end
|
|
92
|
+
send_response "PRS"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def process_enquiry_message
|
|
96
|
+
update_cloud
|
|
97
|
+
send_response "QNE"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def process_enquiry_answer_message
|
|
101
|
+
agent.cloud.update(RemoteAgent.new({uuid: sender_uuid, timestamp: self.current_message.timestamp, rest_api: received_api}))
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def update_cloud
|
|
105
|
+
agent.cloud.update({agent_uuid: sender_uuid, info: self.current_message.data, timestamp: self.current_message.timestamp}) unless agent.cloud.uuid == sender_uuid
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def agent_receptor?
|
|
109
|
+
agent.uuid == receptor_uuid
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def cloud_receptor?
|
|
113
|
+
agent.cloud.uuid == receptor_uuid
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def get_uuid uuid_param
|
|
117
|
+
if uuid_param.start_with?("AGT:", "CLD:")
|
|
118
|
+
uuid = formatter.convert_to_uuid(uuid_param[4..-1])
|
|
119
|
+
if formatter.uuid_format?(uuid)
|
|
120
|
+
uuid
|
|
121
|
+
else
|
|
122
|
+
raise(ArgumentError, 'Argument format is incorrect')
|
|
123
|
+
end
|
|
124
|
+
else
|
|
125
|
+
raise(ArgumentError, 'Argument format is incorrect')
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def send_response type
|
|
130
|
+
agent.send_message({type: type})
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module RubyNos
|
|
2
|
+
class RemoteAgent
|
|
3
|
+
include Initializable
|
|
4
|
+
attr_accessor :uuid, :rest_api, :endpoints, :timestamp
|
|
5
|
+
|
|
6
|
+
def endpoints
|
|
7
|
+
@endpoints ||= []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_endpoint *args
|
|
11
|
+
endpoints << Endpoint.new(*args)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def endpoints_collection
|
|
15
|
+
endpoints.map{|e| e.to_hash}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def same_endpoints? another_agent
|
|
19
|
+
endpoints_collection == another_agent.endpoints_collection
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def same_api? another_agent
|
|
23
|
+
if rest_api && another_agent.rest_api
|
|
24
|
+
rest_api.to_hash == another_agent.rest_api.to_hash
|
|
25
|
+
else
|
|
26
|
+
false
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def same_timestamp? another_agent
|
|
31
|
+
timestamp == another_agent.timestamp
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def to_hash
|
|
35
|
+
{
|
|
36
|
+
uuid: uuid,
|
|
37
|
+
timestamp: Time.at(timestamp/1000)
|
|
38
|
+
}
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module RubyNos
|
|
2
|
+
class RestApi
|
|
3
|
+
include Initializable
|
|
4
|
+
attr_accessor :name, :endpoints, :port, :host
|
|
5
|
+
|
|
6
|
+
def endpoints
|
|
7
|
+
@endpoints ||= []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def add_endpoint args
|
|
11
|
+
args.merge!({port: port}) unless (args[:port] || args[:po] )
|
|
12
|
+
args.merge!({host: host}) unless (args[:host] || args[:ho] )
|
|
13
|
+
endpoints << Endpoint.new(args)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_hash
|
|
17
|
+
{
|
|
18
|
+
name: self.name,
|
|
19
|
+
apis: endpoints.map{|e| e.to_hash}
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|