sfpagent 0.1.13 → 0.1.14

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sfpagent might be problematic. Click here for more details.

data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.13
1
+ 0.1.14
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+
3
+ DefaultPort=1314
4
+
5
+ # verify the arguments
6
+ if [[ "$1" == "" ]] || [[ "$2" == "" ]]; then
7
+ echo "Usage: install_module <address> [port] <module-name> ..."
8
+ exit 1
9
+ fi
10
+
11
+ # set the agent's address
12
+ address=$1
13
+ shift
14
+
15
+ # set the agent's port number
16
+ re='^[0-9]+$'
17
+ if [[ $1 =~ $re ]]; then
18
+ port=$1
19
+ shift
20
+ else
21
+ port=$DefaultPort
22
+ fi
23
+
24
+ # set a template command for sending the modules
25
+ cmd="curl -s -i -X PUT $address:$port/modules"
26
+
27
+ # for every module in the arguments:
28
+ # - archive the module's files to a temporary file
29
+ # - update the sending command by adding the module
30
+ # if the module is not exist then the program will
31
+ # set missing_module flag and then break from the loop
32
+ missing_module=0
33
+ for module in "$@" ; do
34
+ if [[ -d "$module" ]]; then
35
+ tar cvzhf /tmp/$module.tgz $module 1>/dev/null
36
+ cmd="$cmd -F $module=@/tmp/$module.tgz"
37
+ else
38
+ echo "Module $module is not exist!"
39
+ missing_module=`expr $missing_module + 1`
40
+ fi
41
+ done
42
+
43
+ if [[ $missing_module == 0 ]]; then
44
+ # execute the sending command there is no missing module
45
+ result=`$cmd`
46
+ re='.*HTTP/1\.1 200 OK.*'
47
+ if ! [[ $result =~ $re ]]; then
48
+ missing_module=`expr $missing_module + 1`
49
+ fi
50
+ fi
51
+
52
+ # delete temporary archive files
53
+ for module in "$@"; do
54
+ rm -f /tmp/$module.tgz
55
+ done
56
+
57
+ if [[ $missing_module == 0 ]]; then
58
+ echo "status: ok"
59
+ else
60
+ echo "status: failed"
61
+ fi
62
+
63
+ exit $missing_module
data/bin/sfpagent CHANGED
@@ -48,8 +48,7 @@ elsif opts[:stop]
48
48
 
49
49
  elsif opts[:restart]
50
50
  opts[:daemon] = true
51
- Sfp::Agent.stop
52
- Sfp::Agent.start(opts)
51
+ Sfp::Agent.start(opts) if Sfp::Agent.stop
53
52
 
54
53
  elsif opts[:status]
55
54
  Sfp::Agent.status
@@ -11,21 +11,23 @@ require 'digest/md5'
11
11
 
12
12
  module Sfp
13
13
  module Agent
14
- NetHelper = Object.new.extend(Nuri::Net::Helper)
14
+ NetHelper = Object.new.extend(Sfp::Net::Helper)
15
15
 
16
- CachedDir = (Process.euid == 0 ? '/var/sfpagent' : File.expand_path('~/.sfpagent'))
17
- Dir.mkdir(CachedDir, 0700) if not File.exist?(CachedDir)
16
+ CacheDir = (Process.euid == 0 ? '/var/sfpagent' : File.expand_path('~/.sfpagent'))
17
+ Dir.mkdir(CacheDir, 0700) if not File.exist?(CacheDir)
18
18
 
19
19
  DefaultPort = 1314
20
20
 
21
- PIDFile = "#{CachedDir}/sfpagent.pid"
22
- LogFile = "#{CachedDir}/sfpagent.log"
23
- ModelFile = "#{CachedDir}/sfpagent.model"
24
- AgentsDataFile = "#{CachedDir}/sfpagent.agents"
21
+ PIDFile = "#{CacheDir}/sfpagent.pid"
22
+ LogFile = "#{CacheDir}/sfpagent.log"
23
+ ModelFile = "#{CacheDir}/sfpagent.model"
24
+ AgentsDataFile = "#{CacheDir}/sfpagent.agents"
25
25
 
26
- BSigFile = "#{CachedDir}/bsig.model"
27
- BSigPIDFile = "#{CachedDir}/bsig.pid"
28
- BSigThreadsLockFile = "#{CachedDir}/bsig.threads.lock.#{Time.now.nsec}"
26
+ CacheModelFile = "#{CacheDir}/cache.model"
27
+
28
+ BSigFile = "#{CacheDir}/bsig.model"
29
+ BSigPIDFile = "#{CacheDir}/bsig.pid"
30
+ BSigThreadsLockFile = "#{CacheDir}/bsig.threads.lock.#{Time.now.nsec}"
29
31
 
30
32
  @@logger = WEBrick::Log.new(LogFile, WEBrick::BasicLog::INFO ||
31
33
  WEBrick::BasicLog::ERROR ||
@@ -47,6 +49,10 @@ module Sfp
47
49
  @@logger
48
50
  end
49
51
 
52
+ def self.config
53
+ @@config
54
+ end
55
+
50
56
  # Start the agent.
51
57
  #
52
58
  # options:
@@ -60,11 +66,11 @@ module Sfp
60
66
  Sfp::Agent.logger.info "Starting SFP Agent daemons..."
61
67
  puts "Starting SFP Agent daemons..."
62
68
 
63
- Process.daemon
69
+ Process.daemon if p[:daemon]
64
70
 
65
71
  begin
66
72
  # check modules directory, and create it if it's not exist
67
- p[:modules_dir] = File.expand_path(p[:modules_dir].to_s.strip != '' ? p[:modules_dir].to_s : "#{CachedDir}/modules")
73
+ p[:modules_dir] = File.expand_path(p[:modules_dir].to_s.strip != '' ? p[:modules_dir].to_s : "#{CacheDir}/modules")
68
74
  Dir.mkdir(p[:modules_dir], 0700) if not File.exist?(p[:modules_dir])
69
75
  @@config = p
70
76
 
@@ -97,6 +103,10 @@ module Sfp
97
103
  trap(signal) {
98
104
  Sfp::Agent.logger.info "Shutting down web server and BSig engine..."
99
105
  bsig_engine.stop
106
+ loop do
107
+ break if bsig_engine.status == :stopped
108
+ sleep 1
109
+ end
100
110
  server.shutdown
101
111
  }
102
112
  }
@@ -121,19 +131,23 @@ module Sfp
121
131
  puts "Stopping SFP Agent with PID #{pid}..."
122
132
  Process.kill 'HUP', pid
123
133
 
124
- sleep (Sfp::BSig::SleepTime + 0.25)
125
-
126
- # forcely kill the process if it is still running
127
- system("kill -9 #{pid} 1>/dev/null 2>/dev/null")
128
-
129
- Sfp::Agent.logger.info "SFP Agent daemon has stopped."
130
- puts "SFP Agent daemon has stopped."
134
+ begin
135
+ sleep (Sfp::BSig::SleepTime + 0.25)
136
+ Process.kill 0, pid
137
+ Sfp::Agent.logger.info "SFP Agent daemon is still running."
138
+ puts "SFP Agent daemon is still running."
139
+ return false
140
+ rescue
141
+ Sfp::Agent.logger.info "SFP Agent daemon has stopped."
142
+ puts "SFP Agent daemon has stopped."
143
+ File.delete(PIDFile) if File.exist?(PIDFile)
144
+ end
131
145
  rescue
132
146
  puts "SFP Agent is not running."
147
+ File.delete(PIDFile) if File.exist?(PIDFile)
133
148
  end
134
149
 
135
- ensure
136
- File.delete(PIDFile) if File.exist?(PIDFile)
150
+ true
137
151
  end
138
152
 
139
153
  # Print the status of the agent.
@@ -149,6 +163,39 @@ module Sfp
149
163
  end
150
164
  end
151
165
 
166
+ def self.get_cache_model(name)
167
+ model = JSON[File.read(CacheModelFile)]
168
+ (model.has_key?(name) ? model[name] : nil)
169
+ end
170
+
171
+ def self.set_cache_model(p={})
172
+ File.open(CacheModelFile, File::RDWR|File::CREAT, 0600) do |f|
173
+ f.flock(File::LOCK_EX)
174
+ json = f.read
175
+ model = (json.length >= 2 ? JSON[json] : {})
176
+
177
+ if p[:name]
178
+ if p[:model]
179
+ model[p[:name]] = p[:model]
180
+ Sfp::Agent.logger.info "Setting cache model for #{p[:name]}..."
181
+ else
182
+ model.delete(p[:name]) if model.has_key?(p[:name])
183
+ Sfp::Agent.logger.info "Deleting cache model for #{p[:name]}..."
184
+ end
185
+ else
186
+ model = {}
187
+ Sfp::Agent.logger.info "Deleting all cache model..."
188
+ end
189
+
190
+ f.rewind
191
+ f.write(JSON.generate(model))
192
+ f.flush
193
+ f.truncate(f.pos)
194
+ end
195
+
196
+ true
197
+ end
198
+
152
199
  # Save given model to cached file, and then reload the model.
153
200
  #
154
201
  def self.set_model(model)
@@ -342,7 +389,7 @@ module Sfp
342
389
  Sfp::Agent.logger.info "Successfully loading #{counter} modules."
343
390
  end
344
391
 
345
- def self.get_schemata(module_name)
392
+ def self.get_sfp(module_name)
346
393
  dir = @@config[:modules_dir]
347
394
 
348
395
  filepath = "#{dir}/#{module_name}/#{module_name}.sfp"
@@ -372,6 +419,23 @@ module Sfp
372
419
  data
373
420
  end
374
421
 
422
+ # Push a list of modules to an agent using a script in $SFPAGENT_HOME/bin/install_module.
423
+ #
424
+ # parameters:
425
+ # :address => address of target agent
426
+ # :port => port of target agent
427
+ # :modules => an array of modules' name that will be pushed
428
+ #
429
+ def self.push_modules(p={})
430
+ fail "Incomplete parameters." if !p[:modules] or !p[:address] or !p[:port]
431
+
432
+ install_module = File.expand_path('../../../bin/install_module', __FILE__)
433
+ modules = p[:modules].join(' ')
434
+ cmd = "cd #{@@config[:modules_dir]}; #{install_module} #{p[:address]} #{p[:port]} #{modules}"
435
+ result = `#{cmd}`
436
+ (result =~ /status: ok/)
437
+ end
438
+
375
439
  def self.uninstall_all_modules(p={})
376
440
  return true if @@config[:modules_dir] == ''
377
441
  if system("rm -rf #{@@config[:modules_dir]}/*")
@@ -397,8 +461,16 @@ module Sfp
397
461
  result
398
462
  end
399
463
 
400
- def self.install_module(name, data)
401
- return false if @@config[:modules_dir].to_s == ''
464
+ def self.install_modules(modules)
465
+ modules.each { |name,data| return false if not install_module(name, data, false) }
466
+
467
+ load_modules(@@config)
468
+
469
+ true
470
+ end
471
+
472
+ def self.install_module(name, data, reload=true)
473
+ return false if @@config[:modules_dir].to_s == '' or data.nil?
402
474
 
403
475
  if !File.directory? @@config[:modules_dir]
404
476
  File.delete @@config[:modules_dir] if File.exist? @@config[:modules_dir]
@@ -422,11 +494,9 @@ module Sfp
422
494
  end
423
495
  system("cd #{module_dir}; rm data.tgz")
424
496
  }
425
- load_modules(@@config)
426
-
427
- # rebuild the model
428
- update_model({:rebuild => true})
429
497
 
498
+ load_modules(@@config) if reload
499
+
430
500
  Sfp::Agent.logger.info "Installing module #{name} [OK]"
431
501
 
432
502
  true
@@ -441,22 +511,52 @@ module Sfp
441
511
  end
442
512
  end
443
513
 
444
- def self.set_agents(agents)
445
- File.open(AgentsDataFile, 'w', 0600) do |f|
446
- raise Exception, "Invalid agents list." if not agents.is_a?(Hash)
447
- buffer = {}
448
- agents.each { |name,data|
449
- raise Exception, "Invalid agents list." if not data.is_a?(Hash) or
450
- not data.has_key?('sfpAddress') or data['sfpAddress'].to_s.strip == '' or
451
- not data.has_key?('sfpPort')
452
- buffer[name] = {}
453
- buffer[name]['sfpAddress'] = data['sfpAddress'].to_s
454
- buffer[name]['sfpPort'] = data['sfpPort'].to_s.strip.to_i
455
- buffer[name]['sfpPort'] = DefaultPort if buffer[name]['sfpPort'] == 0
514
+ # parameter:
515
+ # :data => To delete an agent: { "agent_name" => nil }
516
+ # To add/modify an agent: { "agent_name" => { "sfpAddress" => "10.0.0.1", "sfpPort" => 1314 } }
517
+ #
518
+ def self.set_agents(data)
519
+ data.each { |name,agent|
520
+ return false if agent.is_a?(Hash) and (not agent['sfpAddress'].is_a?(String) or
521
+ agent['sfpAddress'].strip == '' or agent['sfpPort'].to_i <= 0)
522
+ }
523
+
524
+ updated = false
525
+ agents = nil
526
+ File.open(AgentsDataFile, File::RDWR|File::CREAT, 0644) { |f|
527
+ f.flock(File::LOCK_EX)
528
+ json = f.read
529
+ agents = (json == '' ? {} : JSON[json])
530
+ current_hash = agents.hash
531
+ data.each { |k,v|
532
+ if !agents.has_key?(k) or v.nil? or agents[k].hash != v.hash
533
+ agents[k] = v
534
+ end
535
+ }
536
+ agents.keys.each { |k| agents.delete(k) if agents[k].nil? }
537
+
538
+ if current_hash != agents.hash
539
+ updated = true
540
+ f.rewind
541
+ f.write(JSON.generate(agents))
542
+ f.flush
543
+ f.truncate(f.pos)
544
+ end
545
+ }
546
+
547
+ if updated # if updated then broadcast to other agents
548
+ http_data = {'agents' => JSON.generate(data)}
549
+
550
+ agents.each { |name,agent|
551
+ begin
552
+ code, _ = NetHelper.put_data(agent['sfpAddress'], agent['sfpPort'], '/agents', http_data, 5, 20)
553
+ raise Exception if code != '200'
554
+ rescue Exception => e
555
+ Sfp::Agent.logger.warn "Push agents list to #{agent['sfpAddress']}:#{agent['sfpPort']} [Failed]"
556
+ end
456
557
  }
457
- f.write(JSON.generate(buffer))
458
- f.flush
459
558
  end
559
+
460
560
  true
461
561
  end
462
562
 
@@ -479,15 +579,15 @@ module Sfp
479
579
  # /pid => save daemon's PID to a file (only requested from localhost)
480
580
  # /state => return the current state
481
581
  # /model => return the current model
482
- # /schemata => return the schemata of a module
582
+ # /sfp => return the SFP description of a module
483
583
  # /modules => return a list of available modules
484
584
  # /agents => return a list of agents database
485
585
  # /log => return last 100 lines of log file
486
586
  #
487
587
  def do_GET(request, response)
488
588
  status = 400
489
- content_type, body = ''
490
- if not trusted(request.peeraddr[2])
589
+ content_type = body = ''
590
+ if not trusted(request)
491
591
  status = 403
492
592
  else
493
593
  path = (request.path[-1,1] == '/' ? request.path.chop : request.path)
@@ -509,11 +609,14 @@ module Sfp
509
609
  elsif path == '/model'
510
610
  status, content_type, body = get_model
511
611
 
612
+ elsif path =~ /\/model\/cache\/.+/
613
+ status, content_type, body = self.get_cache_model({:name => path[13, path.length-13]})
614
+
512
615
  elsif path == '/bsig'
513
616
  status, content_Type, body = get_bsig
514
617
 
515
- elsif path =~ /^\/schemata\/.+/
516
- status, content_type, body = get_schemata({:module => path[10, path.length-10]})
618
+ elsif path =~ /^\/sfp\/.+/
619
+ status, content_type, body = get_sfp({:module => path[10, path.length-10]})
517
620
 
518
621
  elsif path == '/modules'
519
622
  status, content_type, body = [200, 'application/json', JSON.generate(Sfp::Agent.get_modules)]
@@ -540,7 +643,7 @@ module Sfp
540
643
  def do_POST(request, response)
541
644
  status = 400
542
645
  content_type, body = ''
543
- if not self.trusted(request.peeraddr[2])
646
+ if not self.trusted(request)
544
647
  status = 403
545
648
  else
546
649
  path = (request.path[-1,1] == '/' ? ryyequest.path.chop : request.path)
@@ -557,36 +660,40 @@ module Sfp
557
660
  # Handle HTTP Put request
558
661
  #
559
662
  # uri:
560
- # /model => receive a new model and save to cached file
663
+ # /model => receive a new model and then save it
664
+ # /model/cache => receive a "cache" model and then save it
561
665
  # /modules => save the module if parameter "module" is provided
562
- # delete the module if parameter "module" is not provided
563
666
  # /agents => save the agents' list if parameter "agents" is provided
564
- # delete all agents if parameter "agents" is not provided
565
667
  # /bsig => receive BSig model and receive it in cached directory
566
668
  # /bsig/satisfier => receive goal request from other agents and then start
567
669
  # a satisfier thread to try to achieve it
670
+ #
568
671
  def do_PUT(request, response)
569
672
  status = 400
570
- content_type, body = ''
571
- if not self.trusted(request.peeraddr[2])
673
+ content_type = body = ''
674
+ if not self.trusted(request)
572
675
  status = 403
573
676
  else
574
677
  path = (request.path[-1,1] == '/' ? ryyequest.path.chop : request.path)
575
678
 
576
- if path == '/model'
679
+ if path == '/model' and request.query.has_key?('model')
577
680
  status, content_type, body = self.set_model({:query => request.query})
578
681
 
579
- elsif path =~ /\/modules\/.+/
682
+ elsif path =~ /\/model\/cache\/.+/ and request.query.length > 0
683
+ status, content_type, body = self.set_cache_model({:name => path[13, path.length-13],
684
+ :query => request.query})
685
+
686
+ elsif path =~ /\/modules\/.+/ and request.query.length > 0
580
687
  status, content_type, body = self.manage_modules({:name => path[9, path.length-9],
581
688
  :query => request.query})
582
689
 
583
- elsif path == '/modules'
584
- status, content_type, body = self.manage_modules({:delete => true})
690
+ elsif path == '/modules' and request.query.length > 0
691
+ status, content_type, body = self.manage_modules({:query => request.query})
585
692
 
586
- elsif path == '/agents'
587
- status, content_type, body = self.manage_agents({:query => request.query})
693
+ elsif path == '/agents' and request.query.has_key?('agents')
694
+ status, content_type, body = self.set_agents({:query => request.query})
588
695
 
589
- elsif path == '/bsig'
696
+ elsif path == '/bsig' and request.query.has_key?('bsig')
590
697
  status, content_type, body = self.set_bsig({:query => request.query})
591
698
 
592
699
  elsif path == '/bsig/satisfier'
@@ -600,9 +707,52 @@ module Sfp
600
707
  response.body = body
601
708
  end
602
709
 
603
- def manage_agents(p={})
710
+ # Handle HTTP Put request
711
+ #
712
+ # uri:
713
+ # /model => delete existing model
714
+ # /modules => delete a module with name specified in parameter "module", or
715
+ # delete all modules if parameter "module" is not provided
716
+ # /agents => delete agents database
717
+ # /bsig => delete existing BSig model
718
+ #
719
+ def do_DELETE(request, response)
720
+ status = 400
721
+ content_type = body = ''
722
+ if not self.trusted(request)
723
+ status = 403
724
+ else
725
+ path = (request.path[-1,1] == '/' ? ryyequest.path.chop : request.path)
726
+
727
+ if path == '/model'
728
+ status, content_type, body = self.set_model
729
+
730
+ elsif path == '/model/cache'
731
+ status, content_type, body = self.set_cache_model
732
+
733
+ elsif path =~ /\/model\/cache\/.+/
734
+ status, content_type, body = self.set_cache_model({:name => path[13, path.length-13]})
735
+
736
+ elsif path == '/modules'
737
+ status, content_type, body = self.manage_modules({:deleteall => true})
738
+
739
+ elsif path =~ /\/modules\/.+/
740
+ status, content_type, body = self.manage_modules({:name => path[9, path.length-9]})
741
+
742
+ elsif path == '/agents'
743
+ status, content_type, body = self.set_agents
744
+
745
+ elsif path == '/bsig'
746
+ status, content_type, body = self.set_bsig
747
+
748
+ end
749
+
750
+ end
751
+ end
752
+
753
+ def set_agents(p={})
604
754
  begin
605
- if p[:query].has_key?('agents')
755
+ if p[:query] and p[:query].has_key?('agents')
606
756
  return [200, '', ''] if Sfp::Agent.set_agents(JSON[p[:query]['agents']])
607
757
  else
608
758
  return [200, '', ''] if Sfp::Agent.set_agents({})
@@ -614,23 +764,47 @@ module Sfp
614
764
  end
615
765
 
616
766
  def manage_modules(p={})
617
- if p[:delete]
618
- return [200, '', ''] if Sfp::Agent.uninstall_all_modules
619
- else
620
- p[:name], _ = p[:name].split('/', 2)
621
- if p[:query].has_key?('module')
767
+ if p[:name]
768
+ if p[:query]
622
769
  return [200, '', ''] if Sfp::Agent.install_module(p[:name], p[:query]['module'])
623
770
  else
624
771
  return [200, '', ''] if Sfp::Agent.uninstall_module(p[:name])
625
772
  end
773
+ elsif p[:query].length > 0
774
+ return [200, '', ''] if Sfp::Agent.install_modules(p[:query])
775
+ else
776
+ return [200, '', ''] if Sfp::Agent.uninstall_all_modules
626
777
  end
778
+
627
779
  [500, '', '']
628
780
  end
629
781
 
630
- def get_schemata(p={})
782
+ def get_cache_model(p={})
783
+ model = Sfp::Agent.get_cache_model(p[:name])
784
+ if model
785
+ [200, 'application/json', JSON.generate(model)]
786
+ else
787
+ [404, '', '']
788
+ end
789
+ end
790
+
791
+ def set_cache_model(p={})
792
+ p[:model] = JSON[p[:query]['model']] if p[:query].is_a?(Hash) and p[:query]['model']
793
+
794
+ if p[:name]
795
+ return [200, '', ''] if Sfp::Agent.set_cache_model(p)
796
+ else
797
+ return [200, '', ''] if Sfp::Agent.set_cache_model
798
+ end
799
+
800
+ [500, '', '']
801
+ end
802
+
803
+
804
+ def get_sfp(p={})
631
805
  begin
632
806
  module_name, _ = p[:module].split('/', 2)
633
- return [200, 'application/json', Sfp::Agent.get_schemata(module_name)]
807
+ return [200, 'application/json', Sfp::Agent.get_sfp(module_name)]
634
808
  rescue Exception => e
635
809
  @logger.error "Sending schemata [Failed]\n#{e}"
636
810
  end
@@ -680,7 +854,7 @@ module Sfp
680
854
  end
681
855
 
682
856
  def execute(p={})
683
- return [400, '', ''] if not p[:query].has_key?('action')
857
+ return [400, '', ''] if not (p[:query] and p[:query].has_key?('action'))
684
858
  begin
685
859
  return [200, '', ''] if Sfp::Agent.execute_action(JSON[p[:query]['action']])
686
860
  rescue
@@ -733,7 +907,7 @@ module Sfp
733
907
  [500, '', '']
734
908
  end
735
909
 
736
- def trusted(address)
910
+ def trusted(request)
737
911
  true
738
912
  end
739
913
 
data/lib/sfpagent/bsig.rb CHANGED
@@ -1,14 +1,14 @@
1
1
  require 'thread'
2
2
 
3
3
  class Sfp::BSig
4
- include Nuri::Net::Helper
4
+ include Sfp::Net::Helper
5
5
 
6
6
  SleepTime = 5
7
7
  MaxTries = 5
8
8
 
9
9
  SatisfierPath = '/bsig/satisfier'
10
- CachedDir = (Process.euid == 0 ? '/var/sfpagent' : File.expand_path('~/.sfpagent'))
11
- SatisfierLockFile = "#{CachedDir}/bsig.satisfier.lock.#{Time.now.nsec}"
10
+ CacheDir = (Process.euid == 0 ? '/var/sfpagent' : File.expand_path('~/.sfpagent'))
11
+ SatisfierLockFile = "#{CacheDir}/bsig.satisfier.lock.#{Time.now.nsec}"
12
12
 
13
13
  attr_reader :enabled, :status, :mode
14
14
 
@@ -16,6 +16,7 @@ class Sfp::BSig
16
16
  @lock = Mutex.new
17
17
  @enabled = false
18
18
  @status = :stopped
19
+ @lock_postprocess = Mutex.new
19
20
  end
20
21
 
21
22
  def stop
@@ -32,7 +33,7 @@ class Sfp::BSig
32
33
  Thread.new {
33
34
  register_satisfier_thread(:reset)
34
35
 
35
- system("rm -f #{CachedDir}/operator.*.lock")
36
+ system("rm -f #{CacheDir}/operator.*.lock")
36
37
 
37
38
  Sfp::Agent.logger.info "[main] BSig engine is running."
38
39
 
@@ -107,7 +108,8 @@ class Sfp::BSig
107
108
  operator = select_operator(flaws, operators, pi)
108
109
  return :failure if operator.nil?
109
110
 
110
- #Sfp::Agent.logger.info "[#{mode}] Flaws: #{JSON.generate(flaws)}" # debugging
111
+ # debugging
112
+ #Sfp::Agent.logger.info "[#{mode}] Flaws: #{JSON.generate(flaws)}"
111
113
 
112
114
  return :ongoing if not lock_operator(operator)
113
115
 
@@ -116,7 +118,8 @@ class Sfp::BSig
116
118
  next_pi = operator['pi'] + 1
117
119
  pre_local, pre_remote = split_preconditions(operator)
118
120
 
119
- #Sfp::Agent.logger.info "[#{mode}] local-flaws: #{JSON.generate(pre_local)}, remote-flaws: #{JSON.generate(pre_remote)}" # debugging
121
+ # debugging
122
+ #Sfp::Agent.logger.info "[#{mode}] local-flaws: #{JSON.generate(pre_local)}, remote-flaws: #{JSON.generate(pre_remote)}"
120
123
 
121
124
  status = nil
122
125
  tries = MaxTries
@@ -146,17 +149,29 @@ class Sfp::BSig
146
149
  end
147
150
 
148
151
  def achieve_remote_goal(id, goal, pi, mode)
152
+
149
153
  if goal.length > 0
150
154
  agents = Sfp::Agent.get_agents
151
155
  split_goal_by_agent(goal).each do |agent_name,agent_goal|
152
- return false if not agents.has_key?(agent_name) or agents[agent_name]['sfpAddress'].to_s == ''
153
-
154
- return false if not send_goal_to_agent(agents[agent_name], id, agent_goal, pi, agent_name, mode)
156
+ if agents.has_key?(agent_name)
157
+ return false if agents[agent_name]['sfpAddress'].to_s == ''
158
+ return false if not send_goal_to_agent(agents[agent_name], id, agent_goal, pi, agent_name, mode)
159
+ else
160
+ return false if not verify_state_of_not_exist_agent(agent_name, agent_goal)
161
+ end
155
162
  end
156
163
  end
157
164
  true
158
165
  end
159
166
 
167
+ def verify_state_of_not_exist_agent(name, goal)
168
+ state = { name => { 'created' => false } }
169
+ goal.each { |var,val|
170
+ return false if state.at?(var) != val
171
+ }
172
+ true
173
+ end
174
+
160
175
  def receive_goal_from_agent(id, goal, pi)
161
176
  register_satisfier_thread
162
177
 
@@ -187,7 +202,7 @@ class Sfp::BSig
187
202
  unregister_satisfier_thread
188
203
  end
189
204
 
190
- protected
205
+ #protected
191
206
  def register_satisfier_thread(mode=nil)
192
207
  File.open(SatisfierLockFile, File::RDWR|File::CREAT, 0644) { |f|
193
208
  f.flock(File::LOCK_EX)
@@ -212,7 +227,7 @@ class Sfp::BSig
212
227
 
213
228
  def lock_operator(operator)
214
229
  @lock.synchronize {
215
- operator_lock_file = "#{CachedDir}/operator.#{operator['name']}.lock"
230
+ operator_lock_file = "#{CacheDir}/operator.#{operator['name']}.lock"
216
231
  return false if File.exist?(operator_lock_file)
217
232
  File.open(operator_lock_file, 'w') { |f| f.write('1') }
218
233
  return true
@@ -221,17 +236,17 @@ class Sfp::BSig
221
236
 
222
237
  def unlock_operator(operator)
223
238
  @lock.synchronize {
224
- operator_lock_file = "#{CachedDir}/operator.#{operator['name']}.lock"
239
+ operator_lock_file = "#{CacheDir}/operator.#{operator['name']}.lock"
225
240
  File.delete(operator_lock_file) if File.exist?(operator_lock_file)
226
241
  }
227
242
  end
228
243
 
229
244
  def split_goal_by_agent(goal)
230
- agents = Sfp::Agent.get_agents
245
+ #agents = Sfp::Agent.get_agents
231
246
  agent_goal = {}
232
247
  goal.each { |var,val|
233
248
  _, agent_name, _ = var.split('.', 3)
234
- fail "Agent #{agent_name} is not in database!" if not agents.has_key?(agent_name)
249
+ #fail "Agent #{agent_name} is not in database!" if not agents.has_key?(agent_name)
235
250
  agent_goal[agent_name] = {} if not agent_goal.has_key?(agent_name)
236
251
  agent_goal[agent_name][var] = val
237
252
  }
@@ -239,17 +254,21 @@ class Sfp::BSig
239
254
  end
240
255
 
241
256
  def send_goal_to_agent(agent, id, g, pi, agent_name='', mode)
242
- data = {'id' => id,
243
- 'goal' => JSON.generate(g),
244
- 'pi' => pi}
245
-
246
- Sfp::Agent.logger.info "[#{mode}] Request goal to: #{agent_name} [WAIT]"
247
-
248
- code, _ = put_data(agent['sfpAddress'], agent['sfpPort'], SatisfierPath, data)
249
-
250
- Sfp::Agent.logger.info "[#{mode}] Request goal to: #{agent_name} - status: #{code}"
251
-
252
- (code == '200')
257
+ begin
258
+ data = {'id' => id,
259
+ 'goal' => JSON.generate(g),
260
+ 'pi' => pi}
261
+
262
+ Sfp::Agent.logger.info "[#{mode}] Request goal to: #{agent_name} [WAIT]"
263
+
264
+ code, _ = put_data(agent['sfpAddress'], agent['sfpPort'], SatisfierPath, data)
265
+
266
+ Sfp::Agent.logger.info "[#{mode}] Request goal to: #{agent_name} - status: #{code}"
267
+
268
+ (code == '200')
269
+ rescue
270
+ false
271
+ end
253
272
  end
254
273
 
255
274
  def get_current_state
@@ -262,7 +281,13 @@ class Sfp::BSig
262
281
  return goal.clone if current.nil?
263
282
  flaws = {}
264
283
  goal.each { |var,val|
265
- current_value = current.at?(var)
284
+ _, agent_name, _ = var.split('.', 3)
285
+ if agent_name != Sfp::Agent.whoami?
286
+ current_value = Sfp::Resource.resolve(var)
287
+ else
288
+ current_value = current.at?(var)
289
+ end
290
+
266
291
  if current_value.is_a?(Sfp::Undefined)
267
292
  flaws[var] = val if not val.is_a?(Sfp::Undefined)
268
293
  else
@@ -312,6 +337,150 @@ class Sfp::BSig
312
337
 
313
338
  def invoke(operator, mode)
314
339
  Sfp::Agent.logger.info "[#{mode}] Invoking #{operator['name']}"
315
- Sfp::Agent.execute_action(operator)
340
+
341
+ begin
342
+ status = Sfp::Agent.execute_action(operator)
343
+ if status
344
+ if operator['name'] =~ /^\$(\.[a-zA-Z0-9_]+)*\.(create_vm)/
345
+ postprocess_create_vm(operator)
346
+ elsif operator['name'] =~ /^\$(\.[a-zA-Z0-9_]+)*\.(delete_vm)/
347
+ postprocess_delete_vm(operator)
348
+ end
349
+ end
350
+ rescue Exception => e
351
+ Sfp::Agent.logger.error "Error in invoking operator #{operator['name']}\n#{e}\n#{e.backtrace.join("\n")}"
352
+ return false
353
+ end
354
+
355
+ status
356
+ end
357
+
358
+ def postprocess_delete_vm(operator)
359
+ @lock_postprocess.synchronize {
360
+ _, agent_name, _ = operator['name'].split('.', 3)
361
+
362
+ Sfp::Agent.logger.info "Postprocess delete VM #{agent_name}"
363
+
364
+ # update agents database (automatically broadcast to other agents)
365
+ Sfp::Agent.set_agents({agent_name => nil})
366
+ }
367
+ end
368
+
369
+ def postprocess_create_vm(operator)
370
+ @lock_postprocess.synchronize {
371
+ refs = operator['name'].split('.')
372
+ vms_ref = refs[0..-2].join('.') + '.vms'
373
+
374
+ _, agent_name, _ = operator['parameters']['$.vm'].split('.', 3)
375
+
376
+ Sfp::Agent.logger.info "Postprocess create VM #{agent_name}"
377
+
378
+ # update proxy component's state
379
+ state = Sfp::Agent.get_state
380
+ return false if not state.is_a?(Hash)
381
+
382
+ # get VM's address
383
+ vms = state.at?(vms_ref)
384
+ return false if !vms.is_a?(Hash) or !vms[agent_name].is_a?(Hash) or vms[agent_name]['ip'].to_s.strip == ''
385
+ data = {agent_name => {'sfpAddress' => vms[agent_name]['ip'], 'sfpPort' => Sfp::Agent::DefaultPort}}
386
+
387
+ # update agents database
388
+ Sfp::Agent.set_agents(data)
389
+
390
+ # get new agent's model and BSig model from cache database
391
+ model = Sfp::Agent.get_cache_model(agent_name)
392
+ model['model']['in_cloud'] = refs[0..-2].join('.')
393
+
394
+ if not model.nil?
395
+ address = data[agent_name]['sfpAddress']
396
+ port = data[agent_name]['sfpPort']
397
+
398
+ # push required modules
399
+ push_modules(model, address, port)
400
+
401
+ # push agent database to new agent
402
+ code, _ = put_data(address, port, '/agents', {'agents' => JSON.generate(Sfp::Agent.get_agents)})
403
+
404
+ # push new agent's model
405
+ code, _ = put_data(address, port, '/model', {'model' => JSON.generate({agent_name => model['model']})})
406
+
407
+ # push new agent's BSig model
408
+ code, _ = put_data(address, port, '/bsig', {'bsig' => JSON.generate(model['bsig'])}) if code == '200'
409
+
410
+ return (code == '200')
411
+ end
412
+ }
413
+ false
316
414
  end
415
+
416
+ def push_modules(agent_model, address, port)
417
+ name = agent_model['_self']
418
+ finder = Sfp::Helper::SchemaCollector.new
419
+ {:agent => agent_model}.accept(finder)
420
+ schemata = finder.schemata.uniq.map { |x| x.sub(/^\$\./, '').downcase }
421
+
422
+ modules_dir = Sfp::Agent.config[:modules_dir]
423
+ install_module = File.expand_path('../../../bin/install_module', __FILE__)
424
+
425
+ begin
426
+ # get modules list
427
+ code, body = get_data(address, port, '/modules')
428
+ raise Exception, "Unable to get modules list from #{name}" if code.to_i != 200
429
+
430
+ modules = JSON[body]
431
+ list = ''
432
+ schemata.each { |m|
433
+ list += "#{m} " if m != 'object' and File.exist?("#{modules_dir}/#{m}") and
434
+ (not modules.has_key?(m) or modules[m] != get_local_module_hash(m, modules_dir).to_s)
435
+ }
436
+
437
+ return true if list == ''
438
+
439
+ if system("cd #{modules_dir}; #{install_module} #{address} #{port} #{list} 1>/dev/null 2>/tmp/install_module.error")
440
+ Sfp::Agent.logger.info "Push modules #{list}to #{name} [OK]"
441
+ else
442
+ Sfp::Agent.logger.warn "Push modules #{list}to #{name} [Failed]"
443
+ end
444
+
445
+ return true
446
+
447
+ rescue Exception => e
448
+ Sfp::Agent.logger.warn "[WARN] Cannot push module to #{name} - #{e}"
449
+ end
450
+
451
+ false
452
+ end
453
+
454
+ # return the list of Hash value of all modules
455
+ #
456
+ def get_local_module_hash(name, modules_dir)
457
+ module_dir = File.expand_path("#{modules_dir}/#{name}")
458
+ if File.directory? module_dir
459
+ if `which md5sum`.strip.length > 0
460
+ return `find #{module_dir} -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | awk '{print $1}'`.strip
461
+ elsif `which md5`.strip.length > 0
462
+ return `find #{module_dir} -type f -exec md5 {} + | awk '{print $4}' | sort | md5`.strip
463
+ end
464
+ end
465
+ nil
466
+ end
467
+
317
468
  end
469
+
470
+ module Sfp::Helper
471
+ end
472
+
473
+ class Sfp::Helper::SchemaCollector
474
+ attr_reader :schemata
475
+ def initialize
476
+ @schemata = []
477
+ end
478
+
479
+ def visit(name, value, parent)
480
+ if value.is_a?(Hash) and value.has_key?('_classes')
481
+ value['_classes'].each { |s| @schemata << s }
482
+ end
483
+ true
484
+ end
485
+ end
486
+
@@ -2,6 +2,12 @@
2
2
  # predefined methods: update_state, apply, reset, resolve
3
3
  #
4
4
  module Sfp::Resource
5
+ @@resource = Object.new.extend(Sfp::Resource)
6
+
7
+ def self.resolve(path)
8
+ @@resource.resolve(path)
9
+ end
10
+
5
11
  attr_accessor :parent, :synchronized
6
12
  attr_reader :state, :model
7
13
 
@@ -1,10 +1,10 @@
1
1
  require 'uri'
2
2
  require 'net/http'
3
3
 
4
- module Nuri::Net
4
+ module Sfp::Net
5
5
  end
6
6
 
7
- module Nuri::Net::Helper
7
+ module Sfp::Net::Helper
8
8
  def http_request(uri, request, open_timeout=5, read_timeout=1800)
9
9
  http = Net::HTTP.new(uri.host, uri.port)
10
10
  http.open_timeout = open_timeout
@@ -1,85 +1,217 @@
1
- module Planner
2
- def initialize(sas)
1
+ #!/usr/bin/env ruby
2
+
3
+ class Planner
4
+ def initialize(p={})
3
5
  # TODO
4
6
  # - build from SAS string
5
- # - generate image dependencies and joins graph
7
+ # - generate image dependencies and joints graph
8
+
9
+ @vars = []
10
+ @variables = {}
11
+ @ops = []
12
+ @operators = {}
13
+ @init = @goal = nil
14
+
15
+ lines = p[:sas].split("\n")
16
+ i = 0
17
+ while i < lines.length
18
+ if lines[i] == 'begin_variable'
19
+ i, var = Variable.read(i, lines)
20
+ @vars << var
21
+ @variables[var.sym] = var
22
+ elsif lines[i] == 'begin_operator'
23
+ i, op = Operator.read(i, lines, @vars)
24
+ @ops << op
25
+ @operators[op.sym] = op
26
+ elsif lines[i] == 'begin_state'
27
+ i, @init = State.read(i, lines, @vars)
28
+ elsif lines[i] == 'begin_goal'
29
+ i, @goal = State.read(i, lines, @vars)
30
+ end
31
+ i += 1
32
+ end
33
+
34
+ @ops.each { |op| op.update_variables_joints_and_dependencies(@variables) }
35
+
36
+ puts "#{@vars.length} variables"
37
+ puts "#{@ops.length} operators"
38
+ puts "#{@init.length} initial state"
39
+ puts "#{@goal.length} goal state"
40
+
41
+ @vars.each { |v|
42
+ puts v.to_s
43
+ if v.dependencies.length > 0
44
+ print "\tdep|"
45
+ v.dependencies.each { |k,v| print "#{k}:#{v.length}|" }
46
+ puts ''
47
+ end
48
+ if v.joints.length > 0
49
+ print "\tjoint|"
50
+ v.joints.each { |k,v| print "#{k}:#{v.length}|" }
51
+ puts ''
52
+ end
53
+ }
54
+ @ops.each { |op| puts op.inspect }
55
+ puts @init.inspect
56
+ puts @goal.inspect
57
+ end
58
+
59
+ def to_image(p={})
6
60
  end
7
61
 
8
62
  class Variable < Array
9
- attr_accessor :init, :goal, :joins, :dependencies
10
- attr_reader :name
63
+ attr_reader :name, :sym
64
+ attr_accessor :init, :goal, :joints, :dependencies
65
+
66
+ def self.read(i, lines)
67
+ var = Variable.new(lines[i+1])
68
+ i += 4
69
+ i.upto(lines.length) do |j|
70
+ i = j
71
+ break if lines[j] == 'end_variable'
72
+ var << lines[j].to_sym
73
+ end
74
+ fail "Cannot find end_variable" if lines[i] != 'end_variable'
75
+ [i, var]
76
+ end
11
77
 
12
78
  def initialize(name, init=nil, goal=nil)
13
79
  @name = name
80
+ @sym = @name.to_sym
14
81
  @values = []
15
82
  @map = {}
16
83
  @init = init
17
84
  @goal = goal
18
- @joins = {}
85
+ @joints = {}
19
86
  @dependencies = {}
20
87
  end
88
+
89
+ alias :super_to_s :to_s
90
+ def to_s
91
+ @name + " " + super_to_s
92
+ end
21
93
  end
22
94
 
23
95
  class Operator
24
- attr_reader :name, :cost, :preconditions, :postconditions, :variables
96
+ attr_reader :name, :sym
97
+ attr_accessor :cost, :preconditions, :postconditions, :variables
98
+
99
+ def self.read(i, lines, variables)
100
+ op = Operator.new(lines[i+1])
101
+ i += 2
102
+ last = nil
103
+ i.upto(lines.length) do |j|
104
+ i = j
105
+ break if lines[j] == 'end_operator'
106
+ parts = lines[j].split(' ')
107
+ if parts.length > 1
108
+ var = variables[parts[1].to_i]
109
+ pre = (parts[2] == '-1' ? nil : var[parts[2].to_i])
110
+ post = (parts[3].nil? ? nil : var[parts[3].to_i])
111
+ op.param var, pre, post
112
+ end
113
+ last = lines[j]
114
+ end
115
+ op.cost = last.to_i
116
+ fail "Cannot find end_operator" if lines[i] != 'end_operator'
117
+ [i, op]
118
+ end
25
119
 
26
120
  def initialize(name, cost=1)
27
121
  @name = name
122
+ @sym = @name.to_sym
28
123
  @cost = cost
29
124
  @preconditions = {}
30
125
  @postconditions = {}
31
126
  @variables = {}
32
127
  end
33
128
 
34
- def <<(variable, pre=nil, post=nil)
129
+ def param(variable, pre=nil, post=nil)
35
130
  return if variable.nil? or (pre.nil? and post.nil?)
36
131
  if !pre.nil?
37
132
  fail "Invalid precondition #{variable.name}:#{pre}" if !variable.index(pre)
38
- @preconditions[variable] = pre
133
+ @preconditions[variable.sym] = pre
39
134
  end
40
135
  if !post.nil?
41
136
  fail "Invalid postcondition #{variable.name}:#{post}" if !variable.index(post)
42
- @postconditions[variable] = post
137
+ @postconditions[variable.sym] = post
43
138
  end
44
- @variables[variable.name] = variable
139
+ @variables[variable.sym] = variable
45
140
  end
46
141
 
47
142
  def applicable(state)
48
- @preconditions.each { |var,pre| return false if state[var] != pre }
143
+ @preconditions.each { |var,pre| return false if state[var.sym] != pre }
49
144
  true
50
145
  end
51
146
 
52
147
  def apply(state)
53
- @postconditions.each { |var,post| state[var] = post }
148
+ @postconditions.each { |var,post| state[var.sym] = post }
54
149
  end
55
150
 
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]
151
+ def update_variables_joints_and_dependencies(variables)
152
+ @postconditions.each_key { |post|
153
+ var_post = variables[post]
154
+ @preconditions.each_key { |pre|
155
+ next if post == pre
156
+ var_pre = variables[pre]
157
+ if !var_post.dependencies.has_key?(pre)
158
+ var_post.dependencies[pre] = [self]
62
159
  else
63
- var_post.dependencies[var_pre] << self
160
+ var_post.dependencies[pre] << self
64
161
  end
65
162
  }
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]
163
+ @postconditions.each_key { |post2|
164
+ next if post == post2
165
+ var_post2 = variables[post2]
166
+ if !var_post.joints.has_key?(post2)
167
+ var_post.joints[post2] = [self]
70
168
  else
71
- var_post.joins[var_post2] << self
169
+ var_post.joints[post2] << self
72
170
  end
73
171
  }
74
172
  }
75
173
  end
174
+
175
+ def to_s
176
+ @name + " pre:" + @preconditions.inspect + " post:" + @postconditions.inspect
177
+ end
76
178
  end
77
179
 
78
180
  class State < Hash
79
181
  attr_reader :id
80
-
182
+
183
+ def self.read(i, lines, variables)
184
+ state = State.new(lines[i] == 'begin_state' ? 'init' : 'goal')
185
+ if state.id == 'init'
186
+ i += 1
187
+ var_index = 0
188
+ i.upto(lines.length) do |j|
189
+ i = j
190
+ break if lines[j] == 'end_state'
191
+ var = variables[var_index]
192
+ state[var.sym] = var[lines[j].to_i]
193
+ var_index += 1
194
+ end
195
+ fail "Cannot find end_state" if lines[i] != 'end_state'
196
+ [i, state]
197
+ elsif state.id == 'goal'
198
+ i += 2
199
+ i.upto(lines.length) do |j|
200
+ i = j
201
+ break if lines[j] == 'end_goal'
202
+ parts = lines[j].split(' ')
203
+ var = variables[parts[0].to_i]
204
+ state[var.sym] = var[parts[1].to_i]
205
+ end
206
+ fail "Cannot find end_goal" if lines[i] != 'end_goal'
207
+ [i, state]
208
+ end
209
+ end
210
+
81
211
  def initialize(id)
82
212
  @id = id
83
213
  end
84
214
  end
85
215
  end
216
+
217
+ Planner.new(:sas => File.read(ARGV[0]))
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sfpagent
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.14
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2013-08-13 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sfp
16
- requirement: &6605340 !ruby/object:Gem::Requirement
16
+ requirement: &13903480 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,7 +21,7 @@ dependencies:
21
21
  version: 0.3.12
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *6605340
24
+ version_requirements: *13903480
25
25
  description: A Ruby implementation of SFP agent.
26
26
  email: herry13@gmail.com
27
27
  executables:
@@ -34,6 +34,7 @@ files:
34
34
  - README.md
35
35
  - VERSION
36
36
  - bin/cert.rb
37
+ - bin/install_module
37
38
  - bin/sfpagent
38
39
  - lib/sfpagent.rb
39
40
  - lib/sfpagent/agent.rb