cloud-toaster 1.1.5 → 1.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +8 -8
  2. data/Gemfile +5 -45
  3. data/README.md +2 -2
  4. data/VERSION +1 -1
  5. data/chef/cookbooks/lxc/recipes/setup_database.rb +12 -4
  6. data/chef/cookbooks/toaster/recipes/testing.rb +2 -2
  7. data/cloud-toaster.gemspec +94 -0
  8. data/config.json +3 -1
  9. data/lib/toaster/api.rb +25 -22
  10. data/lib/toaster/chef/chef_listener.rb +3 -3
  11. data/lib/toaster/chef/chef_node_inspector.rb +6 -4
  12. data/lib/toaster/chef/chef_util.rb +26 -8
  13. data/lib/toaster/chef/failsafe_resource_parser.rb +1 -0
  14. data/lib/toaster/chef/resource_inspector.rb +1 -1
  15. data/lib/toaster/model/automation.rb +26 -7
  16. data/lib/toaster/model/automation_run.rb +2 -11
  17. data/lib/toaster/model/key_value_pair.rb +13 -1
  18. data/lib/toaster/model/task.rb +3 -3
  19. data/lib/toaster/model/task_execution.rb +3 -2
  20. data/lib/toaster/model/task_parameter.rb +17 -24
  21. data/lib/toaster/state/convergence.rb +2 -2
  22. data/lib/toaster/state/idempotence.rb +13 -7
  23. data/lib/toaster/state/system_state.rb +1 -0
  24. data/lib/toaster/test/test_case.rb +10 -5
  25. data/lib/toaster/test/test_runner.rb +131 -124
  26. data/lib/toaster/test/test_suite.rb +1 -1
  27. data/lib/toaster/test_manager.rb +3 -9
  28. data/lib/toaster/toaster_app_service.rb +0 -4
  29. data/lib/toaster/util/config.rb +6 -1
  30. data/lib/toaster/util/lxc.rb +1 -1
  31. data/webapp/app/assets/javascripts/jstree.util.js +2 -1
  32. data/webapp/app/assets/stylesheets/application.css +21 -1
  33. data/webapp/app/assets/stylesheets/layout.css +1 -1
  34. data/webapp/app/controllers/analysis_controller.rb +12 -0
  35. data/webapp/app/controllers/application_controller.rb +11 -3
  36. data/webapp/app/controllers/base_controller.rb +1 -0
  37. data/webapp/app/controllers/execs_controller.rb +12 -4
  38. data/webapp/app/controllers/scripts_controller.rb +91 -52
  39. data/webapp/app/controllers/test_controller.rb +4 -4
  40. data/webapp/app/views/analysis/convergence.html.erb +14 -11
  41. data/webapp/app/views/analysis/idempotence.html.erb +40 -1
  42. data/webapp/app/views/analysis/index.html.erb +117 -0
  43. data/webapp/app/views/execs/automation_runs.html.erb +10 -4
  44. data/webapp/app/views/execs/task_executions.html.erb +20 -16
  45. data/webapp/app/views/layouts/application.html.erb +22 -9
  46. data/webapp/app/views/scripts/graph.html.erb +1 -1
  47. data/webapp/app/views/scripts/import_chef.html.erb +4 -0
  48. data/webapp/app/views/scripts/scripts.html.erb +5 -17
  49. data/webapp/app/views/settings/{index.html.erb → config.html.erb} +4 -6
  50. data/webapp/app/views/settings/containers.html.erb +7 -9
  51. data/webapp/app/views/test/gen.html.erb +1 -0
  52. data/webapp/app/views/test/suites.html.erb +1 -1
  53. data/webapp/app/views/util/chef.html.erb +2 -2
  54. data/webapp/config/initializers/devise.rb +4 -1
  55. data/webapp/config/routes.rb +15 -8
  56. data/webapp/log/development.log +39521 -0
  57. data/webapp/tmp/cache/assets/development/sprockets/0dc0562647e703ca0535da3b9fcad796 +0 -0
  58. data/webapp/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
  59. data/webapp/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
  60. data/webapp/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
  61. data/webapp/tmp/cache/assets/development/sprockets/72cbfd5bf33945bcce154f7a4feaf04d +0 -0
  62. data/webapp/tmp/cache/assets/development/sprockets/7b2b7d9034fc7b77daf5da1436667e6f +0 -0
  63. data/webapp/tmp/cache/assets/development/sprockets/b6f1534bcdbff92a16c85487f363235a +0 -0
  64. data/webapp/tmp/cache/assets/development/sprockets/bfac6cd2984fbd5e0d762389e3c37164 +0 -0
  65. data/webapp/tmp/cache/assets/development/sprockets/c5907cfd07b24ad19b8c80e8af618a57 +0 -0
  66. data/webapp/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
  67. data/webapp/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
  68. data/webapp/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
  69. metadata +53 -51
@@ -70,6 +70,7 @@ class ::Chef
70
70
  super
71
71
  rescue Object => ex
72
72
  puts "WARN: cannot run instance_eval on recipe: #{ex} - #{ex.backtrace.join("\n")}"
73
+ puts "WARN: eval string was: #{string}"
73
74
  end
74
75
  end
75
76
  def method_missing(method_symbol, *args, &block)
@@ -45,7 +45,7 @@ module Toaster
45
45
  param = param[0].strip
46
46
  param = MarkupUtil.convert_array_to_dot_notation(param)
47
47
  if task_or_sourcecode.kind_of?(Task)
48
- param = TaskParameter.new(task_or_sourcecode, param)
48
+ param = TaskParameter.new(:task => task_or_sourcecode, :key => param)
49
49
  else
50
50
  param = TaskParameter.new(:key => param)
51
51
  end
@@ -18,11 +18,12 @@ module Toaster
18
18
  class Automation < ActiveRecord::Base
19
19
 
20
20
  belongs_to :user
21
- has_many :tasks, nil, {:autosave => true, :dependent => :delete_all}
22
- has_many :automation_attributes, nil, {:autosave => true, :dependent => :delete_all}
23
- has_many :ignore_properties, nil, {:class_name => IgnoreProperty, :autosave => true, :dependent => :delete_all}
24
- has_many :additional_properties, nil, {:class_name => AdditionalProperty, :autosave => true, :dependent => :delete_all}
25
- has_many :automation_runs, nil, {:autosave => true, :dependent => :delete_all}
21
+ has_many :tasks, :autosave => true, :dependent => :destroy
22
+ has_many :test_suites, :autosave => true, :dependent => :destroy
23
+ has_many :automation_attributes, :autosave => true, :dependent => :destroy
24
+ has_many :ignore_properties, :autosave => true, :dependent => :destroy
25
+ has_many :additional_properties, :autosave => true, :dependent => :destroy
26
+ has_many :automation_runs, :autosave => true, :dependent => :destroy
26
27
 
27
28
  #attr_accessor :tasks, :attributes,
28
29
  attr_accessor :chef_run_list, :additional_state_configs, :version
@@ -43,6 +44,19 @@ module Toaster
43
44
  return tasks
44
45
  end
45
46
 
47
+ # collect the executed state transitions of
48
+ # all tasks contained in this automation
49
+ def global_state_transitions()
50
+ result = Set.new
51
+ get_globally_executed_tasks().each do |task|
52
+ result += task.global_state_transitions
53
+ end
54
+ return result
55
+ end
56
+ def num_global_state_transitions()
57
+ global_state_transitions().size
58
+ end
59
+
46
60
  #
47
61
  # return a map AutomationRun -> (list of TaskExecution)
48
62
  #
@@ -56,6 +70,11 @@ module Toaster
56
70
  return result
57
71
  end
58
72
 
73
+ def get_all_test_cases()
74
+ TestCase.joins(:automation_run => :automation).where(
75
+ "automations.id = #{self.id}")
76
+ end
77
+
59
78
  def is_chef?
60
79
  "#{language}".casecmp("chef") == 0
61
80
  end
@@ -137,12 +156,12 @@ module Toaster
137
156
  def get_task(task_id, check_automation_runs=false)
138
157
  task_id = task_id.to_s
139
158
  tasks.each do |t|
140
- return t if t.id.to_s == task_id || t.uuid.to_s == task_id
159
+ return t if (t.id.to_s == task_id || t.uuid.to_s == task_id)
141
160
  end
142
161
  if check_automation_runs
143
162
  automation_runs().each do |r|
144
163
  r.task_executions().each do |exe|
145
- if exe.task && exe.task.uuid.to_s == task_id || exe.task.uuid.to_s == task_id
164
+ if exe.task && (exe.task.id.to_s == task_id || exe.task.uuid == task_id)
146
165
  return exe.task
147
166
  end
148
167
  end
@@ -18,8 +18,8 @@ module Toaster
18
18
 
19
19
  belongs_to :user
20
20
  belongs_to :automation
21
- has_many :task_executions, nil, {:autosave => true, :dependent => :delete_all}
22
- has_many :run_attributes, nil, {:autosave => true, :dependent => :delete_all}
21
+ has_many :task_executions, :autosave => true, :dependent => :destroy
22
+ has_many :run_attributes, :autosave => true, :dependent => :destroy
23
23
 
24
24
  # FIELDS:
25
25
  # :uuid, :machine_id, :automation, :start_time,
@@ -86,15 +86,6 @@ module Toaster
86
86
  return end_time - start_time
87
87
  end
88
88
 
89
- def self.load(id, automation = nil)
90
- id = DB.instance.wrap_db_id(id)
91
- preset_fields = {}
92
- preset_fields["automation"] = automation if automation
93
- runs = find({"_id" => id}, preset_fields)
94
- return nil if !runs || runs.empty?
95
- return runs[0]
96
- end
97
-
98
89
  def self.find(criteria={})
99
90
  DB.find_activerecord(AutomationRun, criteria)
100
91
  end
@@ -37,7 +37,19 @@ module Toaster
37
37
  }
38
38
  return result
39
39
  end
40
-
40
+
41
+ def self.flat_attributes_from_hash(hash, clazz=KeyValuePair)
42
+ result = []
43
+ return result if !hash
44
+ SystemState.get_flat_attributes(hash).each do |key,value|
45
+ result << clazz.new(
46
+ :key => key,
47
+ :value => value
48
+ )
49
+ end
50
+ return result
51
+ end
52
+
41
53
  def to_s
42
54
  return "#{self.class}(#{key}=#{value})"
43
55
  end
@@ -14,8 +14,8 @@ require "toaster/chef/resource_inspector"
14
14
  module Toaster
15
15
  class Task < ActiveRecord::Base
16
16
 
17
- has_many :task_parameters
18
- has_many :task_executions
17
+ has_many :task_parameters, :autosave => true, :dependent => :destroy
18
+ has_many :task_executions, :autosave => true, :dependent => :destroy
19
19
  belongs_to :automation
20
20
 
21
21
  attr_accessor :resource_obj
@@ -271,12 +271,12 @@ module Toaster
271
271
  params = {
272
272
  :resource => resource.to_s,
273
273
  :action => action,
274
- :sourcecode => sourcecode,
275
274
  :sourcefile => sourcefile,
276
275
  :sourceline => sourceline
277
276
  }
278
277
  task = find_by(params)
279
278
  if !task
279
+ params[:sourcecode] = sourcecode
280
280
  task = Task.new(params)
281
281
  end
282
282
  task.resource_obj = resource
@@ -15,7 +15,7 @@ module Toaster
15
15
 
16
16
  belongs_to :task
17
17
  belongs_to :automation_run
18
- has_many :state_changes
18
+ has_many :state_changes, :autosave => true, :dependent => :destroy
19
19
  serialize :state_before, JSON
20
20
  serialize :state_after, JSON
21
21
 
@@ -68,7 +68,8 @@ module Toaster
68
68
  end
69
69
 
70
70
  def self.load_all_for_automation(auto)
71
- return joins(:automation_run).where(:automation_run => {:automation_id => auto.id})
71
+ return joins(:automation_run).where(
72
+ :automation_runs => {:automation_id => auto.id})
72
73
  end
73
74
 
74
75
  end
@@ -9,14 +9,7 @@ require "toaster/model/key_value_pair"
9
9
  module Toaster
10
10
  class TaskParameter < KeyValuePair
11
11
 
12
- @@id_attributes = ["task_id", "key", "value", "type"]
13
-
14
- def initialize(attr_hash)
15
- if !attr_hash[:uuid]
16
- attr_hash[:uuid] = Util.generate_short_uid()
17
- end
18
- super(attr_hash)
19
- end
12
+ belongs_to :task
20
13
 
21
14
  def self.find(criteria={}, preset_fields={})
22
15
  criteria["db_type"] = "task_parameter" if !criteria["db_type"]
@@ -30,22 +23,22 @@ module Toaster
30
23
  return objs
31
24
  end
32
25
 
33
- def self.load(uuid_or_task, key = nil, value = nil, type = nil, constraints = [])
34
- param = TaskParameter.new(nil, nil)
35
- hash = {}
36
- if !uuid_or_task.kind_of?(Task)
37
- uuid = uuid_or_task
38
- return nil if !uuid
39
- criteria = {"uuid" => uuid, "db_type" => "task_parameter"}
40
- hash = DB.instance.find_one(criteria)
41
- else
42
- task = uuid_or_task
43
- param = TaskParameter.new(task, key, value, type, constraints)
44
- hash = DB.instance.get_or_insert(param.to_hash(), @@id_attributes)
45
- end
46
- param = DB.apply_values(param, hash)
47
- return param
48
- end
26
+ # def self.load(uuid_or_task, key = nil, value = nil, type = nil, constraints = [])
27
+ # param = TaskParameter.new(nil, nil)
28
+ # hash = {}
29
+ # if !uuid_or_task.kind_of?(Task)
30
+ # uuid = uuid_or_task
31
+ # return nil if !uuid
32
+ # criteria = {"uuid" => uuid, "db_type" => "task_parameter"}
33
+ # hash = DB.instance.find_one(criteria)
34
+ # else
35
+ # task = uuid_or_task
36
+ # param = TaskParameter.new(task, key, value, type, constraints)
37
+ # hash = DB.instance.get_or_insert(param.to_hash(), @@id_attributes)
38
+ # end
39
+ # param = DB.apply_values(param, hash)
40
+ # return param
41
+ # end
49
42
 
50
43
  def hash
51
44
  h = @key.hash
@@ -51,7 +51,7 @@ module Toaster
51
51
  SystemState.remove_ignore_props!(p, ignore_props)
52
52
  end
53
53
  # remove ignored properties from state changes
54
- SystemState.remove_ignore_props!(ch, ignore_props)
54
+ SystemState.remove_ignore_props!(ch, ignore_props.collect { |ip| ip.key } )
55
55
 
56
56
  return convergence_for_prop_changes(ch, ps, prop_value_percentage_threshold)
57
57
  end
@@ -150,7 +150,7 @@ module Toaster
150
150
  # build tmp result hash
151
151
  task_execs.each do |run,exes|
152
152
  tmp[run] = {}
153
- exes.unshift(TaskExecution.new(nil,nil,nil,[],"start"))
153
+ exes.unshift(TaskExecution.new(:uuid => "start"))
154
154
  exes.each_with_index do |exe,idx|
155
155
  if exe.kind_of?(TaskExecution)
156
156
  pre_state = MarkupUtil.clone(exe.state_before) || {}
@@ -11,8 +11,14 @@ include Toaster
11
11
  module Toaster
12
12
  class Idempotence
13
13
 
14
- def initialize(test_suite)
15
- @test_suite = test_suite
14
+ def initialize(test_suite_or_automation)
15
+ if test_suite_or_automation.kind_of?(Automation)
16
+ @automation = test_suite_or_automation
17
+ @test_cases = @automation.get_all_test_cases
18
+ else
19
+ @automation = test_suite_or_automation.automation
20
+ @test_cases = test_suite_or_automation.test_cases
21
+ end
16
22
  @repeated_task_execs = nil
17
23
  @non_idempotent_tasks_details = nil
18
24
  @non_idempotent_taskseqs_details = nil
@@ -154,7 +160,7 @@ module Toaster
154
160
  return @repeated_task_execs if @repeated_task_execs
155
161
  result = {}
156
162
  tasks = {}
157
- @test_suite.test_cases.each do |tc|
163
+ @test_cases.each do |tc|
158
164
  if tc.automation_run
159
165
  tc.repeat_task_uuids.each do |rt|
160
166
  #puts "#{rt.inspect}"
@@ -165,12 +171,12 @@ module Toaster
165
171
  rt.each do |repeated_task|
166
172
  #execs = tc.task_executions(repeated_task)
167
173
  #tasks[repeated_task] = Task.find("uuid"=>repeated_task)[0] if !tasks[repeated_task]
168
- tasks[repeated_task] = @test_suite.automation.get_task(repeated_task, true)
174
+ tasks[repeated_task] = @automation.get_task(repeated_task, true)
169
175
  #puts "===> #{tasks[repeated_task]}"
170
176
  execs = TaskExecution.find(
171
- {"task_id"=>tasks[repeated_task].id, "automation_run_id"=>tc.automation_run.id},
172
- {"task" => tasks[repeated_task], "automation_run" => tc.automation_run}
173
- )
177
+ :task_id => tasks[repeated_task].id,
178
+ :automation_run_id => tc.automation_run.id
179
+ ).to_a
174
180
  execs.sort! { |a,b|
175
181
  a.start_time <=> b.start_time
176
182
  }
@@ -242,6 +242,7 @@ module Toaster
242
242
  end
243
243
  #puts "TRACE: ignore_prop_names #{ignore_prop_names}"
244
244
  ignore_prop_names.each do |key|
245
+ key = key.key if key.respond_to?(:key) # get IgnoreProperty.key
245
246
  if props_hash.kind_of?(Array)
246
247
  props_hash.dup.each do |k|
247
248
 
@@ -16,7 +16,7 @@ module Toaster
16
16
 
17
17
  belongs_to :test_suite
18
18
  belongs_to :automation_run
19
- has_many :test_attributes, nil, {:autosave => true, :dependent => :delete_all}
19
+ has_many :test_attributes, :autosave => true, :dependent => :destroy
20
20
  serialize :skip_task_uuids, JSON
21
21
  serialize :repeat_task_uuids, JSON
22
22
 
@@ -46,10 +46,10 @@ module Toaster
46
46
  def delete_test_result()
47
47
  # delete all task executions
48
48
  automation_run.task_executions.each do |exe|
49
- exe.delete
49
+ exe.destroy
50
50
  end
51
51
  # delete automation run
52
- automation_run.delete
52
+ automation_run.destroy
53
53
  # reset properties and save
54
54
  automation_run = nil
55
55
  start_time = 0
@@ -59,7 +59,7 @@ module Toaster
59
59
 
60
60
  def create_chef_node_attrs()
61
61
  attrs = {
62
- "toaster_testing" => {
62
+ "toaster" => {
63
63
  # set task IDs which are to be skipped during the test execution
64
64
  "skip_tasks" => skip_task_uuids.dup,
65
65
  # set task IDs which are to be repeated during the test execution
@@ -143,11 +143,16 @@ module Toaster
143
143
  return automation_run ? automation_run.success : nil
144
144
  end
145
145
 
146
+ # force loading of associations from DB
147
+ def load_associations
148
+ hash() # loads all
149
+ end
150
+
146
151
  def hash()
147
152
  h = 0
148
153
  h += skip_task_uuids.hash
149
154
  h += repeat_task_uuids.hash
150
- h += test_suite.uuid.hash
155
+ h += test_suite.uuid.hash if test_suite
151
156
  h += test_attributes ? test_attributes.hash : 0
152
157
  return h
153
158
  end
@@ -23,14 +23,17 @@ module Toaster
23
23
  @delay_between_tests = 6 # wait some seconds before starting next test
24
24
  @semaphore = Mutex.new
25
25
  @signal = ConditionVariable.new
26
+ @singleton = nil
26
27
 
27
28
  class << self
28
- attr_accessor :last_start_time, :delay_between_tests, :semaphore, :signal
29
+ attr_accessor :last_start_time, :delay_between_tests, :semaphore, :signal, :singleton
29
30
  end
30
31
 
31
- def initialize(test_suite=nil, max_threads_active=nil, terminate_when_queue_empty=true)
32
- @test_suite = test_suite
33
- @max_threads_active = max_threads_active ? max_threads_active : 5
32
+ private
33
+
34
+ # private constructor
35
+ def initialize(max_threads_active=nil, terminate_when_queue_empty=false)
36
+ @max_threads_active = max_threads_active ? max_threads_active : Config.get("testing.max_threads")
34
37
  @active_threads = []
35
38
  @terminate_when_queue_empty = terminate_when_queue_empty
36
39
  @request_queue = Queue.new
@@ -39,102 +42,19 @@ module Toaster
39
42
  @received_requests = []
40
43
  @handled_requests = []
41
44
  if !terminate_when_queue_empty
42
- start_worker_threads()
45
+ start()
43
46
  end
44
47
  end
45
48
 
49
+ public
50
+
46
51
  def start()
47
52
  start_worker_threads()
48
53
  end
49
- def start_worker_threads()
50
- current_num = 0
51
- @local_semaphore.synchronize do
52
- current_num = @active_threads.size
53
- end
54
- puts "DEBUG: currently active worker threads: #{current_num} of #{@max_threads_active}"
55
- ((current_num)..(@max_threads_active-1)).each do
56
- t = Thread.start {
57
- running = true
58
- while running
59
- begin
60
- test_case = nil
61
- @local_semaphore.synchronize do
62
- # terminate if no more requests are queued
63
- if @request_queue.empty? && @terminate_when_queue_empty
64
- # do NOT add this check --> leads to busy wait loop!
65
- #if @handled_requests.size >= @received_requests.size
66
- running = false
67
- #end
68
- end
69
- if running
70
- test_case = @request_queue.pop()
71
- @received_requests << test_case
72
- end
73
- end
74
- if test_case
75
- begin
76
- automation_run = TestRunner.execute_test(test_case)
77
- @result_map.put(test_case, automation_run)
78
- rescue Object => ex
79
- err = "WARN: exception when running test case: #{ex}\n#{ex.backtrace.join("\n")}"
80
- puts err
81
- @result_map.put(test_case, err)
82
- end
83
- @local_semaphore.synchronize do
84
- @handled_requests << test_case
85
- end
86
- end
87
- rescue Exception => ex
88
- puts "WARN: exception in test runner thread: #{ex}\n#{ex.backtrace.join("\n")}"
89
- @result_map.put(test_case, nil)
90
- end
91
- end
92
- @local_semaphore.synchronize do
93
- @active_threads.delete(self)
94
- end
95
- }
96
- @active_threads << t
97
- end
98
- end
99
54
 
100
55
  def stop()
101
56
  stop_threads()
102
57
  end
103
- def stop_threads()
104
- @active_threads.dup.each do |t|
105
- t.terminate()
106
- @active_threads.delete(t)
107
- end
108
- end
109
-
110
- def start_test_suite(blocking=true)
111
-
112
- start_worker_threads()
113
-
114
- @test_generator = TestGenerator.new(@test_suite) if @test_suite && !@test_generator
115
-
116
- @test_suite.save()
117
-
118
- # generate tests
119
- execute_tests(@test_generator.gen_all_tests())
120
-
121
- @test_suite.save()
122
-
123
- end
124
-
125
- def wait_until_finished(test_cases)
126
- test_cases = [test_cases] if !test_cases.kind_of?(Array)
127
- test_cases.dup.each do |t|
128
- # this operation will block until a results becomes available..
129
- @result_map.get(t)
130
- end
131
- end
132
-
133
- def self.schedule_tests(test_suite, test_case_uuids)
134
- runner = TestRunner.new(test_suite)
135
- runner.schedule_tests(test_suite, test_case_uuids)
136
- runner.start_worker_threads()
137
- end
138
58
 
139
59
  def schedule_tests(test_suite, test_case_uuids)
140
60
  test_cases = []
@@ -149,26 +69,6 @@ module Toaster
149
69
  end
150
70
  end
151
71
 
152
- def self.ensure_automation_exists_in_db(automation_name,
153
- recipes, test_suite, destroy_container=true, print_output=false)
154
-
155
- chef_node_name = ChefUtil.extract_node_name(automation_name)
156
- # prepare run list (add toaster::testing recipe definitions)
157
- actual_run_list = ChefUtil.prepare_run_list(chef_node_name, recipes)
158
- reduced_run_list = ChefUtil.get_reduced_run_list(actual_run_list)
159
- automation = Automation.find_by_cookbook_and_runlist(automation_name, reduced_run_list)
160
- return automation if !automation.nil?
161
-
162
- # if we don't have a matching automation in the DB yet, execute an initial run..
163
- c = TestCase.new(test_suite)
164
- test_suite.test_cases << c
165
- puts "INFO: Executing initial automation run; test case '#{c.uuid}'"
166
- automation_run = execute_test(c, destroy_container, print_output)
167
- puts "DEBUG: Finished execution of initial automation run; test case '#{c.uuid}': #{automation_run}"
168
- return nil if !automation_run
169
- return automation_run.automation
170
- end
171
-
172
72
  def schedule_test_case(test_case)
173
73
  execute_test_case(test_case, false)
174
74
  end
@@ -182,15 +82,42 @@ module Toaster
182
82
  @request_queue.push(test)
183
83
  end
184
84
 
185
- # start worker threads (if they are not already running..)
186
- start_worker_threads()
187
-
188
85
  if do_wait_until_finished
189
86
  puts "INFO: Waiting until #{test_cases.size} test cases are finished."
190
87
  wait_until_finished(test_cases)
191
88
  end
192
89
  end
193
90
 
91
+ # singleton pattern
92
+ def self.instance
93
+ self.semaphore.synchronize do
94
+ if !self.singleton
95
+ self.singleton = TestRunner.new()
96
+ end
97
+ end
98
+ return singleton
99
+ end
100
+
101
+ def self.ensure_automation_exists_in_db(automation_name,
102
+ recipes, test_suite, destroy_container=true, print_output=false)
103
+
104
+ chef_node_name = ChefUtil.extract_node_name(automation_name)
105
+ # prepare run list (add toaster::testing recipe definitions)
106
+ actual_run_list = ChefUtil.prepare_run_list(chef_node_name, recipes)
107
+ reduced_run_list = ChefUtil.get_reduced_run_list(actual_run_list)
108
+ automation = Automation.find_by_cookbook_and_runlist(automation_name, reduced_run_list)
109
+ return automation if !automation.nil?
110
+
111
+ # if we don't have a matching automation in the DB yet, execute an initial run..
112
+ c = TestCase.new(test_suite)
113
+ test_suite.test_cases << c
114
+ puts "INFO: Executing initial automation run; test case '#{c.uuid}'"
115
+ automation_run = execute_test(c, destroy_container, print_output)
116
+ puts "DEBUG: Finished execution of initial automation run; test case '#{c.uuid}': #{automation_run}"
117
+ return nil if !automation_run
118
+ return automation_run.automation
119
+ end
120
+
194
121
  #
195
122
  # execute the provided test case
196
123
  #
@@ -207,7 +134,7 @@ module Toaster
207
134
  test_id = test_suite.uuid
208
135
  recipes = automation.recipes
209
136
  end
210
-
137
+
211
138
  sleep_time = 0
212
139
  self.semaphore.synchronize do
213
140
  time = TimeStamp.now()
@@ -217,12 +144,12 @@ module Toaster
217
144
  end
218
145
  self.last_start_time = time + sleep_time
219
146
  end
220
-
147
+
221
148
  # sleep a bit, and then this test is ready to go...
222
149
  sleep(sleep_time)
223
150
  time_now = TimeStamp.now()
224
151
  test_case.start_time = time_now
225
-
152
+
226
153
  # start test case execution
227
154
  automation_run = nil
228
155
  error_output = nil
@@ -241,10 +168,11 @@ module Toaster
241
168
  else
242
169
  raise "Unknown automation language/type: '#{automation.language}'"
243
170
  end
244
-
171
+
172
+ automation_run.success = true
245
173
  test_case.test_suite().test_cases << test_case if !test_case.test_suite().test_cases().include?(test_case)
246
174
  test_case.automation_run = automation_run
247
-
175
+
248
176
  num_attempts = 0
249
177
  rescue Object => ex
250
178
  error_output = ex
@@ -253,7 +181,7 @@ module Toaster
253
181
  puts "#{ex.backtrace.join("\n")}"
254
182
  end
255
183
  end
256
-
184
+
257
185
  if !automation_run
258
186
  machine_id = Util.get_machine_id()
259
187
  automation = test_case.test_suite.automation
@@ -271,23 +199,38 @@ module Toaster
271
199
  automation_run.save
272
200
  test_case.automation_run = automation_run
273
201
  end
274
-
202
+
275
203
  test_case.end_time = TimeStamp.now().to_i
276
204
  test_case.save
277
-
205
+
278
206
  return automation_run
279
207
  end
280
208
 
281
209
  private
282
210
 
211
+ def stop_threads()
212
+ @active_threads.dup.each do |t|
213
+ t.terminate()
214
+ @active_threads.delete(t)
215
+ end
216
+ end
217
+
218
+ # apply some necessary preparations to the test container
283
219
  def self.prepare_test_container(lxc)
284
- # apply some necessary preparations to the test container
285
-
220
+
286
221
  # copy config file from host into container
287
222
  config_file_host = "#{Dir.home}/.toaster"
288
223
  config_file_cont = "#{lxc['rootdir']}/root/.toaster"
289
224
  `cp '#{config_file_host}' '#{config_file_cont}'`
290
225
 
226
+ # set DB host for container
227
+ if Toaster::Config.get("db.host_from_container")
228
+ config_hash = JSON.parse(File.read(config_file_cont).strip)
229
+ config_hash["db"] = {} if !config_hash["db"]
230
+ config_hash["db"]["host"] = Toaster::Config.get("db.host_from_container")
231
+ Util.write(config_file_cont, MarkupUtil.to_json(config_hash), true)
232
+ end
233
+
291
234
  # TODO: should we always re-install the toaster gem?
292
235
  #`ssh #{lxc["ip"]} "gem install --no-ri --no-rdoc cloud-toaster 2>&1"`
293
236
 
@@ -357,7 +300,9 @@ module Toaster
357
300
  end
358
301
 
359
302
  if automation_run_id
360
- automation_run = AutomationRun.load(automation_run_id)
303
+ automation_run = AutomationRun.find(automation_run_id)
304
+ automation_run.success = true
305
+ automation_run.save
361
306
  if test_id
362
307
  if !automation_run.automation
363
308
  #automation_run.automation = Automation.load(automation_run.automation_id)
@@ -396,5 +341,67 @@ module Toaster
396
341
  return automation_run
397
342
  end
398
343
 
344
+ def start_worker_threads()
345
+ current_num = 0
346
+ @local_semaphore.synchronize do
347
+ current_num = @active_threads.size
348
+ end
349
+ #puts "DEBUG: currently active worker threads: #{current_num} of #{@max_threads_active}"
350
+ ((current_num)..(@max_threads_active-1)).each do
351
+ t = Thread.start {
352
+ running = true
353
+ while running
354
+ begin
355
+ test_case = nil
356
+ @local_semaphore.synchronize do
357
+ # terminate if no more requests are queued
358
+ if @request_queue.empty? && @terminate_when_queue_empty
359
+ # do NOT add this check --> leads to busy wait loop!
360
+ #if @handled_requests.size >= @received_requests.size
361
+ running = false
362
+ #end
363
+ end
364
+ if running
365
+ test_case = @request_queue.pop()
366
+ @received_requests << test_case
367
+ end
368
+ end
369
+ if test_case
370
+ begin
371
+ # do this to load all fields and avoid activeRecord error
372
+ # "could not obtain a database connection within X seconds""
373
+ test_case.load_associations()
374
+ # now execute the test case
375
+ automation_run = TestRunner.execute_test(test_case)
376
+ @result_map.put(test_case, automation_run)
377
+ rescue Object => ex
378
+ err = "WARN: exception when running test case: #{ex}\n#{ex.backtrace.join("\n")}"
379
+ puts err
380
+ @result_map.put(test_case, err)
381
+ end
382
+ @local_semaphore.synchronize do
383
+ @handled_requests << test_case
384
+ end
385
+ end
386
+ rescue Exception => ex
387
+ puts "WARN: exception in test runner thread: #{ex}\n#{ex.backtrace.join("\n")}"
388
+ @result_map.put(test_case, nil)
389
+ end
390
+ end
391
+ @local_semaphore.synchronize do
392
+ @active_threads.delete(self)
393
+ end
394
+ }
395
+ @active_threads << t
396
+ end
397
+ end
398
+
399
+ def wait_until_finished(test_cases)
400
+ test_cases = [test_cases] if !test_cases.kind_of?(Array)
401
+ test_cases.dup.each do |t|
402
+ # this operation will block until a results becomes available..
403
+ @result_map.get(t)
404
+ end
405
+ end
399
406
  end
400
407
  end