virtual_box 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +9 -0
- data/.document +5 -0
- data/.project +12 -0
- data/.rspec +1 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +44 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +67 -0
- data/Rakefile +35 -22
- data/VERSION +1 -0
- data/lib/virtual_box/board.rb +287 -0
- data/lib/virtual_box/cli.rb +20 -0
- data/lib/virtual_box/dhcp.rb +149 -0
- data/lib/virtual_box/disk.rb +138 -0
- data/lib/virtual_box/io_bus.rb +261 -0
- data/lib/virtual_box/net.rb +144 -0
- data/lib/virtual_box/nic.rb +213 -0
- data/lib/virtual_box/version.rb +37 -24
- data/lib/virtual_box/vm.rb +289 -24
- data/lib/virtual_box.rb +14 -9
- data/test/helper.rb +24 -0
- data/test/tasks/tinycore.rake +378 -0
- data/test/virtual_box/board_test.rb +39 -0
- data/test/virtual_box/cli_test.rb +33 -0
- data/test/virtual_box/dhcp_test.rb +52 -0
- data/test/virtual_box/disk_test.rb +116 -0
- data/test/virtual_box/integration_test.rb +53 -0
- data/test/virtual_box/io_bus_test.rb +54 -0
- data/test/virtual_box/net_test.rb +80 -0
- data/test/virtual_box/nic_test.rb +50 -0
- data/test/virtual_box/version_test.rb +71 -0
- data/test/virtual_box/vm_test.rb +55 -0
- data/virtual_box.gemspec +81 -22
- metadata +208 -89
- data/CHANGELOG +0 -1
- data/LICENSE +0 -21
- data/Manifest +0 -17
- data/README.textile +0 -19
- data/lib/virtual_box/command_line.rb +0 -27
- data/lib/virtual_box/vm/general_settings.rb +0 -136
- data/lib/virtual_box/vm/identity.rb +0 -43
- data/lib/virtual_box/vm/lifecycle.rb +0 -42
- data/test/command_line_test.rb +0 -19
- data/test/general_settings_test.rb +0 -20
- data/test/lifecycle_test.rb +0 -64
- data/test/version_test.rb +0 -56
- data/testdata/golden_general_params.txt +0 -1
@@ -0,0 +1,149 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
|
3
|
+
# Specification for a virtual DHCP server.
|
4
|
+
class Dhcp
|
5
|
+
# The name of the VirtualBox network served by this DHCP server.
|
6
|
+
#
|
7
|
+
# This name must match the name of a host-only or internal network that is
|
8
|
+
# registered with VirtualBox.
|
9
|
+
# @return [String]
|
10
|
+
attr_accessor :net_name
|
11
|
+
|
12
|
+
# This DHCP server's IP address on the virtual network that it serves.
|
13
|
+
# @return [String]
|
14
|
+
attr_accessor :ip
|
15
|
+
|
16
|
+
# The network mask reported by this DHCP server.
|
17
|
+
# @return [String]
|
18
|
+
attr_accessor :netmask
|
19
|
+
|
20
|
+
# The first IP address in this DHCP server's address pool.
|
21
|
+
# @return [String]
|
22
|
+
attr_accessor :start_ip
|
23
|
+
|
24
|
+
# The last IP address in this DHCP server's address pool.
|
25
|
+
# @return [String]
|
26
|
+
attr_accessor :end_ip
|
27
|
+
|
28
|
+
undef :ip
|
29
|
+
def ip
|
30
|
+
@ip ||= self.class.ip_btos((self.class.ip_stob(start_ip) &
|
31
|
+
self.class.ip_stob(netmask)) + 1)
|
32
|
+
end
|
33
|
+
|
34
|
+
undef :netmask
|
35
|
+
def netmask
|
36
|
+
@netmask ||= '255.255.255.0'
|
37
|
+
end
|
38
|
+
|
39
|
+
undef :start_ip
|
40
|
+
def start_ip
|
41
|
+
@start_ip ||= self.class.ip_btos(self.class.ip_stob(ip) + 1)
|
42
|
+
end
|
43
|
+
|
44
|
+
undef :end_ip
|
45
|
+
def end_ip
|
46
|
+
nm = self.class.ip_stob(netmask)
|
47
|
+
@end_ip ||= self.class.ip_btos((self.class.ip_stob(start_ip) & nm) +
|
48
|
+
~nm - 1)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Creates a DHCP server specification based on the given attributes.
|
52
|
+
#
|
53
|
+
# The DHCP server is not automatically added to VirtualBox.
|
54
|
+
# @param [Hash<Symbol, Object>] options ActiveRecord-style initial values for
|
55
|
+
# attributes; can be used together with Net#to_hash to save and restore
|
56
|
+
def initialize(options = {})
|
57
|
+
options.each { |k, v| self.send :"#{k}=", v }
|
58
|
+
end
|
59
|
+
|
60
|
+
# Hash capturing this specification. Can be passed to Dhcp#new.
|
61
|
+
#
|
62
|
+
# @return [Hash<Symbol, Object>] Ruby-friendly Hash that can be used to
|
63
|
+
# re-create this DHCP server specification
|
64
|
+
def to_hash
|
65
|
+
{ :net_name => 'net_name', :ip => ip, :netmask => netmask,
|
66
|
+
:start_ip => start_ip, :end_ip => end_ip }
|
67
|
+
end
|
68
|
+
|
69
|
+
# True if this DHCP rule has been registered with VirtualBox.
|
70
|
+
def live?
|
71
|
+
servers = self.class.all
|
72
|
+
dhcp = servers.find { |server| server.net_name == net_name }
|
73
|
+
dhcp ? true : false
|
74
|
+
end
|
75
|
+
|
76
|
+
# Adds this DHCP server to VirtualBox.
|
77
|
+
#
|
78
|
+
# @return [VirtualBox::Dhcp] self, for easy call chaining
|
79
|
+
def add
|
80
|
+
remove if live?
|
81
|
+
|
82
|
+
result = VirtualBox.run_command ['VBoxManage', 'dhcpserver', 'add',
|
83
|
+
'--netname', net_name, '--ip', ip, '--netmask', netmask,
|
84
|
+
'--lowerip', start_ip, '--upperip', end_ip, '--enable']
|
85
|
+
if result.status != 0
|
86
|
+
raise 'Unexpected error code returned by VirtualBox'
|
87
|
+
end
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
# Removes this DHCP server from VirtualBox.
|
92
|
+
#
|
93
|
+
# @return [VirtualBox::Dhcp] self, for easy call chaining
|
94
|
+
def remove
|
95
|
+
VirtualBox.run_command ['VBoxManage', 'dhcpserver', 'remove', '--netname',
|
96
|
+
net_name]
|
97
|
+
self
|
98
|
+
end
|
99
|
+
|
100
|
+
# The DHCP servers added to with VirtualBox.
|
101
|
+
#
|
102
|
+
# @return [Array<VirtualBox::Dhcp>] all the DHCP servers added to VirtualBox
|
103
|
+
def self.all
|
104
|
+
result = VirtualBox.run_command ['VBoxManage', '--nologo', 'list', '--long',
|
105
|
+
'dhcpservers']
|
106
|
+
if result.status != 0
|
107
|
+
raise 'Unexpected error code returned by VirtualBox'
|
108
|
+
end
|
109
|
+
result.output.split("\n\n").
|
110
|
+
map { |dhcp_info| new.from_dhcp_info(dhcp_info) }
|
111
|
+
end
|
112
|
+
|
113
|
+
# Parses information about a DHCP server returned by VirtualBox.
|
114
|
+
#
|
115
|
+
# The parsed information is used to replace this network's specification.
|
116
|
+
# @param [String] dhcp_info output from "VBoxManage list --long dhcpservers"
|
117
|
+
# for one server
|
118
|
+
# @return [VirtualBox::Dhcp] self, for easy call chaining
|
119
|
+
def from_dhcp_info(dhcp_info)
|
120
|
+
info = Hash[dhcp_info.split("\n").map { |line|
|
121
|
+
line.split(':', 2).map(&:strip)
|
122
|
+
}]
|
123
|
+
|
124
|
+
self.net_name = info['NetworkName']
|
125
|
+
self.ip = info['IP']
|
126
|
+
self.netmask = info['NetworkMask']
|
127
|
+
self.start_ip = info['lowerIPAddress']
|
128
|
+
self.end_ip = info['upperIPAddress']
|
129
|
+
self
|
130
|
+
end
|
131
|
+
|
132
|
+
# Converts an IP number into a string.
|
133
|
+
#
|
134
|
+
# @param [Integer] ip_number a 32-bit big-endian number holding an IP address
|
135
|
+
# @return [String] the IP address, encoded using the dot (.) notation
|
136
|
+
def self.ip_btos(ip_number)
|
137
|
+
[ip_number].pack('N').unpack('C*').join('.')
|
138
|
+
end
|
139
|
+
|
140
|
+
# Converts an IP string into a number.
|
141
|
+
#
|
142
|
+
# @param [String] ip_string an IP address using the dot (.) notation
|
143
|
+
# @return [Integer] the IP adddres, encoded as a 32-bit big-endian number
|
144
|
+
def self.ip_stob(ip_string)
|
145
|
+
ip_string.split('.').map(&:to_i).pack('C*').unpack('N').first
|
146
|
+
end
|
147
|
+
end # class VirtualBox::Dhcp
|
148
|
+
|
149
|
+
end # namespace VirtualBox
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
|
3
|
+
# Descriptor for a VirtualBox hard-disk or DVD image.
|
4
|
+
class Disk
|
5
|
+
# Path to the file storing this disk image.
|
6
|
+
#
|
7
|
+
# @return [String]
|
8
|
+
attr_accessor :file
|
9
|
+
|
10
|
+
# The format of this disk image.
|
11
|
+
#
|
12
|
+
# The recognized formats are :raw, :vdi, :vmdk, and :vhd.
|
13
|
+
# @return [Symbol]
|
14
|
+
attr_accessor :format
|
15
|
+
|
16
|
+
# The type of media that the image represents.
|
17
|
+
#
|
18
|
+
# The recognized types are :disk and :dvd.
|
19
|
+
# @return [Symbol]
|
20
|
+
attr_accessor :media
|
21
|
+
|
22
|
+
undef :format
|
23
|
+
def format
|
24
|
+
@format ||= self.class.guess_image_format file
|
25
|
+
end
|
26
|
+
|
27
|
+
undef :media
|
28
|
+
def media
|
29
|
+
@media ||= self.class.guess_media_type file
|
30
|
+
end
|
31
|
+
|
32
|
+
# Creates an image descriptor with the given attributes.
|
33
|
+
#
|
34
|
+
# @param [Hash<Symbol, Object>] options ActiveRecord-style initial values for
|
35
|
+
# attributes; can be used together with Disk#to_hash to save and restore
|
36
|
+
def initialize(options)
|
37
|
+
options.each { |k, v| self.send :"#{k}=", v }
|
38
|
+
self.file = File.expand_path file
|
39
|
+
end
|
40
|
+
|
41
|
+
# Attaches this disk to a virtual machine.
|
42
|
+
#
|
43
|
+
# @param [VirtualBox::Vm] vm the VM that this image will be attached to
|
44
|
+
# @param [VirtualBox::IoBus] io_bus the IO controller that this disk will be
|
45
|
+
# attached to
|
46
|
+
# @param [Integer] port the IO bus port this disk will be connected to
|
47
|
+
# @param [Integer] device number indicating the device's ordering on its port
|
48
|
+
# @return [VirtualBox::Disk] self, for easy call chaining
|
49
|
+
def add_to(vm, io_bus, port, device)
|
50
|
+
media_arg = case media
|
51
|
+
when :disk
|
52
|
+
'hdd'
|
53
|
+
when :dvd
|
54
|
+
'dvddrive'
|
55
|
+
end
|
56
|
+
result = VirtualBox.run_command ['VBoxManage', '--nologo', 'storageattach',
|
57
|
+
vm.uid, '--storagectl', io_bus.name, '--port', port.to_s,
|
58
|
+
'--device', device.to_s, '--type', media_arg, '--medium', file]
|
59
|
+
if result.status != 0
|
60
|
+
raise 'Unexpected error code returned by VirtualBox'
|
61
|
+
end
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
# Creates a new image descriptor based on the given attributes.
|
66
|
+
#
|
67
|
+
# @param Hash<Symbol, Object> options ActiveRecord-style initial values for
|
68
|
+
# attributes; can be used together with Disk#to_hash to save and restore
|
69
|
+
def to_hash
|
70
|
+
{ :file => file, :format => format, :media => media }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Creates a VirtualBox disk image.
|
74
|
+
#
|
75
|
+
# @param [Hash] options one or many of the options documented below
|
76
|
+
# @option options [String] file path to the file that will hold the disk image
|
77
|
+
# @option options [Integer] size the image size, in bytes
|
78
|
+
# @option options [Symbol] format the image format; if not provided, an
|
79
|
+
# intelligent guess is made, based on the file extension
|
80
|
+
# @option options [Boolean] prealloc unless explicitly set to true, the image
|
81
|
+
# file will grow in size as the disk's blocks are used
|
82
|
+
#
|
83
|
+
# @return [VirtualBox::Disk] a Disk describing the image that was created
|
84
|
+
def self.create(options)
|
85
|
+
path = options[:file]
|
86
|
+
format = options[:format] || guess_image_format(path)
|
87
|
+
size_mb = (options[:size] / (1024 * 1024)).to_i
|
88
|
+
memo = options[:memo] || 'Created with the virtual_box RubyGem'
|
89
|
+
variant = options[:prealloc] ? 'Fixed' : 'Standard'
|
90
|
+
|
91
|
+
result = VirtualBox.run_command ['VBoxManage', '--nologo', 'createhd',
|
92
|
+
'--filename', path, '--size', size_mb.to_s, '--format', format.to_s,
|
93
|
+
'--variant', variant]
|
94
|
+
if result.status != 0
|
95
|
+
raise 'Unexpected error code returned by VirtualBox'
|
96
|
+
end
|
97
|
+
|
98
|
+
Disk.new :file => path, :format => format, :media => :disk
|
99
|
+
end
|
100
|
+
|
101
|
+
# Disk image format based on the extension in the file name.
|
102
|
+
def self.guess_image_format(image_file)
|
103
|
+
parts = File.basename(image_file).split('.')
|
104
|
+
if parts.length >= 2
|
105
|
+
case parts.last
|
106
|
+
when 'vdi'
|
107
|
+
:vdi
|
108
|
+
when 'vmdk'
|
109
|
+
:vmdk
|
110
|
+
when 'vhd'
|
111
|
+
:vhd
|
112
|
+
when 'iso'
|
113
|
+
:raw
|
114
|
+
else
|
115
|
+
:vdi
|
116
|
+
end
|
117
|
+
else
|
118
|
+
:vdi
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Disk media type on the extension in the file name.
|
123
|
+
def self.guess_media_type(image_file)
|
124
|
+
parts = File.basename(image_file).split('.')
|
125
|
+
if parts.length >= 2
|
126
|
+
case parts.last
|
127
|
+
when 'iso'
|
128
|
+
:dvd
|
129
|
+
else
|
130
|
+
:disk
|
131
|
+
end
|
132
|
+
else
|
133
|
+
:disk
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end # class VirtualBox::Disk
|
137
|
+
|
138
|
+
end # namespace VirtualBox
|
@@ -0,0 +1,261 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
|
3
|
+
# Specification for a IO controller attached to a virtual machine.
|
4
|
+
class IoBus
|
5
|
+
# A user-friendly name for the I/O controller.
|
6
|
+
#
|
7
|
+
# I/O controller names must be unique within the scope of a virtual machine.
|
8
|
+
# VirtualBox uses "IDE Controller" and "SATA Controller" as default names.
|
9
|
+
# @return [String]
|
10
|
+
attr_accessor :name
|
11
|
+
|
12
|
+
# The kind of bus used by this controller.
|
13
|
+
#
|
14
|
+
# The following bus types are recognized: :ide, :sata, :sas, :scsi, :floppy.
|
15
|
+
# @return [Symbol]
|
16
|
+
attr_accessor :bus
|
17
|
+
|
18
|
+
# The chipset simulated by the IO controller
|
19
|
+
#
|
20
|
+
# The following chipsets are recogniezd:
|
21
|
+
# IDE :: :piix3, :piix4, and :ich6
|
22
|
+
# SATA :: :ahci (Intel AHCI)
|
23
|
+
# SCSI :: :lsi_logic and :bus_logic
|
24
|
+
# SAS :: lsi_logic_sas
|
25
|
+
# Floppy :: :i82078
|
26
|
+
# @return [Symbol]
|
27
|
+
attr_accessor :chip
|
28
|
+
|
29
|
+
# True if the VM BIOS considers this controller bootable.
|
30
|
+
# @return [Boolean]
|
31
|
+
attr_accessor :bootable
|
32
|
+
|
33
|
+
# True if the controller's I/O bypasses the host OS cache.
|
34
|
+
# @return [Boolean]
|
35
|
+
attr_accessor :no_cache
|
36
|
+
|
37
|
+
# The maximum number of I/O devices supported by this controller.
|
38
|
+
# @return [Integer]
|
39
|
+
attr_accessor :max_ports
|
40
|
+
|
41
|
+
# The disks connected to this controller's bus.
|
42
|
+
# @return [Hash<Array<Integer>, VirtualBox::Disk>]
|
43
|
+
attr_accessor :disks
|
44
|
+
|
45
|
+
undef :name
|
46
|
+
def name
|
47
|
+
@name ||= case bus
|
48
|
+
when :ide, :sata, :scsi, :sas
|
49
|
+
"#{bus.upcase} Controller"
|
50
|
+
when :floppy
|
51
|
+
'Floppy Controller'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
undef :no_cache
|
56
|
+
def no_cache
|
57
|
+
@no_cache.nil? ? false : @bootable
|
58
|
+
end
|
59
|
+
|
60
|
+
undef :bootable
|
61
|
+
def bootable
|
62
|
+
@bootable.nil? ? true : @bootable
|
63
|
+
end
|
64
|
+
|
65
|
+
undef :chip
|
66
|
+
def chip
|
67
|
+
@chip ||= case @bus
|
68
|
+
when :ide
|
69
|
+
:piix4
|
70
|
+
when :sata
|
71
|
+
:ahci
|
72
|
+
when :scsi
|
73
|
+
:lsi_logic
|
74
|
+
when :sas
|
75
|
+
:lsi_logic_sas
|
76
|
+
when :floppy
|
77
|
+
:i82078
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
undef :bus
|
82
|
+
def bus
|
83
|
+
@bus ||= case @chip
|
84
|
+
when :piix3, :piix4, :ich6
|
85
|
+
:ide
|
86
|
+
when :ahci
|
87
|
+
:sata
|
88
|
+
when :lsi_logic, :bus_logic
|
89
|
+
:scsi
|
90
|
+
when :lsi_logic_sas
|
91
|
+
:sas
|
92
|
+
when :i82078
|
93
|
+
:floppy
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
undef :max_ports
|
98
|
+
def max_ports
|
99
|
+
@max_ports ||= case bus
|
100
|
+
when :sata
|
101
|
+
30
|
102
|
+
when :sas, :scsi
|
103
|
+
16
|
104
|
+
when :ide
|
105
|
+
2
|
106
|
+
when :floppy
|
107
|
+
1
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
undef :disks=
|
112
|
+
def disks=(new_disks)
|
113
|
+
@disks = {}
|
114
|
+
new_disks.each do |disk|
|
115
|
+
if disk.kind_of? VirtualBox::Disk
|
116
|
+
@disks[[first_free_port, 0]] = disk
|
117
|
+
else
|
118
|
+
options = disk.dup
|
119
|
+
port = options.delete(:port) || first_free_port
|
120
|
+
device = options.delete(:device) || 0
|
121
|
+
@disks[[port, device]] = VirtualBox::Disk.new options
|
122
|
+
end
|
123
|
+
end
|
124
|
+
new_disks
|
125
|
+
end
|
126
|
+
|
127
|
+
# Parses "VBoxManage showvminfo --machinereadable" output into this instance.
|
128
|
+
#
|
129
|
+
# @return [VirtualBox::IoBus] self, for easy call chaining
|
130
|
+
def from_params(params, bus_id)
|
131
|
+
self.name = params["storagecontrollername#{bus_id}"]
|
132
|
+
self.bootable = params["storagecontrollerbootable#{bus_id}"] == 'on'
|
133
|
+
self.max_ports = params["storagecontrollermaxportcount#{bus_id}"].to_i
|
134
|
+
case params["storagecontrollertype#{bus_id}"]
|
135
|
+
when 'PIIX3'
|
136
|
+
self.chip = :piix3
|
137
|
+
when 'PIIX4'
|
138
|
+
self.chip = :piix4
|
139
|
+
when 'ICH6'
|
140
|
+
self.chip = :ich6
|
141
|
+
when 'IntelAhci'
|
142
|
+
self.chip = :ahci
|
143
|
+
when 'LsiLogic'
|
144
|
+
self.chip = :lsi_logic
|
145
|
+
when 'BusLogic'
|
146
|
+
self.chip = :bus_logic
|
147
|
+
when 'LSILogicSAS'
|
148
|
+
self.chip = :lsi_logic_sas
|
149
|
+
when 'I82078'
|
150
|
+
self.chip = :i82078
|
151
|
+
end
|
152
|
+
self.no_cache = nil
|
153
|
+
|
154
|
+
image_re = /\A#{name}-(\d+)-(\d+)\Z/
|
155
|
+
@disks = {}
|
156
|
+
params.each do |key, value|
|
157
|
+
next unless match = image_re.match(key)
|
158
|
+
next if value == 'none'
|
159
|
+
port, device = match[1].to_i, match[2].to_i
|
160
|
+
@disks[[port, device]] = VirtualBox::Disk.new :file => value
|
161
|
+
end
|
162
|
+
self
|
163
|
+
end
|
164
|
+
|
165
|
+
# Parameters for "VBoxManage storagectl" to add this IO bus to a VM.
|
166
|
+
#
|
167
|
+
# @return [Array<String>] the parameters for a "VBoxManage storagectl" command
|
168
|
+
# that get this IO bus added to a VM.
|
169
|
+
def to_params
|
170
|
+
params = []
|
171
|
+
params.push '--name', name
|
172
|
+
params.push '--add', bus.to_s
|
173
|
+
params.push '--controller', case chip
|
174
|
+
when :piix3, :piix4, :ich6, :i82078
|
175
|
+
chip.to_s.upcase
|
176
|
+
when :ahci
|
177
|
+
'IntelAhci'
|
178
|
+
when :lsi_logic
|
179
|
+
'LsiLogic'
|
180
|
+
when :bus_logic
|
181
|
+
'BusLogic'
|
182
|
+
when :lsi_logic_sas
|
183
|
+
'LSILogicSAS'
|
184
|
+
end
|
185
|
+
params.push '--sataportcount', max_ports.to_s
|
186
|
+
params.push '--hostiocache', (no_cache ? 'off' : 'on')
|
187
|
+
params.push '--bootable', (bootable ? 'on' : 'off')
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
# Adds this IO bus and all its disks to a virtual machine.
|
192
|
+
#
|
193
|
+
# @param [VirtualBox::Vm] vm the virtual machine that this IO controller will
|
194
|
+
# be added to
|
195
|
+
# @return [VirtualBox::IoBus] self, for easy call chaining
|
196
|
+
def add_to(vm)
|
197
|
+
add_bus_to vm
|
198
|
+
disks.each do |port_device, disk|
|
199
|
+
disk.add_to vm, self, port_device.first, port_device.last
|
200
|
+
end
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
# Removes this IO bus from a virtual machine.
|
205
|
+
#
|
206
|
+
# @param [VirtualBox::Vm] vm the virtual machine that this IO controller will
|
207
|
+
# be removed from
|
208
|
+
# @return [VirtualBox::IoBus] self, for easy call chaining
|
209
|
+
def remove_from(vm)
|
210
|
+
result = VirtualBox.run_command ['VBoxManage', '--nologo', 'storagectl',
|
211
|
+
vm.uuid, '--name', name, '--remove']
|
212
|
+
if result.status != 0
|
213
|
+
raise 'Unexpected error code returned by VirtualBox'
|
214
|
+
end
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
218
|
+
# Adds this IO bus to a virtual machine.
|
219
|
+
#
|
220
|
+
# @param [VirtualBox::Vm] vm the virtual machine that this IO bus will be
|
221
|
+
# added to
|
222
|
+
# @return [VirtualBox::IoBus] self, for easy call chaining
|
223
|
+
def add_bus_to(vm)
|
224
|
+
command = ['VBoxManage', '--nologo', 'storagectl', vm.uid].concat to_params
|
225
|
+
result = VirtualBox.run_command command
|
226
|
+
if result.status != 0
|
227
|
+
raise 'Unexpected error code returned by VirtualBox'
|
228
|
+
end
|
229
|
+
self
|
230
|
+
end
|
231
|
+
|
232
|
+
# Creates a new IO controller specification based on the given attributes.
|
233
|
+
#
|
234
|
+
# @param [Hash<Symbol, Object>] options ActiveRecord-style initial values for
|
235
|
+
# attributes; can be used together with IoBus#to_hash to save and restore
|
236
|
+
def initialize(options = {})
|
237
|
+
@disks = {}
|
238
|
+
options.each { |k, v| self.send :"#{k}=", v }
|
239
|
+
end
|
240
|
+
|
241
|
+
# Hash capturing this specification. Can be passed to IoBus#new.
|
242
|
+
#
|
243
|
+
# @return [Hash<Symbol, Object>] Ruby-friendly Hash that can be used to
|
244
|
+
# re-create this IO controller specification
|
245
|
+
def to_hash
|
246
|
+
disk_hashes = disks.map do |port_device, disk|
|
247
|
+
disk.to_hash.merge! :port => port_device.first,
|
248
|
+
:device => port_device.last
|
249
|
+
end
|
250
|
+
{ :name => name, :bus => bus, :chip => chip, :bootable => bootable,
|
251
|
+
:no_cache => no_cache, :max_ports => max_ports, :disks => disk_hashes }
|
252
|
+
end
|
253
|
+
|
254
|
+
# Finds an unused port on this IO controller's bus.
|
255
|
+
# @return [Integer] a port number that a new disk can be attached to
|
256
|
+
def first_free_port
|
257
|
+
disks.empty? ? 0 : disks.keys.min.first + 1
|
258
|
+
end
|
259
|
+
end # class VirtualBox::IoBus
|
260
|
+
|
261
|
+
end # namespace VirtualBox
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module VirtualBox
|
2
|
+
|
3
|
+
# Descriptor for a virtual network managed by Virtual Box.
|
4
|
+
class Net
|
5
|
+
# The IP address received by the host computer on this virtual network.
|
6
|
+
# @return [String]
|
7
|
+
attr_accessor :ip
|
8
|
+
|
9
|
+
# The network mask received by the host computer on this virtual network.
|
10
|
+
# @return [String]
|
11
|
+
attr_accessor :netmask
|
12
|
+
|
13
|
+
# The name of the host's virtual NIC that's connected to this network.
|
14
|
+
#
|
15
|
+
# VirtualBox's CLI does not provide a way to set the NIC name. Therefore, this
|
16
|
+
# attribute is read-only, and it is automatically set when the network is
|
17
|
+
# registered with VirtualBox.
|
18
|
+
# @return [String]
|
19
|
+
attr_reader :if_name
|
20
|
+
|
21
|
+
# The name of the VirtualBox internal network.
|
22
|
+
#
|
23
|
+
# VirtualBox's CLI does not provide a way to set the internal network name.
|
24
|
+
# Therefore, this attribute is read-only, and it is automatically set when the
|
25
|
+
# network is registered with VirtualBox.
|
26
|
+
# @return [String]
|
27
|
+
attr_reader :name
|
28
|
+
|
29
|
+
# The MAC address of the host's virtual NIC that's connected to this network.
|
30
|
+
#
|
31
|
+
# VirtualBox's CLI does not provide a way to set the MAC. Therefore, this
|
32
|
+
# attribute is read-only, and it is automatically set when the network is
|
33
|
+
# registered with VirtualBox.
|
34
|
+
# @return [String]
|
35
|
+
attr_reader :mac
|
36
|
+
|
37
|
+
# Creates a virtual network specification rule based on the given attributes.
|
38
|
+
#
|
39
|
+
# The network is not automatically added to VirtualBox.
|
40
|
+
# @param [Hash<Symbol, Object>] options ActiveRecord-style initial values for
|
41
|
+
# attributes; can be used together with Net#to_hash to save and restore
|
42
|
+
def initialize(options = {})
|
43
|
+
options.each { |k, v| self.send :"#{k}=", v }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Hash capturing this specification. Can be passed to Net#new.
|
47
|
+
#
|
48
|
+
# @return [Hash<Symbol, Object>] Ruby-friendly Hash that can be used to
|
49
|
+
# re-create this virtual network specification
|
50
|
+
def to_hash
|
51
|
+
{ :ip => ip, :netmask => netmask }
|
52
|
+
end
|
53
|
+
|
54
|
+
# True if this virtual network has been added to VirtualBox.
|
55
|
+
# @return [Boolean] true if this network exists, false otherwise
|
56
|
+
def live?
|
57
|
+
networks = self.class.all
|
58
|
+
network = networks.find { |net| net.if_name == if_name }
|
59
|
+
network ? true : false
|
60
|
+
end
|
61
|
+
|
62
|
+
# Adds this virtual network specification to VirtualBox.
|
63
|
+
#
|
64
|
+
# @return [VirtualBox::Net] self, for easy call chaining
|
65
|
+
def add
|
66
|
+
unless if_name.nil?
|
67
|
+
raise "Virtual network already added to VirtualBox"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Create the network and pull its name.
|
71
|
+
result = VirtualBox.run_command ['VBoxManage', '--nologo', 'hostonlyif',
|
72
|
+
'create']
|
73
|
+
if result.status != 0
|
74
|
+
raise 'Unexpected error code returned by VirtualBox'
|
75
|
+
end
|
76
|
+
unless match = /^interface\s+'(.*)'\s+.*created/i.match(result.output)
|
77
|
+
raise "VirtualBox output does not include interface name"
|
78
|
+
end
|
79
|
+
@if_name = match[1]
|
80
|
+
|
81
|
+
# Now list all the networks to pull the rest of the information.
|
82
|
+
networks = self.class.all
|
83
|
+
network = networks.find { |net| net.if_name == if_name }
|
84
|
+
@name = network.name
|
85
|
+
@mac = network.mac
|
86
|
+
|
87
|
+
if (ip && ip != network.ip) || (netmask && netmask != network.netmask)
|
88
|
+
result = VirtualBox.run_command ['VBoxManage', '--nologo', 'hostonlyif',
|
89
|
+
'ipconfig', if_name, '--ip', ip, '--netmask', netmask]
|
90
|
+
if result.status != 0
|
91
|
+
raise 'Unexpected error code returned by VirtualBox'
|
92
|
+
end
|
93
|
+
else
|
94
|
+
self.ip = network.ip
|
95
|
+
self.netmask = network.netmask
|
96
|
+
end
|
97
|
+
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
# Removes this virtual network from VirtualBox's database.
|
102
|
+
#
|
103
|
+
# @return [VirtualBox::Net] self, for easy call chaining
|
104
|
+
def remove
|
105
|
+
unless if_name.nil?
|
106
|
+
VirtualBox.run_command ['VBoxManage', 'hostonlyif', 'remove', if_name]
|
107
|
+
end
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# The DHCP servers added to with VirtualBox.
|
112
|
+
#
|
113
|
+
# @return [Array<VirtualBox::Dhcp>] all the DHCP servers added to VirtualBox
|
114
|
+
def self.all
|
115
|
+
result = VirtualBox.run_command ['VBoxManage', '--nologo', 'list', '--long',
|
116
|
+
'hostonlyifs']
|
117
|
+
if result.status != 0
|
118
|
+
raise 'Unexpected error code returned by VirtualBox'
|
119
|
+
end
|
120
|
+
result.output.split("\n\n").
|
121
|
+
map { |net_info| new.from_net_info(net_info) }
|
122
|
+
end
|
123
|
+
|
124
|
+
# Parses information about a DHCP server returned by VirtualBox.
|
125
|
+
#
|
126
|
+
# The parsed information is used to replace this network's specification.
|
127
|
+
# @param [String] net_info output from "VBoxManage list --long hostonlyifs"
|
128
|
+
# for one network
|
129
|
+
# @return [VirtualBox::Net] self, for easy call chaining
|
130
|
+
def from_net_info(net_info)
|
131
|
+
info = Hash[net_info.split("\n").map { |line|
|
132
|
+
line.split(':', 2).map(&:strip)
|
133
|
+
}]
|
134
|
+
|
135
|
+
@if_name = info['Name']
|
136
|
+
@name = info['VBoxNetworkName']
|
137
|
+
@mac = info['HardwareAddress'].upcase.gsub(/[^0-9A-F]/, '')
|
138
|
+
self.ip = info['IPAddress']
|
139
|
+
self.netmask = info['NetworkMask']
|
140
|
+
self
|
141
|
+
end
|
142
|
+
end # class VirtualBox::Dhcp
|
143
|
+
|
144
|
+
end # namespace VirtualBox
|