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