cirrocumulus 0.6.3 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.rdoc +1 -22
  2. data/lib/cirrocumulus.rb +8 -2
  3. data/lib/cirrocumulus/{message.rb → agents/message.rb} +0 -0
  4. data/lib/cirrocumulus/channels.rb +129 -0
  5. data/lib/cirrocumulus/channels/jabber.rb +168 -0
  6. data/lib/cirrocumulus/environment.rb +46 -0
  7. data/lib/cirrocumulus/facts.rb +135 -0
  8. data/lib/cirrocumulus/identifier.rb +84 -0
  9. data/lib/cirrocumulus/ontology.rb +333 -49
  10. data/lib/cirrocumulus/pattern_matching.rb +175 -0
  11. data/lib/cirrocumulus/remote_console.rb +29 -0
  12. data/lib/cirrocumulus/rule_queue.rb +69 -0
  13. data/lib/cirrocumulus/rules/engine.rb +7 -5
  14. data/lib/cirrocumulus/rules/fact.rb +22 -0
  15. data/lib/cirrocumulus/rules/pattern_matcher.rb +19 -0
  16. data/lib/cirrocumulus/rules/run_queue.rb +1 -0
  17. data/lib/cirrocumulus/saga.rb +44 -25
  18. data/lib/console.rb +96 -0
  19. metadata +79 -72
  20. data/.document +0 -5
  21. data/.idea/.name +0 -1
  22. data/.idea/.rakeTasks +0 -7
  23. data/.idea/cirrocumulus.iml +0 -87
  24. data/.idea/encodings.xml +0 -5
  25. data/.idea/misc.xml +0 -5
  26. data/.idea/modules.xml +0 -9
  27. data/.idea/scopes/scope_settings.xml +0 -5
  28. data/.idea/vcs.xml +0 -7
  29. data/.idea/workspace.xml +0 -614
  30. data/Gemfile +0 -18
  31. data/Gemfile.lock +0 -43
  32. data/Rakefile +0 -37
  33. data/VERSION +0 -1
  34. data/cirrocumulus.gemspec +0 -106
  35. data/lib/.gitignore +0 -5
  36. data/lib/cirrocumulus/agent_wrapper.rb +0 -67
  37. data/lib/cirrocumulus/jabber_bus.rb +0 -140
  38. data/lib/cirrocumulus/rule_engine.rb +0 -2
  39. data/lib/cirrocumulus/rule_server.rb +0 -49
  40. data/test/Gemfile +0 -3
  41. data/test/Gemfile.lock +0 -27
  42. data/test/helper.rb +0 -18
  43. data/test/test.rb +0 -85
  44. data/test/test2.rb +0 -30
  45. data/test/test_cirrocumulus.rb +0 -7
@@ -1,24 +1,3 @@
1
1
  = Cirrocumulus
2
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
3
+ Cirrocumulus is an agent-based 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.
@@ -1,2 +1,8 @@
1
- require 'active_support'
2
- require 'cirrocumulus/logger'
1
+ require 'thread'
2
+ require 'sexpistol'
3
+ require_relative 'cirrocumulus/identifier'
4
+ require_relative 'cirrocumulus/facts'
5
+ require_relative 'cirrocumulus/channels/jabber'
6
+ require_relative 'cirrocumulus/channels'
7
+ require_relative 'cirrocumulus/ontology'
8
+ require_relative 'cirrocumulus/environment'
@@ -0,0 +1,129 @@
1
+ require_relative 'channels/jabber'
2
+
3
+ #
4
+ # Communication channel between agents. This is an abstract class.
5
+ #
6
+ class AbstractChannel
7
+ #
8
+ # Informs the other side that given proposition is true.
9
+ #
10
+ def inform(sender, proposition, options = {}); end
11
+
12
+ #
13
+ # Request the other side to perform an action
14
+ #
15
+ def request(sender, action, options = {}); end
16
+
17
+ #
18
+ # Query the other side about expression value.
19
+ #
20
+ def query(sender, expression, options = {}); end
21
+
22
+ #
23
+ # Query the other side if given proposition is true.
24
+ #
25
+ def query_if(sender, proposition, options = {}); end
26
+ end
27
+
28
+ #
29
+ # Communication channel between different threads on local machine.
30
+ #
31
+ class ThreadChannel < AbstractChannel
32
+ def initialize(ontology)
33
+ @ontology = ontology
34
+ end
35
+
36
+ def inform(sender, proposition, options = {})
37
+ @ontology.handle_inform(sender, proposition, options)
38
+ end
39
+
40
+ def request(sender, action, options = {})
41
+ @ontology.handle_request(sender, action, options)
42
+ end
43
+
44
+ def query(sender, expression, options = {})
45
+ @ontology.handle_query(sender, expression, options)
46
+ end
47
+
48
+ def query_if(sender, proposition, options = {})
49
+ @ontology.handle_query_if(sender, proposition, options)
50
+ end
51
+ end
52
+
53
+ #
54
+ # Communication channel over network.
55
+ #
56
+ class NetworkChannel < AbstractChannel
57
+ def initialize(client, remote_jid)
58
+ @remote_jid = remote_jid
59
+ @client = client
60
+ @serializer = Sexpistol.new
61
+ end
62
+
63
+ def inform(sender, proposition, options = {})
64
+ client.queue(serializer.to_sexp(build_message(remote_jid, :inform, proposition, options)))
65
+ end
66
+
67
+ def request(sender, action, options = {})
68
+ client.queue(serializer.to_sexp(build_message(remote_jid, :request, action, options)))
69
+ end
70
+
71
+ def query(sender, expression, options = {})
72
+ client.queue(serializer.to_sexp(build_message(remote_jid, :query, expression, options)))
73
+ end
74
+
75
+ def query_if(sender, proposition, options = {})
76
+ client.queue(serializer.to_sexp(build_message(remote_jid, :query_if, proposition, options)))
77
+ end
78
+
79
+ private
80
+
81
+ attr_reader :client
82
+ attr_reader :remote_jid
83
+ attr_reader :serializer
84
+
85
+ def build_message(receiver_name, act, content, options)
86
+ msg = [
87
+ act,
88
+ [:receiver,
89
+ [:agent_identifier, :name, receiver_name]
90
+ ],
91
+ [:content, content]
92
+ ]
93
+
94
+ msg << [:reply_with, options[:reply_with]] if options.has_key?(:reply_with)
95
+ msg << [:in_reply_to, options[:in_reply_to]] if options.has_key?(:in_reply_to)
96
+ msg << [:conversation_id, options[:conversation_id]] if options.has_key?(:conversation_id)
97
+
98
+ msg
99
+ end
100
+
101
+ end
102
+
103
+ #
104
+ # Factory to retrieve proper communication channel for two agents.
105
+ #
106
+ class ChannelFactory
107
+ def self.retrieve(instance, agent)
108
+ if agent.is_a?(LocalIdentifier)
109
+ ontology_instance = Ontology.query_ontology_instance(agent)
110
+
111
+ if ontology_instance
112
+ return ThreadChannel.new(Ontology.query_ontology_instance(agent))
113
+ else
114
+ puts "[WARN] Thread-local ontology not found for identifier=%s" % agent.to_s
115
+ end
116
+ elsif agent.is_a?(RemoteIdentifier)
117
+ jabber_client = JabberChannel.query_client(instance.to_s)
118
+
119
+ if jabber_client
120
+ return NetworkChannel.new(jabber_client, agent.to_s)
121
+ else
122
+ puts "[WARN] No active Jabber clients."
123
+ end
124
+ end
125
+
126
+ puts "[WARN] No suitable channel found for #{agent.to_s} (#{agent.class.name})"
127
+ nil
128
+ end
129
+ end
@@ -0,0 +1,168 @@
1
+ require 'xmpp4r'
2
+ require 'xmpp4r-simple'
3
+ require 'guid'
4
+ require 'thread'
5
+
6
+ class JabberIdentifier < RemoteIdentifier
7
+ def initialize(jid)
8
+ @jid = "#{Cirrocumulus::Environment.current.name}-#{jid}"
9
+ @channel = JabberChannel.new('172.16.11.4', 'cirrocumulus')
10
+ @channel.connect(@jid, 'q1w2e3r4')
11
+ @thrd = Thread.new do
12
+ parser = Sexpistol.new
13
+ while true do
14
+ @channel.tick()
15
+
16
+ instance = Ontology.query_ontology_instance(self)
17
+ if !instance.nil?
18
+ while true
19
+ msg = @channel.received_messages.pop(true) rescue nil
20
+ break if msg.nil?
21
+
22
+ begin
23
+ fipa_message = parser.parse_string(msg.body)
24
+ id = RemoteIdentifier.new(msg.from.resource)
25
+
26
+ next if fipa_message.size < 1
27
+
28
+ fipa_message = fipa_message.first
29
+
30
+ next if fipa_message.size < 2
31
+
32
+ act = fipa_message[0]
33
+ fipa_message.delete_at(0)
34
+ content = fipa_message
35
+
36
+ receiver = nil
37
+ action_content = nil
38
+ options = {}
39
+ content.each do |parameter|
40
+ next if !parameter.is_a?(Array) || parameter.size < 1
41
+ if parameter[0] == :receiver
42
+ receiver = parameter[1][2]
43
+ elsif parameter[0] == :content
44
+ action_content = parameter[1]
45
+ elsif [:reply_with, :in_reply_to, :conversation_id].include?(parameter[0])
46
+ options[parameter[0]] = parameter[1]
47
+ end
48
+ end
49
+
50
+ next if receiver.nil? || receiver != @jid
51
+
52
+ case act
53
+ when :query
54
+ instance.handle_query(id, action_content, options)
55
+ when :query_if
56
+ instance.handle_query_if(id, action_content, options)
57
+ when :inform
58
+ instance.handle_inform(id, action_content, options)
59
+ when :request
60
+ instance.handle_request(id, action_content, options)
61
+ end
62
+ rescue Exception => ex
63
+ puts ex.message
64
+ puts ex.backtrace.to_s
65
+ end
66
+ end
67
+ end
68
+ sleep 0.1
69
+ end
70
+ end
71
+ end
72
+
73
+ def join
74
+ @thrd.join
75
+ end
76
+
77
+ def to_s
78
+ @jid
79
+ end
80
+ end
81
+
82
+ class JabberChannel
83
+ class << self
84
+ @@jabber_clients = []
85
+
86
+ def query_client(jid)
87
+ @@jabber_clients.find {|c| c.jid == jid}
88
+ end
89
+ end
90
+
91
+ attr_reader :jid
92
+ attr_reader :conference
93
+
94
+ def initialize(server, conference)
95
+ @jabber = nil
96
+ @server = server
97
+ @conference = conference
98
+ @send_q = Queue.new
99
+ @recv_q = Queue.new
100
+
101
+ @@jabber_clients << self
102
+ end
103
+
104
+ def connected?
105
+ @jabber && @jabber.connected?
106
+ end
107
+
108
+ def connect(jid, password)
109
+ @full_jid = "%s@%s" % [jid, @server]
110
+ @jid = jid
111
+
112
+ puts "Using jid #{@jid}"
113
+
114
+ begin
115
+ @jabber = Jabber::Simple.new(@full_jid, password)
116
+ rescue Jabber::ClientAuthenticationFailure => ex
117
+ puts ex.class.name
118
+ client = Jabber::Client.new(@full_jid)
119
+ client.connect()
120
+ client.register(password)
121
+ client.close()
122
+ @jabber = Jabber::Simple.new(@full_jid, password)
123
+ rescue Exception => ex
124
+ puts ex.to_s
125
+ end
126
+
127
+ join_conference(@conference) if connected?
128
+ connected?
129
+ end
130
+
131
+ def disconnect()
132
+ @jabber.disconnect()
133
+ end
134
+
135
+ def queue(msg)
136
+ @send_q << msg
137
+ end
138
+
139
+ def received_messages
140
+ @recv_q
141
+ end
142
+
143
+ def tick()
144
+ return if !connected?
145
+
146
+ @jabber.received_messages do |msg|
147
+ next unless msg.x('jabber:x:delay').nil?
148
+
149
+ @recv_q << msg
150
+ end
151
+
152
+ while true do
153
+ to_send = @send_q.pop(true) rescue nil
154
+ break if to_send.nil?
155
+
156
+ @jabber.send!('<message type="groupchat" to="%s" id="%s"><body>%s</body></message>' % [
157
+ "%s@conference.%s" % [@conference, @server], Guid.new.to_s.gsub('-', ''),
158
+ to_send.gsub('&', '&amp;').gsub('<', '&lt;').gsub('>', '&gt;').gsub('"', '&quot;')
159
+ ])
160
+ end
161
+ end
162
+
163
+ protected
164
+
165
+ def join_conference(conference)
166
+ @jabber.send!("<presence to='#{conference}@conference.#{@server}/#{@jid}' />")
167
+ end
168
+ end
@@ -0,0 +1,46 @@
1
+ module Cirrocumulus
2
+ #
3
+ # Cirrocumulus environment. It is a container where all ontologies will be loaded.
4
+ #
5
+ class Environment
6
+
7
+ def self.current
8
+ @@instance
9
+ end
10
+
11
+ attr_reader :name
12
+
13
+ def initialize(name)
14
+ puts "Loading #{name} Cirrocumulus environment."
15
+
16
+ @name = name
17
+ @ontologies = []
18
+ @@instance = self
19
+ end
20
+
21
+ #
22
+ # Loads ontology instance into environment.
23
+ #
24
+ def load_ontology(ontology_instance)
25
+ puts "Adding #{ontology_instance.name} ontology."
26
+ @ontologies << ontology_instance
27
+ end
28
+
29
+ #
30
+ # Runs the environment. It will also restore all loaded ontologies states.
31
+ #
32
+ def run
33
+ @ontologies.each {|ontology| ontology.restore_state }
34
+ @ontologies.each {|ontology| ontology.run }
35
+ end
36
+
37
+ def join
38
+ @ontologies.each {|ontology| ontology.join }
39
+ end
40
+
41
+ private
42
+
43
+ @@instance = nil
44
+
45
+ end
46
+ end
@@ -0,0 +1,135 @@
1
+ #
2
+ # Fact (piece of knowledge) representation.
3
+ # It holds time when it was observed (added to facts database) and expire time in seconds.
4
+ #
5
+ class Fact
6
+ def initialize(data, time, options)
7
+ @data = data
8
+ @time = time
9
+ @options = options
10
+ @is_deleted = false
11
+ end
12
+
13
+ attr_reader :data
14
+ attr_accessor :is_deleted
15
+
16
+ def timed_out?
17
+ return false if @options[:expires] == nil
18
+ @time + @options[:expires] < Time.now
19
+ end
20
+ end
21
+
22
+ #
23
+ # Adapter for facts database.
24
+ #
25
+ class FactsDatabase
26
+ def initialize
27
+ @storage = []
28
+ end
29
+
30
+ def add(fact, options = {})
31
+ @storage << Fact.new(fact, Time.now, options)
32
+ end
33
+
34
+ def remove(fact)
35
+ @storage.delete_if {|f| f.data == fact}
36
+ end
37
+
38
+ def enumerate
39
+ @storage.dup
40
+ end
41
+ end
42
+
43
+ class KnowledgeClass
44
+ class KnowledgeClassDescription
45
+ attr_accessor :name
46
+ attr_accessor :primary_key
47
+ attr_reader :properties
48
+
49
+ def initialize(klass)
50
+ @name = klass.name
51
+ @properties = []
52
+ end
53
+ end
54
+
55
+ class << self
56
+ @@classes = {}
57
+
58
+ def klass(class_name)
59
+ @@classes[self.name] = KnowledgeClassDescription.new(self) if @@classes[self.name].nil?
60
+ @@classes[self.name].name = class_name.to_s
61
+ end
62
+
63
+ def id(id_name)
64
+ @@classes[self.name] = KnowledgeClassDescription.new(self) if @@classes[self.name].nil?
65
+ @@classes[self.name].primary_key = id_name.to_s
66
+ @@classes[self.name].properties << id_name.to_s
67
+ end
68
+
69
+ def property(property_name)
70
+ @@classes[self.name] = KnowledgeClassDescription.new(self) if @@classes[self.name].nil?
71
+ @@classes[self.name].properties << property_name.to_s
72
+ end
73
+
74
+ def from_fact(fact)
75
+ description = @@classes[self.name]
76
+ return nil if fact[0] != description.name.to_sym
77
+
78
+ instance = self.new
79
+ instance.values[description.primary_key] = fact[1]
80
+ description.properties.each_with_index do |k, idx|
81
+ next if idx == 0
82
+ instance.values[k] = fact[1 + idx*2]
83
+ end
84
+
85
+ instance
86
+ end
87
+ end
88
+
89
+ attr_reader :values
90
+
91
+ def initialize
92
+ @values = {}
93
+ end
94
+
95
+ def to_template
96
+ description = @@classes[self.class.name]
97
+ fact = [description.name.to_sym]
98
+ fact << values[description.primary_key]
99
+ description.properties.each do |k|
100
+ next if k == description.primary_key
101
+
102
+ fact << k.to_sym
103
+ fact << k.upcase.to_sym
104
+ end
105
+
106
+ fact
107
+ end
108
+
109
+ def to_fact
110
+ description = @@classes[self.class.name]
111
+ fact = [description.name.to_sym]
112
+ fact << values[description.primary_key]
113
+ description.properties.each do |k|
114
+ next if k == description.primary_key
115
+
116
+ fact << k.to_sym
117
+ fact << values[k]
118
+ end
119
+
120
+ fact
121
+ end
122
+
123
+ def method_missing(meth, *args, &block)
124
+ description = @@classes[self.class.name]
125
+ description.properties.each do |prop|
126
+ if meth.to_s == "#{prop}="
127
+ return values[prop] = args[0]
128
+ elsif meth.to_s == prop
129
+ return values[prop]
130
+ end
131
+ end
132
+
133
+ super
134
+ end
135
+ end