diff options
-rw-r--r-- | lib/puppet/metatype/providers.rb | 68 | ||||
-rw-r--r-- | lib/puppet/provider.rb | 6 | ||||
-rwxr-xr-x | lib/puppet/provider/mount/parsed.rb | 164 | ||||
-rwxr-xr-x | lib/puppet/provider/parsedfile.rb | 154 | ||||
-rw-r--r-- | lib/puppet/type.rb | 8 | ||||
-rwxr-xr-x | lib/puppet/type/mount.rb | 38 | ||||
-rw-r--r-- | lib/puppet/type/state.rb | 32 | ||||
-rw-r--r-- | lib/puppet/util/fileparsing.rb | 49 | ||||
-rw-r--r-- | test/lib/puppettest/fileparsing.rb | 30 | ||||
-rwxr-xr-x | test/providers/parsedfile.rb | 201 | ||||
-rwxr-xr-x | test/providers/parsedmount.rb | 120 | ||||
-rwxr-xr-x | test/types/mount.rb | 90 | ||||
-rwxr-xr-x | test/util/fileparsing.rb | 60 |
13 files changed, 698 insertions, 322 deletions
diff --git a/lib/puppet/metatype/providers.rb b/lib/puppet/metatype/providers.rb index b1ce820be..0165da83e 100644 --- a/lib/puppet/metatype/providers.rb +++ b/lib/puppet/metatype/providers.rb @@ -41,6 +41,64 @@ class Puppet::Type return @defaultprovider end + # Convert a hash, as provided by, um, a provider, into an instance of self. + def self.hash2obj(hash) + obj = nil + + 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[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. + + # Mark found objects as present + obj.is = [:ensure, :present] + obj.state(:ensure).found = :present + hash.each { |param, value| + if state = obj.state(param) + state.is = value + elsif val = obj[param] + obj[param] = val + else + # There is a value on disk, but it should go away + obj.is = [param, value] + obj[param] = :absent + end + } + else + # create a new obj, since no existing one seems to + # match + 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(namevar) + hash.each { |param, value| + obj.is = [param, value] + } + end + + return obj + end + + # Create a list method that just calls our providers. + def self.mkprovider_list + unless respond_to?(:list) + meta_def(:list) do + suitableprovider.find_all { |p| p.respond_to?(:list) }.collect { |prov| + prov.list.each { |h| h[:provider] = prov.name } + }.flatten.collect do |hash| + hash2obj(hash) + end + end + end + end + # Retrieve a provider by name. def self.provider(name) name = Puppet::Util.symbolize(name) @@ -67,9 +125,9 @@ class Puppet::Type # directly on the type that it's implementing. def self.provide(name, options = {}, &block) name = Puppet::Util.symbolize(name) - model = self parent = if pname = options[:parent] + options.delete(:parent) if pname.is_a? Class pname else @@ -85,6 +143,8 @@ class Puppet::Type Puppet::Type::Provider end + options[:model] ||= self + self.providify provider = genclass(name, @@ -92,9 +152,7 @@ class Puppet::Type :hash => @providers, :prefix => "Provider", :block => block, - :attributes => { - :model => model - } + :attributes => options ) return provider @@ -105,6 +163,8 @@ class Puppet::Type def self.providify return if @paramhash.has_key? :provider model = self + + mkprovider_list() newparam(:provider) do desc "The specific backend for #{self.name.to_s} to use. You will seldom need to specify this -- Puppet will usually discover the diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb index 36c685990..a5890f131 100644 --- a/lib/puppet/provider.rb +++ b/lib/puppet/provider.rb @@ -51,7 +51,11 @@ class Puppet::Provider #unless method_defined? name unless metaclass.method_defined? name meta_def(name) do |args| - cmd = command(name) + " " + args + if args + cmd = command(name) + " " + args + else + cmd = command(name) + end # This might throw an ExecutionFailure, but the system above # will catch it, if so. return execute(cmd) diff --git a/lib/puppet/provider/mount/parsed.rb b/lib/puppet/provider/mount/parsed.rb index 086cd5cd9..af67ca6ff 100755 --- a/lib/puppet/provider/mount/parsed.rb +++ b/lib/puppet/provider/mount/parsed.rb @@ -1,150 +1,43 @@ require 'puppet/provider/parsedfile' -Puppet::Type.type(:mount).provide :parsed, :parent => Puppet::Provider::ParsedFile do - @filetype = Puppet::FileType.filetype(:flat) - - commands :mountcmd => "mount", :umount => "umount", :df => "df" - - def self.init - @platform = Facter["operatingsystem"].value - case @platform - when "Solaris": - @path = "/etc/vfstab" - @fields = [:device, :blockdevice, :path, :fstype, :pass, :atboot, - :options] - @fielddefaults = [ nil ] * @fields.size - when "Darwin": - @filetype = Puppet::FileType.filetype(:netinfo) - @filetype.format = "fstab" - @path = "mounts" - @fields = [:device, :path, :fstype, :options, :dump, :pass] - @fielddefaults = [ nil ] * @fields.size - - # How to map the dumped table to what we want - @fieldnames = { - "name" => :device, - "dir" => :path, - "dump_freq" => :dump, - "passno" => :pass, - "vfstype" => :fstype, - "opts" => :options - } - else - @path = "/etc/fstab" - @fields = [:device, :path, :fstype, :options, :dump, :pass] - @fielddefaults = [ nil ] * 4 + [ "0", "2" ] - end - - # Allow Darwin to override the default filetype - unless defined? @filetype - @filetype = Puppet::FileType.filetype(:flat) - end - end - - init - - # XXX This needs to change to being a netinfo provider - unless @platform == "Darwin" - confine :exists => @path - end - - def self.clear - init - super - end - - # Parse a mount tab. - # - # This method also stores existing comments, and it stores all - # mounts 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) - # provide a single exception for darwin & netinfo - if @filetype == Puppet::FileType.filetype(:netinfo) - return parseninfo(text) - end - count = 0 - - instances = [] - text.chomp.split("\n").each { |line| - hash = {} - case line - when /^#/, /^\s*$/: - # add comments and blank lines to the list as they are - instances << line - comment(line) - else - values = line.split(/\s+/) - if @fields.length < values.length - raise Puppet::Error, "Could not parse line %s" % line - end +fstab = nil +case Facter.value(:operatingsystem) +when "Solaris": fstab = "/etc/vfstab" +else + fstab = "/etc/fstab" +end - values = @fielddefaults.zip(values).collect { |d, v| v || d } - 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 +Puppet::Type.type(:mount).provide(:parsed, + :parent => Puppet::Provider::ParsedFile, + :default_target => fstab, + :filetype => :flat +) do - instances << hash - count += 1 - end - } + commands :mountcmd => "mount", :umount => "umount", :df => "df" - return instances + @platform = Facter["operatingsystem"].value + case @platform + when "Solaris": + @fields = [:device, :blockdevice, :name, :fstype, :pass, :atboot, + :options] + else + @fields = [:device, :name, :fstype, :options, :dump, :pass] + @fielddefaults = [ nil ] * 4 + [ "0", "2" ] end - # Parse a netinfo table. - def self.parseninfo(text) - array = @fileobj.to_array(text) + text_line :comment, :match => /^\s*#/ + text_line :blank, :match => /^\s*$/ - instances = [] - array.each do |record| - hash = {} - @fieldnames.each do |name, field| - if value = record[name] - if field == :options - hash[field] = value.join(",") - else - hash[field] = value[0] - end - else - raise ArgumentError, "Field %s was not provided" % [name] - end - end - - instances << hash - end - - return instances - end - - # Convert the current object into an fstab-style string. - def self.to_record(hash) - self.fields.collect do |field| - if value = hash[field] - value - else - raise Puppet::Error, - "Could not retrieve value for %s" % field - end - end.join("\t") - end + record_line self.name, :fields => @fields, :separator => /\s+/, :joiner => "\t" # This only works when the mount point is synced to the fstab. def mount - mountcmd @model[:path] + mountcmd @model[:name] end # This only works when the mount point is synced to the fstab. def unmount - output = %x{#{command(:umount)} #{@model[:path]}} - - unless $? == 0 - raise Puppet::Error, "Could not unmount %s" % @model[:path] - end + umount @model[:name] end # Is the mount currently mounted? @@ -156,13 +49,12 @@ Puppet::Type.type(:mount).provide :parsed, :parent => Puppet::Provider::ParsedFi when "Solaris": df = "#{command(:df)} -k" end %x{#{df}}.split("\n").find do |line| - p line fs = line.split(/\s+/)[-1] if platform == "Darwin" - fs == "/private/var/automount" + @model[:path] or - fs == @model[:path] + fs == "/private/var/automount" + @model[:name] or + fs == @model[:name] else - fs == @model[:path] + fs == @model[:name] end end end diff --git a/lib/puppet/provider/parsedfile.rb b/lib/puppet/provider/parsedfile.rb index f9e98d96a..5539679d0 100755 --- a/lib/puppet/provider/parsedfile.rb +++ b/lib/puppet/provider/parsedfile.rb @@ -13,17 +13,34 @@ class Puppet::Provider::ParsedFile < Puppet::Provider extend Puppet::Util::FileParsing class << self - attr_reader :filetype attr_accessor :default_target end attr_accessor :state_hash + def self.clean(hash) + newhash = hash.dup + [:record_type, :on_disk].each do |p| + if newhash.include?(p) + newhash.delete(p) + end + end + + return newhash + end + def self.clear @target_objects.clear @records.clear end + def self.filetype + unless defined? @filetype + @filetype = Puppet::FileType.filetype(:flat) + end + return @filetype + end + def self.filetype=(type) if type.is_a?(Class) @filetype = type @@ -34,25 +51,37 @@ class Puppet::Provider::ParsedFile < Puppet::Provider end end - # Flush all of the targets for which there are modified records. + # Flush all of the targets for which there are modified records. The only + # reason we pass a record here is so that we can add it to the stack if + # necessary -- it's passed from the instance calling 'flush'. def self.flush(record) - return unless defined?(@modified) and ! @modified.empty? - # Make sure this record is on the list to be flushed. unless record[:on_disk] record[:on_disk] = true @records << record + + # If we've just added the record, then make sure our + # target will get flushed. + modified(record[:target] || default_target) end + return unless defined?(@modified) and ! @modified.empty? + + flushed = [] @modified.sort { |a,b| a.to_s <=> b.to_s }.uniq.each do |target| Puppet.debug "Flushing %s provider target %s" % [@model.name, target] flush_target(target) + flushed << target end + + @modified.reject! { |t| flushed.include?(t) } end # Flush all of the records relating to a specific target. def self.flush_target(target) - target_object(target).write(to_file(target_records(target))) + target_object(target).write(to_file(target_records(target).reject { |r| + r[:ensure] == :absent + })) end # Return the header placed at the top of each generated file, warning @@ -66,20 +95,43 @@ class Puppet::Provider::ParsedFile < Puppet::Provider # Add another type var. def self.initvars @records = [] + @target_objects = {} # Default to flat files @filetype = Puppet::FileType.filetype(:flat) super end + # Return a list of all of the records we can find. + def self.list + prefetch() + @records.find_all { |r| r[:record_type] == self.name }.collect { |r| + clean(r) + } + end + + def self.list_by_name + list.collect { |r| r[:name] } + end + # Create attribute methods for each of the model's non-metaparam attributes. def self.model=(model) [model.validstates, model.parameters].flatten.each do |attr| - define_method(attr) do @state_hash[attr] end + attr = symbolize(attr) + define_method(attr) do + # If it's not a valid field for this record type (which can happen + # when different platforms support different fields), then just + # return the should value, so the model shuts up. + if @state_hash[attr] or self.class.valid_attr?(self.class.name, attr) + @state_hash[attr] || :absent + else + @model.should(attr) + end + end define_method(attr.to_s + "=") do |val| # Mark that this target was modified. - modeltarget = @model[:target] + modeltarget = @model[:target] || self.class.default_target # If they're the same, then just mark that one as modified if @state_hash[:target] and @state_hash[:target] == modeltarget @@ -94,7 +146,7 @@ class Puppet::Provider::ParsedFile < Puppet::Provider end @state_hash[:target] = modeltarget end - @state_hash[attr] = val.to_s + @state_hash[attr] = val end end @model = model @@ -104,7 +156,7 @@ class Puppet::Provider::ParsedFile < Puppet::Provider # used within the attr= methods. def self.modified(target) @modified ||= [] - @modified << target + @modified << target unless @modified.include?(target) end # Retrieve all of the data from disk. There are three ways to know @@ -127,9 +179,10 @@ class Puppet::Provider::ParsedFile < Puppet::Provider @records += retrieve(target).each do |r| r[:on_disk] = true r[:target] = target + r[:ensure] = :present end - # Retrieve the current state of this target. + # Set current state on any existing resource instances. target_records(target).find_all { |i| i.is_a?(Hash) }.each do |record| # Find any model instances whose names match our instances. if instance = self.model[record[:name]] @@ -140,11 +193,14 @@ class Puppet::Provider::ParsedFile < Puppet::Provider instance.provider.state_hash = record end end - # Any unmatched records are assumed to be unmanaged, so - # we just leave them alone. end end + # Is there an existing record with this name? + def self.record?(name) + @records.find { |r| r[:name] == name } + end + # Retrieve the text for the file. Returns nil in the unlikely # event that it doesn't exist. def self.retrieve(path) @@ -169,7 +225,6 @@ class Puppet::Provider::ParsedFile < Puppet::Provider # Initialize the object if necessary. def self.target_object(target) - @target_objects ||= {} @target_objects[target] ||= @filetype.new(target) @target_objects[target] @@ -198,55 +253,56 @@ class Puppet::Provider::ParsedFile < Puppet::Provider targets << model[:target] end - targets.uniq + targets.uniq.reject { |t| t.nil? } end - # Write our data to disk. - def flush - # Make sure we've got a target and name set. - @state_hash[:target] ||= @model[:target] - @state_hash[:name] ||= @model[:name] - - self.class.flush(@state_hash) + def create + @model.class.validstates.each do |state| + if value = @model.should(state) + @state_hash[state] = value + end + end + self.class.modified(@state_hash[:target] || self.class.default_target) end - def initialize(model) - super - - # Initialize our data hash with the record type. The record type - # in the subclass has to create a record matching its name. - @state_hash = {:record_type => self.class.name} + def destroy + # We use the method here so it marks the target as modified. + self.ensure = :absent end - # Have we been modified since the last flush? - def modified? - @state[:flush_needed] + def exists? + if @state_hash[:ensure] == :absent or @state_hash[:ensure].nil? + return false + else + return true + end end - def store(hash = nil) - hash ||= self.model.to_hash + # Write our data to disk. + def flush + # Make sure we've got a target and name set. - unless @instances - self.hash + # If the target isn't set, then this is our first modification, so + # mark it for flushing. + unless @state_hash[:target] + @state_hash[:target] = @model[:target] || self.class.default_target + self.class.modified(@state_hash[:target]) end + @state_hash[:name] ||= @model.name - if hash.empty? - @me.clear - else - hash.each do |name, value| - if @me[name] != hash[name] - @me[name] = hash[name] - end - end + self.class.flush(@state_hash) + end - @me.each do |name, value| - unless hash.has_key? name - @me.delete(name) - end - end - end + def initialize(model) + super - self.class.store(@instances) + # See if there's already a matching state_hash in the records list; + # else, use a default value. + # We provide a default for 'ensure' here, because the provider will + # override it if the thing exists, but it won't touch it if it doesn't + # exist. + @state_hash = self.class.record?(model[:name]) || + {:record_type => self.class.name, :ensure => :absent} end end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 8df86ee81..1adeb1366 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -363,9 +363,11 @@ class Type < Puppet::Element end # Convert to a transportable object - def to_trans - # Collect all of the "is" values - retrieve() + def to_trans(ret = true) + # Retrieve the object, if they tell use to. + if ret + retrieve() + end trans = TransObject.new(self.title, self.class.name) diff --git a/lib/puppet/type/mount.rb b/lib/puppet/type/mount.rb index 518f21884..a6be65ae6 100755 --- a/lib/puppet/type/mount.rb +++ b/lib/puppet/type/mount.rb @@ -12,22 +12,30 @@ module Puppet currently mounted, if it is ``mounted``, the filesystem is entered into the mount table and mounted." - newvalue(:present, :call => :after) do + newvalue(:present) do if provider.mounted? provider.unmount return :mount_unmounted else + provider.create return :mount_created end end + aliasvalue :unmounted, :present - newvalue(:absent, :event => :mount_deleted, :call => :after) do + newvalue(:absent, :event => :mount_deleted) do if provider.mounted? provider.unmount end + + provider.destroy end - newvalue(:mounted, :event => :mount_mounted, :call => :after) do + newvalue(:mounted, :event => :mount_mounted) do + # Create the mount point if it does not already exist. + if self.is == :absent or self.is.nil? + provider.create + end # We have to flush any changes to disk. @parent.flush @@ -43,13 +51,10 @@ module Puppet end def retrieve - fail "called retrieve" - Puppet.warning @is.inspect if provider.mounted? @is = :mounted else - val = super() - @is = val + @is = super() end end @@ -107,12 +112,29 @@ module Puppet support this." end - newparam(:path) do + newstate(:target) do + desc "The file in which to store the mount table. Only used by + those providers that write to disk (i.e., not NetInfo)." + + defaultto { @parent.class.defaultprovider.default_target } + end + + newparam(:name) do desc "The mount path for the mount." isnamevar end + newparam(:path) do + desc "The deprecated name for the mount point. Please use ``name`` now." + + def should=(value) + warning "'path' is deprecated for mounts. Please use 'name'." + @parent[:name] = value + super + end + end + @doc = "Manages mounted mounts, including putting mount information into the mount table. The actual behavior depends on the value of the 'ensure' parameter." diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb index c639b671b..9faa6d8ec 100644 --- a/lib/puppet/type/state.rb +++ b/lib/puppet/type/state.rb @@ -272,7 +272,7 @@ class State < Puppet::Parameter # Look for a matching value @should.each { |val| - if @is == val + if @is == val or @is == val.to_s return true end } @@ -457,11 +457,27 @@ class State < Puppet::Parameter def self.defaultvalues newvalue(:present) do - @parent.create + if @parent.provider and @parent.provider.respond_to?(:create) + @parent.provider.create + else + @parent.create + end end newvalue(:absent) do - @parent.destroy + if @parent.provider and @parent.provider.respond_to?(:destroy) + @parent.provider.destroy + else + @parent.destroy + end + end + + defaultto do + if @parent.managed? + :present + else + nil + end end # This doc will probably get overridden @@ -498,7 +514,15 @@ class State < Puppet::Parameter # to get checked, which means that those other states do not have # @is values set. This seems to be the source of quite a few bugs, # although they're mostly logging bugs, not functional ones. - if @parent.exists? + if prov = @parent.provider and prov.respond_to?(:exists?) + result = prov.exists? + elsif @parent.respond_to?(:exists?) + result = @parent.exists? + else + raise Puppet::DevError, "No ability to determine if %s exists" % + @parent.class.name + end + if result @is = :present else @is = :absent diff --git a/lib/puppet/util/fileparsing.rb b/lib/puppet/util/fileparsing.rb index 705192b11..91a70c214 100644 --- a/lib/puppet/util/fileparsing.rb +++ b/lib/puppet/util/fileparsing.rb @@ -34,6 +34,15 @@ module Puppet::Util::FileParsing @record_order.clear end + def fields(type) + type = symbolize(type) + if @record_types.include?(type) + @record_types[type][:fields].dup + else + nil + end + end + # Try to match a specific text line. def handle_text_line(line, hash) if line =~ hash[:match] @@ -46,10 +55,22 @@ module Puppet::Util::FileParsing # Try to match a record. def handle_record_line(line, hash) if hash[:match] + raise "No provision yet for matching whole lines" else ret = {} - hash[:fields].zip(line.split(hash[:separator])) do |param, value| - ret[param] = value + sep = hash[:separator] + + # String "helpfully" replaces ' ' with /\s+/ in splitting, so we + # have to work around it. + if sep == " " + sep = / / + end + hash[:fields].zip(line.split(sep)) do |param, value| + if value and value != "" + ret[param] = value + else + ret[param] = :absent + end end ret[:record_type] = hash[:name] return ret @@ -117,6 +138,8 @@ module Puppet::Util::FileParsing r end + options[:absent] ||= "" + options[:separator] ||= /\s+/ # Unless they specified a string-based joiner, just use a single @@ -164,7 +187,14 @@ module Puppet::Util::FileParsing else joinchar = type[:joiner] || type[:separator] - return type[:fields].collect { |field| details[field].to_s }.join(joinchar) + return type[:fields].collect { |field| + # If the field is marked absent, use the appropriate replacement + if details[field] == :absent + type[:absent] + else + details[field].to_s + end + }.join(joinchar) end end @@ -177,6 +207,19 @@ module Puppet::Util::FileParsing end end + def valid_attr?(type, attr) + type = symbolize(type) + if @record_types[type] and @record_types[type][:fields].include?(symbolize(attr)) + return true + else + if symbolize(attr) == :ensure + return true + else + false + end + end + end + private # Define a new type of record. def new_line_type(name, type, options) diff --git a/test/lib/puppettest/fileparsing.rb b/test/lib/puppettest/fileparsing.rb index 004a0c3b5..9f8124df5 100644 --- a/test/lib/puppettest/fileparsing.rb +++ b/test/lib/puppettest/fileparsing.rb @@ -1,28 +1,26 @@ module PuppetTest::FileParsing # Run an isomorphism test on our parsing process. def fakedataparse(file) - @provider.path = file - instances = nil + oldtarget = @provider.default_target + cleanup do + @provider.default_target = oldtarget + end + @provider.default_target = file + assert_nothing_raised { - instances = @provider.retrieve + @provider.prefetch } - text = @provider.fileobj.read + text = @provider.to_file(@provider.target_records(file)) yield if block_given? - dest = tempfile() - @provider.path = dest - - # Now write it back out - assert_nothing_raised { - @provider.store(instances) - } - - newtext = @provider.fileobj.read - - # Don't worry about difference in whitespace - assert_equal(text.gsub(/\s+/, ' '), newtext.gsub(/\s+/, ' ')) + oldlines = File.readlines(file) + newlines = text.chomp.split "\n" + oldlines.zip(newlines).each do |old, new| + assert_equal(old.chomp.gsub(/\s+/, ''), new.gsub(/\s+/, ''), + "Lines are not equal") + end end end diff --git a/test/providers/parsedfile.rb b/test/providers/parsedfile.rb index f7e5a4d48..e4c68dd0c 100755 --- a/test/providers/parsedfile.rb +++ b/test/providers/parsedfile.rb @@ -13,13 +13,14 @@ class TestParsedFile < Test::Unit::TestCase include PuppetTest::FileParsing Puppet::Type.newtype(:parsedfiletype) do + ensurable newstate(:one) do - newvalue(:a) {} - newvalue(:b) {} + newvalue(:a) + newvalue(:b) end newstate(:two) do - newvalue(:c) {} - newvalue(:d) {} + newvalue(:c) + newvalue(:d) end newparam(:name) do @@ -32,7 +33,9 @@ class TestParsedFile < Test::Unit::TestCase # A simple block to skip the complexity of a full transaction. def apply(model) - [:one, :two].each do |st| + [:one, :two, :ensure].each do |st| + Puppet.info "Setting %s: %s => %s" % + [model[:name], st, model.should(st)] model.provider.send(st.to_s + "=", model.should(st)) end end @@ -46,7 +49,8 @@ class TestParsedFile < Test::Unit::TestCase end def mkprovider(name = :parsed) - @provider = @type.provide(name, :parent => Puppet::Provider::ParsedFile) do + @provider = @type.provide(name, :parent => Puppet::Provider::ParsedFile, + :filetype => :ram) do record_line name, :fields => %w{name one two} end end @@ -90,7 +94,7 @@ class TestParsedFile < Test::Unit::TestCase end # The provider converts to strings - assert_equal("yayness", file.name) + assert_equal(:yayness, file.name) end def test_filetype @@ -121,8 +125,8 @@ class TestParsedFile < Test::Unit::TestCase obj = prov.target_object(path) end - # The default filetype is 'flat' - assert_instance_of(Puppet::FileType.filetype(:flat), obj) + # The default filetype is 'ram' + assert_instance_of(Puppet::FileType.filetype(:ram), obj) newobj = nil assert_nothing_raised do @@ -208,6 +212,24 @@ class TestParsedFile < Test::Unit::TestCase assert_equal("b", model.provider.one) assert_equal("b", default.provider.one) assert_equal("d", default.provider.two) + + # Now list all of them and make sure we get everything back + hashes = nil + assert_nothing_raised do + hashes = prov.list + end + + names = nil + assert_nothing_raised do + names = prov.list_by_name + end + + %w{bill jill will}.each do |name| + assert(hashes.find { |r| r[:name] == name}, + "Did not return %s in list" % name) + assert(names.include?(name), + "Did not return %s in list_by_name" % name) + end end # Make sure we can correctly prefetch on a target. @@ -316,8 +338,8 @@ class TestParsedFile < Test::Unit::TestCase assert_nothing_raised { one.flush } # Make sure it changed our file - assert_equal("a", one.provider.one) - assert_equal("c", one.provider.two) + assert_equal(:a, one.provider.one) + assert_equal(:c, one.provider.two) # And make sure it's right on disk assert(prov.target_object(:yayness).read.include?("one a c"), @@ -340,17 +362,19 @@ class TestParsedFile < Test::Unit::TestCase assert_nothing_raised { apply(two) } assert_nothing_raised { two.flush } - assert_equal("b", two.provider.one) + assert_equal(:b, two.provider.one) end def test_creating_file prov = mkprovider + prov.clear - prov.filetype = :ram prov.default_target = :basic model = mkmodel "yay", :target => :basic, :one => "a", :two => "c" + assert_equal(:present, model.should(:ensure)) + apply(model) assert_nothing_raised do @@ -385,16 +409,17 @@ class TestParsedFile < Test::Unit::TestCase second = mkmodel "second", :target => :second mover = mkmodel "mover", :target => :first - # Apply them all [first, second, mover].each do |m| assert_nothing_raised("Could not apply %s" % m[:name]) do apply(m) end end - # Flush - assert_nothing_raised do - [first, second, mover].each do |m| m.flush end + # Flush. + [first, second, mover].each do |m| + assert_nothing_raised do + m.flush + end end check = proc do |target, name| @@ -428,6 +453,148 @@ class TestParsedFile < Test::Unit::TestCase assert(prov.target_object(:first) !~ /mover/, "Mover was not removed from first file") end + + # Make sure that 'ensure' correctly calls 'sync' on all states. + def test_ensure + prov = mkprovider + + prov.filetype = :ram + prov.default_target = :first + + # Make two models, one that starts on disk and one that doesn't + ondisk = mkmodel "ondisk", :target => :first + notdisk = mkmodel "notdisk", :target => :first + + prov.target_object(:first).write "ondisk a c\n" + prov.prefetch + + assert_equal(:present, notdisk.should(:ensure), + "Did not get default ensure value") + + # Try creating the object + assert_nothing_raised { notdisk.provider.create() } + + # Now make sure all of the data is copied over correctly. + notdisk.class.validstates.each do |state| + assert_equal(notdisk.should(state), notdisk.provider.state_hash[state], + "%s was not copied over during creation" % state) + end + + # Flush it to disk and make sure it got copied down + assert_nothing_raised do + notdisk.flush + end + + assert(prov.target_object(:first).read =~ /^notdisk/, + "Did not write out object to disk") + assert(prov.target_object(:first).read =~ /^ondisk/, + "Lost object on disk") + + # Make sure our on-disk model behaves appropriately. + assert_equal(:present, ondisk.provider.ensure) + + # Now destroy the object + assert_nothing_raised { notdisk.provider.destroy() } + + assert_nothing_raised { notdisk.flush } + + # And make sure it's no longer present + assert(prov.target_object(:first).read !~ /^notdisk/, + "Did not remove thing from disk") + assert(prov.target_object(:first).read =~ /^ondisk/, + "Lost object on disk") + assert_equal(:present, ondisk.provider.ensure) + end + + def test_absent_fields + prov = @type.provide(:record, :parent => Puppet::Provider::ParsedFile) do + record_line :record, :fields => %w{name one two}, + :separator => "\s" + end + cleanup { @type.unprovide(:record) } + + records = prov.parse("a d") + + line = records.find { |r| r[:name] == "a" } + assert(line, "Could not find line") + + assert_equal(:absent, line[:one], "field one was not set to absent") + end + + # This test is because in x2puppet I was having problems where multiple + # retrievals somehow destroyed the 'is' values. + def test_value_retrieval + prov = mkprovider + prov.default_target = :yayness + + prov.target_object(:yayness).write "bill a c\njill b d" + + list = Puppet::Type.type(:parsedfiletype).list + + bill = list.find { |r| r[:name] == "bill" } + jill = list.find { |r| r[:name] == "jill" } + assert(bill, "Could not find bill") + assert(jill, "Could not find jill") + + prov = bill.provider + + 4.times do |i| + assert(prov.one, "Did not get a value for 'one' on try %s" % (i + 1)) + end + + # First make sure we can retrieve values multiple times from the + # provider + assert(bill.is(:one), "Bill does not have a value for 'one'") + assert(bill.is(:one), "Bill does not have a value for 'one' on second try") + assert_nothing_raised do + bill.retrieve + end + assert(bill.is(:one), "bill's value for 'one' disappeared") + end + + # Make sure that creating a new model finds existing records in memory + def test_initialize_finds_records + prov = mkprovider + prov.default_target = :yayness + + prov.target_object(:yayness).write "bill a c\njill b d" + + prov.prefetch + + # Now make a model + bill = nil + assert_nothing_raised do + bill = Puppet::Type.type(:parsedfiletype).create :name => "bill" + end + + assert_equal("a", bill.provider.one, + "Record was not found in memory") + end + + # Make sure invalid fields always show up as insync + def test_invalid_fields + prov = @type.provide(:test, :parent => Puppet::Provider::ParsedFile, + :filetype => :ram, :default_target => :yayness) do + record_line :test, :fields => %w{name two} + end + cleanup do @type.unprovide(:test) end + + bill = nil + assert_nothing_raised do + bill = Puppet::Type.type(:parsedfiletype).create :name => "bill", + :one => "a", :two => "c" + end + + assert_apply(bill) + + prov.prefetch + assert_nothing_raised do + bill.retrieve + end + + assert(bill.insync?, + "An invalid field marked the record out of sync") + end end # $Id$ diff --git a/test/providers/parsedmount.rb b/test/providers/parsedmount.rb index 0d146df34..868dde617 100755 --- a/test/providers/parsedmount.rb +++ b/test/providers/parsedmount.rb @@ -13,7 +13,8 @@ class TestParsedMounts < Test::Unit::TestCase def setup super - @provider = Puppet.type(:mount).provider(:parsed) + @mount = Puppet.type(:mount) + @provider = @mount.provider(:parsed) @oldfiletype = @provider.filetype end @@ -21,6 +22,7 @@ class TestParsedMounts < Test::Unit::TestCase def teardown Puppet::FileType.filetype(:ram).clear @provider.filetype = @oldfiletype + @provider.clear super end @@ -33,13 +35,13 @@ class TestParsedMounts < Test::Unit::TestCase @pcount = 1 end args = { - :path => "/fspuppet%s" % @pcount, + :name => "/fspuppet%s" % @pcount, :device => "/dev/dsk%s" % @pcount, } - @provider.fields.each do |field| + @provider.fields(:parsed).each do |field| unless args.include? field - args[field] = "fake%s" % @pcount + args[field] = "fake%s%s" % [field, @pcount] end end @@ -50,15 +52,13 @@ class TestParsedMounts < Test::Unit::TestCase hash = mkmountargs() #hash[:provider] = @provider.name - fakemodel = fakemodel(:mount, hash[:path]) + fakemodel = fakemodel(:mount, hash[:name]) mount = @provider.new(fakemodel) - #mount = Puppet.type(:mount).create(hash) - - hash.each do |name, val| - fakemodel[name] = val - end assert(mount, "Could not create provider mount") + hash[:record_type] = :parsed + hash[:ensure] = :present + mount.state_hash = hash return mount end @@ -69,11 +69,22 @@ class TestParsedMounts < Test::Unit::TestCase @provider.filetype = Puppet::FileType.filetype(:ram) end + def test_default_target + should = case Facter.value(:operatingsystem) + when "Solaris": "/etc/vfstab" + else + "/etc/fstab" + end + assert_equal(should, @provider.default_target) + end + def test_simplemount mkfaketype - assert_nothing_raised { - assert_equal([], @provider.retrieve) - } + target = @provider.default_target + + # Make sure we start with an empty file + assert_equal("", @provider.target_object(target).read, + "Got a non-empty starting file") # Now create a provider mount = nil @@ -82,50 +93,49 @@ class TestParsedMounts < Test::Unit::TestCase } # Make sure we're still empty - assert_nothing_raised { - assert_equal([], @provider.retrieve) - } + assert_equal("", @provider.target_object(target).read, + "Got a non-empty starting file") - hash = mount.model.to_hash - - # Try storing it + # Try flushing it to disk assert_nothing_raised do - mount.store(hash) + mount.flush end - # Make sure we get the mount back - assert_nothing_raised { - assert_equal([hash], @provider.retrieve) - } - - # Now remove the whole object - assert_nothing_raised { - mount.store({}) - assert_equal([], @provider.retrieve) - } + # Make sure it's now in the file. The file format is validated in + # the isomorphic methods. + assert(@provider.target_object(target).read.include?("\t%s\t" % + mount.state_hash[:name]), "Mount was not written to disk") end unless Facter["operatingsystem"].value == "Darwin" def test_mountsparse - fakedataparse(fake_fstab) do + tab = fake_fstab + fakedataparse(tab) do # Now just make we've got some mounts we know will be there - hashes = @provider.retrieve.find_all { |i| i.is_a? Hash } + hashes = @provider.target_records(tab).find_all { |i| i.is_a? Hash } assert(hashes.length > 0, "Did not create any hashes") - root = hashes.find { |i| i[:path] == "/" } + root = hashes.find { |i| i[:name] == "/" } assert(root, "Could not retrieve root mount") end end def test_rootfs fs = nil - @provider.path = fake_fstab() - fakemodel = fakemodel(:mount, "/") - mount = @provider.new(fakemodel) - mount.model[:path] = "/" - assert(mount.hash, "Could not retrieve root fs") + type = @mount.create :name => "/" + + provider = type.provider + + assert(FileTest.exists?(@provider.default_target), + "FSTab %s does not exist" % @provider.default_target) + assert_nothing_raised do + @provider.prefetch + end + + assert_equal(:present, provider.state_hash[:ensure], + "Could not find root fs") assert_nothing_raised { - assert(mount.mounted?, "Root is considered not mounted") + assert(provider.mounted?, "Root is considered not mounted") } end end @@ -133,44 +143,28 @@ class TestParsedMounts < Test::Unit::TestCase if Puppet::SUIDManager.uid == 0 def test_mountfs fs = nil - case Facter["hostname"].value + case Facter.value(:hostname) when "culain": fs = "/ubuntu" when "atalanta": fs = "/mnt" - when "figurehead": fs = "/cg4/net/depts" else $stderr.puts "No mount for mount testing; skipping" return end - oldtext = @provider.fileobj.read + oldtext = @provider.target_object(@provider.default_target).read ftype = @provider.filetype - # Make sure the original gets reinstalled. - if ftype == Puppet::FileType.filetype(:netinfo) - cleanup do - IO.popen("niload -r /mounts .", "w") do |file| - file.puts oldtext - end - end - else - cleanup do - @provider.fileobj.write(oldtext) - end - end - - fakemodel = fakemodel(:mount, "/") - obj = @provider.new(fakemodel) - obj.model[:path] = fs + mount = @mount.create :name => fs + obj = mount.provider current = nil - assert_nothing_raised { current = obj.mounted? } if current - # Make sure the original gets reinstalled. + # Make sure the original gets remounted. cleanup do unless obj.mounted? obj.mount @@ -188,10 +182,13 @@ class TestParsedMounts < Test::Unit::TestCase obj.unmount } assert(! obj.mounted?, "FS still mounted") + # Check the actual output of df + assert(! obj.df(nil).include?(fs), "%s is still listed in df" % fs) assert_nothing_raised { obj.mount } assert(obj.mounted?, "FS not mounted") + assert(obj.df(nil).include?(fs), "%s is not listed in df" % fs) end end @@ -206,8 +203,7 @@ class TestParsedMounts < Test::Unit::TestCase # Catchall for other fstabs name = "linux.fstab" end - oldpath = @provider.path - cleanup do @provider.path = oldpath end + oldpath = @provider.default_target return fakefile(File::join("data/types/mount", name)) end end diff --git a/test/types/mount.rb b/test/types/mount.rb index 880de4434..5a5a5da85 100755 --- a/test/types/mount.rb +++ b/test/types/mount.rb @@ -14,11 +14,20 @@ class TestMounts < Test::Unit::TestCase attr_accessor :mounted + def self.default_target + :yayness + end + def create @ensure = :present + @model.class.validstates.each do |state| + if value = @model.should(state) + self.send(state.to_s + "=", value) + end + end end - def delete + def destroy @ensure = :absent @mounted = false end @@ -50,12 +59,14 @@ class TestMounts < Test::Unit::TestCase def setup super - @realprovider = Puppet::Type.type(:mount).defaultprovider - Puppet::Type.type(:mount).defaultprovider = FakeMountProvider + @mount = Puppet::Type.type(:mount) + @realprovider = @mount.defaultprovider + @mount.defaultprovider = FakeMountProvider end def teardown Puppet.type(:mount).clear + @realprovider.clear Puppet::Type.type(:mount).defaultprovider = nil super end @@ -73,7 +84,10 @@ class TestMounts < Test::Unit::TestCase :device => "/dev/dsk%s" % @pcount, } - @realprovider.fields.each do |field| + [@mount.validstates, @mount.parameters].flatten.each do |field| + next if field == :provider + next if field == :target + next if field == :ensure unless args.include? field args[field] = "fake%s" % @pcount end @@ -87,24 +101,13 @@ class TestMounts < Test::Unit::TestCase end def test_simplemount - mount = nil - oldprv = Puppet.type(:mount).defaultprovider - Puppet.type(:mount).defaultprovider = nil - assert_nothing_raised { - Puppet.type(:mount).defaultprovider.retrieve - - count = 0 - Puppet.type(:mount).each do |h| - count += 1 - end - - assert_equal(0, count, "Found mounts in empty file somehow") - } - Puppet.type(:mount).defaultprovider = oldprv - mount = mkmount assert_apply(mount) + mount.send(:states).each do |state| + assert_equal(state.should, mount.provider.send(state.name), + "%s was not set to %s" % [state.name, state.should]) + end assert_events([], mount) assert_nothing_raised { mount.retrieve } @@ -155,11 +158,60 @@ class TestMounts < Test::Unit::TestCase def test_list list = nil + assert(@mount.respond_to?(:list), + "No list method defined for mount") + assert_nothing_raised do list = Puppet::Type.type(:mount).list end assert(list.length > 0, "Did not return any mounts") + + root = list.find { |o| o[:name] == "/" } + assert(root, "Could not find root root filesystem in list results") + + assert(root.is(:device), "Device was not set") + assert(root.state(:device).value, "Device was not returned by value method") + + assert_nothing_raised do + root.retrieve + end + + assert(root.is(:device), "Device was not set") + assert(root.state(:device).value, "Device was not returned by value method") + end + + # Make sure we actually remove the object from the file and such. + def test_removal + # Reset the provider so that we're using the real thing + @mount.defaultprovider = nil + + provider = @mount.defaultprovider + assert(provider, "Could not retrieve default provider") + + file = provider.default_target + assert(FileTest.exists?(file), + "FSTab %s does not exist" % file) + + # Now switch to ram, so we're just doing this there, not really on disk. + provider.filetype = :ram + #provider.target_object(file).write text + + mount = mkmount + + mount[:ensure] = :present + + assert_events([:mount_created], mount) + assert_events([], mount) + + mount[:ensure] = :absent + assert_events([:mount_deleted], mount) + assert_events([], mount) + + # Now try listing and making sure the object is actually gone. + list = mount.provider.class.list + assert(! list.find { |r| r[:name] == mount[:name] }, + "Mount was not actually removed") end end diff --git a/test/util/fileparsing.rb b/test/util/fileparsing.rb index f5cf0d255..cebc3051e 100755 --- a/test/util/fileparsing.rb +++ b/test/util/fileparsing.rb @@ -273,6 +273,51 @@ class TestUtilFileParsing < Test::Unit::TestCase end end + # Make sure fields that are marked absent get replaced with the appropriate + # string. + def test_absent_fields + parser = FParser.new + + options = nil + assert_nothing_raised do + options = parser.record_line :record, :fields => %w{one two three} + end + assert_equal("", options[:absent], "Did not set a default absent string") + + result = nil + assert_nothing_raised do + result = parser.to_line(:record_type => :record, + :one => "a", :two => :absent, :three => "b") + end + + assert_equal("a b", result, "Absent was not correctly replaced") + + # Now try using a different replacement character + options[:absent] = "*" # Because cron is a pain in my ass + assert_nothing_raised do + result = parser.to_line(:record_type => :record, + :one => "a", :two => :absent, :three => "b") + end + + assert_equal("a * b", result, "Absent was not correctly replaced") + + # Make sure we deal correctly with the string 'absent' + assert_nothing_raised do + result = parser.to_line(:record_type => :record, + :one => "a", :two => "b", :three => 'absent') + end + + assert_equal("a b absent", result, "Replaced string 'absent'") + + # And, of course, make sure we can swap things around. + assert_nothing_raised do + result = parser.to_line(:record_type => :record, + :one => "a", :two => "b", :three => :absent) + end + + assert_equal("a b *", result, "Absent was not correctly replaced") + end + # Make sure we can specify a different join character than split character def test_split_join_record_line parser = FParser.new @@ -331,6 +376,21 @@ billy three four" end end + def test_valid_attrs + parser = FParser.new + + parser.record_line :record, :fields => %w{one two three} + + assert(parser.valid_attr?(:record, :one), + "one was considered invalid") + + assert(parser.valid_attr?(:record, :ensure), + "ensure was considered invalid") + + assert(! parser.valid_attr?(:record, :four), + "four was considered valid") + end + # Make sure we correctly handle optional fields. We'll skip this # functionality until we really know we need it. def disabled_test_optional_fields |