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

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