diff --git a/lib/vagrant/driver.rb b/lib/vagrant/driver.rb deleted file mode 100644 index 9f114f80e..000000000 --- a/lib/vagrant/driver.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Vagrant - module Driver - autoload :VirtualBox, 'vagrant/driver/virtualbox' - autoload :VirtualBox_4_0, 'vagrant/driver/virtualbox_4_0' - autoload :VirtualBox_4_1, 'vagrant/driver/virtualbox_4_1' - end -end diff --git a/lib/vagrant/driver/virtualbox.rb b/lib/vagrant/driver/virtualbox.rb deleted file mode 100644 index baf462b87..000000000 --- a/lib/vagrant/driver/virtualbox.rb +++ /dev/null @@ -1,140 +0,0 @@ -require 'forwardable' - -require 'log4r' - -require 'vagrant/driver/virtualbox_base' - -module Vagrant - module Driver - # This class contains the logic to drive 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 - # with a UUID. - class VMNotFound < StandardError; end - - # We use forwardable to do all our driver forwarding - extend Forwardable - - # The UUID of the virtual machine we represent - attr_reader :uuid - - # The version of virtualbox that is running. - attr_reader :version - - def initialize(uuid=nil) - # Setup the base - super() - - @logger = Log4r::Logger.new("vagrant::driver::virtualbox") - @uuid = uuid - - # Read and assign the version of VirtualBox we know which - # specific driver to instantiate. - begin - @version = read_version || "" - rescue Subprocess::LaunchError - # This means that VirtualBox was not found, so we raise this - # error here. - raise Errors::VirtualBoxNotDetected - end - - # Instantiate the proper version driver for VirtualBox - @logger.debug("Finding driver for VirtualBox version: #{@version}") - driver_map = { - "4.0" => VirtualBox_4_0, - "4.1" => VirtualBox_4_1 - } - - driver_klass = nil - driver_map.each do |key, klass| - if @version.start_with?(key) - driver_klass = klass - break - end - end - - if !driver_klass - supported_versions = driver_map.keys.sort.join(", ") - raise Errors::VirtualBoxInvalidVersion, :supported_versions => supported_versions - end - - @logger.info("Using VirtualBox driver: #{driver_klass}") - @driver = driver_klass.new(@uuid) - - if @uuid - # Verify the VM exists, and if it doesn't, then don't worry - # about it (mark the UUID as nil) - raise VMNotFound if !@driver.vm_exists?(@uuid) - end - end - - def_delegators :@driver, :clear_forwarded_ports, - :clear_shared_folders, - :create_dhcp_server, - :create_host_only_network, - :delete, - :delete_unused_host_only_networks, - :discard_saved_state, - :enable_adapters, - :execute_command, - :export, - :forward_ports, - :halt, - :import, - :read_forwarded_ports, - :read_bridged_interfaces, - :read_guest_additions_version, - :read_host_only_interfaces, - :read_mac_address, - :read_mac_addresses, - :read_machine_folder, - :read_network_interfaces, - :read_state, - :read_used_ports, - :read_vms, - :set_mac_address, - :set_name, - :share_folders, - :ssh_port, - :start, - :suspend, - :verify!, - :verify_image, - :vm_exists? - - protected - - # This returns the version of VirtualBox that is running. - # - # @return [String] - def read_version - # The version string is usually in one of the following formats: - # - # * 4.1.8r1234 - # * 4.1.8r1234_OSE - # * 4.1.8_MacPortsr1234 - # - # Below accounts for all of these. - - # Note: We split this into multiple lines because apparently "".split("_") - # is [], so we have to check for an empty array in between. - output = execute("--version") - if output =~ /vboxdrv kernel module is not loaded/ - raise Errors::VirtualBoxKernelModuleNotLoaded - elsif output =~ /Please install/ - # Check for installation incomplete warnings, for example: - # "WARNING: The character device /dev/vboxdrv does not - # exist. Please install the virtualbox-ose-dkms package and - # the appropriate headers, most likely linux-headers-generic." - raise Errors::VirtualBoxInstallIncomplete - end - - parts = output.split("_") - return nil if parts.empty? - parts[0].split("r")[0] - end - end - end -end diff --git a/lib/vagrant/driver/virtualbox_4_0.rb b/lib/vagrant/driver/virtualbox_4_0.rb deleted file mode 100644 index 23546e2f0..000000000 --- a/lib/vagrant/driver/virtualbox_4_0.rb +++ /dev/null @@ -1,474 +0,0 @@ -require 'log4r' - -require 'vagrant/driver/virtualbox_base' - -module Vagrant - module Driver - # Driver for VirtualBox 4.0.x - class VirtualBox_4_0 < VirtualBoxBase - def initialize(uuid) - super() - - @logger = Log4r::Logger.new("vagrant::driver::virtualbox_4_0") - @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 name = line[/^SharedFolderNameMachineMapping\d+="(.+?)"$/, 1] - execute("sharedfolder", "remove", @uuid, "--name", name) - 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 - interface = execute("hostonlyif", "create") - name = interface[/^Interface '(.+?)' was successfully created$/, 1] - - # 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| - if network_name = line[/^Name:\s+(.+?)$/, 1] - networks << network_name - end - end - - execute("list", "vms").split("\n").each do |line| - if vm_name = line[/^".+?"\s+\{(.+?)\}$/, 1] - info = execute("showvminfo", vm_name, "--machinereadable", :retryable => true) - info.split("\n").each do |line| - if network_name = line[/^hostonlyadapter\d+="(.+?)"$/, 1] - networks.delete(network_name) - 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 current = lines.last[/.+(\d{2})%/, 1] - current = current.to_i - if current > last - last = current - yield current if block_given? - end - end - end - end - end - - # Find the name of the VM name - name = output[/Suggested VM name "(.+?)"/, 1] - if !name - @logger.error("Couldn't find VM name in the output.") - return nil - end - - output = execute("list", "vms") - if existing_vm = output[/^"#{Regexp.escape(name)}" \{(.+?)\}$/, 1] - return existing_vm - 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. - if nic = line[/^nic(\d+)=".+?"$/, 1] - current_nic = nic.to_i - end - - # If we care about active VMs only, then we check the state - # to verify the VM is running. - if active_only && (state = line[/^VMState="(.+?)"$/, 1] and state != "running") - return [] - end - - # Parse out the forwarded port information - if matcher = /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/.match(line) - result = [current_nic, matcher[1], matcher[2].to_i, matcher[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 name = line[/^Name:\s+(.+?)$/, 1] - info[:name] = name - elsif ip = line[/^IPAddress:\s+(.+?)$/, 1] - info[:ip] = ip - elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] - info[:netmask] = netmask - elsif status = line[/^Status:\s+(.+?)$/, 1] - info[:status] = status - 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 value = output[/^Value: (.+?)$/, 1] - # 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. - 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 network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] - info[:network] = network - elsif ip = line[/^IP:\s+(.+?)$/, 1] - info[:ip] = ip - elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] - info[:lower] = lower - elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] - info[:upper] = upper - 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 name = line[/^Name:\s+(.+?)$/, 1] - info[:name] = name - elsif ip = line[/^IPAddress:\s+(.+?)$/, 1] - info[:ip] = ip - elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] - info[:netmask] = netmask - elsif status = line[/^Status:\s+(.+?)$/, 1] - info[:status] = status - 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| - if mac = line[/^macaddress1="(.+?)"$/, 1] - return mac - end - end - - nil - end - - def read_mac_addresses - macs = {} - info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true) - info.split("\n").each do |line| - if matcher = /^macaddress(\d+)="(.+?)"$/.match(line) - adapter = matcher[1].to_i - mac = matcher[2].to_s - macs[adapter] = mac - end - end - macs - end - - def read_machine_folder - execute("list", "systemproperties", :retryable => true).split("\n").each do |line| - if folder = line[/^Default machine folder:\s+(.+?)$/i, 1] - return folder - end - end - - nil - end - - def read_network_interfaces - nics = {} - info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true) - info.split("\n").each do |line| - if matcher = /^nic(\d+)="(.+?)"$/.match(line) - adapter = matcher[1].to_i - type = matcher[2].to_sym - - nics[adapter] ||= {} - nics[adapter][:type] = type - elsif matcher = /^hostonlyadapter(\d+)="(.+?)"$/.match(line) - adapter = matcher[1].to_i - network = matcher[2].to_s - - nics[adapter] ||= {} - nics[adapter][:hostonly] = network - elsif matcher = /^bridgeadapter(\d+)="(.+?)"$/.match(line) - adapter = matcher[1].to_i - network = matcher[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=""$/ - return :inaccessible - elsif state = output[/^VMState="(.+?)"$/, 1] - return state.to_sym - end - - nil - end - - def read_used_ports - ports = [] - execute("list", "vms", :retryable => true).split("\n").each do |line| - if uuid = line[/^".+?" \{(.+?)\}$/, 1] - # 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 vm = line[/^".+?" \{(.+?)\}$/, 1] - results << vm - 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 diff --git a/lib/vagrant/driver/virtualbox_4_1.rb b/lib/vagrant/driver/virtualbox_4_1.rb deleted file mode 100644 index 3983a1112..000000000 --- a/lib/vagrant/driver/virtualbox_4_1.rb +++ /dev/null @@ -1,474 +0,0 @@ -require 'log4r' - -require 'vagrant/driver/virtualbox_base' - -module Vagrant - module Driver - # Driver for VirtualBox 4.1.x - class VirtualBox_4_1 < VirtualBoxBase - def initialize(uuid) - super() - - @logger = Log4r::Logger.new("vagrant::driver::virtualbox_4_1") - @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 folder = line[/^SharedFolderNameMachineMapping\d+="(.+?)"$/, 1] - execute("sharedfolder", "remove", @uuid, "--name", folder) - 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 - interface = execute("hostonlyif", "create") - name = interface[/^Interface '(.+?)' was successfully created$/, 1] - - # 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| - if network = line[/^Name:\s+(.+?)$/, 1] - networks << network - end - end - - execute("list", "vms").split("\n").each do |line| - if vm = line[/^".+?"\s+\{(.+?)\}$/, 1] - info = execute("showvminfo", vm, "--machinereadable", :retryable => true) - info.split("\n").each do |line| - if adapter = line[/^hostonlyadapter\d+="(.+?)"$/, 1] - networks.delete(adapter) - 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 current = lines.last[/.+(\d{2})%/, 1] - current = current.to_i - if current > last - last = current - yield current if block_given? - end - end - end - end - end - - # Find the name of the VM name - name = output[/Suggested VM name "(.+?)"/, 1] - if !name - @logger.error("Couldn't find VM name in the output.") - return nil - end - - output = execute("list", "vms") - if existing_vm = output[/^"#{Regexp.escape(name)}" \{(.+?)\}$/, 1] - return existing_vm - 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. - if nic = line[/^nic(\d+)=".+?"$/, 1] - current_nic = nic.to_i - end - - # If we care about active VMs only, then we check the state - # to verify the VM is running. - if active_only && (state = line[/^VMState="(.+?)"$/, 1] and state != "running") - return [] - end - - # Parse out the forwarded port information - if matcher = /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/.match(line) - result = [current_nic, matcher[1], matcher[2].to_i, matcher[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 name = line[/^Name:\s+(.+?)$/, 1] - info[:name] = name - elsif ip = line[/^IPAddress:\s+(.+?)$/, 1] - info[:ip] = ip - elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] - info[:netmask] = netmask - elsif status = line[/^Status:\s+(.+?)$/, 1] - info[:status] = status - 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 value = output[/^Value: (.+?)$/, 1] - # 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. - 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 network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] - info[:network] = network - elsif ip = line[/^IP:\s+(.+?)$/, 1] - info[:ip] = ip - elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] - info[:lower] = lower - elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] - info[:upper] = upper - 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 name = line[/^Name:\s+(.+?)$/, 1] - info[:name] = name - elsif ip = line[/^IPAddress:\s+(.+?)$/, 1] - info[:ip] = ip - elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] - info[:netmask] = netmask - elsif status = line[/^Status:\s+(.+?)$/, 1] - info[:status] = status - 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| - if mac = line[/^macaddress1="(.+?)"$/, 1] - return mac - end - end - - nil - end - - def read_mac_addresses - macs = {} - info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true) - info.split("\n").each do |line| - if matcher = /^macaddress(\d+)="(.+?)"$/.match(line) - adapter = matcher[1].to_i - mac = matcher[2].to_s - macs[adapter] = mac - end - end - macs - end - - def read_machine_folder - execute("list", "systemproperties", :retryable => true).split("\n").each do |line| - if folder = line[/^Default machine folder:\s+(.+?)$/i, 1] - return folder - end - end - - nil - end - - def read_network_interfaces - nics = {} - info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true) - info.split("\n").each do |line| - if matcher = /^nic(\d+)="(.+?)"$/.match(line) - adapter = matcher[1].to_i - type = matcher[2].to_sym - - nics[adapter] ||= {} - nics[adapter][:type] = type - elsif matcher = /^hostonlyadapter(\d+)="(.+?)"$/.match(line) - adapter = matcher[1].to_i - network = matcher[2].to_s - - nics[adapter] ||= {} - nics[adapter][:hostonly] = network - elsif matcher = /^bridgeadapter(\d+)="(.+?)"$/.match(line) - adapter = matcher[1].to_i - network = matcher[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=""$/ - return :inaccessible - elsif state = output[/^VMState="(.+?)"$/, 1] - return state.to_sym - end - - nil - end - - def read_used_ports - ports = [] - execute("list", "vms", :retryable => true).split("\n").each do |line| - if uuid = line[/^".+?" \{(.+?)\}$/, 1] - # 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 vm = line[/^".+?" \{(.+?)\}$/, 1] - results << vm - 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 diff --git a/lib/vagrant/driver/virtualbox_base.rb b/lib/vagrant/driver/virtualbox_base.rb deleted file mode 100644 index 9e7cd1868..000000000 --- a/lib/vagrant/driver/virtualbox_base.rb +++ /dev/null @@ -1,326 +0,0 @@ -require 'log4r' - -require 'vagrant/util/busy' -require 'vagrant/util/platform' -require 'vagrant/util/retryable' -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 - include Vagrant::Util::Retryable - - 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") - # Get the path. - path = ENV["VBOX_INSTALL_PATH"] - @logger.debug("VBOX_INSTALL_PATH value: #{path}") - - # There can actually be multiple paths in here, so we need to - # split by the separator ";" and see which is a good one. - path.split(";").each do |single| - # Make sure it ends with a \ - single += "\\" if !single.end_with?("\\") - - # If the executable exists, then set it as the main path - # and break out - vboxmanage = "#{path}VBoxManage.exe" - if File.file?(vboxmanage) - @vboxmanage_path = vboxmanage - break - end - end - 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 DHCP server for a host only network. - # - # @param [String] network Name of the host-only network. - # @param [Hash] options Options for the DHCP server. - def create_dhcp_server(network, options) - 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] 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] 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. - # @return [String] UUID of the imported VM. - def import(ovf) - 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] - 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 - - # Returns a list of all UUIDs of virtual machines currently - # known by VirtualBox. - # - # @return [Array] - def read_vms - 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] 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 the driver is ready to accept work. - # - # This should raise a VagrantError if things are not ready. - def verify! - end - - # Verifies that an image can be imported properly. - # - # @param [String] path Path to an OVF file. - # @return [Boolean] - def verify_image(path) - end - - # Checks if a VM with the given UUID exists. - # - # @return [Boolean] - def vm_exists?(uuid) - end - - # Execute the given subcommand for VBoxManage and return the output. - def execute(*command, &block) - # Get the options hash if it exists - opts = {} - opts = command.pop if command.last.is_a?(Hash) - - tries = 0 - tries = 3 if opts[:retryable] - - # Variable to store our execution result - r = nil - - retryable(:on => Errors::VBoxManageError, :tries => tries, :sleep => 1) do - # 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 - else - # Sometimes, VBoxManage fails but doesn't actual return a non-zero - # exit code. For this we inspect the output and determine if an error - # occurred. - if r.stderr =~ /VBoxManage: error:/ - @logger.info("VBoxManage error text found, assuming error.") - raise Errors::VBoxManageError, :command => command.inspect - end - 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 - - # Append in the options for subprocess - command << { :notify => [:stdout, :stderr] } - - Util::Busy.busy(int_callback) do - Subprocess.execute(@vboxmanage_path, *command, &block) - end - end - end - end -end diff --git a/lib/vagrant/environment.rb b/lib/vagrant/environment.rb index 90137cd2f..9238dc283 100644 --- a/lib/vagrant/environment.rb +++ b/lib/vagrant/environment.rb @@ -114,7 +114,7 @@ module Vagrant # @return [Pathname] def dotfile_path return nil if !root_path - root_path.join(File.expand_path(config.global.vagrant.dotfile_name)) + root_path.join(config.global.vagrant.dotfile_name) end # Returns the collection of boxes for the environment. @@ -445,11 +445,22 @@ module Vagrant # Loads the persisted VM (if it exists) for this environment. def load_vms! - result = {} + # This is hardcoded for now. + provider = nil + Vagrant.plugin("1").registered.each do |plugin| + provider = plugin.provider.get(:virtualbox) + break if provider + end + + raise "VirtualBox provider not found." if !provider # Load all the virtual machine instances. + result = {} config.vms.each do |name| - result[name] = Vagrant::VM.new(name, self, config.for_vm(name)) + vm_config = config.for_vm(name) + box = boxes.find(vm_config.vm.box, :virtualbox) + + result[name] = Vagrant::Machine.new(name, provider, vm_config, box, self) end result diff --git a/lib/vagrant/machine.rb b/lib/vagrant/machine.rb index 78a74b2d3..3347e956c 100644 --- a/lib/vagrant/machine.rb +++ b/lib/vagrant/machine.rb @@ -20,6 +20,13 @@ module Vagrant # @return [Environment] attr_reader :env + # ID of the machine. This ID comes from the provider and is not + # guaranteed to be of any particular format except that it is + # a string. + # + # @return [String] + attr_reader :id + # Name of the machine. This is assigned by the Vagrantfile. # # @return [String] @@ -45,6 +52,10 @@ module Vagrant @config = config @env = env @provider = provider_cls.new(self) + + # Read the ID, which is usually in local storage + @id = nil + @id = @env.local_data[:active][@name] if @env.local_data[:active] end # This calls an action on the provider. The provider may or may not @@ -69,6 +80,34 @@ module Vagrant @env.action_runner.run(callable, :machine => self) end + # This sets the unique ID associated with this machine. This will + # persist this ID so that in the future Vagrant will be able to find + # this machine again. The unique ID must be absolutely unique to the + # virtual machine, and can be used by providers for finding the + # actual machine associated with this instance. + # + # **WARNING:** Only providers should ever use this method. + # + # @param [String] value The ID. + def id=(value) + @env.local_data[:active] ||= {} + + if value + # Set the value + @env.local_data[:active][@name] = value + else + # Delete it from the active hash + @env.local_data[:active].delete(@name) + end + + # Commit the local data so that the next time Vagrant is initialized, + # it realizes the VM exists (or doesn't). + @env.local_data.commit + + # Store the ID locally + @id = value + end + # Returns the state of this machine. The state is queried from the # backing provider, so it can be any arbitrary symbol. # diff --git a/plugins/providers/virtualbox/driver/base.rb b/plugins/providers/virtualbox/driver/base.rb new file mode 100644 index 000000000..1b503e950 --- /dev/null +++ b/plugins/providers/virtualbox/driver/base.rb @@ -0,0 +1,327 @@ +require 'log4r' + +require 'vagrant/util/busy' +require 'vagrant/util/platform' +require 'vagrant/util/retryable' +require 'vagrant/util/subprocess' + +module VagrantPlugins + module ProviderVirtualBox + 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 Base + # Include this so we can use `Subprocess` more easily. + include Vagrant::Util::Retryable + + def initialize + @logger = Log4r::Logger.new("vagrant::provider::virtualbox::base") + + # This flag is used to keep track of interrupted state (SIGINT) + @interrupted = false + + # Set the path to VBoxManage + @vboxmanage_path = "VBoxManage" + + if Vagrant::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") + # Get the path. + path = ENV["VBOX_INSTALL_PATH"] + @logger.debug("VBOX_INSTALL_PATH value: #{path}") + + # There can actually be multiple paths in here, so we need to + # split by the separator ";" and see which is a good one. + path.split(";").each do |single| + # Make sure it ends with a \ + single += "\\" if !single.end_with?("\\") + + # If the executable exists, then set it as the main path + # and break out + vboxmanage = "#{path}VBoxManage.exe" + if File.file?(vboxmanage) + @vboxmanage_path = vboxmanage + break + end + end + 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 DHCP server for a host only network. + # + # @param [String] network Name of the host-only network. + # @param [Hash] options Options for the DHCP server. + def create_dhcp_server(network, options) + 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] 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] 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. + # @return [String] UUID of the imported VM. + def import(ovf) + 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] + 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 + + # Returns a list of all UUIDs of virtual machines currently + # known by VirtualBox. + # + # @return [Array] + def read_vms + 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] 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 the driver is ready to accept work. + # + # This should raise a VagrantError if things are not ready. + def verify! + end + + # Verifies that an image can be imported properly. + # + # @param [String] path Path to an OVF file. + # @return [Boolean] + def verify_image(path) + end + + # Checks if a VM with the given UUID exists. + # + # @return [Boolean] + def vm_exists?(uuid) + end + + # Execute the given subcommand for VBoxManage and return the output. + def execute(*command, &block) + # Get the options hash if it exists + opts = {} + opts = command.pop if command.last.is_a?(Hash) + + tries = 0 + tries = 3 if opts[:retryable] + + # Variable to store our execution result + r = nil + + retryable(:on => Vagrant::Errors::VBoxManageError, :tries => tries, :sleep => 1) do + # 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 Vagrant::Errors::VBoxManageError, :command => command.inspect + end + else + # Sometimes, VBoxManage fails but doesn't actual return a non-zero + # exit code. For this we inspect the output and determine if an error + # occurred. + if r.stderr =~ /VBoxManage: error:/ + @logger.info("VBoxManage error text found, assuming error.") + raise Vagrant::Errors::VBoxManageError, :command => command.inspect + end + 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 + + # Append in the options for subprocess + command << { :notify => [:stdout, :stderr] } + + Vagrant::Util::Busy.busy(int_callback) do + Vagrant::Util::Subprocess.execute(@vboxmanage_path, *command, &block) + end + end + end + end + end +end diff --git a/plugins/providers/virtualbox/driver/meta.rb b/plugins/providers/virtualbox/driver/meta.rb new file mode 100644 index 000000000..1cf76436e --- /dev/null +++ b/plugins/providers/virtualbox/driver/meta.rb @@ -0,0 +1,139 @@ +require "forwardable" + +require "log4r" + +require File.expand_path("../base", __FILE__) + +module VagrantPlugins + module ProviderVirtualBox + module Driver + class Meta < Base + # This is raised if the VM is not found when initializing a driver + # with a UUID. + class VMNotFound < StandardError; end + + # We use forwardable to do all our driver forwarding + extend Forwardable + + # The UUID of the virtual machine we represent + attr_reader :uuid + + # The version of virtualbox that is running. + attr_reader :version + + def initialize(uuid=nil) + # Setup the base + super() + + @logger = Log4r::Logger.new("vagrant::provider::virtualbox::meta") + @uuid = uuid + + # Read and assign the version of VirtualBox we know which + # specific driver to instantiate. + begin + @version = read_version || "" + rescue Vagrant::Util::Subprocess::LaunchError + # This means that VirtualBox was not found, so we raise this + # error here. + raise Vagrant::Errors::VirtualBoxNotDetected + end + + # Instantiate the proper version driver for VirtualBox + @logger.debug("Finding driver for VirtualBox version: #{@version}") + driver_map = { + "4.0" => Version_4_0, + "4.1" => Version_4_1 + } + + driver_klass = nil + driver_map.each do |key, klass| + if @version.start_with?(key) + driver_klass = klass + break + end + end + + if !driver_klass + supported_versions = driver_map.keys.sort.join(", ") + raise Vagrant::Errors::VirtualBoxInvalidVersion, :supported_versions => supported_versions + end + + @logger.info("Using VirtualBox driver: #{driver_klass}") + @driver = driver_klass.new(@uuid) + + if @uuid + # Verify the VM exists, and if it doesn't, then don't worry + # about it (mark the UUID as nil) + raise VMNotFound if !@driver.vm_exists?(@uuid) + end + end + + def_delegators :@driver, :clear_forwarded_ports, + :clear_shared_folders, + :create_dhcp_server, + :create_host_only_network, + :delete, + :delete_unused_host_only_networks, + :discard_saved_state, + :enable_adapters, + :execute_command, + :export, + :forward_ports, + :halt, + :import, + :read_forwarded_ports, + :read_bridged_interfaces, + :read_guest_additions_version, + :read_host_only_interfaces, + :read_mac_address, + :read_mac_addresses, + :read_machine_folder, + :read_network_interfaces, + :read_state, + :read_used_ports, + :read_vms, + :set_mac_address, + :set_name, + :share_folders, + :ssh_port, + :start, + :suspend, + :verify!, + :verify_image, + :vm_exists? + + protected + + # This returns the version of VirtualBox that is running. + # + # @return [String] + def read_version + # The version string is usually in one of the following formats: + # + # * 4.1.8r1234 + # * 4.1.8r1234_OSE + # * 4.1.8_MacPortsr1234 + # + # Below accounts for all of these. + + # Note: We split this into multiple lines because apparently "".split("_") + # is [], so we have to check for an empty array in between. + output = execute("--version") + if output =~ /vboxdrv kernel module is not loaded/ + raise Vagrant::Errors::VirtualBoxKernelModuleNotLoaded + elsif output =~ /Please install/ + # Check for installation incomplete warnings, for example: + # "WARNING: The character device /dev/vboxdrv does not + # exist. Please install the virtualbox-ose-dkms package and + # the appropriate headers, most likely linux-headers-generic." + raise Vagrant::Errors::VirtualBoxInstallIncomplete + end + + parts = output.split("_") + return nil if parts.empty? + parts[0].split("r")[0] + end + end + end + end +end diff --git a/plugins/providers/virtualbox/driver/version_4_0.rb b/plugins/providers/virtualbox/driver/version_4_0.rb new file mode 100644 index 000000000..bbf0cde83 --- /dev/null +++ b/plugins/providers/virtualbox/driver/version_4_0.rb @@ -0,0 +1,476 @@ +require 'log4r' + +require File.expand_path("../base", __FILE__) + +module VagrantPlugins + module ProviderVirtualBox + module Driver + # Driver for VirtualBox 4.0.x + class Version_4_0 < Base + def initialize(uuid) + super() + + @logger = Log4r::Logger.new("vagrant::provider::virtualbox_4_0") + @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 name = line[/^SharedFolderNameMachineMapping\d+="(.+?)"$/, 1] + execute("sharedfolder", "remove", @uuid, "--name", name) + 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 + interface = execute("hostonlyif", "create") + name = interface[/^Interface '(.+?)' was successfully created$/, 1] + + # 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| + if network_name = line[/^Name:\s+(.+?)$/, 1] + networks << network_name + end + end + + execute("list", "vms").split("\n").each do |line| + if vm_name = line[/^".+?"\s+\{(.+?)\}$/, 1] + info = execute("showvminfo", vm_name, "--machinereadable", :retryable => true) + info.split("\n").each do |line| + if network_name = line[/^hostonlyadapter\d+="(.+?)"$/, 1] + networks.delete(network_name) + 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 current = lines.last[/.+(\d{2})%/, 1] + current = current.to_i + if current > last + last = current + yield current if block_given? + end + end + end + end + end + + # Find the name of the VM name + name = output[/Suggested VM name "(.+?)"/, 1] + if !name + @logger.error("Couldn't find VM name in the output.") + return nil + end + + output = execute("list", "vms") + if existing_vm = output[/^"#{Regexp.escape(name)}" \{(.+?)\}$/, 1] + return existing_vm + 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. + if nic = line[/^nic(\d+)=".+?"$/, 1] + current_nic = nic.to_i + end + + # If we care about active VMs only, then we check the state + # to verify the VM is running. + if active_only && (state = line[/^VMState="(.+?)"$/, 1] and state != "running") + return [] + end + + # Parse out the forwarded port information + if matcher = /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/.match(line) + result = [current_nic, matcher[1], matcher[2].to_i, matcher[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 name = line[/^Name:\s+(.+?)$/, 1] + info[:name] = name + elsif ip = line[/^IPAddress:\s+(.+?)$/, 1] + info[:ip] = ip + elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] + info[:netmask] = netmask + elsif status = line[/^Status:\s+(.+?)$/, 1] + info[:status] = status + 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 value = output[/^Value: (.+?)$/, 1] + # 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. + 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 network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] + info[:network] = network + elsif ip = line[/^IP:\s+(.+?)$/, 1] + info[:ip] = ip + elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] + info[:lower] = lower + elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] + info[:upper] = upper + 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 name = line[/^Name:\s+(.+?)$/, 1] + info[:name] = name + elsif ip = line[/^IPAddress:\s+(.+?)$/, 1] + info[:ip] = ip + elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] + info[:netmask] = netmask + elsif status = line[/^Status:\s+(.+?)$/, 1] + info[:status] = status + 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| + if mac = line[/^macaddress1="(.+?)"$/, 1] + return mac + end + end + + nil + end + + def read_mac_addresses + macs = {} + info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true) + info.split("\n").each do |line| + if matcher = /^macaddress(\d+)="(.+?)"$/.match(line) + adapter = matcher[1].to_i + mac = matcher[2].to_s + macs[adapter] = mac + end + end + macs + end + + def read_machine_folder + execute("list", "systemproperties", :retryable => true).split("\n").each do |line| + if folder = line[/^Default machine folder:\s+(.+?)$/i, 1] + return folder + end + end + + nil + end + + def read_network_interfaces + nics = {} + info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true) + info.split("\n").each do |line| + if matcher = /^nic(\d+)="(.+?)"$/.match(line) + adapter = matcher[1].to_i + type = matcher[2].to_sym + + nics[adapter] ||= {} + nics[adapter][:type] = type + elsif matcher = /^hostonlyadapter(\d+)="(.+?)"$/.match(line) + adapter = matcher[1].to_i + network = matcher[2].to_s + + nics[adapter] ||= {} + nics[adapter][:hostonly] = network + elsif matcher = /^bridgeadapter(\d+)="(.+?)"$/.match(line) + adapter = matcher[1].to_i + network = matcher[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=""$/ + return :inaccessible + elsif state = output[/^VMState="(.+?)"$/, 1] + return state.to_sym + end + + nil + end + + def read_used_ports + ports = [] + execute("list", "vms", :retryable => true).split("\n").each do |line| + if uuid = line[/^".+?" \{(.+?)\}$/, 1] + # 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 vm = line[/^".+?" \{(.+?)\}$/, 1] + results << vm + 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 Vagrant::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 diff --git a/plugins/providers/virtualbox/driver/version_4_1.rb b/plugins/providers/virtualbox/driver/version_4_1.rb new file mode 100644 index 000000000..e4a1268a8 --- /dev/null +++ b/plugins/providers/virtualbox/driver/version_4_1.rb @@ -0,0 +1,476 @@ +require 'log4r' + +require File.expand_path("../base", __FILE__) + +module VagrantPlugins + module ProviderVirtualBox + module Driver + # Driver for VirtualBox 4.1.x + class Version_4_1 < Base + def initialize(uuid) + super() + + @logger = Log4r::Logger.new("vagrant::provider::virtualbox_4_1") + @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 folder = line[/^SharedFolderNameMachineMapping\d+="(.+?)"$/, 1] + execute("sharedfolder", "remove", @uuid, "--name", folder) + 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 + interface = execute("hostonlyif", "create") + name = interface[/^Interface '(.+?)' was successfully created$/, 1] + + # 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| + if network = line[/^Name:\s+(.+?)$/, 1] + networks << network + end + end + + execute("list", "vms").split("\n").each do |line| + if vm = line[/^".+?"\s+\{(.+?)\}$/, 1] + info = execute("showvminfo", vm, "--machinereadable", :retryable => true) + info.split("\n").each do |line| + if adapter = line[/^hostonlyadapter\d+="(.+?)"$/, 1] + networks.delete(adapter) + 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 current = lines.last[/.+(\d{2})%/, 1] + current = current.to_i + if current > last + last = current + yield current if block_given? + end + end + end + end + end + + # Find the name of the VM name + name = output[/Suggested VM name "(.+?)"/, 1] + if !name + @logger.error("Couldn't find VM name in the output.") + return nil + end + + output = execute("list", "vms") + if existing_vm = output[/^"#{Regexp.escape(name)}" \{(.+?)\}$/, 1] + return existing_vm + 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. + if nic = line[/^nic(\d+)=".+?"$/, 1] + current_nic = nic.to_i + end + + # If we care about active VMs only, then we check the state + # to verify the VM is running. + if active_only && (state = line[/^VMState="(.+?)"$/, 1] and state != "running") + return [] + end + + # Parse out the forwarded port information + if matcher = /^Forwarding.+?="(.+?),.+?,.*?,(.+?),.*?,(.+?)"$/.match(line) + result = [current_nic, matcher[1], matcher[2].to_i, matcher[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 name = line[/^Name:\s+(.+?)$/, 1] + info[:name] = name + elsif ip = line[/^IPAddress:\s+(.+?)$/, 1] + info[:ip] = ip + elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] + info[:netmask] = netmask + elsif status = line[/^Status:\s+(.+?)$/, 1] + info[:status] = status + 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 value = output[/^Value: (.+?)$/, 1] + # 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. + 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 network = line[/^NetworkName:\s+HostInterfaceNetworking-(.+?)$/, 1] + info[:network] = network + elsif ip = line[/^IP:\s+(.+?)$/, 1] + info[:ip] = ip + elsif lower = line[/^lowerIPAddress:\s+(.+?)$/, 1] + info[:lower] = lower + elsif upper = line[/^upperIPAddress:\s+(.+?)$/, 1] + info[:upper] = upper + 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 name = line[/^Name:\s+(.+?)$/, 1] + info[:name] = name + elsif ip = line[/^IPAddress:\s+(.+?)$/, 1] + info[:ip] = ip + elsif netmask = line[/^NetworkMask:\s+(.+?)$/, 1] + info[:netmask] = netmask + elsif status = line[/^Status:\s+(.+?)$/, 1] + info[:status] = status + 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| + if mac = line[/^macaddress1="(.+?)"$/, 1] + return mac + end + end + + nil + end + + def read_mac_addresses + macs = {} + info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true) + info.split("\n").each do |line| + if matcher = /^macaddress(\d+)="(.+?)"$/.match(line) + adapter = matcher[1].to_i + mac = matcher[2].to_s + macs[adapter] = mac + end + end + macs + end + + def read_machine_folder + execute("list", "systemproperties", :retryable => true).split("\n").each do |line| + if folder = line[/^Default machine folder:\s+(.+?)$/i, 1] + return folder + end + end + + nil + end + + def read_network_interfaces + nics = {} + info = execute("showvminfo", @uuid, "--machinereadable", :retryable => true) + info.split("\n").each do |line| + if matcher = /^nic(\d+)="(.+?)"$/.match(line) + adapter = matcher[1].to_i + type = matcher[2].to_sym + + nics[adapter] ||= {} + nics[adapter][:type] = type + elsif matcher = /^hostonlyadapter(\d+)="(.+?)"$/.match(line) + adapter = matcher[1].to_i + network = matcher[2].to_s + + nics[adapter] ||= {} + nics[adapter][:hostonly] = network + elsif matcher = /^bridgeadapter(\d+)="(.+?)"$/.match(line) + adapter = matcher[1].to_i + network = matcher[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=""$/ + return :inaccessible + elsif state = output[/^VMState="(.+?)"$/, 1] + return state.to_sym + end + + nil + end + + def read_used_ports + ports = [] + execute("list", "vms", :retryable => true).split("\n").each do |line| + if uuid = line[/^".+?" \{(.+?)\}$/, 1] + # 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 vm = line[/^".+?" \{(.+?)\}$/, 1] + results << vm + 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 Vagrant::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 diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index 487bc4ec8..d5c3c130a 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -3,7 +3,7 @@ require "vagrant" module VagrantPlugins module ProviderVirtualBox class Plugin < Vagrant.plugin("1") - name "virtualbox provider" + name "VirtualBox provider" description <<-EOF The VirtualBox provider allows Vagrant to manage and control VirtualBox-based virtual machines. @@ -14,5 +14,13 @@ module VagrantPlugins Provider end end + + # Drop some autoloads in here to optimize the performance of loading + # our drivers only when they are needed. + module Driver + autoload :Meta, File.expand_path("../driver/meta", __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__) + end end end diff --git a/plugins/providers/virtualbox/provider.rb b/plugins/providers/virtualbox/provider.rb index 8cbd51f74..eda1de5bb 100644 --- a/plugins/providers/virtualbox/provider.rb +++ b/plugins/providers/virtualbox/provider.rb @@ -1,6 +1,19 @@ module VagrantPlugins module ProviderVirtualBox class Provider < Vagrant.plugin("1", :provider) + def initialize(machine) + @machine = machine + @driver = Driver::Meta.new(@machine.id) + end + + # Return the state of VirtualBox virtual machine by actually + # querying VBoxManage. + def state + return :not_created if !@driver.uuid + state = @driver.read_state + return :unknown if !state + state + end end end end diff --git a/test/unit/vagrant/machine_test.rb b/test/unit/vagrant/machine_test.rb index f941c349b..6817c034f 100644 --- a/test/unit/vagrant/machine_test.rb +++ b/test/unit/vagrant/machine_test.rb @@ -12,10 +12,23 @@ describe Vagrant::Machine do end let(:box) { Object.new } let(:config) { Object.new } - let(:env) { test_env.create_vagrant_env } + let(:env) do + # We need to create a Vagrantfile so that this test environment + # has a proper root path + test_env.vagrantfile("") + + # Create the Vagrant::Environment instance + test_env.create_vagrant_env + end + let(:test_env) { isolated_environment } - let(:instance) { described_class.new(name, provider_cls, config, box, env) } + let(:instance) { new_instance } + + # Returns a new instance with the test data + def new_instance + described_class.new(name, provider_cls, config, box, env) + end describe "initialization" do it "should initialize the provider with the machine object" do @@ -89,6 +102,22 @@ describe Vagrant::Machine do end end + describe "setting the ID" do + it "should not have an ID by default" do + instance.id.should be_nil + end + + it "should set an ID" do + instance.id = "bar" + instance.id.should == "bar" + end + + it "should persist the ID" do + instance.id = "foo" + new_instance.id.should == "foo" + end + end + describe "state" do it "should query state from the provider" do state = :running