#!/usr/bin/env ruby

$:.unshift("../../lib") if __FILE__ =~ /\.rb$/

require 'puppettest'

class TestMasterClient < Test::Unit::TestCase
    include PuppetTest::ServerTest
    
    class FakeTrans
        def initialize
            @counters = Hash.new { |h,k| h[k] = 0 }
        end
        [:evaluate, :report, :cleanup, :addtimes, :tags, :ignoreschedules].each do |m|
            define_method(m.to_s + "=") do |*args|
                @counters[m] += 1
            end
            define_method(m) do |*args|
                @counters[m] += 1
            end
            define_method(m.to_s + "?") do
                @counters[m]
            end
        end
    end
    class FakeComponent
        attr_accessor :trans
        def evaluate
            @trans = FakeTrans.new
            @trans
        end
        
        def finalize
            @finalized = true
        end
        
        def finalized?
            @finalized
        end
    end

    def setup
        super
        @master = Puppet::Network::Client.master
    end

    def mkmaster(file = nil)
        master = nil

        file ||= mktestmanifest()
        # create our master
        assert_nothing_raised() {
            # this is the default server setup
            master = Puppet::Network::Handler.master.new(
                :Manifest => file,
                :UseNodes => false,
                :Local => true
            )
        }
        return master
    end

    def mkclient(master = nil)
        master ||= mkmaster()
        client = nil
        assert_nothing_raised() {
            client = Puppet::Network::Client.master.new(
                :Master => master
            )
        }

        return client
    end
    
    def mk_fake_client
        server = Puppet::Network::Handler.master.new :Code => ""
        master = Puppet::Network::Client.master.new :Server => server, :Local => true

        # Now create some objects
        objects = FakeComponent.new

        master.send(:instance_variable_set, "@objects", objects)

        class << master
            def report(r)
                @reported ||= 0
                @reported += 1
            end
            def reported
                @reported ||= 0
                @reported
            end
        end
        return master, objects
    end
    
    def test_apply
        master, objects = mk_fake_client
        
        check = Proc.new do |hash|
            assert(objects.trans, "transaction was not created")
            trans = objects.trans
            hash[:yes].each do |m|
                assert_equal(1, trans.send(m.to_s + "?"), "did not call #{m} enough times")
            end
            hash[:no].each do |m|
                assert_equal(0, trans.send(m.to_s + "?"), "called #{m} too many times")
            end
        end
        
        # First try it with no arguments
        assert_nothing_raised do
            master.apply
        end
        check.call :yes => %w{evaluate cleanup addtimes}, :no => %w{report tags ignoreschedules}
        assert_equal(0, master.reported, "master sent report with reports disabled")
        
        
        # Now enable reporting and make sure the report method gets called
        Puppet[:report] = true
        assert_nothing_raised do
            master.apply
        end
        check.call :yes => %w{evaluate cleanup addtimes}, :no => %w{tags ignoreschedules}
        assert_equal(1, master.reported, "master did not send report")
        
        # Now try it with tags enabled
        assert_nothing_raised do
            master.apply("tags")
        end
        check.call :yes => %w{evaluate cleanup tags addtimes}, :no => %w{ignoreschedules}
        assert_equal(2, master.reported, "master did not send report")
        
        # and ignoreschedules
        assert_nothing_raised do
            master.apply("tags", true)
        end
        check.call :yes => %w{evaluate cleanup tags ignoreschedules addtimes}, :no => %w{}
        assert_equal(3, master.reported, "master did not send report")
    end
    
    def test_getconfig
        client = mkclient
        
        $methodsrun = []
        cleanup { $methodsrun = nil }
        client.meta_def(:getplugins) do
            $methodsrun << :getplugins
        end
        client.meta_def(:get_actual_config) do
            $methodsrun << :get_actual_config
            result = Puppet::TransBucket.new()
            result.type = "testing"
            result.name = "yayness"
            result
        end
        
        assert_nothing_raised do
            client.getconfig
        end
        [:get_actual_config].each do |method|
            assert($methodsrun.include?(method), "method %s was not run" % method)
        end
        assert(! $methodsrun.include?(:getplugins), "plugins were synced even tho disabled")

        # Now set pluginsync
        Puppet[:pluginsync] = true
        $methodsrun.clear
        
        assert_nothing_raised do
            client.getconfig
        end
        [:getplugins, :get_actual_config].each do |method|
            assert($methodsrun.include?(method), "method %s was not run" % method)
        end
        
        objects = client.objects
        assert(objects.finalized?, "objects were not finalized")
    end

    def test_disable
        FileUtils.mkdir_p(Puppet[:statedir])
        manifest = mktestmanifest

        master = mkmaster(manifest)

        client = mkclient(master)

        assert(! FileTest.exists?(@createdfile))

        assert_nothing_raised {
            client.disable
        }

        assert_nothing_raised {
            client.run
        }

        assert(! FileTest.exists?(@createdfile), "Disabled client ran")

        assert_nothing_raised {
            client.enable
        }

        assert_nothing_raised {
            client.run
        }

        assert(FileTest.exists?(@createdfile), "Enabled client did not run")
    end

    # Make sure we're getting the client version in our list of facts
    def test_clientversionfact
        facts = nil
        assert_nothing_raised {
            facts = Puppet::Network::Client.master.facts
        }

        assert_equal(Puppet.version.to_s, facts["clientversion"])
        
    end

    # Make sure non-string facts don't make things go kablooie
    def test_nonstring_facts
        FileUtils.mkdir_p(Puppet[:statedir])
        # Add a nonstring fact
        Facter.add("nonstring") do
            setcode { 1 }
        end

        assert_equal(1, Facter.nonstring, "Fact was a string from facter")

        client = mkclient()

        assert(! FileTest.exists?(@createdfile))

        assert_nothing_raised {
            client.run
        }
    end
    
    # This method is supposed
    def test_download
        source = tempfile()
        dest = tempfile()
        sfile = File.join(source, "file")
        dfile = File.join(dest, "file")
        Dir.mkdir(source)
        File.open(sfile, "w") {|f| f.puts "yay"}
        
        files = []
        assert_nothing_raised do
            files = Puppet::Network::Client.master.download(:dest => dest, :source => source, :name => "testing")
        end
        
        assert(FileTest.directory?(dest), "dest dir was not created")
        assert(FileTest.file?(dfile), "dest file was not created")
        assert_equal(File.read(sfile), File.read(dfile), "Dest file had incorrect contents")
        assert_equal([dest, dfile].sort, files.sort, "Changed files were not returned correctly")
    end

    def test_getplugins
        Puppet[:pluginsource] = tempfile()
        Dir.mkdir(Puppet[:pluginsource])

        myplugin = File.join(Puppet[:pluginsource], "myplugin.rb")
        File.open(myplugin, "w") do |f|
            f.puts %{Puppet::Type.newtype(:myplugin) do
    newparam(:argument) do
        isnamevar
    end
end
}
        end

        assert_nothing_raised {
            Puppet::Network::Client.master.getplugins
        }

        destfile = File.join(Puppet[:plugindest], "myplugin.rb")

        assert(File.exists?(destfile), "Did not get plugin")

        obj = Puppet::Type.type(:myplugin)

        assert(obj, "Did not define type")

        assert(obj.validattr?(:argument),
            "Did not get namevar")

        # Now modify the file and make sure the type is replaced
        File.open(myplugin, "w") do |f|
            f.puts %{Puppet::Type.newtype(:myplugin) do
    newparam(:yayness) do
        isnamevar
    end

    newparam(:rahness) do
    end
end
}
        end

        assert_nothing_raised {
            Puppet::Network::Client.master.getplugins
        }

        destfile = File.join(Puppet[:pluginpath], "myplugin.rb")

        obj = Puppet::Type.type(:myplugin)

        assert(obj, "Did not define type")

        assert(obj.validattr?(:yayness),
            "Did not get namevar")

        assert(obj.validattr?(:rahness),
            "Did not get other var")

        assert(! obj.validattr?(:argument),
            "Old namevar is still valid")

        # Now try it again, to make sure we don't have any objects lying around
        assert_nothing_raised {
            Puppet::Network::Client.master.getplugins
        }
    end

    def test_getfacts
        Puppet[:factsource] = tempfile()
        Dir.mkdir(Puppet[:factsource])
        hostname = Facter.value(:hostname)

        myfact = File.join(Puppet[:factsource], "myfact.rb")
        File.open(myfact, "w") do |f|
            f.puts %{Facter.add("myfact") do
            setcode { "yayness" }
end
}
        end

        assert_nothing_raised {
            Puppet::Network::Client.master.getfacts
        }

        destfile = File.join(Puppet[:factdest], "myfact.rb")

        assert(File.exists?(destfile), "Did not get fact")

        assert_equal(hostname, Facter.value(:hostname),
            "Lost value to hostname")
        assert_equal("yayness", Facter.value(:myfact),
            "Did not get correct fact value")

        # Now modify the file and make sure the type is replaced
        File.open(myfact, "w") do |f|
            f.puts %{Facter.add("myfact") do
            setcode { "funtest" }
end
}
        end

        assert_nothing_raised {
            Puppet::Network::Client.master.getfacts
        }

        assert_equal("funtest", Facter.value(:myfact),
            "Did not reload fact")
        assert_equal(hostname, Facter.value(:hostname),
            "Lost value to hostname")

        # Now run it again and make sure the fact still loads
        assert_nothing_raised {
            Puppet::Network::Client.master.getfacts
        }

        assert_equal("funtest", Facter.value(:myfact),
            "Did not reload fact")
        assert_equal(hostname, Facter.value(:hostname),
            "Lost value to hostname")
    end

    # Make sure we load all facts on startup.
    def test_loadfacts
        dirs = [tempfile(), tempfile()]
        count = 0
        names = []
        dirs.each do |dir|
            Dir.mkdir(dir)
            name = "fact%s" % count
            names << name
            file = File.join(dir, "%s.rb" % name)

            # Write out a plugin file
            File.open(file, "w") do |f|
                f.puts %{Facter.add("#{name}") do setcode { "#{name}" } end }
            end
            count += 1
        end

        Puppet[:factpath] = dirs.join(":")

        names.each do |name|
            assert_nil(Facter.value(name), "Somehow retrieved invalid fact")
        end

        assert_nothing_raised {
            Puppet::Network::Client.master.loadfacts
        }

        names.each do |name|
            assert_equal(name, Facter.value(name),
                    "Did not retrieve facts")
        end
    end

    if Process.uid == 0
    # Testing #283.  Make sure plugins et al are downloaded as the running user.
    def test_download_ownership
        dir = tstdir()
        dest = tstdir()
        file = File.join(dir, "file")
        File.open(file, "w") { |f| f.puts "funtest" }

        user = nonrootuser()
        group = nonrootgroup()
        chowner = Puppet::Type.type(:file).create :path => dir,
            :owner => user.name, :group => group.name, :recurse => true
        assert_apply(chowner)
        chowner.remove

        assert_equal(user.uid, File.stat(file).uid)
        assert_equal(group.gid, File.stat(file).gid)


        assert_nothing_raised {
            Puppet::Network::Client.master.download(:dest => dest, :source => dir,
                :name => "testing"
            ) {}
        }

        destfile = File.join(dest, "file")

        assert(FileTest.exists?(destfile), "Did not create destfile")

        assert_equal(Process.uid, File.stat(destfile).uid)
    end
    end
    
    # Test retrieving all of the facts.
    def test_facts
        facts = nil
        assert_nothing_raised do
            facts = Puppet::Network::Client.master.facts
        end
        Facter.to_hash.each do |fact, value|
            assert_equal(facts[fact.downcase], value, "%s is not equal" % fact.inspect)
        end
        
        # Make sure the puppet version got added
        assert_equal(Puppet::PUPPETVERSION, facts["clientversion"], "client version did not get added")
        
        # And make sure the ruby version is in there
        assert_equal(RUBY_VERSION, facts["rubyversion"], "ruby version did not get added")
    end
    
    # #424
    def test_caching_of_compile_time
        file = tempfile()
        manifest = tempfile()
        File.open(manifest, "w") { |f| f.puts "file { '#{file}': content => yay }" }
        
        driver = mkmaster(manifest)
        driver.local = false
        master = mkclient(driver)
        
        # We have to make everything thinks it's remote, because there's no local caching info
        master.local = false
        
        assert(! master.fresh?(master.class.facts),
            "Considered fresh with no compile at all")
        
        assert_nothing_raised { master.run }
        assert(master.fresh?(master.class.facts),
            "not considered fresh after compile")
        
        # Now make sure the config time is cached
        assert(master.compile_time, "No stored config time")
        assert_equal(master.compile_time, Puppet::Util::Storage.cache(:configuration)[:compile_time], "times did not match")
        time = master.compile_time
        master.clear
        File.unlink(file)
        Puppet::Util::Storage.store
        
        # Now make a new master
        Puppet::Util::Storage.clear
        master = mkclient(driver)
        master.run
        assert_equal(time, master.compile_time, "time was not retrieved from cache")
        assert(FileTest.exists?(file), "file was not created on second run")
    end

    def test_default_objects
        # Make sure they start out missing
        assert_nil(Puppet::Type.type(:filebucket)["puppet"],
            "default filebucket already exists")
        assert_nil(Puppet::Type.type(:schedule)["daily"],
            "default schedules already exists")

        master = mkclient()

        # Now make sure they got created
        assert(Puppet::Type.type(:filebucket)["puppet"],
            "default filebucket not found")
        assert(Puppet::Type.type(:schedule)["daily"],
            "default schedules not found")

        # clear everything, and make sure we can recreate them
        Puppet::Type.allclear
        assert_nil(Puppet::Type.type(:filebucket)["puppet"],
            "default filebucket not removed")
        assert_nil(Puppet::Type.type(:schedule)["daily"],
            "default schedules not removed")
        assert_nothing_raised  { master.mkdefault_objects }
        assert(Puppet::Type.type(:filebucket)["puppet"],
            "default filebucket not found")
        assert(Puppet::Type.type(:schedule)["daily"],
            "default schedules not found")


        # Make sure we've got schedules
        assert(Puppet::Type.type(:schedule)["hourly"], "Could not retrieve hourly schedule")
        assert(Puppet::Type.type(:filebucket)["puppet"], "Could not retrieve default bucket")
    end

    # #540 - make sure downloads aren't affected by noop
    def test_download_in_noop
        source = tempfile
        File.open(source, "w") { |f| f.puts "something" }
        dest = tempfile
        Puppet[:noop] = true
        assert_nothing_raised("Could not download in noop") do
            @master.download(:dest => dest, :source => source, :tag => "yay")
        end

        assert(FileTest.exists?(dest), "did not download in noop mode")

        assert(Puppet[:noop], "noop got disabled in run")
    end

    # #491 - make sure a missing config doesn't kill us
    def test_missing_localconfig
        master = mkclient
        master.local = false
        driver = master.send(:instance_variable_get, "@driver")
        driver.local = false
        # Retrieve the configuration
        master.getconfig

        # Now the config is up to date, so get rid of the @objects var and
        # the cached config
        master.clear
        File.unlink(master.cachefile)

        assert_nothing_raised("Missing cache file threw error") do
            master.getconfig
        end

        assert(! @logs.detect { |l| l.message =~ /Could not load/},
            "Tried to load cache when it is non-existent")
    end

    # #519 - cache the facts so that we notice if they change.
    def test_factchanges_cause_recompile
        $value = "one"
        Facter.add(:testfact) do
            setcode { $value }
        end
        assert_equal("one", Facter.value(:testfact), "fact was not set correctly")
        master = mkclient
        master.local = false
        driver = master.send(:instance_variable_get, "@driver")
        driver.local = false

        assert_nothing_raised("Could not compile config") do
            master.getconfig
        end

        $value = "two"
        Facter.clear
        Facter.loadfacts
        Facter.add(:testfact) do
            setcode { $value }
        end
        facts = master.class.facts
        assert_equal("two", Facter.value(:testfact), "fact did not change")

        assert(master.send(:facts_changed?, facts),
            "master does not think facts changed")
        assert(! master.fresh?(facts),
            "master is considered fresh after facts changed")

        assert_nothing_raised("Could not recompile when facts changed") do
            master.getconfig
        end

    end

    def test_locking
        master = mkclient

        class << master
            def getconfig
                raise ArgumentError, "Just testing"
            end
        end

        assert_raise(ArgumentError, "did not fail") do
            master.run
        end

        assert(! master.send(:lockfile).locked?,
            "Master is still locked after failure")
    end
end

# $Id: master.rb 2326 2007-03-19 19:39:26Z luke $


syntax highlighted by Code2HTML, v. 0.9.1