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.
- checksums.yaml +8 -8
- data/Gemfile +5 -45
- data/README.md +2 -2
- data/VERSION +1 -1
- data/chef/cookbooks/lxc/recipes/setup_database.rb +12 -4
- data/chef/cookbooks/toaster/recipes/testing.rb +2 -2
- data/cloud-toaster.gemspec +94 -0
- data/config.json +3 -1
- data/lib/toaster/api.rb +25 -22
- data/lib/toaster/chef/chef_listener.rb +3 -3
- data/lib/toaster/chef/chef_node_inspector.rb +6 -4
- data/lib/toaster/chef/chef_util.rb +26 -8
- data/lib/toaster/chef/failsafe_resource_parser.rb +1 -0
- data/lib/toaster/chef/resource_inspector.rb +1 -1
- data/lib/toaster/model/automation.rb +26 -7
- data/lib/toaster/model/automation_run.rb +2 -11
- data/lib/toaster/model/key_value_pair.rb +13 -1
- data/lib/toaster/model/task.rb +3 -3
- data/lib/toaster/model/task_execution.rb +3 -2
- data/lib/toaster/model/task_parameter.rb +17 -24
- data/lib/toaster/state/convergence.rb +2 -2
- data/lib/toaster/state/idempotence.rb +13 -7
- data/lib/toaster/state/system_state.rb +1 -0
- data/lib/toaster/test/test_case.rb +10 -5
- data/lib/toaster/test/test_runner.rb +131 -124
- data/lib/toaster/test/test_suite.rb +1 -1
- data/lib/toaster/test_manager.rb +3 -9
- data/lib/toaster/toaster_app_service.rb +0 -4
- data/lib/toaster/util/config.rb +6 -1
- data/lib/toaster/util/lxc.rb +1 -1
- data/webapp/app/assets/javascripts/jstree.util.js +2 -1
- data/webapp/app/assets/stylesheets/application.css +21 -1
- data/webapp/app/assets/stylesheets/layout.css +1 -1
- data/webapp/app/controllers/analysis_controller.rb +12 -0
- data/webapp/app/controllers/application_controller.rb +11 -3
- data/webapp/app/controllers/base_controller.rb +1 -0
- data/webapp/app/controllers/execs_controller.rb +12 -4
- data/webapp/app/controllers/scripts_controller.rb +91 -52
- data/webapp/app/controllers/test_controller.rb +4 -4
- data/webapp/app/views/analysis/convergence.html.erb +14 -11
- data/webapp/app/views/analysis/idempotence.html.erb +40 -1
- data/webapp/app/views/analysis/index.html.erb +117 -0
- data/webapp/app/views/execs/automation_runs.html.erb +10 -4
- data/webapp/app/views/execs/task_executions.html.erb +20 -16
- data/webapp/app/views/layouts/application.html.erb +22 -9
- data/webapp/app/views/scripts/graph.html.erb +1 -1
- data/webapp/app/views/scripts/import_chef.html.erb +4 -0
- data/webapp/app/views/scripts/scripts.html.erb +5 -17
- data/webapp/app/views/settings/{index.html.erb → config.html.erb} +4 -6
- data/webapp/app/views/settings/containers.html.erb +7 -9
- data/webapp/app/views/test/gen.html.erb +1 -0
- data/webapp/app/views/test/suites.html.erb +1 -1
- data/webapp/app/views/util/chef.html.erb +2 -2
- data/webapp/config/initializers/devise.rb +4 -1
- data/webapp/config/routes.rb +15 -8
- data/webapp/log/development.log +39521 -0
- data/webapp/tmp/cache/assets/development/sprockets/0dc0562647e703ca0535da3b9fcad796 +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/357970feca3ac29060c1e3861e2c0953 +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/72cbfd5bf33945bcce154f7a4feaf04d +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/7b2b7d9034fc7b77daf5da1436667e6f +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/b6f1534bcdbff92a16c85487f363235a +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/bfac6cd2984fbd5e0d762389e3c37164 +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/c5907cfd07b24ad19b8c80e8af618a57 +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/cffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/d771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/webapp/tmp/cache/assets/development/sprockets/f7cbd26ba1d28d48de824f0e94586655 +0 -0
- metadata +53 -51
@@ -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,
|
22
|
-
has_many :
|
23
|
-
has_many :
|
24
|
-
has_many :
|
25
|
-
has_many :
|
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.
|
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,
|
22
|
-
has_many :run_attributes,
|
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
|
data/lib/toaster/model/task.rb
CHANGED
@@ -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(
|
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
|
-
|
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(
|
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(
|
15
|
-
|
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
|
-
@
|
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] = @
|
174
|
+
tasks[repeated_task] = @automation.get_task(repeated_task, true)
|
169
175
|
#puts "===> #{tasks[repeated_task]}"
|
170
176
|
execs = TaskExecution.find(
|
171
|
-
|
172
|
-
|
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
|
}
|
@@ -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,
|
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.
|
49
|
+
exe.destroy
|
50
50
|
end
|
51
51
|
# delete automation run
|
52
|
-
automation_run.
|
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
|
-
"
|
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
|
-
|
32
|
-
|
33
|
-
|
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
|
-
|
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
|
-
|
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.
|
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
|