vagrant-bhyve 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.
- checksums.yaml +7 -0
- data/.gitignore +16 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +21 -0
- data/README.md +111 -0
- data/Rakefile +6 -0
- data/Vagrantfiles/Vagrantfile_CentOS-6 +14 -0
- data/Vagrantfiles/Vagrantfile_CentOS-7 +11 -0
- data/Vagrantfiles/Vagrantfile_Debian_Ubuntu +11 -0
- data/example_box/README.md +46 -0
- data/example_box/Vagrantfile +9 -0
- data/example_box/metadata.json +3 -0
- data/lib/vagrant-bhyve.rb +21 -0
- data/lib/vagrant-bhyve/action.rb +215 -0
- data/lib/vagrant-bhyve/action/boot.rb +26 -0
- data/lib/vagrant-bhyve/action/cleanup.rb +23 -0
- data/lib/vagrant-bhyve/action/create_bridge.rb +33 -0
- data/lib/vagrant-bhyve/action/create_tap.rb +31 -0
- data/lib/vagrant-bhyve/action/destroy.rb +23 -0
- data/lib/vagrant-bhyve/action/forward_ports.rb +53 -0
- data/lib/vagrant-bhyve/action/import.rb +32 -0
- data/lib/vagrant-bhyve/action/load.rb +27 -0
- data/lib/vagrant-bhyve/action/prepare_nfs_settings.rb +50 -0
- data/lib/vagrant-bhyve/action/prepare_nfs_valid_ids.rb +23 -0
- data/lib/vagrant-bhyve/action/setup.rb +27 -0
- data/lib/vagrant-bhyve/action/shutdown.rb +26 -0
- data/lib/vagrant-bhyve/action/wait_until_up.rb +48 -0
- data/lib/vagrant-bhyve/config.rb +75 -0
- data/lib/vagrant-bhyve/driver.rb +674 -0
- data/lib/vagrant-bhyve/errors.rb +55 -0
- data/lib/vagrant-bhyve/executor.rb +38 -0
- data/lib/vagrant-bhyve/plugin.rb +73 -0
- data/lib/vagrant-bhyve/provider.rb +126 -0
- data/lib/vagrant-bhyve/version.rb +5 -0
- data/locales/en.yml +90 -0
- data/vagrant-bhyve.gemspec +24 -0
- metadata +137 -0
@@ -0,0 +1,674 @@
|
|
1
|
+
require "log4r"
|
2
|
+
require "fileutils"
|
3
|
+
require "digest/md5"
|
4
|
+
require "io/console"
|
5
|
+
require "ruby_expect"
|
6
|
+
|
7
|
+
|
8
|
+
module VagrantPlugins
|
9
|
+
module ProviderBhyve
|
10
|
+
class Driver
|
11
|
+
|
12
|
+
# This executor is responsible for actually executing commands, including
|
13
|
+
# bhyve, dnsmasq and other shell utils used to get VM's state
|
14
|
+
attr_accessor :executor
|
15
|
+
|
16
|
+
def initialize(machine)
|
17
|
+
@logger = Log4r::Logger.new("vagrant_bhyve::driver")
|
18
|
+
@machine = machine
|
19
|
+
@data_dir = @machine.data_dir
|
20
|
+
@executor = Executor::Exec.new
|
21
|
+
|
22
|
+
# if vagrant is excecuted by root (or with sudo) then the variable
|
23
|
+
# will be empty string, otherwise it will be 'sudo' to make sure we
|
24
|
+
# can run bhyve, bhyveload and pf with sudo privilege
|
25
|
+
if Process.uid == 0
|
26
|
+
@sudo = ''
|
27
|
+
else
|
28
|
+
@sudo = 'sudo'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def import(machine, ui)
|
33
|
+
box_dir = machine.box.directory
|
34
|
+
instance_dir = @data_dir
|
35
|
+
store_attr('id', machine.id)
|
36
|
+
password = ''
|
37
|
+
check_and_install('gcp', 'coreutils', ui)
|
38
|
+
check_and_install('fdisk-linux', 'linuxfdisk', ui)
|
39
|
+
execute(false, "gcp --sparse=always #{box_dir.join('disk.img').to_s} #{instance_dir.to_s}")
|
40
|
+
if box_dir.join('uefi.fd').exist?
|
41
|
+
FileUtils.copy(box_dir.join('uefi.fd'), instance_dir)
|
42
|
+
store_attr('firmware', 'uefi')
|
43
|
+
else
|
44
|
+
store_attr('firmware', 'bios')
|
45
|
+
boot_partition = execute(false, "cd #{instance_dir.to_s} && fdisk-linux -lu disk.img | grep 'disk.img' | grep -E '\\*' | awk '{print $1}'")
|
46
|
+
if boot_partition == ''
|
47
|
+
store_attr('bootloader', 'bhyveload')
|
48
|
+
else
|
49
|
+
if execute(true, "sudo -n grub-bhyve --help") != 0
|
50
|
+
ui.warn "We need to use your password to commmunicate with grub-bhyve, please make sure the password you input is correct."
|
51
|
+
password = ui.ask("Password:", echo: false)
|
52
|
+
end
|
53
|
+
store_attr('bootloader', 'grub-bhyve')
|
54
|
+
# We need vmm module to be loaded to use grub-bhyve
|
55
|
+
load_module('vmm')
|
56
|
+
# Check whether grub-bhyve is installed
|
57
|
+
check_and_install('grub-bhyve', 'grub2-bhyve', ui)
|
58
|
+
instance_dir.join('device.map').open('w') do |f|
|
59
|
+
f.puts "(hd0) #{instance_dir.join('disk.img').to_s}"
|
60
|
+
end
|
61
|
+
partition_index = boot_partition =~ /\d/
|
62
|
+
partition_id = boot_partition[partition_index..-1]
|
63
|
+
grub_run_partition = "msdos#{partition_id}"
|
64
|
+
files = grub_bhyve_execute("ls (hd0,#{grub_run_partition})/", password, :match)
|
65
|
+
if files =~ /grub2\//
|
66
|
+
grub_run_dir = "/grub2"
|
67
|
+
store_attr('grub_run_partition', grub_run_partition)
|
68
|
+
store_attr('grub_run_dir', grub_run_dir)
|
69
|
+
elsif files =~ /grub\//
|
70
|
+
files = grub_bhyve_execute("ls (hd0,#{grub_run_partition})/grub/", password, :match)
|
71
|
+
if files =~ /grub\.conf/
|
72
|
+
grub_conf = grub_bhyve_execute("cat (hd0,#{grub_run_partition})/grub/grub.conf", password, :before)
|
73
|
+
info_index = grub_conf =~ /title/
|
74
|
+
boot_info = grub_conf[info_index..-1]
|
75
|
+
kernel_info_index = boot_info =~ /kernel/
|
76
|
+
initrd_info_index = boot_info =~ /initrd/
|
77
|
+
kernel_info = boot_info[kernel_info_index..initrd_info_index - 1].gsub("\r\e[1B", "").gsub("kernel ", "linux (hd0,#{grub_run_partition})")
|
78
|
+
initrd_info = boot_info[initrd_info_index..-1].gsub("\r\e[1B", "").gsub("initrd ", "initrd (hd0,#{grub_run_partition})")
|
79
|
+
instance_dir.join('grub.cfg').open('w') do |f|
|
80
|
+
f.puts kernel_info
|
81
|
+
f.puts initrd_info
|
82
|
+
f.puts "boot"
|
83
|
+
end
|
84
|
+
elsif files =~ /grub\.cfg/
|
85
|
+
store_attr('grub_run_partition', grub_run_partition)
|
86
|
+
end
|
87
|
+
else
|
88
|
+
if files =~ /boot\//
|
89
|
+
files = grub_bhyve_execute("ls (hd0,#{grub_run_partition})/boot/", password, :match)
|
90
|
+
if files =~ /grub2/
|
91
|
+
grub_run_dir = "/boot/grub2"
|
92
|
+
store_attr('grub_run_partition', grub_run_partition)
|
93
|
+
store_attr('grub_run_dir', grub_run_dir)
|
94
|
+
elsif files =~ /grub/
|
95
|
+
files = grub_bhyve_execute("ls (hd0,#{grub_run_partition})/boot/grub/", password, :match)
|
96
|
+
if files =~ /grub\.conf/
|
97
|
+
grub_conf = grub_bhyve_execute("cat (hd0,#{grub_run_partition})/boot/grub/grub.conf", password, :before)
|
98
|
+
info_index = grub_conf =~ /title/
|
99
|
+
boot_info = grub_conf[info_index..-1]
|
100
|
+
kernel_info_index = boot_info =~ /kernel/
|
101
|
+
initrd_info_index = boot_info =~ /initrd/
|
102
|
+
kernel_info = boot_info[kernel_info_index..initrd_info_index - 1].gsub("\r\e[1B", "").gsub("kernel ","linux (hd0,#{grub_run_partition})/boot")
|
103
|
+
initrd_info = boot_info[initrd_info_index..-1].gsub("\r\e[1B", "").gsub("initrd ", "initrd (hd0,#{grub_run_partition})/boot")
|
104
|
+
instance_dir.join('grub.cfg').open('w') do |f|
|
105
|
+
f.puts kernel_info
|
106
|
+
f.puts initrd_info
|
107
|
+
f.puts "boot"
|
108
|
+
end
|
109
|
+
elsif files =~ /grub\.cfg/
|
110
|
+
store_attr('grub_run_partition', grub_run_partition)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def destroy
|
120
|
+
FileUtils.rm_rf(Dir.glob(@data_dir.join('*').to_s))
|
121
|
+
end
|
122
|
+
|
123
|
+
def check_bhyve_support
|
124
|
+
# Check whether FreeBSD version is lower than 10
|
125
|
+
result = execute(true, "test $(uname -K) -lt 1000000")
|
126
|
+
raise Errors::SystemVersionIsTooLow if result == 0
|
127
|
+
|
128
|
+
# Check whether POPCNT is supported
|
129
|
+
result = execute(false, "#{@sudo} grep -E '^[ ] +Features2' /var/run/dmesg.boot | tail -n 1")
|
130
|
+
raise Errors::MissingPopcnt unless result =~ /POPCNT/
|
131
|
+
|
132
|
+
# Check whether EPT is supported for Intel
|
133
|
+
result = execute(false, "#{@sudo} grep -E '^[ ]+VT-x' /var/run/dmesg.boot | tail -n 1")
|
134
|
+
raise Errors::MissingEpt unless result =~ /EPT/
|
135
|
+
|
136
|
+
# Check VT-d
|
137
|
+
#result = execute(false, "#{@sudo} acpidump -t | grep DMAR")
|
138
|
+
#raise Errors::MissingIommu if result.length == 0
|
139
|
+
end
|
140
|
+
|
141
|
+
def load_module(module_name)
|
142
|
+
result = execute(true, "#{@sudo} kldstat -qm #{module_name} >/dev/null 2>&1")
|
143
|
+
if result != 0
|
144
|
+
result = execute(true, "#{@sudo} kldload #{module_name} >/dev/null 2>&1")
|
145
|
+
raise Errors::UnableToLoadModule if result != 0
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def create_network_device(device_name, device_type)
|
150
|
+
return if device_name.length == 0
|
151
|
+
|
152
|
+
# Check whether the bridge has been created
|
153
|
+
interface_name = get_interface_name(device_name)
|
154
|
+
interface_name = execute(false, "#{@sudo} ifconfig #{device_type} create") if interface_name.length == 0
|
155
|
+
raise Errors::UnableToCreateInterface if interface_name.length == 0
|
156
|
+
# Add new created device's description
|
157
|
+
execute(false, "#{@sudo} ifconfig #{interface_name} description #{device_name} up")
|
158
|
+
|
159
|
+
# Store the new created network device's name
|
160
|
+
store_attr(device_type, interface_name)
|
161
|
+
|
162
|
+
# Configure tap device
|
163
|
+
if device_type == 'tap'
|
164
|
+
# Add the tap device as bridge's member
|
165
|
+
bridge = get_attr('bridge')
|
166
|
+
# Make sure the tap deivce has the same mtu value
|
167
|
+
# with the bridge
|
168
|
+
mtu = execute(false, "ifconfig #{bridge} | head -n1 | awk '{print $NF}'")
|
169
|
+
execute(false, "#{@sudo} ifconfig #{interface_name} mtu #{mtu}") if mtu.length != 0 and mtu != '1500'
|
170
|
+
execute(false, "#{@sudo} ifconfig #{bridge} addm #{interface_name}")
|
171
|
+
# Setup VM-specific pf rules
|
172
|
+
id = get_attr('id')
|
173
|
+
pf_conf = @data_dir.join('pf.conf')
|
174
|
+
pf_conf.open('w') do |f|
|
175
|
+
f.puts "set skip on #{interface_name}"
|
176
|
+
end
|
177
|
+
comment_mark = "# vagrant-bhyve #{interface_name}"
|
178
|
+
if execute(true, "test -s /etc/pf.conf") == 0
|
179
|
+
if execute(true, "grep \"#{comment_mark}\" /etc/pf.conf") != 0
|
180
|
+
comment_mark_bridge = "# vagrant-bhyve #{bridge}"
|
181
|
+
if execute(true, "grep \"#{comment_mark_bridge}\" /etc/pf.conf") != 0
|
182
|
+
execute(false, "#{@sudo} sed -i '' '1i\\\n#{comment_mark}\n' /etc/pf.conf")
|
183
|
+
execute(false, "#{@sudo} sed -i '' '2i\\\ninclude \"#{pf_conf.to_s}\"\n' /etc/pf.conf")
|
184
|
+
else
|
185
|
+
bridge_line = execute(false, "grep -A 1 \"#{comment_mark_bridge}\" /etc/pf.conf | tail -1")
|
186
|
+
bridge_line = bridge_line.gsub("\"", "\\\"")
|
187
|
+
bridge_line = bridge_line.gsub("/", "\\/")
|
188
|
+
execute(false, "#{@sudo} sed -i '' '/#{bridge_line}/a\\\n#{comment_mark}\n' /etc/pf.conf")
|
189
|
+
execute(false, "#{@sudo} sed -i '' '/#{comment_mark}/a\\\ninclude \"#{pf_conf.to_s}\"\n' /etc/pf.conf")
|
190
|
+
end
|
191
|
+
end
|
192
|
+
else
|
193
|
+
execute(false, "echo \"#{comment_mark}\" | #{@sudo} tee -a /etc/pf.conf")
|
194
|
+
execute(false, "echo \"include \\\"#{pf_conf.to_s}\\\"\" | #{@sudo} tee -a /etc/pf.conf")
|
195
|
+
end
|
196
|
+
restart_service('pf')
|
197
|
+
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{id}' -f #{pf_conf.to_s}")
|
198
|
+
#if !pf_enabled?
|
199
|
+
# execute(false, "#{@sudo} pfctl -e")
|
200
|
+
#end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# For now, only IPv4 is supported
|
205
|
+
def enable_nat(bridge, ui)
|
206
|
+
bridge_name = get_interface_name(bridge)
|
207
|
+
return if execute(true, "ifconfig #{bridge_name} | grep inet") == 0
|
208
|
+
|
209
|
+
directory = @data_dir
|
210
|
+
# Choose a subnet for this bridge
|
211
|
+
index = bridge_name =~ /\d/
|
212
|
+
bridge_num = bridge_name[index..-1]
|
213
|
+
sub_net = "172.16." + bridge_num
|
214
|
+
|
215
|
+
# Config IP for the bridge
|
216
|
+
execute(false, "#{@sudo} ifconfig #{bridge_name} #{sub_net}.1/24")
|
217
|
+
|
218
|
+
# Get default gateway
|
219
|
+
gateway = execute(false, "netstat -4rn | grep default | awk '{print $4}'")
|
220
|
+
store_attr('gateway', gateway)
|
221
|
+
# Add gateway as a bridge member
|
222
|
+
#execute(false, "#{@sudo} ifconfig #{bridge_name} addm #{gateway}")
|
223
|
+
|
224
|
+
# Enable forwarding
|
225
|
+
execute(false, "#{@sudo} sysctl net.inet.ip.forwarding=1 >/dev/null 2>&1")
|
226
|
+
execute(false, "#{@sudo} sysctl net.inet6.ip6.forwarding=1 >/dev/null 2>&1")
|
227
|
+
|
228
|
+
# Change pf's configuration
|
229
|
+
pf_conf = directory.join("pf.conf")
|
230
|
+
pf_conf.open("w") do |pf_file|
|
231
|
+
pf_file.puts "set skip on #{bridge_name}"
|
232
|
+
pf_file.puts "nat on #{gateway} from {#{sub_net}.0/24} to any -> (#{gateway})"
|
233
|
+
end
|
234
|
+
pf_bridge_conf = "/usr/local/etc/pf.#{bridge_name}.conf"
|
235
|
+
comment_mark = "# vagrant-bhyve #{bridge_name}"
|
236
|
+
execute(false, "#{@sudo} mv #{pf_conf.to_s} #{pf_bridge_conf}")
|
237
|
+
if execute(true, "test -s /etc/pf.conf") == 0
|
238
|
+
if execute(true, "grep \"#{comment_mark}\" /etc/pf.conf") != 0
|
239
|
+
execute(false, "#{@sudo} sed -i '' '1i\\\n#{comment_mark}\n' /etc/pf.conf")
|
240
|
+
execute(false, "#{@sudo} sed -i '' '2i\\\ninclude \"#{pf_bridge_conf}\"\n' /etc/pf.conf")
|
241
|
+
end
|
242
|
+
else
|
243
|
+
execute(false, "echo \"#{comment_mark}\" | #{@sudo} tee -a /etc/pf.conf")
|
244
|
+
execute(false, "echo \"include \\\"#{pf_bridge_conf}\\\"\" | #{@sudo} tee -a /etc/pf.conf")
|
245
|
+
end
|
246
|
+
restart_service('pf')
|
247
|
+
# Use pfctl to enable pf rules
|
248
|
+
#execute(false, "#{@sudo} cp #{pf_conf.to_s} /usr/local/etc/pf.#{bridge_name}.conf")
|
249
|
+
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{bridge_name}' -f /usr/local/etc/pf.#{bridge_name}.conf")
|
250
|
+
# execute(false, "#{@sudo} pfctl -a '/vagrant_#{bridge_name}' -sr")
|
251
|
+
|
252
|
+
# Create a basic dnsmasq setting
|
253
|
+
# Basic settings
|
254
|
+
check_and_install('dnsmasq', 'dnsmasq', ui)
|
255
|
+
dnsmasq_conf = directory.join("dnsmasq.conf")
|
256
|
+
dnsmasq_conf.open("w") do |dnsmasq_file|
|
257
|
+
dnsmasq_file.puts <<-EOF
|
258
|
+
domain-needed
|
259
|
+
except-interface=lo0
|
260
|
+
bind-interfaces
|
261
|
+
local-service
|
262
|
+
dhcp-authoritative
|
263
|
+
EOF
|
264
|
+
# DHCP part
|
265
|
+
dnsmasq_file.puts "interface=#{bridge_name}"
|
266
|
+
dnsmasq_file.puts "dhcp-range=#{sub_net + ".10," + sub_net + ".254"}"
|
267
|
+
dnsmasq_file.puts "dhcp-option=option:dns-server,#{sub_net + ".1"}"
|
268
|
+
end
|
269
|
+
execute(false, "#{@sudo} cp #{dnsmasq_conf.to_s} /usr/local/etc/dnsmasq.#{bridge_name}.conf")
|
270
|
+
dnsmasq_cmd = "dnsmasq -C /usr/local/etc/dnsmasq.#{bridge_name}.conf -l /var/run/dnsmasq.#{bridge_name}.leases -x /var/run/dnsmasq.#{bridge_name}.pid"
|
271
|
+
execute(false, "#{@sudo} #{dnsmasq_cmd}")
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
def get_ip_address(interface_name, type=:guest)
|
276
|
+
bridge_name = get_attr('bridge')
|
277
|
+
if type == :guest
|
278
|
+
return nil if execute(true, "test -e /var/run/dnsmasq.#{bridge_name}.pid") != 0
|
279
|
+
mac = get_attr('mac')
|
280
|
+
leases_file = Pathname.new("/var/run/dnsmasq.#{bridge_name}.leases")
|
281
|
+
leases_info = leases_file.open('r'){|f| f.readlines}.select{|line| line.match(mac)}
|
282
|
+
raise Errors::NotFoundLeasesInfo if leases_info == []
|
283
|
+
# IP address for a device is on third coloum
|
284
|
+
ip = leases_info[0].split[2]
|
285
|
+
elsif type == :host
|
286
|
+
return nil if execute(true, "ifconfig #{bridge_name}")
|
287
|
+
ip = execute(false, "ifconfig #{bridge_name} | grep -i inet").split[1]
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def ip_ready?
|
292
|
+
bridge_name = get_attr('bridge')
|
293
|
+
mac = get_attr('mac')
|
294
|
+
leases_file = Pathname.new("/var/run/dnsmasq.#{bridge_name}.leases")
|
295
|
+
return (leases_file.open('r'){|f| f.readlines}.select{|line| line.match(mac)} != [])
|
296
|
+
end
|
297
|
+
|
298
|
+
def ssh_ready?(ssh_info)
|
299
|
+
if ssh_info
|
300
|
+
return execute(true, "nc -z #{ssh_info[:host]} #{ssh_info[:port]}") == 0
|
301
|
+
end
|
302
|
+
return false
|
303
|
+
end
|
304
|
+
|
305
|
+
def load(machine, ui)
|
306
|
+
loader_cmd = @sudo
|
307
|
+
directory = @data_dir
|
308
|
+
config = machine.provider_config
|
309
|
+
loader = get_attr('bootloader')
|
310
|
+
case loader
|
311
|
+
when 'bhyveload'
|
312
|
+
loader_cmd += ' bhyveload'
|
313
|
+
# Set autoboot, and memory and disk
|
314
|
+
loader_cmd += " -m #{config.memory}"
|
315
|
+
loader_cmd += " -d #{directory.join('disk.img').to_s}"
|
316
|
+
loader_cmd += " -e autoboot_delay=0"
|
317
|
+
when 'grub-bhyve'
|
318
|
+
loader_cmd += " grub-bhyve"
|
319
|
+
loader_cmd += " -m #{directory.join('device.map').to_s}"
|
320
|
+
loader_cmd += " -M #{config.memory}"
|
321
|
+
# Maybe there should be some grub config in Vagrantfile, for now
|
322
|
+
# we just use this hd0,1 as default root and don't use -d -g
|
323
|
+
# argument
|
324
|
+
grub_cfg = directory.join('grub.cfg')
|
325
|
+
grub_run_partition = get_attr('grub_run_partition')
|
326
|
+
grub_run_dir = get_attr('grub_run_dir')
|
327
|
+
if grub_cfg.exist?
|
328
|
+
loader_cmd += " -r host -d #{directory.to_s}"
|
329
|
+
else
|
330
|
+
if grub_run_partition
|
331
|
+
loader_cmd += " -r hd0,#{grub_run_partition}"
|
332
|
+
else
|
333
|
+
loader_cmd += " -r hd0,1"
|
334
|
+
end
|
335
|
+
|
336
|
+
if grub_run_dir
|
337
|
+
loader_cmd += " -d #{grub_run_dir}"
|
338
|
+
end
|
339
|
+
# Find an available nmdm device and add it as loader's -m argument
|
340
|
+
nmdm_num = find_available_nmdm
|
341
|
+
loader_cmd += " -c /dev/nmdm#{nmdm_num}A"
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
vm_name = get_attr('vm_name')
|
346
|
+
loader_cmd += " #{vm_name}"
|
347
|
+
execute(false, loader_cmd)
|
348
|
+
end
|
349
|
+
|
350
|
+
def boot(machine, ui)
|
351
|
+
firmware = get_attr('firmware')
|
352
|
+
loader = get_attr('bootloader')
|
353
|
+
directory = @data_dir
|
354
|
+
config = machine.provider_config
|
355
|
+
|
356
|
+
# Run in bhyve in background
|
357
|
+
bhyve_cmd = "sudo -b"
|
358
|
+
# Prevent virtual CPU use 100% of host CPU
|
359
|
+
bhyve_cmd += " bhyve -HP"
|
360
|
+
|
361
|
+
# Configure for hostbridge & lpc device, Windows need slot 0 and 31
|
362
|
+
# while others don't care, so we use slot 0 and 31
|
363
|
+
case config.hostbridge
|
364
|
+
when 'amd'
|
365
|
+
bhyve_cmd += " -s 0,amd_hostbridge"
|
366
|
+
when 'no'
|
367
|
+
else
|
368
|
+
bhyve_cmd += " -s 0,hostbridge"
|
369
|
+
end
|
370
|
+
bhyve_cmd += " -s 31,lpc"
|
371
|
+
|
372
|
+
# Generate ACPI tables for FreeBSD guest
|
373
|
+
bhyve_cmd += " -A" if loader == 'bhyveload'
|
374
|
+
|
375
|
+
# For UEFI, we need to point a UEFI firmware which should be
|
376
|
+
# included in the box.
|
377
|
+
bhyve_cmd += " -l bootrom,#{directory.join('uefi.fd').to_s}" if firmware == "uefi"
|
378
|
+
|
379
|
+
# TODO Enable graphics if the box is configed so
|
380
|
+
|
381
|
+
uuid = get_attr('id')
|
382
|
+
bhyve_cmd += " -U #{uuid}"
|
383
|
+
|
384
|
+
# Allocate resources
|
385
|
+
bhyve_cmd += " -c #{config.cpus}"
|
386
|
+
bhyve_cmd += " -m #{config.memory}"
|
387
|
+
|
388
|
+
# Disk(if any)
|
389
|
+
bhyve_cmd += " -s 1:0,ahci-hd,#{directory.join("disk.img").to_s}"
|
390
|
+
disk_id = 1
|
391
|
+
config.disks.each do |disk|
|
392
|
+
if disk[:format] == "raw"
|
393
|
+
if disk[:path]
|
394
|
+
path = disk[:path]
|
395
|
+
else
|
396
|
+
path = directory.join(disk[:name].to_s).to_s + ".img"
|
397
|
+
end
|
398
|
+
execute(false, "truncate -s #{disk[:size]} #{path}")
|
399
|
+
bhyve_cmd += " -s 1:#{disk_id.to_s},ahci-hd,#{path.to_s}"
|
400
|
+
end
|
401
|
+
disk_id += 1
|
402
|
+
end
|
403
|
+
|
404
|
+
# CDROM(if any)
|
405
|
+
cdrom_id = 0
|
406
|
+
config.cdroms.each do |cdrom|
|
407
|
+
path = File.realpath(cdrom[:path])
|
408
|
+
bhyve_cmd += " -s 2:#{cdrom_id.to_s},ahci-cd,#{path.to_s}"
|
409
|
+
cdrom_id += 1
|
410
|
+
end
|
411
|
+
|
412
|
+
|
413
|
+
# Tap device
|
414
|
+
tap_device = get_attr('tap')
|
415
|
+
mac_address = get_attr('mac')
|
416
|
+
bhyve_cmd += " -s 3:0,virtio-net,#{tap_device},mac=#{mac_address}"
|
417
|
+
|
418
|
+
# Console
|
419
|
+
nmdm_num = find_available_nmdm
|
420
|
+
@data_dir.join('nmdm_num').open('w') { |nmdm_file| nmdm_file.write nmdm_num }
|
421
|
+
bhyve_cmd += " -l com1,/dev/nmdm#{nmdm_num}A"
|
422
|
+
|
423
|
+
vm_name = get_attr('vm_name')
|
424
|
+
bhyve_cmd += " #{vm_name} >/dev/null 2>&1"
|
425
|
+
|
426
|
+
execute(false, bhyve_cmd)
|
427
|
+
while state(vm_name) != :running
|
428
|
+
sleep 0.5
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def shutdown(ui)
|
433
|
+
vm_name = get_attr('vm_name')
|
434
|
+
if state(vm_name) == :not_running
|
435
|
+
ui.warn "You are trying to shutdown a VM which is not running"
|
436
|
+
else
|
437
|
+
bhyve_pid = execute(false, "pgrep -fx 'bhyve: #{vm_name}'")
|
438
|
+
loader_pid = execute(false, "pgrep -fl 'grub-bhyve|bhyveload' | grep #{vm_name} | cut -d' ' -f1")
|
439
|
+
if bhyve_pid.length != 0
|
440
|
+
# We need to kill bhyve process twice and wait some time to make
|
441
|
+
# sure VM is shuted down.
|
442
|
+
while bhyve_pid.length != 0
|
443
|
+
begin
|
444
|
+
execute(false, "#{@sudo} kill -s TERM #{bhyve_pid}")
|
445
|
+
sleep 1
|
446
|
+
bhyve_pid = execute(false, "pgrep -fx 'bhyve: #{vm_name}'")
|
447
|
+
rescue Errors::ExecuteError
|
448
|
+
break
|
449
|
+
end
|
450
|
+
end
|
451
|
+
elsif loader_pid.length != 0
|
452
|
+
ui.warn "Guest is going to be exit in bootloader stage"
|
453
|
+
execute(false, "#{@sudo} kill #{loader_pid}")
|
454
|
+
else
|
455
|
+
ui.warn "Unable to locate process id for #{vm_name}"
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
def forward_port(forward_information, tap_device)
|
461
|
+
id = get_attr('id')
|
462
|
+
ip_address = get_ip_address(tap_device)
|
463
|
+
pf_conf = @data_dir.join('pf.conf')
|
464
|
+
rule = "rdr on #{forward_information[:adapter]} proto {udp, tcp} from any to any port #{forward_information[:host_port]} -> #{ip_address} port #{forward_information[:guest_port]}"
|
465
|
+
|
466
|
+
pf_conf.open('a') do |pf_file|
|
467
|
+
pf_file.puts rule
|
468
|
+
end
|
469
|
+
# Update pf rules
|
470
|
+
comment_mark = "# vagrant-bhyve #{tap_device}"
|
471
|
+
if execute(true, "test -s /etc/pf.conf") == 0
|
472
|
+
if execute(true, "grep \"#{comment_mark}\" /etc/pf.conf") != 0
|
473
|
+
execute(false, "#{@sudo} sed -i '' '1i\\\n#{comment_mark}\n' /etc/pf.conf")
|
474
|
+
execute(false, "#{@sudo} sed -i '' '2i\\\ninclude \"#{pf_conf.to_s}\"\n' /etc/pf.conf")
|
475
|
+
end
|
476
|
+
else
|
477
|
+
execute(false, "echo \"#{comment_mark}\" | #{@sudo} tee -a /etc/pf.conf")
|
478
|
+
execute(false, "echo \"include \\\"#{pf_conf.to_s}\\\"\" | #{@sudo} tee -a /etc/pf.conf")
|
479
|
+
end
|
480
|
+
restart_service('pf')
|
481
|
+
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{id}' -f #{pf_conf.to_s}")
|
482
|
+
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{id}' -sr")
|
483
|
+
#execute(false, "#{@sudo} pfctl -a vagrant_#{id} -F all")
|
484
|
+
|
485
|
+
end
|
486
|
+
|
487
|
+
def cleanup
|
488
|
+
bridge = get_attr('bridge')
|
489
|
+
tap = get_attr('tap')
|
490
|
+
vm_name = get_attr('vm_name')
|
491
|
+
id = get_attr('id')
|
492
|
+
mac = get_attr('mac')
|
493
|
+
directory = @data_dir
|
494
|
+
|
495
|
+
return unless bridge && tap
|
496
|
+
# Destroy vmm device
|
497
|
+
execute(false, "#{@sudo} bhyvectl --destroy --vm=#{vm_name} >/dev/null 2>&1") if state(vm_name) == :uncleaned
|
498
|
+
|
499
|
+
# Clean instance-specific pf rules
|
500
|
+
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{id}' -F all")
|
501
|
+
comment_mark_tap = "# vagrant-bhyve #{tap}"
|
502
|
+
if execute(true, "grep \"#{comment_mark_tap}\" /etc/pf.conf") == 0
|
503
|
+
execute(false, "#{@sudo} sed -i '' '/#{comment_mark_tap}/,+1d' /etc/pf.conf")
|
504
|
+
end
|
505
|
+
# Destory tap interfaces
|
506
|
+
execute(false, "#{@sudo} ifconfig #{tap} destroy") if execute(true, "ifconfig #{tap}") == 0
|
507
|
+
execute(false, "#{@sudo} sed -i '' '/#{mac}/d' /var/run/dnsmasq.#{bridge}.leases") if execute(true, "grep \"#{mac}\" /var/run/dnsmasq.#{bridge}.leases") == 0
|
508
|
+
|
509
|
+
# Delete configure files
|
510
|
+
#FileUtils.rm directory.join('dnsmasq.conf').to_s if directory.join('dnsmasq.conf').exist?
|
511
|
+
#FileUtils.rm directory.join('pf.conf').to_s if directory.join('pf.conf').exist?
|
512
|
+
|
513
|
+
# Clean nat configurations if there is no VMS is using the bridge
|
514
|
+
member_num = 3
|
515
|
+
bridge_exist = execute(true, "ifconfig #{bridge}")
|
516
|
+
member_num = execute(false, "ifconfig #{bridge} | grep -c 'member' || true") if bridge_exist == 0
|
517
|
+
|
518
|
+
if bridge_exist != 0 || member_num.to_i < 2
|
519
|
+
#execute(false, "#{@sudo} pfctl -a '/vagrant_#{bridge}' -F all")
|
520
|
+
comment_mark_bridge = "# vagrant-bhyve #{bridge}"
|
521
|
+
if execute(true, "grep \"#{comment_mark_bridge}\" /etc/pf.conf") == 0
|
522
|
+
execute(false, "#{@sudo} sed -i '' '/#{comment_mark_bridge}/,+1d' /etc/pf.conf")
|
523
|
+
end
|
524
|
+
restart_service('pf')
|
525
|
+
#if directory.join('pf_disabled').exist?
|
526
|
+
# FileUtils.rm directory.join('pf_disabled')
|
527
|
+
# execute(false, "#{@sudo} pfctl -d")
|
528
|
+
#end
|
529
|
+
execute(false, "#{@sudo} ifconfig #{bridge} destroy") if bridge_exist == 0
|
530
|
+
pf_conf = "/usr/local/etc/pf.#{bridge}.conf"
|
531
|
+
execute(false, "#{@sudo} rm #{pf_conf}") if execute(true, "test -e #{pf_conf}") == 0
|
532
|
+
if execute(true, "test -e /var/run/dnsmasq.#{bridge}.pid") == 0
|
533
|
+
dnsmasq_cmd = "dnsmasq -C /usr/local/etc/dnsmasq.#{bridge}.conf -l /var/run/dnsmasq.#{bridge}.leases -x /var/run/dnsmasq.#{bridge}.pid"
|
534
|
+
dnsmasq_conf = "/var/run/dnsmasq.#{bridge}.leases"
|
535
|
+
dnsmasq_leases = "/var/run/dnsmasq.#{bridge}.pid"
|
536
|
+
dnsmasq_pid = "/usr/local/etc/dnsmasq.#{bridge}.conf"
|
537
|
+
execute(false, "#{@sudo} kill -9 $(pgrep -fx \"#{dnsmasq_cmd}\")")
|
538
|
+
execute(false, "#{@sudo} rm #{dnsmasq_leases}") if execute(true, "test -e #{dnsmasq_leases}") == 0
|
539
|
+
execute(false, "#{@sudo} rm #{dnsmasq_pid}") if execute(true, "test -e #{dnsmasq_pid}") == 0
|
540
|
+
execute(false, "#{@sudo} rm #{dnsmasq_conf}") if execute(true, "test -e #{dnsmasq_conf}") == 0
|
541
|
+
end
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def state(vm_name)
|
546
|
+
vmm_exist = execute(true, "test -e /dev/vmm/#{vm_name}") == 0
|
547
|
+
if vmm_exist
|
548
|
+
if execute(true, "pgrep -fx \"bhyve: #{vm_name}\"") == 0
|
549
|
+
:running
|
550
|
+
else
|
551
|
+
:uncleaned
|
552
|
+
end
|
553
|
+
else
|
554
|
+
:stopped
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
def execute(*cmd, **opts, &block)
|
559
|
+
@executor.execute(*cmd, **opts, &block)
|
560
|
+
end
|
561
|
+
|
562
|
+
def get_mac_address(vm_name)
|
563
|
+
# Generate a mac address for this tap device from its vm_name
|
564
|
+
# IEEE Standards OUI for bhyve
|
565
|
+
mac = "58:9c:fc:0"
|
566
|
+
mac += Digest::MD5.hexdigest(vm_name).scan(/../).select.with_index{ |_, i| i.even? }[0..2].join(':')[1..-1]
|
567
|
+
end
|
568
|
+
|
569
|
+
# Get the interface name for a bridge(like 'bridge0')
|
570
|
+
def get_interface_name(device_name)
|
571
|
+
desc = device_name + '\$'
|
572
|
+
cmd = "ifconfig -a | grep -B 1 #{desc} | head -n1 | awk -F: '{print $1}'"
|
573
|
+
result = execute(false, cmd)
|
574
|
+
end
|
575
|
+
|
576
|
+
def restart_service(service_name)
|
577
|
+
status = execute(true, "#{@sudo} pfctl -s all | grep -i disabled")
|
578
|
+
if status == 0
|
579
|
+
cmd = "onerestart"
|
580
|
+
else
|
581
|
+
cmd = "onestart"
|
582
|
+
end
|
583
|
+
status = execute(true, "#{@sudo} service #{service_name} #{cmd} >/dev/null 2>&1")
|
584
|
+
raise Errors::RestartServiceFailed if status != 0
|
585
|
+
end
|
586
|
+
|
587
|
+
def pf_enabled?
|
588
|
+
status = execute(true, "#{@sudo} pfctl -s all | grep -i disabled")
|
589
|
+
if status == 0
|
590
|
+
store_attr('pf_disabled', 'yes')
|
591
|
+
false
|
592
|
+
else
|
593
|
+
true
|
594
|
+
end
|
595
|
+
end
|
596
|
+
|
597
|
+
def find_available_nmdm
|
598
|
+
nmdm_num = 0
|
599
|
+
while true
|
600
|
+
result = execute(true, "ls -l /dev/ | grep 'nmdm#{nmdm_num}A'")
|
601
|
+
break if result != 0
|
602
|
+
nmdm_num += 1
|
603
|
+
end
|
604
|
+
nmdm_num
|
605
|
+
end
|
606
|
+
|
607
|
+
def get_attr(attr)
|
608
|
+
name_file = @data_dir.join(attr)
|
609
|
+
if File.exist?(name_file)
|
610
|
+
name_file.open('r') { |f| f.readline }
|
611
|
+
else
|
612
|
+
nil
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
def pkg_install(package)
|
617
|
+
execute(false, "#{@sudo} ASSUME_ALWAYS_YES=yes pkg install #{package}")
|
618
|
+
end
|
619
|
+
|
620
|
+
def store_attr(name, value)
|
621
|
+
@data_dir.join(name).open('w') { |f| f.write value }
|
622
|
+
end
|
623
|
+
|
624
|
+
def check_and_install(command, package, ui)
|
625
|
+
command_exist = execute(true, "which #{command}")
|
626
|
+
if command_exist != 0
|
627
|
+
ui.warn "We need #{command} in #{package} package, installing with pkg..."
|
628
|
+
pkg_install(package)
|
629
|
+
end
|
630
|
+
end
|
631
|
+
|
632
|
+
def grub_bhyve_execute(command, password, member)
|
633
|
+
vm_name = get_attr('vm_name')
|
634
|
+
exp = RubyExpect::Expect.spawn("sudo grub-bhyve -m #{@data_dir.join('device.map').to_s} -M 128M #{vm_name}")
|
635
|
+
if password == ''
|
636
|
+
exp.procedure do
|
637
|
+
each do
|
638
|
+
expect /grub> / do
|
639
|
+
send command
|
640
|
+
end
|
641
|
+
expect /.*(grub> )$/ do
|
642
|
+
send 'exit'
|
643
|
+
end
|
644
|
+
end
|
645
|
+
end
|
646
|
+
else
|
647
|
+
exp.procedure do
|
648
|
+
each do
|
649
|
+
expect /Password:/ do
|
650
|
+
send password
|
651
|
+
end
|
652
|
+
expect /grub> / do
|
653
|
+
send command
|
654
|
+
end
|
655
|
+
expect /.*(grub> )$/ do
|
656
|
+
send 'exit'
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
execute(false, "#{@sudo} bhyvectl --destroy --vm=#{vm_name}")
|
662
|
+
case member
|
663
|
+
when :match
|
664
|
+
return exp.match.to_s
|
665
|
+
when :before
|
666
|
+
return exp.before.to_s
|
667
|
+
when :last_match
|
668
|
+
return exp.last_match.to_s
|
669
|
+
end
|
670
|
+
end
|
671
|
+
|
672
|
+
end
|
673
|
+
end
|
674
|
+
end
|