diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index b8690d7d5..704039abc 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -1,137 +1,118 @@ # Manage indirections to termini. They are organized in terms of indirections - # - e.g., configuration, node, file, certificate -- and each indirection has one # or more terminus types defined. The indirection is configured via the # +indirects+ method, which will be called by the class extending itself # with this module. module Puppet::Indirector # LAK:FIXME We need to figure out how to handle documentation for the # different indirection types. # A simple class that can function as the base class for indirected types. class Terminus require 'puppet/util/docs' extend Puppet::Util::Docs + + class << self + attr_accessor :name, :indirection + end + def name + self.class.name + end + def indirection + self.class.indirection + end end + require 'puppet/indirector/indirection' + # This handles creating the terminus classes. require 'puppet/util/classgen' extend Puppet::Util::ClassGen # This manages reading in all of our files for us and then retrieving # loaded instances. We still have to define the 'newX' method, but this # does all of the rest -- loading, storing, and retrieving by name. require 'puppet/util/instance_loader' extend Puppet::Util::InstanceLoader # Register a given indirection type. The classes including this module # handle creating terminus instances, but the module itself handles # loading them and managing the classes. def self.register_indirection(name) # Set up autoloading of the appropriate termini. instance_load name, "puppet/indirector/%s" % name end # Define a new indirection terminus. This method is used by the individual # termini in their separate files. Again, the autoloader takes care of # actually loading these files. # Note that the termini are being registered on the Indirector module, not # on the classes including the module. This allows a given indirection to # be used in multiple classes. def self.register_terminus(indirection, terminus, options = {}, &block) - genclass(terminus, + klass = genclass(terminus, :prefix => indirection.to_s.capitalize, :hash => instance_hash(indirection), :attributes => options, :block => block, :parent => options[:parent] || Terminus ) + klass.indirection = indirection + klass.name = terminus end # Retrieve a terminus class by indirection and name. def self.terminus(indirection, terminus) loaded_instance(indirection, terminus) end # Declare that the including class indirects its methods to # this terminus. The terminus name must be the name of a Puppet # default, not the value -- if it's the value, then it gets # evaluated at parse time, which is before the user has had a chance # to override it. # Options are: # +:to+: What parameter to use as the name of the indirection terminus. def indirects(indirection, options = {}) if defined?(@indirection) - raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection[:name], indirection] + raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection.name, indirection] end - options[:name] = indirection - @indirection = options + @indirection = Indirection.new(indirection, options) - # Validate the parameter. This requires that indirecting - # classes require 'puppet/defaults', because of ordering issues, - # but it makes problems much easier to debug. - if param_name = options[:to] - begin - name = Puppet[param_name] - rescue - raise ArgumentError, "Configuration parameter '%s' for indirection '%s' does not exist'" % [param_name, indirection] - end - end # Set up autoloading of the appropriate termini. Puppet::Indirector.register_indirection indirection + + return @indirection end # Define methods for each of the HTTP methods. These just point to the # termini, with consistent error-handling. Each method is called with # the first argument being the indirection type and the rest of the # arguments passed directly on to the indirection terminus. There is # currently no attempt to standardize around what the rest of the arguments # should allow or include or whatever. # There is also no attempt to pre-validate that a given indirection supports # the method in question. We should probably require that indirections # declare supported methods, and then verify that termini implement all of # those methods. [:get, :post, :put, :delete].each do |method_name| define_method(method_name) do |*args| redirect(method_name, *args) end end private - - # Create a new terminus instance. - def make_terminus(name) - # Load our terminus class. - unless klass = Puppet::Indirector.terminus(@indirection[:name], name) - raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, indirection] - end - return klass.new - end - # Redirect a given HTTP method. def redirect(method_name, *args) begin - terminus.send(method_name, *args) - rescue NoMethodError - raise ArgumentError, "Indirection category %s does not respond to REST method %s" % [indirection, method_name] - end - end - - # Return the singleton terminus for this indirection. - def terminus(name = nil) - @termini ||= {} - # Get the name of the terminus. - unless name - unless param_name = @indirection[:to] - raise ArgumentError, "You must specify an indirection terminus for indirection %s" % @indirection[:name] + @indirection.terminus.send(method_name, *args) + rescue NoMethodError => detail + if Puppet[:trace] + puts detail.backtrace end - name = Puppet[param_name] - name = name.intern if name.is_a?(String) - end - - unless @termini[name] - @termini[name] = make_terminus(name) + raise ArgumentError, "The %s terminus of the %s indirection failed to respond to %s: %s" % + [@indirection.terminus.name, @indirection.name, method_name, detail] end - @termini[name] end end diff --git a/lib/puppet/indirector/facts/yaml.rb b/lib/puppet/indirector/facts/yaml.rb index 87860012f..f29ea8ebc 100644 --- a/lib/puppet/indirector/facts/yaml.rb +++ b/lib/puppet/indirector/facts/yaml.rb @@ -1,41 +1,41 @@ Puppet::Indirector.register_terminus :facts, :yaml do desc "Store client facts as flat files, serialized using YAML." # Get a client's facts. def get(node) file = path(node) return nil unless FileTest.exists?(file) begin values = YAML::load(File.read(file)) rescue => detail Puppet.err "Could not load facts for %s: %s" % [node, detail] end Puppet::Node::Facts.new(node, values) end def initialize Puppet.config.use(:yamlfacts) end # Store the facts to disk. - def put(facts) + def post(facts) File.open(path(facts.name), "w", 0600) do |f| begin f.print YAML::dump(facts.values) rescue => detail Puppet.err "Could not write facts for %s: %s" % [facts.name, detail] end end nil end private # Return the path to a given node's file. def path(name) File.join(Puppet[:yamlfactdir], name + ".yaml") end end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb new file mode 100644 index 000000000..7a4c4bd55 --- /dev/null +++ b/lib/puppet/indirector/indirection.rb @@ -0,0 +1,74 @@ +# An actual indirection. +class Puppet::Indirector::Indirection + @@indirections = [] + + # Clear all cached termini from all indirections. + def self.clear_cache + @@indirections.each { |ind| ind.clear_cache } + end + + attr_accessor :name, :termini + attr_reader :to + + # Clear our cached list of termini. + # This is only used for testing. + def clear_cache + @termini.clear + end + + # This is only used for testing. + def delete + @@indirections.delete(self) if @@indirections.include?(self) + end + + def initialize(name, options = {}) + @name = name + options.each do |name, value| + begin + send(name.to_s + "=", value) + rescue NoMethodError + raise ArgumentError, "%s is not a valid Indirection parameter" % name + end + end + @termini = {} + @@indirections << self + end + + # Return the singleton terminus for this indirection. + def terminus(name = nil) + # Get the name of the terminus. + unless name + unless param_name = self.to + raise ArgumentError, "You must specify an indirection terminus for indirection %s" % self.name + end + name = Puppet[param_name] + name = name.intern if name.is_a?(String) + end + + unless @termini[name] + @termini[name] = make_terminus(name) + end + @termini[name] + end + + # Validate the parameter. This requires that indirecting + # classes require 'puppet/defaults', because of ordering issues, + # but it makes problems much easier to debug. + def to=(param_name) + unless Puppet.config.valid?(param_name) + raise ArgumentError, "Configuration parameter '%s' for indirection '%s' does not exist'" % [param_name, self.name] + end + @to = param_name + end + + private + + # Create a new terminus instance. + def make_terminus(name) + # Load our terminus class. + unless klass = Puppet::Indirector.terminus(self.name, name) + raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, self.name] + end + return klass.new + end +end diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb index ed2a8893e..13cd265fb 100644 --- a/lib/puppet/indirector/node/external.rb +++ b/lib/puppet/indirector/node/external.rb @@ -1,55 +1,89 @@ +require 'puppet/node/facts' + Puppet::Indirector.register_terminus :node, :external do desc "Call an external program to get node information." include Puppet::Util + + # Proxy the execution, so it's easier to test. + def execute(command) + Puppet::Util.execute(command) + end + # Look for external node definitions. def get(name) - return nil unless Puppet[:external_nodes] != "none" + unless Puppet[:external_nodes] != "none" + raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node source" + end + unless Puppet[:external_nodes][0] == File::SEPARATOR[0] + raise ArgumentError, "You must set the 'external_nodes' parameter to a fully qualified command" + end + + # Run the command. + unless output = query(name) + return nil + end + + # Translate the output to ruby. + result = translate(name, output) + + return create_node(name, result) + end + + private + + # Turn our outputted objects into a Puppet::Node instance. + def create_node(name, result) + node = Puppet::Node.new(name) + set = false + [:parameters, :classes].each do |param| + if value = result[param] + node.send(param.to_s + "=", value) + set = true + end + end + + if set + node.fact_merge + return node + else + return nil + end + end + + # Call the external command and see if it returns our output. + def query(name) # This is a very cheap way to do this, since it will break on # commands that have spaces in the arguments. But it's good # enough for most cases. external_node_command = Puppet[:external_nodes].split external_node_command << name begin - output = Puppet::Util.execute(external_node_command) + output = execute(external_node_command) rescue Puppet::ExecutionFailure => detail if $?.exitstatus == 1 return nil else Puppet.err "Could not retrieve external node information for %s: %s" % [name, detail] end return nil end if output =~ /\A\s*\Z/ # all whitespace Puppet.debug "Empty response for %s from external node source" % name return nil + else + return output end + end + # Translate the yaml string into Ruby objects. + def translate(name, output) begin - result = YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash } + YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash } rescue => detail raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail] end - - node = Puppe::Node.new(name) - set = false - [:parameters, :classes].each do |param| - if value = result[param] - node.send(param.to_s + "=", value) - set = true - end - end - - if facts = Puppet::Node.facts(name) - node.fact_merge(facts) - end - - if set - return node - else - return nil - end end end diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb index 77be04126..fb60cad31 100644 --- a/lib/puppet/indirector/node/ldap.rb +++ b/lib/puppet/indirector/node/ldap.rb @@ -1,142 +1,140 @@ Puppet::Indirector.register_terminus :node, :ldap do desc "Search in LDAP for node configuration information." # Look for our node in ldap. def get(name) unless ary = ldapsearch(name) return nil end parent, classes, parameters = ary while parent parent, tmpclasses, tmpparams = ldapsearch(parent) classes += tmpclasses if tmpclasses tmpparams.each do |param, value| # Specifically test for whether it's set, so false values are handled # correctly. parameters[param] = value unless parameters.include?(param) end end - node = Puppe::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) - if facts = Puppet::Node.facts(name) - node.fact_merge(facts) - end + node = Puppet::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) + node.fact_merge return node end # Find the ldap node, return the class list and parent node specially, # and everything else in a parameter hash. def ldapsearch(node) filter = Puppet[:ldapstring] classattrs = Puppet[:ldapclassattrs].split("\s*,\s*") if Puppet[:ldapattrs] == "all" # A nil value here causes all attributes to be returned. search_attrs = nil else search_attrs = classattrs + Puppet[:ldapattrs].split("\s*,\s*") end pattr = nil if pattr = Puppet[:ldapparentattr] if pattr == "" pattr = nil else search_attrs << pattr unless search_attrs.nil? end end if filter =~ /%s/ filter = filter.gsub(/%s/, node) end parent = nil classes = [] parameters = nil found = false count = 0 begin # We're always doing a sub here; oh well. ldap.search(Puppet[:ldapbase], 2, filter, search_attrs) do |entry| found = true if pattr if values = entry.vals(pattr) if values.length > 1 raise Puppet::Error, "Node %s has more than one parent: %s" % [node, values.inspect] end unless values.empty? parent = values.shift end end end classattrs.each { |attr| if values = entry.vals(attr) values.each do |v| classes << v end end } parameters = entry.to_hash.inject({}) do |hash, ary| if ary[1].length == 1 hash[ary[0]] = ary[1].shift else hash[ary[0]] = ary[1] end hash end end rescue => detail if count == 0 # Try reconnecting to ldap @ldap = nil retry else raise Puppet::Error, "LDAP Search failed: %s" % detail end end classes.flatten! if classes.empty? classes = nil end if parent or classes or parameters return parent, classes, parameters else return nil end end private # Create an ldap connection. def ldap unless defined? @ldap and @ldap unless Puppet.features.ldap? raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" end begin if Puppet[:ldapssl] @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) elsif Puppet[:ldaptls] @ldap = LDAP::SSLConn.new( Puppet[:ldapserver], Puppet[:ldapport], true ) else @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) end @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) rescue => detail raise Puppet::Error, "Could not connect to LDAP: %s" % detail end end return @ldap end end diff --git a/lib/puppet/indirector/node/none.rb b/lib/puppet/indirector/node/none.rb index 7143033d9..2b326968e 100644 --- a/lib/puppet/indirector/node/none.rb +++ b/lib/puppet/indirector/node/none.rb @@ -1,14 +1,14 @@ +require 'puppet/node/facts' + Puppet::Indirector.register_terminus :node, :none do desc "Always return an empty node object. This is the node source you should use when you don't have some other, functional source you want to use, as the compiler will not work without this node information." # Just return an empty node. def get(name) node = Puppet::Node.new(name) - if facts = Puppet::Node.facts(name) - node.fact_merge(facts) - end + node.fact_merge node end end diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb index 372e80325..2df1b3ab4 100644 --- a/lib/puppet/network/handler/configuration.rb +++ b/lib/puppet/network/handler/configuration.rb @@ -1,212 +1,212 @@ require 'openssl' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/sslcertificates' require 'xmlrpc/server' require 'yaml' class Puppet::Network::Handler class Configuration < Handler desc "Puppet's configuration compilation interface. Passed a node name or other key, retrieves information about the node (using the ``node_source``) and returns a compiled configuration." include Puppet::Util attr_accessor :local @interface = XMLRPC::Service::Interface.new("configuration") { |iface| iface.add_method("string configuration(string)") iface.add_method("string version()") } # Compile a node's configuration. def configuration(key, client = nil, clientip = nil) # If we want to use the cert name as our key if Puppet[:node_name] == 'cert' and client key = client end # Note that this is reasonable, because either their node source should actually # know about the node, or they should be using the ``none`` node source, which # will always return data. unless node = Puppet::Node.search(key) raise Puppet::Error, "Could not find node '%s'" % key end # Add any external data to the node. add_node_data(node) configuration = compile(node) return translate(configuration) end def initialize(options = {}) if options[:Local] @local = options[:Local] else @local = false end # Just store the options, rather than creating the interpreter # immediately. Mostly, this is so we can create the interpreter # on-demand, which is easier for testing. @options = options set_server_facts end # Are we running locally, or are our clients networked? def local? self.local end # Return the configuration version. def version(client = nil, clientip = nil) if client and node = Puppet::Node.search(client) update_node_check(node) return interpreter.configuration_version(node) else # Just return something that will always result in a recompile, because # this is local. return (Time.now + 1000).to_i end end private # Add any extra data necessary to the node. def add_node_data(node) # Merge in our server-side facts, so they can be used during compilation. - node.fact_merge(@server_facts) + node.merge(@server_facts) # Add any specified classes to the node's class list. if classes = @options[:Classes] classes.each do |klass| node.classes << klass end end end # Compile the actual configuration. def compile(node) # Pick the benchmark level. if local? level = :none else level = :notice end # Ask the interpreter to compile the configuration. str = "Compiled configuration for %s" % node.name if node.environment str += " in environment %s" % node.environment end config = nil benchmark(level, "Compiled configuration for %s" % node.name) do begin config = interpreter.compile(node) rescue Puppet::Error => detail if Puppet[:trace] puts detail.backtrace end unless local? Puppet.err detail.to_s end raise XMLRPC::FaultException.new( 1, detail.to_s ) end end return config end # Create our interpreter object. def create_interpreter(options) args = {} # Allow specification of a code snippet or of a file if code = options[:Code] args[:Code] = code elsif options[:Manifest] args[:Manifest] = options[:Manifest] end args[:Local] = local? if options.include?(:UseNodes) args[:UseNodes] = options[:UseNodes] elsif @local args[:UseNodes] = false end # This is only used by the cfengine module, or if --loadclasses was # specified in +puppet+. if options.include?(:Classes) args[:Classes] = options[:Classes] end return Puppet::Parser::Interpreter.new(args) end # Create/return our interpreter. def interpreter unless defined?(@interpreter) and @interpreter @interpreter = create_interpreter(@options) end @interpreter end # Initialize our server fact hash; we add these to each client, and they # won't change while we're running, so it's safe to cache the values. def set_server_facts @server_facts = {} # Add our server version to the fact list @server_facts["serverversion"] = Puppet.version.to_s # And then add the server name and IP {"servername" => "fqdn", "serverip" => "ipaddress" }.each do |var, fact| if value = Facter.value(fact) @server_facts[var] = value else Puppet.warning "Could not retrieve fact %s" % fact end end if @server_facts["servername"].nil? host = Facter.value(:hostname) if domain = Facter.value(:domain) @server_facts["servername"] = [host, domain].join(".") else @server_facts["servername"] = host end end end # Translate our configuration appropriately for sending back to a client. def translate(config) if local? config else CGI.escape(config.to_yaml(:UseBlock => true)) end end # Mark that the node has checked in. FIXME this needs to be moved into # the Node class, or somewhere that's got abstract backends. def update_node_check(node) if Puppet.features.rails? and Puppet[:storeconfigs] Puppet::Rails.connect host = Puppet::Rails::Host.find_or_create_by_name(node.name) host.last_freshcheck = Time.now host.save end end end end diff --git a/lib/puppet/network/handler/facts.rb b/lib/puppet/network/handler/facts.rb deleted file mode 100755 index 4767e8be4..000000000 --- a/lib/puppet/network/handler/facts.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'yaml' -require 'puppet/util/fact_store' - -class Puppet::Network::Handler - # Receive logs from remote hosts. - class Facts < Handler - desc "An interface for storing and retrieving client facts. Currently only - used internally by Puppet." - - @interface = XMLRPC::Service::Interface.new("facts") { |iface| - iface.add_method("void set(string, string)") - iface.add_method("string get(string)") - iface.add_method("integer store_date(string)") - } - - def initialize(hash = {}) - super - - backend = Puppet[:factstore] - - unless klass = Puppet::Util::FactStore.store(backend) - raise Puppet::Error, "Could not find fact store %s" % backend - end - - @backend = klass.new - end - - # Get the facts from our back end. - def get(node) - if facts = @backend.get(node) - return strip_internal(facts) - else - return nil - end - end - - # Set the facts in the backend. - def set(node, facts) - @backend.set(node, add_internal(facts)) - nil - end - - # Retrieve a client's storage date. - def store_date(node) - if facts = get(node) - facts[:_puppet_timestamp].to_i - else - nil - end - end - - private - - # Add internal data to the facts for storage. - def add_internal(facts) - facts = facts.dup - facts[:_puppet_timestamp] = Time.now - facts - end - - # Strip out that internal data. - def strip_internal(facts) - facts = facts.dup - facts.find_all { |name, value| name.to_s =~ /^_puppet_/ }.each { |name, value| facts.delete(name) } - facts - end - end -end diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index a429412d2..993e9d51a 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -1,684 +1,676 @@ require 'puppet' require 'puppet/network/authstore' require 'webrick/httpstatus' require 'cgi' require 'delegate' require 'sync' class Puppet::Network::Handler AuthStoreError = Puppet::AuthStoreError class FileServerError < Puppet::Error; end class FileServer < Handler desc "The interface to Puppet's fileserving abilities." attr_accessor :local CHECKPARAMS = [:mode, :type, :owner, :group, :checksum] # Special filserver module for puppet's module system MODULES = "modules" @interface = XMLRPC::Service::Interface.new("fileserver") { |iface| iface.add_method("string describe(string, string)") iface.add_method("string list(string, string, boolean, array)") iface.add_method("string retrieve(string, string)") } def self.params CHECKPARAMS.dup end # Describe a given file. This returns all of the manageable aspects # of that file. def describe(url, links = :ignore, client = nil, clientip = nil) links = links.intern if links.is_a? String if links == :manage raise Puppet::Network::Handler::FileServerError, "Cannot currently copy links" end mount, path = convert(url, client, clientip) if client mount.debug "Describing %s for %s" % [url, client] end obj = nil unless obj = mount.getfileobject(path, links) return "" end currentvalues = mount.check(obj) desc = [] CHECKPARAMS.each { |check| if value = currentvalues[check] desc << value else if check == "checksum" and currentvalues[:type] == "file" mount.notice "File %s does not have data for %s" % [obj.name, check] end desc << nil end } return desc.join("\t") end # Create a new fileserving module. def initialize(hash = {}) @mounts = {} @files = {} if hash[:Local] @local = hash[:Local] else @local = false end if hash[:Config] == false @noreadconfig = true else @config = Puppet::Util::LoadedFile.new( hash[:Config] || Puppet[:fileserverconfig] ) @noreadconfig = false end if hash.include?(:Mount) @passedconfig = true unless hash[:Mount].is_a?(Hash) raise Puppet::DevError, "Invalid mount hash %s" % hash[:Mount].inspect end hash[:Mount].each { |dir, name| if FileTest.exists?(dir) self.mount(dir, name) end } self.mount(nil, MODULES) else @passedconfig = false readconfig(false) # don't check the file the first time. end end # List a specific directory's contents. def list(url, links = :ignore, recurse = false, ignore = false, client = nil, clientip = nil) mount, path = convert(url, client, clientip) if client mount.debug "Listing %s for %s" % [url, client] end obj = nil unless FileTest.exists?(path) return "" end # We pass two paths here, but reclist internally changes one # of the arguments when called internally. desc = reclist(mount, path, path, recurse, ignore) if desc.length == 0 mount.notice "Got no information on //%s/%s" % [mount, path] return "" end desc.collect { |sub| sub.join("\t") }.join("\n") end def local? self.local end # Mount a new directory with a name. def mount(path, name) if @mounts.include?(name) if @mounts[name] != path raise FileServerError, "%s is already mounted at %s" % [@mounts[name].path, name] else # it's already mounted; no problem return end end # Let the mounts do their own error-checking. @mounts[name] = Mount.new(name, path) @mounts[name].info "Mounted %s" % path return @mounts[name] end # Retrieve a file from the local disk and pass it to the remote # client. def retrieve(url, links = :ignore, client = nil, clientip = nil) links = links.intern if links.is_a? String mount, path = convert(url, client, clientip) if client mount.info "Sending %s to %s" % [url, client] end unless FileTest.exists?(path) return "" end links = links.intern if links.is_a? String if links == :ignore and FileTest.symlink?(path) return "" end str = nil if links == :manage raise Puppet::Error, "Cannot copy links yet." else str = File.read(path) end if @local return str else return CGI.escape(str) end end def umount(name) @mounts.delete(name) if @mounts.include? name end private def authcheck(file, mount, client, clientip) # If we're local, don't bother passing in information. if local? client = nil clientip = nil end unless mount.allowed?(client, clientip) mount.warning "%s cannot access %s" % [client, file] raise Puppet::AuthorizationError, "Cannot access %s" % mount end end def convert(url, client, clientip) readconfig url = URI.unescape(url) mount, stub = splitpath(url, client) authcheck(url, mount, client, clientip) path = nil unless path = mount.subdir(stub, client) mount.notice "Could not find subdirectory %s" % "//%s/%s" % [mount, stub] return "" end return mount, path end # Deal with ignore parameters. def handleignore(children, path, ignore) ignore.each { |ignore| Dir.glob(File.join(path,ignore), File::FNM_DOTMATCH) { |match| children.delete(File.basename(match)) } } return children end # Return the mount for the Puppet modules; allows file copying from # the modules. def modules_mount(module_name, client) # Find our environment, if we have one. - if node = node_handler.details(client || Facter.value("hostname")) + if node = Puppet::Node.get(client || Facter.value("hostname")) env = node.environment else env = nil end # And use the environment to look up the module. mod = Puppet::Module::find(module_name, env) if mod return @mounts[MODULES].copy(mod.name, mod.files) else return nil end end - # Create a node handler instance for looking up our nodes. - def node_handler - unless defined?(@node_handler) - @node_handler = Puppet::Network::Handler.handler(:node).create - end - @node_handler - end - # Read the configuration file. def readconfig(check = true) return if @noreadconfig if check and ! @config.changed? return end newmounts = {} begin File.open(@config.file) { |f| mount = nil count = 1 f.each { |line| case line when /^\s*#/: next # skip comments when /^\s*$/: next # skip blank lines when /\[([-\w]+)\]/: name = $1 if newmounts.include?(name) raise FileServerError, "%s is already mounted at %s" % [newmounts[name], name], count, @config.file end mount = Mount.new(name) newmounts[name] = mount when /^\s*(\w+)\s+(.+)$/: var = $1 value = $2 case var when "path": if mount.name == MODULES Puppet.warning "The '#{MODULES}' module can not have a path. Ignoring attempt to set it" else begin mount.path = value rescue FileServerError => detail Puppet.err "Removing mount %s: %s" % [mount.name, detail] newmounts.delete(mount.name) end end when "allow": value.split(/\s*,\s*/).each { |val| begin mount.info "allowing %s access" % val mount.allow(val) rescue AuthStoreError => detail raise FileServerError.new(detail.to_s, count, @config.file) end } when "deny": value.split(/\s*,\s*/).each { |val| begin mount.info "denying %s access" % val mount.deny(val) rescue AuthStoreError => detail raise FileServerError.new(detail.to_s, count, @config.file) end } else raise FileServerError.new("Invalid argument '%s'" % var, count, @config.file) end else raise FileServerError.new("Invalid line '%s'" % line.chomp, count, @config.file) end count += 1 } } rescue Errno::EACCES => detail Puppet.err "FileServer error: Cannot read %s; cannot serve" % @config #raise Puppet::Error, "Cannot read %s" % @config rescue Errno::ENOENT => detail Puppet.err "FileServer error: '%s' does not exist; cannot serve" % @config #raise Puppet::Error, "%s does not exit" % @config #rescue FileServerError => detail # Puppet.err "FileServer error: %s" % detail end unless newmounts[MODULES] mount = Mount.new(MODULES) mount.allow("*") newmounts[MODULES] = mount end # Verify each of the mounts are valid. # We let the check raise an error, so that it can raise an error # pointing to the specific problem. newmounts.each { |name, mount| unless mount.valid? raise FileServerError, "No path specified for mount %s" % name end } @mounts = newmounts end # Recursively list the directory. FIXME This should be using # puppet objects, not directly listing. def reclist(mount, root, path, recurse, ignore) # Take out the root of the path. name = path.sub(root, '') if name == "" name = "/" end if name == path raise FileServerError, "Could not match %s in %s" % [root, path] end desc = [name] ftype = File.stat(path).ftype desc << ftype if recurse.is_a?(Integer) recurse -= 1 end ary = [desc] if recurse == true or (recurse.is_a?(Integer) and recurse > -1) if ftype == "directory" children = Dir.entries(path) if ignore children = handleignore(children, path, ignore) end children.each { |child| next if child =~ /^\.\.?$/ reclist(mount, root, File.join(path, child), recurse, ignore).each { |cobj| ary << cobj } } end end return ary.reject { |c| c.nil? } end # Split the path into the separate mount point and path. def splitpath(dir, client) # the dir is based on one of the mounts # so first retrieve the mount path mount = nil path = nil if dir =~ %r{/([-\w]+)/?} # Strip off the mount name. mount_name, path = dir.sub(%r{^/}, '').split(File::Separator, 2) unless mount = modules_mount(mount_name, client) unless mount = @mounts[mount_name] raise FileServerError, "Fileserver module '%s' not mounted" % mount_name end end else raise FileServerError, "Fileserver error: Invalid path '%s'" % dir end if path == "" path = nil elsif path # Remove any double slashes that might have occurred path = URI.unescape(path.gsub(/\/\//, "/")) end return mount, path end def to_s "fileserver" end # A simple class for wrapping mount points. Instances of this class # don't know about the enclosing object; they're mainly just used for # authorization. class Mount < Puppet::Network::AuthStore attr_reader :name @@syncs = {} @@files = {} Puppet::Util.logmethods(self, true) def getfileobject(dir, links) unless FileTest.exists?(dir) self.notice "File source %s does not exist" % dir return nil end return fileobj(dir, links) end # Run 'retrieve' on a file. This gets the actual parameters, so # we can pass them to the client. def check(obj) # Retrieval is enough here, because we don't want to cache # any information in the state file, and we don't want to generate # any state changes or anything. We don't even need to sync # the checksum, because we're always going to hit the disk # directly. # We're now caching file data, using the LoadedFile to check the # disk no more frequently than the :filetimeout. path = obj[:path] sync = sync(path) unless data = @@files[path] data = {} sync.synchronize(Sync::EX) do @@files[path] = data data[:loaded_obj] = Puppet::Util::LoadedFile.new(path) data[:values] = properties(obj) return data[:values] end end changed = nil sync.synchronize(Sync::SH) do changed = data[:loaded_obj].changed? end if changed sync.synchronize(Sync::EX) do data[:values] = properties(obj) return data[:values] end else sync.synchronize(Sync::SH) do return data[:values] end end end # Create a map for a specific client. def clientmap(client) { "h" => client.sub(/\..*$/, ""), "H" => client, "d" => client.sub(/[^.]+\./, "") # domain name } end # Replace % patterns as appropriate. def expand(path, client = nil) # This map should probably be moved into a method. map = nil if client map = clientmap(client) else Puppet.notice "No client; expanding '%s' with local host" % path # Else, use the local information map = localmap() end path.gsub(/%(.)/) do |v| key = $1 if key == "%" "%" else map[key] || v end end end # Do we have any patterns in our path, yo? def expandable? if defined? @expandable @expandable else false end end # Create out object. It must have a name. def initialize(name, path = nil) unless name =~ %r{^[-\w]+$} raise FileServerError, "Invalid name format '%s'" % name end @name = name if path self.path = path else @path = nil end super() end def fileobj(path, links) obj = nil if obj = Puppet.type(:file)[path] # This can only happen in local fileserving, but it's an # important one. It'd be nice if we didn't just set # the check params every time, but I'm not sure it's worth # the effort. obj[:check] = CHECKPARAMS else obj = Puppet.type(:file).create( :name => path, :check => CHECKPARAMS ) end if links == :manage links = :follow end # This, ah, might be completely redundant unless obj[:links] == links obj[:links] = links end return obj end # Cache this manufactured map, since if it's used it's likely # to get used a lot. def localmap unless defined? @@localmap @@localmap = { "h" => Facter.value("hostname"), "H" => [Facter.value("hostname"), Facter.value("domain")].join("."), "d" => Facter.value("domain") } end @@localmap end # Return the path as appropriate, expanding as necessary. def path(client = nil) if expandable? return expand(@path, client) else return @path end end # Set the path. def path=(path) # FIXME: For now, just don't validate paths with replacement # patterns in them. if path =~ /%./ # Mark that we're expandable. @expandable = true else unless FileTest.exists?(path) raise FileServerError, "%s does not exist" % path end unless FileTest.directory?(path) raise FileServerError, "%s is not a directory" % path end unless FileTest.readable?(path) raise FileServerError, "%s is not readable" % path end @expandable = false end @path = path end # Return the current values for the object. def properties(obj) obj.retrieve.inject({}) { |props, ary| props[ary[0].name] = ary[1]; props } end # Retrieve a specific directory relative to a mount point. # If they pass in a client, then expand as necessary. def subdir(dir = nil, client = nil) basedir = self.path(client) dirname = if dir File.join(basedir, dir.split("/").join(File::SEPARATOR)) else basedir end dirname end def sync(path) @@syncs[path] ||= Sync.new @@syncs[path] end def to_s "mount[%s]" % @name end # Verify our configuration is valid. This should really check to # make sure at least someone will be allowed, but, eh. def valid? if name == MODULES return @path.nil? else return ! @path.nil? end end # Return a new mount with the same properties as +self+, except # with a different name and path. def copy(name, path) result = self.clone result.path = path result.instance_variable_set(:@name, name) return result end end end end # $Id$ diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb index c8db277ba..9550dd550 100644 --- a/lib/puppet/network/handler/master.rb +++ b/lib/puppet/network/handler/master.rb @@ -1,153 +1,146 @@ require 'openssl' require 'puppet' require 'puppet/parser/interpreter' require 'puppet/sslcertificates' require 'xmlrpc/server' require 'yaml' class Puppet::Network::Handler class MasterError < Puppet::Error; end class Master < Handler desc "Puppet's configuration interface. Used for all interactions related to generating client configurations." include Puppet::Util attr_accessor :ast attr_reader :ca @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| iface.add_method("string getconfig(string)") iface.add_method("int freshness()") } # Tell a client whether there's a fresh config for it def freshness(client = nil, clientip = nil) client ||= Facter.value("hostname") config_handler.version(client, clientip) end def initialize(hash = {}) args = {} # Allow specification of a code snippet or of a file if code = hash[:Code] args[:Code] = code elsif man = hash[:Manifest] args[:Manifest] = man end if hash[:Local] @local = hash[:Local] else @local = false end args[:Local] = true if hash.include?(:CA) and hash[:CA] @ca = Puppet::SSLCertificates::CA.new() else @ca = nil end Puppet.debug("Creating interpreter") if hash.include?(:UseNodes) args[:UseNodes] = hash[:UseNodes] elsif @local args[:UseNodes] = false end # This is only used by the cfengine module, or if --loadclasses was # specified in +puppet+. if hash.include?(:Classes) args[:Classes] = hash[:Classes] end @config_handler = Puppet::Network::Handler.handler(:configuration).new(args) end # Call our various handlers; this handler is getting deprecated. def getconfig(facts, format = "marshal", client = nil, clientip = nil) facts = decode_facts(facts) client, clientip = clientname(client, clientip, facts) # Pass the facts to the fact handler - fact_handler.set(client, facts) + Puppet::Node::Facts.post(Puppet::Node::Facts.new(client, facts)) # And get the configuration from the config handler begin config = config_handler.configuration(client) rescue => detail puts detail.backtrace raise end return translate(config.extract) end private # Manipulate the client name as appropriate. def clientname(name, ip, facts) # Always use the hostname from Facter. client = facts["hostname"] clientip = facts["ipaddress"] if Puppet[:node_name] == 'cert' if name client = name end if ip clientip = ip end end return client, clientip end def config_handler unless defined? @config_handler @config_handler = Puppet::Network::Handler.handler(:config).new :local => local? end @config_handler end # def decode_facts(facts) if @local # we don't need to do anything, since we should already # have raw objects Puppet.debug "Our client is local" else Puppet.debug "Our client is remote" begin facts = YAML.load(CGI.unescape(facts)) rescue => detail raise XMLRPC::FaultException.new( 1, "Could not rebuild facts" ) end end return facts end - def fact_handler - unless defined? @fact_handler - @fact_handler = Puppet::Network::Handler.handler(:facts).new :local => local? - end - @fact_handler - end - # Translate our configuration appropriately for sending back to a client. def translate(config) if local? config else CGI.escape(config.to_yaml(:UseBlock => true)) end end end end diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 9c5d2d397..7ad7bc3b3 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -1,75 +1,84 @@ +require 'puppet/indirector' + # A simplistic class for managing the node information itself. class Puppet::Node + require 'puppet/node/facts' + # Set up indirection, so that nodes can be looked for in # the node sources. - require 'puppet/indirector' extend Puppet::Indirector # Use the node source as the indirection terminus. indirects :node, :to => :node_source # Add the node-searching methods. This is what people will actually # interact with that will find the node with the list of names or # will search for a default node. require 'puppet/node/searching' extend Puppet::Node::Searching attr_accessor :name, :classes, :parameters, :source, :ipaddress, :names attr_reader :time attr_writer :environment # Do not return environments that are the empty string, and use # explicitly set environments, then facts, then a central env # value. def environment unless @environment and @environment != "" if env = parameters["environment"] and env != "" @environment = env elsif env = Puppet[:environment] and env != "" @environment = env else @environment = nil end end @environment end def initialize(name, options = {}) @name = name # Provide a default value. if names = options[:names] if names.is_a?(String) @names = [names] else @names = names end else @names = [name] end if classes = options[:classes] if classes.is_a?(String) @classes = [classes] else @classes = classes end else @classes = [] end @parameters = options[:parameters] || {} @environment = options[:environment] @time = Time.now end # Merge the node facts with parameters from the node source. - # This is only called if the node source has 'fact_merge' set to true. - def fact_merge(facts) - facts.each do |name, value| + def fact_merge + if facts = Puppet::Node::Facts.get(name) + merge(facts.values) + end + end + + # Merge any random parameters into our parameter list. + def merge(params) + params.each do |name, value| @parameters[name] = value unless @parameters.include?(name) end end end diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb index eddf44def..e5774127b 100755 --- a/lib/puppet/node/facts.rb +++ b/lib/puppet/node/facts.rb @@ -1,36 +1,36 @@ +require 'puppet/node' +require 'puppet/indirector' + # Manage a given node's facts. This either accepts facts and stores them, or # returns facts for a given node. class Puppet::Node::Facts # Set up indirection, so that nodes can be looked for in # the node sources. - require 'puppet/indirector' extend Puppet::Indirector # Use the node source as the indirection terminus. indirects :facts, :to => :fact_store attr_accessor :name, :values def initialize(name, values = {}) @name = name @values = values + + add_internal end private - # FIXME These methods are currently unused. - # Add internal data to the facts for storage. - def add_internal(facts) - facts = facts.dup - facts[:_puppet_timestamp] = Time.now - facts + def add_internal + self.values[:_timestamp] = Time.now end # Strip out that internal data. - def strip_internal(facts) - facts = facts.dup - facts.find_all { |name, value| name.to_s =~ /^_puppet_/ }.each { |name, value| facts.delete(name) } - facts + def strip_internal + newvals = values.dup + newvals.find_all { |name, value| name.to_s =~ /^_/ }.each { |name, value| newvals.delete(name) } + newvals end end diff --git a/spec/Rakefile b/spec/Rakefile index 40d107312..bb2a75de5 100644 --- a/spec/Rakefile +++ b/spec/Rakefile @@ -1,10 +1,16 @@ require File.join(File.dirname(__FILE__), "spec_helper.rb") require 'rake' require 'spec/rake/spectask' +basedir = File.dirname(__FILE__) +puppetlibdir = File.join(basedir, "../lib") +puppettestlibdir = File.join(basedir, "../test/lib") +speclibdir = File.join(basedir, "lib") + desc "Run all spec unit tests" Spec::Rake::SpecTask.new('unit') do |t| t.spec_files = FileList['unit/**/*.rb'] + t.libs = [puppetlibdir, puppettestlibdir, speclibdir] end task :default => [:unit] diff --git a/spec/lib/spec/dsl/behaviour.rb b/spec/lib/spec/dsl/behaviour.rb index 5158bb673..93a357f19 100644 --- a/spec/lib/spec/dsl/behaviour.rb +++ b/spec/lib/spec/dsl/behaviour.rb @@ -1,220 +1,224 @@ +require 'puppettest/runnable_test' + module Spec module DSL - class EvalModule < Module; end + class EvalModule < Module; + include PuppetTest::RunnableTest + end class Behaviour extend BehaviourCallbacks class << self def add_shared_behaviour(behaviour) return if behaviour.equal?(found_behaviour = find_shared_behaviour(behaviour.description)) return if found_behaviour and File.expand_path(behaviour.description[:spec_path]) == File.expand_path(found_behaviour.description[:spec_path]) raise ArgumentError.new("Shared Behaviour '#{behaviour.description}' already exists") if found_behaviour shared_behaviours << behaviour end def find_shared_behaviour(behaviour_description) shared_behaviours.find { |b| b.description == behaviour_description } end def shared_behaviours # TODO - this needs to be global, or at least accessible from # from subclasses of Behaviour in a centralized place. I'm not loving # this as a solution, but it works for now. $shared_behaviours ||= [] end end def initialize(*args, &behaviour_block) init_description(*args) init_eval_module before_eval eval_behaviour(&behaviour_block) end private def init_description(*args) unless self.class == Behaviour args << {} unless Hash === args.last args.last[:behaviour_class] = self.class end @description = Description.new(*args) end def init_eval_module @eval_module = EvalModule.new @eval_module.extend BehaviourEval::ModuleMethods @eval_module.include BehaviourEval::InstanceMethods @eval_module.include described_type if described_type.class == Module @eval_module.behaviour = self @eval_module.description = @description end def eval_behaviour(&behaviour_block) @eval_module.class_eval(&behaviour_block) end protected def before_eval end public def run(reporter, dry_run=false, reverse=false, timeout=nil) raise "shared behaviours should never run" if shared? # TODO - change add_behaviour to add_description ?????? reporter.add_behaviour(@description) prepare_execution_context_class before_all_errors = run_before_all(reporter, dry_run) exs = reverse ? examples.reverse : examples example_execution_context = nil if before_all_errors.empty? exs.each do |example| example_execution_context = execution_context(example) example_execution_context.copy_instance_variables_from(@before_and_after_all_context_instance) unless before_all_proc(behaviour_type).nil? befores = before_each_proc(behaviour_type) {|e| raise e} afters = after_each_proc(behaviour_type) example.run(reporter, befores, afters, dry_run, example_execution_context, timeout) end end @before_and_after_all_context_instance.copy_instance_variables_from(example_execution_context) unless after_all_proc(behaviour_type).nil? run_after_all(reporter, dry_run) end def number_of_examples examples.length end def matches?(specified_examples) matcher ||= ExampleMatcher.new(description) examples.each do |example| return true if example.matches?(matcher, specified_examples) end return false end def shared? @description[:shared] end def retain_examples_matching!(specified_examples) return if specified_examples.index(description) matcher = ExampleMatcher.new(description) examples.reject! do |example| !example.matches?(matcher, specified_examples) end end def methods my_methods = super my_methods |= @eval_module.methods my_methods end # Includes modules in the Behaviour (the describe block). def include(*args) @eval_module.include(*args) end def behaviour_type #:nodoc: @description[:behaviour_type] end # Sets the #number on each Example and returns the next number def set_sequence_numbers(number, reverse) #:nodoc: exs = reverse ? examples.reverse : examples exs.each do |example| example.number = number number += 1 end number end protected # Messages that this class does not understand # are passed directly to the @eval_module. def method_missing(sym, *args, &block) @eval_module.send(sym, *args, &block) end def prepare_execution_context_class plugin_mock_framework weave_in_included_modules define_predicate_matchers #this is in behaviour_eval execution_context_class end def weave_in_included_modules mods = [@eval_module] mods << included_modules.dup mods << Spec::Runner.configuration.modules_for(behaviour_type) execution_context_class.class_eval do # WARNING - the following can be executed in the context of any # class, and should never pass more than one module to include # even though we redefine include in this class. This is NOT # tested anywhere, hence this comment. mods.flatten.each {|mod| include mod} end end def execution_context(example) execution_context_class.new(example) end def run_before_all(reporter, dry_run) errors = [] unless dry_run begin @before_and_after_all_context_instance = execution_context(nil) @before_and_after_all_context_instance.instance_eval(&before_all_proc(behaviour_type)) rescue Exception => e errors << e location = "before(:all)" # The easiest is to report this as an example failure. We don't have an Example # at this point, so we'll just create a placeholder. reporter.example_finished(Example.new(location), e, location) if reporter end end errors end def run_after_all(reporter, dry_run) unless dry_run begin @before_and_after_all_context_instance ||= execution_context(nil) @before_and_after_all_context_instance.instance_eval(&after_all_proc(behaviour_type)) rescue Exception => e location = "after(:all)" reporter.example_finished(Example.new(location), e, location) if reporter end end end def plugin_mock_framework case mock_framework = Spec::Runner.configuration.mock_framework when Module include mock_framework else require Spec::Runner.configuration.mock_framework include Spec::Plugins::MockFramework end end def description @description.to_s end def described_type @description.described_type end end end end diff --git a/spec/lib/spec/runner/behaviour_runner.rb b/spec/lib/spec/runner/behaviour_runner.rb index 1ac891f3c..078490e92 100644 --- a/spec/lib/spec/runner/behaviour_runner.rb +++ b/spec/lib/spec/runner/behaviour_runner.rb @@ -1,123 +1,125 @@ module Spec module Runner class BehaviourRunner def initialize(options, arg=nil) @behaviours = [] @options = options end def add_behaviour(behaviour) if !specified_examples.nil? && !specified_examples.empty? behaviour.retain_examples_matching!(specified_examples) end @behaviours << behaviour if behaviour.number_of_examples != 0 && !behaviour.shared? end # Runs all behaviours and returns the number of failures. def run(paths, exit_when_done) prepare!(paths) begin run_behaviours rescue Interrupt ensure report_end end failure_count = report_dump heckle if(failure_count == 0 && !@options.heckle_runner.nil?) if(exit_when_done) exit_code = (failure_count == 0) ? 0 : 1 exit(exit_code) end failure_count end def report_end @options.reporter.end end def report_dump @options.reporter.dump end def prepare!(paths) unless paths.nil? # It's nil when running single specs with ruby paths = find_paths(paths) sorted_paths = sort_paths(paths) load_specs(sorted_paths) # This will populate @behaviours via callbacks to add_behaviour end @options.reporter.start(number_of_examples) @behaviours.reverse! if @options.reverse set_sequence_numbers end def run_behaviours @behaviours.each do |behaviour| + # LAK:NOTE: this 'runnable' test is Puppet-specific. + next unless behaviour.runnable? behaviour.run(@options.reporter, @options.dry_run, @options.reverse, @options.timeout) end end def number_of_examples @behaviours.inject(0) {|sum, behaviour| sum + behaviour.number_of_examples} end FILE_SORTERS = { 'mtime' => lambda {|file_a, file_b| File.mtime(file_b) <=> File.mtime(file_a)} } def sorter(paths) FILE_SORTERS[@options.loadby] end def sort_paths(paths) sorter = sorter(paths) paths = paths.sort(&sorter) unless sorter.nil? paths end private # Sets the #number on each Example def set_sequence_numbers number = 0 @behaviours.each do |behaviour| number = behaviour.set_sequence_numbers(number, @options.reverse) end end def find_paths(paths) result = [] paths.each do |path| if File.directory?(path) result += Dir["#{path}/**/*.rb"] elsif File.file?(path) result << path else raise "File or directory not found: #{path}" end end result end def load_specs(paths) paths.each do |path| load path end end def specified_examples @options.examples end def heckle heckle_runner = @options.heckle_runner @options.heckle_runner = nil behaviour_runner = self.class.new(@options) behaviour_runner.instance_variable_set(:@behaviours, @behaviours) heckle_runner.heckle_with(behaviour_runner) end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a4171bb07..477842495 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,20 +1,20 @@ dir = File.dirname(__FILE__) $:.unshift("#{dir}/lib").unshift("#{dir}/../lib") # Add the old test dir, so that we can still find mocha and spec $:.unshift("#{dir}/../test/lib") require 'mocha' -require 'spec' require 'puppettest' +require 'spec' Spec::Runner.configure do |config| config.mock_with :mocha config.prepend_before :each do setup() if respond_to? :setup end config.prepend_after :each do teardown() if respond_to? :teardown end end diff --git a/spec/unit/indirector/facts/yaml.rb b/spec/unit/indirector/facts/yaml.rb index 45c079a69..176a47f04 100755 --- a/spec/unit/indirector/facts/yaml.rb +++ b/spec/unit/indirector/facts/yaml.rb @@ -1,62 +1,62 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/indirector' require 'puppet/node/facts' require 'puppettest' describe Puppet::Indirector.terminus(:facts, :yaml), " when managing facts" do # For cleanup mechanisms. include PuppetTest # LAK:FIXME It seems like I really do have to hit the filesystem # here, since, like, that's what I'm testing. Is there another/better # way to do this? before do @store = Puppet::Indirector.terminus(:facts, :yaml).new setup # Grr, stupid rspec Puppet[:yamlfactdir] = tempfile Dir.mkdir(Puppet[:yamlfactdir]) end it "should store facts in YAML in the yamlfactdir" do values = {"one" => "two", "three" => "four"} facts = Puppet::Node::Facts.new("node", values) - @store.put(facts) + @store.post(facts) # Make sure the file exists path = File.join(Puppet[:yamlfactdir], facts.name) + ".yaml" File.exists?(path).should be_true # And make sure it's right newvals = YAML.load(File.read(path)) # We iterate over them, because the store might add extra values. values.each do |name, value| newvals[name].should == value end end it "should retrieve values from disk" do values = {"one" => "two", "three" => "four"} # Create the file. path = File.join(Puppet[:yamlfactdir], "node") + ".yaml" File.open(path, "w") do |f| f.print values.to_yaml end facts = Puppet::Node::Facts.get('node') facts.should be_instance_of(Puppet::Node::Facts) # We iterate over them, because the store might add extra values. values.each do |name, value| facts.values[name].should == value end end after do teardown end end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb new file mode 100755 index 000000000..e9b10c8c7 --- /dev/null +++ b/spec/unit/indirector/indirection.rb @@ -0,0 +1,57 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector' + +describe Puppet::Indirector::Indirection, " when initializing" do + it "should set the name" do + @indirection = Puppet::Indirector::Indirection.new(:myind) + @indirection.name.should == :myind + end + + it "should set any passed options" do + @indirection = Puppet::Indirector::Indirection.new(:myind, :to => :node_source) + @indirection.to.should == :node_source + end + + it "should only allow valid configuration parameters to be specified as :to targets" do + proc { Puppet::Indirector::Indirection.new(:myind, :to => :no_such_variable) }.should raise_error(ArgumentError) + end + + after do + if defined? @indirection + @indirection.delete + end + end +end + +describe Puppet::Indirector, " when managing termini" do + before do + @indirection = Puppet::Indirector::Indirection.new(:node, :to => :node_source) + end + + it "should allow the clearance of cached termini" do + terminus1 = mock 'terminus1' + terminus2 = mock 'terminus2' + Puppet::Indirector.terminus(:node, Puppet[:node_source]).stubs(:new).returns(terminus1, terminus2, ArgumentError) + @indirection.terminus.should equal(terminus1) + @indirection.class.clear_cache + @indirection.terminus.should equal(terminus2) + end + + # Make sure it caches the terminus. + it "should return the same terminus each time" do + @indirection = Puppet::Indirector::Indirection.new(:node, :to => :node_source) + @terminus = mock 'new' + Puppet::Indirector.terminus(:node, Puppet[:node_source]).expects(:new).returns(@terminus) + + @indirection.terminus.should equal(@terminus) + @indirection.terminus.should equal(@terminus) + end + + after do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache + end +end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index c4221febb..312c60951 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -1,79 +1,61 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/defaults' require 'puppet/indirector' describe Puppet::Indirector, " when managing indirections" do before do @indirector = Object.new @indirector.send(:extend, Puppet::Indirector) end - # LAK:FIXME This seems like multiple tests, but I don't really know how to test one at a time. - it "should accept specification of an indirection terminus via a configuration parameter" do - @indirector.indirects :test, :to => :node_source - Puppet[:node_source] = "test_source" - klass = mock 'terminus_class' - terminus = mock 'terminus' - klass.expects(:new).returns terminus - Puppet::Indirector.expects(:terminus).with(:test, :test_source).returns(klass) - @indirector.send(:terminus).should equal(terminus) + it "should create an indirection" do + indirection = @indirector.indirects :test, :to => :node_source + indirection.name.should == :test + indirection.to.should == :node_source end it "should not allow more than one indirection in the same object" do @indirector.indirects :test proc { @indirector.indirects :else }.should raise_error(ArgumentError) end it "should allow multiple classes to use the same indirection" do @indirector.indirects :test other = Object.new other.send(:extend, Puppet::Indirector) proc { other.indirects :test }.should_not raise_error end -end - -describe Puppet::Indirector, " when managing termini" do - before do - @indirector = Object.new - @indirector.send(:extend, Puppet::Indirector) - end it "should should autoload termini from disk" do Puppet::Indirector.expects(:instance_load).with(:test, "puppet/indirector/test") @indirector.indirects :test end + + after do + Puppet.config.clear + end end describe Puppet::Indirector, " when performing indirections" do before do @indirector = Object.new @indirector.send(:extend, Puppet::Indirector) @indirector.indirects :test, :to => :node_source # Set up a fake terminus class that will just be used to spit out # mock terminus objects. @terminus_class = mock 'terminus_class' Puppet::Indirector.stubs(:terminus).with(:test, :test_source).returns(@terminus_class) Puppet[:node_source] = "test_source" end it "should redirect http methods to the default terminus" do terminus = mock 'terminus' terminus.expects(:put).with("myargument") @terminus_class.expects(:new).returns(terminus) @indirector.put("myargument") end - - # Make sure it caches the terminus. - it "should use the same terminus for all indirections" do - terminus = mock 'terminus' - terminus.expects(:put).with("myargument") - terminus.expects(:get).with("other_argument") - @terminus_class.expects(:new).returns(terminus) - @indirector.put("myargument") - @indirector.get("other_argument") - end end diff --git a/spec/unit/indirector/node/external.rb b/spec/unit/indirector/node/external.rb new file mode 100755 index 000000000..30b2f74c2 --- /dev/null +++ b/spec/unit/indirector/node/external.rb @@ -0,0 +1,119 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'yaml' +require 'puppet/indirector' + +describe Puppet::Indirector.terminus(:node, :external), " when searching for nodes" do + require 'puppet/node' + + before do + Puppet.config[:external_nodes] = "/yay/ness" + @searcher = Puppet::Indirector.terminus(:node, :external).new + + # Set the searcher up so that we do not need to actually call the + # external script. + @searcher.meta_def(:execute) do |command| + name = command.last.chomp + result = {} + + if name =~ /a/ + result[:parameters] = {'one' => command.last + '1', 'two' => command.last + '2'} + end + + if name =~ /p/ + result['classes'] = [1,2,3].collect { |n| command.last + n.to_s } + end + + return YAML.dump(result) + end + end + + it "should throw an exception if the node_source is external but no external node command is set" do + Puppet[:external_nodes] = "none" + proc { @searcher.get("foo") }.should raise_error(ArgumentError) + end + + it "should throw an exception if the external node source is not fully qualified" do + Puppet[:external_nodes] = "mycommand" + proc { @searcher.get("foo") }.should raise_error(ArgumentError) + end + + it "should execute the command with the node name as the only argument" do + command = [Puppet[:external_nodes], "yay"] + @searcher.expects(:execute).with(command).returns("") + @searcher.get("yay") + end + + it "should return a node object" do + @searcher.get("apple").should be_instance_of(Puppet::Node) + end + + it "should set the node's name" do + @searcher.get("apple").name.should == "apple" + end + + # If we use a name that has a 'p' but no 'a', then our test generator + # will return classes but no parameters. + it "should be able to configure a node's classes" do + node = @searcher.get("plum") + node.classes.should == %w{plum1 plum2 plum3} + node.parameters.should == {} + end + + # If we use a name that has an 'a' but no 'p', then our test generator + # will return parameters but no classes. + it "should be able to configure a node's parameters" do + node = @searcher.get("guava") + node.classes.should == [] + node.parameters.should == {"one" => "guava1", "two" => "guava2"} + end + + it "should be able to configure a node's classes and parameters" do + node = @searcher.get("apple") + node.classes.should == %w{apple1 apple2 apple3} + node.parameters.should == {"one" => "apple1", "two" => "apple2"} + end + + it "should merge node facts with returned parameters" do + facts = Puppet::Node::Facts.new("apple", "three" => "four") + Puppet::Node::Facts.expects(:get).with("apple").returns(facts) + node = @searcher.get("apple") + node.parameters["three"].should == "four" + end + + it "should return nil when it cannot find the node" do + @searcher.get("honeydew").should be_nil + end + + # Make sure a nodesearch with arguments works + def test_nodesearch_external_arguments + mapper = mk_node_mapper + Puppet[:external_nodes] = "#{mapper} -s something -p somethingelse" + searcher = mk_searcher(:external) + node = nil + assert_nothing_raised do + node = searcher.nodesearch("apple") + end + assert_instance_of(SimpleNode, node, "did not create node") + end + + # A wrapper test, to make sure we're correctly calling the external search method. + def test_nodesearch_external_functional + mapper = mk_node_mapper + searcher = mk_searcher(:external) + + Puppet[:external_nodes] = mapper + + node = nil + assert_nothing_raised do + node = searcher.nodesearch("apple") + end + assert_instance_of(SimpleNode, node, "did not create node") + end + + after do + Puppet.config.clear + end +end diff --git a/spec/unit/indirector/node/ldap.rb b/spec/unit/indirector/node/ldap.rb new file mode 100755 index 000000000..c6eb45ffc --- /dev/null +++ b/spec/unit/indirector/node/ldap.rb @@ -0,0 +1,243 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'yaml' +require 'puppet/indirector' + +describe Puppet::Indirector.terminus(:node, :ldap), " when searching for nodes" do + require 'puppet/node' + + def setup + Puppet.config[:external_nodes] = "/yay/ness" + @searcher = Puppet::Indirector.terminus(:node, :ldap).new + nodetable = {} + @nodetable = nodetable + # Override the ldapsearch definition, so we don't have to actually set it up. + @searcher.meta_def(:ldapsearch) do |name| + nodetable[name] + end + end + + it "should return nil for hosts that cannot be found" do + @searcher.get("foo").should be_nil + end + + it "should return Puppet::Node instances" do + @nodetable["foo"] = [nil, %w{}, {}] + @searcher.get("foo").should be_instance_of(Puppet::Node) + end + + it "should set the node name" do + @nodetable["foo"] = [nil, %w{}, {}] + @searcher.get("foo").name.should == "foo" + end + + it "should set the classes" do + @nodetable["foo"] = [nil, %w{one two}, {}] + @searcher.get("foo").classes.should == %w{one two} + end + + it "should set the parameters" do + @nodetable["foo"] = [nil, %w{}, {"one" => "two"}] + @searcher.get("foo").parameters.should == {"one" => "two"} + end + + it "should set classes and parameters from the parent node" do + @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] + @nodetable["middle"] = [nil, %w{three four}, {"three" => "four"}] + node = @searcher.get("foo") + node.classes.sort.should == %w{one two three four}.sort + node.parameters.should == {"one" => "two", "three" => "four"} + end + + it "should prefer child parameters to parent parameters" do + @nodetable["foo"] = ["middle", %w{}, {"one" => "two"}] + @nodetable["middle"] = [nil, %w{}, {"one" => "four"}] + @searcher.get("foo").parameters["one"].should == "two" + end + + it "should recurse indefinitely through parent relationships" do + @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] + @nodetable["middle"] = ["top", %w{three four}, {"three" => "four"}] + @nodetable["top"] = [nil, %w{five six}, {"five" => "six"}] + node = @searcher.get("foo") + node.parameters.should == {"one" => "two", "three" => "four", "five" => "six"} + node.classes.sort.should == %w{one two three four five six}.sort + end + + # This can stay in the main test suite because it doesn't actually use ldapsearch, + # it just overrides the method so it behaves as though it were hitting ldap. + def test_ldap_nodesearch + + # Make sure we get nothing for nonexistent hosts + node = nil + assert_nothing_raised do + node = searcher.nodesearch("nosuchhost") + end + + assert_nil(node, "Got a node for a non-existent host") + + # Now add a base node with some classes and parameters + nodetable["base"] = [nil, %w{one two}, {"base" => "true"}] + + assert_nothing_raised do + node = searcher.nodesearch("base") + end + + assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") + assert_equal("base", node.name, "node name was not set") + + assert_equal(%w{one two}, node.classes, "node classes were not set") + assert_equal({"base" => "true"}, node.parameters, "node parameters were not set") + + # Now use a different with this as the base + nodetable["middle"] = ["base", %w{three}, {"center" => "boo"}] + assert_nothing_raised do + node = searcher.nodesearch("middle") + end + + assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") + assert_equal("middle", node.name, "node name was not set") + + assert_equal(%w{one two three}.sort, node.classes.sort, "node classes were not set correctly with a parent node") + assert_equal({"base" => "true", "center" => "boo"}, node.parameters, "node parameters were not set correctly with a parent node") + + # And one further, to make sure we fully recurse + nodetable["top"] = ["middle", %w{four five}, {"master" => "far"}] + assert_nothing_raised do + node = searcher.nodesearch("top") + end + + assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") + assert_equal("top", node.name, "node name was not set") + + assert_equal(%w{one two three four five}.sort, node.classes.sort, "node classes were not set correctly with the top node") + assert_equal({"base" => "true", "center" => "boo", "master" => "far"}, node.parameters, "node parameters were not set correctly with the top node") + end +end + +describe Puppet::Indirector.terminus(:node, :ldap), " when interacting with ldap" do + confine "LDAP is not available" => Puppet.features.ldap? + confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com" + + def ldapconnect + + @ldap = LDAP::Conn.new("ldap", 389) + @ldap.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) + @ldap.simple_bind("", "") + + return @ldap + end + + def ldaphost(name) + node = Puppet::Node.new(name) + parent = nil + found = false + @ldap.search( "ou=hosts, dc=madstop, dc=com", 2, + "(&(objectclass=puppetclient)(cn=%s))" % name + ) do |entry| + node.classes = entry.vals("puppetclass") || [] + node.parameters = entry.to_hash.inject({}) do |hash, ary| + if ary[1].length == 1 + hash[ary[0]] = ary[1].shift + else + hash[ary[0]] = ary[1] + end + hash + end + parent = node.parameters["parentnode"] + found = true + end + raise "Could not find node %s" % name unless found + + return node, parent + end + + it "should have tests" do + raise ArgumentError + end + + def test_ldapsearch + Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" + Puppet[:ldapnodes] = true + + searcher = Object.new + searcher.extend(Node.node_source(:ldap)) + + ldapconnect() + + # Make sure we get nil and nil back when we search for something missing + parent, classes, parameters = nil + assert_nothing_raised do + parent, classes, parameters = searcher.ldapsearch("nosuchhost") + end + + assert_nil(parent, "Got a parent for a non-existent host") + assert_nil(classes, "Got classes for a non-existent host") + + # Make sure we can find 'culain' in ldap + assert_nothing_raised do + parent, classes, parameters = searcher.ldapsearch("culain") + end + + node, realparent = ldaphost("culain") + assert_equal(realparent, parent, "did not get correct parent node from ldap") + assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") + assert_equal(node.parameters, parameters, "did not get correct ldap parameters from ldap") + + # Now compare when we specify the attributes to get. + Puppet[:ldapattrs] = "cn" + assert_nothing_raised do + parent, classes, parameters = searcher.ldapsearch("culain") + end + assert_equal(realparent, parent, "did not get correct parent node from ldap") + assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") + + list = %w{cn puppetclass parentnode dn} + should = node.parameters.inject({}) { |h, a| h[a[0]] = a[1] if list.include?(a[0]); h } + assert_equal(should, parameters, "did not get correct ldap parameters from ldap") + end +end + +describe Puppet::Indirector.terminus(:node, :ldap), " when connecting to ldap" do + confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") + + it "should have tests" do + raise ArgumentError + end + + def test_ldapreconnect + Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" + Puppet[:ldapnodes] = true + + searcher = Object.new + searcher.extend(Node.node_source(:ldap)) + hostname = "culain.madstop.com" + + # look for our host + assert_nothing_raised { + parent, classes = searcher.nodesearch(hostname) + } + + # Now restart ldap + system("/etc/init.d/slapd restart 2>/dev/null >/dev/null") + sleep(1) + + # and look again + assert_nothing_raised { + parent, classes = searcher.nodesearch(hostname) + } + + # Now stop ldap + system("/etc/init.d/slapd stop 2>/dev/null >/dev/null") + cleanup do + system("/etc/init.d/slapd start 2>/dev/null >/dev/null") + end + + # And make sure we actually fail here + assert_raise(Puppet::Error) { + parent, classes = searcher.nodesearch(hostname) + } + end +end diff --git a/spec/unit/indirector/node/none.rb b/spec/unit/indirector/node/none.rb new file mode 100755 index 000000000..d52d7ca83 --- /dev/null +++ b/spec/unit/indirector/node/none.rb @@ -0,0 +1,32 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'puppet/indirector' +require 'puppet/node/facts' + +describe Puppet::Indirector.terminus(:node, :none), " when searching for nodes" do + before do + Puppet.config[:node_source] = "none" + @searcher = Puppet::Indirector.terminus(:node, :none).new + end + + it "should create a node instance" do + @searcher.get("yay").should be_instance_of(Puppet::Node) + end + + it "should create a new node with the correct name" do + @searcher.get("yay").name.should == "yay" + end + + it "should merge the node's facts" do + facts = Puppet::Node::Facts.new("yay", "one" => "two", "three" => "four") + Puppet::Node::Facts.expects(:get).with("yay").returns(facts) + node = @searcher.get("yay") + node.parameters["one"].should == "two" + node.parameters["three"].should == "four" + end + + after do + Puppet.config.clear + end +end diff --git a/spec/unit/node/facts.rb b/spec/unit/node/facts.rb index c7fc65f38..c677c1d2e 100755 --- a/spec/unit/node/facts.rb +++ b/spec/unit/node/facts.rb @@ -1,25 +1,38 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/node/facts' describe Puppet::Node::Facts, " when indirecting" do before do - Puppet[:fact_store] = "test_store" - @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' - @terminus_class.expects(:new).returns(@terminus) - Puppet::Indirector.expects(:terminus).with(:facts, :test_store).returns(@terminus_class) + Puppet::Indirector.terminus(:facts, Puppet[:fact_store].intern).stubs(:new).returns(@terminus) + + # We have to clear the cache so that the facts ask for our terminus stub, + # instead of anything that might be cached. + Puppet::Indirector::Indirection.clear_cache end it "should redirect to the specified fact store for retrieval" do @terminus.expects(:get).with(:my_facts) Puppet::Node::Facts.get(:my_facts) end it "should redirect to the specified fact store for storage" do - @terminus.expects(:put).with(:my_facts) - Puppet::Node::Facts.put(:my_facts) + @terminus.expects(:post).with(:my_facts) + Puppet::Node::Facts.post(:my_facts) + end + + after do + mocha_verify + Puppet::Indirector::Indirection.clear_cache + end +end + +describe Puppet::Node::Facts, " when storing and retrieving" do + it "should add metadata to the facts" do + facts = Puppet::Node::Facts.new("me", "one" => "two", "three" => "four") + facts.values[:_timestamp].should be_instance_of(Time) end end diff --git a/spec/unit/node/node.rb b/spec/unit/node/node.rb index a6cc1e301..9342dc5ce 100755 --- a/spec/unit/node/node.rb +++ b/spec/unit/node/node.rb @@ -1,116 +1,124 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Node, " when initializing" do before do @node = Puppet::Node.new("testnode") end it "should set the node name" do @node.name.should == "testnode" end it "should default to an empty parameter hash" do @node.parameters.should == {} end it "should default to an empty class array" do @node.classes.should == [] end it "should note its creation time" do @node.time.should be_instance_of(Time) end it "should accept parameters passed in during initialization" do params = {"a" => "b"} @node = Puppet::Node.new("testing", :parameters => params) @node.parameters.should == params end it "should accept classes passed in during initialization" do classes = %w{one two} @node = Puppet::Node.new("testing", :classes => classes) @node.classes.should == classes end it "should always return classes as an array" do @node = Puppet::Node.new("testing", :classes => "myclass") @node.classes.should == ["myclass"] end it "should accept the environment during initialization" do @node = Puppet::Node.new("testing", :environment => "myenv") @node.environment.should == "myenv" end it "should accept names passed in" do @node = Puppet::Node.new("testing", :names => ["myenv"]) @node.names.should == ["myenv"] end end describe Puppet::Node, " when returning the environment" do before do @node = Puppet::Node.new("testnode") end it "should return the 'environment' fact if present and there is no explicit environment" do @node.parameters = {"environment" => "myenv"} @node.environment.should == "myenv" end it "should return the central environment if there is no environment fact nor explicit environment" do Puppet.config.expects(:[]).with(:environment).returns(:centralenv) @node.environment.should == :centralenv end it "should not use an explicit environment that is an empty string" do @node.environment == "" @node.environment.should be_nil end it "should not use an environment fact that is an empty string" do @node.parameters = {"environment" => ""} @node.environment.should be_nil end it "should not use an explicit environment that is an empty string" do Puppet.config.expects(:[]).with(:environment).returns(nil) @node.environment.should be_nil end end describe Puppet::Node, " when merging facts" do before do @node = Puppet::Node.new("testnode") + Puppet::Node::Facts.stubs(:get).with(@node.name).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) end it "should prefer parameters already set on the node over facts from the node" do @node.parameters = {"one" => "a"} - @node.fact_merge("one" => "c") + @node.fact_merge @node.parameters["one"].should == "a" end it "should add passed parameters to the parameter list" do @node.parameters = {"one" => "a"} - @node.fact_merge("two" => "b") + @node.fact_merge @node.parameters["two"].should == "b" end + + it "should accept arbitrary parameters to merge into its parameters" do + @node.parameters = {"one" => "a"} + @node.merge "two" => "three" + @node.parameters["two"].should == "three" + end end describe Puppet::Node, " when indirecting" do before do - Puppet[:node_source] = :test_source - @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' - @terminus_class.expects(:new).returns(@terminus) - Puppet::Indirector.expects(:terminus).with(:node, :test_source).returns(@terminus_class) + Puppet::Indirector.terminus(:node, Puppet[:node_source]).stubs(:new).returns(@terminus) end it "should redirect to the specified node source" do @terminus.expects(:get).with(:my_node) Puppet::Node.get(:my_node) end + + after do + Puppet::Indirector::Indirection.clear_cache + end end diff --git a/spec/unit/other/node.rb b/spec/unit/other/node.rb deleted file mode 100755 index 66d5ba9d7..000000000 --- a/spec/unit/other/node.rb +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../spec_helper' - -describe Puppet::Node, " when initializing" do - before do - @node = Puppet::Node.new("testnode") - end - - it "should set the node name" do - @node.name.should == "testnode" - end - - it "should default to an empty parameter hash" do - @node.parameters.should == {} - end - - it "should default to an empty class array" do - @node.classes.should == [] - end - - it "should note its creation time" do - @node.time.should be_instance_of(Time) - end - - it "should accept parameters passed in during initialization" do - params = {"a" => "b"} - @node = Puppet::Node.new("testing", :parameters => params) - @node.parameters.should == params - end - - it "should accept classes passed in during initialization" do - classes = %w{one two} - @node = Puppet::Node.new("testing", :classes => classes) - @node.classes.should == classes - end - - it "should always return classes as an array" do - @node = Puppet::Node.new("testing", :classes => "myclass") - @node.classes.should == ["myclass"] - end - - it "should accept the environment during initialization" do - @node = Puppet::Node.new("testing", :environment => "myenv") - @node.environment.should == "myenv" - end - - it "should accept names passed in" do - @node = Puppet::Node.new("testing", :names => ["myenv"]) - @node.names.should == ["myenv"] - end -end - -describe Puppet::Node, " when returning the environment" do - before do - @node = Puppet::Node.new("testnode") - end - - it "should return the 'environment' fact if present and there is no explicit environment" do - @node.parameters = {"environment" => "myenv"} - @node.environment.should == "myenv" - end - - it "should return the central environment if there is no environment fact nor explicit environment" do - Puppet.config.expects(:[]).with(:environment).returns(:centralenv) - @node.environment.should == :centralenv - end - - it "should not use an explicit environment that is an empty string" do - @node.environment == "" - @node.environment.should be_nil - end - - it "should not use an environment fact that is an empty string" do - @node.parameters = {"environment" => ""} - @node.environment.should be_nil - end - - it "should not use an explicit environment that is an empty string" do - Puppet.config.expects(:[]).with(:environment).returns(nil) - @node.environment.should be_nil - end -end - -describe Puppet::Node, " when merging facts" do - before do - @node = Puppet::Node.new("testnode") - end - - it "should prefer parameters already set on the node over facts from the node" do - @node.parameters = {"one" => "a"} - @node.fact_merge("one" => "c") - @node.parameters["one"].should == "a" - end - - it "should add passed parameters to the parameter list" do - @node.parameters = {"one" => "a"} - @node.fact_merge("two" => "b") - @node.parameters["two"].should == "b" - end -end diff --git a/spec/unit/util/config.rb b/spec/unit/util/config.rb index 348a54893..28ccb04d7 100755 --- a/spec/unit/util/config.rb +++ b/spec/unit/util/config.rb @@ -1,408 +1,426 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Util::Config, " when specifying defaults" do before do @config = Puppet::Util::Config.new end it "should start with no defined parameters" do @config.params.length.should == 0 end it "should allow specification of default values associated with a section as an array" do @config.setdefaults(:section, :myvalue => ["defaultval", "my description"]) end it "should not allow duplicate parameter specifications" do @config.setdefaults(:section, :myvalue => ["a", "b"]) lambda { @config.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError) end it "should allow specification of default values associated with a section as a hash" do @config.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) end it "should consider defined parameters to be valid" do @config.setdefaults(:section, :myvalue => ["defaultval", "my description"]) @config.valid?(:myvalue).should be_true end it "should require a description when defaults are specified with an array" do lambda { @config.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError) end it "should require a description when defaults are specified with a hash" do lambda { @config.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) end it "should support specifying owner, group, and mode when specifying files" do @config.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "blah", :mode => "boo", :group => "yay", :desc => "whatever"}) end it "should support specifying a short name" do @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) end it "should fail when short names conflict" do @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) lambda { @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) end end describe Puppet::Util::Config, " when setting values" do before do @config = Puppet::Util::Config.new @config.setdefaults :main, :myval => ["val", "desc"] @config.setdefaults :main, :bool => [true, "desc"] end it "should provide a method for setting values from other objects" do @config[:myval] = "something else" @config[:myval].should == "something else" end it "should support a getopt-specific mechanism for setting values" do @config.handlearg("--myval", "newval") @config[:myval].should == "newval" end it "should support a getopt-specific mechanism for turning booleans off" do @config.handlearg("--no-bool") @config[:bool].should == false end it "should support a getopt-specific mechanism for turning booleans on" do # Turn it off first @config[:bool] = false @config.handlearg("--bool") @config[:bool].should == true end it "should clear the cache when setting getopt-specific values" do @config.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] @config[:two].should == "whah yay" @config.handlearg("--one", "else") @config[:two].should == "else yay" end it "should not clear other values when setting getopt-specific values" do @config[:myval] = "yay" @config.handlearg("--no-bool") @config[:myval].should == "yay" end it "should call passed blocks when values are set" do values = [] @config.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) values.should == [] @config[:hooker] = "something" values.should == %w{something} end it "should munge values using the element-specific methods" do @config[:bool] = "false" @config[:bool].should == false end it "should prefer cli values to values set in Ruby code" do @config.handlearg("--myval", "cliarg") @config[:myval] = "memarg" @config[:myval].should == "cliarg" end end describe Puppet::Util::Config, " when returning values" do before do @config = Puppet::Util::Config.new @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"] end it "should provide a mechanism for returning set values" do @config[:one] = "other" @config[:one].should == "other" end it "should interpolate default values for other parameters into returned parameter values" do @config[:one].should == "ONE" @config[:two].should == "ONE TWO" @config[:three].should == "ONE ONE TWO THREE" end it "should interpolate default values that themselves need to be interpolated" do @config[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" end it "should interpolate set values for other parameters into returned parameter values" do @config[:one] = "on3" @config[:two] = "$one tw0" @config[:three] = "$one $two thr33" @config[:four] = "$one $two $three f0ur" @config[:one].should == "on3" @config[:two].should == "on3 tw0" @config[:three].should == "on3 on3 tw0 thr33" @config[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" end it "should not cache interpolated values such that stale information is returned" do @config[:two].should == "ONE TWO" @config[:one] = "one" @config[:two].should == "one TWO" end it "should not cache values such that information from one environment is returned for another environment" do text = "[env1]\none = oneval\n[env2]\none = twoval\n" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") @config.stubs(:read_file).with(file).returns(text) @config.parse(file) @config.value(:one, "env1").should == "oneval" @config.value(:one, "env2").should == "twoval" end it "should have a name determined by the 'name' parameter" do @config.setdefaults(:whatever, :name => ["something", "yayness"]) @config.name.should == :something @config[:name] = :other @config.name.should == :other end end describe Puppet::Util::Config, " when choosing which value to return" do before do @config = Puppet::Util::Config.new @config.setdefaults :section, :one => ["ONE", "a"], :name => ["myname", "w"] end it "should return default values if no values have been set" do @config[:one].should == "ONE" end it "should return values set on the cli before values set in the configuration file" do text = "[main]\none = fileval\n" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") @config.stubs(:parse_file).returns(text) @config.handlearg("--one", "clival") @config.parse(file) @config[:one].should == "clival" end it "should return values set on the cli before values set in Ruby" do @config[:one] = "rubyval" @config.handlearg("--one", "clival") @config[:one].should == "clival" end it "should return values set in the executable-specific section before values set in the main section" do text = "[main]\none = mainval\n[myname]\none = nameval\n" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") @config.stubs(:read_file).with(file).returns(text) @config.parse(file) @config[:one].should == "nameval" end it "should not return values outside of its search path" do text = "[other]\none = oval\n" file = "/some/file" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") @config.stubs(:read_file).with(file).returns(text) @config.parse(file) @config[:one].should == "ONE" end it "should return values in a specified environment" do text = "[env]\none = envval\n" file = "/some/file" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") @config.stubs(:read_file).with(file).returns(text) @config.parse(file) @config.value(:one, "env").should == "envval" end it "should return values in a specified environment before values in the main or name sections" do text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" file = "/some/file" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") @config.stubs(:read_file).with(file).returns(text) @config.parse(file) @config.value(:one, "env").should == "envval" end end describe Puppet::Util::Config, " when parsing its configuration" do before do @config = Puppet::Util::Config.new @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] end it "should return values set in the configuration file" do text = "[main] one = fileval " file = "/some/file" @config.expects(:read_file).with(file).returns(text) @config.parse(file) @config[:one].should == "fileval" end #484 - this should probably be in the regression area it "should not throw an exception on unknown parameters" do text = "[main]\nnosuchparam = mval\n" file = "/some/file" @config.expects(:read_file).with(file).returns(text) lambda { @config.parse(file) }.should_not raise_error end it "should support an old parse method when per-executable configuration files still exist" do # I'm not going to bother testing this method. @config.should respond_to(:old_parse) end it "should convert booleans in the configuration file into Ruby booleans" do text = "[main] one = true two = false " file = "/some/file" @config.expects(:read_file).with(file).returns(text) @config.parse(file) @config[:one].should == true @config[:two].should == false end it "should convert integers in the configuration file into Ruby Integers" do text = "[main] one = 65 " file = "/some/file" @config.expects(:read_file).with(file).returns(text) @config.parse(file) @config[:one].should == 65 end it "should support specifying file all metadata (owner, group, mode) in the configuration file" do @config.setdefaults :section, :myfile => ["/my/file", "a"] text = "[main] myfile = /other/file {owner = luke, group = luke, mode = 644} " file = "/some/file" @config.expects(:read_file).with(file).returns(text) @config.parse(file) @config[:myfile].should == "/other/file" @config.metadata(:myfile).should == {:owner => "luke", :group => "luke", :mode => "644"} end it "should support specifying file a single piece of metadata (owner, group, or mode) in the configuration file" do @config.setdefaults :section, :myfile => ["/my/file", "a"] text = "[main] myfile = /other/file {owner = luke} " file = "/some/file" @config.expects(:read_file).with(file).returns(text) @config.parse(file) @config[:myfile].should == "/other/file" @config.metadata(:myfile).should == {:owner => "luke"} end end describe Puppet::Util::Config, " when reparsing its configuration" do before do @config = Puppet::Util::Config.new @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] end it "should replace in-memory values with on-file values" do # Init the value text = "[main]\none = disk-init\n" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/test/file") @config[:one] = "init" @config.file = file # Now replace the value text = "[main]\none = disk-replace\n" # This is kinda ridiculous - the reason it parses twice is that # it goes to parse again when we ask for the value, because the # mock always says it should get reparsed. @config.expects(:read_file).with(file).returns(text).times(2) @config.reparse @config[:one].should == "disk-replace" end it "should retain parameters set by cli when configuration files are reparsed" do @config.handlearg("--one", "clival") text = "[main]\none = on-disk\n" file = mock 'file' file.stubs(:file).returns("/test/file") @config.stubs(:read_file).with(file).returns(text) @config.parse(file) @config[:one].should == "clival" end it "should remove in-memory values that are no longer set in the file" do # Init the value text = "[main]\none = disk-init\n" file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/test/file") @config.expects(:read_file).with(file).returns(text) @config.parse(file) @config[:one].should == "disk-init" # Now replace the value text = "[main]\ntwo = disk-replace\n" @config.expects(:read_file).with(file).returns(text) @config.parse(file) #@config.reparse # The originally-overridden value should be replaced with the default @config[:one].should == "ONE" # and we should now have the new value in memory @config[:two].should == "disk-replace" end end -#describe Puppet::Util::Config, " when being used to manage the host machine" do -# it "should provide a method that writes files with the correct modes" -# -# it "should provide a method that creates directories with the correct modes" -# -# it "should provide a method to declare what directories should exist" -# -# it "should provide a method to trigger enforcing of file modes on existing files and directories" -# -# it "should provide a method to convert the file mode enforcement into a Puppet manifest" -# -# it "should provide an option to create needed users and groups" -# -# it "should provide a method to print out the current configuration" -# -# it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" -# -# it "should not attempt to manage files within /dev" -#end +describe Puppet::Util::Config, " when being used to manage the host machine" do + it "should provide a method that writes files with the correct modes" do + pending "Not converted from test/unit yet" + end + + it "should provide a method that creates directories with the correct modes" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to declare what directories should exist" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to trigger enforcing of file modes on existing files and directories" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to convert the file mode enforcement into a Puppet manifest" do + pending "Not converted from test/unit yet" + end + + it "should provide an option to create needed users and groups" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to print out the current configuration" do + pending "Not converted from test/unit yet" + end + + it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do + pending "Not converted from test/unit yet" + end + + it "should not attempt to manage files within /dev" do + pending "Not converted from test/unit yet" + end +end diff --git a/test/language/snippets.rb b/test/language/snippets.rb index 2c74543e7..ff8a09881 100755 --- a/test/language/snippets.rb +++ b/test/language/snippets.rb @@ -1,519 +1,520 @@ #!/usr/bin/env ruby $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/network/client' require 'puppet/network/handler' require 'puppettest' class TestSnippets < Test::Unit::TestCase include PuppetTest def setup super @file = Puppet::Type.type(:file) end def self.snippetdir PuppetTest.datadir "snippets" end def assert_file(path, msg = nil) unless file = @file[path] msg ||= "Could not find file %s" % path raise msg end end def assert_mode_equal(mode, path) unless file = @file[path] raise "Could not find file %s" % path end unless mode == file.should(:mode) raise "Mode for %s is incorrect: %o vs %o" % [path, mode, file.should(:mode)] end end def snippet(name) File.join(self.class.snippetdir, name) end def file2ast(file) parser = Puppet::Parser::Parser.new() parser.file = file ast = parser.parse return ast end def snippet2ast(text) parser = Puppet::Parser::Parser.new() parser.string = text ast = parser.parse return ast end def client args = { :Listen => false } Puppet::Network::Client.new(args) end def ast2scope(ast) interp = Puppet::Parser::Interpreter.new( :ast => ast, :client => client() ) scope = Puppet::Parser::Scope.new() ast.evaluate(scope) return scope end def scope2objs(scope) objs = scope.to_trans end def snippet2scope(snippet) ast = snippet2ast(snippet) scope = ast2scope(ast) end def snippet2objs(snippet) ast = snippet2ast(snippet) scope = ast2scope(ast) objs = scope2objs(scope) end def properties(type) properties = type.validproperties end def metaparams(type) mparams = [] Puppet::Type.eachmetaparam { |param| mparams.push param } mparams end def params(type) params = [] type.parameters.each { |name,property| params.push name } params end def randthing(thing,type) list = self.send(thing,type) list[rand(list.length)] end def randeach(type) [:properties, :metaparams, :params].collect { |thing| randthing(thing,type) } end @@snippets = { true => [ %{File { mode => 755 }} ], } def disabled_test_defaults Puppet::Type.eachtype { |type| next if type.name == :puppet or type.name == :component rands = randeach(type) name = type.name.to_s.capitalize [0..1, 0..2].each { |range| params = rands[range] paramstr = params.collect { |param| "%s => fake" % param }.join(", ") str = "%s { %s }" % [name, paramstr] scope = nil assert_nothing_raised { scope = snippet2scope(str) } defaults = nil assert_nothing_raised { defaults = scope.lookupdefaults(name) } p defaults params.each { |param| puts "%s => '%s'" % [name,param] assert(defaults.include?(param)) } } } end # this is here in case no tests get defined; otherwise we get a warning def test_nothing end def snippet_filecreate %w{a b c d}.each { |letter| path = "/tmp/create%stest" % letter assert_file(path) if %w{a b}.include?(letter) assert_mode_equal(0755, path) end } end def snippet_simpledefaults path = "/tmp/defaulttest" assert_file(path) assert_mode_equal(0755, path) end def snippet_simpleselector files = %w{a b c d}.collect { |letter| path = "/tmp/snippetselect%stest" % letter assert_file(path) assert_mode_equal(0755, path) } end def snippet_classpathtest path = "/tmp/classtest" file = @file[path] assert(file, "did not create file %s" % path) assert_nothing_raised { assert_equal( "//testing/component[componentname]/File[/tmp/classtest]", file.path) } end def snippet_argumentdefaults path1 = "/tmp/argumenttest1" path2 = "/tmp/argumenttest2" file1 = @file[path1] file2 = @file[path2] assert_file(path1) assert_mode_equal(0755, path1) assert_file(path2) assert_mode_equal(0644, path2) end def snippet_casestatement paths = %w{ /tmp/existsfile /tmp/existsfile2 /tmp/existsfile3 /tmp/existsfile4 /tmp/existsfile5 } paths.each { |path| file = @file[path] assert(file, "File %s is missing" % path) assert_mode_equal(0755, path) } end def snippet_implicititeration paths = %w{a b c d e f g h}.collect { |l| "/tmp/iteration%stest" % l } paths.each { |path| file = @file[path] assert_file(path) assert_mode_equal(0755, path) } end def snippet_multipleinstances paths = %w{a b c}.collect { |l| "/tmp/multipleinstances%s" % l } paths.each { |path| assert_file(path) assert_mode_equal(0755, path) } end def snippet_namevartest file = "/tmp/testfiletest" dir = "/tmp/testdirtest" assert_file(file) assert_file(dir) assert_equal(:directory, @file[dir].should(:ensure), "Directory is not set to be a directory") end def snippet_scopetest file = "/tmp/scopetest" assert_file(file) assert_mode_equal(0755, file) end def snippet_failmissingexecpath file = "/tmp/exectesting1" execfile = "/tmp/execdisttesting" assert_file(file) assert_nil(Puppet::Type.type(:exec)["exectest"], "invalid exec was created") end def snippet_selectorvalues nums = %w{1 2 3 4 5} files = nums.collect { |n| "/tmp/selectorvalues%s" % n } files.each { |f| assert_file(f) assert_mode_equal(0755, f) } end def snippet_singleselector nums = %w{1 2 3} files = nums.collect { |n| "/tmp/singleselector%s" % n } files.each { |f| assert_file(f) assert_mode_equal(0755, f) } end def snippet_falsevalues file = "/tmp/falsevaluesfalse" assert_file(file) end def disabled_snippet_classargtest [1,2].each { |num| file = "/tmp/classargtest%s" % num assert_file(file) assert_mode_equal(0755, file) } end def snippet_classheirarchy [1,2,3].each { |num| file = "/tmp/classheir%s" % num assert_file(file) assert_mode_equal(0755, file) } end def snippet_singleary [1,2,3,4].each { |num| file = "/tmp/singleary%s" % num assert_file(file) } end def snippet_classincludes [1,2,3].each { |num| file = "/tmp/classincludes%s" % num assert_file(file) assert_mode_equal(0755, file) } end def snippet_componentmetaparams ["/tmp/component1", "/tmp/component2"].each { |file| assert_file(file) } end def snippet_aliastest %w{/tmp/aliastest /tmp/aliastest2 /tmp/aliastest3}.each { |file| assert_file(file) } end def snippet_singlequote { 1 => 'a $quote', 2 => 'some "\yayness\"' }.each { |count, str| path = "/tmp/singlequote%s" % count assert_file(path) assert_equal(str, @file[path].should(:content)) } end # There's no way to actually retrieve the list of classes from the # transaction. def snippet_tag end # Make sure that set tags are correctly in place, yo. def snippet_tagged tags = {"testing" => true, "yayness" => false, "both" => false, "bothtrue" => true, "define" => true} tags.each do |tag, retval| assert_file("/tmp/tagged#{tag}#{retval.to_s}") end end def snippet_defineoverrides file = "/tmp/defineoverrides1" assert_file(file) assert_mode_equal(0755, file) end def snippet_deepclassheirarchy 5.times { |i| i += 1 file = "/tmp/deepclassheir%s" % i assert_file(file) } end def snippet_emptyclass # There's nothing to check other than that it works end def snippet_emptyexec assert(Puppet::Type.type(:exec)["touch /tmp/emptyexectest"], "Did not create exec") end def snippet_multisubs path = "/tmp/multisubtest" assert_file(path) file = @file[path] assert_equal("sub2", file.should(:content), "sub2 did not override content") assert_mode_equal(0755, path) end def snippet_collection assert_file("/tmp/colltest1") assert_nil(@file["/tmp/colltest2"], "Incorrectly collected file") end def snippet_virtualresources %w{1 2 3 4}.each do |num| assert_file("/tmp/virtualtest#{num}") end end def snippet_componentrequire %w{1 2}.each do |num| assert_file("/tmp/testing_component_requires#{num}", "#{num} does not exist") end end def snippet_realize_defined_types assert_file("/tmp/realize_defined_test1") assert_file("/tmp/realize_defined_test2") end def snippet_fqparents assert_file("/tmp/fqparent1", "Did not make file from parent class") assert_file("/tmp/fqparent2", "Did not make file from subclass") end def snippet_fqdefinition assert_file("/tmp/fqdefinition", "Did not make file from fully-qualified definition") end def snippet_subclass_name_duplication assert_file("/tmp/subclass_name_duplication1", "Did not make first file from duplicate subclass names") assert_file("/tmp/subclass_name_duplication2", "Did not make second file from duplicate subclass names") end # Iterate across each of the snippets and create a test. Dir.entries(snippetdir).sort.each { |file| next if file =~ /^\./ mname = "snippet_" + file.sub(/\.pp$/, '') if self.method_defined?(mname) #eval("alias %s %s" % [testname, mname]) testname = ("test_" + mname).intern self.send(:define_method, testname) { facts = { "hostname" => "testhost", "domain" => "domain.com", "ipaddress" => "127.0.0.1", "fqdn" => "testhost.domain.com" } Facter.stubs(:each) facts.each do |name, value| Facter.stubs(:value).with(name).returns(value) end # first parse the file server = Puppet::Network::Handler.master.new( :Manifest => snippet(file), :Local => true ) - server.send(:fact_handler).stubs(:set) - server.send(:fact_handler).stubs(:get).returns(facts) + facts = Puppet::Node::Facts.new("testhost", facts) + Puppet::Node::Facts.stubs(:post) + Puppet::Node::Facts.stubs(:get).returns(facts) client = Puppet::Network::Client.master.new( :Master => server, :Cache => false ) - client.class.stubs(:facts).returns(facts) + client.class.stubs(:facts).returns(facts.values) assert(client.local) assert_nothing_raised { client.getconfig() } client = Puppet::Network::Client.master.new( :Master => server, :Cache => false ) assert(client.local) # Now do it again Puppet::Type.allclear assert_nothing_raised { client.getconfig() } Puppet::Type.eachtype { |type| type.each { |obj| # don't worry about this for now #unless obj.name == "puppet[top]" or # obj.is_a?(Puppet.type(:schedule)) # assert(obj.parent, "%s has no parent" % obj.name) #end assert(obj.name) } } assert_nothing_raised { self.send(mname) } client.clear } mname = mname.intern end } end diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index 45c5b2ed9..06b85f147 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -1,313 +1,314 @@ # Add .../test/lib $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) # Add .../lib $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../lib'))) require 'puppet' require 'mocha' # Only load the test/unit class if we're not in the spec directory. # Else we get the bogus 'no tests, no failures' message. unless Dir.getwd =~ /spec/ require 'test/unit' end # Yay; hackish but it works if ARGV.include?("-d") ARGV.delete("-d") $console = true end module PuppetTest # Munge cli arguments, so we can enable debugging if we want # and so we can run just specific methods. def self.munge_argv require 'getoptlong' result = GetoptLong.new( [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--resolve", "-r", GetoptLong::REQUIRED_ARGUMENT ], [ "-n", GetoptLong::REQUIRED_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ] ) usage = "USAGE: TESTOPTS='[-n -n ...] [-d]' rake [target] [target] ..." opts = [] dir = method = nil result.each { |opt,arg| case opt when "--resolve" dir, method = arg.split(",") when "--debug" $puppet_debug = true Puppet::Util::Log.level = :debug Puppet::Util::Log.newdestination(:console) when "--help" puts usage exit else opts << opt << arg end } suites = nil args = ARGV.dup # Reset the options, so the test suite can deal with them (this is # what makes things like '-n' work). opts.each { |o| ARGV << o } return args end # Find the root of the Puppet tree; this is not the test directory, but # the parent of that dir. def basedir(*list) unless defined? @@basedir Dir.chdir(File.dirname(__FILE__)) do @@basedir = File.dirname(File.dirname(Dir.getwd)) end end if list.empty? @@basedir else File.join(@@basedir, *list) end end def cleanup(&block) @@cleaners << block end def datadir(*list) File.join(basedir, "test", "data", *list) end def exampledir(*args) unless defined? @@exampledir @@exampledir = File.join(basedir, "examples") end if args.empty? return @@exampledir else return File.join(@@exampledir, *args) end end module_function :basedir, :datadir, :exampledir # Rails clobbers RUBYLIB, thanks def libsetup curlibs = ENV["RUBYLIB"].split(":") $:.reject do |dir| dir =~ /^\/usr/ end.each do |dir| unless curlibs.include?(dir) curlibs << dir end end ENV["RUBYLIB"] = curlibs.join(":") end def logcollector collector = [] Puppet::Util::Log.newdestination(collector) cleanup do Puppet::Util::Log.close(collector) end collector end def rake? $0 =~ /test_loader/ end # Redirect stdout and stderr def redirect @stderr = tempfile @stdout = tempfile $stderr = File.open(@stderr, "w") $stdout = File.open(@stdout, "w") cleanup do $stderr = STDERR $stdout = STDOUT end end def setup @memoryatstart = Puppet::Util.memory if defined? @@testcount @@testcount += 1 else @@testcount = 0 end @configpath = File.join(tmpdir, "configdir" + @@testcount.to_s + "/" ) unless defined? $user and $group $user = nonrootuser().uid.to_s $group = nonrootgroup().gid.to_s end Puppet.config.clear Puppet[:user] = $user Puppet[:group] = $group Puppet[:confdir] = @configpath Puppet[:vardir] = @configpath unless File.exists?(@configpath) Dir.mkdir(@configpath) end @@tmpfiles = [@configpath, tmpdir()] @@tmppids = [] @@cleaners = [] @logs = [] # If we're running under rake, then disable debugging and such. #if rake? or ! Puppet[:debug] if defined?($puppet_debug) or ! rake? if textmate? Puppet[:color] = false end Puppet::Util::Log.newdestination(@logs) if defined? $console Puppet.info @method_name Puppet::Util::Log.newdestination(:console) Puppet[:trace] = true end Puppet::Util::Log.level = :debug #$VERBOSE = 1 else Puppet::Util::Log.close Puppet::Util::Log.newdestination(@logs) Puppet[:httplog] = tempfile() end Puppet[:ignoreschedules] = true end def tempfile if defined? @@tmpfilenum @@tmpfilenum += 1 else @@tmpfilenum = 1 end f = File.join(self.tmpdir(), "tempfile_" + @@tmpfilenum.to_s) @@tmpfiles << f return f end def textmate? if ENV["TM_FILENAME"] return true else return false end end def tstdir dir = tempfile() Dir.mkdir(dir) return dir end def tmpdir unless defined? @tmpdir and @tmpdir @tmpdir = case Facter["operatingsystem"].value when "Darwin": "/private/tmp" when "SunOS": "/var/tmp" else "/tmp" end @tmpdir = File.join(@tmpdir, "puppettesting" + Process.pid.to_s) unless File.exists?(@tmpdir) FileUtils.mkdir_p(@tmpdir) File.chmod(01777, @tmpdir) end end @tmpdir end def teardown @@cleaners.each { |cleaner| cleaner.call() } @@tmpfiles.each { |file| unless file =~ /tmp/ puts "Not deleting tmpfile %s" % file next end if FileTest.exists?(file) system("chmod -R 755 %s" % file) system("rm -rf %s" % file) end } @@tmpfiles.clear @@tmppids.each { |pid| %x{kill -INT #{pid} 2>/dev/null} } @@tmppids.clear Puppet::Type.allclear Puppet::Util::Storage.clear Puppet.clear + Puppet.config.clear @memoryatend = Puppet::Util.memory diff = @memoryatend - @memoryatstart if diff > 1000 Puppet.info "%s#%s memory growth (%s to %s): %s" % [self.class, @method_name, @memoryatstart, @memoryatend, diff] end # reset all of the logs Puppet::Util::Log.close @logs.clear # Just in case there are processes waiting to die... require 'timeout' begin Timeout::timeout(5) do Process.waitall end rescue Timeout::Error # just move on end mocha_verify if File.stat("/dev/null").mode & 007777 != 0666 File.open("/tmp/nullfailure", "w") { |f| f.puts self.class } exit(74) end end def logstore @logs = [] Puppet::Util::Log.newdestination(@logs) end end require 'puppettest/support' require 'puppettest/filetesting' require 'puppettest/fakes' require 'puppettest/exetest' require 'puppettest/parsertesting' require 'puppettest/servertest' require 'puppettest/testcase' # $Id$ diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index eef0cd8bc..c4bd7dc2b 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -1,408 +1,407 @@ require 'puppettest' require 'puppet/rails' module PuppetTest::ParserTesting include PuppetTest AST = Puppet::Parser::AST Compile = Puppet::Parser::Compile # A fake class that we can use for testing evaluation. class FakeAST attr_writer :evaluate def evaluated? defined? @evaluated and @evaluated end def evaluate(*args) @evaluated = true return @evaluate end def initialize(val = nil) if val @evaluate = val end end def reset @evaluated = nil end def safeevaluate(*args) evaluate() end end def astarray(*args) AST::ASTArray.new( :children => args ) end def mkcompile(parser = nil) - require 'puppet/network/handler/node' parser ||= mkparser node = mknode return Compile.new(node, parser) end def mknode(name = nil) require 'puppet/node' name ||= "nodename" Puppet::Network::Handler.handler(:node) Puppet::Node.new(name) end def mkinterp(args = {}) args[:Code] ||= "" unless args.include?(:Manifest) args[:Local] ||= true Puppet::Parser::Interpreter.new(args) end def mkparser Puppet::Parser::Parser.new() end def mkscope(hash = {}) hash[:parser] ||= mkparser compile ||= mkcompile(hash[:parser]) compile.topscope.source = (hash[:parser].findclass("", "") || hash[:parser].newclass("")) unless compile.topscope.source raise "Could not find source for scope" end # Make the 'main' stuff compile.send(:evaluate_main) compile.topscope end def classobj(name, hash = {}) hash[:file] ||= __FILE__ hash[:line] ||= __LINE__ hash[:type] ||= name AST::HostClass.new(hash) end def tagobj(*names) args = {} newnames = names.collect do |name| if name.is_a? AST name else nameobj(name) end end args[:type] = astarray(*newnames) assert_nothing_raised("Could not create tag %s" % names.inspect) { return AST::Tag.new(args) } end def resourcedef(type, title, params) unless title.is_a?(AST) title = stringobj(title) end assert_nothing_raised("Could not create %s %s" % [type, title]) { return AST::Resource.new( :file => __FILE__, :line => __LINE__, :title => title, :type => type, :params => resourceinst(params) ) } end def virt_resourcedef(*args) res = resourcedef(*args) res.virtual = true res end def resourceoverride(type, title, params) assert_nothing_raised("Could not create %s %s" % [type, name]) { return AST::ResourceOverride.new( :file => __FILE__, :line => __LINE__, :object => resourceref(type, title), :type => type, :params => resourceinst(params) ) } end def resourceref(type, title) assert_nothing_raised("Could not create %s %s" % [type, title]) { return AST::ResourceReference.new( :file => __FILE__, :line => __LINE__, :type => type, :title => stringobj(title) ) } end def fileobj(path, hash = {"owner" => "root"}) assert_nothing_raised("Could not create file %s" % path) { return resourcedef("file", path, hash) } end def nameobj(name) assert_nothing_raised("Could not create name %s" % name) { return AST::Name.new( :file => tempfile(), :line => rand(100), :value => name ) } end def typeobj(name) assert_nothing_raised("Could not create type %s" % name) { return AST::Type.new( :file => tempfile(), :line => rand(100), :value => name ) } end def nodedef(name) assert_nothing_raised("Could not create node %s" % name) { return AST::NodeDef.new( :file => tempfile(), :line => rand(100), :names => nameobj(name), :code => AST::ASTArray.new( :children => [ varobj("%svar" % name, "%svalue" % name), fileobj("/%s" % name) ] ) ) } end def resourceinst(hash) assert_nothing_raised("Could not create resource instance") { params = hash.collect { |param, value| resourceparam(param, value) } return AST::ResourceInstance.new( :file => tempfile(), :line => rand(100), :children => params ) } end def resourceparam(param, value) # Allow them to pass non-strings in if value.is_a?(String) value = stringobj(value) end assert_nothing_raised("Could not create param %s" % param) { return AST::ResourceParam.new( :file => tempfile(), :line => rand(100), :param => param, :value => value ) } end def stringobj(value) AST::String.new( :file => tempfile(), :line => rand(100), :value => value ) end def varobj(name, value) unless value.is_a? AST value = stringobj(value) end assert_nothing_raised("Could not create %s code" % name) { return AST::VarDef.new( :file => tempfile(), :line => rand(100), :name => nameobj(name), :value => value ) } end def varref(name) assert_nothing_raised("Could not create %s variable" % name) { return AST::Variable.new( :file => __FILE__, :line => __LINE__, :value => name ) } end def argobj(name, value) assert_nothing_raised("Could not create %s compargument" % name) { return AST::CompArgument.new( :children => [nameobj(name), stringobj(value)] ) } end def defaultobj(type, params) pary = [] params.each { |p,v| pary << AST::ResourceParam.new( :file => __FILE__, :line => __LINE__, :param => p, :value => stringobj(v) ) } past = AST::ASTArray.new( :file => __FILE__, :line => __LINE__, :children => pary ) assert_nothing_raised("Could not create defaults for %s" % type) { return AST::ResourceDefaults.new( :file => __FILE__, :line => __LINE__, :type => type, :params => past ) } end def taggedobj(name, ftype = :statement) functionobj("tagged", name, ftype) end def functionobj(function, name, ftype = :statement) func = nil assert_nothing_raised do func = Puppet::Parser::AST::Function.new( :name => function, :ftype => ftype, :arguments => AST::ASTArray.new( :children => [nameobj(name)] ) ) end return func end # This assumes no nodes def assert_creates(manifest, *files) interp = nil assert_nothing_raised { interp = Puppet::Parser::Interpreter.new( :Manifest => manifest, :UseNodes => false ) } config = nil assert_nothing_raised { config = interp.compile(mknode) } comp = nil assert_nothing_raised { comp = config.extract.to_type } assert_apply(comp) files.each do |file| assert(FileTest.exists?(file), "Did not create %s" % file) end end def mk_transobject(file = "/etc/passwd") obj = nil assert_nothing_raised { obj = Puppet::TransObject.new("file", file) obj["owner"] = "root" obj["mode"] = "644" } return obj end def mk_transbucket(*resources) bucket = nil assert_nothing_raised { bucket = Puppet::TransBucket.new bucket.name = "yayname" bucket.type = "yaytype" } resources.each { |o| bucket << o } return bucket end # Make a tree of resources, yielding if desired def mk_transtree(depth = 4, width = 2) top = nil assert_nothing_raised { top = Puppet::TransBucket.new top.name = "top" top.type = "bucket" } bucket = top file = tempfile() depth.times do |i| resources = [] width.times do |j| path = tempfile + i.to_s obj = Puppet::TransObject.new("file", path) obj["owner"] = "root" obj["mode"] = "644" # Yield, if they want if block_given? yield(obj, i, j) end resources << obj end newbucket = mk_transbucket(*resources) bucket.push newbucket bucket = newbucket end return top end # Take a list of AST resources, evaluate them, and return the results def assert_evaluate(children) top = nil assert_nothing_raised("Could not create top object") { top = AST::ASTArray.new( :children => children ) } trans = nil scope = nil assert_nothing_raised { scope = Puppet::Parser::Scope.new() trans = scope.evaluate(:ast => top) } return trans end end diff --git a/test/lib/puppettest/runnable_test.rb b/test/lib/puppettest/runnable_test.rb new file mode 100644 index 000000000..e4b0f9033 --- /dev/null +++ b/test/lib/puppettest/runnable_test.rb @@ -0,0 +1,30 @@ +# Manage whether a test is runnable. +module PuppetTest + module RunnableTest + # Confine this test based on specified criteria. The keys of the + # hash should be the message to use if the test is not suitable, + # and the values should be either 'true' or 'false'; true values + # mean the test is suitable. + def confine(hash) + @confines ||= {} + hash.each do |message, result| + @confines[message] = result + end + end + + # Evaluate all of our tests to see if any of them are false + # and thus whether this test is considered not runnable. + def runnable? + @messages ||= [] + return false unless @messages.empty? + return true unless defined? @confines + @confines.find_all do |message, result| + ! result + end.each do |message, result| + @messages << message + end + + return @messages.empty? + end + end +end diff --git a/test/lib/puppettest/testcase.rb b/test/lib/puppettest/testcase.rb index cfedeee26..15c835854 100644 --- a/test/lib/puppettest/testcase.rb +++ b/test/lib/puppettest/testcase.rb @@ -1,48 +1,29 @@ #!/usr/bin/env ruby # # Created by Luke A. Kanies on 2007-03-05. # Copyright (c) 2007. All rights reserved. require 'puppettest' +require 'puppettest/runnable_test' class PuppetTest::TestCase < Test::Unit::TestCase include PuppetTest - def self.confine(hash) - @confines ||= {} - hash.each do |message, result| - @confines[message] = result - end - end - - def self.runnable? - @messages ||= [] - return false unless @messages.empty? - return true unless defined? @confines - @confines.find_all do |message, result| - ! result - end.each do |message, result| - @messages << message - end - - return @messages.empty? - end + extend PuppetTest::RunnableTest def self.suite # Always skip this parent class. It'd be nice if there were a # "supported" way to do this. if self == PuppetTest::TestCase suite = Test::Unit::TestSuite.new(name) return suite elsif self.runnable? return super else if defined? $console puts "Skipping %s: %s" % [name, @messages.join(", ")] end suite = Test::Unit::TestSuite.new(name) return suite end end end - -# $Id$ diff --git a/test/network/handler/configuration.rb b/test/network/handler/configuration.rb index 0964a4c5e..29a393769 100755 --- a/test/network/handler/configuration.rb +++ b/test/network/handler/configuration.rb @@ -1,173 +1,173 @@ #!/usr/bin/env ruby $:.unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppet/network/handler/configuration' class TestHandlerConfiguration < Test::Unit::TestCase include PuppetTest Config = Puppet::Network::Handler.handler(:configuration) # Check all of the setup stuff. def test_initialize config = nil assert_nothing_raised("Could not create local config") do config = Config.new(:Local => true) end assert(config.local?, "Config is not considered local after being started that way") end # Test creation/returning of the interpreter def test_interpreter config = Config.new # First test the defaults args = {} config.instance_variable_set("@options", args) config.expects(:create_interpreter).with(args).returns(:interp) assert_equal(:interp, config.send(:interpreter), "Did not return the interpreter") # Now run it again and make sure we get the same thing assert_equal(:interp, config.send(:interpreter), "Did not cache the interpreter") end def test_create_interpreter config = Config.new(:Local => false) args = {} # Try it first with defaults. Puppet::Parser::Interpreter.expects(:new).with(:Local => config.local?).returns(:interp) assert_equal(:interp, config.send(:create_interpreter, args), "Did not return the interpreter") # Now reset it and make sure a specified manifest passes through file = tempfile args[:Manifest] = file Puppet::Parser::Interpreter.expects(:new).with(:Local => config.local?, :Manifest => file).returns(:interp) assert_equal(:interp, config.send(:create_interpreter, args), "Did not return the interpreter") # And make sure the code does, too args.delete(:Manifest) args[:Code] = "yay" Puppet::Parser::Interpreter.expects(:new).with(:Local => config.local?, :Code => "yay").returns(:interp) assert_equal(:interp, config.send(:create_interpreter, args), "Did not return the interpreter") end # Make sure node objects get appropriate data added to them. def test_add_node_data # First with no classes config = Config.new fakenode = Object.new # Set the server facts to something config.instance_variable_set("@server_facts", :facts) - fakenode.expects(:fact_merge).with(:facts) + fakenode.expects(:merge).with(:facts) config.send(:add_node_data, fakenode) # Now try it with classes. config.instance_variable_set("@options", {:Classes => %w{a b}}) list = [] fakenode = Object.new - fakenode.expects(:fact_merge).with(:facts) + fakenode.expects(:merge).with(:facts) fakenode.expects(:classes).returns(list).times(2) config.send(:add_node_data, fakenode) assert_equal(%w{a b}, list, "Did not add classes to node") end def test_compile config = Config.new # First do a local node = mock 'node' node.stubs(:name).returns(:mynode) node.stubs(:environment).returns(:myenv) interp = mock 'interpreter' interp.stubs(:environment) interp.expects(:compile).with(node).returns(:config) config.expects(:interpreter).returns(interp) Puppet.expects(:notice) # The log message from benchmarking assert_equal(:config, config.send(:compile, node), "Did not return config") # Now try it non-local node = mock 'node' node.stubs(:name).returns(:mynode) node.stubs(:environment).returns(:myenv) interp = mock 'interpreter' interp.stubs(:environment) interp.expects(:compile).with(node).returns(:config) config = Config.new(:Local => true) config.expects(:interpreter).returns(interp) assert_equal(:config, config.send(:compile, node), "Did not return config") end def test_set_server_facts config = Config.new assert_nothing_raised("Could not call :set_server_facts") do config.send(:set_server_facts) end facts = config.instance_variable_get("@server_facts") %w{servername serverversion serverip}.each do |fact| assert(facts.include?(fact), "Config did not set %s fact" % fact) end end def test_translate # First do a local config config = Config.new(:Local => true) assert_equal(:plain, config.send(:translate, :plain), "Attempted to translate local config") # Now a non-local config = Config.new(:Local => false) obj = Object.new yamld = Object.new obj.expects(:to_yaml).with(:UseBlock => true).returns(yamld) CGI.expects(:escape).with(yamld).returns(:translated) assert_equal(:translated, config.send(:translate, obj), "Did not return translated config") end # Check that we're storing the node freshness into the rails db. Hackilicious. def test_update_node_check # This is stupid. config = Config.new node = Object.new node.expects(:name).returns(:hostname) now = Object.new Time.expects(:now).returns(now) host = Object.new host.expects(:last_freshcheck=).with(now) host.expects(:save) # Only test the case where rails is there Puppet[:storeconfigs] = true Puppet.features.expects(:rails?).returns(true) Puppet::Rails.expects(:connect) Puppet::Rails::Host.expects(:find_or_create_by_name).with(:hostname).returns(host) config.send(:update_node_check, node) end def test_version # First try the case where we can't look up the node config = Config.new node = Object.new Puppet::Node.stubs(:search).with(:client).returns(false, node) interp = Object.new assert_instance_of(Bignum, config.version(:client), "Did not return configuration version") # And then when we find the node. config = Config.new config.expects(:update_node_check).with(node) interp = Object.new interp.expects(:configuration_version).returns(:version) config.expects(:interpreter).returns(interp) assert_equal(:version, config.version(:client), "Did not return configuration version") end end diff --git a/test/network/handler/facts.rb b/test/network/handler/facts.rb deleted file mode 100755 index 03327b8c4..000000000 --- a/test/network/handler/facts.rb +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'mocha' -require 'puppet/network/handler/facts' - -class TestFactsHandler < Test::Unit::TestCase - include PuppetTest::ServerTest - - def setup - super - - @class = Puppet::Network::Handler.handler(:facts) - - @@client_facts = {} - - unless Puppet::Util::FactStore.store(:testing) - Puppet::Util::FactStore.newstore(:testing) do - def get(node) - @@client_facts[node] - end - - def set(node, facts) - @@client_facts[node] = facts - end - end - end - - Puppet[:factstore] = :testing - - @handler = @class.new - - @facts = {:a => :b, :c => :d} - @name = "foo" - - @backend = @handler.instance_variable_get("@backend") - end - - def teardown - @@client_facts.clear - end - - def test_strip_internal - @facts[:_puppet_one] = "yay" - @facts[:_puppet_two] = "boo" - @facts[:_puppetthree] = "foo" - - newfacts = nil - assert_nothing_raised("Could not call strip_internal") do - newfacts = @handler.send(:strip_internal, @facts) - end - - [:_puppet_one, :_puppet_two, :_puppetthree].each do |name| - assert(@facts.include?(name), "%s was removed in strip_internal from original hash" % name) - end - [:_puppet_one, :_puppet_two].each do |name| - assert(! newfacts.include?(name), "%s was not removed in strip_internal" % name) - end - assert_equal("foo", newfacts[:_puppetthree], "_puppetthree was removed in strip_internal") - end - - def test_add_internal - newfacts = nil - assert_nothing_raised("Could not call strip_internal") do - newfacts = @handler.send(:add_internal, @facts) - end - - assert_instance_of(Time, newfacts[:_puppet_timestamp], "Did not set timestamp in add_internal") - assert(! @facts.include?(:_puppet_timestamp), "Modified original hash in add_internal") - end - - def test_set - newfacts = @facts.dup - newfacts[:_puppet_timestamp] = Time.now - @handler.expects(:add_internal).with(@facts).returns(newfacts) - @backend.expects(:set).with(@name, newfacts).returns(nil) - - assert_nothing_raised("Could not set facts") do - assert_nil(@handler.set(@name, @facts), "handler.set did not return nil") - end - end - - def test_get - prefacts = @facts.dup - prefacts[:_puppet_timestamp] = Time.now - @@client_facts[@name] = prefacts - @handler.expects(:strip_internal).with(prefacts).returns(@facts) - @backend.expects(:get).with(@name).returns(prefacts) - - assert_nothing_raised("Could not retrieve facts") do - assert_equal(@facts, @handler.get(@name), "did not get correct answer from handler.get") - end - - @handler = @class.new - assert_nothing_raised("Failed to call 'get' with no stored facts") do - @handler.get("nosuchname") - end - end - - def test_store_date - time = Time.now - @facts[:_puppet_timestamp] = time - - @handler.expects(:get).with(@name).returns(@facts) - - assert_equal(time.to_i, @handler.store_date(@name), "Did not retrieve timestamp correctly") - end -end - -# $Id$ diff --git a/test/network/handler/master.rb b/test/network/handler/master.rb index a976726ef..9749c7bdf 100755 --- a/test/network/handler/master.rb +++ b/test/network/handler/master.rb @@ -1,156 +1,161 @@ #!/usr/bin/env ruby $:.unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' require 'puppet/network/handler/master' class TestMaster < Test::Unit::TestCase include PuppetTest::ServerTest + def teardown + super + Puppet::Indirector::Indirection.clear_cache + end + def test_defaultmanifest textfiles { |file| Puppet[:manifest] = file client = nil master = nil assert_nothing_raised() { # this is the default server setup master = Puppet::Network::Handler.master.new( :Manifest => file, :UseNodes => false, :Local => true ) } assert_nothing_raised() { client = Puppet::Network::Client.master.new( :Master => master ) } # pull our configuration assert_nothing_raised() { client.getconfig stopservices Puppet::Type.allclear } break } end def test_filereread # Start with a normal setting Puppet[:filetimeout] = 15 manifest = mktestmanifest() facts = Puppet::Network::Client.master.facts # Store them, so we don't determine frshness based on facts. Puppet::Util::Storage.cache(:configuration)[:facts] = facts file2 = @createdfile + "2" @@tmpfiles << file2 client = master = nil assert_nothing_raised() { # this is the default server setup master = Puppet::Network::Handler.master.new( :Manifest => manifest, :UseNodes => false, :Local => true ) } assert_nothing_raised() { client = Puppet::Network::Client.master.new( :Master => master ) } assert(client, "did not create master client") # The client doesn't have a config, so it can't be up to date assert(! client.fresh?(facts), "Client is incorrectly up to date") Puppet.config.use(:main) assert_nothing_raised { client.getconfig client.apply } # Now it should be up to date assert(client.fresh?(facts), "Client is not up to date") # Cache this value for later parse1 = master.freshness("mynode") # Verify the config got applied assert(FileTest.exists?(@createdfile), "Created file %s does not exist" % @createdfile) Puppet::Type.allclear sleep 1.5 # Create a new manifest File.open(manifest, "w") { |f| f.puts "file { \"%s\": ensure => file }\n" % file2 } # Verify that the master doesn't immediately reparse the file; we # want to wait through the timeout assert_equal(parse1, master.freshness("mynode"), "Master did not wait through timeout") assert(client.fresh?(facts), "Client is not up to date") # Then eliminate it Puppet[:filetimeout] = 0 # Now make sure the master does reparse #Puppet.notice "%s vs %s" % [parse1, master.freshness] assert(parse1 != master.freshness("mynode"), "Master did not reparse file") assert(! client.fresh?(facts), "Client is incorrectly up to date") # Retrieve and apply the new config assert_nothing_raised { client.getconfig client.apply } assert(client.fresh?(facts), "Client is not up to date") assert(FileTest.exists?(file2), "Second file %s does not exist" % file2) end # Make sure we're correctly doing clientname manipulations. # Testing to make sure we always get a hostname and IP address. def test_clientname # create our master master = Puppet::Network::Handler.master.new( :Manifest => tempfile, :UseNodes => true, :Local => true ) # First check that 'cert' works Puppet[:node_name] = "cert" # Make sure we get the fact data back when nothing is set facts = {"hostname" => "fact_hostname", "ipaddress" => "fact_ip"} certname = "cert_hostname" certip = "cert_ip" resname, resip = master.send(:clientname, nil, nil, facts) assert_equal(facts["hostname"], resname, "Did not use fact hostname when no certname was present") assert_equal(facts["ipaddress"], resip, "Did not use fact ip when no certname was present") # Now try it with the cert stuff present resname, resip = master.send(:clientname, certname, certip, facts) assert_equal(certname, resname, "Did not use cert hostname when certname was present") assert_equal(certip, resip, "Did not use cert ip when certname was present") # And reset the node_name stuff and make sure we use it. Puppet[:node_name] = :facter resname, resip = master.send(:clientname, certname, certip, facts) assert_equal(facts["hostname"], resname, "Did not use fact hostname when nodename was set to facter") assert_equal(facts["ipaddress"], resip, "Did not use fact ip when nodename was set to facter") end end # $Id$