omf_oml 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
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