cirrocumulus 0.1.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.
- data/README.rdoc +24 -0
- data/Rakefile +52 -0
- data/lib/cirrocumulus.rb +0 -0
- data/lib/cirrocumulus/agent.rb +182 -0
- data/lib/cirrocumulus/engine.rb +179 -0
- data/lib/cirrocumulus/kb.rb +44 -0
- data/lib/cirrocumulus/logger.rb +4 -0
- data/lib/cirrocumulus/master_agent.rb +197 -0
- data/lib/cirrocumulus/ontology.rb +28 -0
- data/lib/cirrocumulus/saga.rb +53 -0
- metadata +152 -0
data/README.rdoc
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
= Cirrocumulus
|
2
|
+
|
3
|
+
Cirrocumulus is an agent-based (cloud) infrastructure management system. Each agent, running on a host, is responsible for its own problem and cooperates with the rest via XMPP as a transport and FIPA-ACL messages as a protocol.
|
4
|
+
|
5
|
+
== Technical
|
6
|
+
=== Message structure
|
7
|
+
|
8
|
+
Each message is serialized in XML.
|
9
|
+
It contains routing information, action type and content. Content always is an s-expression.
|
10
|
+
|
11
|
+
Message structure looks like:
|
12
|
+
<fipa-message
|
13
|
+
receiver="optional receiving agent identifier"
|
14
|
+
ontology="domain, e.g. cirrocumulus-vps, cirrocumulus-xen"
|
15
|
+
act="request|query-ref|query-if|inform|failure">
|
16
|
+
<content>
|
17
|
+
(s-expression)
|
18
|
+
</content>
|
19
|
+
</fipa-message>
|
20
|
+
|
21
|
+
=== Action types
|
22
|
+
* request - asks receiver to perform some action, described in content
|
23
|
+
* query-ref - query receiver for some information
|
24
|
+
* query-if - asks receiver, if given proposition in content is true or false
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
#
|
2
|
+
# To change this template, choose Tools | Templates
|
3
|
+
# and open the template in the editor.
|
4
|
+
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rake'
|
8
|
+
require 'rake/clean'
|
9
|
+
require 'rake/gempackagetask'
|
10
|
+
require 'rake/rdoctask'
|
11
|
+
require 'rake/testtask'
|
12
|
+
|
13
|
+
spec = Gem::Specification.new do |s|
|
14
|
+
s.name = 'cirrocumulus'
|
15
|
+
s.homepage = 'https://github.com/deil/cirrocumulus'
|
16
|
+
s.version = '0.1.1'
|
17
|
+
s.has_rdoc = false
|
18
|
+
s.extra_rdoc_files = ['README.rdoc']
|
19
|
+
s.summary = 'Agent-based infrastructure management system'
|
20
|
+
s.description = 'Engine for agent-based infrastructure management system'
|
21
|
+
s.author = 'Anton Kosyakin'
|
22
|
+
s.email = 'deil@mneko.net'
|
23
|
+
# s.executables = ['your_executable_here']
|
24
|
+
s.files = %w(README.rdoc Rakefile) + Dir.glob("{bin,lib,spec}/**/*")
|
25
|
+
s.require_path = "lib"
|
26
|
+
s.bindir = "bin"
|
27
|
+
s.license = ['GPL-2']
|
28
|
+
s.add_dependency("activesupport", "~> 2.3.11")
|
29
|
+
s.add_dependency("log4r", "~> 1.1.9")
|
30
|
+
s.add_dependency("systemu")
|
31
|
+
s.add_dependency("xmpp4r", "~> 0.5")
|
32
|
+
s.add_dependency("xmpp4r-simple", "~> 0.8.8")
|
33
|
+
end
|
34
|
+
|
35
|
+
Rake::GemPackageTask.new(spec) do |p|
|
36
|
+
p.gem_spec = spec
|
37
|
+
p.need_tar = true
|
38
|
+
p.need_zip = true
|
39
|
+
end
|
40
|
+
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
files =['README.rdoc', 'lib/**/*.rb']
|
43
|
+
rdoc.rdoc_files.add(files)
|
44
|
+
rdoc.main = "README" # page to start on
|
45
|
+
rdoc.title = "cirrocumulus Docs"
|
46
|
+
rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
|
47
|
+
rdoc.options << '--line-numbers'
|
48
|
+
end
|
49
|
+
|
50
|
+
#Rake::TestTask.new do |t|
|
51
|
+
# t.test_files = FileList['test/**/*.rb']
|
52
|
+
#end
|
data/lib/cirrocumulus.rb
ADDED
File without changes
|
@@ -0,0 +1,182 @@
|
|
1
|
+
class Agent
|
2
|
+
class AgentInfo
|
3
|
+
attr_accessor :identifier
|
4
|
+
attr_accessor :default_ontology
|
5
|
+
attr_accessor :last_seen_at
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@identifier = @default_ontology = @last_seen_at = nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class NetworkMap
|
13
|
+
|
14
|
+
INVALIDATE_PERIOD = 600*2
|
15
|
+
|
16
|
+
attr_reader :agents
|
17
|
+
attr_accessor :version
|
18
|
+
attr_reader :valid
|
19
|
+
|
20
|
+
def initialize(agent)
|
21
|
+
Log4r::Logger['agent'].debug "initializing empty network map"
|
22
|
+
@agent = agent
|
23
|
+
@agents = []
|
24
|
+
@version = 0
|
25
|
+
@valid = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def tick(cm)
|
29
|
+
@valid -= 1
|
30
|
+
if @valid <= 0
|
31
|
+
Log4r::Logger['agent'].debug "invalidating network map"
|
32
|
+
msg = Cirrocumulus::Message.new(nil, 'request', [:update, [:map, @version.to_s]])
|
33
|
+
msg.ontology = 'cirrocumulus-map'
|
34
|
+
cm.send(msg)
|
35
|
+
@valid = INVALIDATE_PERIOD
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def handle_message(message, cm)
|
40
|
+
if message.ontology == 'cirrocumulus-map'
|
41
|
+
return if !message.receiver.blank? && (message.receiver != @agent.identifier)
|
42
|
+
#p message
|
43
|
+
|
44
|
+
if message.act == 'inform'
|
45
|
+
if message.content.first == :"="
|
46
|
+
ontology = message.content[2].first
|
47
|
+
agent = @agents.find {|a| a.identifier == message.sender}
|
48
|
+
if agent
|
49
|
+
agent.default_ontology = ontology
|
50
|
+
agent.last_seen_at = Time.now.to_i
|
51
|
+
@version = Time.now.to_i
|
52
|
+
Log4r::Logger['agent'].info "got neighbour details: #{agent.inspect}"
|
53
|
+
end
|
54
|
+
else
|
55
|
+
agent_info = message.content
|
56
|
+
agent = AgentInfo.new
|
57
|
+
agent_info.each do |param|
|
58
|
+
if param.first == :identifier
|
59
|
+
agent.identifier = param.second
|
60
|
+
elsif param.first == :default_ontology
|
61
|
+
agent.default_ontology = param.second
|
62
|
+
elsif param.first == :last_seen_at
|
63
|
+
agent.last_seen_at = param.second.to_i
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
my_agent = @agents.find {|a| a.identifier == agent.identifier}
|
68
|
+
if my_agent.nil?
|
69
|
+
my_agent = agent
|
70
|
+
@agents << agent
|
71
|
+
else
|
72
|
+
my_agent.last_seen_at = [my_agent.last_seen_at, agent.last_seen_at].max
|
73
|
+
my_agent.default_ontology = agent.default_ontology || my_agent.default_ontology
|
74
|
+
end
|
75
|
+
|
76
|
+
Log4r::Logger['agent'].info "neighbour updated: #{my_agent.inspect}"
|
77
|
+
@version = Time.now.to_i
|
78
|
+
end
|
79
|
+
elsif message.act == 'request'
|
80
|
+
map_request = message.content
|
81
|
+
foreign_map = map_request.second
|
82
|
+
foreign_map_version = foreign_map.second.to_i
|
83
|
+
if @version >= foreign_map_version
|
84
|
+
@agents.each do |agent|
|
85
|
+
next if agent.default_ontology.blank?
|
86
|
+
msg = Cirrocumulus::Message.new(nil, 'inform', [
|
87
|
+
[:identifier, agent.identifier],
|
88
|
+
[:default_ontology, agent.default_ontology],
|
89
|
+
[:last_seen_at, agent.last_seen_at.to_s]
|
90
|
+
])
|
91
|
+
msg.receiver = message.sender
|
92
|
+
msg.ontology = 'cirrocumulus-map'
|
93
|
+
cm.send(msg)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
process_agent(message, cm)
|
98
|
+
elsif message.act == 'query-ref'
|
99
|
+
#p message.content
|
100
|
+
if message.content.first == :default_ontology
|
101
|
+
msg = Cirrocumulus::Message.new(nil, 'inform', [:'=', [:default_ontology], [@agent.default_ontology]])
|
102
|
+
msg.receiver = message.sender
|
103
|
+
msg.ontology = 'cirrocumulus-map'
|
104
|
+
cm.send(msg)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
else
|
108
|
+
process_agent(message, cm)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def process_agent(message, cm)
|
115
|
+
agent_id = message.sender
|
116
|
+
return if agent_id == @agent.identifier
|
117
|
+
|
118
|
+
agent = @agents.find {|a| a.identifier == agent_id}
|
119
|
+
if agent
|
120
|
+
agent.last_seen_at = Time.now.to_i
|
121
|
+
@version = Time.now.to_i
|
122
|
+
else
|
123
|
+
agent = AgentInfo.new
|
124
|
+
agent.identifier = agent_id
|
125
|
+
agent.last_seen_at = Time.now.to_i
|
126
|
+
Log4r::Logger['agent'].info "discovered neighbour: #{agent.identifier}"
|
127
|
+
@agents << agent
|
128
|
+
@version = Time.now.to_i
|
129
|
+
|
130
|
+
msg = Cirrocumulus::Message.new(nil, 'query-ref', [:default_ontology])
|
131
|
+
msg.receiver = agent.identifier
|
132
|
+
msg.ontology = 'cirrocumulus-map'
|
133
|
+
cm.send(msg)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
attr_reader :default_ontology
|
139
|
+
attr_reader :identifier
|
140
|
+
attr_reader :network_map
|
141
|
+
|
142
|
+
def initialize(cm)
|
143
|
+
@cm = cm
|
144
|
+
@identifier = cm.jid
|
145
|
+
@sagas = []
|
146
|
+
@saga_idx = 0
|
147
|
+
@network_map = NetworkMap.new(self)
|
148
|
+
end
|
149
|
+
|
150
|
+
def tick()
|
151
|
+
@network_map.tick(@cm)
|
152
|
+
|
153
|
+
@sagas.each do |saga|
|
154
|
+
next if saga.is_finished?
|
155
|
+
saga.timeout -= 1 if saga.timeout > 0
|
156
|
+
saga.handle(nil) if saga.timeout == 0
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def restore_state()
|
161
|
+
end
|
162
|
+
|
163
|
+
def handles_ontology?(ontology)
|
164
|
+
return @default_ontology == ontology || ontology == 'cirrocumulus-map'
|
165
|
+
end
|
166
|
+
|
167
|
+
def handle_message(message, kb)
|
168
|
+
@network_map.handle_message(message, @cm)
|
169
|
+
|
170
|
+
if message.ontology != 'cirrocumulus-map'
|
171
|
+
handle(message, kb)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.get_node(sender)
|
176
|
+
sender.split('-').first
|
177
|
+
end
|
178
|
+
|
179
|
+
def self.get_subsystem(sender)
|
180
|
+
sender.split('-').second
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,179 @@
|
|
1
|
+
require 'xmpp4r'
|
2
|
+
require 'xmpp4r-simple'
|
3
|
+
require 'sexpistol'
|
4
|
+
require 'systemu'
|
5
|
+
require 'activesupport'
|
6
|
+
|
7
|
+
class Cirrocumulus
|
8
|
+
class Message
|
9
|
+
attr_accessor :sender, :act, :content
|
10
|
+
attr_accessor :receiver, :reply_with, :in_reply_to
|
11
|
+
attr_accessor :ontology
|
12
|
+
|
13
|
+
def initialize(sender, act, content)
|
14
|
+
@sender = sender
|
15
|
+
@act = act
|
16
|
+
@content = content
|
17
|
+
end
|
18
|
+
|
19
|
+
def failed?
|
20
|
+
act == 'failure' || act == 'refuse'
|
21
|
+
end
|
22
|
+
|
23
|
+
def context
|
24
|
+
Context.new(@sender, @reply_with)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class Context
|
29
|
+
attr_reader :sender
|
30
|
+
attr_reader :reply_with
|
31
|
+
|
32
|
+
def initialize(sender, reply_with)
|
33
|
+
@sender = sender
|
34
|
+
@reply_with = reply_with
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.platform
|
39
|
+
if PLATFORM =~ /freebsd/
|
40
|
+
return 'freebsd'
|
41
|
+
elsif PLATFORM =~ /linux/
|
42
|
+
return 'linux'
|
43
|
+
end
|
44
|
+
|
45
|
+
return 'unknown'
|
46
|
+
end
|
47
|
+
|
48
|
+
attr_reader :jid
|
49
|
+
|
50
|
+
def initialize(suffix, generate_jid = true)
|
51
|
+
Log4r::Logger['cirrocumulus'].info 'platform: ' + Cirrocumulus::platform
|
52
|
+
_, hostname = systemu 'hostname'
|
53
|
+
hostname.strip!
|
54
|
+
@jid = generate_jid ? "#{hostname}-#{suffix}" : suffix
|
55
|
+
Log4r::Logger['cirrocumulus'].info "logging as " + @jid
|
56
|
+
connect()
|
57
|
+
end
|
58
|
+
|
59
|
+
def send(message)
|
60
|
+
msg = "<fipa-message ontology=\"#{message.ontology}\""
|
61
|
+
msg += " receiver=\"#{message.receiver}\"" if message.receiver
|
62
|
+
msg += " act=\"#{message.act}\""
|
63
|
+
msg += " reply-with=\"#{message.reply_with}\"" if message.reply_with
|
64
|
+
msg += " in-reply-to=\"#{message.in_reply_to}\"" if message.in_reply_to
|
65
|
+
message_content = message.content if message.content.is_a? String
|
66
|
+
message_content = Sexpistol.new.to_sexp(message.content) if message.content.is_a? Array
|
67
|
+
msg += "><content>#{message_content}</content></fipa-message>"
|
68
|
+
@im.send!("<message type=\"groupchat\" to=\"#{JABBER_CONFERENCE}\" id=\"aaefa\"><body>#{msg.gsub('&', '&').gsub('<', '<').gsub('>', '>')}</body></message>")
|
69
|
+
end
|
70
|
+
|
71
|
+
def inform(receiver, answer, ontology = nil)
|
72
|
+
msg = "<fipa-message ontology=\"#{ontology || @ontology}\" receiver=\"#{receiver}\" act=\"inform\"><content>#{answer}</content></fipa-message>"
|
73
|
+
@im.send!("<message type=\"groupchat\" to=\"#{JABBER_CONFERENCE}\" id=\"aaefa\"><body>#{msg.gsub('&', '&').gsub('<', '<').gsub('>', '>')}</body></message>")
|
74
|
+
end
|
75
|
+
|
76
|
+
def refuse(receiver, action, reason, ontology = nil)
|
77
|
+
msg = "<fipa-message ontology=\"#{ontology || @ontology}\" receiver=\"#{receiver}\" act=\"refuse\"><content>(#{action} #{reason})</content></fipa-message>"
|
78
|
+
@im.send!("<message type=\"groupchat\" to=\"#{JABBER_CONFERENCE}\" id=\"aaefa\"><body>#{msg.gsub('&', '&').gsub('<', '<').gsub('>', '>')}</body></message>")
|
79
|
+
end
|
80
|
+
|
81
|
+
def failure(receiver, action, ontology = nil)
|
82
|
+
msg = "<fipa-message ontology=\"#{ontology || @ontology}\" receiver=\"#{receiver}\" act=\"failure\"><content>#{action}</content></fipa-message>"
|
83
|
+
@im.send!("<message type=\"groupchat\" to=\"#{JABBER_CONFERENCE}\" id=\"aaefa\"><body>#{msg.gsub('&', '&').gsub('<', '<').gsub('>', '>')}</body></message>")
|
84
|
+
end
|
85
|
+
|
86
|
+
def request(receiver, action, ontology = nil)
|
87
|
+
msg = "<fipa-message ontology=\"#{ontology || @ontology}\" receiver=\"#{receiver}\" act=\"request\"><content>#{action}</content></fipa-message>"
|
88
|
+
@im.send!("<message type=\"groupchat\" to=\"#{JABBER_CONFERENCE}\" id=\"aaefa\"><body>#{msg.gsub('&', '&').gsub('<', '<').gsub('>', '>')}</body></message>")
|
89
|
+
end
|
90
|
+
|
91
|
+
def run(agent, kb, sniff = false)
|
92
|
+
Log4r::Logger['cirrocumulus'].info("entering main loop")
|
93
|
+
agent.restore_state()
|
94
|
+
|
95
|
+
s = Sexpistol.new
|
96
|
+
|
97
|
+
loop do
|
98
|
+
kb.collect_knowledge()
|
99
|
+
|
100
|
+
begin
|
101
|
+
connect() if @im.nil? || !@im.connected?
|
102
|
+
rescue Exception => e
|
103
|
+
puts e.to_s
|
104
|
+
end
|
105
|
+
|
106
|
+
next if @im.nil? || !@im.connected?
|
107
|
+
|
108
|
+
@im.received_messages do |message|
|
109
|
+
next if !message.x('jabber:x:delay').nil?
|
110
|
+
|
111
|
+
begin
|
112
|
+
xml = Hash.from_xml(message.body)['fipa_message']
|
113
|
+
|
114
|
+
ontology = xml['ontology']
|
115
|
+
sender = message.from.resource
|
116
|
+
receiver = xml['receiver']
|
117
|
+
supported_ontology = agent.handles_ontology?(ontology)
|
118
|
+
if (supported_ontology && (receiver == @jid || receiver.blank?)) || receiver == @jid || sniff
|
119
|
+
act = xml['act']
|
120
|
+
content_raw = xml['content']
|
121
|
+
content = s.parse_string(content_raw)
|
122
|
+
msg = Cirrocumulus::Message.new(sender, act, content)
|
123
|
+
msg.receiver = receiver
|
124
|
+
msg.reply_with = xml['reply_with']
|
125
|
+
msg.in_reply_to = xml['in_reply_to']
|
126
|
+
msg.ontology = ontology
|
127
|
+
flatten_message_content(msg)
|
128
|
+
agent.handle_message(msg, kb)
|
129
|
+
end
|
130
|
+
rescue Exception => e
|
131
|
+
puts e.to_s
|
132
|
+
puts e.backtrace
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
agent.tick()
|
137
|
+
sleep 0.5
|
138
|
+
end
|
139
|
+
|
140
|
+
@im.disconnect
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def connect()
|
146
|
+
begin
|
147
|
+
@im.disconnect if @im && @im.connected?
|
148
|
+
full_jid = @jid + "@" + JABBER_SERVER
|
149
|
+
@im = Jabber::Simple.new(full_jid, JABBER_DEFAULT_PASSWORD)
|
150
|
+
rescue Jabber::ClientAuthenticationFailure => ex
|
151
|
+
Log4r::Logger['cirrocumulus'].warn ex.to_s
|
152
|
+
Log4r::Logger['cirrocumulus'].info "registering new account with default password"
|
153
|
+
|
154
|
+
client = Jabber::Client.new(full_jid)
|
155
|
+
client.connect()
|
156
|
+
client.register(JABBER_DEFAULT_PASSWORD) #, {'username' => full_jid, 'password' => JABBER_DEFAULT_PASSWORD})
|
157
|
+
client.close()
|
158
|
+
@im = Jabber::Simple.new(full_jid, JABBER_DEFAULT_PASSWORD)
|
159
|
+
rescue Exception => ex
|
160
|
+
puts ex
|
161
|
+
#puts ex.backtrace.to_s
|
162
|
+
end
|
163
|
+
|
164
|
+
if !@im.nil? && @im.connected?
|
165
|
+
Log4r::Logger['cirrocumulus'].info 'joining ' + JABBER_CONFERENCE
|
166
|
+
@im.send!("<presence to='#{JABBER_CONFERENCE}/#{@jid}' />")
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def flatten_message_content(message)
|
171
|
+
if !message.content.is_a?(Array)
|
172
|
+
message.content = [message.content]
|
173
|
+
else
|
174
|
+
while message.content.is_a?(Array) && message.content.size == 1 && message.content.first.is_a?(Array)
|
175
|
+
message.content = message.content.first
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Kb
|
2
|
+
def initialize
|
3
|
+
@knowledge = []
|
4
|
+
end
|
5
|
+
|
6
|
+
def add_fact(key, value)
|
7
|
+
s = Sexpistol.new
|
8
|
+
key_str = s.to_sexp(key)
|
9
|
+
#puts "add: #{key_str}"
|
10
|
+
@knowledge << {:key => key_str, :value => value}
|
11
|
+
end
|
12
|
+
|
13
|
+
def query_fact(key)
|
14
|
+
s = Sexpistol.new
|
15
|
+
key_str = s.to_sexp(key)
|
16
|
+
|
17
|
+
#puts "query: #{key}"
|
18
|
+
@knowledge.each do |h|
|
19
|
+
if h[:key] == key_str
|
20
|
+
#puts "found: #{h[:value]}"
|
21
|
+
return h[:value]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def remove_fact(key)
|
29
|
+
#puts "remove fact: #{key}"
|
30
|
+
@knowledge.delete(key)
|
31
|
+
end
|
32
|
+
|
33
|
+
def keys
|
34
|
+
res = []
|
35
|
+
@knowledge.each do |h|
|
36
|
+
res << h[:key]
|
37
|
+
end
|
38
|
+
|
39
|
+
res
|
40
|
+
end
|
41
|
+
|
42
|
+
def collect_knowledge()
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
module Agent
|
2
|
+
class AgentInfo
|
3
|
+
attr_accessor :identifier
|
4
|
+
attr_accessor :default_ontology
|
5
|
+
attr_accessor :last_seen_at
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@identifier = @default_ontology = @last_seen_at = nil
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class NetworkMap
|
13
|
+
|
14
|
+
INVALIDATE_PERIOD = 600*2
|
15
|
+
|
16
|
+
attr_reader :agents
|
17
|
+
attr_accessor :version
|
18
|
+
attr_reader :valid
|
19
|
+
|
20
|
+
def initialize(agent)
|
21
|
+
Log4r::Logger['agent'].debug "initializing empty network map"
|
22
|
+
@agent = agent
|
23
|
+
@agents = []
|
24
|
+
@version = 0
|
25
|
+
@valid = 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def tick(cm)
|
29
|
+
@valid -= 1
|
30
|
+
if @valid <= 0
|
31
|
+
Log4r::Logger['agent'].debug "invalidating network map"
|
32
|
+
msg = Cirrocumulus::Message.new(nil, 'request', [:update, [:map, @version.to_s]])
|
33
|
+
msg.ontology = 'cirrocumulus-map'
|
34
|
+
cm.send(msg)
|
35
|
+
@valid = INVALIDATE_PERIOD
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def handle_message(message, cm)
|
40
|
+
if message.ontology == 'cirrocumulus-map'
|
41
|
+
return if !message.receiver.blank? && (message.receiver != @agent.identifier)
|
42
|
+
#p message
|
43
|
+
|
44
|
+
if message.act == 'inform'
|
45
|
+
if message.content.first == :"="
|
46
|
+
ontology = message.content[2].first
|
47
|
+
agent = @agents.find {|a| a.identifier == message.sender}
|
48
|
+
if agent
|
49
|
+
agent.default_ontology = ontology
|
50
|
+
agent.last_seen_at = Time.now.to_i
|
51
|
+
@version = Time.now.to_i
|
52
|
+
Log4r::Logger['agent'].info "got neighbour details: #{agent.inspect}"
|
53
|
+
end
|
54
|
+
else
|
55
|
+
agent_info = message.content
|
56
|
+
agent = AgentInfo.new
|
57
|
+
agent_info.each do |param|
|
58
|
+
if param.first == :identifier
|
59
|
+
agent.identifier = param.second
|
60
|
+
elsif param.first == :default_ontology
|
61
|
+
agent.default_ontology = param.second
|
62
|
+
elsif param.first == :last_seen_at
|
63
|
+
agent.last_seen_at = param.second.to_i
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
my_agent = @agents.find {|a| a.identifier == agent.identifier}
|
68
|
+
if my_agent.nil?
|
69
|
+
my_agent = agent
|
70
|
+
@agents << agent
|
71
|
+
else
|
72
|
+
my_agent.last_seen_at = [my_agent.last_seen_at, agent.last_seen_at].max
|
73
|
+
my_agent.default_ontology = agent.default_ontology || my_agent.default_ontology
|
74
|
+
end
|
75
|
+
|
76
|
+
Log4r::Logger['agent'].info "neighbour updated: #{my_agent.inspect}"
|
77
|
+
@version = Time.now.to_i
|
78
|
+
end
|
79
|
+
elsif message.act == 'request'
|
80
|
+
map_request = message.content
|
81
|
+
foreign_map = map_request.second
|
82
|
+
foreign_map_version = foreign_map.second.to_i
|
83
|
+
if @version >= foreign_map_version
|
84
|
+
@agents.each do |agent|
|
85
|
+
next if agent.default_ontology.blank?
|
86
|
+
msg = Cirrocumulus::Message.new(nil, 'inform', [
|
87
|
+
[:identifier, agent.identifier],
|
88
|
+
[:default_ontology, agent.default_ontology],
|
89
|
+
[:last_seen_at, agent.last_seen_at.to_s]
|
90
|
+
])
|
91
|
+
msg.receiver = message.sender
|
92
|
+
msg.ontology = 'cirrocumulus-map'
|
93
|
+
cm.send(msg)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
process_agent(message, cm)
|
98
|
+
elsif message.act == 'query-ref'
|
99
|
+
#p message.content
|
100
|
+
if message.content.first == :default_ontology
|
101
|
+
msg = Cirrocumulus::Message.new(nil, 'inform', [:'=', [:default_ontology], [@agent.default_ontology]])
|
102
|
+
msg.receiver = message.sender
|
103
|
+
msg.ontology = 'cirrocumulus-map'
|
104
|
+
cm.send(msg)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
else
|
108
|
+
process_agent(message, cm)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def process_agent(message, cm)
|
115
|
+
agent_id = message.sender
|
116
|
+
return if agent_id == @agent.identifier
|
117
|
+
|
118
|
+
agent = @agents.find {|a| a.identifier == agent_id}
|
119
|
+
if agent
|
120
|
+
agent.last_seen_at = Time.now.to_i
|
121
|
+
@version = Time.now.to_i
|
122
|
+
else
|
123
|
+
agent = AgentInfo.new
|
124
|
+
agent.identifier = agent_id
|
125
|
+
agent.last_seen_at = Time.now.to_i
|
126
|
+
Log4r::Logger['agent'].info "discovered neighbour: #{agent.identifier}"
|
127
|
+
@agents << agent
|
128
|
+
@version = Time.now.to_i
|
129
|
+
|
130
|
+
msg = Cirrocumulus::Message.new(nil, 'query-ref', [:default_ontology])
|
131
|
+
msg.receiver = agent.identifier
|
132
|
+
msg.ontology = 'cirrocumulus-map'
|
133
|
+
cm.send(msg)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class Base
|
139
|
+
attr_reader :identifier
|
140
|
+
attr_reader :network_map
|
141
|
+
|
142
|
+
def initialize(cm)
|
143
|
+
Log4r::Logger['agent'].info('Initializing new agent')
|
144
|
+
|
145
|
+
@cm = cm
|
146
|
+
@identifier = cm.jid
|
147
|
+
@ontologies = []
|
148
|
+
@network_map = NetworkMap.new(self)
|
149
|
+
end
|
150
|
+
|
151
|
+
def load_ontologies(ontologies_list)
|
152
|
+
ontologies_list.each do |ontology_name|
|
153
|
+
ontology = eval("#{ontology_name}.new(self)")
|
154
|
+
self.ontologies << ontology
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def handles_ontology?(ontology_name)
|
159
|
+
self.ontologies.any? {|ontology| ontology.name == ontology_name}
|
160
|
+
end
|
161
|
+
|
162
|
+
def restore_state()
|
163
|
+
self.ontologies.each do |ontology|
|
164
|
+
begin
|
165
|
+
ontology.restore_state()
|
166
|
+
rescue Exception => e
|
167
|
+
Log4r::Logger['agent'].warn "failed to restore state for ontology %s" % ontology.name
|
168
|
+
Log4r::Logger['agent'].warn e.backtrace.to_s
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def send_message(message)
|
174
|
+
@cm.send(message)
|
175
|
+
end
|
176
|
+
|
177
|
+
def tick()
|
178
|
+
@network_map.tick(@cm)
|
179
|
+
|
180
|
+
self.ontologies.each {|ontology| ontology.tick() }
|
181
|
+
end
|
182
|
+
|
183
|
+
def handle_message(message, kb)
|
184
|
+
@network_map.handle_message(message, @cm)
|
185
|
+
self.ontologies.each {|ontology| ontology.handle_message(message, kb) if message.ontology == ontology.name}
|
186
|
+
rescue Exception => e
|
187
|
+
Log4r::Logger['agent'].warn "failed to handle incoming message: %s" % e.to_s
|
188
|
+
puts e.backtrace.to_s
|
189
|
+
end
|
190
|
+
|
191
|
+
protected
|
192
|
+
|
193
|
+
attr_reader :cm
|
194
|
+
attr_reader :ontologies
|
195
|
+
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ontology
|
2
|
+
class Base
|
3
|
+
attr_reader :name
|
4
|
+
attr_reader :agent
|
5
|
+
|
6
|
+
def initialize(name, agent)
|
7
|
+
@name = name
|
8
|
+
@agent = agent
|
9
|
+
end
|
10
|
+
|
11
|
+
def restore_state()
|
12
|
+
puts "call to dummy Ontology::Base.restore_state()"
|
13
|
+
end
|
14
|
+
|
15
|
+
def tick()
|
16
|
+
end
|
17
|
+
|
18
|
+
def handle_message(message, kb)
|
19
|
+
puts "call to dummy Ontology::Base.handle_message()"
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def logger
|
25
|
+
Log4r::Logger['agent']
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class Saga
|
2
|
+
attr_reader :id
|
3
|
+
attr_reader :context
|
4
|
+
attr_reader :finished
|
5
|
+
attr_accessor :timeout
|
6
|
+
|
7
|
+
DEFAULT_TIMEOUT = 15
|
8
|
+
LONG_TIMEOUT = 60
|
9
|
+
|
10
|
+
STATE_ERROR = -1
|
11
|
+
STATE_START = 0
|
12
|
+
STATE_FINISHED = 255
|
13
|
+
|
14
|
+
def initialize(id, cm, agent)
|
15
|
+
@agent = agent
|
16
|
+
@cm = cm
|
17
|
+
@id = id
|
18
|
+
@finished = false
|
19
|
+
@timeout = -1
|
20
|
+
@state = STATE_START
|
21
|
+
end
|
22
|
+
|
23
|
+
def is_finished?
|
24
|
+
@finished || @state == STATE_ERROR
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def clear_timeout()
|
30
|
+
@timeout = -1
|
31
|
+
end
|
32
|
+
|
33
|
+
def set_timeout(secs)
|
34
|
+
Log4r::Logger['agent'].debug "[#{id}] waiting for #{secs} second(s)" if secs > 1
|
35
|
+
@timeout = secs*2
|
36
|
+
end
|
37
|
+
|
38
|
+
def change_state(new_state)
|
39
|
+
Log4r::Logger['agent'].debug "[#{id}] switching state from #{@state} to #{new_state}"
|
40
|
+
@state = new_state
|
41
|
+
end
|
42
|
+
|
43
|
+
def finish()
|
44
|
+
clear_timeout()
|
45
|
+
change_state(STATE_FINISHED)
|
46
|
+
@finished = true
|
47
|
+
Log4r::Logger['agent'].info "[#{id}] finished"
|
48
|
+
end
|
49
|
+
|
50
|
+
def error()
|
51
|
+
change_state(STATE_ERROR)
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cirrocumulus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 25
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Anton Kosyakin
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-09-13 00:00:00 +04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activesupport
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 21
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 3
|
33
|
+
- 11
|
34
|
+
version: 2.3.11
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: log4r
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 1
|
46
|
+
segments:
|
47
|
+
- 1
|
48
|
+
- 1
|
49
|
+
- 9
|
50
|
+
version: 1.1.9
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: systemu
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 3
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
type: :runtime
|
66
|
+
version_requirements: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: xmpp4r
|
69
|
+
prerelease: false
|
70
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
hash: 1
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
- 5
|
79
|
+
version: "0.5"
|
80
|
+
type: :runtime
|
81
|
+
version_requirements: *id004
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: xmpp4r-simple
|
84
|
+
prerelease: false
|
85
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ~>
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 47
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
- 8
|
94
|
+
- 8
|
95
|
+
version: 0.8.8
|
96
|
+
type: :runtime
|
97
|
+
version_requirements: *id005
|
98
|
+
description: Engine for agent-based infrastructure management system
|
99
|
+
email: deil@mneko.net
|
100
|
+
executables: []
|
101
|
+
|
102
|
+
extensions: []
|
103
|
+
|
104
|
+
extra_rdoc_files:
|
105
|
+
- README.rdoc
|
106
|
+
files:
|
107
|
+
- README.rdoc
|
108
|
+
- Rakefile
|
109
|
+
- lib/cirrocumulus.rb
|
110
|
+
- lib/cirrocumulus/agent.rb
|
111
|
+
- lib/cirrocumulus/kb.rb
|
112
|
+
- lib/cirrocumulus/saga.rb
|
113
|
+
- lib/cirrocumulus/logger.rb
|
114
|
+
- lib/cirrocumulus/ontology.rb
|
115
|
+
- lib/cirrocumulus/engine.rb
|
116
|
+
- lib/cirrocumulus/master_agent.rb
|
117
|
+
has_rdoc: true
|
118
|
+
homepage: https://github.com/deil/cirrocumulus
|
119
|
+
licenses:
|
120
|
+
- - GPL-2
|
121
|
+
post_install_message:
|
122
|
+
rdoc_options: []
|
123
|
+
|
124
|
+
require_paths:
|
125
|
+
- lib
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
hash: 3
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
version: "0"
|
135
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
hash: 3
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
version: "0"
|
144
|
+
requirements: []
|
145
|
+
|
146
|
+
rubyforge_project:
|
147
|
+
rubygems_version: 1.3.7
|
148
|
+
signing_key:
|
149
|
+
specification_version: 3
|
150
|
+
summary: Agent-based infrastructure management system
|
151
|
+
test_files: []
|
152
|
+
|