jamie 0.1.0.alpha21 → 0.1.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +8 -0
- data/Rakefile +32 -24
- data/jamie.gemspec +1 -0
- data/lib/jamie.rb +132 -18
- data/lib/jamie/cli.rb +29 -8
- data/lib/jamie/version.rb +1 -1
- data/spec/jamie_spec.rb +6 -0
- metadata +18 -2
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -1,24 +1,38 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
|
-
require 'cane/rake_task'
|
3
2
|
require 'rake/testtask'
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
Jamie::RakeTasks#define
|
10
|
-
Jamie::ThorTasks#define
|
11
|
-
Jamie::CLI#pry_prompts
|
12
|
-
)
|
13
|
-
cane.style_exclude = %w(
|
14
|
-
lib/vendor/hash_recursive_merge.rb
|
15
|
-
)
|
16
|
-
cane.doc_exclude = %w(
|
17
|
-
lib/vendor/hash_recursive_merge.rb
|
18
|
-
)
|
3
|
+
|
4
|
+
Rake::TestTask.new do |t|
|
5
|
+
t.libs.push "lib"
|
6
|
+
t.test_files = FileList['spec/**/*_spec.rb']
|
7
|
+
t.verbose = true
|
19
8
|
end
|
20
9
|
|
21
|
-
|
10
|
+
task :default => [ :test ]
|
11
|
+
|
12
|
+
unless RUBY_ENGINE == 'jruby'
|
13
|
+
require 'cane/rake_task'
|
14
|
+
require 'tailor/rake_task'
|
15
|
+
|
16
|
+
desc "Run cane to check quality metrics"
|
17
|
+
Cane::RakeTask.new do |cane|
|
18
|
+
cane.abc_exclude = %w(
|
19
|
+
Jamie::RakeTasks#define
|
20
|
+
Jamie::ThorTasks#define
|
21
|
+
Jamie::CLI#pry_prompts
|
22
|
+
Jamie::Instance#synchronize_or_call
|
23
|
+
)
|
24
|
+
cane.style_exclude = %w(
|
25
|
+
lib/vendor/hash_recursive_merge.rb
|
26
|
+
)
|
27
|
+
cane.doc_exclude = %w(
|
28
|
+
lib/vendor/hash_recursive_merge.rb
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
Tailor::RakeTask.new
|
33
|
+
|
34
|
+
Rake::Task[:default].enhance [ :cane, :tailor ]
|
35
|
+
end
|
22
36
|
|
23
37
|
desc "Display LOC stats"
|
24
38
|
task :stats do
|
@@ -28,10 +42,4 @@ task :stats do
|
|
28
42
|
sh "countloc -r spec"
|
29
43
|
end
|
30
44
|
|
31
|
-
Rake::
|
32
|
-
t.libs.push "lib"
|
33
|
-
t.test_files = FileList['spec/**/*_spec.rb']
|
34
|
-
t.verbose = true
|
35
|
-
end
|
36
|
-
|
37
|
-
task :default => [ :test, :cane, :tailor, :stats ]
|
45
|
+
Rake::Task[:default].enhance [ :stats ]
|
data/jamie.gemspec
CHANGED
data/lib/jamie.rb
CHANGED
@@ -18,6 +18,7 @@
|
|
18
18
|
|
19
19
|
require 'base64'
|
20
20
|
require 'benchmark'
|
21
|
+
require 'celluloid'
|
21
22
|
require 'delegate'
|
22
23
|
require 'digest'
|
23
24
|
require 'erb'
|
@@ -29,6 +30,7 @@ require 'net/https'
|
|
29
30
|
require 'net/scp'
|
30
31
|
require 'net/ssh'
|
31
32
|
require 'pathname'
|
33
|
+
require 'thread'
|
32
34
|
require 'socket'
|
33
35
|
require 'stringio'
|
34
36
|
require 'vendor/hash_recursive_merge'
|
@@ -41,6 +43,8 @@ module Jamie
|
|
41
43
|
class << self
|
42
44
|
|
43
45
|
attr_accessor :logger
|
46
|
+
attr_accessor :crashes
|
47
|
+
attr_accessor :mutex
|
44
48
|
|
45
49
|
# Returns the root path of the Jamie gem source code.
|
46
50
|
#
|
@@ -49,10 +53,15 @@ module Jamie
|
|
49
53
|
@source_root ||= Pathname.new(File.expand_path('../../', __FILE__))
|
50
54
|
end
|
51
55
|
|
56
|
+
def crashes?
|
57
|
+
! crashes.empty?
|
58
|
+
end
|
59
|
+
|
52
60
|
def default_logger
|
53
61
|
env_log = ENV['JAMIE_LOG'] && ENV['JAMIE_LOG'].downcase.to_sym
|
62
|
+
env_log = Util.to_logger_level(env_log) unless env_log.nil?
|
54
63
|
|
55
|
-
Logger.new(:
|
64
|
+
Logger.new(:stdout => STDOUT, :level => env_log)
|
56
65
|
end
|
57
66
|
end
|
58
67
|
|
@@ -101,9 +110,7 @@ module Jamie
|
|
101
110
|
# @return [Array<Instance>] all instances, resulting from all platform and
|
102
111
|
# suite combinations
|
103
112
|
def instances
|
104
|
-
|
105
|
-
platforms.map { |platform| new_instance(suite, platform) }
|
106
|
-
}.flatten)
|
113
|
+
instances_array(load_instances)
|
107
114
|
end
|
108
115
|
|
109
116
|
# @return [String] path to the Jamie YAML file
|
@@ -162,6 +169,24 @@ module Jamie
|
|
162
169
|
|
163
170
|
private
|
164
171
|
|
172
|
+
def load_instances
|
173
|
+
return @instance_count if @instance_count && @instance_count > 0
|
174
|
+
|
175
|
+
results = []
|
176
|
+
suites.product(platforms).each_with_index do |arr, index|
|
177
|
+
results << new_instance(arr[0], arr[1], index)
|
178
|
+
end
|
179
|
+
@instance_count = results.size
|
180
|
+
end
|
181
|
+
|
182
|
+
def instances_array(instance_count)
|
183
|
+
results = []
|
184
|
+
instance_count.times do |index|
|
185
|
+
results << Celluloid::Actor["instance_#{index}".to_sym]
|
186
|
+
end
|
187
|
+
Collection.new(results)
|
188
|
+
end
|
189
|
+
|
165
190
|
def new_suite(hash)
|
166
191
|
path_hash = {
|
167
192
|
:data_bags_path => calculate_path("data_bags", hash[:name]),
|
@@ -182,19 +207,24 @@ module Jamie
|
|
182
207
|
Driver.for_plugin(hash[:driver_plugin], hash[:driver_config])
|
183
208
|
end
|
184
209
|
|
185
|
-
def new_instance(suite, platform)
|
186
|
-
log_root = File.expand_path(File.join(jamie_root, ".jamie", "logs"))
|
210
|
+
def new_instance(suite, platform, index)
|
187
211
|
platform_hash = platform_driver_hash(platform.name)
|
188
212
|
driver = new_driver(merge_driver_hash(platform_hash))
|
189
213
|
FileUtils.mkdir_p(log_root)
|
190
214
|
|
191
|
-
Instance.
|
215
|
+
supervisor = Instance.supervise_as(
|
216
|
+
"instance_#{index}".to_sym,
|
192
217
|
:suite => suite,
|
193
218
|
:platform => platform,
|
194
219
|
:driver => driver,
|
195
220
|
:jr => Jr.new(suite.name),
|
196
|
-
:logger => new_instance_logger(
|
221
|
+
:logger => new_instance_logger(index)
|
197
222
|
)
|
223
|
+
supervisor.actors.first
|
224
|
+
end
|
225
|
+
|
226
|
+
def log_root
|
227
|
+
File.expand_path(File.join(jamie_root, ".jamie", "logs"))
|
198
228
|
end
|
199
229
|
|
200
230
|
def platform_driver_hash(platform_name)
|
@@ -203,13 +233,14 @@ module Jamie
|
|
203
233
|
h.select { |key, value| [ :driver_plugin, :driver_config ].include?(key) }
|
204
234
|
end
|
205
235
|
|
206
|
-
def new_instance_logger(
|
236
|
+
def new_instance_logger(index)
|
207
237
|
level = Util.to_logger_level(self.log_level)
|
238
|
+
color = Color::COLORS[index % Color::COLORS.size].to_sym
|
208
239
|
|
209
240
|
lambda do |name|
|
210
241
|
logfile = File.join(log_root, "#{name}.log")
|
211
242
|
|
212
|
-
Logger.new(:stdout => STDOUT, :logdev => logfile,
|
243
|
+
Logger.new(:stdout => STDOUT, :color => color, :logdev => logfile,
|
213
244
|
:level => level, :progname => name)
|
214
245
|
end
|
215
246
|
end
|
@@ -276,6 +307,31 @@ module Jamie
|
|
276
307
|
# Default log level verbosity
|
277
308
|
DEFAULT_LOG_LEVEL = :info
|
278
309
|
|
310
|
+
module Color
|
311
|
+
ANSI = {
|
312
|
+
:reset => 0, :black => 30, :red => 31, :green => 32, :yellow => 33,
|
313
|
+
:blue => 34, :magenta => 35, :cyan => 36, :white => 37,
|
314
|
+
:bright_black => 30, :bright_red => 31, :bright_green => 32,
|
315
|
+
:bright_yellow => 33, :bright_blue => 34, :bright_magenta => 35,
|
316
|
+
:bright_cyan => 36, :bright_white => 37
|
317
|
+
}.freeze
|
318
|
+
|
319
|
+
COLORS = %w(
|
320
|
+
cyan yellow green magenta red blue bright_cyan bright_yellow
|
321
|
+
bright_green bright_magenta bright_red, bright_blue
|
322
|
+
).freeze
|
323
|
+
|
324
|
+
def self.escape(name)
|
325
|
+
return "" if name.nil?
|
326
|
+
return "" unless ansi = ANSI[name]
|
327
|
+
"\e[#{ansi}m"
|
328
|
+
end
|
329
|
+
|
330
|
+
def self.colorize(str, name)
|
331
|
+
"#{escape(name)}#{str}#{escape(:reset)}"
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
279
335
|
# Logging implementation for Jamie. By default the console/stdout output will
|
280
336
|
# be displayed differently than the file log output. Therefor, this class
|
281
337
|
# wraps multiple loggers that conform to the stdlib `Logger` class behavior.
|
@@ -286,10 +342,12 @@ module Jamie
|
|
286
342
|
include ::Logger::Severity
|
287
343
|
|
288
344
|
def initialize(options = {})
|
345
|
+
color = options[:color] || :bright_white
|
346
|
+
|
289
347
|
@loggers = []
|
290
348
|
@loggers << logdev_logger(options[:logdev]) if options[:logdev]
|
291
|
-
@loggers << stdout_logger(options[:stdout]) if options[:stdout]
|
292
|
-
@loggers << stdout_logger(STDOUT) if @loggers.empty?
|
349
|
+
@loggers << stdout_logger(options[:stdout], color) if options[:stdout]
|
350
|
+
@loggers << stdout_logger(STDOUT, color) if @loggers.empty?
|
293
351
|
|
294
352
|
self.progname = options[:progname] || "Jamie"
|
295
353
|
self.level = options[:level] || default_log_level
|
@@ -318,10 +376,10 @@ module Jamie
|
|
318
376
|
Util.to_logger_level(Jamie::DEFAULT_LOG_LEVEL)
|
319
377
|
end
|
320
378
|
|
321
|
-
def stdout_logger(stdout)
|
379
|
+
def stdout_logger(stdout, color)
|
322
380
|
logger = StdoutLogger.new(stdout)
|
323
381
|
logger.formatter = proc do |severity, datetime, progname, msg|
|
324
|
-
"#{msg}\n"
|
382
|
+
Color.colorize("#{msg}\n", color)
|
325
383
|
end
|
326
384
|
logger
|
327
385
|
end
|
@@ -500,8 +558,13 @@ module Jamie
|
|
500
558
|
# @author Fletcher Nichol <fnichol@nichol.ca>
|
501
559
|
class Instance
|
502
560
|
|
561
|
+
include Celluloid
|
503
562
|
include Logging
|
504
563
|
|
564
|
+
class << self
|
565
|
+
attr_accessor :mutexes
|
566
|
+
end
|
567
|
+
|
505
568
|
# @return [Suite] the test suite configuration
|
506
569
|
attr_reader :suite
|
507
570
|
|
@@ -538,6 +601,7 @@ module Jamie
|
|
538
601
|
@logger = logger.is_a?(Proc) ? logger.call(name) : logger
|
539
602
|
|
540
603
|
@driver.instance = self
|
604
|
+
setup_driver_mutex
|
541
605
|
end
|
542
606
|
|
543
607
|
# @return [String] name of this instance
|
@@ -643,7 +707,7 @@ module Jamie
|
|
643
707
|
destroy if destroy_mode == :passing
|
644
708
|
end
|
645
709
|
info "Finished testing #{to_str} (#{elapsed.real} seconds)."
|
646
|
-
|
710
|
+
Actor.current
|
647
711
|
ensure
|
648
712
|
destroy if destroy_mode == :always
|
649
713
|
end
|
@@ -670,6 +734,15 @@ module Jamie
|
|
670
734
|
end
|
671
735
|
end
|
672
736
|
|
737
|
+
def setup_driver_mutex
|
738
|
+
if driver.class.serial_actions
|
739
|
+
Jamie.mutex.synchronize do
|
740
|
+
self.class.mutexes ||= Hash.new
|
741
|
+
self.class.mutexes[driver.class] = Mutex.new
|
742
|
+
end
|
743
|
+
end
|
744
|
+
end
|
745
|
+
|
673
746
|
def transition_to(desired)
|
674
747
|
result = nil
|
675
748
|
FSM.actions(last_action, desired).each do |transition|
|
@@ -704,13 +777,13 @@ module Jamie
|
|
704
777
|
info("Finished #{output_verb.downcase} #{to_str}" +
|
705
778
|
" (#{elapsed.real} seconds).")
|
706
779
|
yield if block_given?
|
707
|
-
|
780
|
+
Actor.current
|
708
781
|
end
|
709
782
|
|
710
|
-
def action(what)
|
783
|
+
def action(what, &block)
|
711
784
|
state = load_state
|
712
785
|
elapsed = Benchmark.measure do
|
713
|
-
|
786
|
+
synchronize_or_call(what, state, &block)
|
714
787
|
end
|
715
788
|
state[:last_action] = what.to_s
|
716
789
|
elapsed
|
@@ -718,6 +791,18 @@ module Jamie
|
|
718
791
|
dump_state(state)
|
719
792
|
end
|
720
793
|
|
794
|
+
def synchronize_or_call(what, state, &block)
|
795
|
+
if Array(driver.class.serial_actions).include?(what)
|
796
|
+
debug("#{to_str} is synchronizing on #{driver.class}##{what}")
|
797
|
+
self.class.mutexes[driver.class].synchronize do
|
798
|
+
debug("#{to_str} is messaging #{driver.class}##{what}")
|
799
|
+
block.call(state)
|
800
|
+
end
|
801
|
+
else
|
802
|
+
block.call(state)
|
803
|
+
end
|
804
|
+
end
|
805
|
+
|
721
806
|
def load_state
|
722
807
|
if File.exists?(statefile)
|
723
808
|
Util.symbolized_hash(YAML.load_file(statefile))
|
@@ -1037,6 +1122,10 @@ module Jamie
|
|
1037
1122
|
|
1038
1123
|
attr_writer :instance
|
1039
1124
|
|
1125
|
+
class << self
|
1126
|
+
attr_reader :serial_actions
|
1127
|
+
end
|
1128
|
+
|
1040
1129
|
def initialize(config = {})
|
1041
1130
|
@config = config
|
1042
1131
|
self.class.defaults.each do |attr, value|
|
@@ -1096,6 +1185,9 @@ module Jamie
|
|
1096
1185
|
|
1097
1186
|
attr_reader :config, :instance
|
1098
1187
|
|
1188
|
+
ACTION_METHODS = %w{create converge setup verify destroy}.
|
1189
|
+
map(&:to_sym).freeze
|
1190
|
+
|
1099
1191
|
def logger
|
1100
1192
|
instance.logger
|
1101
1193
|
end
|
@@ -1122,6 +1214,17 @@ module Jamie
|
|
1122
1214
|
def self.default_config(attr, value)
|
1123
1215
|
defaults[attr] = value
|
1124
1216
|
end
|
1217
|
+
|
1218
|
+
def self.no_parallel_for(*methods)
|
1219
|
+
Array(methods).each do |meth|
|
1220
|
+
if ! ACTION_METHODS.include?(meth)
|
1221
|
+
raise ArgumentError, "##{meth} is not a whitelisted method."
|
1222
|
+
end
|
1223
|
+
end
|
1224
|
+
|
1225
|
+
@serial_actions ||= []
|
1226
|
+
@serial_actions += methods
|
1227
|
+
end
|
1125
1228
|
end
|
1126
1229
|
|
1127
1230
|
# Base class for a driver that uses SSH to communication with an instance.
|
@@ -1435,4 +1538,15 @@ module Jamie
|
|
1435
1538
|
end
|
1436
1539
|
end
|
1437
1540
|
|
1541
|
+
# Initialize the base logger and use that for Celluloid's logger
|
1438
1542
|
Jamie.logger = Jamie.default_logger
|
1543
|
+
Celluloid.logger = Jamie.logger
|
1544
|
+
|
1545
|
+
# Setup a collection of instance crash exceptions for error reporting
|
1546
|
+
Jamie.crashes = []
|
1547
|
+
Celluloid.exception_handler do |exception|
|
1548
|
+
Jamie.logger.debug("An instance crashed because of #{exception.inspect}")
|
1549
|
+
Jamie.crashes << exception
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
Jamie.mutex = Mutex.new
|
data/lib/jamie/cli.rb
CHANGED
@@ -35,6 +35,7 @@ module Jamie
|
|
35
35
|
# Constructs a new instance.
|
36
36
|
def initialize(*args)
|
37
37
|
super
|
38
|
+
$stdout.sync = true
|
38
39
|
@config = Jamie::Config.new(ENV['JAMIE_YAML'])
|
39
40
|
end
|
40
41
|
|
@@ -46,9 +47,11 @@ module Jamie
|
|
46
47
|
|
47
48
|
[:create, :converge, :setup, :verify, :destroy].each do |action|
|
48
49
|
desc(
|
49
|
-
"#{action} [(all|<REGEX>)]",
|
50
|
+
"#{action} [(all|<REGEX>)] [opts]",
|
50
51
|
"#{action.capitalize} one or more instances"
|
51
52
|
)
|
53
|
+
method_option :parallel, :aliases => "-p", :type => :boolean,
|
54
|
+
:desc => "Perform action against all matching instances in parallel"
|
52
55
|
define_method(action) { |*args| exec_action(action) }
|
53
56
|
end
|
54
57
|
|
@@ -63,17 +66,26 @@ module Jamie
|
|
63
66
|
* always: instances will always be destroyed afterwards.\n
|
64
67
|
* never: instances will never be destroyed afterwards.
|
65
68
|
DESC
|
69
|
+
method_option :parallel, :aliases => "-p", :type => :boolean,
|
70
|
+
:desc => "Perform action against all matching instances in parallel"
|
66
71
|
method_option :destroy, :aliases => "-d", :default => "passing",
|
67
72
|
:desc => "Destroy strategy to use after testing (passing, always, never)."
|
68
73
|
def test(*args)
|
74
|
+
if ! %w{passing always never}.include?(options[:destroy])
|
75
|
+
raise ArgumentError, "Destroy mode must be passing, always, or never."
|
76
|
+
end
|
77
|
+
|
69
78
|
banner "Starting Jamie"
|
70
79
|
elapsed = Benchmark.measure do
|
71
|
-
destroy_mode = options[:destroy]
|
72
|
-
|
73
|
-
|
80
|
+
destroy_mode = options[:destroy].to_sym
|
81
|
+
@task = :test
|
82
|
+
results = parse_subcommand(args.first)
|
83
|
+
|
84
|
+
if options[:parallel]
|
85
|
+
run_parallel(results, destroy_mode)
|
86
|
+
else
|
87
|
+
run_serial(results, destroy_mode)
|
74
88
|
end
|
75
|
-
result = parse_subcommand(args.first)
|
76
|
-
Array(result).each { |instance| instance.test(destroy_mode.to_sym) }
|
77
89
|
end
|
78
90
|
banner "Jamie is finished. (#{elapsed.real} seconds)"
|
79
91
|
end
|
@@ -132,12 +144,21 @@ module Jamie
|
|
132
144
|
banner "Starting Jamie"
|
133
145
|
elapsed = Benchmark.measure do
|
134
146
|
@task = action
|
135
|
-
|
136
|
-
|
147
|
+
results = parse_subcommand(args.first)
|
148
|
+
options[:parallel] ? run_parallel(results) : run_serial(results)
|
137
149
|
end
|
138
150
|
banner "Jamie is finished. (#{elapsed.real} seconds)"
|
139
151
|
end
|
140
152
|
|
153
|
+
def run_serial(instances, *args)
|
154
|
+
Array(instances).map { |i| i.public_send(task, *args) }
|
155
|
+
end
|
156
|
+
|
157
|
+
def run_parallel(instances, *args)
|
158
|
+
futures = Array(instances).map { |i| i.future.public_send(task) }
|
159
|
+
futures.map { |i| i.value }
|
160
|
+
end
|
161
|
+
|
141
162
|
def parse_subcommand(arg = nil)
|
142
163
|
arg == "all" ? get_all_instances : get_filtered_instances(arg)
|
143
164
|
end
|
data/lib/jamie/version.rb
CHANGED
data/spec/jamie_spec.rb
CHANGED
@@ -28,8 +28,10 @@ end
|
|
28
28
|
SimpleCov.start 'gem'
|
29
29
|
|
30
30
|
require 'fakefs/spec_helpers'
|
31
|
+
require 'logger'
|
31
32
|
require 'minitest/autorun'
|
32
33
|
require 'ostruct'
|
34
|
+
require 'stringio'
|
33
35
|
|
34
36
|
require 'jamie'
|
35
37
|
require 'jamie/driver/dummy'
|
@@ -391,6 +393,10 @@ describe Jamie::Instance do
|
|
391
393
|
|
392
394
|
let(:instance) { Jamie::Instance.new(opts) }
|
393
395
|
|
396
|
+
before do
|
397
|
+
Celluloid.logger = Logger.new(StringIO.new)
|
398
|
+
end
|
399
|
+
|
394
400
|
it "raises an ArgumentError if suite is missing" do
|
395
401
|
opts.delete(:suite)
|
396
402
|
proc { Jamie::Instance.new(opts) }.must_raise ArgumentError
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jamie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.0.
|
4
|
+
version: 0.1.0.beta1
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,8 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: celluloid
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
14
30
|
- !ruby/object:Gem::Dependency
|
15
31
|
name: thor
|
16
32
|
requirement: !ruby/object:Gem::Requirement
|