omf_sfa 0.2.5 → 0.2.6
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/{examples → bin}/brite2rspec.rb +0 -0
- data/lib/omf-sfa/am/am-rest/api_template.html +1 -0
- data/lib/omf-sfa/am/am-rest/rest_handler.rb +143 -48
- data/lib/omf-sfa/resource/channel.rb +1 -1
- data/lib/omf-sfa/resource/link.rb +17 -2
- data/lib/omf-sfa/resource/node.rb +4 -1
- data/lib/omf-sfa/resource/oproperty.rb +127 -65
- data/lib/omf-sfa/resource/oresource.rb +2 -1
- data/lib/omf-sfa/resource/sfa_base.rb +14 -0
- data/lib/omf-sfa/util/brite_parser.rb +160 -0
- data/lib/omf-sfa/util/graph_json.rb +277 -0
- data/lib/omf-sfa/version.rb +1 -1
- data/omf_sfa.gemspec +4 -0
- metadata +38 -3
File without changes
|
@@ -5,6 +5,7 @@ require 'uuid'
|
|
5
5
|
require 'set'
|
6
6
|
require 'json'
|
7
7
|
require 'thin/async'
|
8
|
+
require 'cgi'
|
8
9
|
|
9
10
|
require 'omf_base/lobject'
|
10
11
|
|
@@ -57,8 +58,8 @@ module OMF::SFA::AM::Rest
|
|
57
58
|
end
|
58
59
|
|
59
60
|
class UnsupportedMethodException < RackException
|
60
|
-
def initialize()
|
61
|
-
super 403, "Unsupported Method"
|
61
|
+
def initialize(method_name = nil, class_name = nil)
|
62
|
+
super 403, "Unsupported Method '#{class_name || 'Unknown'}::#{method_name || 'Unknown'}'"
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
@@ -82,6 +83,18 @@ module OMF::SFA::AM::Rest
|
|
82
83
|
end
|
83
84
|
end
|
84
85
|
|
86
|
+
# Raised when a request triggers an async call whose
|
87
|
+
# result we need before answering the request
|
88
|
+
#
|
89
|
+
class RetryLaterException < Exception
|
90
|
+
attr_reader :delay
|
91
|
+
|
92
|
+
def initialize(delay = 3)
|
93
|
+
@delay = delay
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
|
85
98
|
|
86
99
|
class RestHandler < OMF::Base::LObject
|
87
100
|
@@service_name = nil
|
@@ -103,6 +116,19 @@ module OMF::SFA::AM::Rest
|
|
103
116
|
self.new().render_html(parts)
|
104
117
|
end
|
105
118
|
|
119
|
+
# Parse format of 'resource_uri' and (re)turn into a
|
120
|
+
# description hash (optionally provided).
|
121
|
+
#
|
122
|
+
def self.parse_resource_uri(resource_uri, description = {})
|
123
|
+
if UUID.validate(resource_uri)
|
124
|
+
description[:uuid] = resource_uri
|
125
|
+
elsif resource_uri.start_with? 'urn:'
|
126
|
+
description[:urn] = resource_uri
|
127
|
+
else
|
128
|
+
description[:name] = resource_uri
|
129
|
+
end
|
130
|
+
description
|
131
|
+
end
|
106
132
|
|
107
133
|
def initialize(opts = {})
|
108
134
|
@opts = opts
|
@@ -112,12 +138,13 @@ module OMF::SFA::AM::Rest
|
|
112
138
|
begin
|
113
139
|
Thread.current[:http_host] = env["HTTP_HOST"]
|
114
140
|
req = ::Rack::Request.new(env)
|
141
|
+
headers = {
|
142
|
+
'Access-Control-Allow-Origin' => '*',
|
143
|
+
'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS',
|
144
|
+
'Access-Control-Allow-Headers' => 'origin, x-csrftoken, content-type, accept'
|
145
|
+
}
|
115
146
|
if req.request_method == 'OPTIONS'
|
116
|
-
return [200 ,
|
117
|
-
'Access-Control-Allow-Origin' => '*' ,
|
118
|
-
'Access-Control-Allow-Methods' => 'GET, POST, OPTIONS',
|
119
|
-
'Access-Control-Allow-Headers' => 'origin, x-csrftoken, content-type, accept'
|
120
|
-
}, ""]
|
147
|
+
return [200 , headers, ""]
|
121
148
|
end
|
122
149
|
content_type, body = dispatch(req)
|
123
150
|
if body.is_a? Thin::AsyncResponse
|
@@ -131,7 +158,8 @@ module OMF::SFA::AM::Rest
|
|
131
158
|
body = JSON.pretty_generate(body)
|
132
159
|
end
|
133
160
|
#return [200 ,{'Content-Type' => content_type}, body + "\n"]
|
134
|
-
|
161
|
+
headers['Content-Type'] = content_type
|
162
|
+
return [200 , headers, body + "\n"]
|
135
163
|
rescue RackException => rex
|
136
164
|
return rex.reply
|
137
165
|
rescue RedirectException => rex
|
@@ -139,16 +167,33 @@ module OMF::SFA::AM::Rest
|
|
139
167
|
return [301, {'Location' => rex.path, "Content-Type" => ""}, ['Next window!']]
|
140
168
|
# rescue OMF::SFA::AM::AMManagerException => aex
|
141
169
|
# return RackException.new(400, aex.to_s).reply
|
170
|
+
rescue RetryLaterException => rex
|
171
|
+
body = {
|
172
|
+
type: 'retry',
|
173
|
+
delay: rex.delay
|
174
|
+
}
|
175
|
+
debug "Retry later request"
|
176
|
+
if req['_format'] == 'html'
|
177
|
+
headers['Refresh'] = rex.delay.to_s
|
178
|
+
opts = {} #{html_header: "<META HTTP-EQUIV='refresh' CONTENT='#{rex.delay}'>"}
|
179
|
+
body = convert_to_html(body, env, opts)
|
180
|
+
return [200 , headers, body + "\n"]
|
181
|
+
end
|
182
|
+
headers['Content-Type'] = 'application/json'
|
183
|
+
return [504, headers, JSON.pretty_generate(body)]
|
184
|
+
|
142
185
|
rescue Exception => ex
|
143
186
|
body = {
|
144
|
-
:error
|
145
|
-
|
146
|
-
:
|
187
|
+
type: 'error',
|
188
|
+
error: {
|
189
|
+
reason: ex.to_s,
|
190
|
+
bt: ex.backtrace #.select {|l| !l.start_with?('/') }
|
147
191
|
}
|
148
192
|
}
|
149
193
|
warn "ERROR: #{ex}"
|
150
194
|
debug ex.backtrace.join("\n")
|
151
|
-
|
195
|
+
headers['Content-Type'] = 'application/json'
|
196
|
+
return [500, headers, JSON.pretty_generate(body)]
|
152
197
|
end
|
153
198
|
end
|
154
199
|
|
@@ -168,7 +213,7 @@ module OMF::SFA::AM::Rest
|
|
168
213
|
#debug 'POST(', resource_uri, '): body(', format, '): "', description, '"'
|
169
214
|
|
170
215
|
if resource = opts[:resource]
|
171
|
-
debug 'POST: Modify ', resource
|
216
|
+
debug 'POST: Modify ', resource, ' --- ', resource.class
|
172
217
|
modify_resource(resource, description, opts)
|
173
218
|
else
|
174
219
|
if description.is_a? Array
|
@@ -179,13 +224,13 @@ module OMF::SFA::AM::Rest
|
|
179
224
|
return show_resources(resources, nil, opts)
|
180
225
|
else
|
181
226
|
debug 'POST: Create ', resource_uri
|
182
|
-
if resource_uri
|
183
|
-
if UUID.validate(resource_uri)
|
184
|
-
description[:uuid] = resource_uri
|
185
|
-
else
|
186
|
-
description[:name] = resource_uri
|
187
|
-
end
|
188
|
-
end
|
227
|
+
# if resource_uri
|
228
|
+
# if UUID.validate(resource_uri)
|
229
|
+
# description[:uuid] = resource_uri
|
230
|
+
# else
|
231
|
+
# description[:name] = resource_uri
|
232
|
+
# end
|
233
|
+
# end
|
189
234
|
resource = create_resource(description, opts, resource_uri)
|
190
235
|
end
|
191
236
|
end
|
@@ -200,6 +245,7 @@ module OMF::SFA::AM::Rest
|
|
200
245
|
end
|
201
246
|
|
202
247
|
def on_delete(resource_uri, opts)
|
248
|
+
res = ['application/json', {}]
|
203
249
|
if resource = opts[:resource]
|
204
250
|
if (context = opts[:context])
|
205
251
|
remove_resource_from_context(resource, context)
|
@@ -210,17 +256,21 @@ module OMF::SFA::AM::Rest
|
|
210
256
|
resource.destroy
|
211
257
|
end
|
212
258
|
else
|
213
|
-
|
214
|
-
raise OMF::SFA::AM::Rest::BadRequestException.new "I'm sorry, Dave. I'm afraid I can't do that."
|
259
|
+
res = on_delete_all(opts) || res
|
215
260
|
end
|
216
|
-
|
217
|
-
return res
|
261
|
+
res
|
218
262
|
end
|
219
263
|
|
264
|
+
def on_delete_all(opts)
|
265
|
+
# Delete ALL resources of this type
|
266
|
+
raise OMF::SFA::AM::Rest::BadRequestException.new "I'm sorry, Dave. I'm afraid I can't do that."
|
267
|
+
end
|
220
268
|
|
221
269
|
def find_handler(path, opts)
|
222
|
-
debug "find_handler: path; '#{path}' opts: #{opts}"
|
223
|
-
|
270
|
+
#debug "find_handler: path; '#{path}' opts: #{opts}"
|
271
|
+
debug "find_handler: path; '#{path}'"
|
272
|
+
rid = path.shift
|
273
|
+
resource_id = opts[:resource_uri] = (rid ? URI.decode(rid) : nil) # make sure we get rid of any URI encoding
|
224
274
|
opts[:resource] = nil
|
225
275
|
if resource_id
|
226
276
|
resource = opts[:resource] = find_resource(resource_id, {}, opts)
|
@@ -231,7 +281,8 @@ module OMF::SFA::AM::Rest
|
|
231
281
|
opts[:context] = resource
|
232
282
|
comp = path.shift
|
233
283
|
if (handler = @coll_handlers[comp.to_sym])
|
234
|
-
opts[:
|
284
|
+
opts[:context_name] = comp
|
285
|
+
opts[:resource_uri] = URI.decode(path.join('/'))
|
235
286
|
if handler.is_a? Proc
|
236
287
|
return handler.call(path, opts)
|
237
288
|
end
|
@@ -250,30 +301,38 @@ module OMF::SFA::AM::Rest
|
|
250
301
|
end
|
251
302
|
description.delete(:href)
|
252
303
|
resource.update(description) ? resource : nil
|
253
|
-
#raise UnsupportedMethodException.new
|
254
304
|
end
|
255
305
|
|
256
306
|
|
257
307
|
def create_resource(description, opts, resource_uri = nil)
|
258
|
-
|
308
|
+
debug "Create: uri: '#{resource_uri.inspect}' class: #{description.class}--#{description}"
|
259
309
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
310
|
+
query = {}
|
311
|
+
unless (resource_uri || '').empty?
|
312
|
+
query = self.class.parse_resource_uri(resource_uri, query)
|
313
|
+
description.merge!(query)
|
314
|
+
else
|
315
|
+
[:uuid, :urn, :name].each do |k|
|
316
|
+
if v = description[k]
|
317
|
+
query[k] = v
|
318
|
+
end
|
265
319
|
end
|
266
320
|
end
|
267
321
|
|
268
322
|
# Let's find if the resource already exists. If yes, just modify it
|
269
|
-
if
|
270
|
-
debug 'Trying to find resource ', uuid, "'"
|
271
|
-
resource = @resource_class.first(uuid: uuid)
|
323
|
+
# if description[:uuid] || descri
|
324
|
+
# debug 'Trying to find resource ', uuid, "'"
|
325
|
+
# resource = @resource_class.first(uuid: uuid)
|
326
|
+
# end
|
327
|
+
unless query.empty?
|
328
|
+
debug 'Trying to find "', @resource_class, '" ', query, "'"
|
329
|
+
resource = @resource_class.first(query)
|
272
330
|
end
|
273
331
|
if resource
|
274
332
|
modify_resource(resource, description, opts)
|
275
333
|
else
|
276
|
-
resource
|
334
|
+
debug 'Trying to create resource ', @resource_class, ' - ', description
|
335
|
+
resource = _really_create_resource(description, opts)
|
277
336
|
on_new_resource(resource)
|
278
337
|
end
|
279
338
|
if (context = opts[:context])
|
@@ -282,6 +341,25 @@ module OMF::SFA::AM::Rest
|
|
282
341
|
return resource
|
283
342
|
end
|
284
343
|
|
344
|
+
def _really_create_resource(description, opts)
|
345
|
+
@resource_class.create(description)
|
346
|
+
end
|
347
|
+
|
348
|
+
# Parse format of 'resource_uri' and (re)turn into a
|
349
|
+
# description hash (optionally provided).
|
350
|
+
#
|
351
|
+
def _parse_resource_uri(resource_uri, description = {})
|
352
|
+
if UUID.validate(resource_uri)
|
353
|
+
description[:uuid] = resource_uri
|
354
|
+
elsif resource_uri.start_with? 'urn:'
|
355
|
+
description[:urn] = resource_uri
|
356
|
+
else
|
357
|
+
description[:name] = resource_uri
|
358
|
+
end
|
359
|
+
description
|
360
|
+
end
|
361
|
+
|
362
|
+
|
285
363
|
# Can be used to further customize a newly created
|
286
364
|
# resource.
|
287
365
|
#
|
@@ -289,12 +367,12 @@ module OMF::SFA::AM::Rest
|
|
289
367
|
debug "Created: #{resource}"
|
290
368
|
end
|
291
369
|
|
292
|
-
def add_resource_to_context(
|
293
|
-
raise UnsupportedMethodException.new
|
370
|
+
def add_resource_to_context(resource, context)
|
371
|
+
raise UnsupportedMethodException.new(:add_resource_to_context, self.class)
|
294
372
|
end
|
295
373
|
|
296
|
-
def remove_resource_from_context(
|
297
|
-
raise UnsupportedMethodException.new
|
374
|
+
def remove_resource_from_context(resource, context)
|
375
|
+
raise UnsupportedMethodException.new(:remove_resource_from_context, self.class)
|
298
376
|
end
|
299
377
|
|
300
378
|
|
@@ -305,6 +383,7 @@ module OMF::SFA::AM::Rest
|
|
305
383
|
# store them in +opts+.
|
306
384
|
#
|
307
385
|
def populate_opts(req, opts)
|
386
|
+
opts[:req] = req
|
308
387
|
path = req.path_info.split('/').select { |p| !p.empty? }
|
309
388
|
opts[:target] = find_handler(path, opts)
|
310
389
|
rl = req.params.delete('_level')
|
@@ -411,12 +490,12 @@ module OMF::SFA::AM::Rest
|
|
411
490
|
descr.delete(:resource_uri)
|
412
491
|
if UUID.validate(resource_uri)
|
413
492
|
descr[:uuid] = resource_uri
|
493
|
+
elsif resource_uri.start_with?('urn')
|
494
|
+
descr[:urn] = resource_uri
|
414
495
|
else
|
415
496
|
descr[:name] = resource_uri
|
416
497
|
end
|
417
|
-
|
418
|
-
descr[:urn] = resource_uri
|
419
|
-
end
|
498
|
+
|
420
499
|
#authenticator = Thread.current["authenticator"]
|
421
500
|
debug "Finding #{@resource_class}.first(#{descr})"
|
422
501
|
@resource_class.first(descr)
|
@@ -424,7 +503,11 @@ module OMF::SFA::AM::Rest
|
|
424
503
|
|
425
504
|
def show_resource_list(opts)
|
426
505
|
# authenticator = Thread.current["authenticator"]
|
427
|
-
|
506
|
+
if (context = opts[:context])
|
507
|
+
resources = context.send(opts[:context_name].to_sym)
|
508
|
+
else
|
509
|
+
resources = @resource_class.all()
|
510
|
+
end
|
428
511
|
show_resources(resources, nil, opts)
|
429
512
|
end
|
430
513
|
|
@@ -433,9 +516,10 @@ module OMF::SFA::AM::Rest
|
|
433
516
|
hopts = {max_level: opts[:max_level], level: 0}
|
434
517
|
objs = {}
|
435
518
|
res_hash = resources.map do |a|
|
519
|
+
next unless a # TODO: This seems to be a bug in OProperty (removing objects)
|
436
520
|
a.to_hash(objs, hopts)
|
437
521
|
#a.to_hash_brief(:href_use_class_prefix => true)
|
438
|
-
end
|
522
|
+
end.compact
|
439
523
|
if resource_name
|
440
524
|
prefix = about = opts[:req].path
|
441
525
|
res = {
|
@@ -488,6 +572,7 @@ module OMF::SFA::AM::Rest
|
|
488
572
|
# Render an HTML page using the resource's template. The
|
489
573
|
# template is populated with information provided in 'parts'
|
490
574
|
#
|
575
|
+
# * :header - HTML header additions
|
491
576
|
# * :title - HTML title
|
492
577
|
# * :service - Service path (usually a set of <a>)
|
493
578
|
# * :content - Main content
|
@@ -497,6 +582,9 @@ module OMF::SFA::AM::Rest
|
|
497
582
|
def render_html(parts = {})
|
498
583
|
#puts "PP>> #{parts}"
|
499
584
|
tmpl = html_template()
|
585
|
+
if (header = parts[:header])
|
586
|
+
tmpl = tmpl.gsub('##HEADER##', header)
|
587
|
+
end
|
500
588
|
if (result = parts[:result])
|
501
589
|
tmpl = tmpl.gsub('##JS##', JSON.pretty_generate(result))
|
502
590
|
end
|
@@ -532,6 +620,7 @@ module OMF::SFA::AM::Rest
|
|
532
620
|
_convert_obj_to_html(body, nil, res, opts)
|
533
621
|
|
534
622
|
render_html(
|
623
|
+
header: opts[:html_header] || '',
|
535
624
|
result: body,
|
536
625
|
title: @@service_name || env["HTTP_HOST"],
|
537
626
|
service: h2.join('/'),
|
@@ -546,7 +635,7 @@ module OMF::SFA::AM::Rest
|
|
546
635
|
protected
|
547
636
|
def _convert_obj_to_html(obj, ref_name, res, opts)
|
548
637
|
klass = obj.class
|
549
|
-
#puts "CONVERT>>>> #{obj.class}::#{obj}"
|
638
|
+
#puts "CONVERT>>>> #{ref_name} ... #{obj.class}::#{obj.to_s[0 .. 80]}"
|
550
639
|
if (obj.is_a? OMF::SFA::Resource::OPropertyArray) || obj.is_a?(Array)
|
551
640
|
if obj.empty?
|
552
641
|
res << '<span class="empty">empty</span>'
|
@@ -563,6 +652,9 @@ module OMF::SFA::AM::Rest
|
|
563
652
|
if obj.to_s.start_with? 'http://'
|
564
653
|
res << _convert_link_to_html(obj)
|
565
654
|
else
|
655
|
+
if obj.is_a? String
|
656
|
+
obj = CGI.escapeHTML obj
|
657
|
+
end
|
566
658
|
res << " <span class='value'>#{obj}</span> "
|
567
659
|
end
|
568
660
|
end
|
@@ -573,6 +665,9 @@ module OMF::SFA::AM::Rest
|
|
573
665
|
array.each do |obj|
|
574
666
|
#puts "AAA>>>> #{obj}::#{opts}"
|
575
667
|
name = nil
|
668
|
+
if (obj.is_a? OMF::SFA::Resource::OResource)
|
669
|
+
obj = obj.to_hash()
|
670
|
+
end
|
576
671
|
if obj.is_a? Hash
|
577
672
|
if name = obj[:name] || obj[:uuid]
|
578
673
|
res << "<li><span class='key'>#{_convert_link_to_html obj[:href], name}:</span>"
|
@@ -15,7 +15,7 @@ module OMF::SFA::Resource
|
|
15
15
|
sfa_class 'channel', :namespace => :ol
|
16
16
|
sfa :number, :attribute => true
|
17
17
|
sfa :frequency, :attribute => true
|
18
|
-
sfa :interfaces, :inline => true, :has_many => true
|
18
|
+
sfa :interfaces, :inline => true, :has_many => true, :inverse => :channel
|
19
19
|
|
20
20
|
def _from_sfa_interfaces_property_xml(resource_el, props, context)
|
21
21
|
resource_el.children.each do |el|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
|
2
|
-
|
2
|
+
require 'set'
|
3
3
|
require 'omf-sfa/resource/channel'
|
4
4
|
#require 'omf-sfa/resource/link_property'
|
5
5
|
|
@@ -21,10 +21,25 @@ module OMF::SFA::Resource
|
|
21
21
|
oproperty :packet_loss, Float # 0 .. 1
|
22
22
|
|
23
23
|
sfa_class 'link'
|
24
|
-
sfa :link_type, :content_attribute => :name
|
24
|
+
sfa :link_type, attr_value: 'name' #:content_attribute => :name
|
25
|
+
sfa :component_manager
|
25
26
|
sfa :property # sets capacity, latency and packet_loss
|
26
27
|
#sfa :properties, LinkProperty, :inline => true, :has_many => true
|
27
28
|
|
29
|
+
#Override xml serialization of 'component_manager'
|
30
|
+
def _to_sfa_xml_component_manager(res_el, pdef, obj2id, opts)
|
31
|
+
cms = Set.new
|
32
|
+
self.interfaces.each do |ifs|
|
33
|
+
cms << ifs.node.component_manager
|
34
|
+
end
|
35
|
+
cms.each do |cm|
|
36
|
+
next unless cm # not sure if 'nil' could show up her
|
37
|
+
el = res_el.add_child(Nokogiri::XML::Element.new('component_manager', res_el.document))
|
38
|
+
el.set_attribute('name', cm)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
28
43
|
#Override xml serialization of 'property'
|
29
44
|
def _to_sfa_xml_property(res_el, pdef, obj2id, opts)
|
30
45
|
capacity = self.capacity
|
@@ -9,16 +9,18 @@ module OMF::SFA::Resource
|
|
9
9
|
class Node < OComponent
|
10
10
|
|
11
11
|
|
12
|
+
oproperty :component_manager, String, :required => false
|
12
13
|
oproperty :hardware_type, String, :required => false
|
13
14
|
oproperty :available, Boolean, :default => true
|
14
15
|
oproperty :sliver_type, SliverType, :required => false # Is this required?
|
15
|
-
oproperty :interfaces, :interface, :functional => false
|
16
|
+
oproperty :interfaces, :interface, :functional => false, :inverse => :node
|
16
17
|
oproperty :exclusive, Boolean, :default => true
|
17
18
|
oproperty :services, OMF::SFA::Resource::AbstractService, functional: false
|
18
19
|
|
19
20
|
#belongs_to :sliver
|
20
21
|
|
21
22
|
sfa_class 'node'
|
23
|
+
sfa :component_manager, attr_value: 'name'
|
22
24
|
sfa :hardware_type, :inline => true, :has_many => true
|
23
25
|
sfa :available, attr_value: 'now', in_request: false # <available now="true">
|
24
26
|
#sfa :sliver_type, :attr_value => 'name'
|
@@ -28,6 +30,7 @@ module OMF::SFA::Resource
|
|
28
30
|
sfa :services, :has_many => true
|
29
31
|
|
30
32
|
|
33
|
+
|
31
34
|
# Override xml serialization of 'interface'
|
32
35
|
def _to_sfa_property_xml(pname, value, res_el, pdef, obj2id, opts)
|
33
36
|
if pname == 'interfaces'
|
@@ -8,26 +8,26 @@ require 'omf_base/lobject'
|
|
8
8
|
# and overrides Time objects serialization. We want 'JSON.load' to return actual Time
|
9
9
|
# objects instead of Strings.
|
10
10
|
#
|
11
|
-
class Time
|
12
|
-
def to_json(*args)
|
13
|
-
{
|
14
|
-
JSON.create_id => self.class.name,
|
15
|
-
's' => tv_sec,
|
16
|
-
'n' => respond_to?(:tv_nsec) ? tv_nsec : tv_usec * 1000
|
17
|
-
}.to_json(*args)
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.json_create(object)
|
21
|
-
if usec = object.delete('u') # used to be tv_usec -> tv_nsec
|
22
|
-
object['n'] = usec * 1000
|
23
|
-
end
|
24
|
-
if instance_methods.include?(:tv_nsec)
|
25
|
-
at(object['s'], Rational(object['n'], 1000))
|
26
|
-
else
|
27
|
-
at(object['s'], object['n'] / 1000)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
11
|
+
# class Time
|
12
|
+
# def to_json(*args)
|
13
|
+
# {
|
14
|
+
# JSON.create_id => self.class.name,
|
15
|
+
# 's' => tv_sec,
|
16
|
+
# 'n' => respond_to?(:tv_nsec) ? tv_nsec : tv_usec * 1000
|
17
|
+
# }.to_json(*args)
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# def self.json_create(object)
|
21
|
+
# if usec = object.delete('u') # used to be tv_usec -> tv_nsec
|
22
|
+
# object['n'] = usec * 1000
|
23
|
+
# end
|
24
|
+
# if instance_methods.include?(:tv_nsec)
|
25
|
+
# at(object['s'], Rational(object['n'], 1000))
|
26
|
+
# else
|
27
|
+
# at(object['s'], object['n'] / 1000)
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
# end
|
31
31
|
|
32
32
|
#raise "JSON deserialisation no longer working - require 'json' early" unless JSON.load(Time.now.to_json).is_a? Time
|
33
33
|
|
@@ -42,35 +42,27 @@ module OMF::SFA::Resource
|
|
42
42
|
|
43
43
|
property :name, String
|
44
44
|
property :type, String, length: 2
|
45
|
-
property :s_value,
|
45
|
+
property :s_value, Text, :lazy => false # actually serialized Object
|
46
46
|
property :n_value, Float
|
47
47
|
|
48
48
|
belongs_to :o_resource
|
49
49
|
|
50
|
-
def self.
|
51
|
-
|
52
|
-
|
53
|
-
h =
|
54
|
-
|
55
|
-
i += 1
|
56
|
-
if (val = h[:v]).is_a? String
|
57
|
-
val = "'#{val}'"
|
58
|
-
end
|
59
|
-
"#{tbl}.#{h[:f]} #{h[:t] == 's' ? 'LIKE' : '='} #{val}"
|
60
|
-
end
|
61
|
-
i.times do |j|
|
62
|
-
where << "r.id = p#{j}.o_resource_id"
|
50
|
+
def self.create(attr = {})
|
51
|
+
if (value = attr.delete(:value))
|
52
|
+
h = self._analyse_value(value)
|
53
|
+
attr[h[:f]] = h[:v]
|
54
|
+
attr[:type] = h[:t]
|
63
55
|
end
|
64
|
-
|
56
|
+
super
|
57
|
+
end
|
65
58
|
|
66
|
-
|
67
|
-
from =
|
68
|
-
from << "omf_sfa_resource_o_resources AS r" # TODO: Shouldn't hard-code that
|
59
|
+
def self.prop_all(query, opts = {}, resource_class = nil)
|
60
|
+
from, where = self._build_query(query, resource_class)
|
69
61
|
q = "SELECT DISTINCT r.id, r.type, r.uuid, r.name FROM #{from.join(', ')} WHERE #{where.join(' AND ')};"
|
70
62
|
if l = opts[:limit]
|
71
63
|
q += " LIMIT #{l} OFFSET #{opts[:offset] || 0}"
|
72
64
|
end
|
73
|
-
debug "prop_all q: #{q}"
|
65
|
+
#debug "prop_all q: #{q}"
|
74
66
|
res = repository(:default).adapter.select(q)
|
75
67
|
ores = res.map do |qr|
|
76
68
|
if resource_class
|
@@ -83,6 +75,63 @@ module OMF::SFA::Resource
|
|
83
75
|
ores
|
84
76
|
end
|
85
77
|
|
78
|
+
def self.count(query)
|
79
|
+
from, where = self._build_query(query)
|
80
|
+
q = "SELECT COUNT(r.id) FROM #{from.join(', ')} WHERE #{where.join(' AND ')};"
|
81
|
+
res = repository(:default).adapter.select(q)
|
82
|
+
#debug "count res: #{res} q: #{query} sql: #{q}"
|
83
|
+
res[0]
|
84
|
+
end
|
85
|
+
|
86
|
+
# Query:
|
87
|
+
# resource: Resource to find properties for
|
88
|
+
# name: Name of property
|
89
|
+
# value: Value of property
|
90
|
+
#
|
91
|
+
# Return the FROM and WHERE elements for 'query'
|
92
|
+
#
|
93
|
+
# TODO: THIS IS REALLY BROKEN! Need to define what a query is in this context
|
94
|
+
#
|
95
|
+
def self._build_query(query, resource_class = nil)
|
96
|
+
i = 0
|
97
|
+
where = query.map do |pn, v|
|
98
|
+
tbl = "p#{i}"
|
99
|
+
case pn.to_sym
|
100
|
+
when :resource
|
101
|
+
unless v.is_a? OResource
|
102
|
+
raise "Expected type OResource for :resource, but got '#{v}::#{v.class}'"
|
103
|
+
end
|
104
|
+
"#{tbl}.o_resource_id = #{v.id}"
|
105
|
+
when :name
|
106
|
+
"#{tbl}.name = '#{v}'"
|
107
|
+
else
|
108
|
+
h = _analyse_value(v)
|
109
|
+
i += 1
|
110
|
+
if (val = h[:v]).is_a? String
|
111
|
+
val = "'#{val}'"
|
112
|
+
end
|
113
|
+
"#{tbl}.#{h[:f]} #{h[:t] == 's' ? 'LIKE' : '='} #{val}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# TODO: Hack!!!
|
118
|
+
unless i <= 1
|
119
|
+
raise "Are you using the query facility on OProperties correctly? (#{i})"
|
120
|
+
else
|
121
|
+
i = 1
|
122
|
+
end
|
123
|
+
|
124
|
+
i.times do |j|
|
125
|
+
where << "r.id = p#{j}.o_resource_id"
|
126
|
+
end
|
127
|
+
where << "r.type = '#{resource_class}'" if resource_class
|
128
|
+
|
129
|
+
table = storage_names[:default]
|
130
|
+
from = i.times.map {|j| "#{table} AS p#{j}" }
|
131
|
+
from << "omf_sfa_resource_o_resources AS r" # TODO: Shouldn't hard-code that
|
132
|
+
[from, where]
|
133
|
+
end
|
134
|
+
|
86
135
|
@@name2class = {}
|
87
136
|
def self._create_resource(query_result)
|
88
137
|
qr = query_result
|
@@ -102,31 +151,12 @@ module OMF::SFA::Resource
|
|
102
151
|
|
103
152
|
def value=(val)
|
104
153
|
h = self.class._analyse_value(val)
|
154
|
+
#puts "VALUE>>> #{h}"
|
105
155
|
attribute_set(h[:f], h[:v])
|
106
156
|
attribute_set(:type, h[:t])
|
107
157
|
save
|
108
158
|
end
|
109
159
|
|
110
|
-
# if val.is_a? Numeric
|
111
|
-
# attribute_set(:n_value, val)
|
112
|
-
# attribute_set(:type, (val.is_a? Integer) ? 'i' : 'f')
|
113
|
-
# elsif val.is_a? String
|
114
|
-
# attribute_set(:s_value, val)
|
115
|
-
# attribute_set(:type, 's')
|
116
|
-
# elsif val.is_a? OResource
|
117
|
-
# attribute_set(:s_value, val.uuid.to_s)
|
118
|
-
# attribute_set(:type, 'r')
|
119
|
-
# elsif val.is_a? Time
|
120
|
-
# attribute_set(:n_value, val.to_i)
|
121
|
-
# attribute_set(:type, 't')
|
122
|
-
# else
|
123
|
-
# puts "OOOOO INSETRT (#{attribute_get(:name)})> #{val.class}"
|
124
|
-
# attribute_set(:s_value, JSON.generate([val]))
|
125
|
-
# attribute_set(:type, 'o')
|
126
|
-
# end
|
127
|
-
# save
|
128
|
-
# end
|
129
|
-
|
130
160
|
def self._analyse_value(val)
|
131
161
|
|
132
162
|
if val.is_a? Numeric
|
@@ -233,18 +263,41 @@ module OMF::SFA::Resource
|
|
233
263
|
|
234
264
|
end # OProperty
|
235
265
|
|
266
|
+
|
236
267
|
class OPropertyArray
|
268
|
+
|
237
269
|
def <<(val)
|
238
|
-
#puts "
|
239
|
-
p = OProperty.create(name: @name, o_resource: @resource)
|
270
|
+
#puts "------------------ Checking #{@resource.name} => #{@name}"
|
240
271
|
if @on_set_block
|
241
272
|
val = @on_set_block.call(val)
|
242
273
|
return if val.nil? #
|
243
274
|
end
|
244
|
-
|
275
|
+
if val.is_a? OResource
|
276
|
+
# Make sure we only have a single reference
|
277
|
+
return if OProperty.count(name: @name, resource: @resource, value: val) > 0
|
278
|
+
end
|
279
|
+
#puts ">>> Adding #{val} to #{@name} - #{@on_set_block}"
|
280
|
+
p = OProperty.create(name: @name, o_resource: @resource, value: val)
|
281
|
+
if @on_modified_block
|
282
|
+
@on_modified_block.call(val, true)
|
283
|
+
end
|
284
|
+
#p.value = val
|
245
285
|
self
|
246
286
|
end
|
247
287
|
|
288
|
+
def delete(val)
|
289
|
+
# this is rather inefficient
|
290
|
+
p = OProperty.all(name: @name, o_resource: @resource).find do |p|
|
291
|
+
p.value == val
|
292
|
+
end
|
293
|
+
return nil unless p
|
294
|
+
p.destroy
|
295
|
+
if @on_removed_block
|
296
|
+
@on_removed_block.call(val)
|
297
|
+
end
|
298
|
+
val
|
299
|
+
end
|
300
|
+
|
248
301
|
# Delete all members
|
249
302
|
def clear
|
250
303
|
OProperty.all(name: @name, o_resource: @resource).destroy
|
@@ -268,7 +321,8 @@ module OMF::SFA::Resource
|
|
268
321
|
end
|
269
322
|
|
270
323
|
def length
|
271
|
-
|
324
|
+
#raise "LENGTH"
|
325
|
+
OProperty.count(name: @name, resource: @resource)
|
272
326
|
end
|
273
327
|
|
274
328
|
def empty?
|
@@ -277,8 +331,12 @@ module OMF::SFA::Resource
|
|
277
331
|
|
278
332
|
# Callback to support 'reverse' operation
|
279
333
|
def on_modified(&block)
|
280
|
-
raise "Not implemented"
|
281
|
-
|
334
|
+
#raise "Not implemented"
|
335
|
+
@on_modified_block = block
|
336
|
+
end
|
337
|
+
|
338
|
+
def on_removed(&block)
|
339
|
+
@on_removed_block = block
|
282
340
|
end
|
283
341
|
|
284
342
|
def on_set(&block)
|
@@ -299,6 +357,10 @@ module OMF::SFA::Resource
|
|
299
357
|
"<#{self.class}: name=#{@name} resource=#{@resource.name || @resource.uuid} >"
|
300
358
|
end
|
301
359
|
|
360
|
+
def method_missing(m, *args, &block)
|
361
|
+
self.to_a().send(m, &block)
|
362
|
+
end
|
363
|
+
|
302
364
|
def initialize(resource, name)
|
303
365
|
@resource = resource
|
304
366
|
@name = name
|
@@ -3,6 +3,7 @@ require 'dm-core'
|
|
3
3
|
require 'dm-types'
|
4
4
|
require 'dm-validations'
|
5
5
|
require 'dm-aggregates'
|
6
|
+
require 'dm_noisy_failures'
|
6
7
|
|
7
8
|
require 'omf_base/lobject'
|
8
9
|
require 'set'
|
@@ -271,7 +272,7 @@ module OMF::SFA::Resource
|
|
271
272
|
alias_method :[], :oproperty_get
|
272
273
|
|
273
274
|
def oproperty_set(pname, value, type = nil)
|
274
|
-
#puts "OPROPERTY_SET pname:'#{pname}', value:'#{value.class}
|
275
|
+
#puts "OPROPERTY_SET pname:'#{pname}', value:'#{value}'::#{value.class}::#{type}, self:'#{self.inspect}'"
|
275
276
|
pname = pname.to_sym
|
276
277
|
if pname == :name
|
277
278
|
self.name = value
|
@@ -31,6 +31,7 @@ module OMF::SFA
|
|
31
31
|
@@sfa_classes = {}
|
32
32
|
@@sfa_name2class = {}
|
33
33
|
@@sfa_suppress_id = {}
|
34
|
+
@@sfa_suppress_uuid = {}
|
34
35
|
|
35
36
|
#
|
36
37
|
# @opts
|
@@ -74,6 +75,14 @@ module OMF::SFA
|
|
74
75
|
@@sfa_suppress_id[self] == true
|
75
76
|
end
|
76
77
|
|
78
|
+
def sfa_suppress_uuid
|
79
|
+
@@sfa_suppress_uuid[self] = true
|
80
|
+
end
|
81
|
+
|
82
|
+
def sfa_suppress_uuid?
|
83
|
+
@@sfa_suppress_uuid[self] == true
|
84
|
+
end
|
85
|
+
|
77
86
|
# Define a SFA property
|
78
87
|
#
|
79
88
|
# @param [Symbol] name name of resource in RSpec
|
@@ -467,6 +476,11 @@ module OMF::SFA
|
|
467
476
|
unless opts[:suppress_id] || self.class.sfa_suppress_id?
|
468
477
|
new_element.set_attribute('id', id) #if detail_level > 0
|
469
478
|
end
|
479
|
+
|
480
|
+
unless opts[:suppress_uuid] || self.class.sfa_suppress_uuid?
|
481
|
+
new_element.set_attribute('uuid', self.uuid) #if detail_level > 0
|
482
|
+
end
|
483
|
+
|
470
484
|
#if href = self.href(opts)
|
471
485
|
# new_element.set_attribute('omf:href', href)
|
472
486
|
#end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
#
|
2
|
+
# Load topology from BRITE file
|
3
|
+
#
|
4
|
+
|
5
|
+
require 'omf_sfa'
|
6
|
+
|
7
|
+
# BRITE format
|
8
|
+
#
|
9
|
+
# Topology: ( [numNodes] Nodes, [numEdges] Edges )
|
10
|
+
# Model ( [ModelNum] ): [Model.toString()]
|
11
|
+
#
|
12
|
+
# Nodes: ([numNodes]):
|
13
|
+
# [NodeID] [x-coord] [y-coord] [inDegree] [outDegree] [ASid] [type]
|
14
|
+
# [NodeID] [x-coord] [y-coord] [inDegree] [outDegree] [ASid] [type]
|
15
|
+
# [NodeID] [x-coord] [y-coord] [inDegree] [outDegree] [ASid] [type]
|
16
|
+
# ...
|
17
|
+
#
|
18
|
+
# Edges: ([numEdges]):
|
19
|
+
# [EdgeID] [fromNodeID] [toNodeID] [Length] [Delay] [Bandwidth] [ASFromNodeID] [ASToNodeID] [EdgeType] [Direction]
|
20
|
+
# [EdgeID] [fromNodeID] [toNodeID] [Length] [Delay] [Bandwidth] [ASFromNodeID] [ASToNodeID] [EdgeType] [Direction]
|
21
|
+
# [EdgeID] [fromNodeID] [toNodeID] [Length] [Delay] [Bandwidth] [ASFromNodeID] [ASToNodeID] [EdgeType] [Direction]
|
22
|
+
#
|
23
|
+
module OMF::SFA::Util
|
24
|
+
class BriteParser < OMF::Base::LObject
|
25
|
+
|
26
|
+
def initialize()
|
27
|
+
@nodes = {}
|
28
|
+
@edges = []
|
29
|
+
#@sliver = OMF::SFA::Resource::Sliver.def_sliver
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_new_node(&block)
|
34
|
+
@on_new_node = block
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_new_edge(&block)
|
38
|
+
@on_new_edge = block
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_node(opts)
|
42
|
+
node = @on_new_node.call(opts)
|
43
|
+
@nodes[opts[:id]] = node if node
|
44
|
+
end
|
45
|
+
|
46
|
+
def create_edge(opts)
|
47
|
+
from = @nodes[opts[:from]]
|
48
|
+
to = @nodes[opts[:to]]
|
49
|
+
edge = @on_new_edge.call(opts, from, to)
|
50
|
+
@edges << edge if edge
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_rspec()
|
54
|
+
OComponent.to_rspec(@nodes.values() + @edges, :request, suppress_id: true)
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_file(file_name)
|
58
|
+
f = File.open(File.absolute_path(file_name))
|
59
|
+
sp = [
|
60
|
+
lambda {|l, i| _parse_header(l, i)},
|
61
|
+
lambda {|l, i| _parse_nodes(l, i)},
|
62
|
+
lambda {|l, i| _parse_edges(l, i)},
|
63
|
+
]
|
64
|
+
p = sp.shift
|
65
|
+
i = 0
|
66
|
+
f.each do |l|
|
67
|
+
if l.strip!.empty?
|
68
|
+
next if i == 0 # skip multiple consectutive empty lines
|
69
|
+
p = sp.shift
|
70
|
+
i = 0
|
71
|
+
next
|
72
|
+
end
|
73
|
+
p.call(l, i)
|
74
|
+
i += 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def _parse_header(l, i)
|
79
|
+
case l
|
80
|
+
when /^Topology/
|
81
|
+
# ignore Topology: ( 111 Nodes, 111 Edges )
|
82
|
+
when /^Model/
|
83
|
+
unless m = l.match(/Model\W*([\d]*)\W*:*(.*)/)
|
84
|
+
fatal "Missing 'Model' declaration in header"
|
85
|
+
exit -1
|
86
|
+
end
|
87
|
+
@model_type = m[1]
|
88
|
+
@model_opts = m[2]
|
89
|
+
else
|
90
|
+
warn "Ignoring unexpected header line - #{l}"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def _parse_nodes(l, i)
|
95
|
+
return if i == 0
|
96
|
+
|
97
|
+
unless m = l.match(/([\d]+)\s+([\d\.]+)\s+([\d\.]+)\s+([\d]+)\s+([\d]+)\s+([\d\-]+)\s+(.*)/)
|
98
|
+
fatal "Can't parse node declaration - #{l}"
|
99
|
+
exit -1
|
100
|
+
end
|
101
|
+
if (pa = m.to_a[1 .. -1]).length != 7
|
102
|
+
fatal "Expected 7 parameters in node declaration, but got '#{pa.length}' - #{l}"
|
103
|
+
exit -1
|
104
|
+
end
|
105
|
+
# [NodeID] [x-coord] [y-coord] [inDegree] [outDegree] [ASid] [type]
|
106
|
+
n = {}
|
107
|
+
[[:id, :i], [:x, :f], [:y, :f], nil, nil, nil, [:type]].each_with_index do |k, i|
|
108
|
+
next if k.nil?
|
109
|
+
|
110
|
+
v = pa[i]
|
111
|
+
case k[1]
|
112
|
+
when :i
|
113
|
+
v = v.to_i
|
114
|
+
when :f
|
115
|
+
v = v.to_f
|
116
|
+
else
|
117
|
+
v.strip!
|
118
|
+
end
|
119
|
+
#puts "name: #{name} i: #{i}"
|
120
|
+
n[k[0]] = v
|
121
|
+
end
|
122
|
+
create_node(n)
|
123
|
+
#puts "NODES - #{n}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def _parse_edges(l, i)
|
127
|
+
return if i == 0
|
128
|
+
unless m = l.match(/([\d]+)\s+([\d]+)\s+([\d]+)\s+([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s+([\d\-]+)\s+([\d\-]+)\s+([^\s]+)\s+([^\s]+)/)
|
129
|
+
fatal "Can't parse edge declaration - #{l}"
|
130
|
+
exit -1
|
131
|
+
end
|
132
|
+
if (pa = m.to_a[1 .. -1]).length != 10
|
133
|
+
fatal "Expected 10 parameters in edge declaration, but got '#{pa.length}' - #{pa}"
|
134
|
+
exit -1
|
135
|
+
end
|
136
|
+
# [EdgeID] [fromNodeID] [toNodeID] [Length] [Delay] [Bandwidth] [ASFromNodeID] [ASToNodeID] [EdgeType] [Direction]
|
137
|
+
e = {}
|
138
|
+
[[:id, :i], [:from, :i], [:to, :i], [:length, :f], [:delay, :f], [:bw, :f], nil, nil, [:type], [:direction]].each_with_index do |k, i|
|
139
|
+
next if k.nil?
|
140
|
+
|
141
|
+
v = pa[i]
|
142
|
+
case k[1]
|
143
|
+
when :i
|
144
|
+
v = v.to_i
|
145
|
+
when :f
|
146
|
+
v = v.to_f
|
147
|
+
else
|
148
|
+
v.strip!
|
149
|
+
end
|
150
|
+
#puts "name: #{name} i: #{i}"
|
151
|
+
e[k[0]] = v
|
152
|
+
end
|
153
|
+
create_edge(e)
|
154
|
+
#@edges << e
|
155
|
+
#puts "EDGES - #{e}"
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
@@ -0,0 +1,277 @@
|
|
1
|
+
|
2
|
+
require 'json'
|
3
|
+
require 'omf_base'
|
4
|
+
require 'omf-sfa/resource'
|
5
|
+
include OMF::SFA::Resource
|
6
|
+
|
7
|
+
|
8
|
+
module OMF::SFA::Util
|
9
|
+
|
10
|
+
class GraphJSONException < Exception
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# This class handles the convertion between GraphJson
|
16
|
+
# and a set of resources.
|
17
|
+
#
|
18
|
+
# Usage:
|
19
|
+
# GraphJson.parse_file(file_name) => Array of OResources
|
20
|
+
#
|
21
|
+
class GraphJSON < OMF::Base::LObject
|
22
|
+
|
23
|
+
def self.parse_file(file_name, opts = {})
|
24
|
+
content = File.open(file_name).read()
|
25
|
+
self.new.parse(JSON.parse(content, symbolize_names: true), opts)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Parse a hash following the GraphJSON format and return a
|
29
|
+
# list od resources.
|
30
|
+
#
|
31
|
+
# * opts
|
32
|
+
# - :node_services Services to add to each node (default: [])
|
33
|
+
# - :create_new_uuids Don't use '_id' for UUID, create new ones
|
34
|
+
#
|
35
|
+
def parse(descr_hash, opts = {})
|
36
|
+
@create_new_uuids = (opts[:create_new_uuids] == true)
|
37
|
+
unless graph = descr_hash[:graph]
|
38
|
+
raise GraphJSONException.new "Expected description 'graph' element at root - #{descr_hash}"
|
39
|
+
end
|
40
|
+
[:nodes, :edges].each do |type|
|
41
|
+
unless graph[type]
|
42
|
+
raise GraphJSONException.new "Missing '#{type}' declaration"
|
43
|
+
end
|
44
|
+
graph[type].each do |e|
|
45
|
+
unless id = e[:_id]
|
46
|
+
raise GraphJSONException.new "Missing '_id' for #{type} - #{e}"
|
47
|
+
end
|
48
|
+
if @id2descr.key? id
|
49
|
+
raise GraphJSONException.new "Duplicated id '#{id}' detected - #{e}"
|
50
|
+
end
|
51
|
+
@id2descr[id] = e
|
52
|
+
end
|
53
|
+
end
|
54
|
+
parse_defaults(graph, opts)
|
55
|
+
parse_nodes(graph[:nodes], opts)
|
56
|
+
parse_edges(graph, opts)
|
57
|
+
#puts ">>>>> #{@resources}"
|
58
|
+
@resources
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize()
|
62
|
+
@id2descr = {}
|
63
|
+
@resources = {}
|
64
|
+
@sliver_types = {}
|
65
|
+
end
|
66
|
+
|
67
|
+
def parse_defaults(graph, opts)
|
68
|
+
@defaults = {node: {}, interface: {}, network: {}}
|
69
|
+
(graph[:defaults] || {}).each do |type, h|
|
70
|
+
type_def = @defaults[type.to_sym] ||= {}
|
71
|
+
h.each do |name, id|
|
72
|
+
unless ref = @id2descr[id]
|
73
|
+
raise GraphJSONException.new "Defaults refer to unknown id '#{id}'"
|
74
|
+
end
|
75
|
+
unless val = ref[name]
|
76
|
+
raise GraphJSONException.new "Defaults refer to unspecified property '#{name}' in '#{id}' - #{ref}"
|
77
|
+
end
|
78
|
+
type_def[name] = val
|
79
|
+
#puts "#{type}::#{name} ===> #{val}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def parse_nodes(nodes, opts)
|
85
|
+
nodes.each do |n|
|
86
|
+
unless type = n[:_type]
|
87
|
+
raise GraphJSONException.new "Missing '_type' declaration - #{n}"
|
88
|
+
end
|
89
|
+
case type
|
90
|
+
when 'node'
|
91
|
+
parse_node(n, opts)
|
92
|
+
when 'network'
|
93
|
+
parse_network(n, opts)
|
94
|
+
else
|
95
|
+
raise GraphJSONException.new "Unknown node type '#{type}' - #{n}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def parse_node(node, gopts)
|
101
|
+
el_defaults = @defaults[:node]
|
102
|
+
opts = {}
|
103
|
+
unless id = node[:_id]
|
104
|
+
raise GraphJSONException.new "Missing node ID '_id' - #{node}"
|
105
|
+
end
|
106
|
+
opts[:uuid] = id unless @create_new_uuids
|
107
|
+
opts[:name] = node[:name]
|
108
|
+
parse_value(node, :component_manager, el_defaults, opts, false)
|
109
|
+
parse_value(node, :urn, el_defaults, opts, false)
|
110
|
+
opts[:sliver_type] = parse_sliver_type(node, el_defaults)
|
111
|
+
|
112
|
+
@resources[id] = node_r = Node.create(opts)
|
113
|
+
(gopts[:node_services] || []).each do |s|
|
114
|
+
node_r.services << s
|
115
|
+
end
|
116
|
+
node_r
|
117
|
+
end
|
118
|
+
|
119
|
+
def parse_network(network, opts)
|
120
|
+
el_defaults = @defaults[:network]
|
121
|
+
# add defaults
|
122
|
+
parse_value(network, :netmask, el_defaults, network, false)
|
123
|
+
parse_value(network, :type, el_defaults, network, false)
|
124
|
+
end
|
125
|
+
|
126
|
+
def parse_edges(graph, gopts)
|
127
|
+
# First collect interfaces into node declaration
|
128
|
+
graph[:edges].each do |e|
|
129
|
+
[[:_source, :tail, :_target], [:_target, :head, :_source]].each do |n_id, if_id, opp_id|
|
130
|
+
n_descr = @id2descr[e[n_id]]
|
131
|
+
next unless n_descr[:_type] == "node"
|
132
|
+
if_a = (n_descr[:__ifs] ||= [])
|
133
|
+
if_a << (if_descr = e[if_id] || {})
|
134
|
+
opp_descr = @id2descr[e[opp_id]]
|
135
|
+
if opp_descr[:_type] == "network"
|
136
|
+
if_descr[:__nw] = opp_descr
|
137
|
+
(opp_descr[:__ifs] ||= []) << if_descr
|
138
|
+
end
|
139
|
+
(e[:__ifs] ||= []) << if_descr
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
#
|
144
|
+
graph[:nodes].each do |n_descr|
|
145
|
+
next unless n_descr[:_type] == "node"
|
146
|
+
node_id = n_descr[:_id]
|
147
|
+
if_a = n_descr[:__ifs]
|
148
|
+
|
149
|
+
# Add names to all interfaces
|
150
|
+
names = if_a.map {|ifd| ifd[:name] }.compact
|
151
|
+
i = 0
|
152
|
+
if_a.each do |ifs|
|
153
|
+
next if ifs[:name] # ok, has one already
|
154
|
+
begin
|
155
|
+
name = "if#{i}"
|
156
|
+
i += 1
|
157
|
+
end while names.include?(name)
|
158
|
+
ifs[:name] = name
|
159
|
+
end
|
160
|
+
|
161
|
+
# Create interface resource
|
162
|
+
node_r = @resources[node_id]
|
163
|
+
if_a.each do |ifs|
|
164
|
+
|
165
|
+
opts = { name: (ifs[:__client_id] = "#{node_r.name}:#{ifs[:name]}") }
|
166
|
+
if ip_decl = ifs[:ip]
|
167
|
+
if ip_decl.key? :type
|
168
|
+
ip_decl[:ip_type] = ip_decl.delete(:type)
|
169
|
+
end
|
170
|
+
#puts "IP>>> #{ip_decl}"
|
171
|
+
opts[:ip] = Ip.create(ip_decl)
|
172
|
+
else
|
173
|
+
|
174
|
+
# TODO: Maybe create IP address if the other end is a network
|
175
|
+
if nw = ifs[:__nw]
|
176
|
+
idx = nw[:__ifs].index do |oifs|
|
177
|
+
ifs[:__client_id] == oifs[:__client_id]
|
178
|
+
end
|
179
|
+
#puts "\n#{idx}\n"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
if_r = ifs[:__if_r] = Interface.create(opts)
|
183
|
+
node_r.interfaces << if_r
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
# Create Link resources
|
189
|
+
graph[:edges].each do |e|
|
190
|
+
source = @id2descr[e[:_source]]
|
191
|
+
target = @id2descr[e[:_target]]
|
192
|
+
unless source && target
|
193
|
+
raise GraphJSONException.new "Can't find source or target node - #{e}"
|
194
|
+
end
|
195
|
+
if source[:_type] == 'node' && target[:_type] == 'node'
|
196
|
+
# direct link
|
197
|
+
opts = {}
|
198
|
+
unless id = e[:_id]
|
199
|
+
raise GraphJSONException.new "Missing edge ID '_id' - #{e}"
|
200
|
+
end
|
201
|
+
opts[:uuid] = id unless @create_new_uuids
|
202
|
+
opts[:name] = e[:name] #|| id
|
203
|
+
link_r = Link.create(opts)
|
204
|
+
@resources[id] = link_r
|
205
|
+
elsif source[:_type] == 'network' && target[:_type] == 'network'
|
206
|
+
raise GraphJSONException.new "Can't connect two networks directly - #{e}"
|
207
|
+
else
|
208
|
+
# one side is a network
|
209
|
+
network = source[:_type] == 'network' ? source : target
|
210
|
+
unless link_r = @resources[nw_id = network[:_id]]
|
211
|
+
opts = {name: network[:name]}
|
212
|
+
opts[:uuid] = nw_id unless @create_new_uuids
|
213
|
+
link_r = Link.create(opts)
|
214
|
+
@resources[nw_id] = link_r
|
215
|
+
end
|
216
|
+
end
|
217
|
+
if link_r
|
218
|
+
link_r.link_type = e[:link_type] || "lan"
|
219
|
+
e[:__ifs].each do |ifs|
|
220
|
+
link_r.interfaces << ifs[:__if_r]
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def parse_sliver_type(node, el_defaults)
|
228
|
+
sliver_type = parse_value(node, :sliver_type, el_defaults, nil, true)
|
229
|
+
disk_image_url = parse_value(node, :disk_image, el_defaults, nil, true)
|
230
|
+
id = "#{sliver_type}-#{disk_image_url}"
|
231
|
+
unless st_res = @sliver_types[id]
|
232
|
+
di = DiskImage.create(url: disk_image_url)
|
233
|
+
st_res = @sliver_types[id] = SliverType.create(name: sliver_type, disk_image: di)
|
234
|
+
end
|
235
|
+
st_res
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
def parse_value(el, name, defaults, opts, is_mandatory = false)
|
240
|
+
val = el[name] || defaults[name]
|
241
|
+
if is_mandatory && val.nil?
|
242
|
+
raise GraphJSONException.new "Can't find value for mandatory property '#{name}' in '#{el}'"
|
243
|
+
end
|
244
|
+
if opts && !val.nil?
|
245
|
+
opts[name.to_sym] = val
|
246
|
+
end
|
247
|
+
val
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
end
|
253
|
+
|
254
|
+
if $0 == __FILE__
|
255
|
+
|
256
|
+
OMF::Base::Loggable.init_log 'graph_json'
|
257
|
+
|
258
|
+
GURN.default_domain = "urn:publicid:IDN+acme.org"
|
259
|
+
OMF::SFA::Resource::OResource.init()
|
260
|
+
|
261
|
+
file = ARGV[0] || File.join(File.dirname(__FILE__), '/../../../examples/four_node_one_network.gjson')
|
262
|
+
begin
|
263
|
+
opts = {
|
264
|
+
node_services: [
|
265
|
+
InstallService.create(install_path: "/local", url: "http://emmy9.casa.umass.edu/InstaGENI_Images/install-script.tar.gz"),
|
266
|
+
ExecuteService.create(command: "sudo sh /local/postboot_script.sh", shell: "sh")
|
267
|
+
],
|
268
|
+
create_new_uuids: false
|
269
|
+
}
|
270
|
+
resources = OMF::SFA::Util::GraphJSON.parse_file(file, opts)
|
271
|
+
#puts resources
|
272
|
+
rspec = OComponent.to_rspec(resources.values, :request, suppress_id: true)
|
273
|
+
puts rspec
|
274
|
+
rescue OMF::SFA::Util::GraphJSONException => ex
|
275
|
+
puts "ERROR: #{ex}"
|
276
|
+
end
|
277
|
+
end
|
data/lib/omf-sfa/version.rb
CHANGED
data/omf_sfa.gemspec
CHANGED
@@ -32,6 +32,7 @@ Gem::Specification.new do |s|
|
|
32
32
|
s.add_runtime_dependency "dm-validations", "~> 1.2.0"
|
33
33
|
s.add_runtime_dependency "dm-migrations", "~> 1.2.0"
|
34
34
|
s.add_runtime_dependency "dm-sqlite-adapter", "~> 1.2.0"
|
35
|
+
s.add_runtime_dependency 'dm-noisy-failures'
|
35
36
|
s.add_runtime_dependency "uuid", "~> 2.3.5"
|
36
37
|
s.add_runtime_dependency "json", "~> 1.7.7"
|
37
38
|
#
|
@@ -45,4 +46,7 @@ Gem::Specification.new do |s|
|
|
45
46
|
s.add_runtime_dependency "omf_base", "~> 1.0.3"
|
46
47
|
s.add_runtime_dependency "eventmachine", "~> 1.0.3"
|
47
48
|
s.add_runtime_dependency "em-minitest-spec", "~> 1.1.1"
|
49
|
+
s.add_runtime_dependency "ruby-ip"
|
50
|
+
|
51
|
+
|
48
52
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omf_sfa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-08-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: minitest
|
@@ -219,6 +219,22 @@ dependencies:
|
|
219
219
|
- - ~>
|
220
220
|
- !ruby/object:Gem::Version
|
221
221
|
version: 1.2.0
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
name: dm-noisy-failures
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
225
|
+
none: false
|
226
|
+
requirements:
|
227
|
+
- - ! '>='
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :runtime
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
none: false
|
234
|
+
requirements:
|
235
|
+
- - ! '>='
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
version: '0'
|
222
238
|
- !ruby/object:Gem::Dependency
|
223
239
|
name: uuid
|
224
240
|
requirement: !ruby/object:Gem::Requirement
|
@@ -395,10 +411,27 @@ dependencies:
|
|
395
411
|
- - ~>
|
396
412
|
- !ruby/object:Gem::Version
|
397
413
|
version: 1.1.1
|
414
|
+
- !ruby/object:Gem::Dependency
|
415
|
+
name: ruby-ip
|
416
|
+
requirement: !ruby/object:Gem::Requirement
|
417
|
+
none: false
|
418
|
+
requirements:
|
419
|
+
- - ! '>='
|
420
|
+
- !ruby/object:Gem::Version
|
421
|
+
version: '0'
|
422
|
+
type: :runtime
|
423
|
+
prerelease: false
|
424
|
+
version_requirements: !ruby/object:Gem::Requirement
|
425
|
+
none: false
|
426
|
+
requirements:
|
427
|
+
- - ! '>='
|
428
|
+
- !ruby/object:Gem::Version
|
429
|
+
version: '0'
|
398
430
|
description: OMF's Aggregate manager with SFA and new REST API.
|
399
431
|
email:
|
400
432
|
- omf-user@lists.nicta.com.au
|
401
433
|
executables:
|
434
|
+
- brite2rspec.rb
|
402
435
|
- parse_rspec.rb
|
403
436
|
extensions: []
|
404
437
|
extra_rdoc_files: []
|
@@ -407,9 +440,9 @@ files:
|
|
407
440
|
- Gemfile
|
408
441
|
- README.md
|
409
442
|
- Rakefile
|
443
|
+
- bin/brite2rspec.rb
|
410
444
|
- bin/parse_rspec.rb
|
411
445
|
- etc/omf-sfa/omf-sfa-am.yaml
|
412
|
-
- examples/brite2rspec.rb
|
413
446
|
- examples/exogeni5nodemanifest.rspec
|
414
447
|
- examples/instageni5nodemanifest.rspec
|
415
448
|
- examples/waxman_10.brite
|
@@ -472,7 +505,9 @@ files:
|
|
472
505
|
- lib/omf-sfa/resource/sfa_base.rb
|
473
506
|
- lib/omf-sfa/resource/sliver_type.rb
|
474
507
|
- lib/omf-sfa/resource/user.rb
|
508
|
+
- lib/omf-sfa/util/brite_parser.rb
|
475
509
|
- lib/omf-sfa/util/create_sample_testbed.rb
|
510
|
+
- lib/omf-sfa/util/graph_json.rb
|
476
511
|
- lib/omf-sfa/util/load_from_sfa_xml.rb
|
477
512
|
- lib/omf-sfa/version.rb
|
478
513
|
- lib/omf_sfa.rb
|