cloud-toaster 1.1.5 → 1.1.6

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