cirrocumulus 0.9.2 → 0.9.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/lib/cirrocumulus/channels.rb +39 -0
- data/lib/cirrocumulus/channels/jabber.rb +25 -6
- data/lib/cirrocumulus/facts.rb +41 -2
- data/lib/cirrocumulus/ontology.rb +89 -10
- data/lib/cirrocumulus/saga.rb +13 -0
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
= Cirrocumulus
|
2
2
|
|
3
|
-
Cirrocumulus is an agent-based infrastructure management
|
3
|
+
Cirrocumulus is an agent-based infrastructure management engine. 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.
|
@@ -23,6 +23,21 @@ class AbstractChannel
|
|
23
23
|
# Query the other side if given proposition is true.
|
24
24
|
#
|
25
25
|
def query_if(sender, proposition, options = {}); end
|
26
|
+
|
27
|
+
#
|
28
|
+
# Agree to perform an action
|
29
|
+
#
|
30
|
+
def agree(sender, action, options = {}); end
|
31
|
+
|
32
|
+
#
|
33
|
+
# Refuse to perform an action, because of reason.
|
34
|
+
#
|
35
|
+
def refuse(sender, action, reason, options = {}); end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Attempted to perform an action, but failed with given reason.
|
39
|
+
#
|
40
|
+
def failure(sender, action, reason, options = {}); end
|
26
41
|
end
|
27
42
|
|
28
43
|
#
|
@@ -48,6 +63,18 @@ class ThreadChannel < AbstractChannel
|
|
48
63
|
def query_if(sender, proposition, options = {})
|
49
64
|
@ontology.handle_query_if(sender, proposition, options)
|
50
65
|
end
|
66
|
+
|
67
|
+
def agree(sender, action, options = {})
|
68
|
+
@ontology.handle_agree(sender, action, options)
|
69
|
+
end
|
70
|
+
|
71
|
+
def refuse(sender, action, reason, options = {})
|
72
|
+
@ontology.handle_refuse(sender, action, reason, options)
|
73
|
+
end
|
74
|
+
|
75
|
+
def failure(sender, action, reason, options = {})
|
76
|
+
@ontology.handle_failure(sender, action, reason, options)
|
77
|
+
end
|
51
78
|
end
|
52
79
|
|
53
80
|
#
|
@@ -76,6 +103,18 @@ class NetworkChannel < AbstractChannel
|
|
76
103
|
client.queue(serializer.to_sexp(build_message(remote_jid, :query_if, proposition, options)))
|
77
104
|
end
|
78
105
|
|
106
|
+
def agree(sender, action, options = {})
|
107
|
+
client.queue(serializer.to_sexp(build_message(remote_jid, :agree, action, options)))
|
108
|
+
end
|
109
|
+
|
110
|
+
def refuse(sender, action, reason, options = {})
|
111
|
+
client.queue(serializer.to_sexp(build_message(remote_jid, :refuse, [action, reason], options)))
|
112
|
+
end
|
113
|
+
|
114
|
+
def failure(sender, action, reason, options = {})
|
115
|
+
client.queue(serializer.to_sexp(build_message(remote_jid, :failure, [action, reason], options)))
|
116
|
+
end
|
117
|
+
|
79
118
|
private
|
80
119
|
|
81
120
|
attr_reader :client
|
@@ -6,7 +6,7 @@ require 'thread'
|
|
6
6
|
class JabberIdentifier < RemoteIdentifier
|
7
7
|
def initialize(jid)
|
8
8
|
@jid = "#{Cirrocumulus::Environment.current.name}-#{jid}"
|
9
|
-
@channel = JabberChannel.new(
|
9
|
+
@channel = JabberChannel.new()
|
10
10
|
@channel.connect(@jid, 'q1w2e3r4')
|
11
11
|
@thrd = Thread.new do
|
12
12
|
parser = Sexpistol.new
|
@@ -58,6 +58,12 @@ class JabberIdentifier < RemoteIdentifier
|
|
58
58
|
instance.handle_inform(id, action_content, options)
|
59
59
|
when :request
|
60
60
|
instance.handle_request(id, action_content, options)
|
61
|
+
when :agree
|
62
|
+
instance.handle_agree(id, action_content, options)
|
63
|
+
when :refuse
|
64
|
+
instance.handle_refuse(id, action_content[0], action_content[1], options)
|
65
|
+
when :failure
|
66
|
+
instance.handle_failure(id, action_content[0], action_content[1], options)
|
61
67
|
end
|
62
68
|
rescue Exception => ex
|
63
69
|
puts ex.message
|
@@ -86,15 +92,27 @@ class JabberChannel
|
|
86
92
|
def query_client(jid)
|
87
93
|
@@jabber_clients.find {|c| c.jid == jid}
|
88
94
|
end
|
95
|
+
|
96
|
+
def server(server)
|
97
|
+
@@server = server
|
98
|
+
end
|
99
|
+
|
100
|
+
def conference(conf)
|
101
|
+
@@conference = conf
|
102
|
+
end
|
103
|
+
|
104
|
+
def jid_suffix(suffix)
|
105
|
+
@@jid_suffix = suffix
|
106
|
+
end
|
89
107
|
end
|
90
108
|
|
91
109
|
attr_reader :jid
|
92
110
|
attr_reader :conference
|
93
111
|
|
94
|
-
def initialize(server, conference)
|
112
|
+
def initialize(server = nil, conference = nil)
|
95
113
|
@jabber = nil
|
96
|
-
@server = server
|
97
|
-
@conference = conference
|
114
|
+
@server = server || @@server
|
115
|
+
@conference = conference || @@conference
|
98
116
|
@send_q = Queue.new
|
99
117
|
@recv_q = Queue.new
|
100
118
|
|
@@ -106,7 +124,7 @@ class JabberChannel
|
|
106
124
|
end
|
107
125
|
|
108
126
|
def connect(jid, password)
|
109
|
-
@full_jid = "%s@%s" % [jid, @server]
|
127
|
+
@full_jid = "%s@%s" % [jid, @@jid_suffix || @server]
|
110
128
|
@jid = jid
|
111
129
|
|
112
130
|
puts "Using jid #{@jid}"
|
@@ -115,6 +133,7 @@ class JabberChannel
|
|
115
133
|
@jabber = Jabber::Simple.new(@full_jid, password)
|
116
134
|
rescue Jabber::ClientAuthenticationFailure => ex
|
117
135
|
puts ex.class.name
|
136
|
+
puts ex.to_s
|
118
137
|
client = Jabber::Client.new(@full_jid)
|
119
138
|
client.connect()
|
120
139
|
client.register(password)
|
@@ -163,6 +182,6 @@ class JabberChannel
|
|
163
182
|
protected
|
164
183
|
|
165
184
|
def join_conference(conference)
|
166
|
-
@jabber.send!("<presence to='#{conference}@conference.#{@server}/#{@jid}' />")
|
185
|
+
@jabber.send!("<presence to='#{conference}@conference.#{@@jid_suffix || @server}/#{@jid}' />")
|
167
186
|
end
|
168
187
|
end
|
data/lib/cirrocumulus/facts.rb
CHANGED
@@ -71,6 +71,20 @@ class KnowledgeClass
|
|
71
71
|
@@classes[self.name].properties << property_name.to_s
|
72
72
|
end
|
73
73
|
|
74
|
+
def to_template
|
75
|
+
description = @@classes[self.name]
|
76
|
+
fact = [description.name.to_sym]
|
77
|
+
fact << description.primary_key.upcase.to_sym
|
78
|
+
description.properties.each do |k|
|
79
|
+
next if k == description.primary_key
|
80
|
+
|
81
|
+
fact << k.to_sym
|
82
|
+
fact << k.upcase.to_sym
|
83
|
+
end
|
84
|
+
|
85
|
+
fact
|
86
|
+
end
|
87
|
+
|
74
88
|
def from_fact(fact)
|
75
89
|
description = @@classes[self.name]
|
76
90
|
return nil if fact[0] != description.name.to_sym
|
@@ -88,8 +102,19 @@ class KnowledgeClass
|
|
88
102
|
|
89
103
|
attr_reader :values
|
90
104
|
|
91
|
-
def initialize
|
105
|
+
def initialize(options = {})
|
92
106
|
@values = {}
|
107
|
+
|
108
|
+
description = @@classes[self.class.name]
|
109
|
+
if options.has_key?(description.primary_key.to_sym)
|
110
|
+
@values[description.primary_key] = options[description.primary_key.to_sym]
|
111
|
+
end
|
112
|
+
|
113
|
+
description.properties.each do |prop|
|
114
|
+
next if prop == description.primary_key
|
115
|
+
next unless options.has_key?(prop.to_sym)
|
116
|
+
@values[prop] = options[prop.to_sym]
|
117
|
+
end
|
93
118
|
end
|
94
119
|
|
95
120
|
def to_template
|
@@ -100,7 +125,21 @@ class KnowledgeClass
|
|
100
125
|
next if k == description.primary_key
|
101
126
|
|
102
127
|
fact << k.to_sym
|
103
|
-
fact << k
|
128
|
+
fact << values[k]
|
129
|
+
end
|
130
|
+
|
131
|
+
fact
|
132
|
+
end
|
133
|
+
|
134
|
+
def to_params
|
135
|
+
description = @@classes[self.class.name]
|
136
|
+
fact = [description.name.to_sym]
|
137
|
+
fact << (values.has_key?(description.primary_key) ? values[description.primary_key] : description.primary_key.upcase.to_sym)
|
138
|
+
description.properties.each do |k|
|
139
|
+
next if k == description.primary_key
|
140
|
+
|
141
|
+
fact << k.to_sym
|
142
|
+
fact << (values.has_key?(k) ? values[k] : k.upcase.to_sym)
|
104
143
|
end
|
105
144
|
|
106
145
|
fact
|
@@ -79,7 +79,8 @@ class Ontology
|
|
79
79
|
return if predicate.empty?
|
80
80
|
return if current_ruleset.count {|rule| rule.name == name} > 0
|
81
81
|
|
82
|
-
|
82
|
+
distilled_predicate = predicate.map {|cond| cond.is_a?(KnowledgeClass) ? cond.to_params : cond}
|
83
|
+
current_ruleset << RuleDescription.new(name, distilled_predicate, options, block)
|
83
84
|
end
|
84
85
|
end
|
85
86
|
|
@@ -180,6 +181,10 @@ class Ontology
|
|
180
181
|
end
|
181
182
|
end
|
182
183
|
|
184
|
+
def report_incident(subject, description)
|
185
|
+
|
186
|
+
end
|
187
|
+
|
183
188
|
def dump_kb()
|
184
189
|
@facts.enumerate.map {|fact| fact.data.to_s}
|
185
190
|
end
|
@@ -222,7 +227,37 @@ class Ontology
|
|
222
227
|
|
223
228
|
def inform_and_wait(agent, fact, options = {})
|
224
229
|
|
225
|
-
|
230
|
+
end
|
231
|
+
|
232
|
+
#
|
233
|
+
# The action of agreeing to perform some action, possibly in the future.
|
234
|
+
#
|
235
|
+
def agree(agent, action, options = {})
|
236
|
+
puts "%25s | agree with %s to perform %s %s" % [identifier, agent, Sexpistol.new.to_sexp(action), print_message_options(options)]
|
237
|
+
|
238
|
+
channel = ChannelFactory.retrieve(identifier, agent)
|
239
|
+
channel.agree(identifier, action, options) if channel
|
240
|
+
end
|
241
|
+
|
242
|
+
#
|
243
|
+
# The action of refusing to perform a given action, and explaining the reason for the refusal.
|
244
|
+
#
|
245
|
+
def refuse(agent, action, reason, options = {})
|
246
|
+
puts "%25s | refuse %s to perform %s because %s %s" % [identifier, agent, Sexpistol.new.to_sexp(action), Sexpistol.new.to_sexp(reason), print_message_options(options)]
|
247
|
+
|
248
|
+
channel = ChannelFactory.retrieve(identifier, agent)
|
249
|
+
channel.refuse(identifier, action, reason, options) if channel
|
250
|
+
end
|
251
|
+
|
252
|
+
#
|
253
|
+
# The action of telling another agent that an action was attempted but the attempt failed.
|
254
|
+
#
|
255
|
+
def failure(agent, action, reason = true , options = {})
|
256
|
+
puts "%25s | inform %s that action %s failed with reason %s %s" % [identifier, agent, Sexpistol.new.to_sexp(action), Sexpistol.new.to_sexp(reason), print_message_options(options)]
|
257
|
+
|
258
|
+
channel = ChannelFactory.retrieve(identifier, agent)
|
259
|
+
channel.failure(identifier, action, reason, options) if channel
|
260
|
+
end
|
226
261
|
|
227
262
|
#
|
228
263
|
# Send request to another agent.
|
@@ -268,29 +303,62 @@ class Ontology
|
|
268
303
|
def handle_inform(sender, proposition, options = {})
|
269
304
|
puts "%25s | received %s from %s %s" % [identifier, Sexpistol.new.to_sexp(proposition), sender, print_message_options(options)]
|
270
305
|
|
271
|
-
if
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
306
|
+
if !handle_saga_reply(sender, :inform, proposition, options)
|
307
|
+
@classes.each do |klass|
|
308
|
+
if instance = klass.from_fact(proposition)
|
309
|
+
matcher = PatternMatcher.new(@facts.enumerate)
|
310
|
+
bindings = matcher.match(instance.to_template)
|
311
|
+
if !bindings.empty?
|
312
|
+
replace instance.to_template, matcher.pattern_matches?(proposition, instance.to_template)
|
313
|
+
else
|
314
|
+
assert(proposition)
|
315
|
+
end
|
316
|
+
|
317
|
+
return
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
276
321
|
assert proposition, :origin => sender
|
277
322
|
end
|
278
323
|
end
|
279
324
|
|
325
|
+
|
326
|
+
def handle_agree(sender, action, options = {})
|
327
|
+
if !handle_saga_reply(sender, :agree, action, options)
|
328
|
+
puts "%25s | %s agreed to perform %s %s" % [identifier, sender, Sexpistol.new.to_sexp(action), print_message_options(options)]
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def handle_refuse(sender, action, reason, options = {})
|
333
|
+
if !handle_saga_reply(sender, :refuse, [action, reason], options)
|
334
|
+
puts "%25s | %s refused to perform %s because %s %s" % [identifier, sender, Sexpistol.new.to_sexp(action), Sexpistol.new.to_sexp(reason), print_message_options(options)]
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
def handle_failure(sender, action, reason, options = {})
|
339
|
+
if !handle_saga_reply(sender, :failure, [action, reason], options)
|
340
|
+
puts "%25s | %s failed to perform %s because %s %s" % [identifier, sender, Sexpistol.new.to_sexp(action), Sexpistol.new.to_sexp(reason), print_message_options(options)]
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
280
344
|
#
|
281
345
|
# Abstract method to handle requests to this ontology.
|
282
346
|
#
|
283
|
-
def handle_request(sender, contents, options = {})
|
347
|
+
def handle_request(sender, contents, options = {})
|
348
|
+
if !handle_saga_reply(sender, :request, contents, options)
|
349
|
+
puts "%25s | %s requests to perform %s %s" % [identifier, sender, Sexpistol.new.to_sexp(contents), print_message_options(options)]
|
350
|
+
end
|
351
|
+
end
|
284
352
|
|
285
353
|
def handle_query(sender, expression, options = {})
|
286
|
-
puts "%25s | %s queries %s %s" % [identifier, sender, Sexpistol.new.to_sexp(expression), print_message_options(options)]
|
354
|
+
puts "%25s | %s queries %s %s" % [identifier, sender, Sexpistol.new.to_sexp(expression), print_message_options(options)] unless handle_saga_reply(sender, :query, expression, options)
|
287
355
|
end
|
288
356
|
|
289
357
|
#
|
290
358
|
# Handles query-if to ontology. By default, it lookups the fact in KB and replies to the sender.
|
291
359
|
#
|
292
360
|
def handle_query_if(sender, proposition, options = {})
|
293
|
-
puts "%25s | %s queries if %s %s" % [identifier, sender, Sexpistol.new.to_sexp(proposition), print_message_options(options)]
|
361
|
+
puts "%25s | %s queries if %s %s" % [identifier, sender, Sexpistol.new.to_sexp(proposition), print_message_options(options)] unless handle_saga_reply(sender, :query, expression, options)
|
294
362
|
end
|
295
363
|
|
296
364
|
protected
|
@@ -298,6 +366,17 @@ class Ontology
|
|
298
366
|
attr_reader :facts
|
299
367
|
attr_accessor :running
|
300
368
|
|
369
|
+
def handle_saga_reply(sender, action, content, options)
|
370
|
+
if options.has_key?(:conversation_id) || options.has_key?(:in_reply_to)
|
371
|
+
saga_id = options[:conversation_id] || options[:in_reply_to]
|
372
|
+
saga = @sagas.find {|saga| saga.id == saga_id}
|
373
|
+
saga.handle_reply(sender, content, :action => action) if saga
|
374
|
+
return true
|
375
|
+
end
|
376
|
+
|
377
|
+
false
|
378
|
+
end
|
379
|
+
|
301
380
|
def assert_nb(fact, options = {}, silent = false)
|
302
381
|
silent = options unless options.is_a?(Hash)
|
303
382
|
options = {} unless options.is_a?(Hash)
|
data/lib/cirrocumulus/saga.rb
CHANGED
@@ -43,10 +43,23 @@ class Saga
|
|
43
43
|
#
|
44
44
|
# Inter-agent communications with context of this saga.
|
45
45
|
#
|
46
|
+
|
46
47
|
def inform(agent, proposition)
|
47
48
|
@ontology.inform agent, proposition, :conversation_id => self.id
|
48
49
|
end
|
49
50
|
|
51
|
+
def agree(agent, action)
|
52
|
+
@ontology.agree agent, action, :conversation_id => self.id
|
53
|
+
end
|
54
|
+
|
55
|
+
def refuse(agent, action, reason)
|
56
|
+
@ontology.refuse agent, action, reason, :conversation_id => self.id
|
57
|
+
end
|
58
|
+
|
59
|
+
def failure(agent, action, reason = true)
|
60
|
+
@ontology.failure agent, action, reason, :conversation_id => self.id
|
61
|
+
end
|
62
|
+
|
50
63
|
def request(agent, action)
|
51
64
|
@ontology.request agent, action, :conversation_id => self.id
|
52
65
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cirrocumulus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-12-
|
12
|
+
date: 2012-12-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: log4r
|