Name | Total Lines | Lines of Code | Total Coverage | Code Coverage |
---|---|---|---|---|
lib/model/instance.rb | 533 | 435 | 82.93%
|
80.00%
|
Code reported as executed by Ruby looks like this...and this: this line is also marked as covered.Lines considered as run by rcov, but not reported by Ruby, look like this,and this: these lines were inferred by rcov (using simple heuristics).Finally, here's a line marked as not executed.
1 # |
2 # Copyright [2011] [Red Hat, Inc.] |
3 # |
4 # Licensed under the Apache License, Version 2.0 (the "License"); |
5 # you may not use this file except in compliance with the License. |
6 # You may obtain a copy of the License at |
7 # |
8 # http://www.apache.org/licenses/LICENSE-2.0 |
9 # |
10 # Unless required by applicable law or agreed to in writing, software |
11 # distributed under the License is distributed on an "AS IS" BASIS, |
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 # See the License for the specific language governing permissions and |
14 # limitations under the License. |
15 # |
16 require 'fileutils' |
17 require 'tmpdir' |
18 require 'nokogiri' |
19 require 'open-uri' |
20 |
21 require 'net/http' |
22 require 'net/https' |
23 |
24 require 'active_support/ordered_hash' |
25 |
26 require 'zlib' |
27 require 'archive/tar/minitar' |
28 include Archive::Tar |
29 |
30 require 'lib/model/base' |
31 |
32 class Dir |
33 class << self |
34 @@dirstack = [] |
35 def pushd(dir, &block) |
36 @@dirstack.unshift(Dir.pwd) |
37 Dir.chdir(dir) |
38 if block_given? |
39 yield |
40 popd |
41 end |
42 return @@dirstack |
43 end |
44 def popd |
45 Dir.chdir(@@dirstack.shift) unless @@dirstack.empty? |
46 return @@dirstack |
47 end |
48 end |
49 end |
50 |
51 module ConfigServer |
52 module Model |
53 class InvalidInstanceConfigError < StandardError |
54 attr_reader :errors |
55 attr_reader :cause |
56 attr_reader :message |
57 |
58 def initialize(errors=nil, cause=nil) |
59 @errors = (errors.nil?) ? [] : errors |
60 if not @errors.is_a? Array |
61 @errors = [@errors] |
62 end |
63 @message = errors |
64 @cause = cause if not cause.nil? |
65 end |
66 |
67 def to_s |
68 s = super() |
69 @errors.each do |error| |
70 s = "#{s}\n Error: #{error}" |
71 end |
72 s |
73 end |
74 end |
75 |
76 def self.instance_config_schema_location=(location) |
77 @intance_config_schema_location = location |
78 end |
79 |
80 def self.instance_config_schema_location |
81 @intance_config_schema_location |
82 end |
83 |
84 class InvalidValidatorError < StandardError |
85 end |
86 |
87 class Instance < Base |
88 @@INSTANCE_CONFIG_FILE = "instance-config.xml" |
89 @@PROVIDED_PARAMS_FILE = "provided-parameters.xml" |
90 @@REQUIRED_PARAMS_FILE = "required-parameters.xml" |
91 @@IP_FILE = "ip" |
92 @@EMPTY_DOCUMENT = Nokogiri::XML("") |
93 |
94 # Nokogiri XML validator |
95 @@validator = nil |
96 |
97 attr_reader :instance_config, :ip, :secret, :uuid |
98 |
99 def self.exists?(uuid) |
100 File.exist?(storage_path uuid) |
101 end |
102 |
103 def self.storage_path(uuid=nil) |
104 path = uuid ? File.join("instances", uuid) : "instances" |
105 super path |
106 end |
107 |
108 def self.find(uuid) |
109 Instance.new(uuid) if exists?(uuid) |
110 end |
111 |
112 def self.delete!(uuid) |
113 FileUtils.rm_rf(storage_path uuid) |
114 end |
115 |
116 def self.get_validator |
117 validator_schema = ConfigServer::Model.instance_config_schema_location |
118 begin |
119 @@validator ||= open(validator_schema) do |v| |
120 Nokogiri::XML::RelaxNG(v) |
121 end |
122 rescue SocketError => se |
123 raise InvalidValidatorError, |
124 "Could not load validator from address #{validator_schema}" |
125 rescue SystemCallError => sce |
126 raise InvalidValidatorError, |
127 "Could not load validator from file #{validator_schema}" |
128 end |
129 @@validator |
130 end |
131 |
132 def self.validate(uuid, xml) |
133 # make sure the xml is wrapped in Nokogiri |
134 if xml.instance_of?(String) or xml.kind_of?(IO) |
135 xml = Nokogiri::XML(xml) |
136 end |
137 errors = [] |
138 begin |
139 errors = get_validator.validate(xml) |
140 rescue Exception => e |
141 raise InvalidInstanceConfigError.new(), ["The provided instance " + |
142 "config for #{uuid} caused an error during validation: ", e] |
143 end |
144 if errors.size > 0 |
145 raise InvalidInstanceConfigError.new(errors), |
146 "The provided instance config for #{uuid} is not a valid " + |
147 "instance-config document." |
148 end |
149 xml |
150 end |
151 |
152 @uuid = "" |
153 @secret = nil |
154 @ip = "" |
155 @instance_dir = "" |
156 |
157 def initialize(uuid, configs=nil) |
158 super() |
159 Instance.ensure_storage_path |
160 |
161 @uuid = uuid |
162 @instance_dir = Instance.storage_path uuid |
163 ensure_instance_dir |
164 |
165 if configs.nil? |
166 load_configs |
167 else |
168 replace_instance_config(configs) |
169 end |
170 self |
171 end |
172 |
173 def deployable |
174 @deployable ||= Deployable.new(deployable_id) |
175 end |
176 |
177 def assembly_name |
178 @assembly_name ||= |
179 (config % 'instance-config')['name'] if not config.nil? |
180 end |
181 |
182 def instance_config=(xml) |
183 replace_instance_config(xml) |
184 end |
185 |
186 def ip=(ip) |
187 replace_ip(ip) |
188 end |
189 |
190 def provided_parameters_values=(params={}) |
191 logger.debug("provided params: #{params.inspect}") |
192 if not (params.nil? or params.empty?) |
193 params.each do |k,v| |
194 param = pp % "//provided-parameter[@name='#{k}']" |
195 param.inner_html = "<value><![CDATA[#{v}]]></value>" if not param.nil? |
196 end |
197 File.open(get_path(@@PROVIDED_PARAMS_FILE), 'w') do |f| |
198 @provided_parameters.write_xml_to(f) |
199 end |
200 end |
201 end |
202 |
203 def provided_parameters(opts={}) |
204 opts[:only_empty] ||= (not (opts[:only_with_values] || opts[:all])) |
205 if opts[:raw] |
206 return provided_parametrs_raw |
207 end |
208 |
209 xpath = case |
210 when opts[:only_empty] |
211 '//provided-parameter[not(value)]' |
212 when opts[:only_with_values] |
213 '//provided-parameter/value/..' |
214 else # opts[:all] |
215 '//provided-parameter' |
216 end |
217 |
218 if opts[:include_values] |
219 #FIXME: only handles scalar values |
220 (pp / xpath).map do |p| |
221 {p['name'] => ((p%'value').nil? ? nil : (p%'value').content)} |
222 end.inject(:merge) |
223 else |
224 (pp / xpath).map do |p| |
225 p['name'] |
226 end |
227 end |
228 end |
229 |
230 def required_parameters_values=(params={}) |
231 logger.debug("required params: #{params.inspect}") |
232 params.each do |k,v| |
233 param = rp % "//required-parameter[@name='#{k}']" |
234 logger.debug("param: #{param.to_xml}") |
235 logger.debug("value: #{v}") |
236 param.inner_html = "<value><![CDATA[#{v}]]></value>" if not param.nil? |
237 end if not (params.nil? or params.empty?) |
238 File.open(get_path(@@REQUIRED_PARAMS_FILE), 'w') do |f| |
239 rp.write_xml_to(f) |
240 end |
241 end |
242 |
243 def required_parameters(opts={}) |
244 if opts[:raw] |
245 return required_parameters_raw |
246 end |
247 |
248 xpath = case |
249 when opts[:only_empty] |
250 '//required-parameter[not(value)]' |
251 when opts[:only_with_values] |
252 '//required-parameter/value/..' |
253 else # opts[:all] |
254 '//required-parameter' |
255 end |
256 |
257 params = (rp / xpath).map do |p| |
258 {:name => p['name'], |
259 :assembly => p['assembly'], |
260 :parameter => p['parameter']} + |
261 if opts[:include_values] |
262 #FIXME: only handles scalar values |
263 v = p % 'value' |
264 {:value => (v.content if not v.nil?)} |
265 else |
266 {} |
267 end |
268 end |
269 end |
270 |
271 def services |
272 services = ActiveSupport::OrderedHash.new |
273 (config / '//service').each do |s| |
274 name = s["name"] |
275 params_with_values = (s / './parameters/parameter/value/..') + |
276 (rp / "//required-parameter[@service='#{name}']/value/..") |
277 parameters = (params_with_values.map do |p| |
278 {p["name"] => (p % 'value').content} |
279 end || []).inject(:merge) || {} |
280 services[name] = parameters |
281 end |
282 services |
283 end |
284 |
285 def required_parameters_remaining? |
286 (rp / "//required-parameter[not(value)]").size > 0 |
287 end |
288 |
289 def has_file? |
290 File.exists?(get_file) |
291 end |
292 |
293 def file |
294 path = get_path("#{@uuid}.tgz") |
295 return (File.exists?(path)) ? path : nil |
296 end |
297 |
298 def file=(file) |
299 if file.nil? |
300 return nil |
301 else |
302 path = get_path("#{@uuid}.tgz") |
303 File.open(path, "wb") do |f| |
304 f.write(file[:tempfile].read) |
305 end |
306 return path |
307 end |
308 end |
309 |
310 private |
311 alias config instance_config |
312 |
313 def required_parameters_raw |
314 @required_parameters || @@EMPTY_DOCUMENT |
315 end |
316 alias rp required_parameters_raw |
317 |
318 def provided_parameters_raw |
319 @provided_parameters || @@EMPTY_DOCUMENT |
320 end |
321 alias pp provided_parameters_raw |
322 |
323 def ensure_instance_dir |
324 FileUtils.mkdir_p(@instance_dir, :mode => 0700) if not File.directory?(@instance_dir) |
325 end |
326 |
327 def load_configs |
328 @instance_config = get_xml(get_path(@@INSTANCE_CONFIG_FILE)) |
329 @provided_parameters = get_xml(get_path(@@PROVIDED_PARAMS_FILE)) |
330 @required_parameters = get_xml(get_path(@@REQUIRED_PARAMS_FILE)) |
331 @ip = get_ip |
332 deployable |
333 end |
334 |
335 def get_path(filename) |
336 File.join(@instance_dir, filename) |
337 end |
338 |
339 def get_xml(filename) |
340 File.open(filename) do |f| |
341 Nokogiri::XML(f) |
342 end if File.exists?(filename) |
343 end |
344 |
345 def deployable_id |
346 (config % '//deployable')['id'] if not config.nil? |
347 end |
348 |
349 def replace_instance_config(xml) |
350 xml = Instance.validate(@uuid, xml) |
351 file = get_path(@@INSTANCE_CONFIG_FILE) |
352 File.open(file, "w") do |f| |
353 xml.write_xml_to(f) |
354 end |
355 @instance_config = xml |
356 |
357 replace_provided_parameters |
358 replace_required_parameters |
359 |
360 replace_tarball |
361 |
362 @secret = get_secret |
363 |
364 deployable |
365 @deployable.add_instance(@uuid) |
366 |
367 @instance_config |
368 end |
369 |
370 def get_ip |
371 filename = get_path(@@IP_FILE) |
372 File.open(filename) do |f| |
373 f.read |
374 end if File.exists?(filename) |
375 end |
376 |
377 def get_secret |
378 config.root['secret'] |
379 end |
380 |
381 def replace_ip(ip) |
382 file = get_path(@@IP_FILE) |
383 File.open(file, 'w') {|f| f.write(ip) } |
384 end |
385 |
386 def replace_provided_parameters |
387 provided_params = config / '//provided-parameter' |
388 xml = "" |
389 if not provided_params.empty? |
390 xml = "<provided-parameters>\n" |
391 provided_params.each do |p| |
392 xml += " <provided-parameter name='#{p['name']}'/>\n" |
393 end |
394 xml += "</provided-parameters>\n" |
395 end |
396 # Always create the provided_params file, even |
397 # if it's empty...saves a little trouble later |
398 # on with some validation |
399 file = get_path(@@PROVIDED_PARAMS_FILE) |
400 File.open(file, 'w') do |f| |
401 f.write(xml) |
402 end |
403 @provided_parameters = Nokogiri::XML(xml) |
404 end |
405 |
406 def replace_required_parameters |
407 file = get_path(@@REQUIRED_PARAMS_FILE) |
408 # grab all the services with reference parameters |
409 services = config / '//service/parameters/parameter/reference/../../..' |
410 if not services.empty? |
411 xml = "<required-parameters>\n" |
412 services.each do |s| |
413 xml += " <required-parameter service='#{s['name']}'" |
414 (services / './parameters/parameter/reference/..').each do |p| |
415 xml += " name='#{p['name']}'" |
416 ref = p % 'reference' |
417 xml += " assembly='#{ref['assembly']}'" |
418 xml += " parameter='#{ref['provided-parameter']}'/>\n" |
419 end |
420 end |
421 xml += "</required-parameters>\n" |
422 logger.debug("reqparams xml: #{xml}") |
423 |
424 File.open(file, 'w') do |f| |
425 f.write(xml) |
426 end |
427 @required_parameters = Nokogiri::XML(xml) |
428 else |
429 File.delete(file) if File.exists?(file) |
430 @required_parameters = nil |
431 end |
432 end |
433 |
434 def replace_tarball |
435 # mk a tmpdir |
436 Dir.mktmpdir do |dir| |
437 # grab all the executable files and conf files |
438 get_configuration_scripts(dir, :type => :executable) |
439 get_configuration_scripts(dir, :type => :file) |
440 |
441 # tar the contents of the tmpdir |
442 Dir.pushd(dir) do |
443 tar = File.open(get_path("#{@uuid}.tgz"), 'wb') do |f| |
444 tgz = Zlib::GzipWriter.new(f) |
445 Minitar.pack('.', tgz) |
446 end |
447 end |
448 # auto-unlinks the tmpdir |
449 end |
450 end |
451 |
452 def get_configuration_scripts(dir, opts={}) |
453 opts[:type] ||= :executable |
454 config_type = opts[:type] |
455 if not [:executable, :file].include? config_type |
456 raise RuntimeError, "unknown configuration file type #{config_type}" |
457 end |
458 (config / config_type.to_s).each do |node| |
459 config_dir = dir |
460 parent = (:file == config_type) ? node.parent.parent : node.parent |
461 if "service" == parent.name |
462 svc_name = parent['name'] |
463 config_dir = "#{dir}/#{svc_name}" |
464 Dir.mkdir config_dir if not File.exists? config_dir |
465 end |
466 # if file is URL, download file |
467 # else read file from cdata contents |
468 begin |
469 file_data = get_configuration_file(node) |
470 write_configuration_file(file_data, config_dir, opts[:type]) |
471 rescue => e |
472 puts "ERROR: could not get configuration file contents" |
473 puts e |
474 end |
475 |
476 end |
477 end |
478 |
479 def get_configuration_file(node) |
480 file_data = {} |
481 if not node['url'] |
482 file_content_node = node.first_element_child |
483 if file_content_node.nil? |
484 raise "No 'contents' element found for configuration file without url: #{node.name}" |
485 end |
486 file_data[:name] = file_content_node[:filename] |
487 file_data[:body] = file_content_node.content |
488 else |
489 file_data = download_file(node['url']) |
490 if file_data[:code] != "200" |
491 raise "Could not download file #{node['url']}. Http Response code: #{file_data[:code]}" |
492 end |
493 end |
494 file_data |
495 end |
496 |
497 def write_configuration_file(file_data, config_dir, file_type=:executable) |
498 filename = (:file == file_type) ? file_data[:name] : "start" |
499 if file_type == :executable |
500 open("#{config_dir}/#{filename}", "wb", 0777) do |file| |
501 file << file_data[:body] |
502 end |
503 else |
504 open("#{config_dir}/#{filename}", "wb") do |file| |
505 file << file_data[:body] |
506 end |
507 end |
508 end |
509 |
510 def download_file(url) |
511 result = {} |
512 uri = URI.parse(url) |
513 http = Net::HTTP.new(uri.host, uri.port) |
514 http.use_ssl = uri.port == 443 |
515 request = Net::HTTP::Get.new(uri.path) |
516 response = http.start {|h| h.request(request) } |
517 result[:code] = response.code |
518 if "200" == response.code.to_s |
519 result[:name] = uri.path.split('/').last |
520 result[:body] = response.body |
521 end |
522 result |
523 end |
524 |
525 def pending_required_params?(service=nil) |
526 xpath = (service.nil?) ? |
527 '//required-parameter[not(value)]' : |
528 "//required-parameter[@service='#{service}'][not(value)]" |
529 not (rp.nil? or (rp / xpath).empty?) |
530 end |
531 end |
532 end |
533 end |
Generated on Wed Dec 14 16:00:31 -0500 2011 with rcov 0.9.11