Add a new type called mountpoint. This one should only ensure that a device on a mountpoint is mounted/not mounted.
Valid properties are device, fstype and options but they are not required because it's likely that there already is an fstab entry for the described mount. Mountoptions are currently always treated in sync because checking can be very hard: * plain mount command can show different options, e.g. to mount read-only you say »mount -o ro« but running a plain mount will report options as »read only« on Solaris. * there may be options that show up when calling `mount` that you havent specified explicitly. E.g mounting with `mount -o defaults` or without any option will become 'rw' when running `mount` afterwards Signed-off-by: Stefan Schulte <[email protected]> --- Local-branch: feature/next/mounttab lib/puppet/type/mountpoint.rb | 124 +++++++++++++++++++++ spec/unit/type/mountpoint_spec.rb | 220 +++++++++++++++++++++++++++++++++++++ 2 files changed, 344 insertions(+), 0 deletions(-) create mode 100755 lib/puppet/type/mountpoint.rb create mode 100755 spec/unit/type/mountpoint_spec.rb diff --git a/lib/puppet/type/mountpoint.rb b/lib/puppet/type/mountpoint.rb new file mode 100755 index 0000000..171d683 --- /dev/null +++ b/lib/puppet/type/mountpoint.rb @@ -0,0 +1,124 @@ +require 'puppet/property/list' +module Puppet + newtype(:mountpoint) do + @doc = "Manages mounted filesystems, but does not create any information + in the fstab. You have to use the mounttab type for that. The type is + currently not able to manage mount options. So if you want to have non- + default mountoptions make sure you also use the mounttab type to create + an fstab entry first. + + Note that if a `mountpoint` receives an event from another resource, + it will try to remount the filesystems if it is currently mounted. Depending + on the operating system, the provider and the value of the remount parameter + this can be a simple mount -o remount or mount/remount." + + feature :refreshable, "The provider can remount the filesystem.", + :methods => [:remount] + + newproperty(:ensure) do + desc "Control wether the mountpoint should be mounted or not. You can + use `mounted` to mount the device or `unmounted` to make sure that + no device is mounted on the specified mountpoint" + + newvalue :mounted + newvalue :unmounted + + end + + newproperty(:device) do + desc "The device providing the mount. This can be whatever + device is supporting by the mount, including network + devices or devices specified by UUID rather than device + path, depending on the operating system. If you already have an entry + in your fstab (or you use the mounttab type to create such an entry), + it is generally not necessary to specify the device explicitly" + + validate do |value| + raise Puppet::Error, "device must not contain whitespace: #{value}" if value =~ /\s/ + end + + end + + newproperty(:fstype) do + desc "The mount type. Valid values depend on the + operating system. If you already have an entry in your fstab (or you use + the mounttab type to create such an entry), it is generally not necessary to + specify the fstype explicitly" + + validate do |value| + raise Puppet::Error, "fstype must not contain whitespace: #{value}" if value =~ /\s/ + end + + end + + newproperty(:options, :parent => Puppet::Property::List) do + desc "Mount options for the mount. This property is currently just used + when mounting the device. It is always treated as in sync so if the + desired device is already mounted but mounted with wrong options, + puppet will not correct it. This limitation is there because options + reported by the mount command are often different from the options + you may find in fstab" + + def insync?(is = nil) + true + end + + def delimiter + "," + end + + def inclusive? + true + end + + validate do |value| + raise Puppet::Error, "multiple options have to be specified as an array not a comma separated list" if value =~ /,/ + raise Puppet::Error, "options must not contain whitespace: #{value}" if value =~ /\s/ + end + + end + + newparam(:name) do + desc "The mount path for the mount." + + isnamevar + + validate do |value| + raise Puppet::Error, "mount name must not contain whitespace: #{value}" if value =~ /\s/ + # Except of root / a trailing slash can cause problems (#6793) + raise Puppet::Error, "mount should be specified without a trailing slash: #{value}" if value =~ /.+\/$/ + end + + end + + newparam(:remounts) do + desc "Whether the mount can be remounted with `mount -o remount`. If + this is false, then the filesystem will be unmounted and remounted + manually, which is prone to failure." + + newvalues(:true, :false) + defaultto do + case Facter.value(:operatingsystem) + when "FreeBSD", "Darwin", "AIX" + :false + when "Solaris", "HP-UX" + if fstype = @resource[:fstype] and fstype.downcase == 'nfs' + # According to mount_nfs, remount can just change options from ro to rw + # but nothing more + :false + else + :true + end + else + :true + end + end + end + + def refresh + # Only remount if we're supposed to be mounted. + provider.remount if provider.get(:fstype) != "swap" and provider.get(:ensure) == :mounted + end + + end +end diff --git a/spec/unit/type/mountpoint_spec.rb b/spec/unit/type/mountpoint_spec.rb new file mode 100755 index 0000000..2c40519 --- /dev/null +++ b/spec/unit/type/mountpoint_spec.rb @@ -0,0 +1,220 @@ +#!/usr/bin/env ruby + +require 'spec_helper' + +describe Puppet::Type.type(:mountpoint) do + + before do + @class = described_class + @provider_class = @class.provide(:fake) { mk_resource_methods } + @provider = @provider_class.new + @resource = stub 'resource', :resource => nil, :provider => @provider + + @class.stubs(:defaultprovider).returns @provider_class + @class.any_instance.stubs(:provider).returns @provider + end + + it "should have :name as its keyattribute" do + @class.key_attributes.should == [:name] + end + + describe "when validating attributes" do + + [:name, :provider, :remounts].each do |param| + it "should have a #{param} parameter" do + @class.attrtype(param).should == :param + end + end + + [:ensure, :device, :fstype, :options ].each do |property| + it "should have a #{property} property" do + @class.attrtype(property).should == :property + end + end + + end + + describe "when validating values" do + + describe "for name" do + + it "should support absolute paths" do + proc { @class.new(:name => "/foo", :ensure => :mounted) }.should_not raise_error + end + + it "should support root /" do + proc { @class.new(:name => "/", :ensure => :mounted) }.should_not raise_error + end + + it "should not support whitespace" do + proc { @class.new(:name => "/foo bar", :ensure => :mounted) }.should raise_error(Puppet::Error, /name.*whitespace/) + end + + it "should not allow trailing slashes" do + proc { @class.new(:name => "/foo/", :ensure => :mounted) }.should raise_error(Puppet::Error, /mount should be specified without a trailing slash/) + proc { @class.new(:name => "/foo//", :ensure => :mounted) }.should raise_error(Puppet::Error, /mount should be specified without a trailing slash/) + end + + end + + + describe "for ensure" do + + it "should support mounted as a value for ensure" do + proc { @class.new(:name => "/foo", :ensure => :mounted) }.should_not raise_error + end + + it "should support unmounted as a value for ensure" do + proc { @class.new(:name => "/foo", :ensure => :unmounted) }.should_not raise_error + end + + end + + describe "for device" do + + it "should support normal /dev paths for device" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => '/dev/hda1') }.should_not raise_error + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => '/dev/dsk/c0d0s0') }.should_not raise_error + end + + it "should support labels for device" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => 'LABEL=/boot') }.should_not raise_error + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => 'LABEL=SWAP-hda6') }.should_not raise_error + end + + it "should support pseudo devices for device" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => 'ctfs') }.should_not raise_error + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => 'swap') }.should_not raise_error + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => 'sysfs') }.should_not raise_error + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => 'proc') }.should_not raise_error + end + + it "should not support whitespace in device" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => '/dev/my dev/foo') }.should raise_error Puppet::Error, /device.*whitespace/ + proc { @class.new(:name => "/foo", :ensure => :mounted, :device => "/dev/my\tdev/foo") }.should raise_error Puppet::Error, /device.*whitespace/ + end + + end + + describe "for fstype" do + + it "should support valid fstypes" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :fstype => 'ext3') }.should_not raise_error + proc { @class.new(:name => "/foo", :ensure => :mounted, :fstype => 'proc') }.should_not raise_error + proc { @class.new(:name => "/foo", :ensure => :mounted, :fstype => 'sysfs') }.should_not raise_error + end + + it "should support auto as a special fstype" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :fstype => 'auto') }.should_not raise_error + end + + it "should not support whitespace in fstype" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :fstype => 'ext 3') }.should raise_error Puppet::Error, /fstype.*whitespace/ + end + + end + + describe "for options" do + + it "should support a single option" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :options => 'ro') }.should_not raise_error + end + + it "should support muliple options as an array" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :options => ['ro','rsize=4096']) }.should_not raise_error + end + + it "should support an empty array as options" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :options => []) }.should_not raise_error + end + + it "should not support a comma separated option" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :options => ['ro','foo,bar','intr']) }.should raise_error Puppet::Error, /option.*have to be specified as an array/ + end + + it "should not support blanks in options" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :options => ['ro','foo bar','intr']) }.should raise_error Puppet::Error, /option.*whitespace/ + end + + end + + describe "for remounts" do + + it "should support true as a value for remounts" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :remounts => :true) }.should_not raise_error + end + + it "should support false as value for remounts" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :remounts => :true) }.should_not raise_error + end + + it "should not support anything else as a value" do + proc { @class.new(:name => "/foo", :ensure => :mounted, :remounts => :truu) }.should raise_error Puppet::Error, /remounts failed.*Invalid value/ + end + + ["FreeBSD", "Darwin","AIX"].each do |os| + describe "when on #{os}" do + + it "should always default to false" do + Facter.stubs(:value).with(:operatingsystem).returns os + @class.new(:name => "/mnt/foo", :fstype => 'ext3', :ensure => :mounted)[:remounts].should == :false + @class.new(:name => "/mnt/foo", :fstype => 'nfs', :ensure => :mounted)[:remounts].should == :false + end + + end + end + + ["Solaris", "HP-UX"].each do |os| + describe "when on #{os}" do + + before :each do + Facter.stubs(:value).with(:operatingsystem).returns os + end + + it "should default to false for nfs mounts" do + @class.new(:name => "/mnt/foo", :fstype => 'nfs', :ensure => :mounted)[:remounts].should == :false + end + + it "should default to true for other fstypes" do + @class.new(:name => "/mnt/foo", :fstype => 'ext3', :ensure => :mounted)[:remounts].should == :true + end + + end + end + + ["Linux"].each do |os| + describe "when on #{os}" do + it "should alway default to true" do + Facter.stubs(:value).with(:operatingsystem).returns os + @class.new(:name => "/mnt/foo", :fstype => 'ext3', :ensure => :mounted)[:remounts].should == :true + @class.new(:name => "/mnt/foo", :fstype => 'nfs', :ensure => :mounted)[:remounts].should == :true + end + end + end + + end + + end + + describe "when syncing options" do + + before :each do + @options = @class.attrclass(:options).new(:resource => @resource, :should => %w{rw rsize=2048 wsize=2048}) + end + + it "should pass the sorted joined array to the provider" do + @provider.expects(:options=).with('rsize=2048,rw,wsize=2048') + @options.sync + end + + # Good look fixing this one ;-) -- stefan + it "should always treat options insync" do + @options.insync?(%w{rw rsize=2048}).should == true + @options.insync?(%w{rw rsize=2048 wsize=2048 intr}).should == true + @options.insync?(%w{rw rsize=1024 wsize=2048}).should == true + @options.insync?(%w{foo}).should == true + end + + end + +end -- 1.7.5.rc3 -- You received this message because you are subscribed to the Google Groups "Puppet Developers" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/puppet-dev?hl=en.
