cirrocumulus 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|