Move NFS to a built-in middleware

This commit is contained in:
Mitchell Hashimoto 2013-02-08 15:34:04 -08:00
parent a76556f3eb
commit 6afb4326fa
10 changed files with 219 additions and 214 deletions

View File

@ -16,6 +16,7 @@ module Vagrant
autoload :GracefulHalt, "vagrant/action/builtin/graceful_halt"
autoload :HandleBoxUrl, "vagrant/action/builtin/handle_box_url"
autoload :Lock, "vagrant/action/builtin/lock"
autoload :NFS, "vagrant/action/builtin/nfs"
autoload :Provision, "vagrant/action/builtin/provision"
autoload :SSHExec, "vagrant/action/builtin/ssh_exec"
autoload :SSHRun, "vagrant/action/builtin/ssh_run"

View File

@ -0,0 +1,117 @@
require 'digest/md5'
require 'fileutils'
require 'pathname'
require "log4r"
module Vagrant
module Action
module Builtin
# This built-in middleware exports and mounts NFS shared folders.
#
# To use this middleware, two configuration parameters must be given
# via the environment hash:
#
# - `:nfs_host_ip` - The IP of where to mount the NFS folder from.
# - `:nfs_machine_ip` - The IP of the machine where the NFS folder
# will be mounted.
#
class NFS
def initialize(app, env)
@app = app
@logger = Log4r::Logger.new("vagrant::action::builtin::nfs")
end
def call(env)
# We forward things along first. We do everything on the tail
# end of the middleware call.
@app.call(env)
# Used by prepare_permission, so we need to save it
@env = env
raise Errors::NFSNoHostIP if !env[:nfs_host_ip]
raise Errors::NFSNoGuestIP if !env[:nfs_machine_ip]
folders = {}
env[:machine].config.vm.synced_folders.each do |id, opts|
# If this synced folder doesn't enable NFS, ignore it.
next if !opts[:nfs]
# Expand the host path, create it if we have to and
# store away the folder.
hostpath = Pathname.new(opts[:hostpath]).expand_path(env[:root_path])
if !hostpath.directory? && opts[:create]
# Host path doesn't exist, so let's create it.
@logger.debug("Host path doesn't exist, creating: #{hostpath}")
begin
FileUtils.mkpath(hostpath)
rescue Errno::EACCES
raise Vagrant::Errors::SharedFolderCreateFailed,
:path => hostpath.to_s
end
end
# Set the hostpath back on the options and save it
opts[:hostpath] = hostpath.to_s
folders[id] = opts
end
if !folders.empty?
# Prepare the folder, this means setting up various options
# and such on the folder itself.
folders.each { |id, opts| prepare_folder(opts) }
# Export the folders
env[:ui].info I18n.t("vagrant.actions.vm.nfs.exporting")
env[:host].nfs_export(env[:machine].id, env[:nfs_machine_ip], folders)
# Mount
env[:ui].info I18n.t("vagrant.actions.vm.nfs.mounting")
# Only mount folders that have a guest path specified.
mount_folders = {}
folders.each do |id, opts|
mount_folders[id] = opts.dup if opts[:guestpath]
end
# Mount them!
env[:machine].guest.mount_nfs(env[:nfs_host_ip], mount_folders)
end
end
protected
def prepare_folder(opts)
opts[:map_uid] = prepare_permission(:uid, opts)
opts[:map_gid] = prepare_permission(:gid, opts)
opts[:nfs_version] ||= 3
# The poor man's UUID. An MD5 hash here is sufficient since
# we need a 32 character "uuid" to represent the filesystem
# of an export. Hashing the host path is safe because two of
# the same host path will hash to the same fsid.
opts[:uuid] = Digest::MD5.hexdigest(opts[:hostpath])
end
# Prepares the UID/GID settings for a single folder.
def prepare_permission(perm, opts)
key = "map_#{perm}".to_sym
return nil if opts.has_key?(key) && opts[key].nil?
# The options on the hash get priority, then the default
# values
value = opts.has_key?(key) ? opts[key] : @env[:machine].config.nfs.send(key)
return value if value != :auto
# Get UID/GID from folder if we've made it this far
# (value == :auto)
stat = File.stat(opts[:hostpath])
return stat.send(perm)
end
end
end
end
end

View File

@ -251,16 +251,12 @@ module Vagrant
error_key(:not_found, "vagrant.actions.vm.host_only_network")
end
class NFSHostRequired < VagrantError
error_key(:host_required, "vagrant.actions.vm.nfs")
class NFSNoGuestIP < VagrantError
error_key(:nfs_no_guest_ip)
end
class NFSNotSupported < VagrantError
error_key(:not_supported, "vagrant.actions.vm.nfs")
end
class NFSNoHostNetwork < VagrantError
error_key(:no_host_network, "vagrant.actions.vm.nfs")
class NFSNoHostIP < VagrantError
error_key(:nfs_no_host_ip)
end
class NoEnvironmentError < VagrantError

View File

@ -1,10 +1,13 @@
require 'log4r'
require "vagrant"
require "vagrant/util/retryable"
module VagrantPlugins
module GuestLinux
class Guest < Vagrant.plugin("2", :guest)
include Vagrant::Util::Retryable
class LinuxError < Vagrant::Errors::VagrantError
error_namespace("vagrant.guest.linux")
end
@ -55,9 +58,12 @@ module VagrantPlugins
# Do the actual creating and mounting
@vm.communicate.sudo("mkdir -p #{real_guestpath}")
@vm.communicate.sudo("mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{real_guestpath}",
:error_class => LinuxError,
:error_key => :mount_nfs_fail)
retryable(:on => LinuxError, :tries => 3, :sleep => 1) do
@vm.communicate.sudo("mount -o vers=#{opts[:nfs_version]} #{ip}:'#{opts[:hostpath]}' #{real_guestpath}",
:error_class => LinuxError,
:error_key => :mount_nfs_fail)
end
end
end

View File

@ -195,6 +195,7 @@ module VagrantPlugins
errors << I18n.t("vagrant.config.vm.box_not_found", :name => box) if \
box && !box_url && !machine.box
has_nfs = false
@synced_folders.each do |id, options|
hostpath = Pathname.new(options[:hostpath]).expand_path(machine.env.root_path)
@ -203,10 +204,23 @@ module VagrantPlugins
:path => options[:hostpath])
end
if options[:nfs] && (options[:owner] || options[:group])
# Owner/group don't work with NFS
errors << I18n.t("vagrant.config.vm.shared_folder_nfs_owner_group",
:path => options[:hostpath])
if options[:nfs]
has_nfs = true
if options[:owner] || options[:group]
# Owner/group don't work with NFS
errors << I18n.t("vagrant.config.vm.shared_folder_nfs_owner_group",
:path => options[:hostpath])
end
end
end
if has_nfs
if !machine.env.host
errors << I18n.t("vagrant.config.vm.nfs_requires_host")
else
errors << I18n.t("vagrant.config.vm.nfs_not_supported") if \
!machine.env.host.nfs?
end
end

View File

@ -33,9 +33,9 @@ module VagrantPlugins
autoload :MessageNotRunning, File.expand_path("../action/message_not_running", __FILE__)
autoload :MessageWillNotDestroy, File.expand_path("../action/message_will_not_destroy", __FILE__)
autoload :Network, File.expand_path("../action/network", __FILE__)
autoload :NFS, File.expand_path("../action/nfs", __FILE__)
autoload :Package, File.expand_path("../action/package", __FILE__)
autoload :PackageVagrantfile, File.expand_path("../action/package_vagrantfile", __FILE__)
autoload :PrepareNFSSettings, File.expand_path("../action/prepare_nfs_settings", __FILE__)
autoload :PruneNFSExports, File.expand_path("../action/prune_nfs_exports", __FILE__)
autoload :Resume, File.expand_path("../action/resume", __FILE__)
autoload :SaneDefaults, File.expand_path("../action/sane_defaults", __FILE__)
@ -61,6 +61,7 @@ module VagrantPlugins
b.use CheckPortCollisions
b.use PruneNFSExports
b.use NFS
b.use PrepareNFSSettings
b.use ClearSharedFolders
b.use ShareFolders
b.use ClearNetworkInterfaces

View File

@ -1,185 +0,0 @@
require 'digest/md5'
require 'fileutils'
require 'pathname'
require 'log4r'
module VagrantPlugins
module ProviderVirtualBox
module Action
class NFS
def initialize(app,env)
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
@app = app
@env = env
verify_settings if nfs_enabled?
end
def call(env)
@env = env
extract_folders
if !folders.empty?
prepare_folders
export_folders
end
@app.call(env)
mount_folders if !folders.empty?
end
# Returns the folders which are to be synced via NFS.
def folders
@folders ||= {}
end
# Removes the NFS enabled shared folders from the configuration,
# so they will no longer be mounted by the actual shared folder
# task.
def extract_folders
# Load the NFS enabled shared folders
@folders = {}
@env[:machine].config.vm.synced_folders.each do |id, opts|
if opts[:nfs]
# Duplicate the options, set the hostpath, and set disabled on the original
# options so the ShareFolders middleware doesn't try to mount it.
folder = opts.dup
hostpath = Pathname.new(opts[:hostpath]).expand_path(@env[:root_path])
if !hostpath.directory? && opts[:create]
# Host path doesn't exist, so let's create it.
@logger.debug("Host path doesn't exist, creating: #{hostpath}")
begin
FileUtils.mkpath(hostpath)
rescue Errno::EACCES
raise Vagrant::Errors::SharedFolderCreateFailed,
:path => hostpath.to_s
end
end
# Set the hostpath now that it exists.
folder[:hostpath] = hostpath.to_s
# Assign the folder to our instance variable for later use
@folders[id] = folder
# Disable the folder so that regular shared folders don't try to
# mount it.
opts[:disabled] = true
end
end
end
# Prepares the settings for the NFS folders, such as setting the
# options on the NFS folders.
def prepare_folders
@folders = @folders.inject({}) do |acc, data|
id, opts = data
opts[:map_uid] = prepare_permission(:uid, opts)
opts[:map_gid] = prepare_permission(:gid, opts)
opts[:nfs_version] ||= 3
# The poor man's UUID. An MD5 hash here is sufficient since
# we need a 32 character "uuid" to represent the filesystem
# of an export. Hashing the host path is safe because two of
# the same host path will hash to the same fsid.
opts[:uuid] = Digest::MD5.hexdigest(opts[:hostpath])
acc[id] = opts
acc
end
end
# Prepares the UID/GID settings for a single folder.
def prepare_permission(perm, opts)
key = "map_#{perm}".to_sym
return nil if opts.has_key?(key) && opts[key].nil?
# The options on the hash get priority, then the default
# values
value = opts.has_key?(key) ? opts[key] : @env[:machine].config.nfs.send(key)
return value if value != :auto
# Get UID/GID from folder if we've made it this far
# (value == :auto)
stat = File.stat(opts[:hostpath])
return stat.send(perm)
end
# Uses the host class to export the folders via NFS. This typically
# involves adding a line to `/etc/exports` for this VM, but it is
# up to the host class to define the specific behavior.
def export_folders
@env[:ui].info I18n.t("vagrant.actions.vm.nfs.exporting")
@env[:host].nfs_export(@env[:machine].id, guest_ip, folders)
end
# Uses the system class to mount the NFS folders.
def mount_folders
@env[:ui].info I18n.t("vagrant.actions.vm.nfs.mounting")
# Only mount the folders which have a guest path specified
mount_folders = {}
folders.each do |name, opts|
if opts[:guestpath]
mount_folders[name] = opts.dup
end
end
@env[:machine].guest.mount_nfs(host_ip, mount_folders)
end
# Returns the IP address of the first host only network adapter
#
# @return [String]
def host_ip
@env[:machine].provider.driver.read_network_interfaces.each do |adapter, opts|
if opts[:type] == :hostonly
@env[:machine].provider.driver.read_host_only_interfaces.each do |interface|
if interface[:name] == opts[:hostonly]
return interface[:ip]
end
end
end
end
nil
end
# Returns the IP address of the guest by looking at the first
# enabled host only network.
#
# @return [String]
def guest_ip
@env[:machine].config.vm.networks.each do |type, args|
if type == :private_network && args[0].is_a?(String)
return args[0]
end
end
nil
end
# Checks if there are any NFS enabled shared folders.
def nfs_enabled?
@env[:machine].config.vm.synced_folders.each do |id, options|
return true if options[:nfs]
end
false
end
# Verifies that the host is set and supports NFS.
def verify_settings
raise Vagrant::Errors::NFSHostRequired if @env[:host].nil?
raise Vagrant::Errors::NFSNotSupported if !@env[:host].nfs?
raise Vagrant::Errors::NFSNoHostNetwork if !guest_ip
end
end
end
end
end

View File

@ -0,0 +1,51 @@
module VagrantPlugins
module ProviderVirtualBox
module Action
class PrepareNFSSettings
def initialize(app,env)
@app = app
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
end
def call(env)
@app.call(env)
env[:nfs_host_ip] = read_host_ip(env[:machine])
env[:nfs_machine_ip] = read_machine_ip(env[:machine])
end
# Returns the IP address of the first host only network adapter
#
# @param [Machine] machine
# @return [String]
def read_host_ip(machine)
machine.provider.driver.read_network_interfaces.each do |adapter, opts|
if opts[:type] == :hostonly
machine.provider.driver.read_host_only_interfaces.each do |interface|
if interface[:name] == opts[:hostonly]
return interface[:ip]
end
end
end
end
nil
end
# Returns the IP address of the guest by looking at the first
# enabled host only network.
#
# @return [String]
def read_machine_ip(machine)
machine.config.vm.networks.each do |type, args|
if type == :private_network && args[0].is_a?(String)
return args[0]
end
end
nil
end
end
end
end
end

View File

@ -27,7 +27,8 @@ module VagrantPlugins
def shared_folders
{}.tap do |result|
@env[:machine].config.vm.synced_folders.each do |id, data|
next if data[:disabled]
# Ignore NFS shared folders
next if data[:nfs]
# This to prevent overwriting the actual shared folders data
result[id] = data.dup

View File

@ -162,6 +162,12 @@ en:
A multi-vm environment is required for name specification to this command.
multi_vm_target_required: |-
This command requires a specific VM name to target in a multi-VM environment.
nfs_no_guest_ip: |-
No guest IP was given to the Vagrant core NFS helper. This is an
internal error that should be reported as a bug.
nfs_no_host_ip: |-
No host IP was given to the Vagrant core NFS helper. This is
an internal error that should be reported as a bug.
no_env: |-
A Vagrant environment is required to run this command. Run `vagrant init`
to set one up in this directory, or change to a directory with a
@ -406,6 +412,15 @@ en:
network_ip_ends_one: |-
The host only network IP '%{ip}' must not end in a 1, as this
is reserved for the host machine.
nfs_not_supported: |-
It appears your machine doesn't support NFS, or there is not an
adapter to enable NFS on this machine for Vagrant. Please verify
that `nfsd` is installed on your machine, and try again. If you're
on Windows, NFS isn't supported. If the problem persists, please
contact Vagrant support.
nfs_requires_host: |-
Using NFS shared folders requires a host to be specified
using `config.vagrant.host`.
shared_folder_hostpath_missing: |-
The host path of the shared folder is missing: %{path}
shared_folder_nfs_owner_group: |-
@ -697,18 +712,6 @@ en:
specified network manually.
preparing: "Preparing host only network..."
nfs:
host_required: |-
A host class is required for NFS shared folders. By default, these
are auto-detected, but can be overridden with `config.vagrant.host`.
There is currently no host class loaded.
no_host_network: |-
NFS shared folders requires that host only networking is enabled
with a static IP. Please enable host only network and assign a
static IP via `config.vm.network`.
not_supported: |-
The host class is reporting that NFS is not supported by this host,
or `nfsd` may not be installed. Please verify that `nfsd` is installed
on your machine, and retry.
exporting: Exporting NFS shared folders...
mounting: Mounting NFS shared folders...
persist: