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.
@@ -12,7 +12,7 @@ GEM
12
12
  systemu (>= 2.4.0)
13
13
  metaclass (0.0.1)
14
14
  minitest (2.12.1)
15
- mocha (0.11.3)
15
+ mocha (0.11.4)
16
16
  metaclass (~> 0.0.1)
17
17
  multi_json (1.3.4)
18
18
  net-ssh (2.3.0)
@@ -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.2
1
+ 0.1.4
@@ -16,7 +16,7 @@ class Error < StandardError
16
16
  # VirtualBox.run call.
17
17
  def initialize(cli_result)
18
18
  @exit_code = cli_result[:status]
19
- @output = output
19
+ @output = cli_result[:output]
20
20
 
21
21
  super "VirtualBox CLI exited with code #{@exit_code}:\n#{@output}\n"
22
22
  end
@@ -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 :if_name
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 :name
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.if_name == if_name }
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 if_name.nil?
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
- @if_name = match[1]
93
+ @name = match[1]
92
94
 
93
- # Now list all the networks to pull the rest of the information.
94
- networks = self.class.all
95
- network = networks.find { |net| net.if_name == if_name }
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', if_name, '--ip', ip, '--netmask', netmask]
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 if_name.nil?
118
+ unless name.nil?
118
119
  dhcp.remove self if dhcp
119
- VirtualBox.run_command ['VBoxManage', 'hostonlyif', 'remove', if_name]
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(net_info)
134
- net.dhcp = dhcps[net.name]
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
- @if_name = info['Name']
151
- @name = info['VBoxNetworkName']
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']
@@ -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.if_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.if_name
91
+ command.push '--ifname', net_or_name.name
92
92
  else
93
93
  command.push '--netname', net_or_name
94
94
  end
@@ -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
- action = case action
171
- when :kill
172
- 'poweroff'
173
- when :power_button
174
- 'acpipowerbutton'
175
- when :nmi
176
- 'injectnmi'
177
- end
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
- VirtualBox.run_command! ['VBoxManage', '--nologo', 'controlvm', uid,
180
- action]
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
@@ -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('.')
@@ -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.if_name }]).register
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
- `ifconfig -a`.must_include card[:name]
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.if_name.wont_be_nil
53
- `ifconfig -a`.must_include @net.if_name
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.name.wont_be_nil
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.if_name.wont_be_nil
94
- `ifconfig -a`.must_include @net.if_name
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.if_name == @net.if_name }
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.if_name.wont_be_nil
128
- `ifconfig -a`.must_include @net.if_name
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
- networks = VirtualBox::Net.all
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
@@ -18,7 +18,7 @@ describe VirtualBox::Vm do
18
18
  @vm.wont_be :registered?
19
19
  end
20
20
 
21
- describe 'registered' do
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
- describe 'started' do
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
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "virtual_box"
8
- s.version = "0.1.2"
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-06"
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.24"
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.2
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-06 00:00:00.000000000 Z
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: -3995801418779294892
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.24
240
+ rubygems_version: 1.8.23
241
241
  signing_key:
242
242
  specification_version: 3
243
243
  summary: VirtualBox driver