tenderloin 0.2.0

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