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,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