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.
Files changed (47) hide show
  1. data/.autotest +9 -0
  2. data/.document +5 -0
  3. data/.project +12 -0
  4. data/.rspec +1 -0
  5. data/Gemfile +14 -0
  6. data/Gemfile.lock +44 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.markdown +67 -0
  9. data/Rakefile +35 -22
  10. data/VERSION +1 -0
  11. data/lib/virtual_box/board.rb +287 -0
  12. data/lib/virtual_box/cli.rb +20 -0
  13. data/lib/virtual_box/dhcp.rb +149 -0
  14. data/lib/virtual_box/disk.rb +138 -0
  15. data/lib/virtual_box/io_bus.rb +261 -0
  16. data/lib/virtual_box/net.rb +144 -0
  17. data/lib/virtual_box/nic.rb +213 -0
  18. data/lib/virtual_box/version.rb +37 -24
  19. data/lib/virtual_box/vm.rb +289 -24
  20. data/lib/virtual_box.rb +14 -9
  21. data/test/helper.rb +24 -0
  22. data/test/tasks/tinycore.rake +378 -0
  23. data/test/virtual_box/board_test.rb +39 -0
  24. data/test/virtual_box/cli_test.rb +33 -0
  25. data/test/virtual_box/dhcp_test.rb +52 -0
  26. data/test/virtual_box/disk_test.rb +116 -0
  27. data/test/virtual_box/integration_test.rb +53 -0
  28. data/test/virtual_box/io_bus_test.rb +54 -0
  29. data/test/virtual_box/net_test.rb +80 -0
  30. data/test/virtual_box/nic_test.rb +50 -0
  31. data/test/virtual_box/version_test.rb +71 -0
  32. data/test/virtual_box/vm_test.rb +55 -0
  33. data/virtual_box.gemspec +81 -22
  34. metadata +208 -89
  35. data/CHANGELOG +0 -1
  36. data/LICENSE +0 -21
  37. data/Manifest +0 -17
  38. data/README.textile +0 -19
  39. data/lib/virtual_box/command_line.rb +0 -27
  40. data/lib/virtual_box/vm/general_settings.rb +0 -136
  41. data/lib/virtual_box/vm/identity.rb +0 -43
  42. data/lib/virtual_box/vm/lifecycle.rb +0 -42
  43. data/test/command_line_test.rb +0 -19
  44. data/test/general_settings_test.rb +0 -20
  45. data/test/lifecycle_test.rb +0 -64
  46. data/test/version_test.rb +0 -56
  47. 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