furnish 0.0.1

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