omf_common 6.0.0.pre.10 → 6.0.0.pre.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. data/bin/monitor_topic.rb +80 -0
  2. data/bin/send_create.rb +94 -0
  3. data/bin/send_request.rb +58 -0
  4. data/example/engine_alt.rb +136 -0
  5. data/example/vm_alt.rb +65 -0
  6. data/lib/omf_common.rb +224 -3
  7. data/lib/omf_common/comm.rb +113 -46
  8. data/lib/omf_common/comm/amqp/amqp_communicator.rb +76 -0
  9. data/lib/omf_common/comm/amqp/amqp_topic.rb +91 -0
  10. data/lib/omf_common/comm/local/local_communicator.rb +64 -0
  11. data/lib/omf_common/comm/local/local_topic.rb +42 -0
  12. data/lib/omf_common/comm/topic.rb +190 -0
  13. data/lib/omf_common/{dsl/xmpp.rb → comm/xmpp/communicator.rb} +93 -53
  14. data/lib/omf_common/comm/xmpp/topic.rb +147 -0
  15. data/lib/omf_common/{dsl → comm/xmpp}/xmpp_mp.rb +2 -0
  16. data/lib/omf_common/eventloop.rb +94 -0
  17. data/lib/omf_common/eventloop/em.rb +57 -0
  18. data/lib/omf_common/eventloop/local_evl.rb +78 -0
  19. data/lib/omf_common/message.rb +112 -229
  20. data/lib/omf_common/message/json/json_message.rb +129 -0
  21. data/lib/omf_common/message/xml/message.rb +410 -0
  22. data/lib/omf_common/message/xml/relaxng_schema.rb +17 -0
  23. data/lib/omf_common/message/xml/topic_message.rb +20 -0
  24. data/lib/omf_common/protocol/6.0.rnc +11 -21
  25. data/lib/omf_common/protocol/6.0.rng +52 -119
  26. data/lib/omf_common/version.rb +1 -1
  27. data/omf_common.gemspec +4 -2
  28. data/test/fixture/pubsub.rb +19 -19
  29. data/test/omf_common/{dsl/xmpp_spec.rb → comm/xmpp/communicator_spec.rb} +47 -111
  30. data/test/omf_common/comm/xmpp/topic_spec.rb +113 -0
  31. data/test/omf_common/comm_spec.rb +1 -0
  32. data/test/omf_common/message/xml/message_spec.rb +136 -0
  33. data/test/omf_common/message_spec.rb +37 -131
  34. data/test/test_helper.rb +4 -1
  35. metadata +38 -28
  36. data/lib/omf_common/core_ext/object.rb +0 -21
  37. data/lib/omf_common/relaxng_schema.rb +0 -17
  38. data/lib/omf_common/topic.rb +0 -34
  39. data/lib/omf_common/topic_message.rb +0 -20
  40. data/test/omf_common/topic_message_spec.rb +0 -114
  41. data/test/omf_common/topic_spec.rb +0 -75
@@ -0,0 +1,190 @@
1
+ require 'monitor'
2
+ require 'securerandom'
3
+
4
+ module OmfCommon
5
+ class Comm
6
+ class Topic
7
+
8
+ @@name2inst = {}
9
+ @@lock = Monitor.new
10
+
11
+ def self.create(name, opts = {}, &block)
12
+ # Force string conversion as 'name' can be an ExperimentProperty
13
+ name = name.to_s.to_sym
14
+ @@lock.synchronize do
15
+ unless @@name2inst[name]
16
+ debug "New topic: #{name}"
17
+ #opts[:address] ||= address_for(name)
18
+ @@name2inst[name] = self.new(name, opts, &block)
19
+ else
20
+ debug "Existing topic: #{name}"
21
+ block.call(@@name2inst[name]) if block
22
+ end
23
+ @@name2inst[name]
24
+ end
25
+ end
26
+
27
+ def self.[](name)
28
+ @@name2inst[name]
29
+ end
30
+
31
+ attr_reader :id
32
+
33
+ # Request the creation of a new resource. Returns itself
34
+ #
35
+ def create(res_type, config_props = {}, core_props = {}, &block)
36
+ # new_res = nil
37
+ #res_name = res_name.to_sym
38
+ #config_props[:name] ||= res_name
39
+ config_props[:type] ||= res_type
40
+ debug "Create resource of type '#{res_type}'"
41
+ create_message_and_publish(:create, config_props, core_props, block)
42
+ self
43
+ end
44
+
45
+ def configure(props = {}, core_props = {}, &block)
46
+ create_message_and_publish(:configure, props, core_props, block)
47
+ self
48
+ end
49
+
50
+ def request(select = [], core_props = {}, &block)
51
+ # TODO: What are the parameters to the request method really?
52
+ create_message_and_publish(:request, select, core_props, block)
53
+ self
54
+ end
55
+
56
+ def inform(type, props = {}, core_props = {}, &block)
57
+ msg = OmfCommon::Message.create(:inform, props, core_props.merge(itype: type))
58
+ publish(msg, &block)
59
+ self
60
+ end
61
+
62
+ # def inform(type, props = {}, &block)
63
+ # msg = OmfCommon::Message.create(:inform, props)
64
+ # msg.itype = type
65
+ # publish(msg, &block)
66
+ # self
67
+ # end
68
+
69
+ def release(resource, core_props = {}, &block)
70
+ unless resource.is_a? self.class
71
+ raise "Expected '#{self.class}', but got '#{resource.class}'"
72
+ end
73
+ msg = OmfCommon::Message.create(:release, {}, core_props.merge(res_id: resource.id))
74
+ publish(msg, &block)
75
+ self
76
+ end
77
+
78
+
79
+ def create_message_and_publish(type, props = {}, core_props = {}, block = nil)
80
+ debug "(#{id}) create_message_and_publish '#{type}': #{props.inspect}"
81
+ msg = OmfCommon::Message.create(type, props, core_props)
82
+ publish(msg, &block)
83
+ end
84
+
85
+ def publish(msg, &block)
86
+ # TODO should it be _send_message(msg, &block) ?
87
+ #raise "Expected message but got '#{msg.class}" unless msg.is_a?(OmfCommon::Message)
88
+ _send_message(msg, block)
89
+ end
90
+
91
+ [:created,
92
+ :create_succeeded, :create_failed,
93
+ :inform_status, :inform_failed,
94
+ :released, :failed,
95
+ :message
96
+ ].each do |itype|
97
+ mname = "on_#{itype}"
98
+ define_method(mname) do |*args, &message_block|
99
+ debug "(#{id}) register handler for '#{mname}'"
100
+ @lock.synchronize do
101
+ (@handlers[itype] ||= []) << message_block
102
+ end
103
+ self
104
+ end
105
+ end
106
+
107
+ # Unsubscribe from the underlying comms layer
108
+ #
109
+ def unsubscribe()
110
+
111
+ end
112
+
113
+ def on_subscribed(&block)
114
+ raise "Not implemented"
115
+ end
116
+
117
+ # For detecting message publishing error, means if callback indeed yield a Topic object, there is no publishing error, thus always false
118
+ def error?
119
+ false
120
+ end
121
+
122
+ def address
123
+ raise "Not implemented"
124
+ end
125
+
126
+ def after(delay_sec, &block)
127
+ return unless block
128
+ OmfCommon.eventloop.after(delay_sec) do
129
+ block.arity == 1 ? block.call(self) : block.call
130
+ end
131
+ end
132
+
133
+ private
134
+
135
+ def initialize(id, opts = {})
136
+ @id = id
137
+ #@address = opts[:address]
138
+ @handlers = {}
139
+ @lock = Monitor.new
140
+ @context2cbk = {}
141
+ end
142
+
143
+
144
+ def _send_message(msg, block = nil)
145
+ if (block)
146
+ # register callback for responses to 'mid'
147
+ @context2cbk[msg.mid.to_s] = {block: block, created_at: Time.now}
148
+ end
149
+ end
150
+
151
+ def on_incoming_message(msg)
152
+ type = msg.operation
153
+ debug "(#{id}) Deliver message '#{type}': #{msg.inspect}"
154
+ htypes = [type, :message]
155
+ if type == :inform
156
+ if it = msg.itype.to_s.downcase
157
+ #puts "TTT> #{it}"
158
+ case it
159
+ when "creation_ok"
160
+ htypes << :create_succeeded
161
+ when 'status'
162
+ htypes << :inform_status
163
+ else
164
+ htypes << it.to_sym
165
+ end
166
+ end
167
+ end
168
+
169
+ debug "(#{id}) Message type '#{htypes.inspect}' (#{msg.class}:#{msg.cid})"
170
+ hs = htypes.map do |ht| @handlers[ht] end.compact.flatten
171
+ debug "(#{id}) Distributing message to '#{hs.inspect}'"
172
+ hs.each do |block|
173
+ block.call msg
174
+ end
175
+ if cbk = @context2cbk[msg.cid.to_s]
176
+ debug "(#{id}) Distributing message to '#{cbk.inspect}'"
177
+ cbk[:last_used] = Time.now
178
+ cbk[:block].call(msg)
179
+ # else
180
+ # if msg.cid
181
+ # puts "====NOOOO for #{msg.cid} - #{@context2cbk.keys.inspect}"
182
+ # end
183
+ end
184
+
185
+ end
186
+
187
+
188
+ end
189
+ end
190
+ end
@@ -1,10 +1,18 @@
1
1
  require 'blather/client/dsl'
2
2
 
3
+ require 'omf_common/comm/xmpp/xmpp_mp'
4
+ require 'omf_common/comm/xmpp/topic'
5
+ require 'uri'
6
+ require 'socket'
7
+
3
8
  module OmfCommon
4
- module DSL
5
- module Xmpp
9
+ class Comm
10
+ class XMPP
11
+ class Communicator < OmfCommon::Comm
6
12
  include Blather::DSL
7
13
 
14
+ attr_accessor :published_messages, :normal_shutdown_mode, :retry_counter
15
+
8
16
  HOST_PREFIX = 'pubsub'
9
17
 
10
18
  PUBSUB_CONFIGURE = Blather::Stanza::X.new({
@@ -17,9 +25,63 @@ module OmfCommon
17
25
  { :var => "pubsub#publish_model", :value => "open" }]
18
26
  })
19
27
 
28
+ # Capture system :INT & :TERM signal
29
+ def on_interrupted(&block)
30
+ if block
31
+ trap(:INT) { block.call(self) }
32
+ trap(:TERM) { block.call(self) }
33
+ end
34
+ end
35
+
36
+ def on_connected(&block)
37
+ when_ready do
38
+ block.call(self)
39
+ end
40
+ end
41
+
42
+ # Set up XMPP options and start the Eventmachine, connect to XMPP server
43
+ #
44
+ def init(opts = {})
45
+ @pubsub_host = opts[:pubsub_domain]
46
+ if opts[:url]
47
+ url = URI(opts[:url])
48
+ username, password, server = url.user, url.password, url.host
49
+ else
50
+ username, password, server = opts[:username], opts[:password], opts[:server]
51
+ end
52
+
53
+ random_name = "#{Socket.gethostname}-#{Process.pid}"
54
+ username ||= random_name
55
+ password ||= random_name
56
+
57
+ raise ArgumentError, "Username cannot be nil when connect to XMPP" if username.nil?
58
+ raise ArgumentError, "Password cannot be nil when connect to XMPP" if password.nil?
59
+ raise ArgumentError, "Server cannot be nil when connect to XMPP" if server.nil?
60
+
61
+ @retry_counter = 0
62
+ @normal_shutdown_mode = false
63
+
64
+ connect(username, password, server)
65
+
66
+ disconnected do
67
+ unless normal_shutdown_mode
68
+ unless retry_counter > 0
69
+ @retry_counter += 1
70
+ client.connect
71
+ else
72
+ error "Authentication failed."
73
+ OmfCommon.eventloop.stop
74
+ end
75
+ else
76
+ shutdown
77
+ end
78
+ end
79
+ end
80
+
20
81
  # Set up XMPP options and start the Eventmachine, connect to XMPP server
21
82
  #
22
83
  def connect(username, password, server)
84
+ info "Connecting to '#{server}' ..."
23
85
  jid = "#{username}@#{server}"
24
86
  client.setup(jid, password)
25
87
  client.run
@@ -28,38 +90,18 @@ module OmfCommon
28
90
 
29
91
  # Shut down XMPP connection
30
92
  def disconnect(opts = {})
31
- if opts[:delete_affiliations]
32
- affiliations do |a|
33
- # owner means topics created, owned
34
- owner_topics = a[:owner] ? a[:owner].size : 0
35
- # none means... topics subscribed to
36
- none_topics = a[:none] ? a[:none].size : 0
37
-
38
- if none_topics > 0
39
- info "Unsubscribing #{none_topics} pubsub topic(s)"
40
- unsubscribe
41
- end
42
-
43
- if owner_topics > 0
44
- info "Deleting #{owner_topics} pubsub topic(s) in 2 seconds"
45
- EM.add_timer(2) do
46
- a[:owner].each { |topic| delete_topic(topic) }
47
- end
48
- end
49
-
50
- shutdown if none_topics == 0 && owner_topics == 0
51
- end
52
- else
53
- shutdown
54
- end
93
+ # NOTE Do not clean up
94
+ info "Disconnecting ..."
95
+ @normal_shutdown_mode = true
96
+ shutdown
55
97
  OmfCommon::DSL::Xmpp::MPConnection.inject(Time.now.to_f, jid, 'disconnect') if OmfCommon::Measure.enabled?
56
98
  end
57
99
 
58
100
  # Create a new pubsub topic with additional configuration
59
101
  #
60
102
  # @param [String] topic Pubsub topic name
61
- def create_topic(topic, &block)
62
- pubsub.create(topic, default_host, PUBSUB_CONFIGURE, &callback_logging(__method__, topic, &block))
103
+ def create_topic(topic, opts = {})
104
+ OmfCommon::Comm::XMPP::Topic.create(topic)
63
105
  end
64
106
 
65
107
  # Delete a pubsub topic
@@ -75,22 +117,19 @@ module OmfCommon
75
117
  # @param [Hash] opts
76
118
  # @option opts [Boolean] :create_if_non_existent create the topic if non-existent, use this option with caution
77
119
  def subscribe(topic, opts = {}, &block)
78
- if opts[:create_if_non_existent]
79
- affiliations do |a|
80
- if a[:owner] && a[:owner].include?(topic)
81
- pubsub.subscribe(topic, nil, default_host, &callback_logging(__method__, topic, &block))
82
- else
83
- create_topic(topic) do
84
- pubsub.subscribe(topic, nil, default_host, &callback_logging(__method__, topic, &block))
85
- end
86
- end
87
- end
88
- else
89
- pubsub.subscribe(topic, nil, default_host, &callback_logging(__method__, topic, &block))
90
- end
120
+ topic = topic.first if topic.is_a? Array
121
+ OmfCommon::Comm::XMPP::Topic.create(topic, &block)
91
122
  MPSubscription.inject(Time.now.to_f, jid, 'join', topic) if OmfCommon::Measure.enabled?
92
123
  end
93
124
 
125
+ def _subscribe(topic, &block)
126
+ pubsub.subscribe(topic, nil, default_host, &callback_logging(__method__, topic, &block))
127
+ end
128
+
129
+ def _create(topic, &block)
130
+ pubsub.create(topic, default_host, PUBSUB_CONFIGURE, &callback_logging(__method__, topic, &block))
131
+ end
132
+
94
133
  # Un-subscribe all existing subscriptions from all pubsub topics.
95
134
  def unsubscribe
96
135
  pubsub.subscriptions(default_host) do |m|
@@ -108,10 +147,12 @@ module OmfCommon
108
147
  # Publish to a pubsub topic
109
148
  #
110
149
  # @param [String] topic Pubsub topic name
111
- # @param [String] message Any XML fragment to be sent as payload
150
+ # @param [OmfCommon::Message] message Any XML fragment to be sent as payload
112
151
  def publish(topic, message, &block)
113
152
  raise StandardError, "Invalid message" unless message.valid?
114
153
 
154
+ message = message.xml unless message.kind_of? String
155
+
115
156
  new_block = proc do |stanza|
116
157
  published_messages << OpenSSL::Digest::SHA1.new(message.to_s)
117
158
  block.call(stanza) if block
@@ -121,13 +162,6 @@ module OmfCommon
121
162
  MPPublished.inject(Time.now.to_f, jid, topic, message.to_s.gsub("\n",'')) if OmfCommon::Measure.enabled?
122
163
  end
123
164
 
124
- # Event machine related method delegation
125
- %w(add_timer add_periodic_timer).each do |m_name|
126
- define_method(m_name) do |*args, &block|
127
- EM.send(m_name, *args, &block)
128
- end
129
- end
130
-
131
165
  # Event callback for pubsub topic event(item published)
132
166
  #
133
167
  def topic_event(additional_guard = nil, &block)
@@ -148,16 +182,21 @@ module OmfCommon
148
182
 
149
183
  private
150
184
 
185
+ def initialize(opts = {})
186
+ self.published_messages = []
187
+ super
188
+ end
189
+
151
190
  # Provide a new block wrap to automatically log errors
152
191
  def callback_logging(*args, &block)
153
- m = args.empty? ? "OPERATION" : args.map {|v| v.to_s.upcase }.join(" ")
192
+ m = args.empty? ? "OPERATION" : args.join(" >> ")
154
193
  proc do |stanza|
155
194
  if stanza.respond_to?(:error?) && stanza.error?
156
195
  e_stanza = Blather::StanzaError.import(stanza)
157
196
  if [:unexpected_request].include? e_stanza.name
158
197
  logger.debug e_stanza
159
- elsif e_stanza.name == :conflict
160
- logger.debug e_stanza
198
+ elsif e_stanza.name == :conflict
199
+ #logger.debug e_stanza
161
200
  else
162
201
  logger.warn "#{e_stanza} Original: #{e_stanza.original}"
163
202
  end
@@ -168,8 +207,9 @@ module OmfCommon
168
207
  end
169
208
 
170
209
  def default_host
171
- "#{HOST_PREFIX}.#{client.jid.domain}"
210
+ @pubsub_host || "#{HOST_PREFIX}.#{jid.domain}"
172
211
  end
173
212
  end
174
213
  end
175
214
  end
215
+ end
@@ -0,0 +1,147 @@
1
+ module OmfCommon
2
+ class Comm
3
+ class XMPP
4
+ class Topic < OmfCommon::Comm::Topic
5
+ %w(creation.ok creation.failed status released error warn).each do |itype|
6
+ define_method("on_#{itype.gsub(/\./, '_')}") do |*args, &message_block|
7
+ mid = args[0].mid if args[0]
8
+
9
+ raise ArgumentError, 'Missing callback' if message_block.nil?
10
+
11
+ event_block = proc do |event|
12
+ message_block.call(OmfCommon::Message.parse(event.items.first.payload))
13
+ end
14
+
15
+ guard_block = proc do |event|
16
+ (event.items?) && (!event.delayed?) &&
17
+ event.items.first.payload &&
18
+ (omf_message = OmfCommon::Message.parse(event.items.first.payload)) &&
19
+ event.node == id.to_s &&
20
+ omf_message.operation == :inform &&
21
+ omf_message.read_content(:itype) == itype.upcase &&
22
+ (mid ? (omf_message.cid == mid) : true)
23
+ end
24
+
25
+ OmfCommon.comm.topic_event(guard_block, &event_block)
26
+ end
27
+ end
28
+
29
+ def on_message(message_guard_proc = nil, ref_id = 0, &message_block)
30
+ @lock.synchronize do
31
+ @on_message_cbks[ref_id] ||= []
32
+ @on_message_cbks[ref_id] << message_block
33
+ end
34
+
35
+ event_block = proc do |event|
36
+ @on_message_cbks.each do |id, cbks|
37
+ cbks.each do |cbk|
38
+ cbk.call(OmfCommon::Message.parse(event.items.first.payload))
39
+ end
40
+ end
41
+ end
42
+
43
+ guard_block = proc do |event|
44
+ (event.items?) && (!event.delayed?) &&
45
+ event.items.first.payload &&
46
+ (omf_message = OmfCommon::Message.parse(event.items.first.payload)) &&
47
+ event.node == id.to_s &&
48
+ (valid_guard?(message_guard_proc) ? message_guard_proc.call(omf_message) : true)
49
+ end
50
+ OmfCommon.comm.topic_event(guard_block, &event_block)
51
+ end
52
+
53
+ def delete_on_message_cbk_by_id(id)
54
+ @lock.synchronize do
55
+ @on_message_cbks[id] && @on_message_cbks.reject! { |k| k == id.to_s }
56
+ end
57
+ end
58
+
59
+ def inform(type, props = {}, core_props = {}, &block)
60
+ msg = OmfCommon::Message.create(:inform, props, core_props.merge(itype: type))
61
+ publish(msg, &block)
62
+ self
63
+ end
64
+
65
+ def publish(msg, &block)
66
+ _send_message(msg, &block)
67
+ end
68
+
69
+ def address
70
+ "xmpp://#{id.to_s}@#{OmfCommon.comm.jid.domain}"
71
+ end
72
+
73
+ def on_subscribed(&block)
74
+ return unless block
75
+
76
+ @lock.synchronize do
77
+ @on_subscrided_handlers << block
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ def initialize(id, opts = {}, &block)
84
+ @on_message_cbks = Hashie::Mash.new
85
+
86
+ id = $1 if id =~ /^xmpp:\/\/(.+)@.+$/
87
+
88
+ super
89
+
90
+ @on_subscrided_handlers = []
91
+
92
+ topic_block = proc do |stanza|
93
+ if stanza.error?
94
+ block.call(stanza) if block
95
+ else
96
+ block.call(self) if block
97
+
98
+ @lock.synchronize do
99
+ @on_subscrided_handlers.each do |handler|
100
+ handler.call
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ OmfCommon.comm._create(id.to_s) do |stanza|
107
+ if stanza.error?
108
+ e_stanza = Blather::StanzaError.import(stanza)
109
+ if e_stanza.name == :conflict
110
+ # Topic exists, just subscribe to it.
111
+ OmfCommon.comm._subscribe(id.to_s, &topic_block)
112
+ else
113
+ block.call(stanza) if block
114
+ end
115
+ else
116
+ OmfCommon.comm._subscribe(id.to_s, &topic_block)
117
+ end
118
+ end
119
+ end
120
+
121
+ def _send_message(msg, &block)
122
+ # while sending a message, need to setup handler for replying messages
123
+ OmfCommon.comm.publish(self.id, msg) do |stanza|
124
+ if !stanza.error? && !block.nil?
125
+ on_error(msg, &block)
126
+ on_warn(msg, &block)
127
+ case msg.operation
128
+ when :create
129
+ on_creation_ok(msg, &block)
130
+ on_creation_failed(msg, &block)
131
+ when :configure, :request
132
+ on_status(msg, &block)
133
+ when :release
134
+ on_released(msg, &block)
135
+ end
136
+ end
137
+ end
138
+ end
139
+
140
+ def valid_guard?(guard_proc)
141
+ guard_proc && guard_proc.class == Proc
142
+ end
143
+
144
+ end
145
+ end
146
+ end
147
+ end