8468 - make more virtualbox commands retryable
Issue: https://github.com/mitchellh/vagrant/issues/8468 A lot of vboxmanage commands are flakey and frequently cause bringing multiple machines up at once to fail, especially when the host system is under heavy load. Most commands are also safe to retry and just result in a no-op, so we can simply add 'retryable' to a lot of existing calls. For the others we need to do a little bit of cleanup or reevaluate the parameters before trying again.
This commit is contained in:
parent
9aecd5e1a4
commit
2e775e8101
|
@ -17,19 +17,23 @@ module VagrantPlugins
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_forwarded_ports
|
def clear_forwarded_ports
|
||||||
args = []
|
retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
|
||||||
read_forwarded_ports(@uuid).each do |nic, name, _, _|
|
args = []
|
||||||
args.concat(["--natpf#{nic}", "delete", name])
|
read_forwarded_ports(@uuid).each do |nic, name, _, _|
|
||||||
end
|
args.concat(["--natpf#{nic}", "delete", name])
|
||||||
|
end
|
||||||
|
|
||||||
execute("modifyvm", @uuid, *args) if !args.empty?
|
execute("modifyvm", @uuid, *args) if !args.empty?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear_shared_folders
|
def clear_shared_folders
|
||||||
info = execute("showvminfo", @uuid, "--machinereadable", retryable: true)
|
retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
|
||||||
info.split("\n").each do |line|
|
info = execute("showvminfo", @uuid, "--machinereadable", retryable: true)
|
||||||
if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/
|
info.split("\n").each do |line|
|
||||||
execute("sharedfolder", "remove", @uuid, "--name", $1.to_s)
|
if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/
|
||||||
|
execute("sharedfolder", "remove", @uuid, "--name", $1.to_s)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -41,22 +45,29 @@ module VagrantPlugins
|
||||||
args += ["--snapshot", snapshot_name, "--options", "link"]
|
args += ["--snapshot", snapshot_name, "--options", "link"]
|
||||||
end
|
end
|
||||||
|
|
||||||
execute("clonevm", master_id, *args)
|
execute("clonevm", master_id, *args, retryable: true)
|
||||||
return get_machine_id(machine_name)
|
return get_machine_id(machine_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_dhcp_server(network, options)
|
def create_dhcp_server(network, options)
|
||||||
execute("dhcpserver", "add", "--ifname", network,
|
retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
|
||||||
"--ip", options[:dhcp_ip],
|
begin
|
||||||
"--netmask", options[:netmask],
|
execute("dhcpserver", "add", "--ifname", network,
|
||||||
"--lowerip", options[:dhcp_lower],
|
"--ip", options[:dhcp_ip],
|
||||||
"--upperip", options[:dhcp_upper],
|
"--netmask", options[:netmask],
|
||||||
"--enable")
|
"--lowerip", options[:dhcp_lower],
|
||||||
|
"--upperip", options[:dhcp_upper],
|
||||||
|
"--enable")
|
||||||
|
rescue Vagrant::Errors::VBoxManageError => e
|
||||||
|
return if e.extra_data[:stderr] == 'VBoxManage: error: DHCP server already exists'
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_host_only_network(options)
|
def create_host_only_network(options)
|
||||||
# Create the interface
|
# Create the interface
|
||||||
execute("hostonlyif", "create") =~ /^Interface '(.+?)' was successfully created$/
|
execute("hostonlyif", "create", retryable: true) =~ /^Interface '(.+?)' was successfully created$/
|
||||||
name = $1.to_s
|
name = $1.to_s
|
||||||
|
|
||||||
# Get the IP so we can determine v4 vs v6
|
# Get the IP so we can determine v4 vs v6
|
||||||
|
@ -66,11 +77,13 @@ module VagrantPlugins
|
||||||
if ip.ipv4?
|
if ip.ipv4?
|
||||||
execute("hostonlyif", "ipconfig", name,
|
execute("hostonlyif", "ipconfig", name,
|
||||||
"--ip", options[:adapter_ip],
|
"--ip", options[:adapter_ip],
|
||||||
"--netmask", options[:netmask])
|
"--netmask", options[:netmask],
|
||||||
|
retryable: true)
|
||||||
elsif ip.ipv6?
|
elsif ip.ipv6?
|
||||||
execute("hostonlyif", "ipconfig", name,
|
execute("hostonlyif", "ipconfig", name,
|
||||||
"--ipv6", options[:adapter_ip],
|
"--ipv6", options[:adapter_ip],
|
||||||
"--netmasklengthv6", options[:netmask].to_s)
|
"--netmasklengthv6", options[:netmask].to_s,
|
||||||
|
retryable: true)
|
||||||
else
|
else
|
||||||
raise "BUG: Unknown IP type: #{ip.inspect}"
|
raise "BUG: Unknown IP type: #{ip.inspect}"
|
||||||
end
|
end
|
||||||
|
@ -85,7 +98,26 @@ module VagrantPlugins
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_snapshot(machine_id, snapshot_name)
|
def create_snapshot(machine_id, snapshot_name)
|
||||||
execute("snapshot", machine_id, "take", snapshot_name)
|
execute("snapshot", machine_id, "take", snapshot_name, retryable: true) do |type, data|
|
||||||
|
if type == :stderr
|
||||||
|
# Append the data so we can see the full view
|
||||||
|
total << data.gsub("\r", "")
|
||||||
|
|
||||||
|
# Break up the lines. We can't get the progress until we see an "OK"
|
||||||
|
lines = total.split("\n")
|
||||||
|
|
||||||
|
# The progress of the import will be in the last line. Do a greedy
|
||||||
|
# regular expression to find what we're looking for.
|
||||||
|
match = /.+(\d{2})%/.match(lines.last)
|
||||||
|
if match
|
||||||
|
current = match[1].to_i
|
||||||
|
if current > last
|
||||||
|
last = current
|
||||||
|
yield current if block_given?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_snapshot(machine_id, snapshot_name)
|
def delete_snapshot(machine_id, snapshot_name)
|
||||||
|
@ -95,7 +127,7 @@ module VagrantPlugins
|
||||||
yield 0 if block_given?
|
yield 0 if block_given?
|
||||||
|
|
||||||
# Snapshot and report the % progress
|
# Snapshot and report the % progress
|
||||||
execute("snapshot", machine_id, "delete", snapshot_name) do |type, data|
|
execute("snapshot", machine_id, "delete", snapshot_name, retryable: true) do |type, data|
|
||||||
if type == :stderr
|
if type == :stderr
|
||||||
# Append the data so we can see the full view
|
# Append the data so we can see the full view
|
||||||
total << data.gsub("\r", "")
|
total << data.gsub("\r", "")
|
||||||
|
@ -142,7 +174,7 @@ module VagrantPlugins
|
||||||
total = ""
|
total = ""
|
||||||
yield 0 if block_given?
|
yield 0 if block_given?
|
||||||
|
|
||||||
execute("snapshot", machine_id, "restore", snapshot_name) do |type, data|
|
execute("snapshot", machine_id, "restore", snapshot_name, retryable: true) do |type, data|
|
||||||
if type == :stderr
|
if type == :stderr
|
||||||
# Append the data so we can see the full view
|
# Append the data so we can see the full view
|
||||||
total << data.gsub("\r", "")
|
total << data.gsub("\r", "")
|
||||||
|
@ -165,7 +197,7 @@ module VagrantPlugins
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete
|
def delete
|
||||||
execute("unregistervm", @uuid, "--delete")
|
execute("unregistervm", @uuid, "--delete", retryable: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_unused_host_only_networks
|
def delete_unused_host_only_networks
|
||||||
|
@ -199,12 +231,12 @@ module VagrantPlugins
|
||||||
raw("dhcpserver", "remove", "--ifname", name)
|
raw("dhcpserver", "remove", "--ifname", name)
|
||||||
|
|
||||||
# Delete the actual host only network interface.
|
# Delete the actual host only network interface.
|
||||||
execute("hostonlyif", "remove", name)
|
execute("hostonlyif", "remove", name, retryable: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def discard_saved_state
|
def discard_saved_state
|
||||||
execute("discardstate", @uuid)
|
execute("discardstate", @uuid, retryable: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def enable_adapters(adapters)
|
def enable_adapters(adapters)
|
||||||
|
@ -237,7 +269,7 @@ module VagrantPlugins
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
execute("modifyvm", @uuid, *args)
|
execute("modifyvm", @uuid, *args, retryable: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def execute_command(command)
|
def execute_command(command)
|
||||||
|
@ -245,11 +277,20 @@ module VagrantPlugins
|
||||||
end
|
end
|
||||||
|
|
||||||
def export(path)
|
def export(path)
|
||||||
execute("export", @uuid, "--output", path.to_s)
|
retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
|
||||||
|
begin
|
||||||
|
execute("export", @uuid, "--output", path.to_s)
|
||||||
|
rescue Vagrant::Errors::VBoxManageError => e
|
||||||
|
raise if !e.extra_data[:stderr].include?("VERR_E_FILE_ERROR")
|
||||||
|
|
||||||
|
# If the file already exists we'll throw a custom error
|
||||||
|
raise Vagrant::Errors::VirtualBoxFileExists,
|
||||||
|
stderr: e.extra_data[:stderr]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def forward_ports(ports)
|
def forward_ports(ports)
|
||||||
args = []
|
|
||||||
ports.each do |options|
|
ports.each do |options|
|
||||||
pf_builder = [options[:name],
|
pf_builder = [options[:name],
|
||||||
options[:protocol] || "tcp",
|
options[:protocol] || "tcp",
|
||||||
|
@ -258,11 +299,11 @@ module VagrantPlugins
|
||||||
options[:guestip] || "",
|
options[:guestip] || "",
|
||||||
options[:guestport]]
|
options[:guestport]]
|
||||||
|
|
||||||
args.concat(["--natpf#{options[:adapter] || 1}",
|
args = ["--natpf#{options[:adapter] || 1}",
|
||||||
pf_builder.join(",")])
|
pf_builder.join(",")]
|
||||||
end
|
|
||||||
|
|
||||||
execute("modifyvm", @uuid, *args) if !args.empty?
|
execute("modifyvm", @uuid, *args, retryable: true)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_machine_id(machine_name)
|
def get_machine_id(machine_name)
|
||||||
|
@ -273,7 +314,7 @@ module VagrantPlugins
|
||||||
end
|
end
|
||||||
|
|
||||||
def halt
|
def halt
|
||||||
execute("controlvm", @uuid, "poweroff")
|
execute("controlvm", @uuid, "poweroff", retryable: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def import(ovf)
|
def import(ovf)
|
||||||
|
@ -322,7 +363,7 @@ module VagrantPlugins
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
execute("import", ovf , *name_params, *disk_params) do |type, data|
|
execute("import", ovf , *name_params, *disk_params, retryable: true) do |type, data|
|
||||||
if type == :stdout
|
if type == :stdout
|
||||||
# Keep track of the stdout so that we can get the VM name
|
# Keep track of the stdout so that we can get the VM name
|
||||||
output << data
|
output << data
|
||||||
|
@ -604,26 +645,30 @@ module VagrantPlugins
|
||||||
|
|
||||||
def reconfig_host_only(interface)
|
def reconfig_host_only(interface)
|
||||||
execute("hostonlyif", "ipconfig", interface[:name],
|
execute("hostonlyif", "ipconfig", interface[:name],
|
||||||
"--ipv6", interface[:ipv6])
|
"--ipv6", interface[:ipv6], retryable: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_dhcp_server(network_name)
|
def remove_dhcp_server(network_name)
|
||||||
execute("dhcpserver", "remove", "--netname", network_name)
|
execute("dhcpserver", "remove", "--netname", network_name, retryable: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_mac_address(mac)
|
def set_mac_address(mac)
|
||||||
execute("modifyvm", @uuid, "--macaddress1", mac)
|
execute("modifyvm", @uuid, "--macaddress1", mac, retryable: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_name(name)
|
def set_name(name)
|
||||||
execute("modifyvm", @uuid, "--name", name, retryable: true)
|
retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
|
||||||
rescue Vagrant::Errors::VBoxManageError => e
|
begin
|
||||||
raise if !e.extra_data[:stderr].include?("VERR_ALREADY_EXISTS")
|
execute("modifyvm", @uuid, "--name", name)
|
||||||
|
rescue Vagrant::Errors::VBoxManageError => e
|
||||||
|
raise if !e.extra_data[:stderr].include?("VERR_ALREADY_EXISTS")
|
||||||
|
|
||||||
# We got VERR_ALREADY_EXISTS. This means that we're renaming to
|
# We got VERR_ALREADY_EXISTS. This means that we're renaming to
|
||||||
# a VM name that already exists. Raise a custom error.
|
# a VM name that already exists. Raise a custom error.
|
||||||
raise Vagrant::Errors::VirtualBoxNameExists,
|
raise Vagrant::Errors::VirtualBoxNameExists,
|
||||||
stderr: e.extra_data[:stderr]
|
stderr: e.extra_data[:stderr]
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def share_folders(folders)
|
def share_folders(folders)
|
||||||
|
@ -647,10 +692,10 @@ module VagrantPlugins
|
||||||
args << "--transient" if folder.key?(:transient) && folder[:transient]
|
args << "--transient" if folder.key?(:transient) && folder[:transient]
|
||||||
|
|
||||||
# Enable symlinks on the shared folder
|
# Enable symlinks on the shared folder
|
||||||
execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1")
|
execute("setextradata", @uuid, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{folder[:name]}", "1", retryable: true)
|
||||||
|
|
||||||
# Add the shared folder
|
# Add the shared folder
|
||||||
execute("sharedfolder", "add", @uuid, *args)
|
execute("sharedfolder", "add", @uuid, *args, retryable: true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -672,40 +717,40 @@ module VagrantPlugins
|
||||||
|
|
||||||
def start(mode)
|
def start(mode)
|
||||||
command = ["startvm", @uuid, "--type", mode.to_s]
|
command = ["startvm", @uuid, "--type", mode.to_s]
|
||||||
r = raw(*command)
|
retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
|
||||||
|
r = raw(*command)
|
||||||
|
|
||||||
if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/
|
if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/
|
||||||
# Some systems return an exit code 1 for some reason. For that
|
# Some systems return an exit code 1 for some reason. For that
|
||||||
# we depend on the output.
|
# we depend on the output.
|
||||||
return true
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
# If we reached this point then it didn't work out.
|
||||||
|
raise Vagrant::Errors::VBoxManageError,
|
||||||
|
command: command.inspect,
|
||||||
|
stderr: r.stderr
|
||||||
end
|
end
|
||||||
|
|
||||||
# If we reached this point then it didn't work out.
|
|
||||||
raise Vagrant::Errors::VBoxManageError,
|
|
||||||
command: command.inspect,
|
|
||||||
stderr: r.stderr
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def suspend
|
def suspend
|
||||||
execute("controlvm", @uuid, "savestate")
|
execute("controlvm", @uuid, "savestate", retryable: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unshare_folders(names)
|
def unshare_folders(names)
|
||||||
names.each do |name|
|
names.each do |name|
|
||||||
begin
|
retryable(on: Vagrant::Errors::VBoxManageError, tries: 3, sleep: 1) do
|
||||||
execute(
|
begin
|
||||||
"sharedfolder", "remove", @uuid,
|
execute(
|
||||||
"--name", name,
|
"sharedfolder", "remove", @uuid,
|
||||||
"--transient")
|
"--name", name,
|
||||||
|
"--transient")
|
||||||
|
|
||||||
execute(
|
execute(
|
||||||
"setextradata", @uuid,
|
"setextradata", @uuid,
|
||||||
"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}")
|
"VBoxInternal2/SharedFoldersEnableSymlinksCreate/#{name}")
|
||||||
rescue Vagrant::Errors::VBoxManageError => e
|
rescue Vagrant::Errors::VBoxManageError => e
|
||||||
if e.extra_data[:stderr].include?("VBOX_E_FILE_ERROR")
|
raise if !e.extra_data[:stderr].include?("VBOX_E_FILE_ERROR")
|
||||||
# The folder doesn't exist. ignore.
|
|
||||||
else
|
|
||||||
raise
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue