omf_ec 6.0.0.pre.4 → 6.0.0.pre.5
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/omf_ec +75 -57
- data/example/engine_oedl.rb +20 -20
- data/example/engine_test.rb +51 -73
- data/example/test_exp/test00.rb +25 -5
- data/example/test_exp/test01.rb +4 -5
- data/example/test_exp/test02.rb +2 -5
- data/example/test_exp/test03.rb +15 -28
- data/lib/omf_ec/app_definition.rb +3 -3
- data/lib/omf_ec/backward/default_events.rb +13 -8
- data/lib/omf_ec/backward/dsl.rb +4 -36
- data/lib/omf_ec/backward/exp/testbed.rb +7 -7
- data/lib/omf_ec/backward/group.rb +23 -9
- data/lib/omf_ec/context/app_context.rb +40 -9
- data/lib/omf_ec/context/group_context.rb +18 -47
- data/lib/omf_ec/context/net_context.rb +3 -3
- data/lib/omf_ec/dsl.rb +55 -40
- data/lib/omf_ec/experiment.rb +112 -20
- data/lib/omf_ec/experiment_property.rb +222 -0
- data/lib/omf_ec/group.rb +54 -104
- data/lib/omf_ec/version.rb +1 -1
- data/lib/omf_ec.rb +59 -7
- data/omf_ec.gemspec +4 -2
- data/test/omf_ec/dsl_spec.rb +53 -0
- data/test/omf_ec/experiment_property_spec.rb +81 -0
- data/test/omf_ec/experiment_spec.rb +41 -0
- data/test/omf_ec/group_spec.rb +45 -2
- metadata +35 -19
- data/example/test_exp/test04.rb +0 -105
- data/example/test_exp/test05.rb +0 -87
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'hashie'
|
2
|
+
require 'singleton'
|
3
|
+
|
4
|
+
module OmfEc
|
5
|
+
#
|
6
|
+
# This class defines an Experiment Property, and also holds all of the
|
7
|
+
# Experiment Properties defined for a given experiment.
|
8
|
+
# Most of this implementation is re-used from OMF 5.4
|
9
|
+
#
|
10
|
+
class ExperimentProperty
|
11
|
+
|
12
|
+
# Contains all the experiment properties
|
13
|
+
@@properties = Hashie::Mash.new
|
14
|
+
|
15
|
+
# Holds all observers on any Experiment Property creation
|
16
|
+
@@creation_observers = []
|
17
|
+
|
18
|
+
#
|
19
|
+
# Returns a given property
|
20
|
+
# - name =nameof the property to return
|
21
|
+
#
|
22
|
+
# [Return] a property
|
23
|
+
#
|
24
|
+
def self.[](name)
|
25
|
+
p = @@properties[name.to_s.to_sym]
|
26
|
+
if p.nil?
|
27
|
+
raise OEDLCommandException.new(name,
|
28
|
+
"Unknown experiment property '#{name}'\n\tKnown properties are "+
|
29
|
+
"'#{ExperimentProperty.names.join(', ')}'")
|
30
|
+
end
|
31
|
+
return p
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.[]=(name, val)
|
35
|
+
p = ExperimentProperty[name.to_sym]
|
36
|
+
p.set(val)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.length; @@properties.length end
|
40
|
+
|
41
|
+
# Minitest needs to be able to turn this Class into a string, this is
|
42
|
+
# normally done through the default 'method_missing' of the Classe
|
43
|
+
# but we redefined that... so to run minitest we need to explicitly
|
44
|
+
# define 'to_str' for this Class
|
45
|
+
def self.to_str; "ExperimentProperty" end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Handles missing method, allows to access an existing Experiment
|
49
|
+
# Property with the syntax 'propcontext.propname'
|
50
|
+
#
|
51
|
+
def self.method_missing(name, args = nil)
|
52
|
+
name = name.to_s
|
53
|
+
if setter = (name[-1] == ?=)
|
54
|
+
name.chop!
|
55
|
+
end
|
56
|
+
p = ExperimentProperty[name.to_sym]
|
57
|
+
if setter
|
58
|
+
p.set(args)
|
59
|
+
else
|
60
|
+
return p
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Iterate over all Experiment Properties. The block
|
65
|
+
# will be called with the respective property as single
|
66
|
+
# argument
|
67
|
+
#
|
68
|
+
# - sort_names = if 'true' sort the properties (default: true)
|
69
|
+
# - &block = the block of commands to call
|
70
|
+
#
|
71
|
+
def self.each(sort_names = false, &block)
|
72
|
+
names = @@properties.keys
|
73
|
+
names = names.sort_by {|sym| sym.to_s} if (sort_names)
|
74
|
+
names.each { |n| block.call(@@properties[n]) }
|
75
|
+
end
|
76
|
+
|
77
|
+
# Return an existing Experiment Property, or create a new one
|
78
|
+
#
|
79
|
+
# - name = name of the property to create/return
|
80
|
+
# - value = value to assign to this property
|
81
|
+
# - description = short string description for this property
|
82
|
+
#
|
83
|
+
# [Return] an Experiment Property
|
84
|
+
#
|
85
|
+
def self.create(name, value = nil, description = nil)
|
86
|
+
name = name.to_s
|
87
|
+
# http://stackoverflow.com/questions/4378670/what-is-a-ruby-regex-to-match-a-function-name
|
88
|
+
if /[@$"]/ =~ name.to_sym.inspect
|
89
|
+
raise OEDLCommandException.new("ExperimentProperty.create",
|
90
|
+
"Cannot create property '#{name}', its name is not a valid Ruby name")
|
91
|
+
end
|
92
|
+
p = nil
|
93
|
+
name = name.to_sym
|
94
|
+
if (p = @@properties[name]) != nil
|
95
|
+
p.set(value) if value != nil
|
96
|
+
p.description = description if description != nil
|
97
|
+
else
|
98
|
+
p = ExperimentProperty.new(name, value, description)
|
99
|
+
@@properties[name] = p
|
100
|
+
# Let the observers know that we created a new property
|
101
|
+
@@creation_observers.each { |proc| proc.call(:create, p) }
|
102
|
+
end
|
103
|
+
return p
|
104
|
+
end
|
105
|
+
|
106
|
+
#
|
107
|
+
# Return the names of the all defined Experiment Properties
|
108
|
+
#
|
109
|
+
# [Return] an Array with the names of all defined Experiment Properties
|
110
|
+
#
|
111
|
+
def self.names() return @@properties.keys end
|
112
|
+
|
113
|
+
# Add an observer for any creation of a new Experiment Property
|
114
|
+
#
|
115
|
+
# - proc = block to execute when a new Experiment Property is created
|
116
|
+
#
|
117
|
+
def self.add_observer(&proc) @@creation_observers << proc end
|
118
|
+
|
119
|
+
attr_reader :name, :value, :id
|
120
|
+
attr_accessor :description
|
121
|
+
|
122
|
+
private :initialize
|
123
|
+
|
124
|
+
#
|
125
|
+
# Create a new Experiment Property
|
126
|
+
#
|
127
|
+
# - name = name of the property to create/return
|
128
|
+
# - value = value to assign to this property
|
129
|
+
# - description = short string description for this property
|
130
|
+
#
|
131
|
+
def initialize(name, value = nil, description = nil)
|
132
|
+
@name = name.to_s
|
133
|
+
@description = description
|
134
|
+
@change_observers = Array.new
|
135
|
+
set(value)
|
136
|
+
end
|
137
|
+
|
138
|
+
#
|
139
|
+
# Add a block of command to the list of actions to do
|
140
|
+
# when this property is being changed
|
141
|
+
#
|
142
|
+
# - &block = the block of command to add
|
143
|
+
#
|
144
|
+
def on_change (&block)
|
145
|
+
debug "Somebody bound to me"
|
146
|
+
@change_observers << block
|
147
|
+
end
|
148
|
+
|
149
|
+
#
|
150
|
+
# Update the value of this Experiment Property
|
151
|
+
#
|
152
|
+
# - value = new value for this property
|
153
|
+
#
|
154
|
+
def set(value)
|
155
|
+
@value = value
|
156
|
+
info "#{name} = #{value.inspect} (#{value.class})"
|
157
|
+
@change_observers.each { |proc| proc.call(value) }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Implicit conversion to String (required for + operator)
|
161
|
+
def to_str() @value.to_s end
|
162
|
+
|
163
|
+
# Explicit conversion to String
|
164
|
+
alias_method :to_s, :to_str
|
165
|
+
|
166
|
+
# Division operator for Integer and Float properties
|
167
|
+
def /(right)
|
168
|
+
if @value.kind_of?(Integer) || @value.kind_of?(Float)
|
169
|
+
return (@value / right)
|
170
|
+
else
|
171
|
+
raise OEDLCommandException.new("/", "Illegal operation, "+
|
172
|
+
"the value of Experiment Property '#{@name}' is not numerical "+
|
173
|
+
"(current value is of type #{value.class})")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Multiplication operator for Integer and Float properties
|
178
|
+
def *(right)
|
179
|
+
if @value.kind_of?(Integer) || @value.kind_of?(Float)
|
180
|
+
return (@value * right)
|
181
|
+
else
|
182
|
+
raise OEDLCommandException.new("*", "Illegal operation, "+
|
183
|
+
"the value of Experiment Property '#{@name}' is not numerical "+
|
184
|
+
"(current value is of type #{value.class})")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Substraction operator for Integer and Float properties
|
189
|
+
def -(right)
|
190
|
+
if @value.kind_of?(Integer) || @value.kind_of?(Float)
|
191
|
+
return (@value - right)
|
192
|
+
else
|
193
|
+
raise OEDLCommandException.new("-", "Illegal operation, "+
|
194
|
+
"the value of Experiment Property '#{@name}' is not numerical "+
|
195
|
+
"(current value is of type #{value.class})")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Addition operator for Integer, Float, and String properties
|
200
|
+
def +(right)
|
201
|
+
if @value.kind_of?(Integer) || @value.kind_of?(Float) || @value.kind_of?(String)
|
202
|
+
return (@value + right)
|
203
|
+
else
|
204
|
+
raise OEDLCommandException.new("+", "Illegal operation, "+
|
205
|
+
"The value of Experiment Property '#{@name}' does not support addition "+
|
206
|
+
"(current value is of type #{value.class})")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
# Explicit Coercion for Integer, Float, and String properties
|
211
|
+
# (allow property to be on the right-hand of an operator such as +)
|
212
|
+
def coerce(other)
|
213
|
+
if @value.kind_of?(Integer) || @value.kind_of?(Float) || @value.kind_of?(String)
|
214
|
+
return other, @value
|
215
|
+
else
|
216
|
+
raise OEDLCommandException.new("coercion", "Illegal operation, "+
|
217
|
+
"The value of Experiment Property '#{@name}' cannot be coerced to allow "+
|
218
|
+
" the requested operation (current value is of type #{value.class})")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
data/lib/omf_ec/group.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'securerandom'
|
2
|
+
require 'monitor'
|
2
3
|
|
3
4
|
module OmfEc
|
4
5
|
# Group instance used in experiment script
|
@@ -9,12 +10,15 @@ module OmfEc
|
|
9
10
|
# @!attribute members [Array] holding members to be added to group
|
10
11
|
# @!attribute apps [Array] holding applications to be added to group
|
11
12
|
class Group
|
13
|
+
include MonitorMixin
|
14
|
+
|
12
15
|
attr_accessor :name, :id, :net_ifs, :members, :app_contexts
|
16
|
+
attr_reader :topic
|
13
17
|
|
14
18
|
# @param [String] name name of the group
|
15
19
|
# @param [Hash] opts
|
16
20
|
# @option opts [Boolean] :unique Should the group be unique or not, default is true
|
17
|
-
def initialize(name, opts = {})
|
21
|
+
def initialize(name, opts = {}, &block)
|
18
22
|
@opts = {unique: true}.merge!(opts)
|
19
23
|
self.name = name
|
20
24
|
self.id = @opts[:unique] ? SecureRandom.uuid : self.name
|
@@ -22,57 +26,45 @@ module OmfEc
|
|
22
26
|
self.net_ifs = []
|
23
27
|
self.members = []
|
24
28
|
self.app_contexts = []
|
25
|
-
end
|
26
|
-
|
27
|
-
# Add existing resources to the group
|
28
|
-
#
|
29
|
-
# Resources to be added could be a list of resources, groups, or the mixture of both.
|
30
|
-
def add_resource(*names)
|
31
|
-
names.each do |name|
|
32
|
-
# resource to add is a group
|
33
|
-
if OmfEc.exp.groups.any? { |v| v.name == name }
|
34
|
-
self.add_resource(*group(name).members.uniq)
|
35
|
-
else
|
36
|
-
OmfEc.comm.subscribe(name) do |m|
|
37
|
-
unless m.error?
|
38
|
-
# resource with uid: name is available
|
39
|
-
unless OmfEc.exp.state.any? { |v| v[:uid] == name }
|
40
|
-
OmfEc.exp.state << { uid: name }
|
41
|
-
end
|
42
|
-
|
43
|
-
r = OmfEc.comm.get_topic(name)
|
44
29
|
|
45
|
-
|
46
|
-
r = OmfEc.exp.state.find { |v| v[:uid] == i.read_property(:uid) }
|
47
|
-
unless r.nil?
|
48
|
-
i.each_property do |p|
|
49
|
-
key = p.attr('key').to_sym
|
50
|
-
r[key] = i.read_property(key)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
Experiment.instance.process_events
|
54
|
-
end
|
30
|
+
@resource_topics = {}
|
55
31
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
end
|
32
|
+
OmfEc.subscribe_and_monitor(id, self, &block)
|
33
|
+
super()
|
34
|
+
end
|
60
35
|
|
61
|
-
|
62
|
-
|
63
|
-
|
36
|
+
def associate_topic(topic)
|
37
|
+
self.synchronize do
|
38
|
+
@topic = topic
|
39
|
+
end
|
40
|
+
end
|
64
41
|
|
65
|
-
|
42
|
+
def associate_resource_topic(name, res_topic)
|
43
|
+
self.synchronize do
|
44
|
+
@resource_topics[name] = res_topic
|
45
|
+
end
|
46
|
+
end
|
66
47
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
Experiment.instance.process_events
|
71
|
-
end
|
48
|
+
def resource_topic(name)
|
49
|
+
@resource_topics[name]
|
50
|
+
end
|
72
51
|
|
73
|
-
|
74
|
-
|
75
|
-
|
52
|
+
# Add existing resources to the group
|
53
|
+
#
|
54
|
+
# Resources to be added could be a list of resources, groups, or the mixture of both.
|
55
|
+
def add_resource(*names)
|
56
|
+
self.synchronize do
|
57
|
+
# Recording membership first, used for ALL_UP event
|
58
|
+
names.each do |name|
|
59
|
+
g = OmfEc.experiment.group(name)
|
60
|
+
if g # resource to add is a group
|
61
|
+
@members += g.members
|
62
|
+
self.add_resource(*g.members.uniq)
|
63
|
+
else
|
64
|
+
@members << name.to_s
|
65
|
+
OmfEc.subscribe_and_monitor(name) do |res|
|
66
|
+
info "Config #{name} to join #{self.name}"
|
67
|
+
res.configure(membership: self.id)
|
76
68
|
end
|
77
69
|
end
|
78
70
|
end
|
@@ -84,73 +76,31 @@ module OmfEc
|
|
84
76
|
# @param [String] name
|
85
77
|
# @param [Hash] opts to be used to create new resources
|
86
78
|
def create_resource(name, opts, &block)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
# Naming convention of child resource group
|
96
|
-
resource_group_name = "#{self.id}_#{opts[:type]}"
|
97
|
-
|
98
|
-
|
99
|
-
unless OmfEc.exp.sub_groups.include?(resource_group_name)
|
100
|
-
OmfEc.exp.sub_groups << resource_group_name
|
101
|
-
|
102
|
-
rg = OmfEc.comm.get_topic(resource_group_name)
|
103
|
-
# Receive status inform message
|
104
|
-
rg.on_message lambda {|m| m.operation == :inform && m.read_content('inform_type') == 'STATUS' && m.context_id.nil? } do |i|
|
105
|
-
r = OmfEc.exp.state.find { |v| v[:uid] == i.read_property(:uid) }
|
106
|
-
unless r.nil?
|
107
|
-
if i.read_property("status_type") == 'APP_EVENT'
|
108
|
-
info "APP_EVENT #{i.read_property('event')} "+
|
109
|
-
"from app #{i.read_property("app")} - msg: #{i.read_property("msg")}"
|
110
|
-
end
|
111
|
-
i.each_property do |p|
|
112
|
-
r[p.attr('key').to_sym] = p.content.ducktype
|
113
|
-
end
|
114
|
-
end
|
115
|
-
Experiment.instance.process_events
|
116
|
-
end
|
117
|
-
|
118
|
-
# Receive failed inform message
|
119
|
-
rg.on_message lambda {|m| m.operation == :inform && m.read_content('inform_type') == 'FAILED' && m.context_id.nil? } do |i|
|
120
|
-
warn "RC reports failure: '#{i.read_content("reason")}'"
|
79
|
+
self.synchronize do
|
80
|
+
raise ArgumentError, "Option :type is required for creating resource" if opts[:type].nil?
|
81
|
+
|
82
|
+
# Make a deep copy of opts in case it contains structures of structures
|
83
|
+
begin
|
84
|
+
opts = Marshal.load ( Marshal.dump(opts.merge(hrn: name)))
|
85
|
+
rescue Exception => e
|
86
|
+
raise "#{e.message} - Could not deep copy opts: '#{opts.inspect}'"
|
121
87
|
end
|
122
|
-
end
|
123
|
-
|
124
|
-
# We create another group topic for new resouce
|
125
|
-
OmfEc.comm.subscribe(resource_group_name, create_if_non_existent: true) do |m|
|
126
|
-
unless m.error?
|
127
88
|
|
128
|
-
|
129
|
-
|
130
|
-
opts.each_pair do |k, v|
|
131
|
-
m.property(k, v)
|
132
|
-
end
|
133
|
-
end
|
89
|
+
# Naming convention of child resource group
|
90
|
+
resource_group_name = "#{self.id}_#{opts[:type].to_s}"
|
134
91
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
block.call if block
|
141
|
-
Experiment.instance.process_events
|
142
|
-
end
|
143
|
-
|
144
|
-
c.on_inform_failed do |i|
|
145
|
-
warn "RC reports failure: '#{i.read_content("reason")}'"
|
146
|
-
end
|
92
|
+
OmfEc.subscribe_and_monitor(resource_group_name) do |res_group|
|
93
|
+
associate_resource_topic(opts[:type].to_s, res_group)
|
94
|
+
# Send create message to group
|
95
|
+
r_type = opts.delete(:type)
|
96
|
+
@topic.create(r_type, opts.merge(membership: resource_group_name))
|
147
97
|
end
|
148
98
|
end
|
149
99
|
end
|
150
100
|
|
151
101
|
# @return [OmfEc::Context::GroupContext]
|
152
102
|
def resources
|
153
|
-
OmfEc::Context::GroupContext.new(group: self
|
103
|
+
OmfEc::Context::GroupContext.new(group: self)
|
154
104
|
end
|
155
105
|
|
156
106
|
include OmfEc::Backward::Group
|
data/lib/omf_ec/version.rb
CHANGED
data/lib/omf_ec.rb
CHANGED
@@ -5,6 +5,7 @@ require 'omf_ec/backward/app_definition'
|
|
5
5
|
require 'omf_ec/backward/default_events'
|
6
6
|
require 'omf_ec/backward/core_ext/array'
|
7
7
|
require "omf_ec/version"
|
8
|
+
require "omf_ec/experiment_property"
|
8
9
|
require "omf_ec/experiment"
|
9
10
|
require "omf_ec/group"
|
10
11
|
require "omf_ec/app_definition"
|
@@ -22,17 +23,68 @@ module OmfEc
|
|
22
23
|
|
23
24
|
alias_method :exp, :experiment
|
24
25
|
|
25
|
-
# Experiment's communicator instance
|
26
|
-
# @return [OmfCommon::Comm]
|
27
|
-
def communicator
|
28
|
-
Experiment.instance.comm
|
29
|
-
end
|
30
|
-
|
31
26
|
# Full path of lib directory
|
32
27
|
def lib_root
|
33
28
|
File.expand_path("../..", "#{__FILE__}/lib")
|
34
29
|
end
|
35
30
|
|
36
|
-
|
31
|
+
def register_default_callback(topic)
|
32
|
+
topic.on_creation_failed do |msg|
|
33
|
+
warn "RC reports creation.failed: '#{msg[:reason]}'"
|
34
|
+
debug msg
|
35
|
+
end
|
36
|
+
|
37
|
+
topic.on_error do |msg|
|
38
|
+
warn "RC reports error: '#{msg[:reason]}'"
|
39
|
+
debug msg
|
40
|
+
end
|
41
|
+
|
42
|
+
topic.on_warn do |msg|
|
43
|
+
warn "RC reports warning: '#{msg[:reason]}'"
|
44
|
+
debug msg
|
45
|
+
end
|
46
|
+
|
47
|
+
topic.on_creation_ok do |msg|
|
48
|
+
debug "Received CREATION.OK via #{topic.id}"
|
49
|
+
info "Resource #{msg[:res_id]} created"
|
50
|
+
|
51
|
+
OmfEc.experiment.add_or_update_resource_state(msg[:uid], msg.properties)
|
52
|
+
|
53
|
+
OmfEc.experiment.process_events
|
54
|
+
end
|
55
|
+
|
56
|
+
topic.on_status do |msg|
|
57
|
+
props = []
|
58
|
+
msg.each_property { |k, v| props << "#{k}: #{v}" }
|
59
|
+
debug "#{topic.id} >> inform: #{props.join(", ")}"
|
60
|
+
|
61
|
+
if msg[:status_type] == 'APP_EVENT'
|
62
|
+
info "APP_EVENT #{msg[:event]} from app #{msg[:app]} - msg: #{msg[:msg]}"
|
63
|
+
end
|
64
|
+
|
65
|
+
OmfEc.experiment.add_or_update_resource_state(msg[:uid], msg.properties)
|
66
|
+
OmfEc.experiment.process_events
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
#TODO: Could we find a better name for this method?
|
71
|
+
def subscribe_and_monitor(topic_id, context_obj = nil, &block)
|
72
|
+
topic = OmfCommon::Comm::Topic[topic_id]
|
73
|
+
if topic.nil?
|
74
|
+
OmfCommon.comm.subscribe(topic_id) do |topic|
|
75
|
+
if topic.error?
|
76
|
+
error "Failed to subscribe #{topic_id}"
|
77
|
+
else
|
78
|
+
info "Subscribed to #{topic_id}"
|
79
|
+
context_obj.associate_topic(topic) if context_obj
|
80
|
+
block.call(context_obj || topic) if block
|
81
|
+
register_default_callback(topic)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
else
|
85
|
+
context_obj.associate_topic(topic) if context_obj
|
86
|
+
block.call(context_obj || topic) if block
|
87
|
+
end
|
88
|
+
end
|
37
89
|
end
|
38
90
|
end
|
data/omf_ec.gemspec
CHANGED
@@ -7,9 +7,11 @@ Gem::Specification.new do |s|
|
|
7
7
|
s.version = OmfEc::VERSION
|
8
8
|
s.authors = ["NICTA"]
|
9
9
|
s.email = ["omf-user@lists.nicta.com.au"]
|
10
|
-
s.homepage = "
|
10
|
+
s.homepage = "http://omf.mytestbed.net"
|
11
11
|
s.summary = %q{OMF experiment controller}
|
12
12
|
s.description = %q{Experiment controller of OMF, a generic framework for controlling and managing networking testbeds.}
|
13
|
+
s.required_ruby_version = '>= 1.9.3'
|
14
|
+
s.license = 'MIT'
|
13
15
|
|
14
16
|
s.rubyforge_project = "omf_ec"
|
15
17
|
|
@@ -21,5 +23,5 @@ Gem::Specification.new do |s|
|
|
21
23
|
# specify any dependencies here; for example:
|
22
24
|
s.add_development_dependency "minitest", "~> 3.2"
|
23
25
|
s.add_runtime_dependency "omf_common", "~> 6.0.0.pre"
|
24
|
-
s.add_runtime_dependency "gli", "~> 2.
|
26
|
+
s.add_runtime_dependency "gli", "~> 2.5.3"
|
25
27
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'omf_ec/dsl'
|
3
|
+
|
4
|
+
describe OmfEc::DSL do
|
5
|
+
describe "when included" do
|
6
|
+
|
7
|
+
include OmfEc::DSL
|
8
|
+
|
9
|
+
it "must respond to after and every" do
|
10
|
+
respond_to?(:after).must_equal true
|
11
|
+
respond_to?(:every).must_equal true
|
12
|
+
end
|
13
|
+
|
14
|
+
it "must respond to def_property" do
|
15
|
+
@exp = MiniTest::Mock.new
|
16
|
+
@exp.expect(:add_property, true, [String, Object, String])
|
17
|
+
|
18
|
+
OmfEc.stub :experiment, @exp do
|
19
|
+
def_property('name', 'default', 'testing')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it "must respond to def_application" do
|
24
|
+
block = proc { 1 }
|
25
|
+
def_application('bob', &block).must_equal 1
|
26
|
+
OmfEc.experiment.app_definitions.key?('bob').must_equal true
|
27
|
+
end
|
28
|
+
|
29
|
+
it "must respond to def_group" do
|
30
|
+
block = proc { 1 }
|
31
|
+
OmfEc.stub :subscribe_and_monitor, true do
|
32
|
+
def_group('bob', &block).must_be_kind_of OmfEc::Group
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it "must respond to all_groups iterator" do
|
37
|
+
block = proc { 1 }
|
38
|
+
all_groups(&block)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "must respond to all_groups?" do
|
42
|
+
OmfEc.stub :subscribe_and_monitor, true do
|
43
|
+
OmfEc.experiment.stub :groups, [] do
|
44
|
+
all_groups? { true }.must_equal false
|
45
|
+
end
|
46
|
+
def_group('bob')
|
47
|
+
all_groups? { |g| g.name == 'bob' }.must_equal true
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'omf_ec/experiment_property'
|
3
|
+
require 'omf_ec/dsl'
|
4
|
+
include OmfEc::DSL
|
5
|
+
|
6
|
+
describe OmfEc::ExperimentProperty do
|
7
|
+
|
8
|
+
describe "when a new ExperimentProperty is created" do
|
9
|
+
it "must raise an error if it is given an invalid name" do
|
10
|
+
created_properties = 0
|
11
|
+
# Test only a few common invalid name patterns
|
12
|
+
%w(12 a=b a/b 1a .a a.b a?b a!b !a ?a #a $a @a %a).each do |name|
|
13
|
+
begin
|
14
|
+
OmfEc::ExperimentProperty.create(name)
|
15
|
+
created_properties = created_properties + 1
|
16
|
+
rescue Exception => ex
|
17
|
+
ex.must_be_kind_of OEDLCommandException
|
18
|
+
end
|
19
|
+
end
|
20
|
+
created_properties.must_equal 0
|
21
|
+
end
|
22
|
+
|
23
|
+
it "must not create a new property if one already exist with the same name" do
|
24
|
+
size_before = OmfEc::ExperimentProperty.length
|
25
|
+
OmfEc::ExperimentProperty.create('bar','a')
|
26
|
+
OmfEc::ExperimentProperty[:bar].value.must_equal 'a'
|
27
|
+
OmfEc::ExperimentProperty.create('bar','b')
|
28
|
+
OmfEc::ExperimentProperty[:bar].value.must_equal 'b'
|
29
|
+
OmfEc::ExperimentProperty.length.must_equal (size_before + 1)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "must return a properly set ExperimentProperty object" do
|
33
|
+
size_before = OmfEc::ExperimentProperty.length
|
34
|
+
OmfEc::ExperimentProperty.create('foo', 1, 'abc')
|
35
|
+
OmfEc::ExperimentProperty[:foo].name.must_equal 'foo'
|
36
|
+
OmfEc::ExperimentProperty[:foo].value.must_equal 1
|
37
|
+
OmfEc::ExperimentProperty[:foo].description.must_equal 'abc'
|
38
|
+
OmfEc::ExperimentProperty.length.must_equal (size_before + 1)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "must inform all of its observers when its value changes" do
|
42
|
+
value = 2
|
43
|
+
foobar = OmfEc::ExperimentProperty.create('foobar',1)
|
44
|
+
foobar.on_change { |v| v.must_equal value }
|
45
|
+
foobar.on_change { |v| (v*2).must_equal value*2 }
|
46
|
+
OmfEc::ExperimentProperty[:foobar] = value
|
47
|
+
OmfEc::ExperimentProperty[:foobar].value.must_equal value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "when a the Class ExperimentProperty is creating a new property" do
|
52
|
+
it "must inform all of its observers" do
|
53
|
+
size_before = OmfEc::ExperimentProperty.length
|
54
|
+
OmfEc::ExperimentProperty.add_observer do |c,p|
|
55
|
+
p.name.must_equal 'barfoo'
|
56
|
+
p.value.must_equal 123
|
57
|
+
p.description.must_equal 'abc'
|
58
|
+
end
|
59
|
+
OmfEc::ExperimentProperty.create('barfoo', 123, 'abc')
|
60
|
+
OmfEc::ExperimentProperty.length.must_equal (size_before + 1)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "when an operation involves an ExperimentProperty" do
|
65
|
+
it "must return the expected result" do
|
66
|
+
OmfEc::ExperimentProperty[:foo] = 2
|
67
|
+
(OmfEc::ExperimentProperty[:foo] + 1).must_equal 3
|
68
|
+
(1 + OmfEc::ExperimentProperty[:foo]).must_equal 3
|
69
|
+
(OmfEc::ExperimentProperty[:foo] - 1).must_equal 1
|
70
|
+
(1 - OmfEc::ExperimentProperty[:foo]).must_equal -1
|
71
|
+
(OmfEc::ExperimentProperty[:foo] * 2).must_equal 4
|
72
|
+
(2 * OmfEc::ExperimentProperty[:foo]).must_equal 4
|
73
|
+
(OmfEc::ExperimentProperty[:foo] / 1).must_equal 2
|
74
|
+
(2 / OmfEc::ExperimentProperty[:foo]).must_equal 1
|
75
|
+
OmfEc::ExperimentProperty[:bar] = 'a'
|
76
|
+
(OmfEc::ExperimentProperty[:bar] + "b").must_equal 'ab'
|
77
|
+
('b' + OmfEc::ExperimentProperty[:bar]).must_equal 'ba'
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|