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
@@ -1,3 +1,5 @@
1
+ require 'oml4r'
2
+
1
3
  module OmfCommon
2
4
  module DSL
3
5
  module Xmpp
@@ -0,0 +1,94 @@
1
+ module OmfCommon
2
+ # Providing event loop support.
3
+ class Eventloop
4
+
5
+ @@providers = {
6
+ em: {
7
+ require: 'omf_common/eventloop/em',
8
+ constructor: 'OmfCommon::Eventloop::EventMachine'
9
+ },
10
+ local: {
11
+ require: 'omf_common/eventloop/local_evl',
12
+ constructor: 'OmfCommon::Eventloop::Local'
13
+ }
14
+ }
15
+ @@instance = nil
16
+
17
+ #
18
+ # opts:
19
+ # :type - eventloop provider
20
+ # :provider - custom provider (opts)
21
+ # :require - gem to load first (opts)
22
+ # :constructor - Class implementing provider
23
+ #
24
+ def self.init(opts, &block)
25
+ if @@instance
26
+ raise "Eventloop provider already iniitalised"
27
+ end
28
+ unless provider = opts[:provider]
29
+ provider = @@providers[opts[:type].to_sym]
30
+ end
31
+ unless provider
32
+ raise "Missing Eventloop provider declaration. Either define 'type' or 'provider'"
33
+ end
34
+
35
+ require provider[:require] if provider[:require]
36
+
37
+ if class_name = provider[:constructor]
38
+ provider_class = class_name.split('::').inject(Object) {|c,n| c.const_get(n) }
39
+ inst = provider_class.new(opts, &block)
40
+ else
41
+ raise "Missing provider creation info - :constructor"
42
+ end
43
+ @@instance = inst
44
+ inst
45
+ end
46
+
47
+ def self.instance
48
+ @@instance
49
+ end
50
+
51
+ # Execute block after some time
52
+ #
53
+ # @param [float] delay in sec
54
+ # @param [block] block to execute
55
+ #
56
+ def after(delay_sec, &block)
57
+ raise "Missing implementation 'after'"
58
+ end
59
+
60
+ # Periodically call block every interval_sec
61
+ #
62
+ # @param [float] interval in sec
63
+ # @param [block] block to execute
64
+ #
65
+ def every(interval_sec, &block)
66
+ raise "Missing implementation 'every'"
67
+ end
68
+
69
+ # Block calling thread until eventloop exits
70
+ def join()
71
+ raise "Missing implementation 'join'"
72
+ end
73
+
74
+ def run()
75
+ raise "Missing implementation 'run'"
76
+ end
77
+
78
+ def stop()
79
+ raise "Missing implementation 'stop'"
80
+ end
81
+
82
+ # Calling 'block' before stopping eventloop
83
+ #
84
+ def on_stop(&block)
85
+ warn "Missing implementation 'on_stop'"
86
+ end
87
+
88
+ private
89
+ def initialize(opts = {}, &block)
90
+ #run(&block) if block
91
+ end
92
+
93
+ end
94
+ end
@@ -0,0 +1,57 @@
1
+ require 'eventmachine'
2
+
3
+ module OmfCommon
4
+ class Eventloop
5
+ # Implements a simple eventloop which only deals with timer events
6
+ #
7
+ class EventMachine < Eventloop
8
+
9
+ def initialize(opts = {}, &block)
10
+ super
11
+ @deferred = []
12
+ @deferred << block if block
13
+ end
14
+
15
+ # Execute block after some time
16
+ #
17
+ # @param [Float] delay in sec
18
+ def after(delay_sec, &block)
19
+ if EM.reactor_running?
20
+ EM.add_timer(delay_sec, &block)
21
+ else
22
+ @deferred << lambda do
23
+ EM.add_timer(delay_sec, &block)
24
+ end
25
+ end
26
+ end
27
+
28
+ # Periodically call block every interval_sec
29
+ #
30
+ # @param [Float] interval in sec
31
+ def every(interval_sec, &block)
32
+ if EM.reactor_running?
33
+ EM.add_periodic_timer(interval_sec, &block)
34
+ else
35
+ @deferred << lambda do
36
+ EM.add_periodic_timer(interval_sec, &block)
37
+ end
38
+ end
39
+ end
40
+
41
+ def run(&block)
42
+ EM.run do
43
+ @deferred.each { |proc| proc.call }
44
+ @deferred = nil
45
+ if block
46
+ block.arity == 0 ? block.call : block.call(self)
47
+ end
48
+ end
49
+ end
50
+
51
+ def stop()
52
+ EM.stop
53
+ end
54
+ end # class
55
+ end
56
+ end
57
+
@@ -0,0 +1,78 @@
1
+
2
+
3
+ module OmfCommon
4
+ class Eventloop
5
+ # Implements a simple eventloop which only deals with timer events
6
+ #
7
+ class Local < Eventloop
8
+
9
+ def initialize(opts = {}, &block)
10
+ super
11
+ @tasks = []
12
+ @running = false
13
+ after(0, &block) if block
14
+ end
15
+
16
+ # Execute block after some time
17
+ #
18
+ # @param [float] delay in sec
19
+ # @param [block] block to execute
20
+ #
21
+ def after(delay_sec, &block)
22
+ @tasks << [Time.now + delay_sec, block]
23
+ end
24
+
25
+ # Periodically call block every interval_sec
26
+ #
27
+ # @param [float] interval in sec
28
+ # @param [block] block to execute
29
+ #
30
+ def every(interval_sec, &block)
31
+ @tasks << [Time.now + interval_sec, block, :periodic => interval_sec]
32
+ end
33
+
34
+ def stop
35
+ @running = false
36
+ end
37
+
38
+ def run(&block)
39
+ after(0, &block) if block
40
+ return if @running
41
+ @running = true
42
+
43
+ while @running do
44
+ now = Time.now
45
+ @tasks = @tasks.sort
46
+ while @tasks[0] && @tasks[0][0] <= now
47
+ # execute
48
+ t = @tasks.shift
49
+ debug "Executing Task #{t}"
50
+ block = t[1]
51
+ block.arity == 0 ? block.call : block.call(self)
52
+ now = Time.now
53
+ # Check if periodic
54
+ if interval = ((t[2] || {})[:periodic])
55
+ if (next_time = t[0] + interval) < now
56
+ warn "Falling behind with periodic task #{t[1]}"
57
+ next_time = now + interval
58
+ end
59
+ @tasks << [next_time, t[1], :periodic => interval]
60
+ end
61
+ end
62
+ # by now, there could have been a few more tasks added, so let's sort again
63
+ @tasks = @tasks.sort
64
+ if @tasks.empty?
65
+ # done
66
+ @running = false
67
+ else
68
+ if (delay = @tasks[0][0] - Time.now) > 0
69
+ debug "Sleeping #{delay}"
70
+ sleep delay
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end # class
76
+ end
77
+ end
78
+
@@ -1,293 +1,176 @@
1
- require 'niceogiri'
2
- require 'hashie'
3
- require 'securerandom'
4
- require 'openssl'
5
- require 'cgi'
6
- require 'omf_common/relaxng_schema'
7
-
8
1
  module OmfCommon
9
2
 
10
3
  class MPMessage < OML4R::MPBase
11
4
  name :message
12
5
  param :time, :type => :double
13
6
  param :operation, :type => :string
14
- param :msg_id, :type => :string
15
- param :context_id, :type => :string
7
+ param :mid, :type => :string
8
+ param :cid, :type => :string
16
9
  param :content, :type => :string
17
10
  end
18
11
 
19
- # Refer to resource life cycle, instance methods are basically construct & parse XML fragment
20
- #
21
- # @example To create a valid omf message, e.g. a 'configure' message:
22
- #
23
- # Message.request do |message|
24
- # message.property('os', 'debian')
25
- # message.property('memory', { value: 2, unit: 'gb' })
26
- # end
27
- #
28
- class Message < Niceogiri::XML::Node
12
+ class Message
13
+
29
14
  OMF_NAMESPACE = "http://schema.mytestbed.net/omf/#{OmfCommon::PROTOCOL_VERSION}/protocol"
30
- OPERATION = %w(create configure request release inform)
31
- # When OML instrumentation is enabled, we do not want to send a the same
32
- # measurement twice, once when a message is created for publishing to T,
33
- # and once when this message comes back (as we are also a subscriber of T)
34
- # Thus we keep track of message IDs here (again only when OML is enabled)
35
- @@msg_id_list = []
36
-
37
- class << self
38
- OPERATION.each do |operation|
39
- define_method(operation) do |*args, &block|
40
- xml = new(operation, nil, OMF_NAMESPACE)
41
- if operation == 'inform'
42
- xml.element('context_id', args[1]) if args[1]
43
- xml.element('inform_type', args[0])
44
- else
45
- xml.element('publish_to', args[0]) if args[0]
46
- end
47
- block.call(xml) if block
48
- xml.sign
49
- end
50
- end
15
+ OMF_CORE_READ = [:operation, :ts, :src, :mid, :replyto, :cid, :itype, :rtype, :guard, :res_id]
16
+ OMF_CORE_WRITE = [:replyto, :itype, :guard]
51
17
 
52
- def parse(xml)
53
- raise ArgumentError, 'Can not parse an empty XML into OMF message' if xml.nil? || xml.empty?
54
- xml_root = Nokogiri::XML(xml).root
55
- result = new(xml_root.element_name, nil, xml_root.namespace.href).inherit(xml_root)
56
- if OmfCommon::Measure.enabled? && !@@msg_id_list.include?(result.msg_id)
57
- MPMessage.inject(Time.now.to_f, result.operation.to_s, result.msg_id, result.context_id, result.to_s.gsub("\n",''))
58
- end
59
- result
60
- end
61
- end
18
+ @@providers = {
19
+ xml: {
20
+ require: 'omf_common/message/xml/message',
21
+ constructor: 'OmfCommon::Message::XML::Message'
22
+ },
23
+ json: {
24
+ require: 'omf_common/message/json/json_message',
25
+ constructor: 'OmfCommon::Message::Json::Message'
26
+ }
27
+ }
28
+ @@message_class = nil
62
29
 
63
- # Construct a property xml node
64
- #
65
- def property(key, value = nil)
66
- key_node = Message.new('property')
67
- key_node.write_attr('key', key)
68
-
69
- unless value.nil?
70
- key_node.write_attr('type', ruby_type_2_prop_type(value.class))
71
- c_node = value_node_set(value)
72
-
73
- if c_node.class == Array
74
- c_node.each { |c_n| key_node.add_child(c_n) }
75
- else
76
- key_node.add_child(c_node)
77
- end
78
- end
79
- add_child(key_node)
80
- key_node
30
+ def self.create(type, properties, body = {})
31
+ @@message_class.create(type, properties, body)
81
32
  end
82
33
 
83
- def value_node_set(value, key = nil)
84
- case value
85
- when Hash
86
- [].tap do |array|
87
- value.each_pair do |k, v|
88
- n = Message.new(k)
89
- n.write_attr('type', ruby_type_2_prop_type(v.class))
90
-
91
- c_node = value_node_set(v, k)
92
- if c_node.class == Array
93
- c_node.each { |c_n| n.add_child(c_n) }
94
- else
95
- n.add_child(c_node)
96
- end
97
- array << n
98
- end
99
- end
100
- when Array
101
- value.map do |v|
102
- n = Message.new('item')
103
- n.write_attr('type', ruby_type_2_prop_type(v.class))
104
-
105
- c_node = value_node_set(v, 'item')
106
- if c_node.class == Array
107
- c_node.each { |c_n| n.add_child(c_n) }
108
- else
109
- n.add_child(c_node)
110
- end
111
- n
112
- end
113
- else
114
- if key.nil?
115
- string_value(value)
116
- else
117
- n = Message.new(key)
118
- n.add_child(string_value(value))
119
- end
120
- end
34
+ def self.create_inform_message(itype = nil, properties = {}, body = {})
35
+ body[:itype] = itype if itype
36
+ create(:inform, properties, body)
121
37
  end
122
38
 
123
- # Generate SHA1 of canonicalised xml and write into the ID attribute of the message
39
+ # Create and return a message by parsing 'str'
124
40
  #
125
- def sign
126
- write_attr('msg_id', SecureRandom.uuid)
127
- write_attr('timestamp', Time.now.utc.iso8601)
128
- canonical_msg = self.canonicalize
41
+ def self.parse(str)
42
+ @@message_class.parse(str)
43
+ end
129
44
 
130
- priv_key = OmfCommon::Key.instance.private_key
131
- digest = OpenSSL::Digest::SHA512.new(canonical_msg)
45
+ def self.init(opts = {})
46
+ if @@message_class
47
+ raise "Message provider already iniitalised"
48
+ end
49
+ unless provider = opts[:provider]
50
+ provider = @@providers[opts[:type]]
51
+ end
52
+ unless provider
53
+ raise "Missing Message provider declaration. Either define 'type' or 'provider'"
54
+ end
132
55
 
133
- signature = Base64.encode64(priv_key.sign(digest, canonical_msg)).encode('utf-8') if priv_key
134
- write_attr('digest', digest)
135
- write_attr('signature', signature) if signature
56
+ require provider[:require] if provider[:require]
136
57
 
137
- if OmfCommon::Measure.enabled?
138
- MPMessage.inject(Time.now.to_f, operation.to_s, msg_id, context_id, self.to_s.gsub("\n",''))
139
- @@msg_id_list << msg_id
58
+ if class_name = provider[:constructor]
59
+ @@message_class = class_name.split('::').inject(Object) {|c,n| c.const_get(n) }
60
+ else
61
+ raise "Missing provider class info - :constructor"
140
62
  end
141
- self
142
63
  end
143
64
 
144
- # param :time, :type => :int32
145
- # param :operation, :type => :string
146
- # param :msg_id, :type => :string
147
- # param :context_id, :type => :string
148
- # param :content, :type => :string
65
+ OMF_CORE_READ.each do |pname|
66
+ define_method(pname.to_s) do |*args|
67
+ _get_core(pname)
68
+ end
69
+ end
149
70
 
71
+ alias_method :type, :operation
150
72
 
151
- # Validate against relaxng schema
152
- #
153
- def valid?
154
- validation = RelaxNGSchema.instance.schema.validate(self.document)
155
- if validation.empty?
156
- true
157
- else
158
- logger.error validation.map(&:message).join("\n")
159
- logger.debug self.to_s
160
- false
73
+ OMF_CORE_WRITE.each do |pname|
74
+ define_method("#{pname}=") do |val|
75
+ _set_core(pname.to_sym, val)
161
76
  end
162
77
  end
163
78
 
164
- # Short cut for adding xml node
79
+ # To access properties
165
80
  #
166
- def element(key, value = nil, &block)
167
- key_node = Message.new(key)
168
- add_child(key_node)
169
- if block
170
- block.call(key_node)
171
- else
172
- key_node.content = value if value
173
- end
81
+ # @param [String] name of the property
82
+ # @param [Hash] ns namespace of property
83
+ def [](name, ns = nil)
84
+ _get_property(name.to_sym, ns)
174
85
  end
175
86
 
176
- # The root element_name represents operation
87
+ # To set properties
177
88
  #
178
- def operation
179
- element_name.to_sym
89
+ # @param [String] name of the property
90
+ # @param [Hash] ns namespace of property
91
+ def []=(name, ns = nil, value)
92
+ # TODO why itype cannot be set?
93
+ #raise if name.to_sym == :itype
94
+ if ns
95
+ @props_ns ||= {}
96
+ @props_ns.merge(ns)
97
+ end
98
+ _set_property(name.to_sym, value, ns)
180
99
  end
181
100
 
182
- # Short cut for grabbing a group of nodes using xpath, but with default namespace
183
- def element_by_xpath_with_default_namespace(xpath_without_ns)
184
- xpath(xpath_without_ns.gsub(/(^|\/{1,2})(\w+)/, '\1xmlns:\2'), :xmlns => OMF_NAMESPACE)
101
+ def each_property(&block)
102
+ raise NotImplementedError
185
103
  end
186
104
 
187
- # In case you think method :element_by_xpath_with_default_namespace is too long
105
+ # Loop over all the unbound (sent without a value) properties
106
+ # of a request message.
188
107
  #
189
- alias_method :read_element, :element_by_xpath_with_default_namespace
108
+ def each_unbound_request_property(&block)
109
+ raise NotImplementedError
110
+ end
190
111
 
191
- # We just want to know the content of an non-repeatable element
112
+ # Loop over all the bound (sent with a value) properties
113
+ # of a request message.
192
114
  #
193
- def read_content(element_name)
194
- element_content = read_element("#{element_name}").first.content rescue nil
195
- unless element_content.nil?
196
- element_content.empty? ? nil : element_content
197
- else
198
- nil
199
- end
115
+ def each_bound_request_property(&block)
116
+ raise NotImplementedError
200
117
  end
201
118
 
202
- # Context ID will be requested quite often
203
- def context_id
204
- read_property(:context_id) || read_content(:context_id)
119
+ def has_properties?
120
+ raise NotImplementedError
205
121
  end
206
122
 
207
- # Resource ID is another frequent requested property
208
- def resource_id
209
- read_property(:resource_id) || read_content(:resource_id)
123
+ def resource
124
+ name = _get_property(:res_id)
125
+ OmfCommon.comm.create_topic(name)
210
126
  end
211
127
 
212
- def publish_to
213
- read_property(:publish_to) || read_content(:publish_to)
128
+ def success?
129
+ ! error?
214
130
  end
215
131
 
216
- def msg_id
217
- read_attr('msg_id')
132
+ def error?
133
+ (itype || '') =~ /(error|ERROR|FAILED)/
218
134
  end
219
135
 
220
- # Get a property by key
221
- #
222
- # @param [String] key name of the property element
223
- # @return [Object] the content of the property, as string, integer, float, or mash(hash with indifferent access)
224
- #
225
- def read_property(key, data_binding = nil)
226
- key = key.to_s
227
- e = read_element("property[@key='#{key}']").first
228
- reconstruct_data(e, data_binding) if e
136
+ def create_inform_reply_message(itype = nil, properties = {}, body = {})
137
+ body[:cid] = self.mid
138
+ self.class.create_inform_message(itype, properties, body)
229
139
  end
230
140
 
231
- def reconstruct_data(node, data_binding = nil)
232
- node_type = node.attr('type')
233
- case node_type
234
- when 'array'
235
- node.element_children.map do |child|
236
- reconstruct_data(child, data_binding)
237
- end
238
- when /hash/
239
- mash ||= Hashie::Mash.new
240
- node.element_children.each do |child|
241
- mash[child.attr('key') || child.element_name] ||= reconstruct_data(child, data_binding)
242
- end
243
- mash
244
- when /boolean/
245
- node.content == "true"
246
- else
247
- if node.content.empty?
248
- nil
249
- elsif data_binding && node_type == 'string'
250
- ERB.new(node.content).result(data_binding)
251
- else
252
- node.content.ducktype
253
- end
254
- end
141
+ def to_s
142
+ raise NotImplementedError
255
143
  end
256
144
 
257
- # Iterate each property element
258
- #
259
- def each_property(&block)
260
- read_element("property").each { |v| block.call(v) }
145
+ def marshall
146
+ raise NotImplementedError
261
147
  end
262
148
 
263
- # Pretty print for application event message
264
- #
265
- def print_app_event
266
- "APP_EVENT (#{read_property(:app)}, ##{read_property(:seq)}, #{read_property(:event)}): #{read_property(:msg)}"
149
+ def valid?
150
+ raise NotImplementedError
151
+ end
152
+
153
+ # Construct default namespace of the props from resource type
154
+ def default_props_ns
155
+ resource_type = _get_core(:rtype)
156
+ resource_type ? { resource_type.to_s => "#{OMF_NAMESPACE}/#{resource_type}" } : {}
157
+ end
158
+
159
+ # Get all property namespace defs
160
+ def props_ns
161
+ @props_ns ||= {}
162
+ default_props_ns.merge(@props_ns)
267
163
  end
268
164
 
269
165
  private
270
166
 
271
- def ruby_type_2_prop_type(ruby_class_type)
272
- v_type = ruby_class_type.to_s.downcase
273
- case v_type
274
- when *%w(trueclass falseclass)
275
- 'boolean'
276
- when *%w(fixnum bignum)
277
- 'integer'
278
- else
279
- v_type
280
- end
167
+ def _get_property(name, ns = nil)
168
+ raise NotImplementedError
281
169
  end
282
170
 
283
- # Get string of a value object, escape if object is string
284
- def string_value(value)
285
- if value.kind_of? String
286
- value = CGI::escape_html(value)
287
- else
288
- value = value.to_s
289
- end
290
- value
171
+ def _set_property(name, value, ns = nil)
172
+ raise NotImplementedError
291
173
  end
292
174
  end
175
+
293
176
  end