Brian Cain f0f60a1075 (#4666) Remove duplicate export folders before writing /etc/exports
Prior to this commit, if you set up multiple folders to export with NFS
on linux with the exact same hostpath, the template used to write
/etc/exports would end up placing the same path with the same IP in
/etc/exports and cause an error preventing the folders from being
properly mounted. This commit fixes that by first looking at which
folders are being exported and if there are any duplicates. If so,
remove the duplicates and only export 1 hostpath folder. If these
duplicate folders have differing nfs linux options, an exception must be
thrown because we cannot assume which options the user intended to
export with.
2017-09-05 16:05:14 -07:00

195 lines
7.1 KiB
Ruby

require_relative "../../../../base"
require_relative "../../../../../../plugins/hosts/linux/cap/nfs"
require_relative "../../../../../../lib/vagrant/util"
describe VagrantPlugins::HostLinux::Cap::NFS do
include_context "unit"
let(:caps) do
VagrantPlugins::HostLinux::Plugin
.components
.host_capabilities[:linux]
end
let(:tmp_exports_path) do
@tmp_exports ||= temporary_file
end
let(:exports_path){ VagrantPlugins::HostLinux::Cap::NFS::NFS_EXPORTS_PATH }
let(:env){ double(:env) }
let(:ui){ double(:ui) }
let(:host){ double(:host) }
before do
@original_exports_path = VagrantPlugins::HostLinux::Cap::NFS::NFS_EXPORTS_PATH
VagrantPlugins::HostLinux::Cap::NFS.send(:remove_const, :NFS_EXPORTS_PATH)
VagrantPlugins::HostLinux::Cap::NFS.const_set(:NFS_EXPORTS_PATH, tmp_exports_path.to_s)
end
after do
VagrantPlugins::HostLinux::Cap::NFS.send(:remove_const, :NFS_EXPORTS_PATH)
VagrantPlugins::HostLinux::Cap::NFS.const_set(:NFS_EXPORTS_PATH, @original_exports_path)
File.unlink(tmp_exports_path.to_s) if File.exist?(tmp_exports_path.to_s)
@tmp_exports = nil
end
describe ".nfs_export" do
let(:cap){ caps.get(:nfs_export) }
before do
allow(env).to receive(:host).and_return(host)
allow(host).to receive(:capability).with(:nfs_apply_command).and_return("/bin/true")
allow(host).to receive(:capability).with(:nfs_check_command).and_return("/bin/true")
allow(host).to receive(:capability).with(:nfs_start_command).and_return("/bin/true")
allow(ui).to receive(:info)
allow(cap).to receive(:system).with("sudo /bin/true").and_return(true)
allow(cap).to receive(:system).with("/bin/true").and_return(true)
end
it "should export new entries" do
cap.nfs_export(env, ui, SecureRandom.uuid, ["127.0.0.1"], "tmp" => {:hostpath => "/tmp"})
exports_content = File.read(exports_path)
expect(exports_content).to match(/\/tmp.*127\.0\.0\.1/)
end
it "should not remove existing entries" do
File.write(exports_path, "/custom/directory hostname1(rw,sync,no_subtree_check)")
cap.nfs_export(env, ui, SecureRandom.uuid, ["127.0.0.1"], "tmp" => {:hostpath => "/tmp"})
exports_content = File.read(exports_path)
expect(exports_content).to match(/\/tmp.*127\.0\.0\.1/)
expect(exports_content).to match(/\/custom\/directory.*hostname1/)
end
it "should remove entries no longer valid" do
valid_id = SecureRandom.uuid
other_id = SecureRandom.uuid
content =<<-EOH
# VAGRANT-BEGIN: #{Process.uid} #{other_id}
"/tmp" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=)
# VAGRANT-END: #{Process.uid} #{other_id}
# VAGRANT-BEGIN: #{Process.uid} #{valid_id}
"/var" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=)
# VAGRANT-END: #{Process.uid} #{valid_id}
EOH
File.write(exports_path, content)
cap.nfs_export(env, ui, valid_id, ["127.0.0.1"], "home" => {:hostpath => "/home"})
exports_content = File.read(exports_path)
expect(exports_content).to include("/home")
expect(exports_content).to include("/tmp")
expect(exports_content).not_to include("/var")
end
it "throws an exception with at least 2 different nfs options" do
folders = {"/vagrant"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","all_squash"]},
"/var/www/project"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","sync"]}}
expect { cap.nfs_export(env, ui, SecureRandom.uuid, ["127.0.0.1"], folders) }.
to raise_error Vagrant::Errors::NFSDupePerms
end
it "writes only 1 hostpath for multiple exports" do
folders = {"/vagrant"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","all_squash"]},
"/var/www/otherproject"=>
{:hostpath=>"/newhome/otherproject",
:linux__nfs_options=>["rw","all_squash"]},
"/var/www/project"=>
{:hostpath=>"/home/vagrant",
:linux__nfs_options=>["rw","all_squash"]}}
valid_id = SecureRandom.uuid
content =<<-EOH
\n# VAGRANT-BEGIN: #{Process.uid} #{valid_id}
"/home/vagrant" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=)
"/newhome/otherproject" 127.0.0.1(rw,all_squash,anonuid=,anongid=,fsid=)
# VAGRANT-END: #{Process.uid} #{valid_id}
EOH
cap.nfs_export(env, ui, valid_id, ["127.0.0.1"], folders)
exports_content = File.read(exports_path)
expect(exports_content).to eq(content)
end
end
describe ".nfs_prune" do
let(:cap){ caps.get(:nfs_prune) }
before do
allow(ui).to receive(:info)
end
it "should remove entries no longer valid" do
invalid_id = SecureRandom.uuid
valid_id = SecureRandom.uuid
content =<<-EOH
# VAGRANT-BEGIN: #{Process.uid} #{invalid_id}
"/tmp" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=)
# VAGRANT-END: #{Process.uid} #{invalid_id}
# VAGRANT-BEGIN: #{Process.uid} #{valid_id}
"/var" 127.0.0.1(rw,no_subtree_check,all_squash,anonuid=,anongid=,fsid=)
# VAGRANT-END: #{Process.uid} #{valid_id}
EOH
File.write(exports_path, content)
cap.nfs_prune(env, ui, [valid_id])
exports_content = File.read(exports_path)
expect(exports_content).to include(valid_id)
expect(exports_content).not_to include(invalid_id)
expect(exports_content).to include("/var")
expect(exports_content).not_to include("/tmp")
end
end
describe ".nfs_write_exports" do
before do
File.write(tmp_exports_path, "original content")
end
it "should write updated contents to file" do
described_class.nfs_write_exports("new content")
exports_content = File.read(exports_path)
expect(exports_content).to include("new content")
expect(exports_content).not_to include("original content")
end
it "should only update contents if different" do
original_stat = File.stat(exports_path)
described_class.nfs_write_exports("original content")
updated_stat = File.stat(exports_path)
expect(original_stat).to eq(updated_stat)
end
it "should retain existing file permissions" do
File.chmod(0600, exports_path)
original_stat = File.stat(exports_path)
described_class.nfs_write_exports("original content")
updated_stat = File.stat(exports_path)
expect(original_stat.mode).to eq(updated_stat.mode)
end
it "should raise exception when failing to move new exports file" do
expect(Vagrant::Util::Subprocess).to receive(:execute).and_return(
Vagrant::Util::Subprocess::Result.new(1, "Failed to move file", "")
)
expect{ described_class.nfs_write_exports("new content") }.to raise_error(Vagrant::Errors::NFSExportsFailed)
end
it "should retain existing file owner and group IDs" do
pending("investigate using a simulated FS to test")
test_with_simulated_fs
end
it "should raise custom exception when chown fails" do
pending("investigate using a simulated FS to test")
test_with_simulated_fs
end
end
end