furnish 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/test/mt_cases.rb ADDED
@@ -0,0 +1,267 @@
1
+ require 'furnish/provisioners/dummy'
2
+
3
+ Dummy = Furnish::Provisioner::Dummy unless defined? Dummy
4
+
5
+ class StartFailDummy < Dummy
6
+ def startup(*args)
7
+ super
8
+ false
9
+ end
10
+ end
11
+
12
+ class StopFailDummy < Dummy
13
+ def shutdown
14
+ super
15
+ false
16
+ end
17
+ end
18
+
19
+ class StartExceptionDummy < Dummy
20
+ def startup(*args)
21
+ super
22
+ raise "ermagherd startup"
23
+ end
24
+ end
25
+
26
+ class StopExceptionDummy < Dummy
27
+ def shutdown(*args)
28
+ super
29
+ raise "ermagherd shutdown"
30
+ end
31
+ end
32
+
33
+ module Furnish
34
+ class TestCase < MiniTest::Unit::TestCase
35
+ def setup
36
+ @tempfiles ||= []
37
+ file = Tempfile.new('furnish_db')
38
+ @tempfiles.push(file)
39
+ logfile = Tempfile.new('furnish_log')
40
+ @tempfiles.push(logfile)
41
+ Furnish.logger = Furnish::Logger.new(logfile, 3)
42
+ Furnish.init(file.path)
43
+ return file
44
+ end
45
+
46
+ def teardown
47
+ Furnish.logger.close
48
+ Furnish.shutdown
49
+ @tempfiles.each do |file|
50
+ file.unlink
51
+ end
52
+ end
53
+ end
54
+
55
+ class SchedulerTestCase < TestCase
56
+ attr_reader :sched
57
+
58
+ def setup
59
+ super
60
+ @sched = Furnish::Scheduler.new
61
+ @monitor = Thread.new { loop { @sched.running?; sleep 1 } }
62
+ @monitor.abort_on_exception = true
63
+ end
64
+
65
+ def teardown
66
+ @monitor.kill rescue nil
67
+ super
68
+ end
69
+ end
70
+
71
+ class RunningSchedulerTestCase < SchedulerTestCase
72
+ def teardown
73
+ sched.stop
74
+ super
75
+ end
76
+
77
+ def assert_started(name)
78
+ assert_includes(sched.vm.solved, name, 'scheduler thinks it solved it')
79
+ assert(sched.vm.groups[name].first.store[ [name, "startup"].join("-") ], "dummy provisioner for #{name} recorded the startup run")
80
+ refute(sched.vm.groups[name].first.store[ [name, "shutdown"].join("-") ], "dummy provisioner for #{name} has not recorded the shutdown run")
81
+ end
82
+
83
+ def assert_shutdown(name, provisioner)
84
+ refute_includes(sched.vm.solved, name, 'scheduler thinks it solved it')
85
+ assert(provisioner.store[ [name, "shutdown"].join("-") ], "dummy provisioner for #{name} recorded the shutdown run")
86
+ end
87
+
88
+ def test_provision_cycle
89
+ machine_names = %w[blarg blarg2 blarg3]
90
+
91
+ machine_names.each do |name|
92
+ assert(sched.schedule_provision(name, Dummy.new))
93
+ end
94
+
95
+ sched.run
96
+ sched.wait_for(*machine_names)
97
+
98
+ machine_names.each do |name|
99
+ assert_started(name)
100
+ end
101
+
102
+ machine_provs = machine_names.map { |n| sched.vm.groups[n].first }
103
+
104
+ sched.teardown
105
+
106
+ machine_names.each_with_index do |name, i|
107
+ assert_shutdown(name, machine_provs[i])
108
+ end
109
+ end
110
+
111
+ def test_dependent_provision
112
+ # since we can't reliably predict linear order, we just paritition it by
113
+ # how the dependency resolver should sort things out. This isn't perfect by
114
+ # any means, but allows us to check the dependency resolver.
115
+ machine_order = {
116
+ "blarg1" => %w[blarg2 blarg3],
117
+ "blarg2" => %w[blarg4],
118
+ "blarg3" => %w[blarg4],
119
+ "blarg4" => [],
120
+ "blarg5" => []
121
+ }
122
+
123
+ assert(sched.schedule_provision('blarg1', Dummy.new))
124
+ assert(sched.schedule_provision('blarg2', Dummy.new, %w[blarg1]))
125
+ assert(sched.schedule_provision('blarg3', Dummy.new, %w[blarg1]))
126
+ assert(sched.schedule_provision('blarg4', Dummy.new, %w[blarg2 blarg3]))
127
+ assert(sched.schedule_provision('blarg5', Dummy.new))
128
+
129
+ sched.run
130
+ sched.wait_for(*machine_order.keys)
131
+
132
+ 1.upto(5) { |x| assert_started("blarg#{x}") }
133
+
134
+ order = Dummy.new.order
135
+ possible_next = Set[*%w[blarg1 blarg5]]
136
+
137
+ while machine = order.shift
138
+ assert_includes(possible_next, machine, "machine was matched in possible nexts")
139
+ machine_order[machine].each do |nexts|
140
+ possible_next.add(nexts)
141
+ end
142
+
143
+ possible_next.delete(machine)
144
+ end
145
+
146
+ machine_provs = (1..5).map { |n| sched.vm.groups["blarg#{n}"].first }
147
+
148
+ sched.teardown
149
+
150
+ 1.upto(5) { |x| assert_shutdown("blarg#{x}", machine_provs[x-1]) }
151
+ end
152
+
153
+ def test_multiprovision_order
154
+ dummies = [Dummy.new, Dummy.new]
155
+ dummies.each_with_index { |x,i| x.id = i }
156
+ assert(sched.schedule_provision('blarg', dummies))
157
+ sched.run
158
+ sched.wait_for('blarg')
159
+ assert_equal(dummies.map(&:id), dummies.first.call_order.to_a)
160
+ dummies.first.call_order.clear
161
+ assert_empty(dummies.first.call_order.to_a)
162
+ sched.teardown
163
+ assert_equal(dummies.reverse.map(&:id), dummies.first.call_order.to_a)
164
+ end
165
+
166
+ def test_single_deprovision
167
+ assert(sched.schedule_provision('blarg', Dummy.new))
168
+ assert(sched.schedule_provision('blarg2', Dummy.new))
169
+ assert(sched.schedule_provision('blarg3', Dummy.new, %w[blarg2]))
170
+
171
+ sched.run
172
+ sched.wait_for('blarg', 'blarg2', 'blarg3')
173
+
174
+ %w[blarg blarg2 blarg3].each do |name|
175
+ assert_includes(sched.vm.solved, name, "#{name} is in the solved list")
176
+ end
177
+
178
+ sched.teardown_group("blarg")
179
+
180
+ [sched.vm.solved, sched.vm.groups.keys].each do |coll|
181
+ assert_includes(coll, "blarg2", "blarg2 is still available")
182
+ assert_includes(coll, "blarg3", "blarg3 is still available")
183
+ refute_includes(coll, "blarg", "blarg is not still available")
184
+ end
185
+
186
+ #
187
+ # vm.dependencies doesn't track empty references, so deprovisions that have
188
+ # dependencies need some extra checks to ensure their behavior. Basically
189
+ # this just means they can't be tested generically.
190
+ #
191
+ assert_includes(sched.vm.dependencies.keys, "blarg3", "blarg3 still has dependencies")
192
+ sched.teardown_group("blarg3")
193
+ refute_includes(sched.vm.dependencies.keys, "blarg3", "blarg3 still has dependencies")
194
+ end
195
+
196
+ def test_run_arguments
197
+ tempfiles = []
198
+
199
+ signals = %w[INFO USR2]
200
+
201
+ signals.each do |signal|
202
+ if Signal.list[signal] # not everyone has INFO
203
+ Signal.trap(signal) { nil } if Signal.list[signal]
204
+
205
+ tf = Tempfile.new('furnish_signal_handlers')
206
+ tempfiles.push(tf)
207
+ Furnish.logger = Furnish::Logger.new(tf)
208
+
209
+ sched.run(false)
210
+ Process.kill(signal, Process.pid)
211
+
212
+ sched.stop
213
+ sleep 0.1 # wait for any writes to complete
214
+
215
+ %w[solved working waiting].each do |section|
216
+ refute_match(/#{section}/, File.read(tf.path), "#{signal} yielded no output with the #{section} set")
217
+ end
218
+
219
+ sched.run(true)
220
+ Process.kill(signal, Process.pid)
221
+
222
+ sched.stop
223
+ sleep 0.1 # wait for any writes to complete
224
+
225
+ %w[solved working waiting].each do |section|
226
+ assert_match(/#{section}/, File.read(tf.path), "#{signal} yielded output with the #{section} set")
227
+ end
228
+ end
229
+ end
230
+ ensure
231
+ tempfiles.each { |f| f.unlink }
232
+ end
233
+
234
+ def test_provision_failures
235
+ dummy = StartFailDummy.new
236
+ assert(sched.schedule_provision('blarg', dummy))
237
+ assert_raises(RuntimeError, "Could not provision blarg with provisioner StartFailDummy") do
238
+ sched.run
239
+ sched.wait_for('blarg')
240
+ end
241
+ sched.stop
242
+ sched.deprovision_group('blarg')
243
+
244
+ # tests scheduler crashes not keeping the scheduler from being restarted
245
+ assert(sched.schedule_provision('blarg', Dummy.new))
246
+ sched.run
247
+ sched.wait_for('blarg')
248
+ sched.stop
249
+ assert_includes(sched.vm.solved, "blarg")
250
+ sched.teardown
251
+ refute_includes(sched.vm.solved, "blarg")
252
+
253
+ dummy = StopFailDummy.new
254
+ assert(sched.schedule_provision('blarg', StopFailDummy.new))
255
+ sched.run
256
+ sched.wait_for('blarg')
257
+ sched.stop
258
+ assert_includes(sched.vm.solved, "blarg")
259
+ assert_raises(RuntimeError) { sched.teardown }
260
+ assert_includes(sched.vm.solved, "blarg")
261
+ sched.force_deprovision = true
262
+ sched.teardown
263
+ refute_includes(sched.vm.solved, "blarg")
264
+ end
265
+ end
266
+ end
267
+
@@ -0,0 +1,95 @@
1
+ #
2
+ # Just some small tests for the dummy provisioner to ensure it's sane. Since
3
+ # it's used heavily in the scheduler tests, it's important to ensure it itself
4
+ # works.
5
+ #
6
+ # That said, the dummy provisioner should not be used for anything "real" and
7
+ # this test is largely an exercise in ensuring the rest of the test suite is
8
+ # not lying to it. Nothing here gets tested that should be used outside of the
9
+ # test suite.
10
+ #
11
+
12
+ require 'helper'
13
+
14
+ class TestDummy < Furnish::TestCase
15
+ def test_defaults
16
+ dummy = Dummy.new
17
+ dummy.name = 'dummy_test'
18
+ assert(dummy.startup, 'startup returns true by default')
19
+ assert(dummy.shutdown, 'shutdown returns true by default')
20
+ assert_equal(['dummy_test'], dummy.report, 'report returns boxed name by default')
21
+
22
+ obj = Palsy::Object.new('dummy')
23
+ %w[startup shutdown report].each do |meth|
24
+ assert(obj["dummy_test-#{meth}"], "#{meth} persists a breadcrumb when run")
25
+ end
26
+ end
27
+
28
+ def test_order_check
29
+ machine_names = %w[one two three]
30
+ machine_names.each do |name|
31
+ dummy = Dummy.new
32
+ dummy.name = name
33
+ assert(dummy.startup)
34
+ end
35
+
36
+ dummy = Dummy.new
37
+ assert_kind_of(Furnish::Provisioner::Dummy, dummy)
38
+ assert_equal(machine_names, dummy.order.to_a, 'order was respected')
39
+
40
+ assert(dummy.order.clear)
41
+ assert_empty(dummy.order.to_a)
42
+
43
+ machine_names.each do |name|
44
+ dummy = Dummy.new
45
+ dummy.name = name
46
+ assert(dummy.shutdown)
47
+ end
48
+
49
+ dummy = Dummy.new
50
+ assert_kind_of(Furnish::Provisioner::Dummy, dummy)
51
+ assert_equal(machine_names, dummy.order.to_a, 'order was respected')
52
+ end
53
+
54
+ def test_call_order
55
+ dummies = Dummy.new, Dummy.new
56
+ dummies.each_with_index do |x, i|
57
+ x.name = "foo"
58
+ x.id = "foo#{i}"
59
+ assert(x.startup)
60
+ end
61
+
62
+ assert_equal(dummies.map(&:id), dummies.first.call_order.to_a)
63
+
64
+ dummies.first.call_order.clear
65
+
66
+ dummies.reverse.each do |x|
67
+ assert(x.startup)
68
+ end
69
+
70
+ assert_equal(dummies.reverse.map(&:id), dummies.first.call_order.to_a)
71
+ end
72
+
73
+ def test_marshal
74
+ dummy = Dummy.new
75
+ dummy.name = "dummy_marshal_test"
76
+ assert(dummy.startup)
77
+ assert(dummy.shutdown)
78
+ assert_equal([dummy.name], dummy.report)
79
+
80
+ obj = Palsy::Object.new('dummy')
81
+ %w[startup shutdown report].each do |meth|
82
+ assert(obj["dummy_marshal_test-#{meth}"], "#{meth} persists a breadcrumb when run")
83
+ end
84
+
85
+ str = Marshal.dump(dummy)
86
+ refute_empty(str, "dummy was dumped successfully")
87
+
88
+ newobj = Marshal.load(str)
89
+ assert_kind_of(Furnish::Provisioner::Dummy, newobj)
90
+ refute_equal(dummy.object_id, newobj.object_id)
91
+
92
+ assert_equal(obj, dummy.store, "palsy object persists")
93
+ assert_equal(obj, newobj.store, "palsy object persists")
94
+ end
95
+ end
@@ -0,0 +1,68 @@
1
+ require 'helper'
2
+ require 'tempfile'
3
+
4
+ class TestLogger < Furnish::TestCase
5
+ def setup
6
+ super
7
+ @logger_file = Tempfile.new('furnish_log')
8
+ @logger = Furnish::Logger.new(@logger_file)
9
+ end
10
+
11
+ def read_logfile
12
+ File.read(@logger_file.path)
13
+ end
14
+
15
+ def test_defaults
16
+ logger = Furnish::Logger.new
17
+ assert_equal($stderr, logger.io, "logger io obj is stderr by default")
18
+ assert_equal(0, logger.debug_level, "logger debug level is 0 by default")
19
+ end
20
+
21
+ def test_logger_behaves_like_io
22
+ @logger.puts "ohai"
23
+ assert_equal("ohai\n", read_logfile)
24
+ end
25
+
26
+ def test_if_debug
27
+ assert_equal(0, @logger.debug_level, "debug level is 0")
28
+ @logger.if_debug do
29
+ puts "foo"
30
+ end
31
+
32
+ assert_empty(read_logfile, "debug level is zero and if_debug defaults at 1")
33
+
34
+ @logger.debug_level = 1
35
+ @logger.if_debug do
36
+ puts "foo"
37
+ end
38
+
39
+ assert_equal("foo\n", read_logfile, "debug level is 1")
40
+
41
+ @logger.if_debug(2) do
42
+ puts "bar"
43
+ end
44
+
45
+ assert_equal("foo\n", read_logfile, "debug level is 1 and if_debug is 2")
46
+
47
+ else_block = proc { puts "quux" }
48
+
49
+ @logger.if_debug(2, else_block) do
50
+ puts "should_not_get_here"
51
+ end
52
+
53
+ assert_equal("foo\nquux\n", read_logfile, "debug level is 1 and else block triggered")
54
+
55
+ @logger.debug_level = 2
56
+ @logger.if_debug(1) do
57
+ puts "level2"
58
+ end
59
+
60
+ assert_equal("foo\nquux\nlevel2\n", read_logfile, "debug level is 2 and if_debug checking for 1")
61
+ end
62
+
63
+ def teardown
64
+ @logger.close
65
+ @logger_file.unlink
66
+ super
67
+ end
68
+ end
@@ -0,0 +1,51 @@
1
+ require 'helper'
2
+
3
+ class TestProvisionerGroup < Furnish::TestCase
4
+ def test_constructor
5
+ dummy = Dummy.new
6
+ pg = Furnish::ProvisionerGroup.new(dummy, 'blarg')
7
+ assert_includes(pg, dummy)
8
+ assert_equal('blarg', pg.name)
9
+ assert_kind_of(Set, pg.dependencies)
10
+ assert_empty(pg.dependencies)
11
+ assert_equal('blarg', dummy.name)
12
+
13
+ dummy = Dummy.new
14
+ pg = Furnish::ProvisionerGroup.new([dummy], 'blarg2', %w[blarg])
15
+ assert_includes(pg, dummy)
16
+ assert_equal('blarg2', pg.name)
17
+ assert_equal(Set['blarg'], pg.dependencies)
18
+ assert_equal('blarg2', dummy.name)
19
+ end
20
+
21
+ def test_up_down
22
+ store = Palsy::Object.new('dummy')
23
+ dummy = Dummy.new
24
+ pg = Furnish::ProvisionerGroup.new(dummy, 'blarg')
25
+
26
+ assert(pg.startup, 'started')
27
+ assert(store[ [pg.name, 'startup'].join("-") ], 'startup ran')
28
+ assert(pg.shutdown, 'stopped')
29
+ assert(store[ [pg.name, 'startup'].join("-") ], 'shutdown ran')
30
+
31
+ dummy = StartFailDummy.new
32
+ pg = Furnish::ProvisionerGroup.new(dummy, 'blarg2')
33
+ assert_raises(RuntimeError, "Could not provision #{pg.name} with provisioner #{dummy.class.name}") { pg.startup }
34
+
35
+ dummy = StopFailDummy.new
36
+ pg = Furnish::ProvisionerGroup.new(dummy, 'blarg3')
37
+ assert_raises(RuntimeError, "Could not deprovision #{pg.name}/#{dummy.class.name}") { pg.shutdown }
38
+ pg.shutdown(true)
39
+
40
+ dummy = StartExceptionDummy.new
41
+ pg = Furnish::ProvisionerGroup.new(dummy, 'blarg4')
42
+ assert_raises(RuntimeError, "Could not provision #{pg.name} with provisioner #{dummy.class.name}") { pg.startup }
43
+
44
+ dummy = StopExceptionDummy.new
45
+ pg = Furnish::ProvisionerGroup.new(dummy, 'blarg4')
46
+ assert_raises(RuntimeError, "Could not deprovision #{pg.name}/#{dummy.class.name}") { pg.shutdown }
47
+ pg.shutdown(true)
48
+ sleep 0.1 # wait for flush
49
+ assert_match(%r!Deprovision #{dummy.class.name}/#{pg.name} had errors:!, File.binread(Furnish.logger.path))
50
+ end
51
+ end
@@ -0,0 +1,33 @@
1
+ require 'helper'
2
+
3
+ class TestSchedulerBasic < Furnish::SchedulerTestCase
4
+ def test_schedule_provision
5
+ sched = Furnish::Scheduler.new
6
+
7
+ assert(sched.schedule_provision('blarg', [Dummy.new]), 'we can schedule')
8
+ assert_includes(sched.vm.waiters.keys, 'blarg', 'exists in the waiters')
9
+ assert_includes(sched.vm.groups.keys, 'blarg', 'exists in the vm group set')
10
+ assert_equal(1, sched.vm.groups['blarg'].count, 'one item array')
11
+ assert_kind_of(Furnish::Provisioner::Dummy, sched.vm.groups['blarg'].first, 'first object is our dummy object')
12
+ assert_equal('blarg', sched.vm.groups['blarg'].first.name, 'name is set properly')
13
+ assert_nil(sched.schedule_provision('blarg', [Dummy.new]), 'does not schedule twice')
14
+
15
+ assert(sched.schedule_provision('blarg2', Dummy.new), 'scheduling does not need an array')
16
+ assert_includes(sched.vm.waiters.keys, 'blarg2', 'exists in the waiters')
17
+ assert_includes(sched.vm.groups.keys, 'blarg2', 'exists in the vm group set')
18
+ assert_kind_of(Furnish::ProvisionerGroup, sched.vm.groups['blarg2'], 'boxes our single item')
19
+ assert_kind_of(Furnish::Provisioner::Dummy, sched.vm.groups['blarg2'].first, 'first object is our dummy object')
20
+ assert_equal('blarg2', sched.vm.groups['blarg2'].first.name, 'name is set properly')
21
+
22
+ assert_raises(
23
+ RuntimeError,
24
+ "One of your dependencies for blarg3 has not been pre-declared. Cannot continue"
25
+ ) do
26
+ sched.schedule_provision('blarg3', Dummy.new, %w[frobnik])
27
+ end
28
+
29
+ assert(sched.schedule_provision('blarg4', Dummy.new, %w[blarg2]), 'scheduled with a dependency')
30
+ assert_includes(sched.vm.waiters.keys, 'blarg4', 'included in waiters list')
31
+ assert_includes(sched.vm.dependencies['blarg4'], 'blarg2', 'dependencies are tracked for provision')
32
+ end
33
+ end
@@ -0,0 +1,16 @@
1
+ require 'helper'
2
+
3
+ class TestSchedulerSerial < Furnish::RunningSchedulerTestCase
4
+ def setup
5
+ super
6
+ sched.serial = true
7
+ end
8
+
9
+ def test_threading_constructs
10
+ assert(sched.schedule_provision('blarg', Dummy.new))
11
+ sched.run
12
+ refute(sched.running?)
13
+ assert_nil(sched.wait_for('blarg'))
14
+ sched.stop # does not explode
15
+ end
16
+ end
@@ -0,0 +1,44 @@
1
+ require 'helper'
2
+
3
+ class SleepyDummy < Dummy
4
+ def startup(*args)
5
+ sleep 1
6
+ super
7
+ end
8
+ end
9
+
10
+ class SleepyFailingDummy < SleepyDummy
11
+ def startup(*args)
12
+ super
13
+ return false
14
+ end
15
+ end
16
+
17
+ class TestSchedulerThreaded < Furnish::RunningSchedulerTestCase
18
+ def setup
19
+ super
20
+ sched.serial = false
21
+ end
22
+
23
+ def test_running
24
+ assert(sched.schedule_provision('blarg', SleepyDummy.new))
25
+ sched.run
26
+ assert(sched.running?, 'running after provision')
27
+ sched.teardown
28
+ refute(sched.running?, 'not running after teardown')
29
+
30
+ # we have a monitor that's waiting for timeouts in the test suite to abort
31
+ # it if the scheduler crashes.
32
+ #
33
+ # this actually tests that functionality, so kill the monitor prematurely.
34
+ #
35
+ @monitor.kill rescue nil
36
+ assert(sched.schedule_provision('blarg', SleepyFailingDummy.new))
37
+ sched.run
38
+ assert(sched.running?, 'running after provision')
39
+ sleep 3
40
+ assert_raises(RuntimeError, "Could not provision blarg with provisioner SleepyFailingDummy") { sched.running? }
41
+ sched.teardown
42
+ refute(sched.running?, 'not running after teardown')
43
+ end
44
+ end
data/test/test_vm.rb ADDED
@@ -0,0 +1,18 @@
1
+ require 'helper'
2
+
3
+ class TestVM < Furnish::TestCase
4
+ def test_initialize
5
+ vm = Furnish::VM.new
6
+ kinds = {
7
+ :groups => Palsy::Map,
8
+ :dependencies => Palsy::Map,
9
+ :solved => Palsy::Set,
10
+ :working => Palsy::Set,
11
+ :waiters => Palsy::Set,
12
+ }
13
+
14
+ kinds.each do |key, klass|
15
+ assert_kind_of(klass, vm.send(key))
16
+ end
17
+ end
18
+ end