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 +2 -0
- data/Gemfile +4 -0
- data/Rakefile +10 -0
- data/lib/omf_oml/endpoint.rb +170 -0
- data/lib/omf_oml/indexed_table.rb +61 -0
- data/lib/omf_oml/network.rb +467 -0
- data/lib/omf_oml/oml_tuple.rb +64 -0
- data/lib/omf_oml/schema.rb +200 -0
- data/lib/omf_oml/sequel/sequel_server.rb +412 -0
- data/lib/omf_oml/sql_row.rb +302 -0
- data/lib/omf_oml/sql_source.rb +131 -0
- data/lib/omf_oml/table.rb +227 -0
- data/lib/omf_oml/tuple.rb +110 -0
- data/lib/omf_oml/version.rb +6 -0
- data/lib/omf_oml.rb +4 -0
- data/omf_oml.gemspec +26 -0
- metadata +73 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -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
|