VirtualBox base class for the driver.

This commit is contained in:
Mitchell Hashimoto 2012-01-07 17:52:47 -08:00
parent cab625c06c
commit 767ee2509e
4 changed files with 290 additions and 132 deletions

View File

@ -1,19 +1,18 @@
require 'log4r' require 'log4r'
require 'vagrant/util/busy'
require 'vagrant/util/platform' require 'vagrant/driver/virtualbox_base'
require 'vagrant/util/subprocess' require 'vagrant/util/subprocess'
module Vagrant module Vagrant
module Driver module Driver
# This class contains the logic to drive VirtualBox. # This class contains the logic to drive VirtualBox.
class VirtualBox #
# Read the VirtualBoxBase source for documentation on each method.
class VirtualBox < VirtualBoxBase
# This is raised if the VM is not found when initializing a driver # This is raised if the VM is not found when initializing a driver
# with a UUID. # with a UUID.
class VMNotFound < StandardError; end class VMNotFound < StandardError; end
# Include this so we can use `Subprocess` more easily.
include Vagrant::Util
# The UUID of the virtual machine we represent # The UUID of the virtual machine we represent
attr_reader :uuid attr_reader :uuid
@ -21,30 +20,12 @@ module Vagrant
attr_reader :version attr_reader :version
def initialize(uuid) def initialize(uuid)
# Setup the base
super()
@logger = Log4r::Logger.new("vagrant::driver::virtualbox") @logger = Log4r::Logger.new("vagrant::driver::virtualbox")
@uuid = uuid @uuid = uuid
# This flag is used to keep track of interrupted state (SIGINT)
@interrupted = false
# Set the path to VBoxManage
@vboxmanage_path = "VBoxManage"
if Util::Platform.windows?
@logger.debug("Windows. Trying VBOX_INSTALL_PATH for VBoxManage")
# On Windows, we use the VBOX_INSTALL_PATH environmental
# variable to find VBoxManage.
if ENV.has_key?("VBOX_INSTALL_PATH")
# The path usually ends with a \ but we make sure here
path = ENV["VBOX_INSTALL_PATH"]
path += "\\" if !path.end_with?("\\")
@vboxmanage_path = "#{path}VBoxManage.exe"
end
end
@logger.info("VBoxManage path: #{@vboxmanage_path}")
# Read and assign the version of VirtualBox we know which # Read and assign the version of VirtualBox we know which
# specific driver to instantiate. # specific driver to instantiate.
begin begin
@ -63,8 +44,6 @@ module Vagrant
end end
end end
# This clears the forwarded ports that have been set on the
# virtual machine.
def clear_forwarded_ports def clear_forwarded_ports
args = [] args = []
read_forwarded_ports(@uuid).each do |nic, name, _, _| read_forwarded_ports(@uuid).each do |nic, name, _, _|
@ -74,8 +53,6 @@ module Vagrant
execute("modifyvm", @uuid, *args) if !args.empty? execute("modifyvm", @uuid, *args) if !args.empty?
end end
# This clears all the shared folders that have been set
# on the virtual machine.
def clear_shared_folders def clear_shared_folders
execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line| execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line|
if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/ if line =~ /^SharedFolderNameMachineMapping\d+="(.+?)"$/
@ -84,7 +61,6 @@ module Vagrant
end end
end end
# Creates a host only network with the given options.
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") =~ /^Interface '(.+?)' was successfully created$/
@ -103,12 +79,10 @@ module Vagrant
} }
end end
# This deletes the VM with the given name.
def delete def delete
execute("unregistervm", @uuid, "--delete") execute("unregistervm", @uuid, "--delete")
end end
# Deletes any host only networks that aren't being used for anything.
def delete_unused_host_only_networks def delete_unused_host_only_networks
networks = [] networks = []
execute("list", "hostonlyifs").split("\n").each do |line| execute("list", "hostonlyifs").split("\n").each do |line|
@ -130,12 +104,10 @@ module Vagrant
end end
end end
# Discards any saved state associated with this VM.
def discard_saved_state def discard_saved_state
execute("discardstate", @uuid) execute("discardstate", @uuid)
end end
# Enables network adapters on this virtual machine.
def enable_adapters(adapters) def enable_adapters(adapters)
args = [] args = []
adapters.each do |adapter| adapters.each do |adapter|
@ -160,39 +132,15 @@ module Vagrant
execute("modifyvm", @uuid, *args) execute("modifyvm", @uuid, *args)
end end
# Executes a raw command.
def execute_command(command) def execute_command(command)
raw(*command) raw(*command)
end end
# Exports the virtual machine to the given path.
#
# @param [String] path Path to the OVF file.
def export(path) def export(path)
# TODO: Progress # TODO: Progress
execute("export", @uuid, "--output", path.to_s) execute("export", @uuid, "--output", path.to_s)
end end
# Forwards a set of ports for a VM.
#
# This will not affect any previously set forwarded ports,
# so be sure to delete those if you need to.
#
# The format of each port hash should be the following:
#
# {
# :name => "foo",
# :hostport => 8500,
# :guestport => 80,
# :adapter => 1,
# :protocol => "tcp"
# }
#
# Note that "adapter" and "protocol" are optional and will default
# to 1 and "tcp" respectively.
#
# @param [Array<Hash>] ports An array of ports to set. See documentation
# for more information on the format.
def forward_ports(ports) def forward_ports(ports)
args = [] args = []
ports.each do |options| ports.each do |options|
@ -210,13 +158,10 @@ module Vagrant
execute("modifyvm", @uuid, *args) execute("modifyvm", @uuid, *args)
end end
# Halts the virtual machine.
def halt def halt
execute("controlvm", @uuid, "poweroff") execute("controlvm", @uuid, "poweroff")
end end
# Imports the VM with the given path to the OVF file. It returns
# the UUID as a string.
def import(ovf, name) def import(ovf, name)
total = "" total = ""
last = 0 last = 0
@ -249,10 +194,6 @@ module Vagrant
nil nil
end end
# This returns a list of the forwarded ports in the form
# of `[nic, name, hostport, guestport]`.
#
# @return [Array<Array>]
def read_forwarded_ports(uuid=nil, active_only=false) def read_forwarded_ports(uuid=nil, active_only=false)
uuid ||= @uuid uuid ||= @uuid
@ -282,7 +223,6 @@ module Vagrant
results results
end end
# This reads the list of host only networks.
def read_bridged_interfaces def read_bridged_interfaces
execute("list", "bridgedifs").split("\n\n").collect do |block| execute("list", "bridgedifs").split("\n\n").collect do |block|
info = {} info = {}
@ -304,14 +244,12 @@ module Vagrant
end end
end end
# This reads the guest additions version for a VM.
def read_guest_additions_version def read_guest_additions_version
output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version") output = execute("guestproperty", "get", @uuid, "/VirtualBox/GuestAdd/Version")
return $1.to_s if output =~ /^Value: (.+?)$/ return $1.to_s if output =~ /^Value: (.+?)$/
return nil return nil
end end
# Reads and returns the available host only interfaces.
def read_host_only_interfaces def read_host_only_interfaces
execute("list", "hostonlyifs").split("\n\n").collect do |block| execute("list", "hostonlyifs").split("\n\n").collect do |block|
info = {} info = {}
@ -330,7 +268,6 @@ module Vagrant
end end
end end
# Reads the MAC address of the first network interface.
def read_mac_address def read_mac_address
execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line| execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line|
return $1.to_s if line =~ /^macaddress1="(.+?)"$/ return $1.to_s if line =~ /^macaddress1="(.+?)"$/
@ -339,7 +276,6 @@ module Vagrant
nil nil
end end
# This reads the folder where VirtualBox places it's VMs.
def read_machine_folder def read_machine_folder
execute("list", "systemproperties").split("\n").each do |line| execute("list", "systemproperties").split("\n").each do |line|
if line =~ /^Default machine folder:\s+(.+?)$/i if line =~ /^Default machine folder:\s+(.+?)$/i
@ -350,10 +286,6 @@ module Vagrant
nil nil
end end
# This reads the network interfaces and returns various information
# about them.
#
# @return [Hash]
def read_network_interfaces def read_network_interfaces
nics = {} nics = {}
execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line| execute("showvminfo", @uuid, "--machinereadable").split("\n").each do |line|
@ -381,8 +313,6 @@ module Vagrant
nics nics
end end
# This reads the state for the given UUID. The state of the VM
# will be returned as a symbol.
def read_state def read_state
output = execute("showvminfo", @uuid, "--machinereadable") output = execute("showvminfo", @uuid, "--machinereadable")
if output =~ /^name="<inaccessible>"$/ if output =~ /^name="<inaccessible>"$/
@ -394,8 +324,6 @@ module Vagrant
nil nil
end end
# This will read all the used ports for port forwarding by
# all virtual machines.
def read_used_ports def read_used_ports
ports = [] ports = []
execute("list", "vms").split("\n").each do |line| execute("list", "vms").split("\n").each do |line|
@ -414,22 +342,10 @@ module Vagrant
ports ports
end end
# This sets the MAC address for a network adapter.
def set_mac_address(mac) def set_mac_address(mac)
execute("modifyvm", @uuid, "--macaddress1", mac) execute("modifyvm", @uuid, "--macaddress1", mac)
end end
# Sets up the shared folder metadata for a virtual machine.
#
# The structure of a folder definition should be the following:
#
# {
# :name => "foo",
# :hostpath => "/foo/bar"
# }
#
# @param [Array<Hash>] folders An array of folder definitions to
# setup.
def share_folders(folders) def share_folders(folders)
folders.each do |folder| folders.each do |folder|
execute("sharedfolder", "add", @uuid, "--name", execute("sharedfolder", "add", @uuid, "--name",
@ -437,7 +353,6 @@ module Vagrant
end end
end end
# This reads the SSH port from the VM.
def ssh_port(expected_port) def ssh_port(expected_port)
# Look for the forwarded port only by comparing the guest port # Look for the forwarded port only by comparing the guest port
read_forwarded_ports.each do |_, _, hostport, guestport| read_forwarded_ports.each do |_, _, hostport, guestport|
@ -447,21 +362,14 @@ module Vagrant
nil nil
end end
# Starts the virtual machine in the given mode.
#
# @param [String] mode Mode to boot the VM: either "headless" or "gui"
def start(mode) def start(mode)
execute("startvm", @uuid, "--type", mode.to_s) execute("startvm", @uuid, "--type", mode.to_s)
end end
# Suspends the virtual machine.
def suspend def suspend
execute("controlvm", @uuid, "savestate") execute("controlvm", @uuid, "savestate")
end end
# Verifies that an image can be imported properly.
#
# @return [Boolean]
def verify_image(path) def verify_image(path)
r = raw("import", path.to_s, "--dry-run") r = raw("import", path.to_s, "--dry-run")
return r.exit_code == 0 return r.exit_code == 0
@ -482,38 +390,6 @@ module Vagrant
# Below accounts for all of these: # Below accounts for all of these:
execute("--version").split("_")[0].split("r")[0] execute("--version").split("_")[0].split("r")[0]
end end
# Execute the given subcommand for VBoxManage and return the output.
def execute(*command, &block)
# Execute the command
r = raw(*command, &block)
# If the command was a failure, then raise an exception that is
# nicely handled by Vagrant.
if r.exit_code != 0
if @interrupted
@logger.info("Exit code != 0, but interrupted. Ignoring.")
else
raise Errors::VBoxManageError, :command => command.inspect
end
end
# Return the output, making sure to replace any Windows-style
# newlines with Unix-style.
r.stdout.gsub("\r\n", "\n")
end
# Executes a command and returns the raw result object.
def raw(*command, &block)
int_callback = lambda do
@interrupted = true
@logger.info("Interrupted.")
end
Util::Busy.busy(int_callback) do
Subprocess.execute(@vboxmanage_path, *command, &block)
end
end
end end
end end
end end

View File

@ -0,0 +1,8 @@
module Vagrant
module Driver
# Driver to communicate with VirtualBox 4.0.x
class VirtualBox_4_0
end
end
end

View File

@ -0,0 +1,10 @@
module Vagrant
module Driver
# Driver to communicate with VirtualBox 4.1.x
class VirtualBox_4_1
def initialize(uuid)
@uuid = uuid
end
end
end
end

View File

@ -0,0 +1,264 @@
require 'log4r'
require 'vagrant/util/busy'
require 'vagrant/util/platform'
require 'vagrant/util/subprocess'
module Vagrant
module Driver
# Base class for all VirtualBox drivers.
#
# This class provides useful tools for things such as executing
# VBoxManage and handling SIGINTs and so on.
class VirtualBoxBase
# Include this so we can use `Subprocess` more easily.
include Vagrant::Util
def initialize
@logger = Log4r::Logger.new("vagrant::driver::virtualbox_base")
# This flag is used to keep track of interrupted state (SIGINT)
@interrupted = false
# Set the path to VBoxManage
@vboxmanage_path = "VBoxManage"
if Util::Platform.windows?
@logger.debug("Windows. Trying VBOX_INSTALL_PATH for VBoxManage")
# On Windows, we use the VBOX_INSTALL_PATH environmental
# variable to find VBoxManage.
if ENV.has_key?("VBOX_INSTALL_PATH")
# The path usually ends with a \ but we make sure here
path = ENV["VBOX_INSTALL_PATH"]
path += "\\" if !path.end_with?("\\")
@vboxmanage_path = "#{path}VBoxManage.exe"
end
end
@logger.info("VBoxManage path: #{@vboxmanage_path}")
end
# Clears the forwarded ports that have been set on the virtual machine.
def clear_forwarded_ports
end
# Clears the shared folders that have been set on the virtual machine.
def clear_shared_folders
end
# Creates a host only network with the given options.
#
# @param [Hash] options Options to create the host only network.
# @return [Hash] The details of the host only network, including
# keys `:name`, `:ip`, and `:netmask`
def create_host_only_network(options)
end
# Deletes the virtual machine references by this driver.
def delete
end
# Deletes any host only networks that aren't being used for anything.
def delete_unused_host_only_networks
end
# Discards any saved state associated with this VM.
def discard_saved_state
end
# Enables network adapters on the VM.
#
# The format of each adapter specification should be like so:
#
# {
# :type => :hostonly,
# :hostonly => "vboxnet0",
# :mac_address => "tubes"
# }
#
# This must support setting up both host only and bridged networks.
#
# @param [Array<Hash>] adapters Array of adapters to enable.
def enable_adapters(adapters)
end
# Execute a raw command straight through to VBoxManage.
#
# @param [Array] command Command to execute.
def execute_command(command)
end
# Exports the virtual machine to the given path.
#
# @param [String] path Path to the OVF file.
# @yield [progress] Yields the block with the progress of the export.
def export(path)
end
# Forwards a set of ports for a VM.
#
# This will not affect any previously set forwarded ports,
# so be sure to delete those if you need to.
#
# The format of each port hash should be the following:
#
# {
# :name => "foo",
# :hostport => 8500,
# :guestport => 80,
# :adapter => 1,
# :protocol => "tcp"
# }
#
# Note that "adapter" and "protocol" are optional and will default
# to 1 and "tcp" respectively.
#
# @param [Array<Hash>] ports An array of ports to set. See documentation
# for more information on the format.
def forward_ports(ports)
end
# Halts the virtual machine (pulls the plug).
def halt
end
# Imports the VM from an OVF file.
#
# @param [String] ovf Path to the OVF file.
# @param [String] name Name of the VM.
# @return [String] UUID of the imported VM.
def import
end
# Returns a list of forwarded ports for a VM.
#
# @param [String] uuid UUID of the VM to read from, or `nil` if this
# VM.
# @param [Boolean] active_only If true, only VMs that are running will
# be checked.
# @return [Array<Array>]
def read_forwarded_ports(uuid=nil, active_only=false)
end
# Returns a list of bridged interfaces.
#
# @return [Hash]
def read_bridged_interfaces
end
# Returns the guest additions version that is installed on this VM.
#
# @return [String]
def read_guest_additions_version
end
# Returns a list of available host only interfaces.
#
# @return [Hash]
def read_host_only_interfaces
end
# Returns the MAC address of the first network interface.
#
# @return [String]
def read_mac_address
end
# Returns the folder where VirtualBox places it's VMs.
#
# @return [String]
def read_machine_folder
end
# Returns a list of network interfaces of the VM.
#
# @return [Hash]
def read_network_interfaces
end
# Returns the current state of this VM.
#
# @return [Symbol]
def read_state
end
# Returns a list of all forwarded ports in use by active
# virtual machines.
#
# @return [Array]
def read_used_ports
end
# Sets the MAC address of the first network adapter.
#
# @param [String] mac MAC address without any spaces/hyphens.
def set_mac_address(mac)
end
# Share a set of folders on this VM.
#
# @param [Array<Hash>] folders
def share_folders(folders)
end
# Reads the SSH port of this VM.
#
# @param [Integer] expected Expected guest port of SSH.
def ssh_port(expected)
end
# Starts the virtual machine.
#
# @param [String] mode Mode to boot the VM. Either "headless"
# or "gui"
def start(mode)
end
# Suspend the virtual machine.
def suspend
end
# Verifies that an image can be imported properly.
#
# @param [String] path Path to an OVF file.
# @return [Boolean]
def verify_image(path)
end
protected
# Execute the given subcommand for VBoxManage and return the output.
def execute(*command, &block)
# Execute the command
r = raw(*command, &block)
# If the command was a failure, then raise an exception that is
# nicely handled by Vagrant.
if r.exit_code != 0
if @interrupted
@logger.info("Exit code != 0, but interrupted. Ignoring.")
else
raise Errors::VBoxManageError, :command => command.inspect
end
end
# Return the output, making sure to replace any Windows-style
# newlines with Unix-style.
r.stdout.gsub("\r\n", "\n")
end
# Executes a command and returns the raw result object.
def raw(*command, &block)
int_callback = lambda do
@interrupted = true
@logger.info("Interrupted.")
end
Util::Busy.busy(int_callback) do
Subprocess.execute(@vboxmanage_path, *command, &block)
end
end
end
end
end