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.
@@ -0,0 +1,22 @@
1
+ require "digest"
2
+
3
+ module RubyNos
4
+ class SignatureGenerator
5
+
6
+ attr_accessor :key
7
+
8
+ def key
9
+ @key ||= RubyNos.signature_key
10
+ end
11
+
12
+ def generate_signature data
13
+ digest = OpenSSL::Digest.new('sha1')
14
+ OpenSSL::HMAC.hexdigest(digest, key, data)
15
+ end
16
+
17
+ def valid_signature? data, signature
18
+ generated_signature = generate_signature(data)
19
+ signature == generated_signature
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,47 @@
1
+ require 'socket'
2
+ require 'ipaddr'
3
+
4
+ module RubyNos
5
+ class UDPReceptor
6
+ attr_accessor :port
7
+
8
+ def initialize
9
+ configure
10
+ end
11
+
12
+ def port
13
+ @port ||= RubyNos.port
14
+ end
15
+
16
+ def multicast_address
17
+ @multicast_address ||= RubyNos.group_address
18
+ end
19
+
20
+ def socket
21
+ @socket ||= UDPSocket.new
22
+ end
23
+
24
+ def listen processor
25
+ Thread.new do
26
+ loop do
27
+ message = @socket.recvfrom(512).first
28
+ processor.process_message(message)
29
+ end
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def configure
36
+ RubyNos.logger.send(:info, "Binding socket to #{bind_addr} IP")
37
+ membership = IPAddr.new(multicast_address).hton + IPAddr.new(bind_addr).hton
38
+ socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, membership)
39
+ socket.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
40
+ socket.bind(bind_addr, port)
41
+ end
42
+
43
+ def bind_addr
44
+ "0.0.0.0"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,26 @@
1
+ require 'socket'
2
+ require 'json'
3
+
4
+ module RubyNos
5
+ class UDPSender
6
+ include Initializable
7
+
8
+ def send args={}
9
+ socket = UDPSocket.open
10
+ socket.setsockopt(:IPPROTO_IP, :IP_MULTICAST_TTL, 1)
11
+ RubyNos.logger.send(:info, "Message sent: #{args[:message]}")
12
+ socket.send(args[:message].to_json, 0, args[:host] || multicast_address, args[:port] || port)
13
+ socket.close
14
+ end
15
+
16
+ private
17
+
18
+ def multicast_address
19
+ @multicast_address ||= RubyNos.group_address
20
+ end
21
+
22
+ def port
23
+ @port ||= RubyNos.port
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module RubyNos
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,22 @@
1
+ gem_name = "ruby_nos"
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require "#{gem_name}/version"
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = gem_name
9
+ spec.version = RubyNos::VERSION
10
+ spec.authors = ["Workshare's dev team"]
11
+ spec.email = ['_Development@workshare.com']
12
+ spec.description = "A gem to provide microservices autodiscovery to Ruby microservices."
13
+ spec.summary = "A gem to provide microservices autodiscovery to Ruby microservices. This gem allows a microservice to publish its existence on a cloud, store other microservices information and public its API."
14
+ spec.homepage = "https://github.com/worshare/#{spec.name.gsub('_','-')}"
15
+ spec.license = "Copyright"
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+ spec.add_development_dependency "bundler", "~> 1.7"
21
+ spec.add_development_dependency "rake", "~> 10.4"
22
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe RubyNos do
4
+ it 'has a version' do
5
+ expect(RubyNos::VERSION).not_to be_nil
6
+ end
7
+ end
@@ -0,0 +1,120 @@
1
+ require "spec_helper"
2
+
3
+ describe "RubyNos::Agent" do
4
+ subject{Agent.new(udp_tx: udp_sender, udp_rx: udp_receptor, cloud: cloud, processor: processor, rest_api: rest_api)}
5
+ let(:udp_receptor) {double("UDPReceptor", listen: nil)}
6
+ let(:udp_sender) {double("UDPSender", send: nil)}
7
+ let(:cloud) {double("cloud", uuid: "abcd", list: list)}
8
+ let(:list) {double("list", list_of_keys: [])}
9
+ let(:processor) {double("processor")}
10
+ let(:rest_api) {double("rest_api", endpoints: [], to_hash: {})}
11
+
12
+ describe "mantain cloud" do
13
+ context "without agents on the cloud" do
14
+ it "ask for other agents" do
15
+ expect(udp_sender).to receive(:send).twice
16
+ thread = subject.maintain_cloud
17
+ sleep 0.1
18
+ thread.kill
19
+ end
20
+ end
21
+
22
+ context "with some agent on the cloud" do
23
+ let(:list) {double("list", list_of_keys: [agent.uuid])}
24
+ let(:agent) {double("agent", uuid: "12345", timestamp: Time.now)}
25
+
26
+ before(:each) do
27
+ allow(list).to receive(:info_for).with(agent.uuid).and_return(agent)
28
+ end
29
+
30
+ context "it exists a previous message of the agent" do
31
+ it "sends_a_ping_message to the agent" do
32
+ expect(udp_sender).to receive(:send).exactly(3).times
33
+ allow(subject).to receive(:last_message_exists?).and_return(true)
34
+ thread = subject.maintain_cloud
35
+ sleep 0.1
36
+ thread.kill
37
+ end
38
+ end
39
+
40
+ context "it does not exists a previous message of the agent" do
41
+ it "eliminates the agent from the list" do
42
+ expect(udp_sender).to receive(:send).twice
43
+ allow(subject).to receive(:last_message_exists?).and_return(false)
44
+ expect(list).to receive(:eliminate).with(agent.uuid)
45
+ thread = subject.maintain_cloud
46
+ sleep 0.1
47
+ thread.kill
48
+ end
49
+ end
50
+ end
51
+ end
52
+
53
+ describe "#join_cloud" do
54
+ let(:receptor_info) {{present: 1, endpoints: []}}
55
+
56
+ before(:each) do
57
+ allow(subject).to receive(:receptor_info).and_return(receptor_info)
58
+ end
59
+
60
+ it "sends only a presence message if the agent does not have an API with endpoints" do
61
+ expect(udp_sender).to receive(:send).once
62
+ subject.join_cloud
63
+ end
64
+
65
+ it "sends a presence and a qne if the agent has an API with endpoints" do
66
+ allow(rest_api).to receive(:endpoints).and_return(["one_endpoint"])
67
+ expect(udp_sender).to receive(:send).twice
68
+ subject.join_cloud
69
+ end
70
+ end
71
+
72
+ describe "#send_desconnection_message" do
73
+ it "sends a message with presence 0" do
74
+ expect(udp_sender).to receive(:send).once
75
+ subject.send_desconnection_message
76
+ end
77
+ end
78
+
79
+
80
+ describe "#listen" do
81
+ it "starts the udp receptor" do
82
+ expect(udp_receptor).to receive(:listen)
83
+ subject.listen
84
+ end
85
+ end
86
+
87
+
88
+ describe "#send_message" do
89
+ let(:message){double("Message", :serialize => "SerializedMessage")}
90
+ let(:rest_api) {RestApi.new}
91
+ let(:receptor_info) {{present: 1, endpoints: []}}
92
+ let(:well_formed_presence_message){Message.new({from: "AGT:#{subject.uuid.gsub("-", "")}", to: "CLD:#{subject.cloud.uuid.gsub("-", "")}", type: "PRS", timestamp: "sometime", data: receptor_info}).serialize}
93
+ let(:well_formed_qne_message){Message.new({from: "AGT:#{subject.uuid.gsub("-", "")}", to: "CLD:#{subject.cloud.uuid.gsub("-", "")}", type: "QNE", timestamp: "sometime", data: rest_api.to_hash}).serialize}
94
+ let(:host) {"0.0.0.0"}
95
+ let(:port) {"3784"}
96
+
97
+ before(:each) do
98
+ subject.cloud = cloud
99
+ subject.rest_api = rest_api
100
+ allow_any_instance_of(Message).to receive(:generate_miliseconds_timestamp).and_return("sometime")
101
+ end
102
+
103
+ it "sends a message using the UDP Socket" do
104
+ expect(Message).to receive(:new).with({:from => "AGT:#{subject.uuid.gsub("-", "")}", :to => "CLD:#{cloud.uuid.gsub("-", "")}", :type => "DSC"}).and_return(message)
105
+ expect(udp_sender).to receive(:send).with({host: host, port: port, :message => "SerializedMessage"})
106
+ subject.send_message({:type => "DSC", :port => port, :host => host})
107
+ end
108
+
109
+ it "add the UDP socket info if it is a presence message" do
110
+ expect(subject).to receive(:receptor_info).and_return(receptor_info)
111
+ expect(udp_sender).to receive(:send).with({host: host, port: port, :message => well_formed_presence_message})
112
+ subject.send_message({:type => "PRS", :port => port, :host => host})
113
+ end
114
+
115
+ it "add the RestAPI info if it is a QNE message" do
116
+ expect(udp_sender).to receive(:send).with({host: host, port: port, :message => well_formed_qne_message})
117
+ subject.send_message({:type => "QNE", :port => port, :host => host})
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,107 @@
1
+ require "spec_helper"
2
+
3
+ describe "#RubyNos::Cloud" do
4
+ subject{Cloud.new(uuid:cloud_uuid)}
5
+ let(:cloud_uuid) {"122445"}
6
+ let(:agent_uuid) {"12345"}
7
+ let(:list) {double("list")}
8
+
9
+
10
+ before do
11
+ subject.list = list
12
+ end
13
+
14
+ describe "#update" do
15
+
16
+ describe "new agent" do
17
+ before do
18
+ allow(list).to receive(:is_on_the_list?).with(agent_uuid).and_return(false)
19
+ end
20
+
21
+ context "receiving a Hash" do
22
+ let(:agent_info) {{agent_uuid: agent_uuid, info: info}}
23
+ let(:info) {{:endpoints => ["UDP,something,something"]}}
24
+
25
+ it "builds a RemoteAgent and stores it on the list" do
26
+ expect(list).to receive(:add).with(an_instance_of(RemoteAgent))
27
+ subject.update(agent_info)
28
+ end
29
+ end
30
+
31
+ context "receiving a RemoteAgent" do
32
+ let(:agent) {instance_double(RemoteAgent, uuid: agent_uuid, timestamp: 1, endpoints: [], rest_api: nil)}
33
+
34
+ it "stores agents information if it is new" do
35
+ expect(list).to receive(:add).with(agent)
36
+ subject.update(agent)
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ describe "old agent" do
43
+ let(:agent) {instance_double(RemoteAgent, uuid: agent_uuid, timestamp: 1, endpoints: [], rest_api: nil)}
44
+
45
+ before(:each) do
46
+ allow(list).to receive(:is_on_the_list?).with(agent_uuid).and_return(true)
47
+ allow(list).to receive(:info_for).with(agent_uuid).and_return(agent)
48
+ end
49
+
50
+ context "agent updated with correct information" do
51
+ let(:agent_updated) {instance_double(RemoteAgent, uuid: agent_uuid, timestamp: 2, endpoints: ["something"], rest_api: nil)}
52
+
53
+ it "if the agent exists and the information is not the same it updates to this new information" do
54
+ expect(subject).to receive(:correct_timestamp?).and_return(true)
55
+ expect(agent).to receive(:same_endpoints?).with(agent_updated).and_return(false)
56
+ expect(list).to receive(:update).with(agent_uuid, agent_updated)
57
+ subject.update(agent_updated)
58
+ end
59
+ end
60
+
61
+ context "agent updating with the same information" do
62
+ let(:agent) {instance_double(RemoteAgent, uuid: agent_uuid, timestamp: 1, endpoints: ["something"], rest_api: nil)}
63
+ let(:agent_updated) {instance_double(RemoteAgent, uuid: agent_uuid, timestamp: 2, endpoints: ["something"], rest_api: nil)}
64
+
65
+ it "if the agent exists and the information is not the same it updates to this new information" do
66
+ expect(subject).to receive(:correct_timestamp?).and_return(true)
67
+ expect(agent).to receive(:same_endpoints?).with(agent_updated).and_return(true)
68
+ expect(agent).to receive(:same_api?).with(agent_updated).and_return(true)
69
+ expect(agent).to receive(:same_timestamp?).with(agent_updated).and_return(true)
70
+ expect(list).to_not receive(:update).with(agent_uuid, agent_updated)
71
+ subject.update(agent_updated)
72
+ end
73
+ end
74
+
75
+
76
+ context "agent updated with nil information" do
77
+ context "nil endpoints" do
78
+ let(:agent) {instance_double(RemoteAgent, uuid: agent_uuid, timestamp: 1, endpoints: ["something"], rest_api: nil)}
79
+ let(:agent_updated) {instance_double(RemoteAgent, uuid: agent_uuid, timestamp: 2, endpoints: [], rest_api: nil)}
80
+
81
+ it "only overwrite the entry with the new information" do
82
+ expect(subject).to receive(:correct_timestamp?).and_return(true)
83
+ expect(agent).to receive(:same_endpoints?).with(agent_updated).and_return(false)
84
+ expect(agent_updated).to receive(:endpoints=).with(agent.endpoints)
85
+ expect(list).to receive(:update).with(agent_uuid, agent_updated)
86
+ subject.update(agent_updated)
87
+ end
88
+ end
89
+
90
+ context "nil api" do
91
+ let(:agent) {instance_double(RemoteAgent, uuid: agent_uuid, timestamp: 1, endpoints: ["something"], rest_api: double("rest_api"))}
92
+ let(:agent_updated) {instance_double(RemoteAgent, uuid: agent_uuid, timestamp: 2, endpoints: ["something"], rest_api: nil)}
93
+
94
+ it "only overwrite the entry with the new information" do
95
+ expect(subject).to receive(:correct_timestamp?).and_return(true)
96
+ expect(agent).to receive(:same_endpoints?).with(agent_updated).and_return(true)
97
+ expect(agent).to receive(:same_api?).and_return(false)
98
+ expect(agent_updated).to receive(:rest_api=).with(agent.rest_api)
99
+ expect(list).to receive(:update).with(agent_uuid, agent_updated)
100
+ subject.update(agent_updated)
101
+ end
102
+ end
103
+
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe "#RubyNos::Endpoint" do
4
+ subject{Endpoint.new}
5
+
6
+ describe "type=" do
7
+ it "can does not allow types that are not on the allowed types list" do
8
+ expect{subject.type = "SOMETHING"}.to raise_error(ArgumentError)
9
+ end
10
+
11
+ it "allows to set a type that is on the list" do
12
+ subject.type = "PUBLIC"
13
+ expect(subject.type).to eq("PUBLIC")
14
+ end
15
+ end
16
+
17
+ describe "#to_hash" do
18
+ it "returns the attributes of the endpoint in a hash" do
19
+ subject.path = "/example_path"
20
+ expect(subject.to_hash.keys).to eq([:pa, :po, :st, :ty, :xp, :ho])
21
+ expect(subject.to_hash[:pa]).to eq("/example_path")
22
+ end
23
+ end
24
+
25
+ describe "#aliasing" do
26
+ let(:endpoint_hash) {{pa: "/api/", po: 1234, st: 0, ty: "PUB", xp: 0, ho: "localhost"}}
27
+
28
+ it "allows to instantiate an endpoint object attributes with the correct values" do
29
+ endpoint = Endpoint.new(endpoint_hash)
30
+ expect(endpoint.port).to eq 1234
31
+ expect(endpoint.path).to eq "/api/"
32
+ expect(endpoint.sticky).to eq 0
33
+ expect(endpoint.type).to eq "PUB"
34
+ expect(endpoint.priority).to eq 0
35
+ expect(endpoint.host).to eq "localhost"
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ require "spec_helper"
2
+
3
+ describe "#RubyNos::Formatter" do
4
+ subject{Formatter.new}
5
+ let(:uuid) {SecureRandom.uuid}
6
+
7
+ describe "convert_to_uuid" do
8
+ let(:string_uuid){uuid.gsub("-", "")}
9
+ it "converts an string into uuid format" do
10
+ expect(subject.convert_to_uuid(string_uuid)).to eq uuid
11
+ end
12
+ end
13
+
14
+ describe "#uuid_format?" do
15
+ it "returns true if the parameter match the uuid format" do
16
+ expect(subject.uuid_format?(uuid)).to eq(true)
17
+ end
18
+ end
19
+
20
+ describe "#uuid_to_string" do
21
+ it "converts an uuid to string" do
22
+ result = subject.uuid_to_string(uuid)
23
+ expect(result.include?("-")).to eq(false)
24
+ expect(uuid.split("").count - result.split("").count).to eq 4
25
+ end
26
+ end
27
+
28
+ describe "#parse_message" do
29
+ let(:message) {{:a => "something"}}
30
+ it "returns the message as a hash format if it was a hash " do
31
+ expect(subject.parse_message(message.to_json)).to eq(message)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ require "spec_helper"
2
+
3
+ describe RubyNos::List do
4
+ subject{List.new}
5
+ let(:element) {double("element", uuid: "12345")}
6
+
7
+ before do
8
+ subject.add(element)
9
+ end
10
+
11
+ describe "#add" do
12
+ it "adds an element to the list" do
13
+ expect(subject.list.count).to eq(1)
14
+ expect(subject.list.first).to eq({element.uuid => element})
15
+ end
16
+ end
17
+
18
+ describe "#update" do
19
+ let(:element_two) {double("another_element", uuid: "12345", some_other_thing: "something")}
20
+
21
+ it "updates an element on the list that has the same uuid" do
22
+ subject.update(element.uuid, element_two)
23
+ expect(subject.list.count).to eq 1
24
+ expect(subject.list.first[element_two.uuid].some_other_thing).to eq "something"
25
+ end
26
+ end
27
+
28
+ describe "#eliminate" do
29
+ it "eliminates an element from the list" do
30
+ subject.eliminate(element.uuid)
31
+ expect(subject.list.count).to eq 0
32
+ end
33
+ end
34
+
35
+ describe "#info_for" do
36
+ it "returns the information on the list for an element" do
37
+ info = subject.info_for(element.uuid)
38
+ expect(info.uuid).to eq("12345")
39
+ end
40
+ end
41
+
42
+ describe "#list_of_keys" do
43
+ it "returns the list of keys in the list" do
44
+ expect(subject.list_of_keys).to eq(["12345"])
45
+ end
46
+ end
47
+
48
+ describe "#is_on_the_list?" do
49
+ it "returns true if the element exists and folder if it does not exist" do
50
+ expect(subject.is_on_the_list?(element.uuid)).to eq(true)
51
+ expect(subject.is_on_the_list?("some_uuid")).to eq(false)
52
+ end
53
+ end
54
+ end