cirrocumulus 0.6.3 → 0.9.2

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.
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