Add support for linked clones for VirtualBox.

This commit is contained in:
mpoeter 2014-09-09 19:17:04 +02:00
parent bde0e3fb2a
commit c20624bfdc
8 changed files with 171 additions and 5 deletions

View File

@ -740,6 +740,14 @@ module Vagrant
error_key(:boot_timeout)
end
class VMCloneFailure < VagrantError
error_key(:failure, "vagrant.actions.vm.clone")
end
class VMCreateMasterFailure < VagrantError
error_key(:failure, "vagrant.actions.vm.clone.create_master")
end
class VMCustomizationFailed < VagrantError
error_key(:failure, "vagrant.actions.vm.customize")
end

View File

@ -12,6 +12,7 @@ module VagrantPlugins
autoload :CleanMachineFolder, File.expand_path("../action/clean_machine_folder", __FILE__)
autoload :ClearForwardedPorts, File.expand_path("../action/clear_forwarded_ports", __FILE__)
autoload :ClearNetworkInterfaces, File.expand_path("../action/clear_network_interfaces", __FILE__)
autoload :CreateClone, File.expand_path("../action/create_clone", __FILE__)
autoload :Created, File.expand_path("../action/created", __FILE__)
autoload :Customize, File.expand_path("../action/customize", __FILE__)
autoload :Destroy, File.expand_path("../action/destroy", __FILE__)
@ -21,6 +22,7 @@ module VagrantPlugins
autoload :ForcedHalt, File.expand_path("../action/forced_halt", __FILE__)
autoload :ForwardPorts, File.expand_path("../action/forward_ports", __FILE__)
autoload :Import, File.expand_path("../action/import", __FILE__)
autoload :ImportMaster, File.expand_path("../action/import_master", __FILE__)
autoload :IsPaused, File.expand_path("../action/is_paused", __FILE__)
autoload :IsRunning, File.expand_path("../action/is_running", __FILE__)
autoload :IsSaved, File.expand_path("../action/is_saved", __FILE__)
@ -313,7 +315,13 @@ module VagrantPlugins
if !env[:result]
b2.use CheckAccessible
b2.use Customize, "pre-import"
b2.use Import
if env[:machine].provider_config.use_linked_clone
b2.use ImportMaster
b2.use CreateClone
else
b2.use Import
end
b2.use MatchMACAddress
end
end

View File

@ -0,0 +1,51 @@
require "log4r"
#require "lockfile"
module VagrantPlugins
module ProviderVirtualBox
module Action
class CreateClone
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::action::vm::clone")
end
def call(env)
@logger.info("Creating linked clone from master '#{env[:master_id]}'")
env[:ui].info I18n.t("vagrant.actions.vm.clone.creating", name: env[:machine].box.name)
env[:machine].id = env[:machine].provider.driver.clonevm(env[:master_id], env[:machine].box.name, "base") do |progress|
env[:ui].clear_line
env[:ui].report_progress(progress, 100, false)
end
# Clear the line one last time since the progress meter doesn't disappear immediately.
env[:ui].clear_line
# Flag as erroneous and return if clone failed
raise Vagrant::Errors::VMCloneFailure if !env[:machine].id
# Continue
@app.call(env)
end
def recover(env)
if env[:machine].state.id != :not_created
return if env["vagrant.error"].is_a?(Vagrant::Errors::VagrantError)
# If we're not supposed to destroy on error then just return
return if !env[:destroy_on_error]
# Interrupted, destroy the VM. We note that we don't want to
# validate the configuration here, and we don't want to confirm
# we want to destroy.
destroy_env = env.clone
destroy_env[:config_validate] = false
destroy_env[:force_confirm_destroy] = true
env[:action_runner].run(Action.action_destroy, destroy_env)
end
end
end
end
end
end

View File

@ -0,0 +1,62 @@
require "log4r"
#require "lockfile"
module VagrantPlugins
module ProviderVirtualBox
module Action
class ImportMaster
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::action::vm::create_master")
end
def call(env)
master_id_file = env[:machine].box.directory.join("master_id")
env[:master_id] = master_id_file.read.chomp if master_id_file.file?
if env[:master_id] && env[:machine].provider.driver.vm_exists?(env[:master_id])
# Master VM already exists -> nothing to do - continue.
@app.call(env)
end
env[:ui].info I18n.t("vagrant.actions.vm.clone.importing", name: env[:machine].box.name)
#TODO - prevent concurrent creation of master vms for the same box.
# Import the virtual machine
ovf_file = env[:machine].box.directory.join("box.ovf").to_s
env[:master_id] = env[:machine].provider.driver.import(ovf_file) do |progress|
env[:ui].clear_line
env[:ui].report_progress(progress, 100, false)
end
# Clear the line one last time since the progress meter doesn't disappear immediately.
env[:ui].clear_line
# Flag as erroneous and return if import failed
raise Vagrant::Errors::VMImportFailure if !env[:master_id]
@logger.info("Imported box #{env[:machine].box.name} as master vm with id #{env[:master_id]}")
@logger.info("Creating base snapshot for master VM.")
env[:machine].provider.driver.create_snapshot(env[:master_id], "base")do |progress|
env[:ui].clear_line
env[:ui].report_progress(progress, 100, false)
end
@logger.debug("Writing id of master VM '#{env[:master_id]}' to #{master_id_file}")
master_id_file.open("w+") do |f|
f.write(env[:master_id])
end
# If we got interrupted, then the import could have been
# interrupted and its not a big deal. Just return out.
return if env[:interrupted]
# Import completed successfully. Continue the chain
@app.call(env)
end
end
end
end
end

View File

@ -32,6 +32,12 @@ module VagrantPlugins
# @return [Boolean]
attr_accessor :gui
# If set to `true`, then a linked clone is created from a master
# VM generated from the specified box.
#
# @return [Boolean]
attr_accessor :use_linked_clone
# This should be set to the name of the machine in the VirtualBox
# GUI.
#
@ -59,6 +65,7 @@ module VagrantPlugins
@name = UNSET_VALUE
@network_adapters = {}
@gui = UNSET_VALUE
@use_linked_clone = UNSET_VALUE
# We require that network adapter 1 is a NAT device.
network_adapter(1, :nat)
@ -136,6 +143,9 @@ module VagrantPlugins
# Default is to not show a GUI
@gui = false if @gui == UNSET_VALUE
# Do not create linked clone by default
@use_linked_clone = false if @use_linked_clone == UNSET_VALUE
# The default name is just nothing, and we default it
@name = nil if @name == UNSET_VALUE
end

View File

@ -79,8 +79,10 @@ module VagrantPlugins
def_delegators :@driver, :clear_forwarded_ports,
:clear_shared_folders,
:clonevm,
:create_dhcp_server,
:create_host_only_network,
:create_snapshot,
:delete,
:delete_unused_host_only_networks,
:discard_saved_state,

View File

@ -34,6 +34,15 @@ module VagrantPlugins
end
end
def clonevm(master_id, box_name, snapshot_name)
@logger.debug("Creating linked clone from master vm with id #{master_id} from snapshot '#{snapshot_name}'")
machine_name = "#{box_name}_#{snapshot_name}_clone_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}"
execute("clonevm", master_id, "--snapshot", snapshot_name, "--options", "link", "--register", "--name", machine_name)
return get_machine_id machine_name
end
def create_dhcp_server(network, options)
execute("dhcpserver", "add", "--ifname", network,
"--ip", options[:dhcp_ip],
@ -62,6 +71,10 @@ module VagrantPlugins
}
end
def create_snapshot(machine_id, snapshot_name)
execute("snapshot", machine_id, "take", snapshot_name)
end
def delete
execute("unregistervm", @uuid, "--delete")
end
@ -156,6 +169,13 @@ module VagrantPlugins
execute("modifyvm", @uuid, *args) if !args.empty?
end
def get_machine_id(machine_name)
output = execute("list", "vms", retryable: true)
match = /^"#{Regexp.escape(machine_name)}" \{(.+?)\}$/.match(output)
return match[1].to_s if match
nil
end
def halt
execute("controlvm", @uuid, "poweroff")
end
@ -231,10 +251,7 @@ module VagrantPlugins
end
end
output = execute("list", "vms", retryable: true)
match = /^"#{Regexp.escape(specified_name)}" \{(.+?)\}$/.match(output)
return match[1].to_s if match
nil
return get_machine_id specified_name
end
def max_network_adapters

View File

@ -1482,6 +1482,14 @@ en:
deleting: Clearing any previously set network interfaces...
clear_shared_folders:
deleting: Cleaning previously set shared folders...
clone:
importing: Importing box '%{name}' as master vm...
creating: Creating linked clone...
failure: Creation of the linked clone failed.
create_master:
failure: |-
Failed to create lock-file for master VM creation for box %{box}.
customize:
failure: |-
A customization command failed: