diff options
author | lamont-granquist <lamont@scriptkiddie.org> | 2014-06-04 14:26:02 -0700 |
---|---|---|
committer | lamont-granquist <lamont@scriptkiddie.org> | 2014-06-04 14:26:02 -0700 |
commit | f367d9a35edc6ec5f0ab2955bba364f0bb2f7fdf (patch) | |
tree | b736a6a8ac6f5815cf35c7e52654ce5fe2c2cf8c | |
parent | 69e554b96d4ef919a87dce892e26331d73cc3054 (diff) | |
parent | afda7ab3cdc5e88a044140dcba31fa8738142754 (diff) | |
download | chef-f367d9a35edc6ec5f0ab2955bba364f0bb2f7fdf.tar.gz |
Merge pull request #1451 from opscode/lcg/CHEF-2824
Add mount provider for Solaris OS and derivates
-rw-r--r-- | lib/chef/platform/provider_mapping.rb | 7 | ||||
-rw-r--r-- | lib/chef/provider/mount/solaris.rb | 233 | ||||
-rw-r--r-- | lib/chef/providers.rb | 1 | ||||
-rw-r--r-- | lib/chef/resource/mount.rb | 7 | ||||
-rw-r--r-- | spec/functional/resource/mount_spec.rb | 25 | ||||
-rw-r--r-- | spec/unit/provider/mount/solaris_spec.rb | 646 |
6 files changed, 907 insertions, 12 deletions
diff --git a/lib/chef/platform/provider_mapping.rb b/lib/chef/platform/provider_mapping.rb index 4ebbde3486..a4b19192ff 100644 --- a/lib/chef/platform/provider_mapping.rb +++ b/lib/chef/platform/provider_mapping.rb @@ -276,6 +276,7 @@ class Chef :solaris => {}, :openindiana => { :default => { + :mount => Chef::Provider::Mount::Solaris, :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Ips, :cron => Chef::Provider::Cron::Solaris, @@ -284,6 +285,7 @@ class Chef }, :opensolaris => { :default => { + :mount => Chef::Provider::Mount::Solaris, :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Ips, :cron => Chef::Provider::Cron::Solaris, @@ -292,6 +294,7 @@ class Chef }, :nexentacore => { :default => { + :mount => Chef::Provider::Mount::Solaris, :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Solaris, :cron => Chef::Provider::Cron::Solaris, @@ -300,6 +303,7 @@ class Chef }, :omnios => { :default => { + :mount => Chef::Provider::Mount::Solaris, :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Ips, :cron => Chef::Provider::Cron::Solaris, @@ -309,6 +313,7 @@ class Chef }, :solaris2 => { :default => { + :mount => Chef::Provider::Mount::Solaris, :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Ips, :cron => Chef::Provider::Cron::Solaris, @@ -316,6 +321,7 @@ class Chef :user => Chef::Provider::User::Solaris, }, "< 5.11" => { + :mount => Chef::Provider::Mount::Solaris, :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::Solaris, :cron => Chef::Provider::Cron::Solaris, @@ -325,6 +331,7 @@ class Chef }, :smartos => { :default => { + :mount => Chef::Provider::Mount::Solaris, :service => Chef::Provider::Service::Solaris, :package => Chef::Provider::Package::SmartOS, :cron => Chef::Provider::Cron::Solaris, diff --git a/lib/chef/provider/mount/solaris.rb b/lib/chef/provider/mount/solaris.rb new file mode 100644 index 0000000000..85158eb564 --- /dev/null +++ b/lib/chef/provider/mount/solaris.rb @@ -0,0 +1,233 @@ +# +# Author:: Hugo Fichter +# Author:: Lamont Granquist (<lamont@getchef.com>) +# Author:: Joshua Timberman (<joshua@getchef.com>) +# Copyright:: Copyright (c) 2009-2014 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 'chef/provider/mount' +require 'chef/log' +require 'chef/mixin/shell_out' +require 'forwardable' + +class Chef + class Provider + class Mount + class Solaris < Chef::Provider::Mount + include Chef::Mixin::ShellOut + extend Forwardable + + VFSTAB = "/etc/vfstab".freeze + + def_delegator :@new_resource, :device, :device + def_delegator :@new_resource, :device_type, :device_type + def_delegator :@new_resource, :dump, :dump + def_delegator :@new_resource, :fstype, :fstype + def_delegator :@new_resource, :mount_point, :mount_point + def_delegator :@new_resource, :options, :options + def_delegator :@new_resource, :pass, :pass + + def load_current_resource + @current_resource = Chef::Resource::Mount.new(new_resource.name) + current_resource.mount_point(mount_point) + current_resource.device(device) + current_resource.device_type(device_type) + update_current_resource_state + end + + def define_resource_requirements + requirements.assert(:mount, :remount) do |a| + a.assertion { !device_should_exist? || ::File.exist?(device) } + a.failure_message(Chef::Exceptions::Mount, "Device #{device} does not exist") + a.whyrun("Assuming device #{device} would have been created") + end + + requirements.assert(:mount, :remount) do |a| + a.assertion { ::File.exist?(mount_point) } + a.failure_message(Chef::Exceptions::Mount, "Mount point #{mount_point} does not exist") + a.whyrun("Assuming mount point #{mount_point} would have been created") + end + end + + def mount_fs + actual_options = options || [] + actual_options.delete("noauto") + command = "mount -F #{fstype}" + command << " -o #{actual_options.join(',')}" unless actual_options.empty? + command << " #{device} #{mount_point}" + shell_out!(command) + end + + def umount_fs + shell_out!("umount #{mount_point}") + end + + def remount_fs + # FIXME: what about options like "-o remount,logging" to enable logging on a UFS device? + shell_out!("mount -o remount #{mount_point}") + end + + def enable_fs + if !mount_options_unchanged? + # we are enabling because our options have changed, so disable first then re-enable. + # XXX: this should be refactored to be the responsibility of the caller + disable_fs if current_resource.enabled + end + + auto = options.nil? || ! options.include?("noauto") + actual_options = unless options.nil? + options.delete("noauto") + options + end + + autostr = auto ? 'yes' : 'no' + passstr = pass == 0 ? "-" : pass + optstr = (actual_options.nil? || actual_options.empty?) ? "-" : actual_options.join(',') + + etc_tempfile do |f| + f.write(IO.read(VFSTAB).chomp) + f.puts("\n#{device}\t-\t#{mount_point}\t#{fstype}\t#{passstr}\t#{autostr}\t#{optstr}") + f.close + # move, preserving modes of destination file + mover = Chef::FileContentManagement::Deploy.strategy(true) + mover.deploy(f.path, VFSTAB) + end + + end + + def disable_fs + contents = [] + + found = false + ::File.readlines(VFSTAB).reverse_each do |line| + if !found && line =~ /^#{device_regex}\s+\S+\s+#{Regexp.escape(mount_point)}/ + found = true + Chef::Log.debug("#{new_resource} is removed from vfstab") + next + end + contents << line + end + + if found + etc_tempfile do |f| + f.write(contents.reverse.join('')) + f.close + # move, preserving modes of destination file + mover = Chef::FileContentManagement::Deploy.strategy(true) + mover.deploy(f.path, VFSTAB) + end + else + # this is likely some kind of internal error, since we should only call disable_fs when there + # the filesystem we want to disable is enabled. + Chef::Log.warn("#{new_resource} did not find the mountpoint to disable in the vfstab") + end + end + + def etc_tempfile + yield Tempfile.open("vfstab", "/etc") + end + + def mount_options_unchanged? + current_resource.fstype == fstype and + current_resource.options == options and + current_resource.dump == dump and + current_resource.pass == pass + end + + def update_current_resource_state + current_resource.mounted(mounted?) + ( enabled, fstype, options, pass ) = read_vfstab_status + current_resource.enabled(enabled) + current_resource.fstype(fstype) + current_resource.options(options) + current_resource.pass(pass) + end + + def enabled? + read_vfstab_status[0] + end + + def mounted? + mounted = false + shell_out!("mount -v").stdout.each_line do |line| + # <device> on <mountpoint> type <fstype> <options> on <date> + # /dev/dsk/c1t0d0s0 on / type ufs read/write/setuid/devices/intr/largefiles/logging/xattr/onerror=panic/dev=700040 on Tue May 1 11:33:55 2012 + case line + when /^#{device_regex}\s+on\s+#{Regexp.escape(mount_point)}\s+/ + Chef::Log.debug("Special device #{device} is mounted as #{mount_point}") + mounted = true + when /^([\/\w]+)\son\s#{Regexp.escape(mount_point)}\s+/ + Chef::Log.debug("Special device #{$1} is mounted as #{mount_point}") + mounted = false + end + end + mounted + end + + private + + def read_vfstab_status + # Check to see if there is a entry in /etc/vfstab. Last entry for a volume wins. + enabled = false + fstype = options = pass = nil + ::File.foreach(VFSTAB) do |line| + case line + when /^[#\s]/ + next + # solaris /etc/vfstab format: + # device device mount FS fsck mount mount + # to mount to fsck point type pass at boot options + when /^#{device_regex}\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ + enabled = true + fstype = $1 + options = $4 + # Store the 'mount at boot' column from vfstab as the 'noauto' option + # in current_resource.options (linux style) + if $3 == "yes" + if options.nil? || options.empty? + options = "noauto" + else + options += ",noauto" + end + end + pass = ( $2 == "-" ) ? 0 : $2.to_i + Chef::Log.debug("Found mount #{device} to #{mount_point} in #{VFSTAB}") + next + when /^[-\/\w]+\s+[-\/\w]+\s+#{Regexp.escape(mount_point)}\s+/ + # if we find a mountpoint on top of our mountpoint, then we are not enabled + enabled = false + Chef::Log.debug("Found conflicting mount point #{mount_point} in #{VFSTAB}") + end + end + [ enabled, fstype, options, pass ] + end + + def device_should_exist? + !%w{tmpfs nfs ctfs proc mntfs objfs sharefs fd smbfs}.include?(fstype) + end + + def device_regex + if ::File.symlink?(device) + "(?:#{Regexp.escape(device)}|#{Regexp.escape(::File.expand_path(::File.readlink(device),::File.dirname(device)))})" + else + Regexp.escape(device) + end + end + + end + end + end +end diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb index d6e70c1d4e..8989aa3beb 100644 --- a/lib/chef/providers.rb +++ b/lib/chef/providers.rb @@ -103,6 +103,7 @@ require 'chef/provider/group/windows' require 'chef/provider/mount/mount' require 'chef/provider/mount/aix' +require 'chef/provider/mount/solaris' require 'chef/provider/mount/windows' require 'chef/provider/deploy/revision' diff --git a/lib/chef/resource/mount.rb b/lib/chef/resource/mount.rb index 507c5329e3..9eafe07253 100644 --- a/lib/chef/resource/mount.rb +++ b/lib/chef/resource/mount.rb @@ -65,10 +65,15 @@ class Chef def device_type(arg=nil) real_arg = arg.kind_of?(String) ? arg.to_sym : arg + valid_devices = if RUBY_PLATFORM =~ /solaris/i + [ :device ] + else + [ :device, :label, :uuid ] + end set_or_return( :device_type, real_arg, - :equal_to => [ :device, :label, :uuid ] + :equal_to => valid_devices ) end diff --git a/spec/functional/resource/mount_spec.rb b/spec/functional/resource/mount_spec.rb index 199ccbd37e..caa139d029 100644 --- a/spec/functional/resource/mount_spec.rb +++ b/spec/functional/resource/mount_spec.rb @@ -21,7 +21,7 @@ require 'chef/mixin/shell_out' require 'tmpdir' # run this test only for following platforms. -include_flag = !(['ubuntu', 'centos', 'aix'].include?(ohai[:platform])) +include_flag = !(['ubuntu', 'centos', 'aix', 'solaris2'].include?(ohai[:platform])) describe Chef::Resource::Mount, :requires_root, :external => include_flag do @@ -52,6 +52,9 @@ describe Chef::Resource::Mount, :requires_root, :external => include_flag do end fstype = "tmpfs" shell_out!("mkfs -q #{device} 512") + when "solaris2" + device = "swap" + fstype = "tmpfs" else end [device, fstype] @@ -71,11 +74,10 @@ describe Chef::Resource::Mount, :requires_root, :external => include_flag do end # platform specific validations. - def mount_should_exists(mount_point, device, fstype = nil, options = nil) + def mount_should_exist(mount_point, device, fstype = nil, options = nil) validation_cmd = "mount | grep #{mount_point} | grep #{device} " validation_cmd << " | grep #{fstype} " unless fstype.nil? validation_cmd << " | grep #{options.join(',')} " unless options.nil? || options.empty? - puts "validation_cmd = #{validation_cmd}" expect(shell_out(validation_cmd).exitstatus).to eq(0) end @@ -87,6 +89,8 @@ describe Chef::Resource::Mount, :requires_root, :external => include_flag do case ohai[:platform] when 'aix' mount_config = "/etc/filesystems" + when 'solaris2' + mount_config = "/etc/vfstab" else mount_config = "/etc/fstab" end @@ -119,7 +123,7 @@ describe Chef::Resource::Mount, :requires_root, :external => include_flag do provider end - def current_resource + let(:current_resource) do provider.load_current_resource provider.current_resource end @@ -138,7 +142,6 @@ describe Chef::Resource::Mount, :requires_root, :external => include_flag do end end end - end after(:all) do @@ -156,28 +159,28 @@ describe Chef::Resource::Mount, :requires_root, :external => include_flag do current_resource.mounted.should be_false new_resource.run_action(:mount) new_resource.should be_updated - mount_should_exists(new_resource.mount_point, new_resource.device) + mount_should_exist(new_resource.mount_point, new_resource.device) end - end - describe "when the filesystem should be remounted and the resource supports remounting" do + # don't run the remount tests on solaris2 (tmpfs does not support remount) + describe "when the filesystem should be remounted and the resource supports remounting", :external => ohai[:platform] == "solaris2" do it "should remount the filesystem if it is mounted" do new_resource.run_action(:mount) - mount_should_exists(new_resource.mount_point, new_resource.device) + mount_should_exist(new_resource.mount_point, new_resource.device) new_resource.supports[:remount] = true new_resource.options "rw,log=NULL" if ohai[:platform] == 'aix' new_resource.run_action(:remount) - mount_should_exists(new_resource.mount_point, new_resource.device, nil, (ohai[:platform] == 'aix') ? new_resource.options : nil) + mount_should_exist(new_resource.mount_point, new_resource.device, nil, (ohai[:platform] == 'aix') ? new_resource.options : nil) end end describe "when the target state is a unmounted filesystem" do it "should umount the filesystem if it is mounted" do new_resource.run_action(:mount) - mount_should_exists(new_resource.mount_point, new_resource.device) + mount_should_exist(new_resource.mount_point, new_resource.device) new_resource.run_action(:umount) mount_should_not_exists(new_resource.mount_point) diff --git a/spec/unit/provider/mount/solaris_spec.rb b/spec/unit/provider/mount/solaris_spec.rb new file mode 100644 index 0000000000..e7dd17c746 --- /dev/null +++ b/spec/unit/provider/mount/solaris_spec.rb @@ -0,0 +1,646 @@ +# +# Author:: Lamont Granquist (<lamont@getchef.com>) +# Copyright:: Copyright (c) 2008-2014 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 'spec_helper' +require 'ostruct' + +describe Chef::Provider::Mount::Solaris do + let(:node) { Chef::Node.new } + + let(:events) { Chef::EventDispatch::Dispatcher.new } + + let(:run_context) { Chef::RunContext.new(node, {}, events) } + + let(:device_type) { :device } + + let(:fstype) { "ufs" } + + let(:device) { "/dev/dsk/c0t2d0s7" } + + let(:mountpoint) { "/mnt/foo" } + + let(:options) { nil } + + let(:new_resource) { + new_resource = Chef::Resource::Mount.new(mountpoint) + new_resource.device device + new_resource.device_type device_type + new_resource.fstype fstype + new_resource.options options + + new_resource.supports :remount => false + new_resource + } + + let(:provider) { + Chef::Provider::Mount::Solaris.new(new_resource, run_context) + } + + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + #device device mount FS fsck mount mount + #to mount to fsck point type pass at boot options + # + fd - /dev/fd fd - no - + /proc - /proc proc - no - + # swap + /dev/dsk/c0t0d0s1 - - swap - no - + # root + /dev/dsk/c0t0d0s0 /dev/rdsk/c0t0d0s0 / ufs 1 no - + # tmpfs + swap - /tmp tmpfs - yes - + # nfs + cartman:/share2 - /cartman nfs - yes rw,soft + # ufs + /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - + EOF + } + + let(:vfstab_file) { + t = Tempfile.new("rspec-vfstab") + t.write(vfstab_file_contents) + t.close + t + } + + let(:mount_output) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t0d0s0 on / type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200000 on Tue Jul 31 22:34:46 2012 + /dev/dsk/c0t2d0s7 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 + EOF + } + + before do + stub_const("Chef::Provider::Mount::Solaris::VFSTAB", vfstab_file.path ) + provider.stub(:shell_out!).with("mount -v").and_return(OpenStruct.new(:stdout => mount_output)) + File.stub(:symlink?).with(device).and_return(false) + File.stub(:exist?).and_call_original # Tempfile.open on ruby 1.8.7 calls File.exist? + File.stub(:exist?).with(device).and_return(true) + File.stub(:exist?).with(mountpoint).and_return(true) + expect(File).to_not receive(:exists?) + end + + describe "#define_resource_requirements" do + before do + # we're not testing the actual actions so stub them all out + [:mount_fs, :umount_fs, :remount_fs, :enable_fs, :disable_fs].each {|m| provider.stub(m) } + end + + it "run_action(:mount) should raise an error if the device does not exist" do + File.stub(:exist?).with(device).and_return(false) + expect { provider.run_action(:mount) }.to raise_error(Chef::Exceptions::Mount) + end + + it "run_action(:remount) should raise an error if the device does not exist" do + File.stub(:exist?).with(device).and_return(false) + expect { provider.run_action(:remount) }.to raise_error(Chef::Exceptions::Mount) + end + + it "run_action(:mount) should raise an error if the mountpoint does not exist" do + File.stub(:exist?).with(mountpoint).and_return false + expect { provider.run_action(:mount) }.to raise_error(Chef::Exceptions::Mount) + end + + it "run_action(:remount) should raise an error if the mountpoint does not exist" do + File.stub(:exist?).with(mountpoint).and_return false + expect { provider.run_action(:remount) }.to raise_error(Chef::Exceptions::Mount) + end + + %w{tmpfs nfs ctfs proc mntfs objfs sharefs fd smbfs}.each do |ft| + context "when the device has a fstype of #{ft}" do + let(:fstype) { ft } + let(:device) { "something_that_is_not_a_file" } + + before do + expect(File).to_not receive(:exist?).with(device) + end + + it "run_action(:mount) should not raise an error" do + expect { provider.run_action(:mount) }.to_not raise_error + end + + it "run_action(:remount) should not raise an error" do + expect { provider.run_action(:remount) }.to_not raise_error + end + end + end + + end + + describe "#load_current_resource" do + context "when loading a normal UFS filesystem" do + + before do + provider.load_current_resource + end + + it "should create a current_resource of type Chef::Resource::Mount" do + expect(provider.current_resource).to be_a(Chef::Resource::Mount) + end + + it "should set the name on the current_resource" do + provider.current_resource.name.should == mountpoint + end + + it "should set the mount_point on the current_resource" do + provider.current_resource.mount_point.should == mountpoint + end + + it "should set the device on the current_resource" do + provider.current_resource.device.should == device + end + + it "should set the device_type on the current_resource" do + provider.current_resource.device_type.should == device_type + end + + it "should set the mounted status on the current_resource" do + expect(provider.current_resource.mounted).to be_true + end + + it "should set the enabled status on the current_resource" do + expect(provider.current_resource.enabled).to be_true + end + + it "should set the fstype field on the current_resource" do + expect(provider.current_resource.fstype).to eql("ufs") + end + + it "should set the options field on the current_resource" do + expect(provider.current_resource.options).to eql(["-", "noauto"]) + end + + it "should set the pass field on the current_resource" do + expect(provider.current_resource.pass).to eql(2) + end + + it "should not throw an exception when the device does not exist - CHEF-1565" do + File.stub(:exist?).with(device).and_return(false) + expect { provider.load_current_resource }.to_not raise_error + end + + it "should not throw an exception when the mount point does not exist" do + File.stub(:exist?).with(mountpoint).and_return false + expect { provider.load_current_resource }.to_not raise_error + end + end + + context "when the device is an smbfs mount" do + let(:mount_output) { + <<-EOF.gsub /^\s*/, '' + //solarsystem/tmp on /mnt type smbfs read/write/setuid/devices/dev=5080000 on Tue Mar 29 11:40:18 2011 + EOF + } + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + //WORKGROUP;username:password@host/share - /mountpoint smbfs - no fileperms=0777,dirperms=0777 + EOF + } + + it "should work at some point in the future" do + pending "SMBFS mounts on solaris look like they will need some future code work and more investigation" + end + end + + context "when the device is an NFS mount" do + let(:mount_output) { + <<-EOF.gsub /^\s*/, '' + cartman:/share2 on /cartman type nfs rsize=32768,wsize=32768,NFSv4,dev=4000004 on Tue Mar 29 11:40:18 2011 + EOF + } + + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + cartman:/share2 - /cartman nfs - yes rw,soft + EOF + } + + let(:fstype) { "nfs" } + + let(:device) { "cartman:/share2" } + + let(:mountpoint) { "/cartman" } + + before do + provider.load_current_resource + end + + it "should set the name on the current_resource" do + provider.current_resource.name.should == mountpoint + end + + it "should set the mount_point on the current_resource" do + provider.current_resource.mount_point.should == mountpoint + end + + it "should set the device on the current_resource" do + provider.current_resource.device.should == device + end + + it "should set the device_type on the current_resource" do + provider.current_resource.device_type.should == device_type + end + + it "should set the mounted status on the current_resource" do + expect(provider.current_resource.mounted).to be_true + end + + it "should set the enabled status on the current_resource" do + expect(provider.current_resource.enabled).to be_true + end + + it "should set the fstype field on the current_resource" do + expect(provider.current_resource.fstype).to eql("nfs") + end + + it "should set the options field on the current_resource" do + expect(provider.current_resource.options).to eql(["rw", "soft", "noauto"]) + end + + it "should set the pass field on the current_resource" do + # is this correct or should it be nil? + expect(provider.current_resource.pass).to eql(0) + end + + end + + context "when the device is symlink" do + + let(:target) { "/dev/mapper/target" } + + let(:mount_output) { + <<-EOF.gsub /^\s*/, '' + #{target} on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 + EOF + } + + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + #{target} /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - + EOF + } + + before do + File.should_receive(:symlink?).with(device).at_least(:once).and_return(true) + File.should_receive(:readlink).with(device).at_least(:once).and_return(target) + + provider.load_current_resource() + end + + it "should set mounted true if the symlink target of the device is found in the mounts list" do + expect(provider.current_resource.mounted).to be_true + end + + it "should set enabled true if the symlink target of the device is found in the vfstab" do + expect(provider.current_resource.enabled).to be_true + end + + it "should have the correct mount options" do + expect(provider.current_resource.options).to eql(["-", "noauto"]) + end + end + + context "when the device is a relative symlink" do + let(:target) { "foo" } + + let(:absolute_target) { File.expand_path(target, File.dirname(device)) } + + let(:mount_output) { + <<-EOF.gsub /^\s*/, '' + #{absolute_target} on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 + EOF + } + + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + #{absolute_target} /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - + EOF + } + + before do + File.should_receive(:symlink?).with(device).at_least(:once).and_return(true) + File.should_receive(:readlink).with(device).at_least(:once).and_return(target) + + provider.load_current_resource() + end + + it "should set mounted true if the symlink target of the device is found in the mounts list" do + expect(provider.current_resource.mounted).to be_true + end + + it "should set enabled true if the symlink target of the device is found in the vfstab" do + expect(provider.current_resource.enabled).to be_true + end + + it "should have the correct mount options" do + expect(provider.current_resource.options).to eql(["-", "noauto"]) + end + end + + context "when the matching mount point is last in the mounts list" do + let(:mount_output) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t0d0s0 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200000 on Tue Jul 31 22:34:46 2012 + /dev/dsk/c0t2d0s7 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 + EOF + } + it "should set mounted true" do + provider.load_current_resource() + provider.current_resource.mounted.should be_true + end + end + + context "when the matching mount point is not last in the mounts list" do + let(:mount_output) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t2d0s7 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 + /dev/dsk/c0t0d0s0 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200000 on Tue Jul 31 22:34:46 2012 + EOF + } + it "should set mounted false" do + provider.load_current_resource() + provider.current_resource.mounted.should be_false + end + end + + context "when the matching mount point is not in the mounts list (mountpoint wrong)" do + let(:mount_output) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t2d0s7 on /mnt/foob type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 + EOF + } + it "should set mounted false" do + provider.load_current_resource() + provider.current_resource.mounted.should be_false + end + end + + context "when the matching mount point is not in the mounts list (raw device wrong)" do + let(:mount_output) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t2d0s72 on /mnt/foo type ufs read/write/setuid/intr/largefiles/xattr/onerror=panic/dev=2200007 on Tue Jul 31 22:34:46 2012 + EOF + } + it "should set mounted false" do + provider.load_current_resource() + provider.current_resource.mounted.should be_false + end + end + + context "when the mount point is last in fstab" do + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t2d0s72 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - + /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - + EOF + } + + it "should set enabled to true" do + provider.load_current_resource + provider.current_resource.enabled.should be_true + end + end + + context "when the mount point is not last in fstab and is a substring of another mount" do + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - + /dev/dsk/c0t2d0s72 /dev/rdsk/c0t2d0s7 /mnt/foo/bar ufs 2 yes - + EOF + } + + it "should set enabled to true" do + provider.load_current_resource + provider.current_resource.enabled.should be_true + end + end + + context "when the mount point is not last in fstab" do + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - + /dev/dsk/c0t2d0s72 /dev/rdsk/c0t2d0s72 /mnt/foo ufs 2 yes - + EOF + } + + it "should set enabled to false" do + provider.load_current_resource + provider.current_resource.enabled.should be_false + end + end + + context "when the mount point is not in fstab, but the mountpoint is a substring of one that is" do + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foob ufs 2 yes - + EOF + } + + it "should set enabled to false" do + provider.load_current_resource + provider.current_resource.enabled.should be_false + end + end + + context "when the mount point is not in fstab, but the device is a substring of one that is" do + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + /dev/dsk/c0t2d0s72 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - + EOF + } + + it "should set enabled to false" do + provider.load_current_resource + provider.current_resource.enabled.should be_false + end + end + + context "when the mountpoint line is commented out" do + let(:vfstab_file_contents) { + <<-EOF.gsub /^\s*/, '' + #/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes - + EOF + } + + it "should set enabled to false" do + provider.load_current_resource + provider.current_resource.enabled.should be_false + end + end + end + + context "after the mount's state has been discovered" do + describe "mount_fs" do + it "should mount the filesystem" do + provider.should_receive(:shell_out!).with("mount -F #{fstype} -o defaults #{device} #{mountpoint}") + provider.mount_fs() + end + + it "should mount the filesystem with options if options were passed" do + options = "logging,noatime,largefiles,nosuid,rw,quota" + new_resource.options(options.split(/,/)) + provider.should_receive(:shell_out!).with("mount -F #{fstype} -o #{options} #{device} #{mountpoint}") + provider.mount_fs() + end + + it "should delete the 'noauto' magic option" do + options = "rw,noauto" + new_resource.options(%w{rw noauto}) + provider.should_receive(:shell_out!).with("mount -F #{fstype} -o rw #{device} #{mountpoint}") + provider.mount_fs() + end + end + + describe "umount_fs" do + it "should umount the filesystem if it is mounted" do + provider.should_receive(:shell_out!).with("umount #{mountpoint}") + provider.umount_fs() + end + end + + describe "remount_fs" do + it "should use mount -o remount" do + provider.should_receive(:shell_out!).with("mount -o remount #{new_resource.mount_point}") + provider.remount_fs + end + end + + describe "when enabling the fs" do + context "in the typical case" do + let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } + + let(:this_mount) { "/dev/dsk/c0t2d0s7\t-\t/mnt/foo\tufs\t2\tyes\tdefaults\n" } + + let(:vfstab_file_contents) { [other_mount].join("\n") } + + before do + provider.stub(:etc_tempfile).and_yield(Tempfile.open("vfstab")) + provider.load_current_resource + provider.enable_fs + end + + it "should leave the other mountpoint alone" do + IO.read(vfstab_file.path).should match(/^#{Regexp.escape(other_mount)}/) + end + + it "should enable the mountpoint we care about" do + IO.read(vfstab_file.path).should match(/^#{Regexp.escape(this_mount)}/) + end + end + + context "when the mount has options=noauto" do + let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } + + let(:this_mount) { "/dev/dsk/c0t2d0s7\t-\t/mnt/foo\tufs\t2\tno\t-\n" } + + let(:options) { [ "noauto" ] } + + let(:vfstab_file_contents) { [other_mount].join("\n") } + + before do + provider.stub(:etc_tempfile).and_yield(Tempfile.open("vfstab")) + provider.load_current_resource + provider.enable_fs + end + + it "should leave the other mountpoint alone" do + IO.read(vfstab_file.path).should match(/^#{Regexp.escape(other_mount)}/) + end + + it "should enable the mountpoint we care about" do + IO.read(vfstab_file.path).should match(/^#{Regexp.escape(this_mount)}/) + end + end + end + + describe "when disabling the fs" do + context "in the typical case" do + let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } + + let(:this_mount) { "/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes -" } + + let(:vfstab_file_contents) { [other_mount, this_mount].join("\n") } + + before do + provider.stub(:etc_tempfile).and_yield(Tempfile.open("vfstab")) + provider.disable_fs + end + + it "should leave the other mountpoint alone" do + IO.read(vfstab_file.path).should match(/^#{Regexp.escape(other_mount)}/) + end + + it "should disable the mountpoint we care about" do + IO.read(vfstab_file.path).should_not match(/^#{Regexp.escape(this_mount)}/) + end + end + + context "when there is a commented out line" do + let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } + + let(:this_mount) { "/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes -" } + + let(:comment) { "#/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes -" } + + let(:vfstab_file_contents) { [other_mount, this_mount, comment].join("\n") } + + before do + provider.stub(:etc_tempfile).and_yield(Tempfile.open("vfstab")) + provider.disable_fs + end + + it "should leave the other mountpoint alone" do + IO.read(vfstab_file.path).should match(/^#{Regexp.escape(other_mount)}/) + end + + it "should disable the mountpoint we care about" do + IO.read(vfstab_file.path).should_not match(/^#{Regexp.escape(this_mount)}/) + end + + it "should keep the comment" do + IO.read(vfstab_file.path).should match(/^#{Regexp.escape(comment)}/) + end + end + + context "when there is a duplicated line" do + let(:other_mount) { "/dev/dsk/c0t2d0s0 /dev/rdsk/c0t2d0s0 / ufs 2 yes -" } + + let(:this_mount) { "/dev/dsk/c0t2d0s7 /dev/rdsk/c0t2d0s7 /mnt/foo ufs 2 yes -" } + + let(:vfstab_file_contents) { [this_mount, other_mount, this_mount].join("\n") } + + before do + provider.stub(:etc_tempfile).and_yield(Tempfile.open("vfstab")) + provider.disable_fs + end + + it "should leave the other mountpoint alone" do + IO.read(vfstab_file.path).should match(/^#{Regexp.escape(other_mount)}/) + end + + it "should still match the duplicated mountpoint" do + IO.read(vfstab_file.path).should match(/^#{Regexp.escape(this_mount)}/) + end + + it "should have removed the last line" do + IO.read(vfstab_file.path).should eql( "#{this_mount}\n#{other_mount}\n" ) + end + end + end + end +end |