Check for modinfo in /sbin if it doesn't appear on the PATH. If it's not found on the PATH or in /sbin, the command will default back to modinfo so the user sees the error message about adding it to their PATH.
387 lines
14 KiB
Ruby
387 lines
14 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)
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute).with("systemctl", "list-units", any_args).
|
|
and_return(Vagrant::Util::Subprocess::Result.new(1, "", ""))
|
|
allow(Vagrant::Util::Platform).to receive(:systemd?).and_return(false)
|
|
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)
|
|
VagrantPlugins::HostLinux::Cap::NFS.reset!
|
|
File.unlink(tmp_exports_path.to_s) if File.exist?(tmp_exports_path.to_s)
|
|
@tmp_exports = nil
|
|
end
|
|
|
|
describe ".nfs_service_name_systemd" do
|
|
let(:cap){ VagrantPlugins::HostLinux::Cap::NFS }
|
|
|
|
context "without service match" do
|
|
it "should use default service name" do
|
|
expect(cap.nfs_service_name_systemd).to eq(cap.const_get(:NFS_DEFAULT_NAME_SYSTEMD))
|
|
end
|
|
end
|
|
|
|
context "with service match" do
|
|
let(:custom_nfs_service_name){ "custom-nfs-server-service-name" }
|
|
before{ expect(Vagrant::Util::Subprocess).to receive(:execute).with("systemctl", "list-units", any_args).
|
|
and_return(Vagrant::Util::Subprocess::Result.new(0, custom_nfs_service_name, "")) }
|
|
|
|
it "should use the matched service name" do
|
|
expect(cap.nfs_service_name_systemd).to eq(custom_nfs_service_name)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".nfs_service_name_sysv" do
|
|
let(:cap){ VagrantPlugins::HostLinux::Cap::NFS }
|
|
|
|
context "without service match" do
|
|
it "should use default service name" do
|
|
expect(cap.nfs_service_name_sysv).to eq(cap.const_get(:NFS_DEFAULT_NAME_SYSV))
|
|
end
|
|
end
|
|
|
|
context "with service match" do
|
|
let(:custom_nfs_service_name){ "/etc/init.d/custom-nfs-server-service-name" }
|
|
before{ expect(Dir).to receive(:glob).with(/.+init\.d.+/).and_return([custom_nfs_service_name]) }
|
|
|
|
it "should use the matched service name" do
|
|
expect(cap.nfs_service_name_sysv).to eq(File.basename(custom_nfs_service_name))
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".nfs_check_command" do
|
|
let(:cap){ caps.get(:nfs_check_command) }
|
|
|
|
context "without systemd" do
|
|
before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(false) }
|
|
|
|
it "should use init.d script" do
|
|
expect(cap.nfs_check_command(env)).to include("init.d")
|
|
end
|
|
end
|
|
context "with systemd" do
|
|
before do
|
|
expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(true)
|
|
end
|
|
|
|
it "should use systemctl" do
|
|
expect(cap.nfs_check_command(env)).to include("systemctl")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".nfs_start_command" do
|
|
let(:cap){ caps.get(:nfs_start_command) }
|
|
|
|
context "without systemd" do
|
|
before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(false) }
|
|
|
|
it "should use init.d script" do
|
|
expect(cap.nfs_start_command(env)).to include("init.d")
|
|
end
|
|
end
|
|
context "with systemd" do
|
|
before{ expect(Vagrant::Util::Platform).to receive(:systemd?).and_return(true) }
|
|
|
|
it "should use systemctl" do
|
|
expect(cap.nfs_start_command(env)).to include("systemctl")
|
|
end
|
|
end
|
|
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(Vagrant::Util::Subprocess).to receive(:execute).and_call_original
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute).with("sudo", "/bin/true").and_return(double(:result, exit_code: 0))
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute).with("/bin/true").and_return(double(:result, exit_code: 0))
|
|
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)
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute).with("mv", any_args).
|
|
and_call_original
|
|
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")
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute).with("mv", any_args).
|
|
and_call_original
|
|
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
|
|
|
|
context "exports file modification" do
|
|
let(:tmp_stat) { double("tmp_stat", uid: 100, gid: 100, mode: tmp_mode) }
|
|
let(:tmp_mode) { 0 }
|
|
let(:exports_stat) { double("stat", uid: exports_uid, gid: exports_gid, mode: exports_mode) }
|
|
let(:exports_uid) { -1 }
|
|
let(:exports_gid) { -1 }
|
|
let(:exports_mode) { 0 }
|
|
let(:new_exports_file) { double("new_exports_file", path: "/dev/null/exports") }
|
|
|
|
before do
|
|
allow(File).to receive(:stat).with(new_exports_file.path).and_return(tmp_stat)
|
|
allow(File).to receive(:stat).with(tmp_exports_path.to_s).and_return(exports_stat)
|
|
allow(new_exports_file).to receive(:puts)
|
|
allow(new_exports_file).to receive(:close)
|
|
allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(Vagrant::Util::Subprocess::Result.new(0, "", ""))
|
|
allow(Tempfile).to receive(:create).with("vagrant").and_return(new_exports_file)
|
|
end
|
|
|
|
it "should retain existing file owner and group IDs" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
|
|
expect(args).to include("sudo")
|
|
expect(args).to include("chown")
|
|
}.and_return(Vagrant::Util::Subprocess::Result.new(0, "", ""))
|
|
described_class.nfs_write_exports("new content")
|
|
end
|
|
|
|
it "should raise custom exception when chown fails" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
|
|
expect(args).to include("sudo")
|
|
expect(args).to include("chown")
|
|
}.and_return(Vagrant::Util::Subprocess::Result.new(1, "", ""))
|
|
expect { described_class.nfs_write_exports("new content") }.to raise_error(Vagrant::Errors::NFSExportsFailed)
|
|
end
|
|
|
|
context "when user has write access to exports file" do
|
|
let(:file_writable?) { true }
|
|
let(:dir_writable?) { false }
|
|
let(:exports_pathname) { double("exports_pathname", writable?: file_writable?, dirname: exports_dir_pathname) }
|
|
let(:exports_dir_pathname) { double("exports_dir_pathname", writable?: dir_writable?) }
|
|
|
|
before do
|
|
allow(File).to receive(:stat).and_return(exports_stat)
|
|
allow(File).to receive(:exist?).and_return(false)
|
|
allow(Pathname).to receive(:new).with(tmp_exports_path.to_s).and_return(exports_pathname)
|
|
end
|
|
|
|
it "should use sudo when moving new file" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
|
|
expect(args).to include("sudo")
|
|
expect(args).to include("mv")
|
|
}.and_return(Vagrant::Util::Subprocess::Result.new(0, "", ""))
|
|
described_class.nfs_write_exports("new content")
|
|
end
|
|
|
|
context "and write access to exports parent directory" do
|
|
let(:dir_writable?) { true }
|
|
|
|
it "should not use sudo when moving new file" do
|
|
expect(Vagrant::Util::Subprocess).to receive(:execute) { |*args|
|
|
expect(args).not_to include("sudo")
|
|
expect(args).to include("mv")
|
|
}.and_return(Vagrant::Util::Subprocess::Result.new(0, "", ""))
|
|
described_class.nfs_write_exports("new content")
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe ".modinfo_path" do
|
|
let(:cap){ VagrantPlugins::HostLinux::Cap::NFS }
|
|
|
|
context "with modinfo on PATH" do
|
|
before do
|
|
expect(Vagrant::Util::Which).to receive(:which).with("modinfo").and_return("/usr/bin/modinfo")
|
|
end
|
|
|
|
it "should use full path to modinfo" do
|
|
expect(cap.modinfo_path).to eq("/usr/bin/modinfo")
|
|
end
|
|
end
|
|
|
|
context "with modinfo at /sbin/modinfo" do
|
|
before do
|
|
expect(Vagrant::Util::Which).to receive(:which).with("modinfo").and_return(nil)
|
|
expect(File).to receive(:file?).with("/sbin/modinfo").and_return(true)
|
|
end
|
|
|
|
it "should use /sbin/modinfo" do
|
|
expect(cap.modinfo_path).to eq("/sbin/modinfo")
|
|
end
|
|
end
|
|
|
|
context "modinfo not found" do
|
|
before do
|
|
expect(Vagrant::Util::Which).to receive(:which).with("modinfo").and_return(nil)
|
|
expect(File).to receive(:file?).with("/sbin/modinfo").and_return(false)
|
|
end
|
|
|
|
it "should use modinfo" do
|
|
expect(cap.modinfo_path).to eq("modinfo")
|
|
end
|
|
end
|
|
|
|
context "with cached value for modinfo_path" do
|
|
before do
|
|
cap.instance_variable_set(:@_modinfo_path, "/usr/local/bin/modinfo")
|
|
end
|
|
|
|
it "should use cached value" do
|
|
expect(cap.modinfo_path).to eq("/usr/local/bin/modinfo")
|
|
end
|
|
end
|
|
end
|
|
end
|