nuri 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
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,9 @@
1
+ module Sfp::Helper::Constraint
2
+ def self.equals(value)
3
+ { '_context' => 'constraint', '_type' => 'equals', '_value' => value }
4
+ end
5
+
6
+ def self.and(name)
7
+ { '_context' => 'constraint', '_type' => 'and', '_self' => name }
8
+ end
9
+ end
@@ -0,0 +1,40 @@
1
+ require 'thread'
2
+
3
+ module Nuri::Master::Model
4
+ @@mutex = Mutex.new
5
+ @@model = {}
6
+
7
+ def self.set(path, data)
8
+ @@mutex.synchronize {
9
+ parent_path, key = path.pop_ref
10
+ if key.nil?
11
+ @@model[parent_path] = data
12
+ else
13
+ parent = @@model.at?(parent_path)
14
+ fail "#{parent_path} is invalid! - #{parent.class.name}" if !parent.is_a?(Hash)
15
+ parent[key] = data
16
+ end
17
+ }
18
+ end
19
+
20
+ def self.get(path)
21
+ data = nil
22
+ @@mutex.synchronize {
23
+ data = @@model.at?(path)
24
+ }
25
+ data
26
+ end
27
+
28
+ def self.delete(path)
29
+ @@mutex.synchronize {
30
+ parent_path, key = path.pop_ref if path.pop_ref
31
+ if key.nil?
32
+ @@model.delete(parent_path)
33
+ else
34
+ parent = @@model.at?(parent_path)
35
+ fail "#{parent_path} is invalid! - #{parent.class.name}" if !parent.is_a?(Hash)
36
+ parent.delete(key)
37
+ end
38
+ }
39
+ end
40
+ end
@@ -0,0 +1,725 @@
1
+ require 'thread'
2
+
3
+ class Nuri::Master
4
+ include Nuri::Net::Helper
5
+ include Nuri::Orchestrator
6
+ include Nuri::Choreographer
7
+ #include Nuri::Server
8
+
9
+ SfpUnknown = Sfp::Unknown.new
10
+ SfpUndefined = Sfp::Undefined.new
11
+
12
+ AgentSchema = '$.Node'
13
+ CloudSchema = '$.Cloud'
14
+ VMSchema = '$.VM'
15
+
16
+ attr_reader :model
17
+
18
+ def initialize(p={})
19
+ @mutex_vm_updater = Mutex.new
20
+ @cloudfinder = Sfp::Helper::CloudFinder.new
21
+ @local_agent = nil
22
+
23
+ # set modules directory
24
+ if p[:modules_dir] and File.directory?(p[:modules_dir])
25
+ @modules_dir = File.expand_path(p[:modules_dir])
26
+ elsif ENV['NURI_HOME'].is_a?(String) and ENV['NURI_HOME'].strip.length > 0
27
+ @modules_dir = File.join(ENV['NURI_HOME'], 'modules')
28
+ elsif File.directory?(File.expand_path(File.dirname(__FILE__) + '/../../modules'))
29
+ @modules_dir = File.expand_path(File.dirname(__FILE__) + '/../../modules')
30
+ elsif File.directory?(File.expand_path('./modules'))
31
+ @modules_dir = File.expand_path('./modules')
32
+ else
33
+ @modules_dir = '/var/nuri/modules'
34
+ end
35
+ fail "Invalid modules directory #{@modules_dir}!" if !File.directory?(@modules_dir)
36
+
37
+ @sas_post_processor = SASPostProcessor
38
+
39
+ set_model(p)
40
+ end
41
+
42
+ def set_model(p={})
43
+ if p[:model_file]
44
+ home_dir = File.expand_path File.dirname(p[:model_file])
45
+ @parser = Sfp::Parser.new({:home_dir => home_dir})
46
+ @parser.parse File.read(p[:model_file])
47
+ p[:model] = @parser.root
48
+ end
49
+
50
+ @model = (p.is_a?(Hash) and p[:model].is_a?(Hash) ? p[:model] : {})
51
+ push_agents_list if @model.length > 0
52
+
53
+ # find a list of cloud proxy
54
+ @model.accept(@cloudfinder.reset)
55
+
56
+ # create a set of not-exist VMs' state
57
+ @map = generate_not_exist_vm_state(false)
58
+ SASPostProcessor.set_map(@map)
59
+ end
60
+
61
+ def get_plan(p={})
62
+ # set parameters value to be given to the planner
63
+ p[:sfp] = create_plan_task(p)
64
+ p[:sas_post_processor] = SASPostProcessor
65
+
66
+ plan = nil
67
+ planning_time = Benchmark.measure do
68
+ planner = Sfp::Planner.new
69
+ plan = planner.solve(p)
70
+ end
71
+ puts "Planning time (s): #{planning_time}"
72
+
73
+ plan
74
+ end
75
+
76
+ def get_state(p={})
77
+ state = {}
78
+ vms = get_vms
79
+ agents = get_agents
80
+
81
+ # push agents list to each agent
82
+ push_agents_list
83
+
84
+ mutex = Mutex.new
85
+
86
+ # get state of non-VM nodes
87
+ (agents.keys - vms.keys).each do |name|
88
+ Thread.new {
89
+ node_name = name
90
+ node_state = get_node_state(node_name, !!p[:push_modules])
91
+ mutex.synchronize { state[node_name] = node_state }
92
+ }
93
+ end
94
+ total = agents.keys.length - vms.keys.length
95
+
96
+ # wait until all threads have finish
97
+ wait? { (state.length >= total) }
98
+
99
+ # assign VMs' address
100
+ exist_vms, not_exist_vms = update_vms_address(state)
101
+
102
+ # get state of existing VM nodes
103
+ exist_vms.each_key { |name|
104
+ Thread.new {
105
+ node_name = name
106
+ node_state = get_node_state(node_name, !!p[:push_modules])
107
+ mutex.synchronize { state[node_name] = node_state }
108
+ }
109
+ }
110
+
111
+ # get state of non-existing VM nodes
112
+ not_exist_vms.each { |name,model|
113
+ state[name] = get_not_exist_vm_state(model)
114
+ }
115
+
116
+ # wait until all threads have finish
117
+ wait? { (state.length >= agents.length) }
118
+
119
+ # update <vm>.in_cloud value
120
+ update_cloud_vm_relations(state, vms)
121
+
122
+ state
123
+ end
124
+
125
+ protected
126
+ def create_plan_task(p={})
127
+ task = get_schemata
128
+
129
+ puts "Getting current state [WAIT]".yellow
130
+ b = Benchmark.measure { task['initial'] = to_state('initial', get_state(p)) }
131
+ puts "Getting current state [OK] : #{b}".green
132
+
133
+ task['initial'].accept(Sfp::Visitor::SfpGenerator.new(task))
134
+ f1 = Sfp::Helper::SfpFlatten.new
135
+ task['initial'].accept(f1)
136
+
137
+ # modify condition of procedures of each VM's component
138
+ # modification: add constraint "$.vm.created = true"
139
+ task['initial'].accept(VMProcedureModifier.new(task['initial']))
140
+
141
+ # construct goal state
142
+ goalgen = GoalGenerator.new
143
+ goal = Sfp::Helper.deep_clone(get_agents)
144
+ goal.accept(FinalAttributeRemover)
145
+ goal.accept(goalgen)
146
+ task['goal'] = goalgen.results
147
+
148
+ # find dead-node, remove from the task, print WARNING to the console
149
+ dead_nodes = task['initial'].select { |k,v| v.is_a?(Sfp::Unknown) }
150
+ dead_nodes.each_key { |name|
151
+ task['initial'].delete(name)
152
+ task['goal'].keep_if { |k,v| !(k =~ /(\$\.#{name}\.|\$\.#{name}$)/) }
153
+ puts "[WARN] Removing node #{name} from the task.".red
154
+ }
155
+
156
+ # print the status of goal state
157
+ puts "Goal state:".yellow
158
+ goalgen.results.each { |k,v|
159
+ next if k[0,1] == '_'
160
+ print "- #{k}: " + Sfp::Helper.sfp_to_s(v['_value']).green
161
+ print " #{Sfp::Helper.sfp_to_s(f1.results[k])}".red if f1.results.has_key?(k) and
162
+ f1.results[k] != v['_value']
163
+ puts ""
164
+ }
165
+
166
+ # add global constraint (if exist)
167
+ task['global'] = @model['global'] if @model.has_key?('global')
168
+
169
+ # add sometime constraint (if exist)
170
+ task['sometime'] = @model['sometime'] if @model.has_key?('sometime')
171
+
172
+ # remove old parent links, and then reconstruct SFP parent links
173
+ task.accept(ParentEliminator)
174
+
175
+ # rebuild SFP data-structure
176
+ task.accept(Sfp::Visitor::SfpGenerator.new(task))
177
+
178
+ task
179
+ end
180
+
181
+ def wait?
182
+ until yield do
183
+ sleep 1
184
+ end
185
+ end
186
+
187
+ def get_dead_vm_state(model, cloud)
188
+ s = {'state' => Sfp::Helper.deep_clone(model)}
189
+ s.accept(VisitorDeadAgentNodeState)
190
+ s.accept(ParentEliminator)
191
+ s['state']['created'] = true
192
+ s['state']['in_cloud'] = cloud
193
+ s['state']
194
+ end
195
+
196
+ def generate_not_exist_vm_state(with_final_attribute=true)
197
+ map = {}
198
+ get_vms.each do |name,model|
199
+ state = {name => get_not_exist_vm_state(model)}
200
+ state.accept(FinalAttributeRemover) if not with_final_attribute
201
+ state.accept(ParentGenerator)
202
+ flatten = Sfp::Helper::SfpFlatten.new
203
+ state.accept(flatten)
204
+ map[name] = {}
205
+ flatten.results.each { |k,v| map[name][k] = v }
206
+ end
207
+ map
208
+ end
209
+
210
+ def get_not_exist_vm_state(model)
211
+ s = {'state' => Sfp::Helper.deep_clone(model)}
212
+ s.accept(VisitorNotExistNodeState)
213
+ s.accept(ParentEliminator)
214
+ s['state']['created'] = false
215
+ s['state']['in_cloud'] = {'_context' => 'null', '_value' => CloudSchema}
216
+ s['state']
217
+ end
218
+
219
+ def update_cloud_vm_relations(state, vms)
220
+ @cloudfinder.clouds.each do |cloud|
221
+ proxy = state.at?(cloud)
222
+ next if not proxy.is_a?(Hash) or not proxy['vms'].is_a?(Hash)
223
+ # for each VMs list of a cloud proxy, assign "in_cloud" attribute
224
+ # to associated VM
225
+ proxy['vms'].each do |name,data|
226
+ next if not vms.has_key?(name)
227
+ if state[name].is_a?(Hash)
228
+ state[name]['in_cloud'] = cloud
229
+ elsif state[name].is_a?(Sfp::Unknown)
230
+ state[name] = get_dead_vm_state(vms[name], cloud)
231
+ end
232
+ end
233
+ end
234
+ end
235
+
236
+ def update_vms_address(state)
237
+ exist_vms = not_exist_vms = nil
238
+ @mutex_vm_updater.synchronize {
239
+ vms = get_vms
240
+
241
+ # Reset sfpAddress, sfpPort, in_cloud of a VM if it's not found in
242
+ # previously assigned cloud
243
+ vms.each do |name,model|
244
+ next if !model['in_cloud'].is_a?(String) or !model['in_cloud'].isref
245
+ cloud, _ = @cloudfinder.clouds.select { |cloud| model['in_cloud'] == cloud }
246
+ if !cloud.nil? and !state.at?("#{cloud}.vms").has_key?(name)
247
+ vms[name]['sfpAddress'] = {'_context'=>'any_value','_isa'=>'$.String'}
248
+ vms[name]['sfpPort'] = {'_context'=>'any_value','_isa'=>'$.Number'}
249
+ end
250
+ end
251
+
252
+ @cloudfinder.clouds.each do |cloud|
253
+ proxy = state.at?(cloud)
254
+ next if not proxy.is_a?(Hash) or not proxy['vms'].is_a?(Hash)
255
+ # for each VMs list of a cloud proxy, assign the available
256
+ # ip address
257
+ proxy['vms'].each do |name,data|
258
+ if vms.has_key?(name) and data['running']
259
+ vms[name]['sfpAddress'] = data['ip']
260
+ vms[name]['sfpPort'] = 1314
261
+ end
262
+ end
263
+ end
264
+
265
+ exist_vms = vms.select { |k,v| v['sfpAddress'].is_a?(String) and v['sfpAddress'] != '' }
266
+ not_exist_vms = vms.select { |k,v| !exist_vms.has_key?(k) }
267
+ }
268
+ [exist_vms, not_exist_vms]
269
+ end
270
+
271
+ def clear_agents_list
272
+ get_agents.each do |name,model|
273
+ begin
274
+ next if not model['sfpAddress'].is_a?(String)
275
+ address = model['sfpAddress'].to_s.strip
276
+ port = model['sfpPort'].to_i
277
+ next if address == '' or port <= 0
278
+
279
+ code, _ = delete_data(address, port, '/agents')
280
+ fail "Bad response: #{code}" if code != '200'
281
+ rescue Exception => exp
282
+ $stderr.puts "Cannot delete agents data on #{name} - #{model['sfpAddress']}:#{model['sfpPort']} - #{exp}\n#{exp.backtrace.join("\n")}"
283
+ return false
284
+ end
285
+ end
286
+ true
287
+ end
288
+
289
+ def push_agents_list
290
+ begin
291
+ agents = {}
292
+ # generate agents list
293
+ get_agents.each do |name, model|
294
+ next if not model['sfpAddress'].is_a?(String)
295
+ address = model['sfpAddress'].to_s.strip
296
+ port = model['sfpPort'].to_s.strip.to_i
297
+ next if address == '' or port <= 0
298
+ agents[name] = {:sfpAddress => address, :sfpPort => port}
299
+ end
300
+ data = {'agents' => JSON.generate(agents)}
301
+
302
+ # send the list to all agents
303
+ agents.each do |name, agent|
304
+ code, _ = put_data(agent[:sfpAddress], agent[:sfpPort], '/agents', data, 5, 20)
305
+ raise Exception, "Push agents list to #{agent[:sfpAddress]}:#{agent[:sfpPort]} [Failed]" if code.to_i != 200
306
+ end
307
+ return true
308
+ rescue Exception => exp
309
+ end
310
+ false
311
+ end
312
+
313
+ def push_modules(agent_model, address=nil, port=nil)
314
+ if address.nil? or port.nil?
315
+ return false if !agent_model.is_a?(Hash) or !agent_model['sfpAddress'].is_a?(String)
316
+ address = agent_model['sfpAddress'].to_s.strip
317
+ port = agent_model['sfpPort'].to_s.strip
318
+ end
319
+
320
+ return false if address == '' or port == ''
321
+
322
+ name = agent_model['_self']
323
+ finder = Sfp::Helper::SchemaCollector.new
324
+ {:agent => agent_model}.accept(finder)
325
+ schemata = finder.schemata.uniq.map { |x| x.sub(/^\$\./, '').downcase }
326
+
327
+ begin
328
+ # get modules list
329
+ code, body = get_data(address, port, '/modules')
330
+ raise Exception, "Unable to get modules list from #{name}" if code.to_i != 200
331
+
332
+ modules = JSON[body]
333
+ list = ''
334
+ schemata.each { |m|
335
+ list += "#{m} " if File.exist?("#{@modules_dir}/#{m}") and
336
+ (not modules.has_key?(m) or modules[m] != get_local_module_hash(m).to_s)
337
+ }
338
+
339
+ return true if list == ''
340
+
341
+ if system("cd #{@modules_dir}; ./install_module #{address} #{port} #{list} 1>/dev/null 2>/tmp/install_module.error")
342
+ puts "Push modules #{list}to #{name} [OK]".green
343
+ else
344
+ puts "Push modules #{list}to #{name} [Failed]".red
345
+ end
346
+
347
+ return true
348
+
349
+ rescue Exception => e
350
+ puts "[WARN] Cannot push module to #{name} - #{e}".red
351
+ end
352
+
353
+ false
354
+ end
355
+
356
+ # return the list of Hash value of all modules
357
+ #
358
+ def get_local_module_hash(name)
359
+ module_dir = "#{@modules_dir}/#{name}"
360
+ if File.directory? module_dir
361
+ if `which md5sum`.strip.length > 0
362
+ return `find #{module_dir} -type f -exec md5sum {} + | awk '{print $1}' | sort | md5sum | awk '{print $1}'`.strip
363
+ elsif `which md5`.strip.length > 0
364
+ return `find #{module_dir} -type f -exec md5 {} + | awk '{print $4}' | sort | md5`.strip
365
+ end
366
+ end
367
+ nil
368
+ end
369
+
370
+ def get_node_state(name, do_push_modules=false)
371
+ push_modules(@model[name]) if do_push_modules
372
+
373
+ model = get_schemata
374
+ model[name] = @model[name]
375
+
376
+ begin
377
+ if http_send_agent_model(name, model)
378
+ agent_state = http_get_agent_state(name, model)
379
+ raise Exception, "Cannot get the current state of #{name}" if agent_state.nil?
380
+ return agent_state[name]
381
+ end
382
+ rescue Exception => e
383
+ puts "[WARN] Cannot get the current state of #{name} : #{e}".red
384
+ end
385
+ SfpUnknown
386
+ end
387
+
388
+ # send HTTP PUT request to push agent's model
389
+ #
390
+ def http_send_agent_model(name, model)
391
+ return false if !model[name].is_a?(Hash) or !model[name]['sfpAddress'].is_a?(String)
392
+ address = model[name]['sfpAddress'].to_s.strip
393
+ port = model[name]['sfpPort'].to_s.strip
394
+ if address != '' and port != ''
395
+ model = Sfp::Helper.deep_clone(model)
396
+ model.accept(ParentEliminator)
397
+ data = {'model' => JSON.generate(model)}
398
+ code, _ = put_data(address, port, '/model', data)
399
+ return (code.to_i == 200)
400
+ end
401
+ false
402
+ end
403
+
404
+ # send HTTP GET requst to get agent's state
405
+ #
406
+ def http_get_agent_state(name, model)
407
+ return nil if !model[name].is_a?(Hash) or !model[name]['sfpAddress'].is_a?(String)
408
+ address = model[name]['sfpAddress'].to_s.strip
409
+ port = model[name]['sfpPort'].to_s.strip
410
+ if address != '' and port != ''
411
+ code, body = get_data(address, port, '/sfpstate')
412
+ if code.to_i == 200 and body.length >= 2
413
+ state = JSON[body]
414
+ return state['state'] if state.is_a?(Hash)
415
+ return state
416
+ end
417
+ end
418
+ nil
419
+ end
420
+
421
+ def get_schemata
422
+ @model.select { |k,v| k[0,1] != '_' and v.is_a?(Hash) and
423
+ v['_context'] == 'class'
424
+ }
425
+ end
426
+
427
+ def get_agents
428
+ #@model.select { |k,v| !(k[0,1] == '_' or not v.is_a?(Hash) or
429
+ # v['_context'] != 'object' or v['_classes'].index(AgentSchema).nil?)
430
+ #}
431
+ Nuri::Master.agents(@model)
432
+ end
433
+
434
+ def self.agents(sfp)
435
+ sfp.select { |k,v| !(k[0] == '_' or not v.is_a?(Hash) or
436
+ v['_context'] != 'object' or v['_classes'].index(AgentSchema).nil?)
437
+ }
438
+ end
439
+
440
+ def get_vms
441
+ get_agents.select { |k,v| not v['_classes'].index(VMSchema).nil? }
442
+ end
443
+
444
+ def to_state(name, value)
445
+ raise Exception, 'Given value should be a Hash!' if not value.is_a?(Hash)
446
+ value['_self'] = name
447
+ value['_context'] = 'state'
448
+ value
449
+ end
450
+
451
+ def get_exist_vms
452
+ get_vms.select { |name,model| model['sfpAddress'].is_a?(String) and
453
+ model['sfpAddress'] != '' }
454
+ end
455
+
456
+
457
+ SfpUndefinedString = Sfp::Undefined.create('$.String')
458
+ SfpUndefinedNumber = Sfp::Undefined.create('$.Number')
459
+ SfpUndefinedBoolean = Sfp::Undefined.create('$.Boolean')
460
+
461
+ VisitorNotExistNodeState = Object.new
462
+ def VisitorNotExistNodeState.visit(name, value, parent)
463
+ return false if name[0,1] == '_'
464
+ if not value.is_a?(Hash)
465
+ if value.is_a?(String)
466
+ if value.isref
467
+ ref_value = parent.at?(value)
468
+ # TODO - need to handle a reference to a primitive value
469
+ if ref_value.is_a?(Hash) and (ref_value.isobject or ref_value.isnull)
470
+ parent[name] = Sfp::Undefined.create(ref_value['_isa'])
471
+ elsif ref_value.is_a?(Sfp::Undefined) or ref_value.is_a?(Sfp::Unknown)
472
+ parent[name] = ref_value
473
+ else
474
+ puts "[WARN] Sfp::Undefined => #{parent.ref.push(name)}: #{ref_value.class.name}"
475
+ parent[name] = SfpUndefined
476
+ end
477
+ else
478
+ parent[name] = SfpUndefinedString
479
+ end
480
+ elsif value.is_a?(Fixnum) or value.is_a?(Float)
481
+ parent[name] = SfpUndefinedNumber
482
+ elsif value.is_a?(TrueClass) or value.is_a?(FalseClass)
483
+ parent[name] = SfpUndefinedBoolean
484
+ else
485
+ puts "[WARN] Sfp::Undefined => " + parent.ref.push(name) + ": " + value.class.name
486
+ parent[name] = SfpUndefined
487
+ end
488
+ elsif value['_context'] == 'null' or value['_context'] == 'any_value'
489
+ parent[name] = Sfp::Undefined.create(value['_isa'])
490
+ elsif value['_context'] != 'object'
491
+ parent.delete(name)
492
+ end
493
+ true
494
+ end
495
+
496
+ VisitorDeadAgentNodeState = Object.new
497
+ def VisitorDeadAgentNodeState.visit(name, value, parent)
498
+ return false if name[0,1] == '_'
499
+ if not value.is_a?(Hash)
500
+ if value.is_a?(String)
501
+ if value.isref
502
+ ref_value = parent.at?(value)
503
+ # TODO - need to handle a reference to a primitive value
504
+ if ref_value.is_a?(Hash) and (ref_value.isobject or ref_value.isnull)
505
+ parent[name] = Sfp::Unknown.create(ref_value['_isa'])
506
+ elsif ref_value.is_a?(Sfp::Unknown) or ref_value.is_a?(Sfp::Unknown)
507
+ parent[name] = ref_value
508
+ else
509
+ puts "[WARN] Sfp::Unknown => #{parent.ref.push(name)}: #{ref_value.class.name}"
510
+ parent[name] = SfpUnknown
511
+ end
512
+ else
513
+ parent[name] = Sfp::Unknown.create('$.String')
514
+ end
515
+ elsif value.is_a?(Fixnum) or value.is_a?(Float)
516
+ parent[name] = Sfp::Unknown.create('$.Number')
517
+ elsif value.is_a?(TrueClass) or value.is_a?(FalseClass)
518
+ parent[name] = Sfp::Unknown.create('$.Boolean')
519
+ else
520
+ puts "[WARN] Sfp::Unknown => " + parent.ref.push(name) + ": " + value.class.name
521
+ parent[name] = SfpUnknown
522
+ end
523
+ elsif value['_context'] == 'null' or value['_context'] == 'any_value'
524
+ parent[name] = Sfp::Unknown.create(value['_isa'])
525
+ elsif value['_context'] != 'object'
526
+ parent.delete(name)
527
+ end
528
+ true
529
+ end
530
+
531
+ class VMProcedureModifier
532
+ def initialize(root)
533
+ @root = root
534
+ end
535
+
536
+ def visit(name, value, parent)
537
+ return false if name[0,1] == '_'
538
+ if value.is_a?(Hash) and value['_context'] == 'procedure'
539
+ _, agent, _ = parent.ref.split('.', 3)
540
+ if not @root[agent]['_classes'].index(VMSchema).nil?
541
+ value['_condition']["$.#{agent}.created"] = Sfp::Helper::Constraint.equals(true)
542
+ end
543
+ end
544
+ true
545
+ end
546
+ end
547
+
548
+ ParentEliminator = ::Sfp::Visitor::ParentEliminator.new
549
+
550
+ ParentGenerator = Object.new
551
+ def ParentGenerator.visit(name, value, parent)
552
+ value['_parent'] = parent if value.is_a?(Hash)
553
+ true
554
+ end
555
+
556
+ FinalAttributeRemover = Object.new
557
+ def FinalAttributeRemover.visit(name, value, parent)
558
+ if value.is_a?(Hash) and value.has_key?('_finals')
559
+ value['_finals'].each { |attr| value.delete(attr) }
560
+ end
561
+ true
562
+ end
563
+
564
+ SfpUnknownRemover = Object.new
565
+ def SfpUnknownRemover.visit(name, value, parent)
566
+ parent.delete(name) if value.is_a?(Sfp::Unknown)
567
+ true
568
+ end
569
+
570
+ SASPostProcessor = Object.new
571
+ # set a map of VM-name => VM-model
572
+ def SASPostProcessor.set_map(map)
573
+ @map = map
574
+ end
575
+ # post processing SAS after compilation
576
+ # goal: to add additional effects whenever a VM is deleted
577
+ def SASPostProcessor.sas_post_processor(parser)
578
+ return if parser.operators.nil?
579
+ parser.operators.each do |name, operator|
580
+ # skip if it's not "delete_vm"
581
+ next if !(name =~ /\.delete_vm$/) or !operator.params.has_key?('$.vm')
582
+ vm = operator.params['$.vm'].sub(/^\$\./, '')
583
+ next if !@map.has_key?(vm)
584
+
585
+ # for each not-exist state VM, add an effect
586
+ @map[vm].each { |k,v|
587
+ next if operator.has_key?(k) # skip if variable is exist (avoid overwrite)
588
+ next if k =~ /\.sfpAddress/ or k =~ /\.sfpPort/ # skip "sfpAddress" and "sfpPort"
589
+ # because these will be assigned dynamically
590
+ var = parser.variables[k]
591
+ next if var.nil? # the variable is not found
592
+
593
+ if v.is_a?(Hash)
594
+ val = parser.types[v['_value']][0] if v['_context'] == 'null'
595
+ raise Exception, "Not implemented yet." # this may arise on Set values
596
+ else
597
+ val = v
598
+ end
599
+
600
+ # add the value to variable's values
601
+ var << val
602
+ var.uniq!
603
+
604
+ # create new parameter, and then add to the operator
605
+ parameter = Sfp::Parameter.new(var, nil, val)
606
+ operator[var.name] = parameter
607
+ }
608
+ end
609
+ end
610
+
611
+ def self.start
612
+ # TODO
613
+ fork {
614
+ while true do
615
+ sleep 5000
616
+ end
617
+ }
618
+ end
619
+
620
+ def self.stop
621
+ # TODO
622
+ end
623
+ end
624
+
625
+ ### Helper classes/modules
626
+
627
+ class Nuri::Master::GoalGenerator
628
+ attr_reader :results
629
+
630
+ def initialize
631
+ @results = Sfp::Helper::Constraint.and('goal')
632
+ end
633
+
634
+ def visit(name, value, parent)
635
+ return false if name[0,1] == '_'
636
+
637
+ if value.is_a?(Hash)
638
+ return true if value['_context'] == 'object'
639
+
640
+ if parent.has_key?('_finals') and parent['_finals'].index(name).nil?
641
+ if value['_context'] == 'set'
642
+ @results[parent.ref.push(name)] = Sfp::Helper::Constraint.equals(value['_values'])
643
+ elsif value['_context'] == 'null'
644
+ # HACK! This should not be commented => null value should not be ignored.
645
+ #@results[parent.ref.push(name)] = Sfp::Helper::Constraint.equals(value)
646
+ end
647
+ end
648
+ return false
649
+ end
650
+ if parent.has_key?('_finals') and parent['_finals'].index(name).nil?
651
+ @results[ parent.ref.push(name) ] = Sfp::Helper::Constraint.equals(value)
652
+ end
653
+ false
654
+ end
655
+ end
656
+
657
+ class Sfp::Helper::SfpFlatten
658
+ attr_reader :results
659
+
660
+ def initialize
661
+ @results = {}
662
+ end
663
+
664
+ def visit(name, value, parent)
665
+ return false if name[0,1] == '_'
666
+ if value.is_a?(Hash)
667
+ return true if value['_context'] == 'object'
668
+
669
+ @results[parent.ref.push(name)] = value if value['_context'] == 'null'
670
+ @results[parent.ref.push(name)] = value if value['_context'] == 'set'
671
+
672
+ return false
673
+ end
674
+
675
+ @results[parent.ref.push(name)] = value
676
+ false
677
+ end
678
+ end
679
+
680
+ module Sfp::Helper
681
+ def self.sfp_to_s(v)
682
+ if v.is_a?(Hash)
683
+ return "null" if v['_context'] == 'null'
684
+ return v['_values'].inspect if v['_context'] == 'set'
685
+ return "<hash>"
686
+ elsif v.is_a?(String) and v =~ /^\$\./
687
+ return v
688
+ end
689
+ v.inspect
690
+ end
691
+ end
692
+
693
+ class Sfp::Helper::SchemaCollector
694
+ attr_reader :schemata
695
+ def initialize
696
+ @schemata = []
697
+ end
698
+
699
+ def visit(name, value, parent)
700
+ if value.is_a?(Hash) and value.has_key?('_classes')
701
+ value['_classes'].each { |s| @schemata << s }
702
+ end
703
+ true
704
+ end
705
+ end
706
+
707
+ class Sfp::Helper::CloudFinder
708
+ CloudSchema = '$.Cloud'
709
+ attr_accessor :clouds
710
+
711
+ def reset
712
+ @clouds = []
713
+ self
714
+ end
715
+
716
+ def visit(name, value, parent)
717
+ if value.is_a?(Hash)
718
+ if value['_context'] == 'object'
719
+ @clouds << parent.ref.push(name) if value.has_key?('_classes') and value['_classes'].index(CloudSchema)
720
+ return true
721
+ end
722
+ end
723
+ false
724
+ end
725
+ end