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/lib/sfpagent/bsig.rb CHANGED
@@ -1,22 +1,311 @@
1
- module Sfp::BSig
2
- end
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
- # TODO -- implement
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 achieve_local_goals(version, goals, operators, pi)
10
- # TODO -- implement
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
- def achieve_remote_goals(version, goals, pi)
14
- # TODO -- implement
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
- module Sfp::BSig::Satisfier
19
- def receive_goals(agent, version, goals, pi)
20
- # TODO -- implement
308
+ def invoke(operator)
309
+ Sfp::Agent.execute_action(operator)
21
310
  end
22
311
  end
@@ -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, default)
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
@@ -1,25 +1,31 @@
1
1
  require 'thread'
2
2
 
3
3
  class Sfp::Runtime
4
- attr_reader :modules
4
+ attr_reader :root
5
5
 
6
6
  def initialize(model)
7
7
  @mutex_procedure = Mutex.new
8
- @root = model
9
- @modules = nil
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 = @modules.at?(module_path)[:_self]
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
- def cleanup(model)
38
- model.select { |k,v| k[0,1] != '_' and !(v.is_a?(Hash) and v['_context'] != 'object') }
39
- end
56
+ @mutex_get_state.synchronize {
57
+ update_state(@root)
58
+ get_object_state(@root, @model)
59
+ }
60
+ end
40
61
 
41
- def add_hidden_attributes(model, state)
42
- model.each { |k,v|
43
- state[k] = v if (k[0,1] == '_' and k != '_parent') or
44
- (v.is_a?(Hash) and v['_context'] == 'procedure')
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
- # Load the implementation of an object, and return its current state
49
- # @param model a Hash
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
- # Return the state of an object
77
- #
78
- def get_object_state(model, root, as_sfp=false, path='$')
79
- modules = {}
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
- # get the state for each attributes which are not covered by this
108
- object's module
109
- (model.keys - state.keys).each do |key|
110
- next if key[0,1] == '_'
111
- if model[key].is_a?(Hash)
112
- modules[key], state[key] = get_object_state(model[key], root, as_sfp, path.push(key)) if
113
- model[key]['_context'] == 'object'
114
- modules[key]['_parent'] = modules if modules[key].is_a?(Hash)
115
- else
116
- state[key] = Sfp::Undefined.new
117
- end
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
- [modules, state]
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
- root = Sfp::Helper.deep_clone(@root)
124
- root.accept(ParentEliminator)
125
- @modules, state = get_object_state(root, root, as_sfp)
126
- @modules.accept(ParentGenerator)
117
+ object
118
+ end
127
119
 
128
- state
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
@@ -15,4 +15,5 @@ libdir = File.expand_path(File.dirname(__FILE__))
15
15
  require libdir + '/sfpagent/net_helper.rb'
16
16
  require libdir + '/sfpagent/runtime.rb'
17
17
  require libdir + '/sfpagent/module.rb'
18
+ require libdir + '/sfpagent/bsig.rb'
18
19
  require libdir + '/sfpagent/agent.rb'
data/sfpagent.gemspec CHANGED
@@ -16,5 +16,5 @@ Gem::Specification.new do |s|
16
16
  s.homepage = 'https://github.com/herry13/sfpagent'
17
17
  s.rubyforge_project = 'sfpagent'
18
18
 
19
- s.add_dependency 'sfp', '~> 0.3.9'
19
+ s.add_dependency 'sfp', '~> 0.3.12'
20
20
  end