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