omf_oml 0.9.3

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.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ .project
2
+ pkg
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in omf_web.gemspec
4
+ gemspec
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake/testtask'
2
+ require "bundler/gem_tasks"
3
+
4
+ task :default => :test
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << 'test'
8
+ t.pattern = "test/**/*_spec.rb"
9
+ t.verbose = true
10
+ end
@@ -0,0 +1,170 @@
1
+
2
+ require 'omf_common/lobject'
3
+ require 'omf_oml'
4
+
5
+ require 'omf_oml/oml_tuple'
6
+
7
+ module OMF::OML
8
+
9
+ # This class parses an OML network stream and creates various OML mstreams which can
10
+ # be visualized. After creating the object, the @run@ method needs to be called to
11
+ # start processing the stream.
12
+ #
13
+ class OmlEndpoint < OMF::Common::LObject
14
+
15
+ # Register a proc to be called when a new stream was
16
+ # discovered on this endpoint.
17
+ #
18
+ def on_new_stream(key = :_, &proc)
19
+ if proc
20
+ @on_new_stream_procs[key] = proc
21
+ else
22
+ @on_new_stream_procs.delete key
23
+ end
24
+ end
25
+
26
+ def initialize(port = 5000, host = "0.0.0.0")
27
+ require 'socket'
28
+ @serv = TCPServer.new(host, port)
29
+ @running = false
30
+ @on_new_stream_procs = {}
31
+ end
32
+
33
+ def report_new_stream(name, stream)
34
+ @on_new_stream_procs.each_value do |proc|
35
+ proc.call(name, stream)
36
+ end
37
+ end
38
+
39
+ def run(in_thread = true)
40
+ if in_thread
41
+ Thread.new do
42
+ _run
43
+ end
44
+ else
45
+ _run
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def _run
52
+ @running = true
53
+ while @running do
54
+ sock = @serv.accept
55
+ debug "OML client connected: #{sock}"
56
+
57
+ Thread.new do
58
+ begin
59
+ conn = OmlSession.new(self)
60
+ conn.run(sock)
61
+ debug "OML client disconnected: #{sock}"
62
+ rescue Exception => ex
63
+ error "Exception: #{ex}"
64
+ debug "Exception: #{ex.backtrace.join("\n\t")}"
65
+ ensure
66
+ sock.close
67
+ end
68
+ end
69
+
70
+ end
71
+ end
72
+ end
73
+
74
+ # PRIVATE
75
+ # An instance of this class is created by +OmlEndpoint+ to deal with
76
+ # and individual client connection (socket). An EndPoint is creating
77
+ # and instance and then immediately calls the +run+ methods.
78
+ #
79
+ #
80
+ class OmlSession < OMF::Common::LObject # :nodoc
81
+
82
+ # Return the value for the respective @key@ in the protocol header.
83
+ #
84
+ def [](key)
85
+ @header[key]
86
+ end
87
+
88
+ def initialize(endpoint)
89
+ @endpoint = endpoint
90
+ @header = {}
91
+ @streams = []
92
+ @on_new_stream_procs = {}
93
+ end
94
+
95
+ # This methods blocks until the peer disconnects. Each new stream is reported
96
+ # to the @reportProc@
97
+ #
98
+ def run(socket)
99
+ parse_header(socket)
100
+ parse_rows(socket)
101
+ end
102
+
103
+ private
104
+ def parse_header(socket, &reportStreamProc)
105
+ while (l = socket.gets.strip)
106
+ #puts "H>> '#{l}'"
107
+ return if l.length == 0
108
+
109
+ key, *value = l.split(':')
110
+ if (key == 'schema')
111
+ parse_schema(value.join(':'))
112
+ else
113
+ @header[key] = value[0].strip
114
+ debug "HEADER: #{key}: #{@header[key]}"
115
+ end
116
+ end
117
+ end
118
+
119
+ def parse_schema(desc)
120
+ debug "SCHEMA: #{desc}"
121
+ els = desc.split(' ')
122
+ #puts "ELS: #{els.inspect}"
123
+ index = els.shift.to_i - 1
124
+ sname = els.shift
125
+ schema_desc = els.collect do |el|
126
+ name, type = el.split(':')
127
+ {:name => name.to_sym, :type => type.to_sym}
128
+ end
129
+ schema_desc.insert(0, {:name => :oml_ts, :type => :double})
130
+ schema_desc.insert(1, {:name => :sender_id, :type => :string})
131
+ schema_desc.insert(2, {:name => :oml_seq_no, :type => :integer})
132
+ schema = OMF::OML::OmlSchema.create(schema_desc)
133
+ @streams[index] = tuple = OmlTuple.new(sname, schema)
134
+ @endpoint.report_new_stream(sname, tuple)
135
+ end
136
+
137
+ def parse_rows(socket)
138
+ sender_id = @header['sender-id'] || 'unknown'
139
+ while (l = socket.gets)
140
+ return if l.length == 0
141
+
142
+ els = l.strip.split("\t")
143
+ #puts "R>> '#{els.inspect}'"
144
+ index = els.delete_at(1).to_i - 1
145
+ els.insert(1, sender_id)
146
+ row = @streams[index].parse_tuple(els)
147
+ end
148
+ end
149
+
150
+ end # OMLEndpoint
151
+
152
+
153
+ end
154
+
155
+ if $0 == __FILE__
156
+
157
+ require 'omf_oml/table'
158
+ ep = OMF::OML::OmlEndpoint.new(3000)
159
+ toml = OMF::OML::OmlTable.new('oml', [[:x], [:y]], :max_size => 20)
160
+ ep.on_new_stream() do |s|
161
+ puts "New stream: #{s}"
162
+ s.on_new_vector() do |v|
163
+ puts "New vector: #{v.select(:oml_ts, :value).join('|')}"
164
+ toml.add_row(v.select(:oml_ts, :value))
165
+ end
166
+ end
167
+ ep.run(false)
168
+
169
+ end
170
+
@@ -0,0 +1,61 @@
1
+
2
+ require 'monitor'
3
+
4
+ require 'omf_oml'
5
+ require 'omf_oml/schema'
6
+
7
+
8
+ module OMF::OML
9
+
10
+ # This table maintains the most recently added
11
+ # row with a unique entry in the +index+ column.
12
+ #
13
+ class OmlIndexedTable < OmlTable
14
+
15
+ # Shadow an existing table and maintain an index on 'index_col'.
16
+ #
17
+ # source_table - Table to shadow
18
+ # index_col - Name of column to index on
19
+ #
20
+ def self.shadow(source_table, index_col, &on_before_row_added)
21
+ name = "#{source_table.name}+#{index_col}"
22
+ ix_table = self.new(name, index_col, source_table.schema, &on_before_row_added)
23
+ source_table.on_row_added(self) do |r|
24
+ ix_table.add_row(r)
25
+ end
26
+ ix_table
27
+ end
28
+
29
+ attr_reader :index_col
30
+
31
+ #
32
+ # index_col - Name of column to index
33
+ # schema - Table schema
34
+ #
35
+ def initialize(name, index_col, schema, &on_before_row_added)
36
+ super name, schema, {}, &on_before_row_added
37
+ @index_col = index_col
38
+ @index2row = {} # each row is associated with an instance of the index
39
+ @index = schema.index_for_col(index_col)
40
+ end
41
+
42
+ def _add_row_finally(row)
43
+ key = row[@index]
44
+ row_id = @index2row[key]
45
+ unless row_id
46
+ row_id = @rows.length
47
+ @index2row[key] = row_id
48
+ end
49
+ current_row = @rows[row_id]
50
+ return nil if current_row == row
51
+
52
+ if current_row
53
+ _notify_content_changed(:removed, [current_row])
54
+ end
55
+ @rows[row_id] = row
56
+ return row
57
+ end
58
+
59
+ end # class
60
+
61
+ end
@@ -0,0 +1,467 @@
1
+ require 'monitor'
2
+ require 'json'
3
+ require 'set'
4
+ require 'omf_common/lobject'
5
+ require 'omf_oml'
6
+
7
+
8
+
9
+ module OMF::OML
10
+
11
+ class OmlNetworkAlreadyExistException < Exception; end
12
+ class OmlNodeAlreadyExistException < Exception; end
13
+ class OmlLinkAlreadyExistException < Exception; end
14
+
15
+ class UnknownOmlNodeException < Exception; end
16
+
17
+ class SameNameOnUpdateException < Exception; end
18
+
19
+ # This class represents a network consisting of nodes and links with their respective
20
+ # attributes.
21
+ #
22
+ class OmlNetwork < OMF::Common::LObject
23
+ include MonitorMixin
24
+
25
+ @@name2network = {}
26
+
27
+ # Return a named network
28
+ #
29
+ def self.[](name)
30
+ @@name2network[name]
31
+ end
32
+
33
+ attr_reader :name
34
+
35
+ #
36
+ # name - Name of table
37
+ # opts -
38
+ #
39
+ def initialize(name = nil, attributes = {})
40
+ super name
41
+ @name = name || "nw_#{object_id}"
42
+ @attributes = attributes
43
+ @nodes = {}
44
+ @name2node = {}
45
+ @links = {}
46
+ @name2link = {}
47
+ @epoch = 0 # increment whenever an element is being updated
48
+ @updateListeners = {}
49
+ if name
50
+ synchronize do
51
+ if @@name2network[name]
52
+ raise OmlNetworkAlreadyExistException.new(name)
53
+ end
54
+ @@name2network[name] = self
55
+ end
56
+ end
57
+ end
58
+
59
+ def nodes()
60
+ @nodes.values
61
+ end
62
+
63
+ # Return the node named +name+. If the node doesn't exist and
64
+ # +new_opts+ is a Hash, create a new one and return that.
65
+ #
66
+ def node(name, new_opts = nil)
67
+ return name if name.kind_of? NetworkNode
68
+ node = @name2node[name.to_sym]
69
+ if node.nil? && !new_opts.nil?
70
+ node = create_node(name, new_opts)
71
+ end
72
+ node
73
+ end
74
+
75
+ def links()
76
+ @links.values
77
+ end
78
+
79
+ # Return the link named +name+. If the link doesn't exist and
80
+ # +new_opts+ is a Hash, create a new one and return that.
81
+ #
82
+ def link(name, new_opts = nil)
83
+ return name if name.kind_of? NetworkLink
84
+ link = @name2link[name.to_sym]
85
+ if link.nil? && !new_opts.nil?
86
+ link = create_link(name, nil, nil, new_opts)
87
+ end
88
+ link
89
+ end
90
+
91
+
92
+ # Register a callback to be called every time network elements change
93
+ # The callback is provided with an arrach of changed elements.
94
+ #
95
+ def on_update(name = :_, &callback)
96
+ if (callback)
97
+ if @updateListeners[name]
98
+ throw SameNameOnUpdateException.new(name)
99
+ end
100
+ @updateListeners[name] = callback
101
+ else
102
+ @updateListeners.delete(name)
103
+ end
104
+ end
105
+
106
+ # NOTE: May need a monitor if used in multi-threaded environments
107
+ #
108
+ def create_node(name = nil, attributes = {})
109
+ name = name.to_sym if name
110
+ synchronize do
111
+ if name && @name2node[name]
112
+ raise OmlNodeAlreadyExistException.new(name)
113
+ end
114
+ node = NetworkNode.new(name, attributes, self)
115
+ @nodes[node.el_id] = node
116
+ @name2node[name] = node if name
117
+ node
118
+ end
119
+ end
120
+
121
+ #
122
+ # opts
123
+ # :from - fromNode if +fromNode+ is nil
124
+ # :to - toNode if +toNode+ is nil
125
+ # ... - rest of options passed on to +NetworkLink+ constructor
126
+ #
127
+ def create_link(name = nil, fromNode = nil, toNode = nil, attributes = {})
128
+ name = name.to_sym if name
129
+ fromNode = attributes.delete(:from) unless fromNode
130
+ toNode = attributes.delete(:to) unless toNode
131
+
132
+ synchronize do
133
+ if name && @name2link[name]
134
+ raise OmlLinkAlreadyExistException.new(name)
135
+ end
136
+ if fromNode
137
+ fromNode = node(fromNode) || (raise UnknownOmlNodeException.new(fromNode))
138
+ end
139
+ if toNode
140
+ toNode = node(toNode) || (raise UnknownOmlNodeException.new(toNode))
141
+ end
142
+ link = NetworkLink.new(name, fromNode, toNode, attributes, self)
143
+ @links[link.el_id] = link
144
+ @name2link[name] = link if name
145
+ link
146
+ end
147
+ end
148
+
149
+ # To have the update listeners only called once when multiple elements are changed at once, perform the
150
+ # changes within a +transaction+ block. The listeners are then called once with an array containing
151
+ # all updated elements.
152
+ #
153
+ def transaction(&block)
154
+ updated = UpdateSet.new
155
+ synchronize do
156
+ @updated = updated
157
+
158
+ @in_transaction = true
159
+ block.call
160
+ @in_transaction = true
161
+ end
162
+ unless updated.empty?
163
+ @updateListeners.values.each do |l|
164
+ l.call(updated)
165
+ end
166
+ end
167
+ end
168
+
169
+ def node_schema(schema = nil)
170
+ if schema
171
+ @node_schema = OmlSchema.create(schema)
172
+ @node_schema.insert_column_at(0, :id)
173
+ @node_schema.insert_column_at(1, :name)
174
+ end
175
+ @node_schema
176
+ end
177
+
178
+ def link_schema(schema = nil)
179
+ if schema
180
+ @link_schema = OmlSchema.create(schema)
181
+ @link_schema.insert_column_at(0, :id)
182
+ @link_schema.insert_column_at(1, :name)
183
+ @link_schema.insert_column_at(2, :from_id)
184
+ @link_schema.insert_column_at(3, :to_id)
185
+ end
186
+ @link_schema
187
+ end
188
+
189
+
190
+ def describe
191
+ nh = {}
192
+ @nodes.each do |id, node| nh[id] = node.describe end
193
+ lh = {}
194
+ @links.each do |id, link| lh[id] = link.describe end
195
+ {:nodes => nh, :links => lh}
196
+ end
197
+
198
+ # Creates two tables, one capturing the link state and one for the node state.
199
+ # Returns the two tables in a hash with keys 'nodes' and 'links'.
200
+ #
201
+ # def to_tables(table_opts = {})
202
+ # node_table = OmlTable.new 'nodes', @node_schema, table_opts
203
+ # @nodes.each do |id, n|
204
+ # node_table.add_row @node_schema.hash_to_row(n.attributes)
205
+ # end
206
+ #
207
+ # link_table = OmlTable.new 'links', @link_schema, table_opts
208
+ # @links.each do |id, l|
209
+ # link_table.add_row @link_schema.hash_to_row(l.attributes)
210
+ # end
211
+ #
212
+ # on_update "__to_tables_#{node_table.object_id}" do |a|
213
+ # a.each do |e|
214
+ # if e.kind_of? NetworkNode
215
+ # node_table.add_row @node_schema.hash_to_row(e.attributes)
216
+ # else
217
+ # link_table.add_row @link_schema.hash_to_row(e.attributes)
218
+ # end
219
+ # end
220
+ # end
221
+ # {:nodes => node_table, :links => link_table}
222
+ # #{:nodes => to_table(:nodes, table_opts), :links => to_table(:links, table_opts)}
223
+ # end
224
+
225
+ # Create a table to track an aspect of this network.
226
+ #
227
+ # aspect - Either :nodes or :links
228
+ #
229
+ def to_table(aspect, table_opts = {})
230
+ aspect = aspect.to_sym
231
+ case aspect
232
+ when :nodes
233
+ table = OmlTable.create @name + '/nodes', @node_schema, table_opts
234
+ table.add_rows(@nodes.map do |id, n|
235
+ @node_schema.hash_to_row(n.attributes)
236
+ end)
237
+ on_update "__to_tables_nodes_#{table.object_id}" do |a|
238
+ nodes = a.map do |e|
239
+ e.kind_of?(NetworkNode) ? @node_schema.hash_to_row(e.attributes) : nil
240
+ end.compact
241
+ table.add_rows(nodes) unless nodes.empty?
242
+ end
243
+
244
+ when :links
245
+ table = OmlTable.create @name + '/links', @link_schema, table_opts
246
+ # @links.each do |id, l|
247
+ # table.add_row @link_schema.hash_to_row(l.attributes)
248
+ # end
249
+ table.add_rows(@links.map do |id, n|
250
+ @link_schema.hash_to_row(n.attributes)
251
+ end)
252
+ on_update "__to_tables_links_#{table.object_id}" do |a|
253
+ links = a.map do |e|
254
+ e.kind_of?(NetworkLink) ? @link_schema.hash_to_row(e.attributes) : nil
255
+ end.compact
256
+ table.add_rows(links) unless links.empty?
257
+ end
258
+
259
+ else
260
+ raise "Unknown aspect '#{aspect}'. Should be either 'nodes' or 'links'."
261
+ end
262
+
263
+ # on_update "__to_tables_#{table.object_id}" do |a|
264
+ # a.each do |e|
265
+ # if aspect == :nodes && e.kind_of?(NetworkNode)
266
+ # table.add_row @node_schema.hash_to_row(e.attributes)
267
+ # end
268
+ # if aspect == :links && e.kind_of?(NetworkLink)
269
+ # table.add_row @link_schema.hash_to_row(e.attributes)
270
+ # end
271
+ # end
272
+ # end
273
+
274
+ table
275
+ end
276
+
277
+
278
+ def to_json
279
+ describe.to_json
280
+ end
281
+
282
+
283
+
284
+ def updated(element)
285
+ synchronize do
286
+ if @in_transaction
287
+ @updated << element
288
+ return
289
+ end
290
+ end
291
+ uset = UpdateSet.new
292
+ uset << element
293
+ @updateListeners.each do |l|
294
+ l.call(uset)
295
+ end
296
+ end
297
+
298
+ end # OMLNetwork
299
+
300
+ class NetworkElementAttributeException < Exception; end
301
+
302
+
303
+ # This class represents an abstract network element and shouldn't be used directly.
304
+ #
305
+ class NetworkElement < OMF::Common::LObject
306
+
307
+ attr_reader :name
308
+ attr_reader :el_id
309
+ attr_reader :attributes
310
+
311
+ def initialize(name, attributes, network)
312
+ super name
313
+ @attributes = attributes.dup
314
+ if @name = name
315
+ @attributes[:name] = name
316
+ end
317
+ if attributes.key?(:id) || attributes.key?(:name)
318
+ raise NetworkElementAttributeException.new("Attributes 'id' and 'name' are reserved attributes")
319
+ end
320
+ @el_id = @attributes[:id] = "e#{self.object_id}"
321
+ @attributes[:name] = name || @el_id
322
+
323
+ @network = network
324
+ end
325
+
326
+ def [](name)
327
+ @attributes[name]
328
+ end
329
+
330
+ def []=(name, value)
331
+ @attributes[name] = _set(value, @attributes[name])
332
+ end
333
+
334
+ # Update the element's attributes. The +attributes+ argument
335
+ # is expected to be a hash with the key identifying the attribute
336
+ # name and the value being the new value to set that attribute to.
337
+ #
338
+ def update(attributes)
339
+ attributes.each do |name, value|
340
+ self[name] = value
341
+ end
342
+ end
343
+
344
+ # Return the current state of the network element as hash
345
+ #
346
+ def describe
347
+ @attributes
348
+ end
349
+
350
+ def node?
351
+ false
352
+ end
353
+
354
+ def link?
355
+ false
356
+ end
357
+
358
+ protected
359
+
360
+ def _set(value, old_value)
361
+ if value != old_value
362
+ @network.updated(self)
363
+ end
364
+ value
365
+ end
366
+
367
+ end # NetworkElement
368
+
369
+ # This class represents a network node. Should NOT be created directly, but only through
370
+ # +OmlNetwork#create_node+ method
371
+ #
372
+ class NetworkNode < NetworkElement
373
+
374
+ def initialize(name, attributes, network)
375
+ super
376
+ end
377
+
378
+ def node?
379
+ true
380
+ end
381
+ end # NetworkNode
382
+
383
+ # This class represents a network link between two nodes.
384
+ # Should NOT be created directly, but only through
385
+ # +OmlNetwork#create_node+ method
386
+ #
387
+ class NetworkLink < NetworkElement
388
+ attr_reader :from # node
389
+ attr_reader :to # node
390
+
391
+ def initialize(name, fromNode, toNode, attributes, network)
392
+ super name, attributes, network
393
+ if fromNode
394
+ @fromNode = fromNode
395
+ #puts ">>>> NODE: #{fromNode.inspect}"
396
+ @attributes[:from_id] = fromNode.el_id
397
+ end
398
+ if toNode
399
+ @toNode = toNode
400
+ @attributes[:to_id] = toNode.el_id
401
+ end
402
+ end
403
+
404
+ def from=(node)
405
+ @attributes[:from_id] = node.el_id if node
406
+ @fromNode = _set(node, @fromNode)
407
+ end
408
+
409
+ def to=(node)
410
+ @attributes[:to_id] = node.el_id if node
411
+ @toNode = _set(node, @toNode)
412
+ end
413
+
414
+ def link?
415
+ true
416
+ end
417
+ end # NetworkLink
418
+
419
+ # This set may hold a set of nodes and links which have been
420
+ # updated during a transaction. It supports the +describe+
421
+ # function which returns a domain-specific combine of all the
422
+ # included network elements.
423
+ #
424
+ class UpdateSet < Set
425
+ def describe()
426
+ nh = {}
427
+ lh = {}
428
+
429
+ self.each do |el|
430
+ d = el.describe
431
+ if el.kind_of? NetworkNode
432
+ nh[el.el_id] = d
433
+ else
434
+ lh[el.el_id] = d
435
+ end
436
+ end
437
+ {:nodes => nh, :links => lh}
438
+ end
439
+ end
440
+ end
441
+
442
+ if $0 == __FILE__
443
+ require 'json'
444
+ include OMF::Common::OML
445
+
446
+ nw = OmlNetwork.new
447
+
448
+ cnt = 3
449
+ cnt.times do |i|
450
+ nw.create_node "n#{i}", :x => i
451
+ end
452
+ cnt.times do |i|
453
+ nw.create_link "l#{i}", "n#{i}", "n#{(i + 1) % cnt}", :y => i
454
+ end
455
+
456
+ puts nw.describe.to_json
457
+
458
+ nw.on_update do |els|
459
+ puts "UPDATED: #{els}"
460
+ end
461
+ nw.nodes.first[:x] = 20
462
+
463
+ nw.transaction do
464
+ nw.nodes.first[:x] = 30
465
+ nw.links.first[:y] = 20
466
+ end
467
+ end