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