Merge branch 'mpoeter-VB-linked-clone-support'
This commit is contained in:
commit
b8c7d16878
|
@ -526,7 +526,11 @@ module Vagrant
|
|||
if name != "dotlock"
|
||||
lock("dotlock", retry: true) do
|
||||
f.close
|
||||
File.delete(lock_path)
|
||||
begin
|
||||
File.delete(lock_path)
|
||||
rescue
|
||||
@logger.debug("Failed to delete lock file #{lock_path} - some other thread might be trying to acquire it -> ignoring this error")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -784,6 +784,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
|
||||
|
|
|
@ -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__)
|
||||
|
@ -325,7 +327,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
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,66 @@
|
|||
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[:machine].env.lock(Digest::MD5.hexdigest(env[:machine].box.name), retry: true) do
|
||||
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.
|
||||
@logger.info("Master VM for '#{env[:machine].box.name}' already exists (id=#{env[:master_id]}) - skipping import step.")
|
||||
return @app.call(env)
|
||||
end
|
||||
|
||||
env[:ui].info I18n.t("vagrant.actions.vm.clone.importing", name: env[:machine].box.name)
|
||||
|
||||
# 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
|
||||
end
|
||||
|
||||
# If we got interrupted, then the import could have been
|
||||
# interrupted and its not a big deal. Just return out.
|
||||
if env[:interrupted]
|
||||
@logger.info("Import of master VM was interrupted -> exiting.")
|
||||
return
|
||||
end
|
||||
|
||||
# Import completed successfully. Continue the chain
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -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
|
||||
|
|
|
@ -84,8 +84,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,
|
||||
|
|
|
@ -35,6 +35,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],
|
||||
|
@ -77,6 +86,10 @@ module VagrantPlugins
|
|||
"--ipv6", interface[:ipv6])
|
||||
end
|
||||
|
||||
def create_snapshot(machine_id, snapshot_name)
|
||||
execute("snapshot", machine_id, "take", snapshot_name)
|
||||
end
|
||||
|
||||
def delete
|
||||
execute("unregistervm", @uuid, "--delete")
|
||||
end
|
||||
|
@ -171,6 +184,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
|
||||
|
@ -246,10 +266,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
|
||||
|
|
|
@ -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],
|
||||
|
@ -73,6 +82,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
|
||||
|
@ -167,6 +180,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
|
||||
|
@ -242,10 +262,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
|
||||
|
|
|
@ -1618,6 +1618,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:
|
||||
|
|
|
@ -36,6 +36,30 @@ config.vm.provider "virtualbox" do |v|
|
|||
end
|
||||
```
|
||||
|
||||
## Linked Clones
|
||||
|
||||
By default new machines are created by importing the base box. For large
|
||||
boxes this produces a large overhead in terms of time (the import operation)
|
||||
and space (the new machine contains a copy of the base box's image).
|
||||
Using linked clones can drastically reduce this overhead.
|
||||
|
||||
Linked clones are based on a master VM, which is generated by importing the
|
||||
base box only once the first time it is required. For the linked clones only
|
||||
differencing disk images are created where the parent disk image belongs to
|
||||
the master VM.
|
||||
|
||||
```ruby
|
||||
config.vm.provider "virtualbox" do |v|
|
||||
v.use_linked_clone = true
|
||||
end
|
||||
```
|
||||
|
||||
<div class="alert alert-info">
|
||||
<strong>Note:</strong> the generated master VMs are currently not removed
|
||||
automatically by Vagrant. This has to be done manually. However, a master
|
||||
VM can only be removed when there are no linked clones connected to it.
|
||||
</div>
|
||||
|
||||
## VBoxManage Customizations
|
||||
|
||||
[VBoxManage](http://www.virtualbox.org/manual/ch08.html) is a utility that can
|
||||
|
|
Loading…
Reference in New Issue