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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +12 -0
- data/CHANGELOG +146 -0
- data/Gemfile +3 -0
- data/LICENSE +28 -0
- data/README.md +64 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/bin/delete_modules +11 -0
- data/bin/install_agent +18 -0
- data/bin/install_module +65 -0
- data/bin/nuri +519 -0
- data/bin/nuri.old +183 -0
- data/bin/push_model +16 -0
- data/examples/.gitignore +3 -0
- data/examples/bonfire.sfp +95 -0
- data/examples/bonfire/epcc.sfp +43 -0
- data/examples/bonfire/epcc0.sfp +49 -0
- data/examples/bonfire/epcc2.sfp +52 -0
- data/examples/bonfire/epcc2a.sfp +25 -0
- data/examples/bonfire/inria.sfp +72 -0
- data/examples/bonfire/inria0.sfp +49 -0
- data/examples/bonfire/inria2.sfp +71 -0
- data/examples/bonfire/inria2a.sfp +44 -0
- data/examples/bonfire/inria2b.sfp +54 -0
- data/examples/bonfire/inria2c.sfp +62 -0
- data/examples/bonfire/inria2d.sfp +71 -0
- data/examples/bonfire/inria2e.sfp +80 -0
- data/examples/bonfire/main.sfp +33 -0
- data/examples/bonfire/old/bonfire-1-1-1.sfp +76 -0
- data/examples/bonfire/old/bonfire-1-10-1.sfp +77 -0
- data/examples/bonfire/old/bonfire-1-2-1.sfp +58 -0
- data/examples/bonfire/old/bonfire-1-3-1.sfp +61 -0
- data/examples/bonfire/old/bonfire-1-4-1.sfp +64 -0
- data/examples/bonfire/old/bonfire-1-5-1.sfp +67 -0
- data/examples/bonfire/old/bonfire-1-6-1.sfp +82 -0
- data/examples/bonfire/old/bonfire-1-7-1.sfp +82 -0
- data/examples/bonfire/old/bonfire-1-8-1.sfp +79 -0
- data/examples/bonfire/old/bonfire-1-9-1.sfp +83 -0
- data/examples/bonfire/old/wp-test1a.sfp +38 -0
- data/examples/bonfire/old/wp-test1b.sfp +18 -0
- data/examples/bonfire/old/wp-test1c.sfp +7 -0
- data/examples/bonfire/old/wp-test2.sfp +47 -0
- data/examples/bonfire/old3/bonfire-epcc.sfp +57 -0
- data/examples/bonfire/old3/bonfire-inria.sfp +72 -0
- data/examples/bonfire/old3/bonfire-master.sfp +18 -0
- data/examples/bonfire/old3/bonfire.sfp +23 -0
- data/examples/bonfire/old3/bonfire2.sfp +49 -0
- data/examples/bonfire/old3/bonfire3.sfp +76 -0
- data/examples/bonfire/old3/bonfire4.sfp +78 -0
- data/examples/bonfire/old3/bonfire5.sfp +34 -0
- data/examples/bonfire/old3/bonfire5b.sfp +84 -0
- data/examples/bonfire/old3/hpvm6.sfp +22 -0
- data/examples/bonfire/old3/model.json +1 -0
- data/examples/bonfire/old3/test0.sfp +16 -0
- data/examples/bonfire/old3/test1.sfp +5 -0
- data/examples/bonfire/old3/test10.sfp +5 -0
- data/examples/bonfire/old3/test2.sfp +18 -0
- data/examples/bonfire/old3/test3.sfp +10 -0
- data/examples/bonfire/old3/test4.sfp +11 -0
- data/examples/bonfire/old3/test5.sfp +18 -0
- data/examples/bonfire/old3/test6.sfp +19 -0
- data/examples/bonfire/old3/test7.sfp +34 -0
- data/examples/bonfire/old3/test8.sfp +5 -0
- data/examples/bonfire/old3/test9.sfp +16 -0
- data/examples/bonfire/old3/wordpress-test-cluster.sfp +38 -0
- data/examples/bonfire/old3/wordpress-test.sfp +22 -0
- data/examples/bonfire/old3/wp-test-2.sfp +49 -0
- data/examples/bonfire/test.sfp +13 -0
- data/examples/generator.rb +66 -0
- data/examples/hadoop2.sfp +20 -0
- data/examples/hpcloud.sfp +18 -0
- data/examples/run.rb +17 -0
- data/examples/test.inc +0 -0
- data/examples/test.sfp +11 -0
- data/lib/naas/d3.js +5 -0
- data/lib/naas/d3.v3.min.js +5 -0
- data/lib/naas/index.css +0 -0
- data/lib/naas/index.html +18 -0
- data/lib/naas/index.js +18 -0
- data/lib/naas/jquery-1.10.2.min.js +6 -0
- data/lib/naas/jquery.js +6 -0
- data/lib/naas/naas.rb +160 -0
- data/lib/nuri.rb +62 -0
- data/lib/nuri/choreographer.rb +151 -0
- data/lib/nuri/constraint_helper.rb +9 -0
- data/lib/nuri/directory.rb +40 -0
- data/lib/nuri/master.rb +725 -0
- data/lib/nuri/net_helper.rb +65 -0
- data/lib/nuri/orchestrator.rb +224 -0
- data/lib/nuri/server.rb +212 -0
- data/modules/.gitignore +4 -0
- data/modules/apache/apache.rb +255 -0
- data/modules/apache/apache.rb.old +167 -0
- data/modules/apache/apache.sfp +146 -0
- data/modules/apache/apache.sfp.future +100 -0
- data/modules/apache/load_balancer +20 -0
- data/modules/apache/model.json +1 -0
- data/modules/apache/test.sfp +8 -0
- data/modules/aptpackage/aptpackage.rb +82 -0
- data/modules/aptpackage/aptpackage.sfp +5 -0
- data/modules/bonfire/.gitignore +2 -0
- data/modules/bonfire/README.md +12 -0
- data/modules/bonfire/bonfire.rb +60 -0
- data/modules/bonfire/bonfire.sfp +9 -0
- data/modules/bonfire/config.yml +4 -0
- data/modules/bonfire/helper.rb +149 -0
- data/modules/bonfire/stresstest.rb +144 -0
- data/modules/bonfire/test.sfp +8 -0
- data/modules/client/client.rb +22 -0
- data/modules/client/client.sfp +14 -0
- data/modules/cloud/cloud.rb +11 -0
- data/modules/cloud/cloud.sfp +26 -0
- data/modules/file/file.rb +91 -0
- data/modules/file/file.sfp +9 -0
- data/modules/hadoop1/core-site.xml +17 -0
- data/modules/hadoop1/hadoop-env.sh +55 -0
- data/modules/hadoop1/hadoop1.rb +384 -0
- data/modules/hadoop1/hadoop1.sfp +93 -0
- data/modules/hadoop1/hdfs-site.xml +16 -0
- data/modules/hadoop1/mapred-site.xml +17 -0
- data/modules/hadoop2/core-site.xml +31 -0
- data/modules/hadoop2/hadoop-env.sh +77 -0
- data/modules/hadoop2/hadoop2.rb +401 -0
- data/modules/hadoop2/hadoop2.sfp +114 -0
- data/modules/hadoop2/hdfs-site.xml +47 -0
- data/modules/hadoop2/mapred-site.xml +71 -0
- data/modules/hadoop2/ports +14 -0
- data/modules/hadoop2/yarn-env.sh +112 -0
- data/modules/hadoop2/yarn-site.xml +107 -0
- data/modules/hpcloud/.gitignore +2 -0
- data/modules/hpcloud/README.md +16 -0
- data/modules/hpcloud/config.yml +3 -0
- data/modules/hpcloud/example.sfp +18 -0
- data/modules/hpcloud/hpcloud.rb +241 -0
- data/modules/hpcloud/hpcloud.sfp +22 -0
- data/modules/hpcloud/test.sfp +5 -0
- data/modules/install_module +65 -0
- data/modules/machine/machine.rb +95 -0
- data/modules/machine/machine.sfp +9 -0
- data/modules/mockcloud/mockcloud.rb +20 -0
- data/modules/mockcloud/mockcloud.sfp +6 -0
- data/modules/mysql/mysql.rb +118 -0
- data/modules/mysql/mysql.sfp +38 -0
- data/modules/mysql/test.sfp +3 -0
- data/modules/node/node.rb +8 -0
- data/modules/node/node.sfp +7 -0
- data/modules/object/object.rb +7 -0
- data/modules/object/object.sfp +1 -0
- data/modules/os/os.rb +38 -0
- data/modules/os/os.sfp +11 -0
- data/modules/package/package.rb +26 -0
- data/modules/package/package.sfp +22 -0
- data/modules/package/test.sfp +6 -0
- data/modules/service/model.json +1 -0
- data/modules/service/service.rb +50 -0
- data/modules/service/service.sfp +46 -0
- data/modules/service/test.sfp +6 -0
- data/modules/tarpackage/tarpackage.rb +93 -0
- data/modules/tarpackage/tarpackage.sfp +5 -0
- data/modules/vm/vm.rb +8 -0
- data/modules/vm/vm.sfp +18 -0
- data/modules/wordpress/wordpress.rb +98 -0
- data/modules/wordpress/wordpress.sfp +34 -0
- data/modules/wordpresscluster/wordpresscluster.rb +150 -0
- data/modules/wordpresscluster/wordpresscluster.sfp +74 -0
- data/nuri.gemspec +26 -0
- metadata +281 -0
|
@@ -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
|
data/lib/nuri/master.rb
ADDED
|
@@ -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
|