summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorantima-gupta <agupta@msystechnologies.com>2020-08-19 18:01:37 +0530
committerantima-gupta <agupta@msystechnologies.com>2020-08-27 14:10:18 +0530
commitf9a0013089ce29f6ffb853f7303be61577bece32 (patch)
tree67150ca329a49ac3b07665be573c0faa1c53b7ed
parentf05dc3618241eaac5755b9904fc9ceb43e59a455 (diff)
downloadchef-f9a0013089ce29f6ffb853f7303be61577bece32.tar.gz
Fixed mount Resource for bind mounts is not idempotent.
Added a separate linux mount provider. Define 'mounted?' method and used 'findmnt -rn' command to list mount points along with device. Added linux_mount recipe to kitchen-tests. Signed-off-by: antima-gupta <agupta@msystechnologies.com>
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/_linux_mount.rb20
-rw-r--r--kitchen-tests/cookbooks/end_to_end/recipes/linux.rb1
-rw-r--r--lib/chef/provider/mount/linux.rb58
-rw-r--r--lib/chef/providers.rb1
-rw-r--r--spec/unit/provider/mount/linux_spec.rb87
5 files changed, 167 insertions, 0 deletions
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/_linux_mount.rb b/kitchen-tests/cookbooks/end_to_end/recipes/_linux_mount.rb
new file mode 100644
index 0000000000..207ef9d1fb
--- /dev/null
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/_linux_mount.rb
@@ -0,0 +1,20 @@
+mount "/proc" do
+ device "proc"
+ fstype "proc"
+ options %w{bind rw}
+ action %i{ mount enable }
+end
+
+mount "/mnt" do
+ device "/tmp"
+ fstype "ext4"
+ options %w{bind rw}
+ action %i{ mount enable }
+end
+
+mount "/mnt" do
+ device "/etc"
+ fstype "ext4"
+ options %w{bind rw}
+ action %i{ mount enable }
+end \ No newline at end of file
diff --git a/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb b/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
index 8fae36662c..bb94fb88d1 100644
--- a/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
+++ b/kitchen-tests/cookbooks/end_to_end/recipes/linux.rb
@@ -122,6 +122,7 @@ include_recipe "::_cron"
include_recipe "::_ohai_hint"
include_recipe "::_openssl"
include_recipe "::_tests"
+include_recipe "::_linux_mount"
# at the moment these do not run properly in docker
# we need to investigate if this is a snap on docker issue or a chef issue
diff --git a/lib/chef/provider/mount/linux.rb b/lib/chef/provider/mount/linux.rb
new file mode 100644
index 0000000000..25a03c08be
--- /dev/null
+++ b/lib/chef/provider/mount/linux.rb
@@ -0,0 +1,58 @@
+#
+# Author:: Joshua Timberman (<joshua@chef.io>)
+# Copyright:: Copyright (c) Chef Software Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require_relative "../mount"
+
+class Chef
+ class Provider
+ class Mount
+ class Linux < Chef::Provider::Mount::Mount
+
+ provides :mount, os: "linux"
+
+ def mounted?
+ mounted = false
+
+ # "mount" outputs the mount points as real paths. Convert
+ # the mount_point of the resource to a real path in case it
+ # contains symlinks in its parents dirs.
+ real_mount_point = if ::File.exists? @new_resource.mount_point
+ ::File.realpath(@new_resource.mount_point)
+ else
+ @new_resource.mount_point
+ end
+
+ shell_out!("findmnt -rn").stdout.each_line do |line|
+ case line
+ when /\A#{Regexp.escape(real_mount_point)}\s+#{device_mount_regex}\s/
+ mounted = true
+ logger.trace("Special device #{device_logstring} mounted as #{real_mount_point}")
+ when %r{\A#{Regexp.escape(real_mount_point)}\s+([/\w])+\s}
+ mounted = false
+ logger.trace("Special device #{$~[1]} mounted as #{real_mount_point}")
+ when %r{\A#{Regexp.escape(real_mount_point)}\s+([/\w])+\[#{device_mount_regex}\]\s}
+ mounted = true
+ logger.trace("Bind device #{device_logstring} mounted as #{real_mount_point}")
+ end
+ end
+ @current_resource.mounted(mounted)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index d2e9f1991b..6b099fc2b7 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -119,6 +119,7 @@ require_relative "provider/mount/mount"
require_relative "provider/mount/aix"
require_relative "provider/mount/solaris"
require_relative "provider/mount/windows"
+require_relative "provider/mount/linux"
require_relative "provider/remote_file/ftp"
require_relative "provider/remote_file/sftp"
diff --git a/spec/unit/provider/mount/linux_spec.rb b/spec/unit/provider/mount/linux_spec.rb
new file mode 100644
index 0000000000..feb8d97167
--- /dev/null
+++ b/spec/unit/provider/mount/linux_spec.rb
@@ -0,0 +1,87 @@
+require "spec_helper"
+require "ostruct"
+
+describe Chef::Provider::Mount::Linux do
+ before(:each) do
+ @node = Chef::Node.new
+ @events = Chef::EventDispatch::Dispatcher.new
+ @run_context = Chef::RunContext.new(@node, {}, @events)
+
+ @new_resource = Chef::Resource::Mount.new("/tmp/foo")
+ @new_resource.device "/dev/sdz1"
+ @new_resource.device_type :device
+ @new_resource.fstype "ext3"
+
+ @new_resource.supports remount: false
+
+ @provider = Chef::Provider::Mount::Linux.new(@new_resource, @run_context)
+
+ allow(::File).to receive(:exists?).with("/dev/sdz1").and_return true
+ allow(::File).to receive(:exists?).with("/tmp/foo").and_return true
+ allow(::File).to receive(:realpath).with("/dev/sdz1").and_return "/dev/sdz1"
+ allow(::File).to receive(:realpath).with("/tmp/foo").and_return "/tmp/foo"
+ end
+
+ it "should set mounted true if the mount point is found in the mounts list" do
+ allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: "/tmp/foo /dev/sdz1 type ext3 (rw)\n"))
+ @provider.load_current_resource
+ expect(@provider.current_resource.mounted).to be_truthy
+ end
+
+ it "should set mounted false if another mount point beginning with the same path is found in the mounts list" do
+ allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: "/tmp/foobar /dev/sdz1 type ext3 (rw)\n"))
+ @provider.load_current_resource
+ expect(@provider.current_resource.mounted).to be_falsey
+ end
+
+ it "should set mounted true if the symlink target of the device is found in the mounts list" do
+ # expand the target path to correct specs on Windows
+ target = ::File.expand_path("/dev/mapper/target")
+
+ allow(::File).to receive(:symlink?).with((@new_resource.device).to_s).and_return(true)
+ allow(::File).to receive(:readlink).with((@new_resource.device).to_s).and_return(target)
+
+ allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: "/tmp/foo #{target} type ext3 (rw)\n"))
+ @provider.load_current_resource
+ expect(@provider.current_resource.mounted).to be_truthy
+ end
+
+ it "should set mounted true if the symlink target of the device is relative and is found in the mounts list - CHEF-4957" do
+ target = "xsdz1"
+
+ # expand the target path to correct specs on Windows
+ absolute_target = ::File.expand_path("/dev/xsdz1")
+
+ allow(::File).to receive(:symlink?).with((@new_resource.device).to_s).and_return(true)
+ allow(::File).to receive(:readlink).with((@new_resource.device).to_s).and_return(target)
+
+ allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: "/tmp/foo #{absolute_target} type ext3 (rw)\n"))
+ @provider.load_current_resource
+ expect(@provider.current_resource.mounted).to be_truthy
+ end
+
+ it "should set mounted true if the mount point is found last in the mounts list" do
+ mount = "#{@new_resource.mount_point} /dev/sdy1 type ext3 (rw)\n"
+ mount << "#{@new_resource.mount_point} #{@new_resource.device} type ext3 (rw)\n"
+
+ allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: mount))
+ @provider.load_current_resource
+ expect(@provider.current_resource.mounted).to be_truthy
+ end
+
+ it "should set mounted false if the mount point is not last in the mounts list" do
+ mount = "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n"
+ mount << "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n"
+
+ allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: mount))
+ @provider.load_current_resource
+ expect(@provider.current_resource.mounted).to be_falsey
+ end
+
+ it "mounted should be false if the mount point is not found in the mounts list" do
+ allow(@provider).to receive(:shell_out!).and_return(OpenStruct.new(stdout: "/dev/sdy1 on /tmp/foo type ext3 (rw)\n"))
+ @provider.load_current_resource
+ expect(@provider.current_resource.mounted).to be_falsey
+ end
+
+end \ No newline at end of file