virtual_box 0.1.2 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/README.markdown +30 -0
- data/VERSION +1 -1
- data/lib/virtual_box/error.rb +1 -1
- data/lib/virtual_box/net.rb +36 -19
- data/lib/virtual_box/net/dhcp.rb +2 -2
- data/lib/virtual_box/vm.rb +34 -10
- data/lib/virtual_box/vm/disk.rb +10 -0
- data/test/helper.rb +3 -0
- data/test/virtual_box/integration_test.rb +2 -6
- data/test/virtual_box/net_test.rb +13 -13
- data/test/virtual_box/vm/disk_test.rb +8 -1
- data/test/virtual_box/vm_test.rb +27 -3
- data/virtual_box.gemspec +3 -3
- metadata +4 -4
data/Gemfile.lock
CHANGED
data/README.markdown
CHANGED
@@ -4,6 +4,36 @@ This gem is a Ruby API for VirtualBox, Sun's open-source virtualization software
|
|
4
4
|
that supports Linux, Mac OS X, and Windows.
|
5
5
|
|
6
6
|
|
7
|
+
## Usage
|
8
|
+
|
9
|
+
virtual_box is mainly intended to help out with testing. The snippet below shows
|
10
|
+
a complete setup / teardown scenario.
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
setup do
|
14
|
+
vdi_file = '/tmp/disk.vdi'
|
15
|
+
@disk = VirtualBox::Vm::Disk.create :file => vdi_file,
|
16
|
+
:size => 16 * 1024 * 1024
|
17
|
+
iso_file = 'test/fixtures/tinycore/remix.iso'
|
18
|
+
@net = VirtualBox::Net.new(:ip => '192.168.66.6', :netmask => '255.255.255.0',
|
19
|
+
:dhcp => { :start_ip => '192.168.66.66' }).add
|
20
|
+
|
21
|
+
@vm = VirtualBox::Vm.new(
|
22
|
+
:board => { :ram => 256, :video_ram => 16, :os => :linux26 },
|
23
|
+
:io_buses => [{ :bus => :ide,
|
24
|
+
:disks => [{ :file => iso_file, :port => 1 }] },
|
25
|
+
{ :bus => :sata, :disks => [@disk] }],
|
26
|
+
:nics => [{ :mode => :host, :chip => :virtual,
|
27
|
+
:net_name => @net.name }]).register
|
28
|
+
end
|
29
|
+
|
30
|
+
teardown do
|
31
|
+
@vm.unregister
|
32
|
+
@net.remove
|
33
|
+
@disk.drop
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
7
37
|
## Features
|
8
38
|
|
9
39
|
Currently, the gem supports the following features:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.4
|
data/lib/virtual_box/error.rb
CHANGED
data/lib/virtual_box/net.rb
CHANGED
@@ -16,15 +16,17 @@ class Net
|
|
16
16
|
# attribute is read-only, and it is automatically set when the network is
|
17
17
|
# registered with VirtualBox.
|
18
18
|
# @return [String]
|
19
|
-
attr_reader :
|
19
|
+
attr_reader :name
|
20
20
|
|
21
21
|
# The name of the VirtualBox internal network.
|
22
22
|
#
|
23
|
+
# This is most likely not useful outside the VirtualBox API.
|
24
|
+
#
|
23
25
|
# VirtualBox's CLI does not provide a way to set the internal network name.
|
24
26
|
# Therefore, this attribute is read-only, and it is automatically set when the
|
25
27
|
# network is registered with VirtualBox.
|
26
28
|
# @return [String]
|
27
|
-
attr_reader :
|
29
|
+
attr_reader :vbox_name
|
28
30
|
|
29
31
|
# The MAC address of the host's virtual NIC that's connected to this network.
|
30
32
|
#
|
@@ -69,8 +71,8 @@ class Net
|
|
69
71
|
# True if this virtual network has been added to VirtualBox.
|
70
72
|
# @return [Boolean] true if this network exists, false otherwise
|
71
73
|
def live?
|
72
|
-
networks = self.class.all
|
73
|
-
network = networks.find { |net| net.
|
74
|
+
networks = self.class.all false
|
75
|
+
network = networks.find { |net| net.name == name }
|
74
76
|
network ? true : false
|
75
77
|
end
|
76
78
|
|
@@ -78,7 +80,7 @@ class Net
|
|
78
80
|
#
|
79
81
|
# @return [VirtualBox::Net] self, for easy call chaining
|
80
82
|
def add
|
81
|
-
unless
|
83
|
+
unless name.nil?
|
82
84
|
raise "Virtual network already added to VirtualBox"
|
83
85
|
end
|
84
86
|
|
@@ -88,17 +90,16 @@ class Net
|
|
88
90
|
unless match = /^interface\s+'(.*)'\s+.*created/i.match(output)
|
89
91
|
raise "VirtualBox output does not include interface name"
|
90
92
|
end
|
91
|
-
@
|
93
|
+
@name = match[1]
|
92
94
|
|
93
|
-
#
|
94
|
-
|
95
|
-
|
96
|
-
@name = network.name
|
95
|
+
# Query VirtualBox to pull the rest of the information.
|
96
|
+
network = self.class.named name
|
97
|
+
@vbox_name = network.vbox_name
|
97
98
|
@mac = network.mac
|
98
99
|
|
99
100
|
if (ip && ip != network.ip) || (netmask && netmask != network.netmask)
|
100
101
|
VirtualBox.run_command! ['VBoxManage', '--nologo', 'hostonlyif',
|
101
|
-
'ipconfig',
|
102
|
+
'ipconfig', name, '--ip', ip, '--netmask', netmask]
|
102
103
|
else
|
103
104
|
self.ip = network.ip
|
104
105
|
self.netmask = network.netmask
|
@@ -114,28 +115,44 @@ class Net
|
|
114
115
|
#
|
115
116
|
# @return [VirtualBox::Net] self, for easy call chaining
|
116
117
|
def remove
|
117
|
-
unless
|
118
|
+
unless name.nil?
|
118
119
|
dhcp.remove self if dhcp
|
119
|
-
VirtualBox.run_command ['VBoxManage', 'hostonlyif', 'remove',
|
120
|
+
VirtualBox.run_command ['VBoxManage', 'hostonlyif', 'remove', name]
|
120
121
|
end
|
121
122
|
self
|
122
123
|
end
|
123
124
|
|
124
125
|
# The virtual networks added to VirtualBox.
|
125
126
|
#
|
127
|
+
# @param [Boolean] with_dhcp if false, the returned VirtualBox::Net instances
|
128
|
+
# will have their dhcp property set to nil, even if they have DHCP
|
129
|
+
# servers; this saves a CLI call when DHCP information is not needed
|
126
130
|
# @return [Array<VirtualBox::Net>] all the DHCP servers added to VirtualBox
|
127
|
-
def self.all
|
128
|
-
dhcps = VirtualBox::Net::Dhcp.all
|
131
|
+
def self.all(with_dhcp = true)
|
132
|
+
dhcps = with_dhcp ? VirtualBox::Net::Dhcp.all : {}
|
129
133
|
|
130
134
|
output = VirtualBox.run_command! ['VBoxManage', '--nologo', 'list',
|
131
135
|
'--long', 'hostonlyifs']
|
132
136
|
output.split("\n\n").map do |net_info|
|
133
|
-
net = new.from_net_info
|
134
|
-
net.dhcp = dhcps[net.
|
137
|
+
net = new.from_net_info net_info
|
138
|
+
net.dhcp = dhcps[net.vbox_name]
|
135
139
|
net
|
136
140
|
end
|
137
141
|
end
|
138
142
|
|
143
|
+
# The virtual network added to VirtualBox with a given name.
|
144
|
+
#
|
145
|
+
# This is a convenience for calling find on Net.all, so it's just as
|
146
|
+
# inefficient.
|
147
|
+
# @param [String] name the name to look for
|
148
|
+
# @param [Boolean] with_dhcp if false, the returned VirtualBox::Net instance
|
149
|
+
# will have its dhcp property set to nil, even if it has a DHCP
|
150
|
+
# server; this saves a CLI call when DHCP information is not needed
|
151
|
+
def self.named(name, with_dhcp = true)
|
152
|
+
networks = all with_dhcp
|
153
|
+
networks.find { |net| net.name == name }
|
154
|
+
end
|
155
|
+
|
139
156
|
# Parses information about a DHCP server returned by VirtualBox.
|
140
157
|
#
|
141
158
|
# The parsed information is used to replace this network's specification.
|
@@ -147,8 +164,8 @@ class Net
|
|
147
164
|
line.split(':', 2).map(&:strip)
|
148
165
|
}]
|
149
166
|
|
150
|
-
@
|
151
|
-
@
|
167
|
+
@name = info['Name']
|
168
|
+
@vbox_name = info['VBoxNetworkName']
|
152
169
|
@mac = info['HardwareAddress'].upcase.gsub(/[^0-9A-F]/, '')
|
153
170
|
self.ip = info['IPAddress']
|
154
171
|
self.netmask = info['NetworkMask']
|
data/lib/virtual_box/net/dhcp.rb
CHANGED
@@ -71,7 +71,7 @@ class Dhcp
|
|
71
71
|
'--netmask', netmask, '--lowerip', start_ip, '--upperip', end_ip,
|
72
72
|
'--enable']
|
73
73
|
if net_or_name.kind_of? VirtualBox::Net
|
74
|
-
command.push '--ifname', net_or_name.
|
74
|
+
command.push '--ifname', net_or_name.name
|
75
75
|
else
|
76
76
|
command.push '--netname', net_or_name
|
77
77
|
end
|
@@ -88,7 +88,7 @@ class Dhcp
|
|
88
88
|
def remove(net_or_name)
|
89
89
|
command = ['VBoxManage', 'dhcpserver', 'remove']
|
90
90
|
if net_or_name.kind_of? VirtualBox::Net
|
91
|
-
command.push '--ifname', net_or_name.
|
91
|
+
command.push '--ifname', net_or_name.name
|
92
92
|
else
|
93
93
|
command.push '--netname', net_or_name
|
94
94
|
end
|
data/lib/virtual_box/vm.rb
CHANGED
@@ -167,20 +167,44 @@ class Vm
|
|
167
167
|
# :nmi:: NMI (non-maskable interrupt)
|
168
168
|
# @return [VirtualBox::Vm] self, for easy call chaining
|
169
169
|
def control(action)
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
170
|
+
result = nil
|
171
|
+
|
172
|
+
3.times do
|
173
|
+
action_arg = case action
|
174
|
+
when :kill
|
175
|
+
'poweroff'
|
176
|
+
when :power_button
|
177
|
+
'acpipowerbutton'
|
178
|
+
when :nmi
|
179
|
+
'injectnmi'
|
180
|
+
else
|
181
|
+
action
|
182
|
+
end
|
178
183
|
|
179
|
-
|
180
|
-
|
184
|
+
result = VirtualBox.run_command ['VBoxManage', '--nologo', 'controlvm',
|
185
|
+
uid, action_arg]
|
186
|
+
return self if result[:status] == 0
|
187
|
+
|
188
|
+
# Perhaps the VM is already powered off?
|
189
|
+
if action == :kill || action == :power_button
|
190
|
+
return self unless live?
|
191
|
+
sleep 0.1
|
192
|
+
else
|
193
|
+
break
|
194
|
+
end
|
195
|
+
end
|
196
|
+
raise VirtualBox::Error, result
|
197
|
+
|
181
198
|
self
|
182
199
|
end
|
183
200
|
|
201
|
+
# True if this virtual machine is running inside VirtualBox.
|
202
|
+
#
|
203
|
+
# @return [Boolean] true if this VM is being simulated by VirtualBox
|
204
|
+
def live?
|
205
|
+
(uid && self.class.started_uids.include?(uid)) ? true : false
|
206
|
+
end
|
207
|
+
|
184
208
|
# The UUIDs of all VMs that are registered with VirtualBox.
|
185
209
|
#
|
186
210
|
# @return [Array<String>] UUIDs for VMs that VirtualBox is aware of
|
data/lib/virtual_box/vm/disk.rb
CHANGED
@@ -93,6 +93,16 @@ class Disk
|
|
93
93
|
new :file => path, :format => format, :media => :disk
|
94
94
|
end
|
95
95
|
|
96
|
+
# Removes the image file backing this disk.
|
97
|
+
#
|
98
|
+
# The method name is drop, as in "DROP TABLE". It doesn't remove the disk from
|
99
|
+
# any VM, it just removes the file.
|
100
|
+
# @return [VirtualBox::Vm::Disk] self, for easy call chaining
|
101
|
+
def drop
|
102
|
+
File.unlink @file if File.exist?(@file)
|
103
|
+
self
|
104
|
+
end
|
105
|
+
|
96
106
|
# Disk image format based on the extension in the file name.
|
97
107
|
def self.guess_image_format(image_file)
|
98
108
|
parts = File.basename(image_file).split('.')
|
data/test/helper.rb
CHANGED
@@ -11,6 +11,9 @@ require 'minitest/unit'
|
|
11
11
|
require 'minitest/spec'
|
12
12
|
require 'mocha'
|
13
13
|
|
14
|
+
require 'simplecov'
|
15
|
+
SimpleCov.start
|
16
|
+
|
14
17
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
15
18
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
16
19
|
require 'virtual_box'
|
@@ -15,7 +15,7 @@ describe 'VirtualBox' do
|
|
15
15
|
:io_buses => [{ :bus => :ide,
|
16
16
|
:disks => [{ :file => iso_file, :port => 1 }] }],
|
17
17
|
:nics => [{ :mode => :host, :chip => :virtual,
|
18
|
-
:net_name => @net.
|
18
|
+
:net_name => @net.name }]).register
|
19
19
|
end
|
20
20
|
|
21
21
|
after do
|
@@ -31,11 +31,7 @@ describe 'VirtualBox' do
|
|
31
31
|
end
|
32
32
|
|
33
33
|
after do
|
34
|
-
unless @vm.nil?
|
35
|
-
@vm.stop
|
36
|
-
# Let VirtualBox stop the VM, so that it can be unregistered.
|
37
|
-
Kernel.sleep 0.5
|
38
|
-
end
|
34
|
+
@vm.stop unless @vm.nil?
|
39
35
|
end
|
40
36
|
|
41
37
|
it 'responds to a SSH connection' do
|
@@ -20,7 +20,9 @@ describe VirtualBox::Net do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
it 'has its device name show up in ifconfig' do
|
23
|
-
|
23
|
+
# NOTE: on OSX, recent VirtualBox builds return user-friendly NIC names,
|
24
|
+
# such as "en0: Ethernet" instead of "en0"
|
25
|
+
`ifconfig -a`.must_include card[:name].split(':', 2).first
|
24
26
|
end
|
25
27
|
end
|
26
28
|
end
|
@@ -49,12 +51,12 @@ describe VirtualBox::Net do
|
|
49
51
|
end
|
50
52
|
|
51
53
|
it 'has an interface name that shows up in the ifconfig output' do
|
52
|
-
@net.
|
53
|
-
`ifconfig -a`.must_include @net.
|
54
|
+
@net.name.wont_be_nil
|
55
|
+
`ifconfig -a`.must_include @net.name
|
54
56
|
end
|
55
57
|
|
56
|
-
it 'has a name' do
|
57
|
-
@net.
|
58
|
+
it 'has a VirtualBox network name' do
|
59
|
+
@net.vbox_name.wont_be_nil
|
58
60
|
end
|
59
61
|
|
60
62
|
it 'has a MAC address' do
|
@@ -90,13 +92,13 @@ describe VirtualBox::Net do
|
|
90
92
|
end
|
91
93
|
|
92
94
|
it 'has an interface name that shows up in the ifconfig output' do
|
93
|
-
@net.
|
94
|
-
`ifconfig -a`.must_include @net.
|
95
|
+
@net.name.wont_be_nil
|
96
|
+
`ifconfig -a`.must_include @net.name
|
95
97
|
end
|
96
98
|
|
97
99
|
it 'shows up on the list of live networks' do
|
98
100
|
networks = VirtualBox::Net.all
|
99
|
-
network = networks.find { |n| n.
|
101
|
+
network = networks.find { |n| n.name == @net.name }
|
100
102
|
network.to_hash.must_equal @net.to_hash
|
101
103
|
end
|
102
104
|
end
|
@@ -124,14 +126,12 @@ describe VirtualBox::Net do
|
|
124
126
|
end
|
125
127
|
|
126
128
|
it 'has an interface name that shows up in the ifconfig output' do
|
127
|
-
@net.
|
128
|
-
`ifconfig -a`.must_include @net.
|
129
|
+
@net.name.wont_be_nil
|
130
|
+
`ifconfig -a`.must_include @net.name
|
129
131
|
end
|
130
132
|
|
131
133
|
it 'shows up on the list of live networks' do
|
132
|
-
|
133
|
-
network = networks.find { |n| n.if_name == @net.if_name }
|
134
|
-
network.to_hash.must_equal @net.to_hash
|
134
|
+
VirtualBox::Net.named(@net.name).to_hash.must_equal @net.to_hash
|
135
135
|
end
|
136
136
|
end
|
137
137
|
end
|
@@ -15,7 +15,7 @@ describe VirtualBox::Vm::Disk do
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
describe 'create' do
|
18
|
+
describe '#create' do
|
19
19
|
describe '16-megabyte VMDK flexible' do
|
20
20
|
let(:vmdk_path) { '/tmp/disk.vmdk' }
|
21
21
|
|
@@ -44,6 +44,13 @@ describe VirtualBox::Vm::Disk do
|
|
44
44
|
it 'returns a VMDK Disk' do
|
45
45
|
@disk.format.must_equal :vmdk
|
46
46
|
end
|
47
|
+
|
48
|
+
describe '#drop' do
|
49
|
+
before { @disk.drop }
|
50
|
+
it 'removes the file' do
|
51
|
+
File.exist?(vmdk_path).must_equal false
|
52
|
+
end
|
53
|
+
end
|
47
54
|
end
|
48
55
|
|
49
56
|
describe '16-megabyte preallocated' do
|
data/test/virtual_box/vm_test.rb
CHANGED
@@ -18,7 +18,7 @@ describe VirtualBox::Vm do
|
|
18
18
|
@vm.wont_be :registered?
|
19
19
|
end
|
20
20
|
|
21
|
-
describe '
|
21
|
+
describe '#register' do
|
22
22
|
before do
|
23
23
|
@vm.register
|
24
24
|
end
|
@@ -36,19 +36,43 @@ describe VirtualBox::Vm do
|
|
36
36
|
uids.must_include @vm.uid
|
37
37
|
end
|
38
38
|
|
39
|
-
|
39
|
+
it 'is not live' do
|
40
|
+
@vm.live?.must_equal false
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#stop' do
|
44
|
+
it "doesn't crash" do
|
45
|
+
@vm.stop
|
46
|
+
@vm.live?.must_equal false
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#start' do
|
40
51
|
before do
|
41
52
|
@vm.start
|
42
53
|
end
|
43
54
|
after do
|
44
55
|
@vm.stop
|
45
|
-
sleep 0.5 # VirtualBox will barf if we unregister the VM right away.
|
46
56
|
end
|
47
57
|
|
48
58
|
it 'shows up on the list of started VM UIDs' do
|
49
59
|
uids = VirtualBox::Vm.started_uids
|
50
60
|
uids.must_include @vm.uid
|
51
61
|
end
|
62
|
+
|
63
|
+
it 'is live' do
|
64
|
+
@vm.live?.must_equal true
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#stop' do
|
68
|
+
before do
|
69
|
+
@vm.stop
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'is no longer live' do
|
73
|
+
@vm.live?.must_equal false
|
74
|
+
end
|
75
|
+
end
|
52
76
|
end
|
53
77
|
end
|
54
78
|
end
|
data/virtual_box.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "virtual_box"
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Victor Costan"]
|
12
|
-
s.date = "2012-05-
|
12
|
+
s.date = "2012-05-07"
|
13
13
|
s.description = "Drives the VirtualBox command-line to manage VMs"
|
14
14
|
s.email = "victor@costan.us"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -55,7 +55,7 @@ Gem::Specification.new do |s|
|
|
55
55
|
s.homepage = "http://github.com/csail/police"
|
56
56
|
s.licenses = ["MIT"]
|
57
57
|
s.require_paths = ["lib"]
|
58
|
-
s.rubygems_version = "1.8.
|
58
|
+
s.rubygems_version = "1.8.23"
|
59
59
|
s.summary = "VirtualBox driver"
|
60
60
|
|
61
61
|
if s.respond_to? :specification_version then
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: virtual_box
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05-
|
12
|
+
date: 2012-05-07 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: posix-spawn
|
@@ -228,7 +228,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
228
228
|
version: '0'
|
229
229
|
segments:
|
230
230
|
- 0
|
231
|
-
hash:
|
231
|
+
hash: 1577724543477993918
|
232
232
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
233
233
|
none: false
|
234
234
|
requirements:
|
@@ -237,7 +237,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
237
237
|
version: '0'
|
238
238
|
requirements: []
|
239
239
|
rubyforge_project:
|
240
|
-
rubygems_version: 1.8.
|
240
|
+
rubygems_version: 1.8.23
|
241
241
|
signing_key:
|
242
242
|
specification_version: 3
|
243
243
|
summary: VirtualBox driver
|