sfpagent 0.1.6 → 0.1.9
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.
Potentially problematic release.
This version of sfpagent might be problematic. Click here for more details.
- data/README.md +9 -9
- data/VERSION +1 -1
- data/bin/sfpagent +5 -3
- data/lib/sfpagent/agent.rb +252 -153
- data/lib/sfpagent/bsig.rb +301 -12
- data/lib/sfpagent/module.rb +7 -3
- data/lib/sfpagent/runtime.rb +113 -89
- data/lib/sfpagent/sfplanner.rb +85 -0
- data/lib/sfpagent.rb +1 -0
- data/sfpagent.gemspec +1 -1
- metadata +5 -4
data/lib/sfpagent/bsig.rb
CHANGED
@@ -1,22 +1,311 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
class Sfp::BSig
|
4
|
+
include Nuri::Net::Helper
|
5
|
+
|
6
|
+
BSigSleepTime = 5
|
7
|
+
MaxTries = 5
|
8
|
+
|
9
|
+
SatisfierPath = '/bsig/satisfier'
|
10
|
+
CachedDir = (Process.euid == 0 ? '/var/sfpagent' : File.expand_path('~/.sfpagent'))
|
11
|
+
SatisfierLockFile = CachedDir + '/bsig.satisfier.lock'
|
12
|
+
|
13
|
+
attr_accessor :enabled, :mode
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@lock = Mutex.new
|
17
|
+
@enabled = false
|
18
|
+
end
|
19
|
+
|
20
|
+
def disable
|
21
|
+
@enabled = false
|
22
|
+
end
|
23
|
+
|
24
|
+
def enable(p={})
|
25
|
+
@lock.synchronize {
|
26
|
+
return if @enabled
|
27
|
+
@enabled = true
|
28
|
+
}
|
29
|
+
|
30
|
+
if p[:mode] == :main
|
31
|
+
enable_main_thread
|
32
|
+
elsif p[:mode] == :satisfier
|
33
|
+
enable_satisfier_thread
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def enable_satisfier_thread
|
38
|
+
@mode = :satisfier
|
39
|
+
Sfp::Agent.logger.info "[satisfier] BSig engine is enabled."
|
40
|
+
end
|
41
|
+
|
42
|
+
def enable_main_thread
|
43
|
+
@mode = :main
|
44
|
+
|
45
|
+
['INT', 'KILL', 'HUP'].each { |signal|
|
46
|
+
trap(signal) {
|
47
|
+
Sfp::Agent.logger.info "Shutting down BSig engine"
|
48
|
+
disable
|
49
|
+
}
|
50
|
+
}
|
51
|
+
register_satisfier_thread(:reset)
|
52
|
+
|
53
|
+
Sfp::Agent.logger.info "[main] BSig engine is running."
|
54
|
+
|
55
|
+
puts "BSig Engine is running with PID #{$$}"
|
56
|
+
File.open(Sfp::Agent::BSigPIDFile, 'w') { |f| f.write($$.to_s) }
|
57
|
+
|
58
|
+
self.execute_model
|
59
|
+
|
60
|
+
Sfp::Agent.logger.info "[main] BSig engine has stopped."
|
61
|
+
end
|
3
62
|
|
4
|
-
module Sfp::BSig::Main
|
5
63
|
def execute_model
|
6
|
-
|
64
|
+
while @enabled
|
65
|
+
begin
|
66
|
+
Sfp::Agent.logger.info "[main] Sfp::BSig enabled"
|
67
|
+
|
68
|
+
wait_for_satisfier?
|
69
|
+
|
70
|
+
bsig = Sfp::Agent.get_bsig
|
71
|
+
if bsig.nil?
|
72
|
+
sleep BSigSleepTime
|
73
|
+
else
|
74
|
+
status = achieve_local_goal(bsig['id'], bsig['goal'], bsig['operators'], 1, :main)
|
75
|
+
Sfp::Agent.logger.info "[main] execute model - status: " + status.to_s
|
76
|
+
if status == :failure
|
77
|
+
Sfp::Agent.logger.error "[main] Executing BSig model [Failed]"
|
78
|
+
sleep BSigSleepTime
|
79
|
+
elsif status == :no_flaw
|
80
|
+
sleep BSigSleepTime
|
81
|
+
end
|
82
|
+
end
|
83
|
+
rescue Exception => e
|
84
|
+
Sfp::Agent.logger.error "Error on executing BSig model\n#{e}\n#{e.backtrace.join("\n")}"
|
85
|
+
sleep BSigSleepTime
|
86
|
+
end
|
87
|
+
end
|
7
88
|
end
|
8
89
|
|
9
|
-
def
|
10
|
-
|
90
|
+
def wait_for_satisfier?
|
91
|
+
total_satisfier = 1
|
92
|
+
loop do
|
93
|
+
total_satisfier = (File.exist?(SatisfierLockFile) ? File.read(SatisfierLockFile).to_i : 0)
|
94
|
+
return if total_satisfier <= 0
|
95
|
+
sleep 1
|
96
|
+
end
|
11
97
|
end
|
12
98
|
|
13
|
-
|
14
|
-
|
99
|
+
# returns
|
100
|
+
# :no_flaw : there is no goal-flaw
|
101
|
+
# :failure : there is a failure on achieving the goal
|
102
|
+
# :ongoing : the selected operator is being executed
|
103
|
+
# :repaired : some goal-flaws have been repaired, but the goal may have other flaws
|
104
|
+
def achieve_local_goal(id, goal, operators, pi, mode=nil)
|
105
|
+
operator = nil
|
106
|
+
|
107
|
+
current = get_current_state
|
108
|
+
flaws = compute_flaws(goal, current)
|
109
|
+
return :no_flaw if flaws.length <= 0
|
110
|
+
Sfp::Agent.logger.info "[#{mode}] Flaws: #{JSON.generate(flaws)}"
|
111
|
+
|
112
|
+
operator = select_operator(flaws, operators, pi)
|
113
|
+
return :failure if operator.nil?
|
114
|
+
|
115
|
+
@lock.synchronize {
|
116
|
+
return :ongoing if operator['selected']
|
117
|
+
operator['selected'] = true
|
118
|
+
Sfp::Agent.logger.info "[#{mode}] Selected operator: #{JSON.generate(operator)}"
|
119
|
+
}
|
120
|
+
|
121
|
+
next_pi = pi + 1
|
122
|
+
pre_local, pre_remote = split_preconditions(operator)
|
123
|
+
|
124
|
+
Sfp::Agent.logger.info "[#{mode}] local-flaws: #{JSON.generate(pre_local)}"
|
125
|
+
Sfp::Agent.logger.info "[#{mode}] remote-flaws: #{JSON.generate(pre_remote)}"
|
126
|
+
|
127
|
+
status = nil
|
128
|
+
tries = MaxTries
|
129
|
+
begin
|
130
|
+
status = achieve_local_goal(id, pre_local, operators, next_pi)
|
131
|
+
if status == :no_flaw or status == :failure or not @enabled
|
132
|
+
break
|
133
|
+
elsif status == :ongoing
|
134
|
+
sleep BSigSleepTime
|
135
|
+
tries += 1
|
136
|
+
elsif status == :repaired
|
137
|
+
tries = MaxTries
|
138
|
+
end
|
139
|
+
tries -= 1
|
140
|
+
end until tries <= 0
|
141
|
+
|
142
|
+
#Sfp::Agent.logger.info "[#{mode}] status local: " + status.to_s
|
143
|
+
return :failure if status == :failure
|
144
|
+
|
145
|
+
return :failure if not achieve_remote_goal(id, pre_remote, next_pi)
|
146
|
+
|
147
|
+
return :failure if not invoke(operator)
|
148
|
+
|
149
|
+
:repaired
|
150
|
+
|
151
|
+
ensure
|
152
|
+
@lock.synchronize { operator['selected'] = false } if not operator.nil?
|
153
|
+
end
|
154
|
+
|
155
|
+
def achieve_remote_goal(id, goal, pi)
|
156
|
+
if goal.length > 0
|
157
|
+
agents = Sfp::Agent.get_agents
|
158
|
+
split_goal_by_agent(goal).each do |agent,g|
|
159
|
+
return false if not agents.has_key?(agent) or agents[agent]['sfpAddress'].to_s == ''
|
160
|
+
return false if not send_goal_to_agent(agents[agent], id, g, pi)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
true
|
164
|
+
end
|
165
|
+
|
166
|
+
def receive_goal_from_agent(id, goal, pi)
|
167
|
+
register_satisfier_thread
|
168
|
+
|
169
|
+
Sfp::Agent.logger.info "[satisfier] enabled: " + @enabled.to_s
|
170
|
+
return false if not @enabled
|
171
|
+
|
172
|
+
bsig = Sfp::Agent.get_bsig
|
173
|
+
|
174
|
+
Sfp::Agent.logger.info "[satisfier] receive_goal_from_agent - " + id.inspect + " - " + goal.inspect + " - " + pi.inspect
|
175
|
+
Sfp::Agent.logger.info "[satisfier] " + bsig.inspect
|
176
|
+
|
177
|
+
return false if bsig.nil? or id < bsig['id']
|
178
|
+
|
179
|
+
status = nil
|
180
|
+
tries = MaxTries
|
181
|
+
begin
|
182
|
+
status = achieve_local_goal(bsig['id'], goal, bsig['operators'], pi, :satisfier)
|
183
|
+
if status == :no_flaw or status == :failure or not @enabled
|
184
|
+
break
|
185
|
+
elsif status == :ongoing
|
186
|
+
sleep BSigSleepTime
|
187
|
+
tries += 1
|
188
|
+
elsif status == :repaired
|
189
|
+
tries = MaxTries
|
190
|
+
end
|
191
|
+
tries -= 1
|
192
|
+
end until tries <= 0
|
193
|
+
|
194
|
+
return false if status == :failure
|
195
|
+
|
196
|
+
true
|
197
|
+
|
198
|
+
ensure
|
199
|
+
unregister_satisfier_thread
|
200
|
+
end
|
201
|
+
|
202
|
+
protected
|
203
|
+
def register_satisfier_thread(mode=nil)
|
204
|
+
return if mode == :reset and File.exist?(SatisfierLockFile)
|
205
|
+
|
206
|
+
File.open(SatisfierLockFile, File::RDWR|File::CREAT, 0644) { |f|
|
207
|
+
f.flock(File::LOCK_EX)
|
208
|
+
value = (mode == :reset ? 0 : (f.read.to_i + 1))
|
209
|
+
f.rewind
|
210
|
+
f.write(value.to_s)
|
211
|
+
f.flush
|
212
|
+
f.truncate(f.pos)
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
def unregister_satisfier_thread
|
217
|
+
File.open(SatisfierLockFile, File::RDWR|File::CREAT, 0644) { |f|
|
218
|
+
f.flock(File::LOCK_EX)
|
219
|
+
value = f.read.to_i - 1
|
220
|
+
f.rewind
|
221
|
+
f.write(value.to_s)
|
222
|
+
f.flush
|
223
|
+
f.truncate(f.pos)
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
def split_goal_by_agent(goal)
|
228
|
+
agents = Sfp::Agent.get_agents
|
229
|
+
agent_goal = {}
|
230
|
+
goal.each { |var,val|
|
231
|
+
_, agent_name, _ = var.split('.', 3)
|
232
|
+
fail "Agent #{agent_name} is not in database!" if not agents.has_key?(agent_name)
|
233
|
+
agent_goal[agent_name] = {} if not agent_goal.has_key?(agent_name)
|
234
|
+
agent_goal[agent_name][var] = val
|
235
|
+
}
|
236
|
+
agent_goal
|
237
|
+
end
|
238
|
+
|
239
|
+
def send_goal_to_agent(agent, id, g, pi)
|
240
|
+
data = {'id' => id,
|
241
|
+
'goal' => JSON.generate(g),
|
242
|
+
'pi' => pi}
|
243
|
+
code, _ = put_data(agent['sfpAddress'], agent['sfpPort'], SatisfierPath, data)
|
244
|
+
Sfp::Agent.logger.info "send_goal_to_agent - status: " + code.to_s
|
245
|
+
(code == '200')
|
246
|
+
end
|
247
|
+
|
248
|
+
def get_current_state
|
249
|
+
state = Sfp::Agent.get_state
|
250
|
+
fail "BSig engine cannot get current state" if state == false
|
251
|
+
state
|
252
|
+
end
|
253
|
+
|
254
|
+
def compute_flaws(goal, current)
|
255
|
+
return goal.clone if current.nil?
|
256
|
+
flaws = {}
|
257
|
+
goal.each { |var,val|
|
258
|
+
current_value = current.at?(var)
|
259
|
+
if current_value.is_a?(Sfp::Undefined)
|
260
|
+
flaws[var] = val if not val.is_a?(Sfp::Undefined)
|
261
|
+
else
|
262
|
+
current_value.sort! if current_value.is_a?(Array)
|
263
|
+
flaws[var]= val if current_value != val
|
264
|
+
end
|
265
|
+
}
|
266
|
+
flaws
|
267
|
+
end
|
268
|
+
|
269
|
+
def select_operator(flaws, operators, pi)
|
270
|
+
selected_operator = nil
|
271
|
+
operators.each { |op|
|
272
|
+
next if op['pi'] < pi
|
273
|
+
if can_repair?(op, flaws)
|
274
|
+
if selected_operator.nil?
|
275
|
+
selected_operator = op
|
276
|
+
elsif selected_operator['pi'] > op['pi']
|
277
|
+
selected_operator = op
|
278
|
+
end
|
279
|
+
end
|
280
|
+
}
|
281
|
+
selected_operator
|
282
|
+
end
|
283
|
+
|
284
|
+
def can_repair?(operator, flaws)
|
285
|
+
operator['effect'].each { |var,val|
|
286
|
+
return true if flaws[var] == val
|
287
|
+
}
|
288
|
+
false
|
289
|
+
end
|
290
|
+
|
291
|
+
def split_preconditions(operator)
|
292
|
+
local = {}
|
293
|
+
remote = {}
|
294
|
+
if not operator.nil?
|
295
|
+
myself = Sfp::Agent.whoami?
|
296
|
+
operator['condition'].each { |var,val|
|
297
|
+
_, agent_name, _ = var.split('.', 3)
|
298
|
+
if agent_name == myself
|
299
|
+
local[var] = val
|
300
|
+
else
|
301
|
+
remote[var] = val
|
302
|
+
end
|
303
|
+
}
|
304
|
+
end
|
305
|
+
[local, remote]
|
15
306
|
end
|
16
|
-
end
|
17
307
|
|
18
|
-
|
19
|
-
|
20
|
-
# TODO -- implement
|
308
|
+
def invoke(operator)
|
309
|
+
Sfp::Agent.execute_action(operator)
|
21
310
|
end
|
22
311
|
end
|
data/lib/sfpagent/module.rb
CHANGED
@@ -5,18 +5,22 @@ module Sfp::Resource
|
|
5
5
|
attr_accessor :parent, :synchronized
|
6
6
|
attr_reader :state, :model
|
7
7
|
|
8
|
-
def init(model
|
8
|
+
def init(model={})
|
9
9
|
@model = {}
|
10
|
-
model.each { |k,v| @model[k] = v }
|
11
10
|
@state = {}
|
12
|
-
@default = {}
|
13
11
|
@synchronized = []
|
12
|
+
|
13
|
+
update_model(model)
|
14
14
|
end
|
15
15
|
|
16
16
|
def update_state
|
17
17
|
@state = {}
|
18
18
|
end
|
19
19
|
|
20
|
+
def update_model(model)
|
21
|
+
model.each { |k,v| @model[k] = v }
|
22
|
+
end
|
23
|
+
|
20
24
|
def apply(p={})
|
21
25
|
true
|
22
26
|
end
|
data/lib/sfpagent/runtime.rb
CHANGED
@@ -1,25 +1,31 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
3
|
class Sfp::Runtime
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :root
|
5
5
|
|
6
6
|
def initialize(model)
|
7
7
|
@mutex_procedure = Mutex.new
|
8
|
-
@
|
9
|
-
@
|
8
|
+
@mutex_get_state = Mutex.new
|
9
|
+
@root = nil
|
10
|
+
set_model(model)
|
11
|
+
end
|
12
|
+
|
13
|
+
def whoami?
|
14
|
+
@model.each { |key,value| return key if key[0,1] != '_' and value['_context'] == 'object' } if !@model.nil?
|
15
|
+
nil
|
10
16
|
end
|
11
17
|
|
12
18
|
def execute_action(action)
|
19
|
+
return false if !defined?(@root) or @root.nil?
|
20
|
+
|
13
21
|
def normalise_parameters(params)
|
14
22
|
p = {}
|
15
23
|
params.each { |k,v| p[k[2,k.length-2]] = v }
|
16
24
|
p
|
17
25
|
end
|
18
26
|
|
19
|
-
self.get_state if not defined?(@modules) or @modules.nil?
|
20
|
-
|
21
27
|
module_path, method_name = action['name'].pop_ref
|
22
|
-
mod = @
|
28
|
+
mod = @root.at?(module_path)[:_self]
|
23
29
|
raise Exception, "Module #{module_path} cannot be found!" if mod.nil?
|
24
30
|
raise Exception, "Cannot execute #{action['name']}!" if not mod.respond_to?(method_name)
|
25
31
|
|
@@ -33,102 +39,109 @@ class Sfp::Runtime
|
|
33
39
|
# TODO - check post-execution state for verification
|
34
40
|
end
|
35
41
|
|
42
|
+
def set_model(model)
|
43
|
+
@mutex_get_state.synchronize {
|
44
|
+
@model = model
|
45
|
+
if @model.is_a?(Hash)
|
46
|
+
root_model = Sfp::Helper.deep_clone(@model)
|
47
|
+
root_model.accept(SFPtoRubyValueConverter)
|
48
|
+
root_model.accept(ParentEliminator)
|
49
|
+
@root = update_model(root_model, root_model, '$')
|
50
|
+
@root.accept(ParentGenerator)
|
51
|
+
end
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
36
55
|
def get_state(as_sfp=false)
|
37
|
-
|
38
|
-
|
39
|
-
|
56
|
+
@mutex_get_state.synchronize {
|
57
|
+
update_state(@root)
|
58
|
+
get_object_state(@root, @model)
|
59
|
+
}
|
60
|
+
end
|
40
61
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
62
|
+
protected
|
63
|
+
def get_object_state(object, model)
|
64
|
+
# get object's state
|
65
|
+
state = (object.has_key?(:_self) ? object[:_self].state : {})
|
66
|
+
|
67
|
+
# add hidden attributes and procedures
|
68
|
+
model.each { |k,v|
|
69
|
+
state[k] = v if (k[0,1] == '_' and k != '_parent') or
|
70
|
+
(v.is_a?(Hash) and v['_context'] == 'procedure')
|
71
|
+
}
|
72
|
+
|
73
|
+
# accumulate children's state
|
74
|
+
object.each { |name,child|
|
75
|
+
next if name.to_s[0,1] == '_'
|
76
|
+
state[name] = get_object_state(child, model[name])
|
77
|
+
}
|
78
|
+
|
79
|
+
# set state=Sfp::Undefined for each attribute that exists in the model
|
80
|
+
# but not covered by SFP object instants
|
81
|
+
(model.keys - state.keys).each do |name|
|
82
|
+
next if name[0,1] == '_'
|
83
|
+
state[name] = Sfp::Undefined.new
|
46
84
|
end
|
47
85
|
|
48
|
-
|
49
|
-
|
50
|
-
# @return a Hash which is the state of the object
|
51
|
-
#
|
52
|
-
def instantiate_module(model, root, as_sfp=false)
|
53
|
-
# extract class name
|
54
|
-
class_name = model['_isa'].sub(/^\$\./, '')
|
55
|
-
|
56
|
-
# throw an exception if schema's implementation is not exist!
|
57
|
-
raise Exception, "Implementation of schema #{class_name} is not available!" if
|
58
|
-
not Sfp::Module.const_defined?(class_name)
|
59
|
-
|
60
|
-
# create an instance of the schema
|
61
|
-
mod = Sfp::Module::const_get(class_name).new
|
62
|
-
default = cleanup(root.at?(model['_isa']))
|
63
|
-
ruby_model = cleanup(model)
|
64
|
-
mod.init(ruby_model, default)
|
65
|
-
|
66
|
-
# update synchronized list of procedures
|
67
|
-
model.each { |k,v|
|
68
|
-
next if k[0,1] == '_' or not (v.is_a?(Hash) and v['_context'] == 'procedure')
|
69
|
-
mod.synchronized << k if v['_synchronized']
|
70
|
-
}
|
71
|
-
|
72
|
-
# return the object instant
|
73
|
-
mod
|
74
|
-
end
|
86
|
+
state
|
87
|
+
end
|
75
88
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
state = {}
|
81
|
-
if model['_context'] == 'object' and model['_isa'].to_s.isref
|
82
|
-
if model['_isa'] != '$.Object'
|
83
|
-
# if this model is an instance of a subclass of Object, then
|
84
|
-
# get the current state of this object
|
85
|
-
#modules[:_self] = nil
|
86
|
-
mod = (!defined?(@modules) or @modules.nil? ? nil : @modules.at?(path))
|
87
|
-
if mod.is_a?(Hash)
|
88
|
-
modules[:_self] = mod[:_self]
|
89
|
-
else
|
90
|
-
# the module has not been instantiated yet!
|
91
|
-
modules[:_self] = instantiate_module(model, root, as_sfp)
|
92
|
-
end
|
93
|
-
# update and get the state
|
94
|
-
modules[:_self].update_state
|
95
|
-
state = modules[:_self].state
|
96
|
-
if !mod.nil? and mod.has_key?(:_vars)
|
97
|
-
state.keep_if { |k,v| mod[:_vars].index(k) }
|
98
|
-
modules[:_vars] = mod[:_vars]
|
99
|
-
else
|
100
|
-
modules[:_vars] = state.keys
|
101
|
-
end
|
102
|
-
# set hidden attributes
|
103
|
-
add_hidden_attributes(model, state) if as_sfp
|
104
|
-
end
|
105
|
-
end
|
89
|
+
def update_state(object)
|
90
|
+
object[:_self].update_state if not object[:_self].nil?
|
91
|
+
object.each { |k,v| update_state(v) if k.to_s[0,1] != '_' }
|
92
|
+
end
|
106
93
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
94
|
+
def update_model(model, root, path)
|
95
|
+
object = {}
|
96
|
+
if model['_context'] == 'object' and model['_isa'].to_s.isref and model['_isa'].to_s != '$.Object'
|
97
|
+
# if this model is an instance of a subclass of Object, then
|
98
|
+
# get the current state of this object
|
99
|
+
obj = (!defined?(@root) or @root.nil? ? nil : @root.at?(path))
|
100
|
+
if obj.is_a?(Hash)
|
101
|
+
object[:_self] = obj[:_self]
|
102
|
+
object[:_self].update_model(model)
|
103
|
+
else
|
104
|
+
Sfp::Agent.logger.info "Instantiating object: #{model['_self']}"
|
105
|
+
# the module has not been instantiated yet!
|
106
|
+
object[:_self] = instantiate_sfp_object(model, root)
|
118
107
|
end
|
108
|
+
end
|
119
109
|
|
120
|
-
|
110
|
+
model.each do |key,child|
|
111
|
+
next if key[0,1] == '_' or not child.is_a?(Hash) or child['_context'] != 'object' or
|
112
|
+
child['_isa'].to_s == '$.Object'
|
113
|
+
#not child['_isa'].to_s.isref or child['_isa'].to_s == '$.Object'
|
114
|
+
object[key] = update_model(child, root, path.push(key))
|
121
115
|
end
|
122
116
|
|
123
|
-
|
124
|
-
|
125
|
-
@modules, state = get_object_state(root, root, as_sfp)
|
126
|
-
@modules.accept(ParentGenerator)
|
117
|
+
object
|
118
|
+
end
|
127
119
|
|
128
|
-
|
120
|
+
def instantiate_sfp_object(model, root)
|
121
|
+
# get SFP schema name
|
122
|
+
schema_name = model['_isa'].sub(/^\$\./, '')
|
123
|
+
|
124
|
+
# throw an exception if schema's implementation is not exist!
|
125
|
+
raise Exception, "Implementation of schema #{schema_name} is not available!" if
|
126
|
+
not Sfp::Module.const_defined?(schema_name)
|
127
|
+
|
128
|
+
# create an instance of the schema
|
129
|
+
object = Sfp::Module::const_get(schema_name).new
|
130
|
+
|
131
|
+
# initialize the instance
|
132
|
+
object_model = model.select { |k,v| k[0,1] != '_' and
|
133
|
+
not (v.is_a?(Hash) and v['_context'] == 'procedure') }
|
134
|
+
object.init(model)
|
135
|
+
|
136
|
+
# update list of synchronized procedures
|
137
|
+
model.each { |k,v|
|
138
|
+
next if k[0,1] == '_' or not (v.is_a?(Hash) and v['_context'] == 'procedure')
|
139
|
+
object.synchronized << k if v['_synchronized']
|
140
|
+
}
|
141
|
+
|
142
|
+
object
|
129
143
|
end
|
130
144
|
|
131
|
-
protected
|
132
145
|
ParentEliminator = Sfp::Visitor::ParentEliminator.new
|
133
146
|
|
134
147
|
ParentGenerator = Object.new
|
@@ -137,4 +150,15 @@ class Sfp::Runtime
|
|
137
150
|
true
|
138
151
|
end
|
139
152
|
|
153
|
+
SFPtoRubyValueConverter = Object.new
|
154
|
+
def SFPtoRubyValueConverter.visit(name, value, parent)
|
155
|
+
if name[0,1] != '_' and value.is_a?(Hash)
|
156
|
+
if value['_context'] == 'null'
|
157
|
+
parent[name] = nil
|
158
|
+
elsif value['_context'] == 'set'
|
159
|
+
parent[name] = value['_values']
|
160
|
+
end
|
161
|
+
end
|
162
|
+
true
|
163
|
+
end
|
140
164
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Planner
|
2
|
+
def initialize(sas)
|
3
|
+
# TODO
|
4
|
+
# - build from SAS string
|
5
|
+
# - generate image dependencies and joins graph
|
6
|
+
end
|
7
|
+
|
8
|
+
class Variable < Array
|
9
|
+
attr_accessor :init, :goal, :joins, :dependencies
|
10
|
+
attr_reader :name
|
11
|
+
|
12
|
+
def initialize(name, init=nil, goal=nil)
|
13
|
+
@name = name
|
14
|
+
@values = []
|
15
|
+
@map = {}
|
16
|
+
@init = init
|
17
|
+
@goal = goal
|
18
|
+
@joins = {}
|
19
|
+
@dependencies = {}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Operator
|
24
|
+
attr_reader :name, :cost, :preconditions, :postconditions, :variables
|
25
|
+
|
26
|
+
def initialize(name, cost=1)
|
27
|
+
@name = name
|
28
|
+
@cost = cost
|
29
|
+
@preconditions = {}
|
30
|
+
@postconditions = {}
|
31
|
+
@variables = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def <<(variable, pre=nil, post=nil)
|
35
|
+
return if variable.nil? or (pre.nil? and post.nil?)
|
36
|
+
if !pre.nil?
|
37
|
+
fail "Invalid precondition #{variable.name}:#{pre}" if !variable.index(pre)
|
38
|
+
@preconditions[variable] = pre
|
39
|
+
end
|
40
|
+
if !post.nil?
|
41
|
+
fail "Invalid postcondition #{variable.name}:#{post}" if !variable.index(post)
|
42
|
+
@postconditions[variable] = post
|
43
|
+
end
|
44
|
+
@variables[variable.name] = variable
|
45
|
+
end
|
46
|
+
|
47
|
+
def applicable(state)
|
48
|
+
@preconditions.each { |var,pre| return false if state[var] != pre }
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
def apply(state)
|
53
|
+
@postconditions.each { |var,post| state[var] = post }
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_joins_and_dependencies
|
57
|
+
@postconditions.each_key { |var_post|
|
58
|
+
@preconditions.each_key { |var_pre|
|
59
|
+
next if var_post == var_pre
|
60
|
+
if !var_post.dependencies.has_key?(var_pre)
|
61
|
+
var_post.dependencies[var_pre] = [self]
|
62
|
+
else
|
63
|
+
var_post.dependencies[var_pre] << self
|
64
|
+
end
|
65
|
+
}
|
66
|
+
@postconditions.each_key { |var_post2|
|
67
|
+
next if var_post == var_post2
|
68
|
+
if !var_post.joins.has_key?(var_post2)
|
69
|
+
var_post.joins[var_post2] = [self]
|
70
|
+
else
|
71
|
+
var_post.joins[var_post2] << self
|
72
|
+
end
|
73
|
+
}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class State < Hash
|
79
|
+
attr_reader :id
|
80
|
+
|
81
|
+
def initialize(id)
|
82
|
+
@id = id
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/sfpagent.rb
CHANGED
data/sfpagent.gemspec
CHANGED