diff options
author | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-04-01 20:15:10 +0000 |
---|---|---|
committer | luke <luke@980ebf18-57e1-0310-9a29-db15c13687c0> | 2006-04-01 20:15:10 +0000 |
commit | 48d7fd66a28157c81cce756e93d97139c3cc061b (patch) | |
tree | b29cd9ab612848ab6a39fbbb9e4930069d439393 | |
parent | c7ae8396c420f862cda8f6121e505473abfd3e64 (diff) | |
download | puppet-48d7fd66a28157c81cce756e93d97139c3cc061b.tar.gz puppet-48d7fd66a28157c81cce756e93d97139c3cc061b.tar.xz puppet-48d7fd66a28157c81cce756e93d97139c3cc061b.zip |
Adding filesystem support, and modifying parsedtypes a bit to fix a bug where non-instance lines were being duplicated
git-svn-id: https://reductivelabs.com/svn/puppet/trunk@1048 980ebf18-57e1-0310-9a29-db15c13687c0
-rw-r--r-- | lib/puppet/parameter.rb | 12 | ||||
-rwxr-xr-x | lib/puppet/type/parsedtype.rb | 58 | ||||
-rwxr-xr-x | lib/puppet/type/parsedtype/filesystem.rb | 186 | ||||
-rw-r--r-- | test/puppettest.rb | 9 | ||||
-rwxr-xr-x | test/types/filesystem.rb | 256 |
5 files changed, 487 insertions, 34 deletions
diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb index 81b5cb68c..e0e4ee54b 100644 --- a/lib/puppet/parameter.rb +++ b/lib/puppet/parameter.rb @@ -328,12 +328,12 @@ module Puppet # it possible to call for states, too. def value if self.is_a?(Puppet::State) - # We should return the 'is' value if there's not 'should' value. - # This might be bad, though, because the 'should' method - # knows whether to return an array or not and that info is - # not exposed, and the 'is' value could be a symbol. I can't - # seem to create a test in which this is a problem, but that doesn't - # mean it's not one. + # We should return the 'is' value if there's not 'should' + # value. This might be bad, though, because the 'should' + # method knows whether to return an array or not and that info + # is not exposed, and the 'is' value could be a symbol. I + # can't seem to create a test in which this is a problem, but + # that doesn't mean it's not one. if self.should return self.should else diff --git a/lib/puppet/type/parsedtype.rb b/lib/puppet/type/parsedtype.rb index 358c489ab..386e94656 100755 --- a/lib/puppet/type/parsedtype.rb +++ b/lib/puppet/type/parsedtype.rb @@ -123,13 +123,6 @@ module Puppet super end - # Override the Puppet::Type#[]= method so that we can store the - # instances in per-user arrays. Then just call +super+. - def self.[]=(name, object) - self.instance(object) - super - end - # In addition to removing the instances in @objects, we have to remove # per-user host tab information. def self.clear @@ -138,6 +131,11 @@ module Puppet super end + # Add a non-object comment or whatever to our list of instances + def self.comment(line) + @instances << line + end + # Override the default Puppet::Type method, because instances # also need to be deleted from the @instances hash def self.delete(child) @@ -155,13 +153,6 @@ module Puppet # HEADER: is definitely not recommended.\n} end - # Store a new instance of a host. Called from Host#initialize. - def self.instance(obj) - unless @instances.include?(obj) - @instances << obj - end - end - # Parse a file # # Subclasses must override this method. @@ -174,12 +165,13 @@ module Puppet def self.hash2obj(hash) obj = nil - unless hash.include?(:name) and hash[:name] - raise Puppet::DevError, "Hash was not passed with name" + namevar = self.namevar + unless hash.include?(namevar) and hash[namevar] + raise Puppet::DevError, "Hash was not passed with namevar" end # if the obj already exists with that name... - if obj = self[hash[:name]] + if obj = self[hash[namevar]] # We're assuming here that objects with the same name # are the same object, which *should* be the case, assuming # we've set up our naming stuff correctly everywhere. @@ -200,15 +192,19 @@ module Puppet else # create a new obj, since no existing one seems to # match - obj = self.create(:name => hash[:name]) + obj = self.create(namevar => hash[namevar]) # We can't just pass the hash in at object creation time, # because it sets the should value, not the is value. - hash.delete(:name) + hash.delete(namevar) hash.each { |param, value| obj.is = [param, value] } end + + # And then add it to our list of instances. This maintains the order + # in the file. + @instances << obj end # Retrieve the text for the file. Returns nil in the unlikely @@ -223,10 +219,11 @@ module Puppet # First we mark all of our objects absent; any objects # subsequently found will be marked present self.each { |obj| - obj.each { |state| - state.is = :absent - } + obj.is = [:ensure, :absent] } + + # We clear this, so that non-objects don't get duplicated + @instances.clear self.parse(text) end end @@ -235,6 +232,11 @@ module Puppet def self.store @fileobj ||= @filetype.new(@path) + # Make sure all of our instances are in the to-be-written array + self.each do |inst| + @instances << inst unless @instances.include? inst + end + if @instances.empty? Puppet.notice "No %s instances for %s" % [self.name, @path] else @@ -275,8 +277,13 @@ module Puppet @fileobj.loaded end + # The 'store' method knows how to handle absence vs. presence def create - self[:ensure] = :present + self.store + end + + # The 'store' method knows how to handle absence vs. presence + def destroy self.store end @@ -285,11 +292,6 @@ module Puppet @states.include?(:ensure) and @states[:ensure].is == :present end - def destroy - self[:ensure] = :absent - self.store - end - # Override the default Puppet::Type method because we need to call # the +@filetype+ retrieve method. def retrieve diff --git a/lib/puppet/type/parsedtype/filesystem.rb b/lib/puppet/type/parsedtype/filesystem.rb new file mode 100755 index 000000000..65b1832d3 --- /dev/null +++ b/lib/puppet/type/parsedtype/filesystem.rb @@ -0,0 +1,186 @@ +require 'etc' +require 'facter' +require 'puppet/type/parsedtype' +require 'puppet/type/state' + +module Puppet + newtype(:filesystem, Puppet::Type::ParsedType) do + + ensurable do + newvalue(:present) do + @parent.create() + end + + newvalue(:absent) do + @parent.destroy() + + if @parent.mounted? + @parent.unmount + end + + :filesystem_removed + end + + newvalue(:mounted) do + if @is == :absent + set_present + end + + @parent.mount + + :filesystem_mounted + end + end + + newstate(:device) do + desc "The device providing the filesystem. This can be whatever + device is supporting by the filesystem, including network + devices or devices specified by UUID rather than device + path, depending on the operating system." + end + + # Solaris specifies two devices, not just one. + newstate(:blockdevice) do + desc "The the device to fsck. This is state 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["operatingsystem"].value == "Solaris" + device = @parent.value(:device) + if device =~ %r{/dsk/} + device.sub(%r{/dsk/}, "/rdsk/") + else + nil + end + else + nil + end + end + end + + newstate(:fstype) do + desc "The filesystem type. Valid values depend on the + operating system." + end + + newstate(:options) do + desc "Mount options for the filesystems, as they would + appear in the fstab." + end + + newstate(:pass) do + desc "The pass in which the filesystem is checked." + end + + newstate(:atboot) do + desc "Whether to mount the filesystem at boot. Not all platforms + support this." + end + + newstate(:dump) do + desc "Whether to dump the filesystem. Not all platforms + support this." + end + + newparam(:path) do + desc "The mount path for the filesystem." + + isnamevar + end + + @doc = "Manages mounted filesystems, including putting mount + information into the filesystem table." + + @instances = [] + + case Facter["operatingsystem"].value + when "Solaris": + @path = "/etc/vfstab" + @fields = [:device, :blockdevice, :path, :fstype, :pass, :atboot, + :options] + else + @path = "/etc/fstab" + @fields = [:device, :path, :fstype, :options, :dump, :pass] + end + + @filetype = Puppet::FileType.filetype(:flat) + + # Parse a filesystem tab. + # + # This method also stores existing comments, and it stores all + # filesystems in order, mostly so that comments are retained in the + # order they were written and in proximity to the same fses. + def self.parse(text) + count = 0 + hash = {} + text.chomp.split("\n").each { |line| + case line + when /^#/, /^\s*$/: + # add comments and blank lines to the list as they are + comment(line) + else + values = line.split(/\s+/) + unless @fields.length == values.length + raise Puppet::Error, "Could not parse line %s" % line + end + + @fields.zip(values).each do |field, value| + hash[field] = value + end + + hash2obj(hash) + + hash.clear + count += 1 + end + } + end + + # This only works when the mount point is synced to the fstab. + def mount + output = %x{mount #{self[:path]} 2>&1} + + unless $? == 0 + raise Puppet::Error, "Could not mount %s: %s" % [self[:path], output] + end + end + + # This only works when the mount point is synced to the fstab. + def unmount + output = %x{umount #{self[:path]}} + + unless $? == 0 + raise Puppet::Error, "Could not mount %s" % self[:path] + end + end + + # Is the filesystem currently mounted? + def mounted? + %x{df}.split("\n").find do |line| + fs = line.split(/\s+/)[-1] + fs == self[:path] + end + end + + # Convert the current object into an fstab-style string. + def to_record + self.class.fields.collect do |field| + if value = self.value(field) + value + else + if @states.include? field + self.warning @states[field].inspect + else + self.warning field.inspect + end + raise Puppet::Error, + "Could not retrieve value for %s" % field + end + end.join("\t") + end + end +end + +# $Id$ diff --git a/test/puppettest.rb b/test/puppettest.rb index 8bcef7faa..0524cf769 100644 --- a/test/puppettest.rb +++ b/test/puppettest.rb @@ -44,6 +44,8 @@ module TestPuppet @@tmpfiles = [@configpath, tmpdir()] @@tmppids = [] + @@cleaners = [] + if $0 =~ /.+\.rb/ or Puppet[:debug] Puppet::Log.newdestination :console Puppet::Log.level = :debug @@ -96,8 +98,15 @@ module TestPuppet end end + def cleanup(&block) + @@cleaners << block + end + def teardown stopservices + + @@cleaners.each { |cleaner| cleaner.call() } + @@tmpfiles.each { |file| if FileTest.exists?(file) system("chmod -R 755 %s" % file) diff --git a/test/types/filesystem.rb b/test/types/filesystem.rb new file mode 100755 index 000000000..189430784 --- /dev/null +++ b/test/types/filesystem.rb @@ -0,0 +1,256 @@ +# Test host job creation, modification, and destruction + +if __FILE__ == $0 + $:.unshift '..' + $:.unshift '../../lib' + $puppetbase = "../.." +end + +require 'puppettest' +require 'puppet' +require 'puppet/type/parsedtype/filesystem' +require 'test/unit' +require 'facter' + +class TestFilesystem < Test::Unit::TestCase + include TestPuppet + def setup + super + @filesystemtype = Puppet.type(:filesystem) + @oldfiletype = @filesystemtype.filetype + end + + def teardown + @filesystemtype.filetype = @oldfiletype + Puppet.type(:file).clear + super + end + + # Here we just create a fake host type that answers to all of the methods + # but does not modify our actual system. + def mkfaketype + pfile = tempfile() + old = @filesystemtype.path + @filesystemtype.path = pfile + + cleanup do + @filesystemtype.path = old + @filesystemtype.fileobj = nil + end + + # Reset this, just in case + @filesystemtype.fileobj = nil + end + + def mkfilesystem + filesystem = nil + + if defined? @pcount + @pcount += 1 + else + @pcount = 1 + end + args = { + :path => "/fspuppet%s" % @pcount, + :device => "/dev/dsk%s" % @pcount, + } + + Puppet.type(:filesystem).fields.each do |field| + unless args.include? field + args[field] = "fake%s" % @pcount + end + end + + assert_nothing_raised { + filesystem = Puppet.type(:filesystem).create(args) + } + + return filesystem + end + + def test_simplefilesystem + mkfaketype + host = nil + assert_nothing_raised { + assert_nil(Puppet.type(:filesystem).retrieve) + } + + filesystem = mkfilesystem + + assert_nothing_raised { + Puppet.type(:filesystem).store + } + + assert_nothing_raised { + assert( + Puppet.type(:filesystem).to_file.include?( + Puppet.type(:filesystem).fileobj.read + ), + "File does not include all of our objects" + ) + } + end + + def test_filesystemsparse + assert_nothing_raised { + @filesystemtype.retrieve + } + + # Now just make we've got some filesystems we know will be there + root = @filesystemtype["/"] + assert(root, "Could not retrieve root filesystem") + end + + def test_rootfs + fs = nil + assert_nothing_raised { + Puppet.type(:filesystem).retrieve + } + + assert_nothing_raised { + fs = Puppet.type(:filesystem)["/"] + } + assert(fs, "Could not retrieve root fs") + + assert_nothing_raised { + assert(fs.mounted?, "Root is considered not mounted") + } + end + + # Make sure it reads and writes correctly. + def test_readwrite + assert_nothing_raised { + Puppet::Type.type(:filesystem).retrieve + } + + # Now switch to storing in ram + mkfaketype + + fs = mkfilesystem + + assert(Puppet::Type.type(:filesystem).path != "/etc/fstab") + + assert_events([:filesystem_created], fs) + + text = Puppet::Type.type(:filesystem).fileobj.read + + assert(text =~ /#{fs[:path]}/, "Text did not include new fs") + + fs[:ensure] = :absent + + assert_events([:filesystem_removed], fs) + text = Puppet::Type.type(:filesystem).fileobj.read + + assert(text !~ /#{fs[:path]}/, "Text still includes new fs") + + fs[:ensure] = :present + + assert_events([:filesystem_created], fs) + + text = Puppet::Type.type(:filesystem).fileobj.read + + assert(text =~ /#{fs[:path]}/, "Text did not include new fs") + end + + if Process.uid == 0 + def test_mountfs + fs = nil + case Facter["hostname"].value + when "culain": fs = "/ubuntu" + else + $stderr.puts "No filesystem for mount testing; skipping" + return + end + + backup = tempfile() + + FileUtils.cp(Puppet::Type.type(:filesystem).path, backup) + + # Make sure the original gets reinstalled. + cleanup do + FileUtils.cp(backup, Puppet::Type.type(:filesystem).path) + end + + Puppet.type(:filesystem).retrieve + + obj = Puppet.type(:filesystem)[fs] + + assert(obj, "Could not retrieve %s object" % fs) + + current = nil + + assert_nothing_raised { + current = obj.mounted? + } + + if current + # Make sure the original gets reinstalled. + cleanup do + unless obj.mounted? + obj.mount + end + end + end + + unless current + assert_nothing_raised { + obj.mount + } + end + + # Now copy all of the states' "is" values to the "should" values + obj.each do |state| + state.should = state.is + end + + # Verify we can remove the filesystem + assert_nothing_raised { + obj[:ensure] = :absent + } + + assert_events([:filesystem_removed], obj) + + # And verify it's gone + assert(!obj.mounted?, "Object is mounted after being removed") + + text = Puppet.type(:filesystem).fileobj.read + + assert(text !~ /#{fs}/, + "Fstab still contains %s" % fs) + + assert_raise(Puppet::Error, "Removed filesystem did not throw an error") { + obj.mount + } + + assert_nothing_raised { + obj[:ensure] = :present + } + + assert_events([:filesystem_created], obj) + + assert(File.read(Puppet.type(:filesystem).path) =~ /#{fs}/, + "Fstab does not contain %s" % fs) + + assert(! obj.mounted?, "Object is mounted incorrectly") + + assert_nothing_raised { + obj[:ensure] = :mounted + } + + assert_events([:filesystem_mounted], obj) + + assert(File.read(Puppet.type(:filesystem).path) =~ /#{fs}/, + "Fstab does not contain %s" % fs) + + assert(obj.mounted?, "Object is not mounted") + + unless current + assert_nothing_raised { + obj.unmount + } + end + end + end +end + +# $Id$ |