virtual_box 0.0.1 → 0.1.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.
- 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
|