cirrocumulus 0.9.2 → 0.9.7
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 +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
|