summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2006-04-01 20:15:10 +0000
committerluke <luke@980ebf18-57e1-0310-9a29-db15c13687c0>2006-04-01 20:15:10 +0000
commit48d7fd66a28157c81cce756e93d97139c3cc061b (patch)
treeb29cd9ab612848ab6a39fbbb9e4930069d439393
parentc7ae8396c420f862cda8f6121e505473abfd3e64 (diff)
downloadpuppet-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.rb12
-rwxr-xr-xlib/puppet/type/parsedtype.rb58
-rwxr-xr-xlib/puppet/type/parsedtype/filesystem.rb186
-rw-r--r--test/puppettest.rb9
-rwxr-xr-xtest/types/filesystem.rb256
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$