diff --git a/lib/puppet/provider/mount/parsed.rb b/lib/puppet/provider/mount/parsed.rb index dd765db8c..c7cc288e5 100644 --- a/lib/puppet/provider/mount/parsed.rb +++ b/lib/puppet/provider/mount/parsed.rb @@ -1,123 +1,266 @@ require 'puppet/provider/parsedfile' require 'puppet/provider/mount' fstab = nil case Facter.value(:osfamily) when "Solaris"; fstab = "/etc/vfstab" +when "AIX"; fstab = "/etc/filesystems" else fstab = "/etc/fstab" end Puppet::Type.type(:mount).provide( :parsed, :parent => Puppet::Provider::ParsedFile, :default_target => fstab, :filetype => :flat ) do include Puppet::Provider::Mount commands :mountcmd => "mount", :umount => "umount" case Facter.value(:osfamily) when "Solaris" @fields = [:device, :blockdevice, :name, :fstype, :pass, :atboot, :options] else @fields = [:device, :name, :fstype, :options, :dump, :pass] end - text_line :comment, :match => /^\s*#/ + if Facter.value(:osfamily) == "AIX" + # * is the comment character on AIX /etc/filesystems + text_line :comment, :match => /^\s*\*/ + else + text_line :comment, :match => /^\s*#/ + end text_line :blank, :match => /^\s*$/ optional_fields = @fields - [:device, :name, :blockdevice] mandatory_fields = @fields - optional_fields # fstab will ignore lines that have fewer than the mandatory number of columns, # so we should, too. field_pattern = '(\s*(?>\S+))' text_line :incomplete, :match => /^(?!#{field_pattern}{#{mandatory_fields.length}})/ - record_line self.name, :fields => @fields, :separator => /\s+/, :joiner => "\t", :optional => optional_fields + case Facter.value(:osfamily) + when "AIX" + # The only field that is actually ordered is :name. See `man filesystems` on AIX + @fields = [:name, :account, :boot, :check, :dev, :free, :mount, :nodename, + :options, :quota, :size, :type, :vfs, :vol, :log] + self.line_separator = "\n" + # Override lines and use scan instead of split, because we DON'T want to + # remove the separators + def self.lines(text) + lines = text.split("\n") + filesystem_stanza = false + filesystem_index = 0 + ret = Array.new + lines.each_with_index do |line,i| + if line.match(%r{^\S+:}) + # Begin new filesystem stanza and save the index + ret[filesystem_index] = filesystem_stanza.join("\n") if filesystem_stanza + filesystem_stanza = Array(line) + filesystem_index = i + # Eat the preceeding blank line + ret[i-1] = nil if i > 0 and ret[i-1] and ret[i-1].match(%r{^\s*$}) + nil + elsif line.match(%r{^(\s*\*.*|\s*)$}) + # Just a comment or blank line; add in place + ret[i] = line + else + # Non-comments or blank lines must be part of a stanza + filesystem_stanza << line + end + end + # Add the final stanza to the return + ret[filesystem_index] = filesystem_stanza.join("\n") if filesystem_stanza + ret = ret.compact.flatten + ret.reject { |line| line.match(/^\* HEADER/) } + end + def self.header + super.gsub(/^#/,'*') + end + + record_line self.name, + :fields => @fields, + :separator => /\n/, + :block_eval => :instance do + + def post_parse(result) + property_map = { + :dev => :device, + :nodename => :nodename, + :options => :options, + :vfs => :fstype, + } + # Result is modified in-place instead of being returned; icky! + memo = result.dup + result.clear + # Save the line for later, just in case it is unparsable + result[:line] = @fields.collect do |field| + memo[field] if memo[field] != :absent + end.compact.join("\n") + result[:record_type] = memo[:record_type] + special_options = Array.new + result[:name] = memo[:name].sub(%r{:\s*$},'').strip + memo.each do |_,k_v| + if k_v and k_v.is_a?(String) and k_v.match("=") + attr_name, attr_value = k_v.split("=",2).map(&:strip) + if attr_map_name = property_map[attr_name.to_sym] + # These are normal "options" options (see `man filesystems`) + result[attr_map_name] = attr_value + else + # These /etc/filesystem attributes have no mount resource simile, + # so are added to the "options" property for puppet's sake + special_options << "#{attr_name}=#{attr_value}" + end + if result[:nodename] + result[:device] = "#{result[:nodename]}:#{result[:device]}" + result.delete(:nodename) + end + end + end + result[:options] = [result[:options],special_options.sort].flatten.compact.join(',') + if ! result[:device] + result[:device] = :absent + Puppet.err "Prefetch: Mount[#{result[:name]}]: Field 'device' is missing" + end + if ! result[:fstype] + result[:fstype] = :absent + Puppet.err "Prefetch: Mount[#{result[:name]}]: Field 'fstype' is missing" + end + end + def to_line(result) + output = Array.new + output << "#{result[:name]}:" + if result[:device] and result[:device].match(%r{^/}) + output << "\tdev\t\t= #{result[:device]}" + elsif result[:device] and result[:device] != :absent + if ! result[:device].match(%{^.+:/}) + # Just skip this entry; it was malformed to begin with + Puppet.err "Mount[#{result[:name]}]: Field 'device' must be in the format of or :" + return result[:line] + end + nodename, path = result[:device].split(":") + output << "\tdev\t\t= #{path}" + output << "\tnodename\t= #{nodename}" + else + # Just skip this entry; it was malformed to begin with + Puppet.err "Mount[#{result[:name]}]: Field 'device' is required" + return result[:line] + end + if result[:fstype] and result[:fstype] != :absent + output << "\tvfs\t\t= #{result[:fstype]}" + else + # Just skip this entry; it was malformed to begin with + Puppet.err "Mount[#{result[:name]}]: Field 'device' is required" + return result[:line] + end + if result[:options] + options = result[:options].split(',') + special_options = options.select do |x| + x.match('=') and + ["account", "boot", "check", "free", "mount", "size", "type", + "vol", "log", "quota"].include? x.split('=').first + end + options = options - special_options + special_options.sort.each do |x| + k, v = x.split("=") + output << "\t#{k}\t\t= #{v}" + end + output << "\toptions\t\t= #{options.join(",")}" unless options.empty? + end + if result[:line] and result[:line].split("\n").sort == output.sort + return "\n#{result[:line]}" + else + return "\n#{output.join("\n")}" + end + end + end + else + record_line self.name, :fields => @fields, :separator => /\s+/, :joiner => "\t", :optional => optional_fields + end # Every entry in fstab is :unmounted until we can prove different def self.prefetch_hook(target_records) target_records.collect do |record| record[:ensure] = :unmounted if record[:record_type] == :parsed record end end def self.instances providers = super mounts = mountinstances.dup # Update fstab entries that are mounted providers.each do |prov| if mounts.delete({:name => prov.get(:name), :mounted => :yes}) then prov.set(:ensure => :mounted) end end # Add mounts that are not in fstab but mounted mounts.each do |mount| providers << new(:ensure => :ghost, :name => mount[:name]) end providers end def self.prefetch(resources = nil) # Get providers for all resources the user defined and that match # a record in /etc/fstab. super # We need to do two things now: # - Update ensure from :unmounted to :mounted if the resource is mounted # - Check for mounted devices that are not in fstab and # set ensure to :ghost (if the user wants to add an entry # to fstab we need to know if the device was mounted before) mountinstances.each do |hash| if mount = resources[hash[:name]] case mount.provider.get(:ensure) when :absent # Mount not in fstab mount.provider.set(:ensure => :ghost) when :unmounted # Mount in fstab mount.provider.set(:ensure => :mounted) end end end end def self.mountinstances # XXX: Will not work for mount points that have spaces in path (does fstab support this anyways?) regex = case Facter.value(:osfamily) when "Darwin" / on (?:\/private\/var\/automount)?(\S*)/ when "Solaris", "HP-UX" /^(\S*) on / when "AIX" /^(?:\S*\s+\S+\s+)(\S+)/ else / on (\S*)/ end instances = [] mount_output = mountcmd.split("\n") if mount_output.length >= 2 and mount_output[1] =~ /^[- \t]*$/ # On some OSes (e.g. AIX) mount output begins with a header line # followed by a line consisting of dashes and whitespace. # Discard these two lines. mount_output[0..1] = [] end mount_output.each do |line| if match = regex.match(line) and name = match.captures.first instances << {:name => name, :mounted => :yes} # Only :name is important here else raise Puppet::Error, "Could not understand line #{line} from mount output" end end instances end def flush needs_mount = @property_hash.delete(:needs_mount) super mount if needs_mount end end diff --git a/lib/puppet/type/mount.rb b/lib/puppet/type/mount.rb index 620ea8dfc..6f9d12629 100644 --- a/lib/puppet/type/mount.rb +++ b/lib/puppet/type/mount.rb @@ -1,286 +1,297 @@ require 'puppet/property/boolean' module Puppet # We want the mount to refresh when it changes. newtype(:mount, :self_refresh => true) do @doc = "Manages mounted filesystems, including putting mount information into the mount table. The actual behavior depends on the value of the 'ensure' parameter. **Refresh:** `mount` resources can respond to refresh events (via `notify`, `subscribe`, or the `~>` arrow). If a `mount` receives an event from another resource **and** its `ensure` attribute is set to `mounted`, Puppet will try to unmount then remount that filesystem. **Autorequires:** If Puppet is managing any parents of a mount resource --- that is, other mount points higher up in the filesystem --- the child mount will autorequire them." feature :refreshable, "The provider can remount the filesystem.", :methods => [:remount] # Use the normal parent class, because we actually want to # call code when sync is called. newproperty(:ensure) do desc "Control what to do with this mount. Set this attribute to `unmounted` to make sure the filesystem is in the filesystem table but not mounted (if the filesystem is currently mounted, it will be unmounted). Set it to `absent` to unmount (if necessary) and remove the filesystem from the fstab. Set to `mounted` to add it to the fstab and mount it. Set to `present` to add to fstab but not change mount/unmount status." # IS -> SHOULD In Sync Action # ghost -> present NO create # absent -> present NO create # (mounted -> present YES) # (unmounted -> present YES) newvalue(:defined) do provider.create return :mount_created end aliasvalue :present, :defined # IS -> SHOULD In Sync Action # ghost -> unmounted NO create, unmount # absent -> unmounted NO create # mounted -> unmounted NO unmount newvalue(:unmounted) do case self.retrieve when :ghost # (not in fstab but mounted) provider.create @resource.flush provider.unmount return :mount_unmounted when nil, :absent # (not in fstab and not mounted) provider.create return :mount_created when :mounted # (in fstab and mounted) provider.unmount syncothers # I guess it's more likely that the mount was originally mounted with # the wrong attributes so I sync AFTER the umount return :mount_unmounted else raise Puppet::Error, "Unexpected change from #{current_value} to unmounted}" end end # IS -> SHOULD In Sync Action # ghost -> absent NO unmount # mounted -> absent NO provider.destroy AND unmount # unmounted -> absent NO provider.destroy newvalue(:absent, :event => :mount_deleted) do current_value = self.retrieve provider.unmount if provider.mounted? provider.destroy unless current_value == :ghost end # IS -> SHOULD In Sync Action # ghost -> mounted NO provider.create # absent -> mounted NO provider.create AND mount # unmounted -> mounted NO mount newvalue(:mounted, :event => :mount_mounted) do # Create the mount point if it does not already exist. current_value = self.retrieve currently_mounted = provider.mounted? provider.create if [nil, :absent, :ghost].include?(current_value) syncothers # The fs can be already mounted if it was absent but mounted provider.property_hash[:needs_mount] = true unless currently_mounted end # insync: mounted -> present # unmounted -> present def insync?(is) if should == :defined and [:mounted,:unmounted].include?(is) true else super end end def syncothers # We have to flush any changes to disk. currentvalues = @resource.retrieve_resource # Determine if there are any out-of-sync properties. oos = @resource.send(:properties).find_all do |prop| unless currentvalues.include?(prop) raise Puppet::DevError, "Parent has property %s but it doesn't appear in the current values", [prop.name] end if prop.name == :ensure false else ! prop.safe_insync?(currentvalues[prop]) end end.each { |prop| prop.sync }.length @resource.flush if oos > 0 end 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." validate do |value| raise Puppet::Error, "device must not contain whitespace: #{value}" if value =~ /\s/ end end # Solaris specifies two devices, not just one. newproperty(:blockdevice) do desc "The device to fsck. This is property is only valid on Solaris, and in most cases will default to the correct value." # Default to the device but with "dsk" replaced with "rdsk". defaultto do if Facter.value(:osfamily) == "Solaris" if device = resource[:device] and device =~ %r{/dsk/} device.sub(%r{/dsk/}, "/rdsk/") elsif fstype = resource[:fstype] and fstype == 'nfs' '-' else nil end else nil end end validate do |value| raise Puppet::Error, "blockdevice must not contain whitespace: #{value}" if value =~ /\s/ end end newproperty(:fstype) do desc "The mount type. Valid values depend on the operating system. This is a required option." validate do |value| raise Puppet::Error, "fstype must not contain whitespace: #{value}" if value =~ /\s/ end end newproperty(:options) do - desc "Mount options for the mounts, as they would - appear in the fstab." + desc "Mount options for the mounts, comma-separated as they would appear + in the fstab on Linux. AIX options other than dev, nodename, or vfs may + be defined here. If specified, AIX options of account, boot, check, free, + mount, size, type, vol, log, and quota must be alphabetically sorted at + the end of the list." validate do |value| raise Puppet::Error, "option must not contain whitespace: #{value}" if value =~ /\s/ end end newproperty(:pass) do desc "The pass in which the mount is checked." defaultto { if @resource.managed? if Facter.value(:osfamily) == 'Solaris' '-' else 0 end end } end newproperty(:atboot, :parent => Puppet::Property::Boolean) do desc "Whether to mount the mount at boot. Not all platforms support this." def munge(value) munged = super if munged :yes else :no end end end newproperty(:dump) do desc "Whether to dump the mount. Not all platform support this. Valid values are `1` or `0` (or `2` on FreeBSD). Default is `0`." if Facter.value(:operatingsystem) == "FreeBSD" newvalue(%r{(0|1|2)}) else newvalue(%r{(0|1)}) end defaultto { 0 if @resource.managed? } end newproperty(:target) do desc "The file in which to store the mount table. Only used by those providers that write to disk." defaultto { if @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile) @resource.class.defaultprovider.default_target else nil end } end newparam(:name) do desc "The mount path for the mount." isnamevar validate do |value| raise Puppet::Error, "name must not contain whitespace: #{value}" if value =~ /\s/ end munge do |value| value.gsub(/^(.+?)\/*$/, '\1') end end newparam(:remounts) do desc "Whether the mount can be remounted `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", "DragonFly", "OpenBSD" + when "FreeBSD", "Darwin", "DragonFly", "OpenBSD" false + when "AIX" + if Facter.value(:kernelmajversion) == "5300" + false + elsif resource[:device] and resource[:device].match(%r{^[^/]+:/}) + false + else + true + end else true end end end def refresh # Only remount if we're supposed to be mounted. provider.remount if self.should(:fstype) != "swap" and provider.mounted? end def value(name) name = name.intern if property = @parameters[name] return property.value end end # Ensure that mounts higher up in the filesystem are mounted first autorequire(:mount) do dependencies = [] Pathname.new(@parameters[:name].value).ascend do |parent| dependencies.unshift parent.to_s end dependencies[0..-2] end end end diff --git a/spec/fixtures/unit/provider/mount/parsed/aix.filesystems b/spec/fixtures/unit/provider/mount/parsed/aix.filesystems index 7347f2b8c..c385536ce 100644 --- a/spec/fixtures/unit/provider/mount/parsed/aix.filesystems +++ b/spec/fixtures/unit/provider/mount/parsed/aix.filesystems @@ -1,144 +1,152 @@ * IBM_PROLOG_BEGIN_TAG * This is an automatically generated prolog. * * bos61B src/bos/etc/filesystems/filesystems 1.23 * * Licensed Materials - Property of IBM * * COPYRIGHT International Business Machines Corp. 1985,1993 * All Rights Reserved * * US Government Users Restricted Rights - Use, duplication or * disclosure restricted by GSA ADP Schedule Contract with IBM Corp. * * @(#)filesystems @(#)29 1.23 src/bos/etc/filesystems/filesystems, cmdfs, bos61B, b2007_38A8 8/16/07 17:18:35 * IBM_PROLOG_END_TAG * * COMPONENT_NAME: CMDFS * * FUNCTIONS: none * * ORIGINS: 27 * * (C) COPYRIGHT International Business Machines Corp. 1985, 1993 * All Rights Reserved * Licensed Materials - Property of IBM * * US Government Users Restricted Rights - Use, duplication or * disclosure restricted by GSA ADP Schedule Contract with IBM Corp. * * * * This version of /etc/filesystems assumes that only the root file system * is created and ready. As new file systems are added, change the check, * mount, free, log, vol and vfs entries for the appropriate stanza. * /: - dev = /dev/hd4 - vfs = jfs2 - log = /dev/hd8 - mount = automatic - check = false - type = bootfs - vol = root - free = true - quota = no + dev = /dev/hd4 + vfs = jfs2 + check = false + free = true + log = NULL + mount = automatic + quota = no + type = bootfs + vol = root /home: - dev = /dev/hd1 - vfs = jfs2 - log = /dev/hd8 - mount = true - check = true - vol = /home - free = false - quota = no + dev = /dev/hd1 + vfs = jfs2 + check = true + free = false + log = NULL + mount = true + quota = no + vol = /home /usr: - dev = /dev/hd2 - vfs = jfs2 - log = /dev/hd8 - mount = automatic - check = false - type = bootfs - vol = /usr - free = false - quota = no + dev = /dev/hd2 + vfs = jfs2 + check = false + free = false + log = NULL + mount = automatic + quota = no + type = bootfs + vol = /usr /var: - dev = /dev/hd9var - vfs = jfs2 - log = /dev/hd8 - mount = automatic - check = false - type = bootfs - vol = /var - free = false - quota = no + dev = /dev/hd9var + vfs = jfs2 + check = false + free = false + log = NULL + mount = automatic + quota = no + type = bootfs + vol = /var /tmp: - dev = /dev/hd3 - vfs = jfs2 - log = /dev/hd8 - mount = automatic - check = false - vol = /tmp - free = false - quota = no + dev = /dev/hd3 + vfs = jfs2 + check = false + free = false + log = NULL + mount = automatic + quota = no + vol = /tmp /admin: - dev = /dev/hd11admin - vfs = jfs2 - log = /dev/hd8 - mount = true - check = false - vol = /admin - free = false - quota = no + dev = /dev/hd11admin + vfs = jfs2 + check = false + free = false + log = NULL + mount = true + quota = no + vol = /admin /proc: - dev = /proc - vol = "/proc" - mount = true - check = false - free = false - vfs = procfs + dev = /proc + vol = "/proc" + mount = true + check = false + free = false + vfs = procfs /opt: - dev = /dev/hd10opt - vfs = jfs2 - log = /dev/hd8 - mount = true - check = true - vol = /opt - free = false - quota = no + dev = /dev/hd10opt + vfs = jfs2 + log = /dev/hd8 + mount = true + check = true + vol = /opt + free = false + quota = no /var/adm/ras/livedump: - dev = /dev/livedump - vfs = jfs2 - log = /dev/hd8 - mount = true - account = false - quota = no + dev = /dev/livedump + vfs = jfs2 + log = /dev/hd8 + mount = true + account = false + quota = no /stage/middleware: - dev = "/stage/middleware" - vfs = nfs - nodename = 192.168.1.11 - mount = true - type = nfs - options = ro,bg,hard,intr,sec=sys - account = false + dev = "/stage/middleware" + vfs = nfs + nodename = 192.168.1.11 + mount = true + type = nfs + options = ro,bg,hard,intr,sec=sys + account = false /home/u0010689: - dev = "/userdata/20010689" - vfs = nfs - nodename = 192.168.1.11 - mount = true - type = nfs - options = bg,hard,intr - account = false + dev = "/userdata/20010689" + vfs = nfs + nodename = 192.168.1.11 + mount = true + type = nfs + options = bg,hard,intr + account = false +/srv/aix: + dev = /srv/aix + nodename = mynode + vfs = nfs + account = false + log = NULL + mount = true + options = vers=2 diff --git a/spec/fixtures/unit/provider/mount/parsed/aix.mount b/spec/fixtures/unit/provider/mount/parsed/aix.mount index 380dbc5ae..3da92074c 100644 --- a/spec/fixtures/unit/provider/mount/parsed/aix.mount +++ b/spec/fixtures/unit/provider/mount/parsed/aix.mount @@ -1,7 +1,11 @@ -node mounted mounted over vfs date options ----- ------- ------------ --- ------------ ------------------- - /dev/hd0 / jfs Dec 17 08:04 rw, log =/dev/hd8 - /dev/hd3 /tmp jfs Dec 17 08:04 rw, log =/dev/hd8 - /dev/hd1 /home jfs Dec 17 08:06 rw, log =/dev/hd8 - /dev/hd2 /usr jfs Dec 17 08:06 rw, log =/dev/hd8 -sue /home/local/src /usr/code nfs Dec 17 08:06 ro, log =/dev/hd8 + node mounted mounted over vfs date options +-------- --------------- --------------- ------ ------------ --------------- + /dev/hd4 / jfs2 Feb 05 10:27 rw,log=NULL + /dev/hd2 /usr jfs2 Feb 05 10:28 rw,log=NULL + /dev/hd9var /var jfs2 Feb 05 10:28 rw,log=NULL + /dev/hd3 /tmp jfs2 Feb 05 10:28 rw,log=NULL + /dev/hd1 /home jfs2 Feb 05 10:28 rw,log=NULL + /dev/hd11admin /admin jfs2 Feb 05 10:28 rw,log=NULL + /proc /proc procfs Feb 05 10:28 rw + /dev/hd10opt /opt jfs2 Feb 05 10:28 rw,log=NULL +mynode /srv/aix /srv/aix nfs Mar 19 14:33 vers=2,rw diff --git a/spec/unit/provider/mount/parsed_spec.rb b/spec/unit/provider/mount/parsed_spec.rb index 196dd4635..d691efe39 100755 --- a/spec/unit/provider/mount/parsed_spec.rb +++ b/spec/unit/provider/mount/parsed_spec.rb @@ -1,283 +1,309 @@ #! /usr/bin/env ruby require 'spec_helper' require 'shared_behaviours/all_parsedfile_providers' describe Puppet::Type.type(:mount).provider(:parsed), :unless => Puppet.features.microsoft_windows? do + before :each do + Facter.clear + end let :vfstab_sample do "/dev/dsk/c0d0s0 /dev/rdsk/c0d0s0 \t\t / \t ufs 1 no\t-" end let :fstab_sample do "/dev/vg00/lv01\t/spare \t \t ext3 defaults\t1 2" end # LAK:FIXME I can't mock Facter because this test happens at parse-time. it "should default to /etc/vfstab on Solaris" do pending "This test only works on Solaris" unless Facter.value(:osfamily) == 'Solaris' described_class.default_target.should == '/etc/vfstab' end + it "should default to /etc/vfstab on Solaris" do + pending "This test only works on AIX" unless Facter.value(:osfamily) == 'AIX' + described_class.default_target.should == '/etc/filesystems' + end + it "should default to /etc/fstab on anything else" do pending "This test does not work on Solaris" if Facter.value(:osfamily) == 'Solaris' described_class.default_target.should == '/etc/fstab' end describe "when parsing a line" do it "should not crash on incomplete lines in fstab" do parse = described_class.parse <<-FSTAB /dev/incomplete /dev/device name FSTAB expect { described_class.to_line(parse[0]) }.to_not raise_error end # it_should_behave_like "all parsedfile providers", # provider_class, my_fixtures('*.fstab') describe "on Solaris", :if => Facter.value(:osfamily) == 'Solaris' do it "should extract device from the first field" do described_class.parse_line(vfstab_sample)[:device].should == '/dev/dsk/c0d0s0' end it "should extract blockdevice from second field" do described_class.parse_line(vfstab_sample)[:blockdevice].should == "/dev/rdsk/c0d0s0" end it "should extract name from third field" do described_class.parse_line(vfstab_sample)[:name].should == "/" end it "should extract fstype from fourth field" do described_class.parse_line(vfstab_sample)[:fstype].should == "ufs" end it "should extract pass from fifth field" do described_class.parse_line(vfstab_sample)[:pass].should == "1" end it "should extract atboot from sixth field" do described_class.parse_line(vfstab_sample)[:atboot].should == "no" end it "should extract options from seventh field" do described_class.parse_line(vfstab_sample)[:options].should == "-" end end describe "on other platforms than Solaris", :if => Facter.value(:osfamily) != 'Solaris' do it "should extract device from the first field" do described_class.parse_line(fstab_sample)[:device].should == '/dev/vg00/lv01' end it "should extract name from second field" do described_class.parse_line(fstab_sample)[:name].should == "/spare" end it "should extract fstype from third field" do described_class.parse_line(fstab_sample)[:fstype].should == "ext3" end it "should extract options from fourth field" do described_class.parse_line(fstab_sample)[:options].should == "defaults" end it "should extract dump from fifth field" do described_class.parse_line(fstab_sample)[:dump].should == "1" end it "should extract options from sixth field" do described_class.parse_line(fstab_sample)[:pass].should == "2" end end end describe "mountinstances" do it "should get name from mountoutput found on Solaris" do Facter.stubs(:value).with(:osfamily).returns 'Solaris' described_class.stubs(:mountcmd).returns(File.read(my_fixture('solaris.mount'))) mounts = described_class.mountinstances mounts.size.should == 6 mounts[0].should == { :name => '/', :mounted => :yes } mounts[1].should == { :name => '/proc', :mounted => :yes } mounts[2].should == { :name => '/etc/mnttab', :mounted => :yes } mounts[3].should == { :name => '/tmp', :mounted => :yes } mounts[4].should == { :name => '/export/home', :mounted => :yes } mounts[5].should == { :name => '/ghost', :mounted => :yes } end it "should get name from mountoutput found on HP-UX" do Facter.stubs(:value).with(:osfamily).returns 'HP-UX' described_class.stubs(:mountcmd).returns(File.read(my_fixture('hpux.mount'))) mounts = described_class.mountinstances mounts.size.should == 17 mounts[0].should == { :name => '/', :mounted => :yes } mounts[1].should == { :name => '/devices', :mounted => :yes } mounts[2].should == { :name => '/dev', :mounted => :yes } mounts[3].should == { :name => '/system/contract', :mounted => :yes } mounts[4].should == { :name => '/proc', :mounted => :yes } mounts[5].should == { :name => '/etc/mnttab', :mounted => :yes } mounts[6].should == { :name => '/etc/svc/volatile', :mounted => :yes } mounts[7].should == { :name => '/system/object', :mounted => :yes } mounts[8].should == { :name => '/etc/dfs/sharetab', :mounted => :yes } mounts[9].should == { :name => '/lib/libc.so.1', :mounted => :yes } mounts[10].should == { :name => '/dev/fd', :mounted => :yes } mounts[11].should == { :name => '/tmp', :mounted => :yes } mounts[12].should == { :name => '/var/run', :mounted => :yes } mounts[13].should == { :name => '/export', :mounted => :yes } mounts[14].should == { :name => '/export/home', :mounted => :yes } mounts[15].should == { :name => '/rpool', :mounted => :yes } mounts[16].should == { :name => '/ghost', :mounted => :yes } end it "should get name from mountoutput found on Darwin" do Facter.stubs(:value).with(:osfamily).returns 'Darwin' described_class.stubs(:mountcmd).returns(File.read(my_fixture('darwin.mount'))) mounts = described_class.mountinstances mounts.size.should == 6 mounts[0].should == { :name => '/', :mounted => :yes } mounts[1].should == { :name => '/dev', :mounted => :yes } mounts[2].should == { :name => '/net', :mounted => :yes } mounts[3].should == { :name => '/home', :mounted => :yes } mounts[4].should == { :name => '/usr', :mounted => :yes } mounts[5].should == { :name => '/ghost', :mounted => :yes } end it "should get name from mountoutput found on Linux" do Facter.stubs(:value).with(:osfamily).returns 'Gentoo' described_class.stubs(:mountcmd).returns(File.read(my_fixture('linux.mount'))) mounts = described_class.mountinstances mounts[0].should == { :name => '/', :mounted => :yes } mounts[1].should == { :name => '/lib64/rc/init.d', :mounted => :yes } mounts[2].should == { :name => '/sys', :mounted => :yes } mounts[3].should == { :name => '/usr/portage', :mounted => :yes } mounts[4].should == { :name => '/ghost', :mounted => :yes } end it "should get name from mountoutput found on AIX" do Facter.stubs(:value).with(:osfamily).returns 'AIX' described_class.stubs(:mountcmd).returns(File.read(my_fixture('aix.mount'))) mounts = described_class.mountinstances mounts[0].should == { :name => '/', :mounted => :yes } - mounts[1].should == { :name => '/tmp', :mounted => :yes } - mounts[2].should == { :name => '/home', :mounted => :yes } - mounts[3].should == { :name => '/usr', :mounted => :yes } - mounts[4].should == { :name => '/usr/code', :mounted => :yes } + mounts[1].should == { :name => '/usr', :mounted => :yes } + mounts[2].should == { :name => '/var', :mounted => :yes } + mounts[3].should == { :name => '/tmp', :mounted => :yes } + mounts[4].should == { :name => '/home', :mounted => :yes } + mounts[5].should == { :name => '/admin', :mounted => :yes } + mounts[6].should == { :name => '/proc', :mounted => :yes } + mounts[7].should == { :name => '/opt', :mounted => :yes } + mounts[8].should == { :name => '/srv/aix', :mounted => :yes } end it "should raise an error if a line is not understandable" do described_class.stubs(:mountcmd).returns("bazinga!") expect { described_class.mountinstances }.to raise_error Puppet::Error, 'Could not understand line bazinga! from mount output' end end - it "should support AIX's paragraph based /etc/filesystems" + it "should support AIX's paragraph based /etc/filesystems" do + pending "This test only works on AIX" unless Facter.value(:osfamily) == 'AIX' + Facter.stubs(:value).with(:osfamily).returns 'AIX' + described_class.stubs(:default_target).returns my_fixture('aix.filesystems') + described_class.stubs(:mountcmd).returns File.read(my_fixture('aix.mount')) + instances = described_class.instances + instances[0].name.should == "/" + instances[0].device.should == "/dev/hd4" + instances[0].fstype.should == "jfs2" + instances[0].options.should == "check=false,free=true,log=NULL,mount=automatic,quota=no,type=bootfs,vol=root" + instances[11].name.should == "/srv/aix" + instances[11].device.should == "mynode" + instances[11].fstype.should == "nfs" + instances[11].options.should == "vers=2,account=false,log=NULL,mount=true" + end my_fixtures('*.fstab').each do |fstab| platform = File.basename(fstab, '.fstab') describe "when calling instances on #{platform}" do before :each do if Facter[:osfamily] == "Solaris" then platform == 'solaris' or pending "We need to stub the operatingsystem fact at load time, but can't" else platform != 'solaris' or pending "We need to stub the operatingsystem fact at load time, but can't" end # Stub the mount output to our fixture. begin mount = my_fixture(platform + '.mount') described_class.stubs(:mountcmd).returns File.read(mount) rescue pending "is #{platform}.mount missing at this point?" end # Note: we have to stub default_target before creating resources # because it is used by Puppet::Type::Mount.new to populate the # :target property. described_class.stubs(:default_target).returns fstab @retrieve = described_class.instances.collect { |prov| {:name => prov.get(:name), :ensure => prov.get(:ensure)}} end # Following mountpoint are present in all fstabs/mountoutputs it "should include unmounted resources" do pending("Solaris:Unable to stub Operating System Fact at runtime", :if => Facter.value(:osfamily) == 'Solaris' ) @retrieve.should include(:name => '/', :ensure => :mounted) end it "should include mounted resources" do pending("Solaris:Unable to stub Operating System Fact at runtime", :if => Facter.value(:osfamily) == "Solaris") @retrieve.should include(:name => '/boot', :ensure => :unmounted) end it "should include ghost resources" do pending("Solaris:Unable to stub Operating System Fact at runtime", :if => Facter.value(:osfamily) == "Solaris") @retrieve.should include(:name => '/ghost', :ensure => :ghost) end end describe "when prefetching on #{platform}" do before :each do if Facter[:osfamily] == "Solaris" then platform == 'solaris' or pending "We need to stub the operatingsystem fact at load time, but can't" else platform != 'solaris' or pending "We need to stub the operatingsystem fact at load time, but can't" end # Stub the mount output to our fixture. begin mount = my_fixture(platform + '.mount') described_class.stubs(:mountcmd).returns File.read(mount) rescue pending "is #{platform}.mount missing at this point?" end # Note: we have to stub default_target before creating resources # because it is used by Puppet::Type::Mount.new to populate the # :target property. described_class.stubs(:default_target).returns fstab @res_ghost = Puppet::Type::Mount.new(:name => '/ghost') # in no fake fstab @res_mounted = Puppet::Type::Mount.new(:name => '/') # in every fake fstab @res_unmounted = Puppet::Type::Mount.new(:name => '/boot') # in every fake fstab @res_absent = Puppet::Type::Mount.new(:name => '/absent') # in no fake fstab # Simulate transaction.rb:prefetch @resource_hash = {} [@res_ghost, @res_mounted, @res_unmounted, @res_absent].each do |resource| @resource_hash[resource.name] = resource end end it "should set :ensure to :unmounted if found in fstab but not mounted" do pending("Solaris:Unable to stub Operating System Fact at runtime", :if => Facter.value(:osfamily) == "Solaris") described_class.prefetch(@resource_hash) @res_unmounted.provider.get(:ensure).should == :unmounted end it "should set :ensure to :ghost if not found in fstab but mounted" do pending("Solaris:Unable to stub Operating System Fact at runtime", :if => Facter.value(:osfamily) == "Solaris") described_class.prefetch(@resource_hash) @res_ghost.provider.get(:ensure).should == :ghost end it "should set :ensure to :mounted if found in fstab and mounted" do pending("Solaris:Unable to stub Operating System Fact at runtime", :if => Facter.value(:osfamily) == "Solaris") described_class.prefetch(@resource_hash) @res_mounted.provider.get(:ensure).should == :mounted end it "should set :ensure to :absent if not found in fstab and not mounted" do described_class.prefetch(@resource_hash) @res_absent.provider.get(:ensure).should == :absent end end end end