nuri 0.5.1

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.
Files changed (169) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +6 -0
  3. data/.travis.yml +12 -0
  4. data/CHANGELOG +146 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +28 -0
  7. data/README.md +64 -0
  8. data/Rakefile +15 -0
  9. data/VERSION +1 -0
  10. data/bin/delete_modules +11 -0
  11. data/bin/install_agent +18 -0
  12. data/bin/install_module +65 -0
  13. data/bin/nuri +519 -0
  14. data/bin/nuri.old +183 -0
  15. data/bin/push_model +16 -0
  16. data/examples/.gitignore +3 -0
  17. data/examples/bonfire.sfp +95 -0
  18. data/examples/bonfire/epcc.sfp +43 -0
  19. data/examples/bonfire/epcc0.sfp +49 -0
  20. data/examples/bonfire/epcc2.sfp +52 -0
  21. data/examples/bonfire/epcc2a.sfp +25 -0
  22. data/examples/bonfire/inria.sfp +72 -0
  23. data/examples/bonfire/inria0.sfp +49 -0
  24. data/examples/bonfire/inria2.sfp +71 -0
  25. data/examples/bonfire/inria2a.sfp +44 -0
  26. data/examples/bonfire/inria2b.sfp +54 -0
  27. data/examples/bonfire/inria2c.sfp +62 -0
  28. data/examples/bonfire/inria2d.sfp +71 -0
  29. data/examples/bonfire/inria2e.sfp +80 -0
  30. data/examples/bonfire/main.sfp +33 -0
  31. data/examples/bonfire/old/bonfire-1-1-1.sfp +76 -0
  32. data/examples/bonfire/old/bonfire-1-10-1.sfp +77 -0
  33. data/examples/bonfire/old/bonfire-1-2-1.sfp +58 -0
  34. data/examples/bonfire/old/bonfire-1-3-1.sfp +61 -0
  35. data/examples/bonfire/old/bonfire-1-4-1.sfp +64 -0
  36. data/examples/bonfire/old/bonfire-1-5-1.sfp +67 -0
  37. data/examples/bonfire/old/bonfire-1-6-1.sfp +82 -0
  38. data/examples/bonfire/old/bonfire-1-7-1.sfp +82 -0
  39. data/examples/bonfire/old/bonfire-1-8-1.sfp +79 -0
  40. data/examples/bonfire/old/bonfire-1-9-1.sfp +83 -0
  41. data/examples/bonfire/old/wp-test1a.sfp +38 -0
  42. data/examples/bonfire/old/wp-test1b.sfp +18 -0
  43. data/examples/bonfire/old/wp-test1c.sfp +7 -0
  44. data/examples/bonfire/old/wp-test2.sfp +47 -0
  45. data/examples/bonfire/old3/bonfire-epcc.sfp +57 -0
  46. data/examples/bonfire/old3/bonfire-inria.sfp +72 -0
  47. data/examples/bonfire/old3/bonfire-master.sfp +18 -0
  48. data/examples/bonfire/old3/bonfire.sfp +23 -0
  49. data/examples/bonfire/old3/bonfire2.sfp +49 -0
  50. data/examples/bonfire/old3/bonfire3.sfp +76 -0
  51. data/examples/bonfire/old3/bonfire4.sfp +78 -0
  52. data/examples/bonfire/old3/bonfire5.sfp +34 -0
  53. data/examples/bonfire/old3/bonfire5b.sfp +84 -0
  54. data/examples/bonfire/old3/hpvm6.sfp +22 -0
  55. data/examples/bonfire/old3/model.json +1 -0
  56. data/examples/bonfire/old3/test0.sfp +16 -0
  57. data/examples/bonfire/old3/test1.sfp +5 -0
  58. data/examples/bonfire/old3/test10.sfp +5 -0
  59. data/examples/bonfire/old3/test2.sfp +18 -0
  60. data/examples/bonfire/old3/test3.sfp +10 -0
  61. data/examples/bonfire/old3/test4.sfp +11 -0
  62. data/examples/bonfire/old3/test5.sfp +18 -0
  63. data/examples/bonfire/old3/test6.sfp +19 -0
  64. data/examples/bonfire/old3/test7.sfp +34 -0
  65. data/examples/bonfire/old3/test8.sfp +5 -0
  66. data/examples/bonfire/old3/test9.sfp +16 -0
  67. data/examples/bonfire/old3/wordpress-test-cluster.sfp +38 -0
  68. data/examples/bonfire/old3/wordpress-test.sfp +22 -0
  69. data/examples/bonfire/old3/wp-test-2.sfp +49 -0
  70. data/examples/bonfire/test.sfp +13 -0
  71. data/examples/generator.rb +66 -0
  72. data/examples/hadoop2.sfp +20 -0
  73. data/examples/hpcloud.sfp +18 -0
  74. data/examples/run.rb +17 -0
  75. data/examples/test.inc +0 -0
  76. data/examples/test.sfp +11 -0
  77. data/lib/naas/d3.js +5 -0
  78. data/lib/naas/d3.v3.min.js +5 -0
  79. data/lib/naas/index.css +0 -0
  80. data/lib/naas/index.html +18 -0
  81. data/lib/naas/index.js +18 -0
  82. data/lib/naas/jquery-1.10.2.min.js +6 -0
  83. data/lib/naas/jquery.js +6 -0
  84. data/lib/naas/naas.rb +160 -0
  85. data/lib/nuri.rb +62 -0
  86. data/lib/nuri/choreographer.rb +151 -0
  87. data/lib/nuri/constraint_helper.rb +9 -0
  88. data/lib/nuri/directory.rb +40 -0
  89. data/lib/nuri/master.rb +725 -0
  90. data/lib/nuri/net_helper.rb +65 -0
  91. data/lib/nuri/orchestrator.rb +224 -0
  92. data/lib/nuri/server.rb +212 -0
  93. data/modules/.gitignore +4 -0
  94. data/modules/apache/apache.rb +255 -0
  95. data/modules/apache/apache.rb.old +167 -0
  96. data/modules/apache/apache.sfp +146 -0
  97. data/modules/apache/apache.sfp.future +100 -0
  98. data/modules/apache/load_balancer +20 -0
  99. data/modules/apache/model.json +1 -0
  100. data/modules/apache/test.sfp +8 -0
  101. data/modules/aptpackage/aptpackage.rb +82 -0
  102. data/modules/aptpackage/aptpackage.sfp +5 -0
  103. data/modules/bonfire/.gitignore +2 -0
  104. data/modules/bonfire/README.md +12 -0
  105. data/modules/bonfire/bonfire.rb +60 -0
  106. data/modules/bonfire/bonfire.sfp +9 -0
  107. data/modules/bonfire/config.yml +4 -0
  108. data/modules/bonfire/helper.rb +149 -0
  109. data/modules/bonfire/stresstest.rb +144 -0
  110. data/modules/bonfire/test.sfp +8 -0
  111. data/modules/client/client.rb +22 -0
  112. data/modules/client/client.sfp +14 -0
  113. data/modules/cloud/cloud.rb +11 -0
  114. data/modules/cloud/cloud.sfp +26 -0
  115. data/modules/file/file.rb +91 -0
  116. data/modules/file/file.sfp +9 -0
  117. data/modules/hadoop1/core-site.xml +17 -0
  118. data/modules/hadoop1/hadoop-env.sh +55 -0
  119. data/modules/hadoop1/hadoop1.rb +384 -0
  120. data/modules/hadoop1/hadoop1.sfp +93 -0
  121. data/modules/hadoop1/hdfs-site.xml +16 -0
  122. data/modules/hadoop1/mapred-site.xml +17 -0
  123. data/modules/hadoop2/core-site.xml +31 -0
  124. data/modules/hadoop2/hadoop-env.sh +77 -0
  125. data/modules/hadoop2/hadoop2.rb +401 -0
  126. data/modules/hadoop2/hadoop2.sfp +114 -0
  127. data/modules/hadoop2/hdfs-site.xml +47 -0
  128. data/modules/hadoop2/mapred-site.xml +71 -0
  129. data/modules/hadoop2/ports +14 -0
  130. data/modules/hadoop2/yarn-env.sh +112 -0
  131. data/modules/hadoop2/yarn-site.xml +107 -0
  132. data/modules/hpcloud/.gitignore +2 -0
  133. data/modules/hpcloud/README.md +16 -0
  134. data/modules/hpcloud/config.yml +3 -0
  135. data/modules/hpcloud/example.sfp +18 -0
  136. data/modules/hpcloud/hpcloud.rb +241 -0
  137. data/modules/hpcloud/hpcloud.sfp +22 -0
  138. data/modules/hpcloud/test.sfp +5 -0
  139. data/modules/install_module +65 -0
  140. data/modules/machine/machine.rb +95 -0
  141. data/modules/machine/machine.sfp +9 -0
  142. data/modules/mockcloud/mockcloud.rb +20 -0
  143. data/modules/mockcloud/mockcloud.sfp +6 -0
  144. data/modules/mysql/mysql.rb +118 -0
  145. data/modules/mysql/mysql.sfp +38 -0
  146. data/modules/mysql/test.sfp +3 -0
  147. data/modules/node/node.rb +8 -0
  148. data/modules/node/node.sfp +7 -0
  149. data/modules/object/object.rb +7 -0
  150. data/modules/object/object.sfp +1 -0
  151. data/modules/os/os.rb +38 -0
  152. data/modules/os/os.sfp +11 -0
  153. data/modules/package/package.rb +26 -0
  154. data/modules/package/package.sfp +22 -0
  155. data/modules/package/test.sfp +6 -0
  156. data/modules/service/model.json +1 -0
  157. data/modules/service/service.rb +50 -0
  158. data/modules/service/service.sfp +46 -0
  159. data/modules/service/test.sfp +6 -0
  160. data/modules/tarpackage/tarpackage.rb +93 -0
  161. data/modules/tarpackage/tarpackage.sfp +5 -0
  162. data/modules/vm/vm.rb +8 -0
  163. data/modules/vm/vm.sfp +18 -0
  164. data/modules/wordpress/wordpress.rb +98 -0
  165. data/modules/wordpress/wordpress.sfp +34 -0
  166. data/modules/wordpresscluster/wordpresscluster.rb +150 -0
  167. data/modules/wordpresscluster/wordpresscluster.sfp +74 -0
  168. data/nuri.gemspec +26 -0
  169. metadata +281 -0
@@ -0,0 +1,65 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+
4
+ module Nuri::Net
5
+ end
6
+
7
+ module Nuri::Net::Helper
8
+ def post_data(address, port, path, data, open_timeout=5, read_timeout=1800)
9
+ uri = create_uri(address, port, path)
10
+ req = Net::HTTP::Post.new(uri.path)
11
+ req.set_form_data(data)
12
+ http_request(uri, req, open_timeout, read_timeout)
13
+ end
14
+
15
+ def put_data(address, port, path, data, open_timeout=5, read_timeout=1800)
16
+ uri = create_uri(address, port, path)
17
+ req = Net::HTTP::Put.new(uri.path)
18
+ req.set_form_data(data)
19
+ http_request(uri, req, open_timeout, read_timeout)
20
+ end
21
+
22
+ def get_data(address, port, path, open_timeout=5, read_timeout=1800)
23
+ uri = create_uri(address, port, path)
24
+ req = Net::HTTP::Get.new(uri.path)
25
+ http_request(uri, req, open_timeout, read_timeout)
26
+ end
27
+
28
+ def delete_data(address, port, path, open_timeout=5, read_timeout=1800)
29
+ uri = create_uri(address, port, path)
30
+ req = Net::HTTP::Delete.new(uri.path)
31
+ http_request(uri, req, open_timeout, read_timeout)
32
+ end
33
+
34
+ private
35
+ def create_uri(address, port, path)
36
+ address = address.to_s.strip
37
+ port = port.to_s.strip
38
+ path = path.to_s.strip
39
+ raise Exception, "Invalid parameters [address:#{address},port:#{port},path:#{path}]" if
40
+ address.length <= 0 or port.length <= 0 or path.length <= 0
41
+ path.sub!(/^\/+/, '')
42
+ URI.parse("http://#{address}:#{port}/#{path}")
43
+ end
44
+
45
+ def use_http_proxy?(uri)
46
+ ENV['no_proxy'].to_s.split(',').each { |pattern|
47
+ pattern.chop! if pattern[-1] == '*'
48
+ return false if uri.host[0,pattern.length] == pattern
49
+ }
50
+ true
51
+ end
52
+
53
+ def http_request(uri, request, open_timeout=5, read_timeout=1800)
54
+ if ENV['http_proxy'].to_s.strip != '' and use_http_proxy?(uri)
55
+ proxy = URI.parse(ENV['http_proxy'])
56
+ http = Net::HTTP::Proxy(proxy.host, proxy.port).new(uri.host, uri.port)
57
+ else
58
+ http = Net::HTTP.new(uri.host, uri.port)
59
+ end
60
+ http.open_timeout = open_timeout
61
+ http.read_timeout = read_timeout
62
+ http.start
63
+ http.request(request) { |res| return [res.code, res.body] }
64
+ end
65
+ end
@@ -0,0 +1,224 @@
1
+ require 'thread'
2
+
3
+ module Nuri::Orchestrator
4
+ include Nuri::Net::Helper
5
+
6
+ def execute_plan(p={})
7
+ raise Exception, "Plan file is not exist!" if not File.exist?(p[:execute].to_s) and !p[:plan]
8
+ raise Exception, "Plan is not exist (parameter :plan must be given)!" if !p[:plan]
9
+
10
+ push_agents_list
11
+
12
+ success = false
13
+ benchmark = Benchmark.measure {
14
+ plan = (p[:plan] ? p[:plan] : JSON[File.read(p[:execute])])
15
+ raise Exception, "Invalid plan!" if plan['workflow'].nil?
16
+ if plan.is_a?(Hash) and plan['type'] == 'sequential'
17
+ success = execute_sequential_plan(plan, p)
18
+ elsif plan.is_a?(Hash) and plan['type'] == 'parallel'
19
+ success = execute_parallel_plan(plan, p)
20
+ end
21
+ }
22
+ puts "Execution time (s): #{benchmark}"
23
+
24
+ success
25
+ end
26
+
27
+ protected
28
+ def send_action_data(action, address, port)
29
+ action['parameters'].each do |name,value|
30
+ next if !value.is_a?(String) or !value.isref or value.split('.').length > 2
31
+ _, target_name = value.split('.', 2)
32
+ data = {'model' => Sfp::Helper.deep_clone(@model[target_name])}
33
+ data.accept(Sfp::Visitor::ParentEliminator.new)
34
+ data = {'model' => JSON.generate(data)}
35
+ code = nil
36
+ begin
37
+ code, _ = put_data(address, port, "/model/cache/#{target_name}", data)
38
+ rescue
39
+ end
40
+ if code != '200'
41
+ $stderr.puts "Sending action data of #{value} to #{address}:#{port} [Failed]".red
42
+ return false
43
+ end
44
+ end
45
+
46
+ true
47
+ end
48
+
49
+ def execute_action(action)
50
+ _, agent_name, _ = action['name'].split('.', 3)
51
+ agents = get_agents
52
+
53
+ return false if !agents[agent_name]['sfpAddress'].is_a?(String)
54
+
55
+ address = agents[agent_name]['sfpAddress'].to_s
56
+ port = agents[agent_name]['sfpPort'].to_s
57
+
58
+ raise Exception, "Cannot find address:port of agent #{agent_name}" if
59
+ address.length <= 0 or port.length <= 0
60
+
61
+ send_action_data(action, address, port)
62
+
63
+ data = {'action' => JSON.generate(action)}
64
+ code, _ = post_data(address, port, '/execute', data)
65
+ if code.to_i == 200
66
+ # if the action is "create_vm" or "delete_vm", then
67
+ # update the VMs' address, and then push agents list
68
+ postprocess_create_or_delete_vm(action) if action['name'] =~ /^\$(\.[a-zA-Z0-9_]+)*\.(create_vm|delete_vm)/
69
+ true
70
+ else
71
+ false
72
+ end
73
+ end
74
+
75
+ def postprocess_create_or_delete_vm(action)
76
+ @mutex_postprocess = Mutex.new if !defined?(@mutex_postprocess)
77
+
78
+ @mutex_postprocess.synchronize {
79
+ _, agent_name, _ = action['name'].split('.', 3)
80
+
81
+ state = {agent_name => get_node_state(agent_name)}
82
+ vms1, vms2 = update_vms_address(state)
83
+
84
+ if action['name'] =~ /^\$(\.[a-zA-Z0-9_]+)*\.(create_vm)/
85
+ vm_name = action['parameters']['$.vm'].sub(/^\$\./, '')
86
+ get_node_state(vm_name, true)
87
+
88
+ elsif action['name'] =~ /^\$(\.[a-zA-Z0-9_]+)*\.(delete_vm)/
89
+ model = @model.at?(action['parameters']['$.vm'])
90
+ if model.is_a?(Hash)
91
+ model['sfpAddress'] = {'_context'=>'any_value','_isa'=>'$.String'}
92
+ model['sfpPort'] = {'_context'=>'any_value','_isa'=>'$.Number'}
93
+ end
94
+ end
95
+
96
+ push_agents_list
97
+ }
98
+ end
99
+
100
+ def execute_sequential_plan(plan, p)
101
+ begin
102
+ index = 1
103
+ plan['workflow'].each do |action|
104
+ print "#{index}. #{action['name']} #{JSON.generate(action['parameters'])}... "
105
+ if execute_action(action)
106
+ puts "[OK]".green
107
+ else
108
+ puts "[Failed]".red
109
+ return false
110
+ end
111
+ index += 1
112
+ end
113
+ return true
114
+ rescue Exception => e
115
+ puts "#{e}\n#{e.backtrace.join("\n")}".red
116
+ end
117
+ false
118
+ end
119
+
120
+ def execute_parallel_plan(plan, options)
121
+ @retries = 2
122
+
123
+ @actions = plan['workflow']
124
+ @actions.sort! { |x,y| x['id'] <=> y['id'] }
125
+ @actions.each { |op|
126
+ op[:executed] = false
127
+ op[:executor] = nil
128
+ op[:string] = "#{op['id'] + 1}: #{op['name']} #{JSON.generate(op['parameters'])}"
129
+ }
130
+
131
+ @threads = []
132
+ @actions_failed = []
133
+ @mutex = Mutex.new
134
+ @failed = false
135
+ @thread_id = 0
136
+
137
+ def next_thread_id
138
+ id = 0
139
+ @mutex.synchronize { @thread_id = id = @thread_id + 1 }
140
+ id
141
+ end
142
+
143
+ def assign_action_with_id(id)
144
+ thread_id = next_thread_id
145
+ action = @actions[id]
146
+ action[:executor] = thread_id
147
+ self.thread_execute_action(thread_id, action)
148
+ end
149
+
150
+ def thread_execute_action(tid, action)
151
+ t = Thread.new {
152
+ # Register the thread
153
+ @mutex.synchronize { @threads << tid }
154
+
155
+ while not @failed and not action[:executed]
156
+ # Try to execute the action
157
+ puts "Executing #{action[:string]} - thread ##{tid} [WAIT]".yellow
158
+ success = false
159
+ 1.upto(@retries) do |i|
160
+ begin
161
+ success = execute_action(action)
162
+ rescue Exception => exp
163
+ puts "Executing(#{i}) #{action[:string]} - thread ##{tid} [FAILED]\n#{exp}\n#{exp.backtrace.join("\n")}"
164
+ end
165
+ break if success
166
+ end
167
+
168
+ # Check if execution failed
169
+ if success
170
+ # Execution was success
171
+ puts "Executing #{action[:string]} - thread ##{tid} [OK]".green
172
+ next_actions = []
173
+ @mutex.synchronize {
174
+ action[:executed] = true # set executed
175
+ # select next action to be executed from successor actions list
176
+ # select a successor action that has not been assigned to any thread yet
177
+ if action['successors'].length > 0
178
+ action['successors'].each { |id|
179
+ if @actions[id][:executor].nil?
180
+ # check if all predecessors actions have been executed
181
+ predecessors_ok = true
182
+ @actions[id]['predecessors'].each { |pid|
183
+ predecessors_ok = (predecessors_ok and @actions[pid][:executed])
184
+ }
185
+ # assign this action to be executed by this thread if all predecessors
186
+ # have been executed
187
+ next_actions << id if predecessors_ok
188
+ end
189
+ }
190
+ end
191
+ next_actions.each { |id| @actions[id][:executor] = tid }
192
+ }
193
+ if next_actions.length > 0
194
+ # execute the first next actions to this thread
195
+ action = @actions[next_actions[0]]
196
+ if next_actions.length > 1
197
+ # execute other next actions to other threads
198
+ for i in 1..(next_actions.length-1)
199
+ assign_action_with_id(next_actions[i])
200
+ end
201
+ end
202
+ end
203
+
204
+ else
205
+ # Execution was failed
206
+ puts "Executing #{action[:string]} - thread ##{tid} [FAILED]".red
207
+ @mutex.synchronize {
208
+ @failed = true # set global flag to stop the execution
209
+ @actions_failed << action
210
+ }
211
+ end
212
+ end
213
+
214
+ @mutex.synchronize { @threads.delete(tid) }
215
+ }
216
+ end
217
+
218
+ plan['init'].each { |id| assign_action_with_id(id) }
219
+ begin
220
+ sleep 1
221
+ end while @threads.length > 0
222
+ (not @failed)
223
+ end
224
+ end
@@ -0,0 +1,212 @@
1
+ require 'yaml'
2
+ require 'logger'
3
+ require 'webrick'
4
+
5
+ module Nuri::Server
6
+ VarDir = (Process.euid == 0 ? '/var/nuri' : File.expand_path('~/.nuri'))
7
+ Dir.mkdir(VarDir, 0700) if not File.exist?(VarDir)
8
+
9
+ PIDFile = "#{VarDir}/nuri.pid"
10
+ PlanFile = "#{VarDir}/plan.json"
11
+ LogFile = "#{VarDir}/nuri.log"
12
+ ModelFile = "#{VarDir}/main.sfp"
13
+ ModulesDir = "#{VarDir}/modules"
14
+ Dir.mkdir(ModulesDir, 0700) if not File.exist?(ModulesDir)
15
+
16
+ ConfigDir = (Process.euid == 0 ? '/etc/nuri' : File.expand_path('~/.nuri'))
17
+ ConfigFile = "#{ConfigDir}/nuri.yml"
18
+
19
+ DefaultIntervalTime = 5
20
+ DefaultPort = 1313
21
+
22
+ def init
23
+ Dir.mkdir(ConfigDir, '0755') if not File.exist?(ConfigDir)
24
+
25
+ if not File.exist?(ConfigFile)
26
+ # generate and save default configuration file
27
+ config = { "port" => DefaultPort,
28
+ "interval_time" => 5 }
29
+ File.open(ConfigFile, 'w', 0644) { |f| f.write(YAML.dump(config)) }
30
+
31
+ # generate an empty model file
32
+ File.open(ModelFile, 'w', 0644) { |f| f.write('') }
33
+ end
34
+
35
+ #if not File.exist?(".git")
36
+ # system("cd #{ConfigDir}; git init 1>/dev/null 2>/dev/null")
37
+ # File.open("#{ConfigDir}/.gitignore", 'w', 0644) { |f| f.write("*.pid\n*.log\n") }
38
+ # system("cd #{ConfigDir}; git add -A . 1>/dev/null 2>/dev/null; git commit -m 'nuri init' 1>/dev/null 2>/dev/null")
39
+ #end
40
+ end
41
+
42
+ def start
43
+ if not File.exist?(ConfigFile)
44
+ puts "Execute 'nuri init' first!"
45
+ exit!(1)
46
+ end
47
+
48
+ if File.exist?(PIDFile)
49
+ pid = File.read(PIDFile).to_i
50
+ begin
51
+ Process.kill 0, pid if pid > 0
52
+ # Nuri server is still running
53
+ puts "Nuri server is already running with PID #{pid}."
54
+ exit!(1)
55
+ rescue
56
+ end
57
+ end
58
+
59
+ @logger = WEBrick::Log.new(LogFile, WEBrick::BasicLog::INFO ||
60
+ WEBrick::BasicLog::ERROR ||
61
+ WEBrick::BasicLog::FATAL ||
62
+ WEBrick::BasicLog::WARN)
63
+
64
+ @logger.info "Reading configuration file [Wait]"
65
+ @config = YAML.load(File.read(ConfigFile))
66
+ @logger.info "Reading configuration file [OK]"
67
+
68
+ ['INT', 'KILL', 'HUP'].each { |signal|
69
+ trap(signal) {
70
+ @logger.info "Shutting down Nuri server..."
71
+ @enabled = false
72
+ @web_server.shutdown
73
+ }
74
+ }
75
+
76
+ puts "Starting Nuri server..."
77
+ Process.daemon
78
+
79
+ File.open("#{PIDFile}", 'w', 0644) { |f| f.write($$.to_s) }
80
+
81
+ start_web_server
82
+
83
+ start_nuri_server
84
+ end
85
+
86
+ def start_nuri_server
87
+ @enabled = true
88
+ @logger.info "Nuri server is running."
89
+
90
+ options = { :parallel => true,
91
+ :model_file => ModelFile,
92
+ :apply => true,
93
+ :push_modules => true,
94
+ :modules_dir => ModulesDir,
95
+ :silent => true }
96
+
97
+ interval_time = (@config['interval_time'] ? @config['interval_time'].to_i : DefaultIntervalTime)
98
+
99
+ while @enabled
100
+ @logger.info "Executing the model."
101
+
102
+ plan = nil
103
+ begin
104
+ #plan = get_plan if File.exist?(ModelFile)
105
+
106
+ if plan.is_a?(Hash)
107
+ File.open(PlanFile, 'w', '0644') { |f| f.write(JSON.pretty_generate(plan)) }
108
+ @logger.info "Executing the generated plan in file #{PlanFile}."
109
+ options[:plan] = plan
110
+ if execute_plan(options)
111
+ @logger.info "Execution is success!"
112
+ else
113
+ @logger.info "Execution is failed!"
114
+ end
115
+ end
116
+
117
+ rescue Exception => e
118
+ @logger.error "Error: #{e}\n#{e.backtrace.join("\n")}"
119
+ end
120
+
121
+ sleep interval_time
122
+ end
123
+
124
+ @logger.info "Nuri server has stopped."
125
+ end
126
+
127
+ def start_web_server
128
+ Thread.new {
129
+ config = { :Host => '0.0.0.0',
130
+ :Port => (@config['port'] ? @config['port'].to_i : DefaultPort),
131
+ :ServerType => WEBrick::SimpleServer,
132
+ :Logger => @logger }
133
+ @web_server = WEBrick::HTTPServer.new(config)
134
+ @web_server.mount('/', Nuri::Server::Handler, self, @logger)
135
+ @web_server.start
136
+ }
137
+ end
138
+
139
+ def stop
140
+ begin
141
+ pid = File.read(PIDFile).to_i
142
+ Process.kill 'HUP', pid
143
+ puts "Stopping Nuri server..."
144
+ rescue
145
+ puts "Nuri server is not running."
146
+ end
147
+ end
148
+
149
+ def get_model(p={})
150
+ (File.exist?(ModelFile) ? File.read(ModelFile) : '')
151
+ end
152
+
153
+ def get_log(total_lines=0)
154
+ (File.exist?(LogFile) ? `tail -n #{total_lines} #{LogFile}` : '')
155
+ end
156
+
157
+ class Handler < WEBrick::HTTPServlet::AbstractServlet
158
+ def initialize(server, master, logger)
159
+ @master = master
160
+ @logger = logger
161
+ end
162
+
163
+ def do_GET(request, response)
164
+ status = 400
165
+ content_type, body = ''
166
+
167
+ if not trusted(request.peeraddr[2])
168
+ status = 403
169
+ else
170
+ path = (request.path[-1,1] == '/' ? request.path.chop : request.path)
171
+ if path == '/model'
172
+ status, content_type, body = [200, 'text/plain', @master.get_model(:json => true)]
173
+ elsif path == '/log'
174
+ status, content_type, body = [200, 'text/plain', @master.get_log(100)]
175
+ end
176
+ end
177
+
178
+ response.status = status
179
+ response['Content-Type'] = content_type
180
+ response.body = body
181
+ end
182
+
183
+ def do_POST(request, response)
184
+ status = 400
185
+ content_type, body = ''
186
+
187
+ response.status = status
188
+ response['Content-Type'] = content_type
189
+ response.body = body
190
+ end
191
+
192
+ def do_PUT(request, response)
193
+ status = 400
194
+ content_type, body = ''
195
+
196
+ response.status = status
197
+ response['Content-Type'] = content_type
198
+ response.body = body
199
+ end
200
+
201
+ def trusted(address)
202
+ # TODO
203
+ true
204
+ end
205
+ end
206
+ end
207
+
208
+ #nuri = Object.new
209
+ #nuri.extend(Nuri::Server)
210
+ #nuri.init
211
+ #nuri.start
212
+ #nuri.stop