VirtualBox 4.2 support

This commit is contained in:
Mitchell Hashimoto 2012-09-13 19:11:32 -07:00
parent 4c49dfe82b
commit 678c6a070d
4 changed files with 465 additions and 1 deletions

View File

@ -1,5 +1,6 @@
## 1.1.0 (unreleased) ## 1.1.0 (unreleased)
- VirtualBox 4.2 support.
- New plugin system which adds much more structure and stability to - New plugin system which adds much more structure and stability to
the overall API. The goal of this system is to make it easier to write the overall API. The goal of this system is to make it easier to write
powerful plugins for Vagrant while providing a backwards-compatible API powerful plugins for Vagrant while providing a backwards-compatible API

View File

@ -42,7 +42,8 @@ module VagrantPlugins
@logger.debug("Finding driver for VirtualBox version: #{@version}") @logger.debug("Finding driver for VirtualBox version: #{@version}")
driver_map = { driver_map = {
"4.0" => Version_4_0, "4.0" => Version_4_0,
"4.1" => Version_4_1 "4.1" => Version_4_1,
"4.2" => Version_4_2
} }
driver_klass = nil driver_klass = nil

View File

@ -0,0 +1,461 @@
require 'log4r'
require File.expand_path("../base", __FILE__)
module VagrantPlugins
module ProviderVirtualBox
module Driver
# Driver for VirtualBox 4.2.x
class Version_4_2 < Base
def initialize(uuid)
super()
@logger = Log4r::Logger.new("vagrant::provider::virtualbox_4_2")
@uuid = uuid
end
def clear_forwarded_ports
args = []
read_forwarded_ports(@uuid).each do |nic, name, _, _|
args.concat(["--natpf#{nic}", "delete", name])
end
execute("modifyvm", @uuid, *args) if !args.empty?
end
def clear_shared_folders
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/
execute("sharedfolder", "remove", @uuid, "--name", $1.to_s)
end
end
end
def create_dhcp_server(network, options)
execute("dhcpserver", "add", "--ifname", network,
"--ip", options[:dhcp_ip],
"--netmask", options[:netmask],
"--lowerip", options[:dhcp_lower],
"--upperip", options[:dhcp_upper],
"--enable")
end
def create_host_only_network(options)
# Create the interface
execute("hostonlyif", "create") =~ /^Interface '(.+?)' was successfully created$/
name = $1.to_s
# Configure it
execute("hostonlyif", "ipconfig", name,
"--ip", options[:adapter_ip],
"--netmask", options[:netmask])
# Return the details
return {
:name => name,
:ip => options[:adapter_ip],
:netmask => options[:netmask],
:dhcp => nil
}
end
def delete
execute("unregistervm", @uuid, "--delete")
end
def delete_unused_host_only_networks
networks = []
execute("list", "hostonlyifs").split("\n").each do |line|
networks << $1.to_s if line =~ /^Name:\s+(.+?)$/
end
execute("list", "vms").split("\n").each do |line|
if line =~ /^".+?"\s+\{(.+?)\}$/
info = execute("showvminfo", $1.to_s, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if line =~ /^hostonlyadapter\d+="(.+?)"$/
networks.delete($1.to_s)
end
end
end
end
networks.each do |name|
# First try to remove any DHCP servers attached. We use `raw` because
# it is okay if this fails. It usually means that a DHCP server was
# never attached.
raw("dhcpserver", "remove", "--ifname", name)
# Delete the actual host only network interface.
execute("hostonlyif", "remove", name)
end
end
def discard_saved_state
execute("discardstate", @uuid)
end
def enable_adapters(adapters)
args = []
adapters.each do |adapter|
args.concat(["--nic#{adapter[:adapter]}", adapter[:type].to_s])
if adapter[:bridge]
args.concat(["--bridgeadapter#{adapter[:adapter]}",
adapter[:bridge]])
end
if adapter[:hostonly]
args.concat(["--hostonlyadapter#{adapter[:adapter]}",
adapter[:hostonly]])
end
if adapter[:mac_address]
args.concat(["--macaddress#{adapter[:adapter]}",
adapter[:mac_address]])
end
if adapter[:nic_type]
args.concat(["--nictype#{adapter[:adapter]}", adapter[:nic_type].to_s])
end
end
execute("modifyvm", @uuid, *args)
end
def execute_command(command)
raw(*command)
end
def export(path)
# TODO: Progress
execute("export", @uuid, "--output", path.to_s)
end
def forward_ports(ports)
args = []
ports.each do |options|
pf_builder = [options[:name],
options[:protocol] || "tcp",
"",
options[:hostport],
"",
options[:guestport]]
args.concat(["--natpf#{options[:adapter] || 1}",
pf_builder.join(",")])
end
execute("modifyvm", @uuid, *args) if !args.empty?
end
def halt
execute("controlvm", @uuid, "poweroff")
end
def import(ovf)
output = ""
total = ""
last = 0
execute("import", ovf) do |type, data|
if type == :stdout
# Keep track of the stdout so that we can get the VM name
output << data
elsif type == :stderr
# Append the data so we can see the full view
total << data
# Break up the lines. We can't get the progress until we see an "OK"
lines = total.split("\n")
if lines.include?("OK.")
# The progress of the import will be in the last line. Do a greedy
# regular expression to find what we're looking for.
if lines.last =~ /.+(\d{2})%/
current = $1.to_i
if current > last
last = current
yield current if block_given?
end
end
end
end
end
# Find the name of the VM name
if output !~ /Suggested VM name "(.+?)"/
@logger.error("Couldn't find VM name in the output.")
return nil
end
name = $1.to_s
output = execute("list", "vms")
if output =~ /^"#{Regexp.escape(name)}" \{(.+?)\}$/
return $1.to_s
end
nil
end
def read_forwarded_ports(uuid=nil, active_only=false)
uuid ||= @uuid
@logger.debug("read_forward_ports: uuid=#{uuid} active_only=#{active_only}")
results = []
current_nic = nil
info = execute("showvminfo", uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
# This is how we find the nic that a FP is attached to,
# since this comes first.
current_nic = $1.to_i if line =~ /^nic(\d+)=".+?"$/
# If we care about active VMs only, then we check the state
# to verify the VM is running.
if active_only && line =~ /^VMState="(.+?)"$/ && $1.to_s != "running"
return []
end
# Parse out the forwarded port information
if line =~ /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/
result = [current_nic, $1.to_s, $2.to_i, $3.to_i]
@logger.debug(" - #{result.inspect}")
results << result
end
end
results
end
def read_bridged_interfaces
execute("list", "bridgedifs").split("\n\n").collect do |block|
info = {}
block.split("\n").each do |line|
if line =~ /^Name:\s+(.+?)$/
info[:name] = $1.to_s
elsif line =~ /^IPAddress:\s+(.+?)$/
info[:ip] = $1.to_s
elsif line =~ /^NetworkMask:\s+(.+?)$/
info[:netmask] = $1.to_s
elsif line =~ /^Status:\s+(.+?)$/
info[:status] = $1.to_s
end
end
# Return the info to build up the results
info
end
end
def read_guest_additions_version
output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version",
:retryable => true)
if output =~ /^Value: (.+?)$/
# Split the version by _ since some distro versions modify it
# to look like this: 4.1.2_ubuntu, and the distro part isn't
# too important.
value = $1.to_s
return value.split("_").first
end
return nil
end
def read_host_only_interfaces
dhcp = {}
execute("list", "dhcpservers", :retryable => true).split("\n\n").each do |block|
info = {}
block.split("\n").each do |line|
if line =~ /^NetworkName:\s+HostInterfaceNetworking-(.+?)$/
info[:network] = $1.to_s
elsif line =~ /^IP:\s+(.+?)$/
info[:ip] = $1.to_s
elsif line =~ /^lowerIPAddress:\s+(.+?)$/
info[:lower] = $1.to_s
elsif line =~ /^upperIPAddress:\s+(.+?)$/
info[:upper] = $1.to_s
end
end
# Set the DHCP info
dhcp[info[:network]] = info
end
execute("list", "hostonlyifs", :retryable => true).split("\n\n").collect do |block|
info = {}
block.split("\n").each do |line|
if line =~ /^Name:\s+(.+?)$/
info[:name] = $1.to_s
elsif line =~ /^IPAddress:\s+(.+?)$/
info[:ip] = $1.to_s
elsif line =~ /^NetworkMask:\s+(.+?)$/
info[:netmask] = $1.to_s
elsif line =~ /^Status:\s+(.+?)$/
info[:status] = $1.to_s
end
end
# Set the DHCP info if it exists
info[:dhcp] = dhcp[info[:name]] if dhcp[info[:name]]
info
end
end
def read_mac_address
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
return $1.to_s if line =~ /^macaddress1="(.+?)"$/
end
nil
end
def read_machine_folder
execute("list", "systemproperties", :retryable => true).split("\n").each do |line|
if line =~ /^Default machine folder:\s+(.+?)$/i
return $1.to_s
end
end
nil
end
def read_network_interfaces
nics = {}
info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
info.split("\n").each do |line|
if line =~ /^nic(\d+)="(.+?)"$/
adapter = $1.to_i
type = $2.to_sym
nics[adapter] ||= {}
nics[adapter][:type] = type
elsif line =~ /^hostonlyadapter(\d+)="(.+?)"$/
adapter = $1.to_i
network = $2.to_s
nics[adapter] ||= {}
nics[adapter][:hostonly] = network
elsif line =~ /^bridgeadapter(\d+)="(.+?)"$/
adapter = $1.to_i
network = $2.to_s
nics[adapter] ||= {}
nics[adapter][:bridge] = network
end
end
nics
end
def read_state
output = execute("showvminfo", @uuid, "--machinereadable", :retryable => true)
if output =~ /^name="<inaccessible>"$/
return :inaccessible
elsif output =~ /^VMState="(.+?)"$/
return $1.to_sym
end
nil
end
def read_used_ports
ports = []
execute("list", "vms", :retryable => true).split("\n").each do |line|
if line =~ /^".+?" \{(.+?)\}$/
uuid = $1.to_s
# Ignore our own used ports
next if uuid == @uuid
read_forwarded_ports(uuid, true).each do |_, _, hostport, _|
ports << hostport
end
end
end
ports
end
def read_vms
results = []
execute("list", "vms", :retryable => true).split("\n").each do |line|
if line =~ /^".+?" \{(.+?)\}$/
results << $1.to_s
end
end
results
end
def set_mac_address(mac)
execute("modifyvm", @uuid, "--macaddress1", mac)
end
def set_name(name)
execute("modifyvm", @uuid, "--name", name)
end
def share_folders(folders)
folders.each do |folder|
args = ["--name",
folder[:name],
"--hostpath",
folder[:hostpath]]
args << "--transient" if folder.has_key?(:transient) && folder[:transient]
execute("sharedfolder", "add", @uuid, *args)
end
end
def ssh_port(expected_port)
@logger.debug("Searching for SSH port: #{expected_port.inspect}")
# Look for the forwarded port only by comparing the guest port
read_forwarded_ports.each do |_, _, hostport, guestport|
return hostport if guestport == expected_port
end
nil
end
def start(mode)
command = ["startvm", @uuid, "--type", mode.to_s]
r = raw(*command)
if r.exit_code == 0 || r.stdout =~ /VM ".+?" has been successfully started/
# Some systems return an exit code 1 for some reason. For that
# we depend on the output.
return true
end
# If we reached this point then it didn't work out.
raise Errors::VBoxManageError, :command => command.inspect
end
def suspend
execute("controlvm", @uuid, "savestate")
end
def verify!
# This command sometimes fails if kernel drivers aren't properly loaded
# so we just run the command and verify that it succeeded.
execute("list", "hostonlyifs")
end
def verify_image(path)
r = raw("import", path.to_s, "--dry-run")
return r.exit_code == 0
end
def vm_exists?(uuid)
raw("showvminfo", uuid).exit_code == 0
end
end
end
end
end

View File

@ -23,6 +23,7 @@ module VagrantPlugins
autoload :Meta, File.expand_path("../driver/meta", __FILE__) autoload :Meta, File.expand_path("../driver/meta", __FILE__)
autoload :Version_4_0, File.expand_path("../driver/version_4_0", __FILE__) autoload :Version_4_0, File.expand_path("../driver/version_4_0", __FILE__)
autoload :Version_4_1, File.expand_path("../driver/version_4_1", __FILE__) autoload :Version_4_1, File.expand_path("../driver/version_4_1", __FILE__)
autoload :Version_4_2, File.expand_path("../driver/version_4_2", __FILE__)
end end
end end
end end