tenderloin 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/LICENCE +21 -0
  2. data/README.md +50 -0
  3. data/Version +1 -0
  4. data/bin/loin +5 -0
  5. data/config/default.rb +26 -0
  6. data/lib/tenderloin/actions/base.rb +93 -0
  7. data/lib/tenderloin/actions/box/add.rb +22 -0
  8. data/lib/tenderloin/actions/box/destroy.rb +14 -0
  9. data/lib/tenderloin/actions/box/download.rb +63 -0
  10. data/lib/tenderloin/actions/box/unpackage.rb +46 -0
  11. data/lib/tenderloin/actions/runner.rb +138 -0
  12. data/lib/tenderloin/actions/vm/boot.rb +52 -0
  13. data/lib/tenderloin/actions/vm/destroy.rb +18 -0
  14. data/lib/tenderloin/actions/vm/halt.rb +14 -0
  15. data/lib/tenderloin/actions/vm/import.rb +32 -0
  16. data/lib/tenderloin/actions/vm/move_hard_drive.rb +53 -0
  17. data/lib/tenderloin/actions/vm/provision.rb +71 -0
  18. data/lib/tenderloin/actions/vm/reload.rb +17 -0
  19. data/lib/tenderloin/actions/vm/shared_folders.rb +47 -0
  20. data/lib/tenderloin/actions/vm/start.rb +17 -0
  21. data/lib/tenderloin/actions/vm/up.rb +55 -0
  22. data/lib/tenderloin/box.rb +143 -0
  23. data/lib/tenderloin/busy.rb +73 -0
  24. data/lib/tenderloin/cli.rb +59 -0
  25. data/lib/tenderloin/commands.rb +154 -0
  26. data/lib/tenderloin/config.rb +144 -0
  27. data/lib/tenderloin/downloaders/base.rb +13 -0
  28. data/lib/tenderloin/downloaders/file.rb +21 -0
  29. data/lib/tenderloin/downloaders/http.rb +47 -0
  30. data/lib/tenderloin/env.rb +156 -0
  31. data/lib/tenderloin/fusion_vm.rb +85 -0
  32. data/lib/tenderloin/ssh.rb +49 -0
  33. data/lib/tenderloin/util.rb +51 -0
  34. data/lib/tenderloin/vm.rb +63 -0
  35. data/lib/tenderloin/vmx_file.rb +28 -0
  36. data/lib/tenderloin.rb +14 -0
  37. data/script/tenderloin-ssh-expect.sh +23 -0
  38. data/templates/Tenderfile +8 -0
  39. data/test/tenderloin/actions/base_test.rb +32 -0
  40. data/test/tenderloin/actions/box/add_test.rb +37 -0
  41. data/test/tenderloin/actions/box/destroy_test.rb +18 -0
  42. data/test/tenderloin/actions/box/download_test.rb +118 -0
  43. data/test/tenderloin/actions/box/unpackage_test.rb +100 -0
  44. data/test/tenderloin/actions/runner_test.rb +262 -0
  45. data/test/tenderloin/actions/vm/boot_test.rb +55 -0
  46. data/test/tenderloin/actions/vm/destroy_test.rb +24 -0
  47. data/test/tenderloin/actions/vm/down_test.rb +32 -0
  48. data/test/tenderloin/actions/vm/export_test.rb +88 -0
  49. data/test/tenderloin/actions/vm/forward_ports_test.rb +50 -0
  50. data/test/tenderloin/actions/vm/halt_test.rb +27 -0
  51. data/test/tenderloin/actions/vm/import_test.rb +36 -0
  52. data/test/tenderloin/actions/vm/move_hard_drive_test.rb +108 -0
  53. data/test/tenderloin/actions/vm/package_test.rb +181 -0
  54. data/test/tenderloin/actions/vm/provision_test.rb +103 -0
  55. data/test/tenderloin/actions/vm/reload_test.rb +44 -0
  56. data/test/tenderloin/actions/vm/resume_test.rb +27 -0
  57. data/test/tenderloin/actions/vm/shared_folders_test.rb +117 -0
  58. data/test/tenderloin/actions/vm/start_test.rb +28 -0
  59. data/test/tenderloin/actions/vm/suspend_test.rb +27 -0
  60. data/test/tenderloin/actions/vm/up_test.rb +98 -0
  61. data/test/tenderloin/box_test.rb +139 -0
  62. data/test/tenderloin/busy_test.rb +83 -0
  63. data/test/tenderloin/commands_test.rb +269 -0
  64. data/test/tenderloin/config_test.rb +123 -0
  65. data/test/tenderloin/downloaders/base_test.rb +20 -0
  66. data/test/tenderloin/downloaders/file_test.rb +32 -0
  67. data/test/tenderloin/downloaders/http_test.rb +40 -0
  68. data/test/tenderloin/env_test.rb +345 -0
  69. data/test/tenderloin/ssh_test.rb +103 -0
  70. data/test/tenderloin/util_test.rb +64 -0
  71. data/test/tenderloin/vm_test.rb +89 -0
  72. data/test/test_helper.rb +92 -0
  73. metadata +241 -0
@@ -0,0 +1,117 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
2
+
3
+ class SharedFoldersActionTest < Test::Unit::TestCase
4
+ setup do
5
+ @mock_vm, @vm, @action = mock_action(Tenderloin::Actions::VM::SharedFolders)
6
+ mock_config
7
+ end
8
+
9
+ def stub_shared_folders
10
+ folders = [%w{foo from to}, %w{bar bfrom bto}]
11
+ @action.expects(:shared_folders).returns(folders)
12
+ folders
13
+ end
14
+
15
+ context "collecting shared folders" do
16
+ should "return the arrays that the callback returns" do
17
+ result = [[1,2,3],[4,5,6]]
18
+ @mock_vm.expects(:invoke_callback).with(:collect_shared_folders).once.returns(result)
19
+ assert_equal result, @action.shared_folders
20
+ end
21
+
22
+ should "filter out invalid results" do
23
+ result = [[1,2,3],[4,5]]
24
+ @mock_vm.expects(:invoke_callback).with(:collect_shared_folders).once.returns(result)
25
+ assert_equal [[1,2,3]], @action.shared_folders
26
+ end
27
+ end
28
+
29
+ context "setting up shared folder metadata" do
30
+ setup do
31
+ @folders = stub_shared_folders
32
+ end
33
+
34
+ should "add all shared folders to the VM" do
35
+ share_seq = sequence("share_seq")
36
+ shared_folders = mock("shared_folders")
37
+ shared_folders.expects(:<<).in_sequence(share_seq).with() { |sf| sf.name == "foo" && sf.hostpath == "from" }
38
+ shared_folders.expects(:<<).in_sequence(share_seq).with() { |sf| sf.name == "bar" && sf.hostpath == "bfrom" }
39
+ @vm.stubs(:shared_folders).returns(shared_folders)
40
+ @vm.expects(:save).with(true).once
41
+
42
+ @action.before_boot
43
+ end
44
+ end
45
+
46
+ context "mounting the shared folders" do
47
+ setup do
48
+ @folders = stub_shared_folders
49
+ end
50
+
51
+ should "mount all shared folders to the VM" do
52
+ mount_seq = sequence("mount_seq")
53
+ ssh = mock("ssh")
54
+ @folders.each do |name, hostpath, guestpath|
55
+ ssh.expects(:exec!).with("sudo mkdir -p #{guestpath}").in_sequence(mount_seq)
56
+ @action.expects(:mount_folder).with(ssh, name, guestpath).in_sequence(mount_seq)
57
+ ssh.expects(:exec!).with("sudo chown #{Tenderloin.config.ssh.username} #{guestpath}").in_sequence(mount_seq)
58
+ end
59
+ Tenderloin::SSH.expects(:execute).yields(ssh)
60
+
61
+ @action.after_boot
62
+ end
63
+ end
64
+
65
+ context "mounting the main folder" do
66
+ setup do
67
+ @ssh = mock("ssh")
68
+ @name = "foo"
69
+ @guestpath = "bar"
70
+ @sleeptime = 0
71
+ @limit = 10
72
+
73
+ @success_return = false
74
+ end
75
+
76
+ def mount_folder
77
+ @action.mount_folder(@ssh, @name, @guestpath, @sleeptime)
78
+ end
79
+
80
+ should "execute the proper mount command" do
81
+ @ssh.expects(:exec!).with("sudo mount -t vboxsf #{@name} #{@guestpath}").returns(@success_return)
82
+ mount_folder
83
+ end
84
+
85
+ should "test type of text and text string to detect error" do
86
+ data = mock("data")
87
+ data.expects(:[]=).with(:result, !@success_return)
88
+
89
+ @ssh.expects(:exec!).yields(data, :stderr, "No such device").returns(@success_return)
90
+ mount_folder
91
+ end
92
+
93
+ should "test type of text and test string to detect success" do
94
+ data = mock("data")
95
+ data.expects(:[]=).with(:result, @success_return)
96
+
97
+ @ssh.expects(:exec!).yields(data, :stdout, "Nothing such device").returns(@success_return)
98
+ mount_folder
99
+ end
100
+
101
+ should "raise an ActionException if the command fails constantly" do
102
+ @ssh.expects(:exec!).times(@limit).returns(!@success_return)
103
+
104
+ assert_raises(Tenderloin::Actions::ActionException) {
105
+ mount_folder
106
+ }
107
+ end
108
+
109
+ should "not raise any exception if the command succeeded" do
110
+ @ssh.expects(:exec!).once.returns(@success_return)
111
+
112
+ assert_nothing_raised {
113
+ mount_folder
114
+ }
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
2
+
3
+ class StartActionTest < Test::Unit::TestCase
4
+ setup do
5
+ @mock_vm, @vm, @action = mock_action(Tenderloin::Actions::VM::Start)
6
+ mock_config
7
+ end
8
+
9
+ context "sub-actions" do
10
+ setup do
11
+ File.stubs(:file?).returns(true)
12
+ File.stubs(:exist?).returns(true)
13
+ @default_order = [Tenderloin::Actions::VM::ForwardPorts, Tenderloin::Actions::VM::SharedFolders, Tenderloin::Actions::VM::Boot]
14
+ end
15
+
16
+ def setup_action_expectations
17
+ default_seq = sequence("default_seq")
18
+ @default_order.each do |action|
19
+ @mock_vm.expects(:add_action).with(action).once.in_sequence(default_seq)
20
+ end
21
+ end
22
+
23
+ should "do the proper actions by default" do
24
+ setup_action_expectations
25
+ @action.prepare
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
2
+
3
+ class SuspendActionTest < Test::Unit::TestCase
4
+ setup do
5
+ @runner, @vm, @action = mock_action(Tenderloin::Actions::VM::Suspend)
6
+ mock_config
7
+ end
8
+
9
+ context "executing" do
10
+ setup do
11
+ @vm.stubs(:running?).returns(true)
12
+ end
13
+
14
+ should "save the state of the VM" do
15
+ @vm.expects(:save_state).with(true).once
16
+ @action.execute!
17
+ end
18
+
19
+ should "raise an ActionException if the VM is not running" do
20
+ @vm.expects(:running?).returns(false)
21
+ @vm.expects(:save_state).never
22
+ assert_raises(Tenderloin::Actions::ActionException) {
23
+ @action.execute!
24
+ }
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,98 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper')
2
+
3
+ class UpActionTest < Test::Unit::TestCase
4
+ setup do
5
+ @mock_vm, @vm, @action = mock_action(Tenderloin::Actions::VM::Up)
6
+ mock_config
7
+ end
8
+
9
+ context "sub-actions" do
10
+ setup do
11
+ File.stubs(:file?).returns(true)
12
+ File.stubs(:exist?).returns(true)
13
+ @default_order = [Tenderloin::Actions::VM::Import, Tenderloin::Actions::VM::ForwardPorts, Tenderloin::Actions::VM::SharedFolders, Tenderloin::Actions::VM::Boot]
14
+ end
15
+
16
+ def setup_action_expectations
17
+ default_seq = sequence("default_seq")
18
+ @default_order.each do |action|
19
+ @mock_vm.expects(:add_action).with(action).once.in_sequence(default_seq)
20
+ end
21
+ end
22
+
23
+ should "raise an ActionException if a dotfile exists but is not a file" do
24
+ File.expects(:file?).with(Tenderloin::Env.dotfile_path).returns(false)
25
+ assert_raises(Tenderloin::Actions::ActionException) {
26
+ @action.prepare
27
+ }
28
+ end
29
+
30
+ should "not raise an ActionException if dotfile doesn't exist" do
31
+ setup_action_expectations
32
+ File.stubs(:exist?).returns(false)
33
+ assert_nothing_raised { @action.prepare }
34
+ end
35
+
36
+ should "not raise an ActionException if dotfile exists but is a file" do
37
+ File.stubs(:file?).returns(true)
38
+ File.stubs(:exist?).returns(true)
39
+ setup_action_expectations
40
+ assert_nothing_raised { @action.prepare }
41
+ end
42
+
43
+ should "do the proper actions by default" do
44
+ setup_action_expectations
45
+ @action.prepare
46
+ end
47
+
48
+ should "add in the provisioning step if enabled" do
49
+ mock_config do |config|
50
+ config.chef.enabled = true
51
+ end
52
+
53
+ @default_order.push(Tenderloin::Actions::VM::Provision)
54
+ setup_action_expectations
55
+ @action.prepare
56
+ end
57
+
58
+ should "add in the action to move hard drive if config is set" do
59
+ mock_config do |config|
60
+ File.expects(:directory?).with("foo").returns(true)
61
+ config.vm.hd_location = "foo"
62
+ end
63
+
64
+ @default_order.insert(0, Tenderloin::Actions::VM::MoveHardDrive)
65
+ setup_action_expectations
66
+ @action.prepare
67
+ end
68
+ end
69
+
70
+ context "callbacks" do
71
+ should "call persist and mac address setup after import" do
72
+ boot_seq = sequence("boot")
73
+ @action.expects(:persist).once.in_sequence(boot_seq)
74
+ @action.expects(:setup_mac_address).once.in_sequence(boot_seq)
75
+ @action.after_import
76
+ end
77
+ end
78
+
79
+ context "persisting" do
80
+ should "persist the VM with Env" do
81
+ @vm.stubs(:uuid)
82
+ Tenderloin::Env.expects(:persist_vm).with(@vm).once
83
+ @action.persist
84
+ end
85
+ end
86
+
87
+ context "setting up MAC address" do
88
+ should "match the mac address with the base" do
89
+ nic = mock("nic")
90
+ nic.expects(:macaddress=).once
91
+
92
+ @vm.expects(:nics).returns([nic]).once
93
+ @vm.expects(:save).with(true).once
94
+
95
+ @action.setup_mac_address
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,139 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class BoxTest < Test::Unit::TestCase
4
+ context "class methods" do
5
+ context "listing all boxes" do
6
+ setup do
7
+ Dir.stubs(:open)
8
+ File.stubs(:directory?).returns(true)
9
+
10
+ @boxes_path = "foo"
11
+ Tenderloin::Env.stubs(:boxes_path).returns(@boxes_path)
12
+ end
13
+
14
+ should "open the boxes directory" do
15
+ Dir.expects(:open).with(Tenderloin::Env.boxes_path)
16
+ Tenderloin::Box.all
17
+ end
18
+
19
+ should "return an array" do
20
+ result = Tenderloin::Box.all
21
+ assert result.is_a?(Array)
22
+ end
23
+
24
+ should "not return the '.' and '..' directories" do
25
+ dir = [".", "..", "..", ".", ".."]
26
+ Dir.expects(:open).yields(dir)
27
+ result = Tenderloin::Box.all
28
+ assert result.empty?
29
+ end
30
+
31
+ should "return the other directories" do
32
+ dir = [".", "foo", "bar", "baz"]
33
+ Dir.expects(:open).yields(dir)
34
+ result = Tenderloin::Box.all
35
+ assert_equal ["foo", "bar", "baz"], result
36
+ end
37
+
38
+ should "ignore the files" do
39
+ dir = ["foo", "bar"]
40
+ files = [true, false]
41
+ Dir.expects(:open).yields(dir)
42
+ dir_sequence = sequence("directory")
43
+ dir.each_with_index do |dir, index|
44
+ File.expects(:directory?).with(File.join(@boxes_path, dir)).returns(files[index]).in_sequence(dir_sequence)
45
+ end
46
+
47
+ result = Tenderloin::Box.all
48
+ assert_equal ["foo"], result
49
+ end
50
+ end
51
+
52
+ context "finding" do
53
+ setup do
54
+ @dir = "foo"
55
+ @name = "bar"
56
+ Tenderloin::Box.stubs(:directory).with(@name).returns(@dir)
57
+ end
58
+
59
+ should "return nil if the box doesn't exist" do
60
+ File.expects(:directory?).with(@dir).once.returns(false)
61
+ assert_nil Tenderloin::Box.find(@name)
62
+ end
63
+
64
+ should "return a box object with the proper name set" do
65
+ File.expects(:directory?).with(@dir).once.returns(true)
66
+ result = Tenderloin::Box.find(@name)
67
+ assert result
68
+ assert_equal @name, result.name
69
+ end
70
+ end
71
+
72
+ context "adding" do
73
+ setup do
74
+ @name = "foo"
75
+ @uri = "bar"
76
+ end
77
+
78
+ should "create a new instance, set the variables, and add it" do
79
+ box = mock("box")
80
+ box.expects(:name=).with(@name)
81
+ box.expects(:uri=).with(@uri)
82
+ box.expects(:add).once
83
+ Tenderloin::Box.expects(:new).returns(box)
84
+ Tenderloin::Box.add(@name, @uri)
85
+ end
86
+ end
87
+
88
+ context "box directory" do
89
+ setup do
90
+ @name = "foo"
91
+ @box_dir = File.join(Tenderloin::Env.boxes_path, @name)
92
+ end
93
+
94
+ should "return the boxes_path joined with the name" do
95
+ assert_equal @box_dir, Tenderloin::Box.directory(@name)
96
+ end
97
+ end
98
+ end
99
+
100
+ context "instance methods" do
101
+ setup do
102
+ @box = Tenderloin::Box.new
103
+ end
104
+
105
+ should "execute the Add action when add is called" do
106
+ @box.expects(:execute!).with(Tenderloin::Actions::Box::Add).once
107
+ @box.add
108
+ end
109
+
110
+ context "box directory" do
111
+ setup do
112
+ @box.name = "foo"
113
+ end
114
+
115
+ should "return the boxes_path joined with the name" do
116
+ result = mock("object")
117
+ Tenderloin::Box.expects(:directory).with(@box.name).returns(result)
118
+ assert result.equal?(@box.directory)
119
+ end
120
+ end
121
+
122
+ context "destroying" do
123
+ should "execute the destroy action" do
124
+ @box.expects(:execute!).with(Tenderloin::Actions::Box::Destroy).once
125
+ @box.destroy
126
+ end
127
+ end
128
+
129
+ context "ovf file" do
130
+ setup do
131
+ @box.stubs(:directory).returns("foo")
132
+ end
133
+
134
+ should "be the directory joined with the config ovf file" do
135
+ assert_equal File.join(@box.directory, Tenderloin.config.vm.box_ovf), @box.ovf_file
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,83 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'test_helper')
2
+
3
+ class BusyTest < Test::Unit::TestCase
4
+ context "waiting for not busy" do
5
+ setup do
6
+ Tenderloin::Busy.reset_trap_thread!
7
+ end
8
+
9
+ should "run in a thread" do
10
+ Thread.expects(:new).once.returns(nil)
11
+ Tenderloin::Busy.wait_for_not_busy
12
+ end
13
+
14
+ should "not start a thread multiple times" do
15
+ Thread.expects(:new).once.returns("foo")
16
+ Tenderloin::Busy.wait_for_not_busy
17
+ end
18
+ end
19
+
20
+ context "during an action in a busy block" do
21
+ should "report as busy" do
22
+ Tenderloin.busy do
23
+ # Inside the block Tenderloin.busy? should be true
24
+ assert Tenderloin.busy?
25
+ end
26
+
27
+ #After the block finishes Tenderloin.busy? should be false
28
+ assert !Tenderloin.busy?
29
+ end
30
+
31
+ should "set busy to false upon exception and reraise the error" do
32
+ assert_raise Exception do
33
+ Tenderloin.busy do
34
+ assert Tenderloin.busy?
35
+ raise Exception
36
+ end
37
+ end
38
+
39
+ assert !Tenderloin.busy?
40
+ end
41
+
42
+ should "complete the trap thread even if an exception occurs" do
43
+ trap_thread = mock("trap_thread")
44
+ trap_thread.expects(:join).once
45
+ Tenderloin::Busy.stubs(:trap_thread).returns(trap_thread)
46
+
47
+ assert_raise Exception do
48
+ Tenderloin.busy do
49
+ raise Exception
50
+ end
51
+ end
52
+ end
53
+
54
+ should "report busy to the outside world regardless of thread" do
55
+ Thread.new do
56
+ Tenderloin.busy do
57
+ sleep(2)
58
+ end
59
+ end
60
+ # Give the thread time to start
61
+ sleep(1)
62
+
63
+ # While the above thread is executing tenderloin should be busy
64
+ assert Tenderloin.busy?
65
+ end
66
+
67
+ should "run the action in a new thread" do
68
+ runner_thread = nil
69
+ Tenderloin.busy do
70
+ runner_thread = Thread.current
71
+ end
72
+
73
+ assert_not_equal Thread.current, runner_thread
74
+ end
75
+
76
+ should "trap INT" do
77
+ trap_seq = sequence("trap_seq")
78
+ Signal.expects(:trap).with("INT", anything).once.in_sequence(trap_seq)
79
+ Signal.expects(:trap).with("INT", "DEFAULT").once.in_sequence(trap_seq)
80
+ Tenderloin.busy do; end
81
+ end
82
+ end
83
+ end