diff options
| author | Luke Kanies <luke@madstop.com> | 2007-08-22 18:03:55 -0500 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2007-08-22 18:03:55 -0500 |
| commit | 0682d7e473cfd8f2fe6bee9eae0868b846fd0d50 (patch) | |
| tree | fb66cc4c81e95ee42905410310095b9a8ae23ecc | |
| parent | ec50484518425ec8ac36f89b087beb27d5a3d2c8 (diff) | |
| parent | 8b3361afae35cfb65754d7bd9aff5b820ed714f0 (diff) | |
| download | puppet-0682d7e473cfd8f2fe6bee9eae0868b846fd0d50.tar.gz puppet-0682d7e473cfd8f2fe6bee9eae0868b846fd0d50.tar.xz puppet-0682d7e473cfd8f2fe6bee9eae0868b846fd0d50.zip | |
Merge branch 'multi_env'
57 files changed, 3965 insertions, 3133 deletions
diff --git a/lib/puppet/configuration.rb b/lib/puppet/configuration.rb index 65e0d9fa8..a8f6e0c35 100644 --- a/lib/puppet/configuration.rb +++ b/lib/puppet/configuration.rb @@ -122,7 +122,11 @@ module Puppet "The configuration file that defines the rights to the different namespaces and methods. This can be used as a coarse-grained authorization system for both ``puppetd`` and ``puppetmasterd``." - ] + ], + :environment => ["", "The environment Puppet is running in. For clients (e.g., ``puppetd``) this + determines the environment itself, which is used to find modules and much more. For + servers (i.e., ``puppetmasterd``) this provides the default environment for nodes we + know nothing about."] ) hostname = Facter["hostname"].value @@ -544,7 +548,11 @@ module Puppet setdefaults(:parser, :typecheck => [true, "Whether to validate types during parsing."], - :paramcheck => [true, "Whether to validate parameters during parsing."] + :paramcheck => [true, "Whether to validate parameters during parsing."], + :node_source => ["none", "Where to look for node configuration information. + The default node source, ``none``, just returns a node with its facts + filled in, which is required for normal functionality. + See the `NodeSourceReference`:trac: for more information."] ) setdefaults(:main, diff --git a/lib/puppet/dsl.rb b/lib/puppet/dsl.rb index 9c652f082..bd0fcbf96 100644 --- a/lib/puppet/dsl.rb +++ b/lib/puppet/dsl.rb @@ -113,10 +113,6 @@ module Puppet @aspects = {} - # For now, just do some hackery so resources work - @@interp = Puppet::Parser::Interpreter.new :Code => "" - @@scope = Puppet::Parser::Scope.new(:interp => @@interp) - @@objects = Hash.new do |hash, key| hash[key] = {} end @@ -237,7 +233,7 @@ module Puppet end unless obj = @@objects[type][name] obj = Resource.new :title => name, :type => type.name, - :source => source, :scope => @@scope + :source => source, :scope => scope @@objects[type][name] = obj @resources << obj @@ -250,12 +246,26 @@ module Puppet :source => source ) - obj.set(param) + obj.send(:set_parameter, param) end obj end + def scope + unless defined?(@scope) + @interp = Puppet::Parser::Interpreter.new :Code => "" + # Load the class, so the node object class is available. + require 'puppet/network/handler/node' + @node = Puppet::Node.new(Facter.value(:hostname)) + @node.parameters = Facter.to_hash + @interp = Puppet::Parser::Interpreter.new :Code => "" + @config = Puppet::Parser::Configuration.new(@node, @interp.parser) + @scope = @config.topscope + end + @scope + end + def type self.name end diff --git a/lib/puppet/network/handler.rb b/lib/puppet/network/handler.rb index 33343e4fe..c2fbfcba5 100644 --- a/lib/puppet/network/handler.rb +++ b/lib/puppet/network/handler.rb @@ -10,7 +10,7 @@ module Puppet::Network # This is so that the handlers can subclass just 'Handler', rather # then having to specify the full class path. Handler = self - attr_accessor :server + attr_accessor :server, :local extend Puppet::Util::SubclassLoader extend Puppet::Util @@ -44,6 +44,10 @@ module Puppet::Network # Create an empty init method with the same signature. def initialize(hash = {}) end + + def local? + self.local + end end end diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb new file mode 100644 index 000000000..7e91d74d6 --- /dev/null +++ b/lib/puppet/network/handler/configuration.rb @@ -0,0 +1,209 @@ +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) + # 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 = node_handler.details(key) + raise Puppet::Error, "Could not find node '%s'" % key + end + + # Add any external data to the node. + add_node_data(node) + + return translate(compile(node)) + 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) + v = interpreter.parsedate + # If we can find the node, then store the fact that the node + # has checked in. + if client and node = node_handler.details(client) + update_node_check(node) + end + + return v + 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) + + # 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. + 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 + Puppet.err detail + 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 + else + args[:Manifest] = options[:Manifest] || Puppet[: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 + + # Create a node handler instance for looking up our nodes. + def node_handler + unless defined?(@node_handler) + @node_handler = Puppet::Network::Handler.handler(:node).new + end + @node_handler + 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 + +# $Id$ diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb index e889c1ba8..acc6c4cda 100644 --- a/lib/puppet/network/handler/master.rb +++ b/lib/puppet/network/handler/master.rb @@ -13,7 +13,7 @@ class Puppet::Network::Handler include Puppet::Util - attr_accessor :ast, :local + attr_accessor :ast attr_reader :ca @interface = XMLRPC::Service::Interface.new("puppetmaster") { |iface| @@ -21,66 +21,9 @@ class Puppet::Network::Handler iface.add_method("int freshness()") } - # FIXME At some point, this should be autodocumenting. - def addfacts(facts) - # Add our server version to the fact list - facts["serverversion"] = Puppet.version.to_s - - # And then add the server name and IP - {"servername" => "fqdn", - "serverip" => "ipaddress" - }.each do |var, fact| - if obj = Facter[fact] - facts[var] = obj.value - else - Puppet.warning "Could not retrieve fact %s" % fact - end - end - - if facts["servername"].nil? - host = Facter.value(:hostname) - if domain = Facter.value(:domain) - facts["servername"] = [host, domain].join(".") - else - facts["servername"] = host - end - end - end - - # 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 - # Tell a client whether there's a fresh config for it def freshness(client = nil, clientip = nil) - if Puppet.features.rails? and Puppet[:storeconfigs] - Puppet::Rails.connect - - host = Puppet::Rails::Host.find_or_create_by_name(client) - host.last_freshcheck = Time.now - if clientip and (! host.ip or host.ip == "" or host.ip == "NULL") - host.ip = clientip - end - host.save - end - if defined? @interpreter - return @interpreter.parsedate - else - return 0 - end + config_handler.version(client, clientip) end def initialize(hash = {}) @@ -99,7 +42,7 @@ class Puppet::Network::Handler @local = false end - args[:Local] = @local + args[:Local] = local? if hash.include?(:CA) and hash[:CA] @ca = Puppet::SSLCertificates::CA.new() @@ -121,104 +64,79 @@ class Puppet::Network::Handler args[:Classes] = hash[:Classes] end - @interpreter = Puppet::Parser::Interpreter.new(args) + @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) - 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" + facts = decode_facts(facts) + client, clientip = clientname(client, clientip, facts) - # XXX this should definitely be done in the protocol, somehow - case format - when "marshal": - Puppet.warning "You should upgrade your client. 'Marshal' will not be supported much longer." - begin - facts = Marshal::load(CGI.unescape(facts)) - rescue => detail - raise XMLRPC::FaultException.new( - 1, "Could not rebuild facts" - ) - end - when "yaml": - begin - facts = YAML.load(CGI.unescape(facts)) - rescue => detail - raise XMLRPC::FaultException.new( - 1, "Could not rebuild facts" - ) - end - else - raise XMLRPC::FaultException.new( - 1, "Unavailable config format %s" % format - ) - end - end + # Pass the facts to the fact handler + fact_handler.set(client, facts) - client, clientip = clientname(client, clientip, facts) + # And get the configuration from the config handler + return config_handler.configuration(client) + end - # Add any server-side facts to our server. - addfacts(facts) + def local=(val) + @local = val + config_handler.local = val + fact_handler.local = val + end - retobjects = nil + private - # This is hackish, but there's no "silence" option for benchmarks - # right now - if @local - #begin - retobjects = @interpreter.run(client, facts) - #rescue Puppet::Error => detail - # Puppet.err detail - # raise XMLRPC::FaultException.new( - # 1, detail.to_s - # ) - #rescue => detail - # Puppet.err detail.to_s - # return "" - #end - else - benchmark(:notice, "Compiled configuration for %s" % client) do - begin - retobjects = @interpreter.run(client, facts) - rescue Puppet::Error => detail - Puppet.err detail - raise XMLRPC::FaultException.new( - 1, detail.to_s - ) - rescue => detail - Puppet.err detail.to_s - return "" - end + # 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 - return retobjects + # we don't need to do anything, since we should already + # have raw objects + Puppet.debug "Our client is local" else - str = nil - case format - when "marshal": - str = Marshal::dump(retobjects) - when "yaml": - str = retobjects.to_yaml(:UseBlock => true) - else + Puppet.debug "Our client is remote" + + begin + facts = YAML.load(CGI.unescape(facts)) + rescue => detail raise XMLRPC::FaultException.new( - 1, "Unavailable config format %s" % format + 1, "Could not rebuild facts" ) end - return CGI.escape(str) end + + return facts end - def local? - if defined? @local and @local - return true - else - return false + def fact_handler + unless defined? @fact_handler + @fact_handler = Puppet::Network::Handler.handler(:facts).new :local => local? end + @fact_handler end end end diff --git a/lib/puppet/network/handler/node.rb b/lib/puppet/network/handler/node.rb new file mode 100644 index 000000000..2c4d3e1b5 --- /dev/null +++ b/lib/puppet/network/handler/node.rb @@ -0,0 +1,227 @@ +# Created by Luke A. Kanies on 2007-08-13. +# Copyright (c) 2007. All rights reserved. + +require 'puppet/util' +require 'puppet/node' +require 'puppet/util/classgen' +require 'puppet/util/instance_loader' + +# Look up a node, along with all the details about it. +class Puppet::Network::Handler::Node < Puppet::Network::Handler + desc "Retrieve information about nodes." + + # Add a new node source. + def self.newnode_source(name, options = {}, &block) + name = symbolize(name) + + fact_merge = options[:fact_merge] + mod = genmodule(name, :extend => SourceBase, :hash => instance_hash(:node_source), :block => block) + mod.send(:define_method, :fact_merge?) do + fact_merge + end + mod + end + + # Collect the docs for all of our node sources. + def self.node_source_docs + docs = "" + + # Use this method so they all get loaded + instance_loader(:node_source).loadall + loaded_instances(:node_source).sort { |a,b| a.to_s <=> b.to_s }.each do |name| + mod = self.node_source(name) + docs += "%s\n%s\n" % [name, "-" * name.to_s.length] + + docs += Puppet::Util::Docs.scrub(mod.doc) + "\n\n" + end + + docs + end + + # List each of the node sources. + def self.node_sources + instance_loader(:node_source).loadall + loaded_instances(:node_source) + end + + # Remove a defined node source; basically only used for testing. + def self.rm_node_source(name) + rmclass(name, :hash => instance_hash(:node_source)) + end + + extend Puppet::Util::ClassGen + extend Puppet::Util::InstanceLoader + + # A simple base module we can use for modifying how our node sources work. + module SourceBase + include Puppet::Util::Docs + end + + @interface = XMLRPC::Service::Interface.new("nodes") { |iface| + iface.add_method("string details(key)") + iface.add_method("string parameters(key)") + iface.add_method("string environment(key)") + iface.add_method("string classes(key)") + } + + # Set up autoloading and retrieving of reports. + autoload :node_source, 'puppet/node_source' + + attr_reader :source + + # Return a given node's classes. + def classes(key) + if node = details(key) + node.classes + else + nil + end + end + + # Return an entire node configuration. This uses the 'nodesearch' method + # defined in the node_source to look for the node. + def details(key, client = nil, clientip = nil) + facts = node_facts(key) + node = nil + names = node_names(key, facts) + names.each do |name| + name = name.to_s if name.is_a?(Symbol) + if node = nodesearch(name) + Puppet.info "Found %s in %s" % [name, @source] + break + end + end + + # If they made it this far, we haven't found anything, so look for a + # default node. + unless node or names.include?("default") + if node = nodesearch("default") + Puppet.notice "Using default node for %s" % key + end + end + + if node + node.source = @source + node.names = names + + # Merge the facts into the parameters. + if fact_merge? + node.fact_merge(facts) + end + return node + else + return nil + end + end + + # Return a given node's environment. + def environment(key, client = nil, clientip = nil) + if node = details(key) + node.environment + else + nil + end + end + + # Create our node lookup tool. + def initialize(hash = {}) + @source = hash[:Source] || Puppet[:node_source] + + unless mod = self.class.node_source(@source) + raise ArgumentError, "Unknown node source '%s'" % @source + end + + extend(mod) + + super + + # We cache node info for speed + @node_cache = {} + end + + # Try to retrieve a given node's parameters. + def parameters(key, client = nil, clientip = nil) + if node = details(key) + node.parameters + else + nil + end + end + + private + + # Store the node to make things a bit faster. + def cache(node) + @node_cache[node.name] = node + end + + # If the node is cached, return it. + def cached?(name) + # Don't use cache when the filetimeout is set to 0 + return false if [0, "0"].include?(Puppet[:filetimeout]) + + if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout] + return node + else + return false + end + end + + # Create/cache a fact handler. + def fact_handler + unless defined?(@fact_handler) + @fact_handler = Puppet::Network::Handler.handler(:facts).new + end + @fact_handler + end + + # Short-hand for creating a new node, so the node sources don't need to + # specify the constant. + def newnode(options) + Puppet::Node.new(options) + end + + # Look up the node facts from our fact handler. + def node_facts(key) + if facts = fact_handler.get(key) + facts + else + {} + end + end + + # Calculate the list of node names we should use for looking + # up our node. + def node_names(key, facts = nil) + facts ||= node_facts(key) + names = [] + + if hostname = facts["hostname"] + unless hostname == key + names << hostname + end + else + hostname = key + end + + if fqdn = facts["fqdn"] + hostname = fqdn + names << fqdn + end + + # Make sure both the fqdn and the short name of the + # host can be used in the manifest + if hostname =~ /\./ + names << hostname.sub(/\..+/,'') + elsif domain = facts['domain'] + names << hostname + "." + domain + end + + # Sort the names inversely by name length. + names.sort! { |a,b| b.length <=> a.length } + + # And make sure the key is first, since that's the most + # likely usage. + ([key] + names).uniq + end +end diff --git a/lib/puppet/network/handler/resource.rb b/lib/puppet/network/handler/resource.rb index ca492bd81..ac29dce53 100755 --- a/lib/puppet/network/handler/resource.rb +++ b/lib/puppet/network/handler/resource.rb @@ -26,7 +26,7 @@ class Puppet::Network::Handler # Apply a TransBucket as a transaction. def apply(bucket, format = "yaml", client = nil, clientip = nil) - unless @local + unless local? begin case format when "yaml": @@ -43,7 +43,7 @@ class Puppet::Network::Handler # Create a client, but specify the remote machine as the server # because the class requires it, even though it's unused - client = Puppet::Network::Client.client(:Master).new(:Server => client||"localhost") + client = Puppet::Network::Client.client(:Master).new(:Master => client||"localhost") # Set the objects client.objects = component diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb new file mode 100644 index 000000000..3bbbe5979 --- /dev/null +++ b/lib/puppet/node.rb @@ -0,0 +1,40 @@ +# A simplistic class for managing the node information itself. +class Puppet::Node + attr_accessor :name, :classes, :parameters, :environment, :source, :ipaddress, :names + attr_reader :time + + def initialize(name, options = {}) + @name = name + + # Provide a default value. + @names = [name] + + if classes = options[:classes] + if classes.is_a?(String) + @classes = [classes] + else + @classes = classes + end + else + @classes = [] + end + + @parameters = options[:parameters] || {} + + unless @environment = options[:environment] + if env = Puppet[:environment] and env != "" + @environment = env + end + end + + @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| + @parameters[name] = value unless @parameters.include?(name) + end + end +end diff --git a/lib/puppet/node_source/external.rb b/lib/puppet/node_source/external.rb new file mode 100644 index 000000000..54111d924 --- /dev/null +++ b/lib/puppet/node_source/external.rb @@ -0,0 +1,51 @@ +Puppet::Network::Handler::Node.newnode_source(:external, :fact_merge => true) do + desc "Call an external program to get node information." + + include Puppet::Util + # Look for external node definitions. + def nodesearch(name) + return nil unless Puppet[:external_nodes] != "none" + + # 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) + 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 + end + + begin + result = 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 = newnode(name) + set = false + [:parameters, :classes].each do |param| + if value = result[param] + node.send(param.to_s + "=", value) + set = true + end + end + + if set + return node + else + return nil + end + end +end diff --git a/lib/puppet/node_source/ldap.rb b/lib/puppet/node_source/ldap.rb new file mode 100644 index 000000000..7b60a3c62 --- /dev/null +++ b/lib/puppet/node_source/ldap.rb @@ -0,0 +1,138 @@ +Puppet::Network::Handler::Node.newnode_source(:ldap, :fact_merge => true) do + desc "Search in LDAP for node configuration information." + + # 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 + + # Look for our node in ldap. + def nodesearch(node) + unless ary = ldapsearch(node) + 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 + + return newnode(node, :classes => classes, :source => "ldap", :parameters => parameters) + 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/node_source/none.rb b/lib/puppet/node_source/none.rb new file mode 100644 index 000000000..ce188add5 --- /dev/null +++ b/lib/puppet/node_source/none.rb @@ -0,0 +1,10 @@ +Puppet::Network::Handler::Node.newnode_source(:none, :fact_merge => true) 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 nodesearch(name) + newnode(name) + end +end diff --git a/lib/puppet/parser/ast/astarray.rb b/lib/puppet/parser/ast/astarray.rb index a0bd5bf89..c0212f919 100644 --- a/lib/puppet/parser/ast/astarray.rb +++ b/lib/puppet/parser/ast/astarray.rb @@ -21,45 +21,38 @@ class Puppet::Parser::AST # We basically always operate declaratively, and when we # do we need to evaluate the settor-like statements first. This # is basically variable and type-default declarations. - if scope.declarative? - # This is such a stupid hack. I've no real idea how to make a - # "real" declarative language, so I hack it so it looks like - # one, yay. - settors = [] - others = [] + # This is such a stupid hack. I've no real idea how to make a + # "real" declarative language, so I hack it so it looks like + # one, yay. + settors = [] + others = [] - # Make a new array, so we don't have to deal with the details of - # flattening and such - items = [] - - # First clean out any AST::ASTArrays - @children.each { |child| - if child.instance_of?(AST::ASTArray) - child.each do |ac| - if ac.class.settor? - settors << ac - else - others << ac - end - end - else - if child.class.settor? - settors << child + # Make a new array, so we don't have to deal with the details of + # flattening and such + items = [] + + # First clean out any AST::ASTArrays + @children.each { |child| + if child.instance_of?(AST::ASTArray) + child.each do |ac| + if ac.class.settor? + settors << ac else - others << child + others << ac end end - } - rets = [settors, others].flatten.collect { |child| - child.safeevaluate(:scope => scope) - } - return rets.reject { |o| o.nil? } - else - # If we're not declarative, just do everything in order. - return @children.collect { |item| - item.safeevaluate(:scope => scope) - }.reject { |o| o.nil? } - end + else + if child.class.settor? + settors << child + else + others << child + end + end + } + rets = [settors, others].flatten.collect { |child| + child.safeevaluate(:scope => scope) + } + return rets.reject { |o| o.nil? } end def push(*ary) diff --git a/lib/puppet/parser/ast/collection.rb b/lib/puppet/parser/ast/collection.rb index c817b5c5e..7daf031cf 100644 --- a/lib/puppet/parser/ast/collection.rb +++ b/lib/puppet/parser/ast/collection.rb @@ -20,7 +20,7 @@ class Collection < AST::Branch newcoll = Puppet::Parser::Collector.new(scope, @type, str, code, self.form) - scope.newcollection(newcoll) + scope.configuration.add_collection(newcoll) newcoll end diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb index 65f310212..17cfa9d61 100644 --- a/lib/puppet/parser/ast/component.rb +++ b/lib/puppet/parser/ast/component.rb @@ -27,7 +27,7 @@ class Puppet::Parser::AST false end - def evaluate(hash) + def evaluate_resource(hash) origscope = hash[:scope] title = hash[:title] args = symbolize_options(hash[:arguments] || {}) diff --git a/lib/puppet/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 642645824..9b60c692f 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -24,8 +24,10 @@ class Puppet::Parser::AST scope = hash[:scope] args = hash[:arguments] - # Verify that we haven't already been evaluated - if scope.class_scope(self) + # Verify that we haven't already been evaluated, and if we have been evaluated, + # make sure that we match the class. + if existing_scope = scope.class_scope(self) + #if existing_scope.source.object_id == self.object_id Puppet.debug "%s class already evaluated" % @type return nil end diff --git a/lib/puppet/parser/collector.rb b/lib/puppet/parser/collector.rb index 6c49c6d57..0846c40ab 100644 --- a/lib/puppet/parser/collector.rb +++ b/lib/puppet/parser/collector.rb @@ -81,7 +81,7 @@ class Puppet::Parser::Collector # If there are no more resources to find, delete this from the list # of collections. if @resources.empty? - @scope.collections.delete(self) + @scope.configuration.delete_collection(self) end return result @@ -94,7 +94,7 @@ class Puppet::Parser::Collector else method = :virtual? end - scope.resources.find_all do |resource| + scope.configuration.resources.find_all do |resource| resource.type == @type and resource.send(method) and match?(resource) end end @@ -117,13 +117,6 @@ class Puppet::Parser::Collector return objects end end - -# if objects and ! objects.empty? -# objects.each { |r| r.virtual = false } -# return objects -# else -# return false -# end end def initialize(scope, type, equery, vquery, form) diff --git a/lib/puppet/parser/configuration.rb b/lib/puppet/parser/configuration.rb new file mode 100644 index 000000000..44fb8c476 --- /dev/null +++ b/lib/puppet/parser/configuration.rb @@ -0,0 +1,555 @@ +# Created by Luke A. Kanies on 2007-08-13. +# Copyright (c) 2007. All rights reserved. + +require 'puppet/external/gratr/digraph' +require 'puppet/external/gratr/import' +require 'puppet/external/gratr/dot' + +require 'puppet/node' +require 'puppet/util/errors' + +# Maintain a graph of scopes, along with a bunch of data +# about the individual configuration we're compiling. +class Puppet::Parser::Configuration + include Puppet::Util + include Puppet::Util::Errors + attr_reader :topscope, :parser, :node, :facts, :collections + attr_accessor :extraction_format + + attr_writer :ast_nodes + + # Add a collection to the global list. + def add_collection(coll) + @collections << coll + end + + # Do we use nodes found in the code, vs. the external node sources? + def ast_nodes? + defined?(@ast_nodes) and @ast_nodes + end + + # Store the fact that we've evaluated a class, and store a reference to + # the scope in which it was evaluated, so that we can look it up later. + def class_set(name, scope) + if existing = @class_scopes[name] + if existing.nodescope? or scope.nodescope? + raise Puppet::ParseError, "Cannot have classes, nodes, or definitions with the same name" + else + raise Puppet::DevError, "Somehow evaluated the same class twice" + end + end + @class_scopes[name] = scope + tag(name) + end + + # Return the scope associated with a class. This is just here so + # that subclasses can set their parent scopes to be the scope of + # their parent class, and it's also used when looking up qualified + # variables. + def class_scope(klass) + # They might pass in either the class or class name + if klass.respond_to?(:classname) + @class_scopes[klass.classname] + else + @class_scopes[klass] + end + end + + # Return a list of all of the defined classes. + def classlist + return @class_scopes.keys.reject { |k| k == "" } + end + + # Compile our configuration. This mostly revolves around finding and evaluating classes. + # This is the main entry into our configuration. + def compile + # Set the client's parameters into the top scope. + set_node_parameters() + + evaluate_main() + + evaluate_ast_node() + + evaluate_classes() + + evaluate_generators() + + fail_on_unevaluated() + + finish() + + if Puppet[:storeconfigs] + store() + end + + return extract() + end + + # FIXME There are no tests for this. + def delete_collection(coll) + @collections.delete(coll) if @collections.include?(coll) + end + + # FIXME There are no tests for this. + def delete_resource(resource) + @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref) + + @resource_graph.remove_vertex!(resource) if @resource_graph.vertex?(resource) + end + + # Evaluate each class in turn. If there are any classes we can't find, + # just tag the configuration and move on. + def evaluate_classes(classes = nil) + classes ||= node.classes + found = [] + classes.each do |name| + if klass = @parser.findclass("", name) + # This will result in class_set getting called, which + # will in turn result in tags. Yay. + klass.safeevaluate(:scope => topscope) + found << name + else + Puppet.info "Could not find class %s for %s" % [name, node.name] + tag(name) + end + end + found + end + + # Make sure we support the requested extraction format. + def extraction_format=(value) + unless respond_to?("extract_to_%s" % value) + raise ArgumentError, "Invalid extraction format %s" % value + end + @extraction_format = value + end + + # Return a resource by either its ref or its type and title. + def findresource(string, name = nil) + if name + string = "%s[%s]" % [string.capitalize, name] + end + + @resource_table[string] + end + + # Set up our configuration. We require a parser + # and a node object; the parser is so we can look up classes + # and AST nodes, and the node has all of the client's info, + # like facts and environment. + def initialize(node, parser, options = {}) + @node = node + @parser = parser + + options.each do |param, value| + begin + send(param.to_s + "=", value) + rescue NoMethodError + raise ArgumentError, "Configuration objects do not accept %s" % param + end + end + + @extraction_format ||= :transportable + + initvars() + end + + # Create a new scope, with either a specified parent scope or + # using the top scope. Adds an edge between the scope and + # its parent to the graph. + def newscope(parent, options = {}) + parent ||= @topscope + options[:configuration] = self + options[:parser] ||= self.parser + scope = Puppet::Parser::Scope.new(options) + @scope_graph.add_edge!(parent, scope) + scope + end + + # Find the parent of a given scope. Assumes scopes only ever have + # one in edge, which will always be true. + def parent(scope) + if ary = @scope_graph.adjacent(scope, :direction => :in) and ary.length > 0 + ary[0] + else + nil + end + end + + # Return any overrides for the given resource. + def resource_overrides(resource) + @resource_overrides[resource.ref] + end + + # Return a list of all resources. + def resources + @resource_table.values + end + + # Store a resource override. + def store_override(override) + override.override = true + + # If possible, merge the override in immediately. + if resource = @resource_table[override.ref] + resource.merge(override) + else + # Otherwise, store the override for later; these + # get evaluated in Resource#finish. + @resource_overrides[override.ref] << override + end + end + + # Store a resource in our resource table. + def store_resource(scope, resource) + # This might throw an exception + verify_uniqueness(resource) + + # Store it in the global table. + @resource_table[resource.ref] = resource + + # And in the resource graph. At some point, this might supercede + # the global resource table, but the table is a lot faster + # so it makes sense to maintain for now. + @resource_graph.add_edge!(scope, resource) + end + + private + + # If ast nodes are enabled, then see if we can find and evaluate one. + def evaluate_ast_node + return unless ast_nodes? + + # Now see if we can find the node. + astnode = nil + #nodes = @parser.nodes + @node.names.each do |name| + break if astnode = @parser.nodes[name.to_s.downcase] + end + + unless astnode + astnode = @parser.nodes["default"] + end + unless astnode + raise Puppet::ParseError, "Could not find default node or by name with '%s'" % node.names.join(", ") + end + + astnode.safeevaluate :scope => topscope + end + + # Evaluate our collections and return true if anything returned an object. + # The 'true' is used to continue a loop, so it's important. + def evaluate_collections + return false if @collections.empty? + + found_something = false + exceptwrap do + @collections.each do |collection| + if collection.evaluate + found_something = true + end + end + end + + return found_something + end + + # Make sure all of our resources have been evaluated into native resources. + # We return true if any resources have, so that we know to continue the + # evaluate_generators loop. + def evaluate_definitions + exceptwrap do + if ary = unevaluated_resources + ary.each do |resource| + resource.evaluate + end + # If we evaluated, let the loop know. + return true + else + return false + end + end + end + + # Iterate over collections and resources until we're sure that the whole + # configuration is evaluated. This is necessary because both collections + # and defined resources can generate new resources, which themselves could + # be defined resources. + def evaluate_generators + count = 0 + loop do + done = true + + # Call collections first, then definitions. + done = false if evaluate_collections + done = false if evaluate_definitions + break if done + if count > 1000 + raise Puppet::ParseError, "Somehow looped more than 1000 times while evaluating host configuration" + end + end + end + + # Find and evaluate our main object, if possible. + def evaluate_main + if klass = @parser.findclass("", "") + # Set the source, so objects can tell where they were defined. + topscope.source = klass + klass.safeevaluate :scope => topscope, :nosubscope => true + end + end + + # Turn our configuration graph into whatever the client is expecting. + def extract + send("extract_to_%s" % extraction_format) + end + + # Create the traditional TransBuckets and TransObjects from our configuration + # graph. This will hopefully be deprecated soon. + def extract_to_transportable + top = nil + current = nil + buckets = {} + + # I'm *sure* there's a simple way to do this using a breadth-first search + # or something, but I couldn't come up with, and this is both fast + # and simple, so I'm not going to worry about it too much. + @scope_graph.vertices.each do |scope| + # For each scope, we need to create a TransBucket, and then + # put all of the scope's resources into that bucket, translating + # each resource into a TransObject. + + # Unless the bucket's already been created, make it now and add + # it to the cache. + unless bucket = buckets[scope] + bucket = buckets[scope] = scope.to_trans + end + + # First add any contained scopes + @scope_graph.adjacent(scope, :direction => :out).each do |vertex| + # If there's not already a bucket, then create and cache it. + unless child_bucket = buckets[vertex] + child_bucket = buckets[vertex] = vertex.to_trans + end + bucket.push child_bucket + end + + # Then add the resources. + if @resource_graph.vertex?(scope) + @resource_graph.adjacent(scope, :direction => :out).each do |vertex| + # Some resources don't get translated, e.g., virtual resources. + if obj = vertex.to_trans + bucket.push obj + end + end + end + end + + # Retrive the bucket for the top-level scope and set the appropriate metadata. + result = buckets[topscope] + case topscope.type + when "": result.type = "main" + when nil: devfail "A Scope with no type" + else + result.type = topscope.type + end + if topscope.name + result.name = topscope.name + end + + unless classlist.empty? + result.classes = classlist + end + + # Clear the cache to encourage the GC + buckets.clear + return result + end + + # Make sure the entire configuration is evaluated. + def fail_on_unevaluated + fail_on_unevaluated_overrides + fail_on_unevaluated_resource_collections + end + + # If there are any resource overrides remaining, then we could + # not find the resource they were supposed to override, so we + # want to throw an exception. + def fail_on_unevaluated_overrides + remaining = [] + @resource_overrides.each do |name, overrides| + remaining += overrides + end + + unless remaining.empty? + fail Puppet::ParseError, + "Could not find object(s) %s" % remaining.collect { |o| + o.ref + }.join(", ") + end + end + + # Make sure we don't have any remaining collections that specifically + # look for resources, because we want to consider those to be + # parse errors. + def fail_on_unevaluated_resource_collections + remaining = [] + @collections.each do |coll| + # We're only interested in the 'resource' collections, + # which result from direct calls of 'realize'. Anything + # else is allowed not to return resources. + # Collect all of them, so we have a useful error. + if r = coll.resources + if r.is_a?(Array) + remaining += r + else + remaining << r + end + end + end + + unless remaining.empty? + raise Puppet::ParseError, "Failed to realize virtual resources %s" % + remaining.join(', ') + end + end + + # Make sure all of our resources and such have done any last work + # necessary. + def finish + @resource_table.each { |name, resource| resource.finish if resource.respond_to?(:finish) } + end + + # Set up all of our internal variables. + def initvars + # The table for storing class singletons. This will only actually + # be used by top scopes and node scopes. + @class_scopes = {} + + # The table for all defined resources. + @resource_table = {} + + # The list of objects that will available for export. + @exported_resources = {} + + # The list of overrides. This is used to cache overrides on objects + # that don't exist yet. We store an array of each override. + @resource_overrides = Hash.new do |overs, ref| + overs[ref] = [] + end + + # The list of collections that have been created. This is a global list, + # but they each refer back to the scope that created them. + @collections = [] + + # A list of tags we've generated; most class names. + @tags = [] + + # Create our initial scope, our scope graph, and add the initial scope to the graph. + @topscope = Puppet::Parser::Scope.new(:configuration => self, :type => "main", :name => "top", :parser => self.parser) + + # For maintaining scope relationships. + @scope_graph = GRATR::Digraph.new + @scope_graph.add_vertex!(@topscope) + + # For maintaining the relationship between scopes and their resources. + @resource_graph = GRATR::Digraph.new + end + + # Set the node's parameters into the top-scope as variables. + def set_node_parameters + node.parameters.each do |param, value| + @topscope.setvar(param, value) + end + end + + # Store the configuration into the database. + def store + unless Puppet.features.rails? + raise Puppet::Error, + "storeconfigs is enabled but rails is unavailable" + end + + unless ActiveRecord::Base.connected? + Puppet::Rails.connect + end + + # We used to have hooks here for forking and saving, but I don't + # think it's worth retaining at this point. + store_to_active_record(@node, @resource_table.values) + end + + # Do the actual storage. + def store_to_active_record(node, resources) + begin + # We store all of the objects, even the collectable ones + benchmark(:info, "Stored configuration for #{node.name}") do + Puppet::Rails::Host.transaction do + Puppet::Rails::Host.store(node, resources) + end + end + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Could not store configs: %s" % detail.to_s + end + end + + # Add a tag. + def tag(*names) + names.each do |name| + name = name.to_s + @tags << name unless @tags.include?(name) + end + nil + end + + # Return the list of tags. + def tags + @tags.dup + end + + # Return an array of all of the unevaluated resources. These will be definitions, + # which need to get evaluated into native resources. + def unevaluated_resources + ary = @resource_table.find_all do |name, object| + ! object.builtin? and ! object.evaluated? + end.collect { |name, object| object } + + if ary.empty? + return nil + else + return ary + end + end + + # Verify that the given resource isn't defined elsewhere. + def verify_uniqueness(resource) + # Short-curcuit the common case, + unless existing_resource = @resource_table[resource.ref] + return true + end + + if typeclass = Puppet::Type.type(resource.type) and ! typeclass.isomorphic? + Puppet.info "Allowing duplicate %s" % typeclass.name + return true + end + + # Either it's a defined type, which are never + # isomorphic, or it's a non-isomorphic type, so + # we should throw an exception. + msg = "Duplicate definition: %s is already defined" % resource.ref + + if existing_resource.file and existing_resource.line + msg << " in file %s at line %s" % + [existing_resource.file, existing_resource.line] + end + + if resource.line or resource.file + msg << "; cannot redefine" + end + + raise Puppet::ParseError.new(msg) + end +end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 946501154..ad58c7040 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -109,7 +109,8 @@ module Functions # Include the specified classes newfunction(:include, :doc => "Evaluate one or more classes.") do |vals| - klasses = evalclasses(*vals) + vals = [vals] unless vals.is_a?(Array) + klasses = configuration.evaluate_classes(vals) missing = vals.find_all do |klass| ! klasses.include?(klass) @@ -144,7 +145,7 @@ module Functions tells you whether the current container is tagged with the specified tags. The tags are ANDed, so that all of the specified tags must be included for the function to return true.") do |vals| - classlist = self.classlist + classlist = configuration.classlist retval = true vals.each do |val| @@ -234,7 +235,7 @@ module Functions vals = [vals] unless vals.is_a?(Array) coll.resources = vals - newcollection(coll) + configuration.add_collection(coll) end newfunction(:search, :doc => "Add another namespace for this class to search. diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 3ba9c0c7a..e37ef5efe 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -3,274 +3,21 @@ require 'timeout' require 'puppet/rails' require 'puppet/util/methodhelper' require 'puppet/parser/parser' +require 'puppet/parser/configuration' require 'puppet/parser/scope' -# The interpreter's job is to convert from a parsed file to the configuration -# for a given client. It really doesn't do any work on its own, it just collects -# and calls out to other objects. +# The interpreter is a very simple entry-point class that +# manages the existence of the parser (e.g., replacing it +# when files are reparsed). You can feed it a node and +# get the node's configuration back. class Puppet::Parser::Interpreter - class NodeDef - include Puppet::Util::MethodHelper - attr_accessor :name, :classes, :parameters, :source - - def evaluate(options) - begin - parameters.each do |param, value| - # Don't try to override facts with these parameters - options[:scope].setvar(param, value) unless options[:scope].lookupvar(param, false) != :undefined - end - - # Also, set the 'nodename', since it might not be obvious how the node was looked up - options[:scope].setvar("nodename", @name) unless options[:scope].lookupvar(@nodename, false) != :undefined - rescue => detail - raise Puppet::ParseError, "Could not set parameters for %s: %s" % [name, detail] - end - - # Then evaluate the classes. - begin - options[:scope].function_include(classes.find_all { |c| options[:scope].findclass(c) }) - rescue => detail - puts detail.backtrace - raise Puppet::ParseError, "Could not evaluate classes for %s: %s" % [name, detail] - end - end - - def initialize(args) - set_options(args) - - raise Puppet::DevError, "NodeDefs require names" unless self.name - - if self.classes.is_a?(String) - @classes = [@classes] - else - @classes ||= [] - end - @parameters ||= {} - end - - def safeevaluate(args) - evaluate(args) - end - end - include Puppet::Util attr_accessor :usenodes - - class << self - attr_writer :ldap - end - - # just shorten the constant path a bit, using what amounts to an alias - AST = Puppet::Parser::AST + attr_reader :parser include Puppet::Util::Errors - # Create an ldap connection. This is a class method so others can call - # it and use the same variables and such. - def self.ldap - unless defined? @ldap and @ldap - 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]) - end - - return @ldap - end - - # Make sure we don't have any remaining collections that specifically - # look for resources, because we want to consider those to be - # parse errors. - def check_resource_collections(scope) - remaining = [] - scope.collections.each do |coll| - if r = coll.resources - if r.is_a?(Array) - remaining += r - else - remaining << r - end - end - end - unless remaining.empty? - raise Puppet::ParseError, "Failed to find virtual resources %s" % - remaining.join(', ') - end - end - - # Iteratively evaluate all of the objects. This finds all of the objects - # that represent definitions and evaluates the definitions appropriately. - # It also adds defaults and overrides as appropriate. - def evaliterate(scope) - count = 0 - loop do - count += 1 - done = true - # First perform collections, so we can collect defined types. - if coll = scope.collections and ! coll.empty? - exceptwrap do - coll.each do |c| - # Only keep the loop going if we actually successfully - # collected something. - if o = c.evaluate - done = false - end - end - end - end - - # Then evaluate any defined types. - if ary = scope.unevaluated - ary.each do |resource| - resource.evaluate - end - # If we evaluated, then loop through again. - done = false - end - break if done - - if count > 1000 - raise Puppet::ParseError, "Got 1000 class levels, which is unsupported" - end - end - end - - # Evaluate a specific node. - def evalnode(client, scope, facts) - return unless self.usenodes - - unless client - raise Puppet::Error, - "Cannot evaluate nodes with a nil client" - end - names = [client] - - # Make sure both the fqdn and the short name of the - # host can be used in the manifest - if client =~ /\./ - names << client.sub(/\..+/,'') - else - names << "#{client}.#{facts['domain']}" - end - - if names.empty? - raise Puppet::Error, - "Cannot evaluate nodes with a nil client" - end - - # Look up our node object. - if nodeclass = nodesearch(*names) - nodeclass.safeevaluate :scope => scope - else - raise Puppet::Error, "Could not find %s with names %s" % - [client, names.join(", ")] - end - end - - # Evaluate all of the code we can find that's related to our client. - def evaluate(client, facts) - - scope = Puppet::Parser::Scope.new(:interp => self) # no parent scope - scope.name = "top" - scope.type = "main" - - scope.host = client || facts["hostname"] || Facter.value(:hostname) - - classes = @classes.dup - - # Okay, first things first. Set our facts. - scope.setfacts(facts) - - # Everyone will always evaluate the top-level class, if there is one. - if klass = findclass("", "") - # Set the source, so objects can tell where they were defined. - scope.source = klass - klass.safeevaluate :scope => scope, :nosubscope => true - end - - # Next evaluate the node. We pass the facts so they can be used - # when building the list of names for which to search. - evalnode(client, scope, facts) - - # If we were passed any classes, evaluate those. - if classes - classes.each do |klass| - if klassobj = findclass("", klass) - klassobj.safeevaluate :scope => scope - end - end - end - - # That was the first pass evaluation. Now iteratively evaluate - # until we've gotten rid of all of everything or thrown an error. - evaliterate(scope) - - # Now make sure we fail if there's anything left to do - failonleftovers(scope) - - # Now finish everything. This recursively calls finish on the - # contained scopes and resources. - scope.finish - - # Store everything. We need to do this before translation, because - # it operates on resources, not transobjects. - if Puppet[:storeconfigs] - args = { - :resources => scope.resources, - :name => scope.host, - :facts => facts - } - unless scope.classlist.empty? - args[:classes] = scope.classlist - end - - storeconfigs(args) - end - - # Now, finally, convert our scope tree + resources into a tree of - # buckets and objects. - objects = scope.translate - - # Add the class list - unless scope.classlist.empty? - objects.classes = scope.classlist - end - - return objects - end - - # Fail if there any overrides left to perform. - def failonleftovers(scope) - overrides = scope.overrides - unless overrides.empty? - fail Puppet::ParseError, - "Could not find object(s) %s" % overrides.collect { |o| - o.ref - }.join(", ") - end - - # Now check that there aren't any extra resource collections. - check_resource_collections(scope) - end - - # Create proxy methods, so the scopes can call the interpreter, since - # they don't have access to the parser. - def findclass(namespace, name) - @parser.findclass(namespace, name) - end - def finddefine(namespace, name) - @parser.finddefine(namespace, name) - end - # create our interpreter def initialize(hash) if @code = hash[:Code] @@ -285,33 +32,13 @@ class Puppet::Parser::Interpreter @usenodes = true end - - if Puppet[:ldapnodes] - # Nodes in the file override nodes in ldap. - @nodesource = :ldap - elsif Puppet[:external_nodes] != "none" - @nodesource = :external - else - # By default, we only search for parsed nodes. - @nodesource = :code - end + # By default, we only search for parsed nodes. + @nodesource = :code @setup = false - # Set it to either the value or nil. This is currently only used - # by the cfengine module. - @classes = hash[:Classes] || [] - @local = hash[:Local] || false - if hash.include?(:ForkSave) - @forksave = hash[:ForkSave] - else - # This is just too dangerous right now. Sorry, it's going - # to have to be slow. - @forksave = false - end - # The class won't always be defined during testing. if Puppet[:storeconfigs] if Puppet.features.rails? @@ -327,270 +54,16 @@ class Puppet::Parser::Interpreter parsefiles end - # Find the ldap node, return the class list and parent node specially, - # and everything else in a parameter hash. - def ldapsearch(node) - unless defined? @ldap and @ldap - setup_ldap() - unless @ldap - Puppet.info "Skipping ldap source; no ldap connection" - return nil - end - end - - 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 - setup_ldap() - 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 - - # Pass these methods through to the parser. - [:newclass, :newdefine, :newnode].each do |name| - define_method(name) do |*args| - @parser.send(name, *args) - end - end - - # Add a new file to be checked when we're checking to see if we should be - # reparsed. - def newfile(*files) - files.each do |file| - unless file.is_a? Puppet::Util::LoadedFile - file = Puppet::Util::LoadedFile.new(file) - end - @files << file - end - end - - # Search for our node in the various locations. - def nodesearch(*nodes) - nodes = nodes.collect { |n| n.to_s.downcase } - - method = "nodesearch_%s" % @nodesource - # Do an inverse sort on the length, so the longest match always - # wins - nodes.sort { |a,b| b.length <=> a.length }.each do |node| - node = node.to_s if node.is_a?(Symbol) - if obj = self.send(method, node) - if obj.is_a?(AST::Node) - nsource = obj.file - else - nsource = obj.source - end - Puppet.info "Found %s in %s" % [node, nsource] - return obj - end - end - - # If they made it this far, we haven't found anything, so look for a - # default node. - unless nodes.include?("default") - if defobj = self.nodesearch("default") - Puppet.notice "Using default node for %s" % [nodes[0]] - return defobj - end - end - - return nil - end - - # See if our node was defined in the code. - def nodesearch_code(name) - @parser.nodes[name] - end - - # Look for external node definitions. - def nodesearch_external(name) - return nil unless Puppet[:external_nodes] != "none" - - # 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) - 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 - end - - begin - result = 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_args = {:source => "external node source", :name => name} - set = false - [:parameters, :classes].each do |param| - if value = result[param] - node_args[param] = value - set = true - end - end - - if set - return NodeDef.new(node_args) - else - return nil - end - end - - # Look for our node in ldap. - def nodesearch_ldap(node) - unless ary = ldapsearch(node) - 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 - - return NodeDef.new(:name => node, :classes => classes, :source => "ldap", :parameters => parameters) - end - def parsedate parsefiles() @parsedate end # evaluate our whole tree - def run(client, facts) - # We have to leave this for after initialization because there - # seems to be a problem keeping ldap open after a fork. - unless @setup - method = "setup_%s" % @nodesource.to_s - if respond_to? method - exceptwrap :type => Puppet::Error, - :message => "Could not set up node source %s" % @nodesource do - self.send(method) - end - end - end + def compile(node) parsefiles() - # Evaluate all of the appropriate code. - objects = evaluate(client, facts) - - # And return it all. - return objects - end - - # Connect to the LDAP Server - def setup_ldap - self.class.ldap = nil - unless Puppet.features.ldap? - Puppet.notice( - "Could not set up LDAP Connection: Missing ruby/ldap libraries" - ) - @ldap = nil - return - end - - begin - @ldap = self.class.ldap() - rescue => detail - raise Puppet::Error, "Could not connect to LDAP: %s" % detail - end - end - - def scope - return @scope + return Puppet::Parser::Configuration.new(node, @parser).compile end private @@ -667,47 +140,6 @@ class Puppet::Parser::Interpreter Puppet.err "Could not parse; using old configuration: %s" % detail end end - - # Store the configs into the database. - def storeconfigs(hash) - unless Puppet.features.rails? - raise Puppet::Error, - "storeconfigs is enabled but rails is unavailable" - end - - unless ActiveRecord::Base.connected? - Puppet::Rails.connect - end - - # Fork the storage, since we don't need the client waiting - # on that. How do I avoid this duplication? - if @forksave - fork { - # We store all of the objects, even the collectable ones - benchmark(:info, "Stored configuration for #{hash[:name]}") do - # Try to batch things a bit, by putting them into - # a transaction - Puppet::Rails::Host.transaction do - Puppet::Rails::Host.store(hash) - end - end - } - else - begin - # We store all of the objects, even the collectable ones - benchmark(:info, "Stored configuration for #{hash[:name]}") do - Puppet::Rails::Host.transaction do - Puppet::Rails::Host.store(hash) - end - end - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - Puppet.err "Could not store configs: %s" % detail.to_s - end - end - end end # $Id$ diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb index 728f75a69..967508e56 100644 --- a/lib/puppet/parser/parser_support.rb +++ b/lib/puppet/parser/parser_support.rb @@ -1,3 +1,5 @@ +# I pulled this into a separate file, because I got +# tired of rebuilding the parser.rb file all the time. class Puppet::Parser::Parser require 'puppet/parser/functions' @@ -442,6 +444,17 @@ class Puppet::Parser::Parser def string=(string) @lexer.string = string end + + # Add a new file to be checked when we're checking to see if we should be + # reparsed. + def watch_file(*files) + files.each do |file| + unless file.is_a? Puppet::Util::LoadedFile + file = Puppet::Util::LoadedFile.new(file) + end + @files << file + end + end end # $Id$ diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index 18ec15ac0..f946c1328 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -10,9 +10,9 @@ class Puppet::Parser::Resource include Puppet::Util::Logging attr_accessor :source, :line, :file, :scope, :rails_id - attr_accessor :virtual, :override, :params, :translated + attr_accessor :virtual, :override, :translated - attr_reader :exported + attr_reader :exported, :evaluated, :params attr_writer :tags @@ -24,7 +24,7 @@ class Puppet::Parser::Resource end # Set up some boolean test methods - [:exported, :translated, :override].each do |method| + [:exported, :translated, :override, :virtual, :evaluated].each do |method| newmeth = (method.to_s + "?").intern define_method(newmeth) do self.send(method) @@ -43,44 +43,6 @@ class Puppet::Parser::Resource end end - # Add default values from our definition. - def adddefaults - defaults = scope.lookupdefaults(self.type) - - defaults.each do |name, param| - unless @params.include?(param.name) - self.debug "Adding default for %s" % param.name - - @params[param.name] = param - end - end - end - - # Add any metaparams defined in our scope. This actually adds any metaparams - # from any parent scope, and there's currently no way to turn that off. - def addmetaparams - Puppet::Type.eachmetaparam do |name| - next if self[name] - if val = scope.lookupvar(name.to_s, false) - unless val == :undefined - set Param.new(:name => name, :value => val, - :source => scope.source) - end - end - end - end - - # Add any overrides for this object. - def addoverrides - overrides = scope.lookupoverrides(self) - - overrides.each do |over| - self.merge(over) - end - - overrides.clear - end - def builtin=(bool) @ref.builtin = bool end @@ -89,12 +51,11 @@ class Puppet::Parser::Resource def evaluate if klass = @ref.definedtype finish() - scope.deleteresource(self) - return klass.evaluate(:scope => scope, + scope.configuration.delete_resource(self) + return klass.evaluate_resource(:scope => scope, :type => self.type, :title => self.title, :arguments => self.to_hash, - :scope => self.scope, :virtual => self.virtual, :exported => self.exported ) @@ -107,6 +68,8 @@ class Puppet::Parser::Resource @evaluated = true end + # Mark this resource as both exported and virtual, + # or remove the exported mark. def exported=(value) if value @virtual = true @@ -116,63 +79,73 @@ class Puppet::Parser::Resource end end - def evaluated? - if defined? @evaluated and @evaluated - true - else - false - end - end - # Do any finishing work on this object, called before evaluation or # before storage/translation. def finish - addoverrides() - adddefaults() - addmetaparams() + add_overrides() + add_defaults() + add_metaparams() + validate() end def initialize(options) - options = symbolize_options(options) - - # Collect the options necessary to make the reference. - refopts = [:type, :title].inject({}) do |hash, param| - hash[param] = options[param] - options.delete(param) - hash + # Set all of the options we can. + options.each do |option, value| + if respond_to?(option.to_s + "=") + send(option.to_s + "=", value) + options.delete(option) + end end - @params = {} - tmpparams = nil - if tmpparams = options[:params] - options.delete(:params) + [:scope, :source].each do |attribute| + unless self.send(attribute) + raise ArgumentError, "Resources require a %s" % attribute + end end - # Now set the rest of the options. - set_options(options) + options = symbolize_options(options) - @ref = Reference.new(refopts) + # Set up our reference. + if type = options[:type] and title = options[:title] + options.delete(:type) + options.delete(:title) + else + raise ArgumentError, "Resources require a type and title" + end - requiredopts(:scope, :source) + @ref = Reference.new(:type => type, :title => title, :scope => self.scope) - @ref.scope = self.scope + @params = {} - if tmpparams - tmpparams.each do |param| - # We use the method here, because it does type-checking. - set(param) + # Define all of the parameters + if params = options[:params] + options.delete(:params) + params.each do |param| + set_parameter(param) end end + + # Throw an exception if we've got any arguments left to set. + unless options.empty? + raise ArgumentError, "Resources do not accept %s" % options.keys.collect { |k| k.to_s }.join(", ") + end end - # Merge an override resource in. + # Merge an override resource in. This will throw exceptions if + # any overrides aren't allowed. def merge(resource) + # Test the resource scope, to make sure the resource is even allowed + # to override. + unless self.source.object_id == resource.source.object_id || resource.source.child_of?(self.source) + raise Puppet::ParseError.new("Only subclasses can override parameters", resource.line, resource.file) + end # Some of these might fail, but they'll fail in the way we want. resource.params.each do |name, param| - set(param) + override_parameter(param) end end + # Modify this resource in the Rails database. Poor design, yo. def modify_rails(db_resource) args = rails_args args.each do |param, value| @@ -223,23 +196,6 @@ class Puppet::Parser::Resource @@paramcheck end - # Verify that all passed parameters are valid. This throws an error if - # there's a problem, so we don't have to worry about the return value. - def paramcheck(param) - param = param.to_s - # Now make sure it's a valid argument to our class. These checks - # are organized in order of commonhood -- most types, it's a valid - # argument and paramcheck is enabled. - if @ref.typeclass.validattr?(param) - true - elsif %w{name title}.include?(param) # always allow these - true - elsif paramcheck? - self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" % - [param.inspect, @ref.type] - end - end - # A temporary occasion, until I get paths in the scopes figured out. def path to_s @@ -250,59 +206,6 @@ class Puppet::Parser::Resource @ref.to_s end - # You have to pass a Resource::Param to this. - def set(param, value = nil, source = nil) - if value and source - param = Puppet::Parser::Resource::Param.new( - :name => param, :value => value, :source => source - ) - elsif ! param.is_a?(Puppet::Parser::Resource::Param) - raise ArgumentError, "Must pass a parameter or all necessary values" - end - # Because definitions are now parse-time, I can paramcheck immediately. - paramcheck(param.name) - - if current = @params[param.name] - # This is where we'd ignore any equivalent values if we wanted to, - # but that would introduce a lot of really bad ordering issues. - if param.source.child_of?(current.source) - if param.add - # Merge with previous value. - param.value = [ current.value, param.value ].flatten - end - - # Replace it, keeping all of its info. - @params[param.name] = param - else - if Puppet[:trace] - puts caller - end - msg = "Parameter '%s' is already set on %s" % - [param.name, self.to_s] - if current.source.to_s != "" - msg += " by %s" % current.source - end - if current.file or current.line - fields = [] - fields << current.file if current.file - fields << current.line.to_s if current.line - msg += " at %s" % fields.join(":") - end - msg += "; cannot redefine" - error = Puppet::ParseError.new(msg) - error.file = param.file if param.file - error.line = param.line if param.line - raise error - end - else - if self.source == param.source or param.source.child_of?(self.source) - @params[param.name] = param - else - fail Puppet::ParseError, "Only subclasses can set parameters" - end - end - end - def tags unless defined? @tags @tags = scope.tags @@ -384,12 +287,102 @@ class Puppet::Parser::Resource return obj end - - def virtual? - self.virtual - end private + + # Add default values from our definition. + def add_defaults + scope.lookupdefaults(self.type).each do |name, param| + unless @params.include?(name) + self.debug "Adding default for %s" % name + + @params[name] = param + end + end + end + + # Add any metaparams defined in our scope. This actually adds any metaparams + # from any parent scope, and there's currently no way to turn that off. + def add_metaparams + Puppet::Type.eachmetaparam do |name| + # Skip metaparams that we already have defined. + next if @params[name] + if val = scope.lookupvar(name.to_s, false) + unless val == :undefined + set_parameter(name, val) + end + end + end + end + + # Add any overrides for this object. + def add_overrides + if overrides = scope.configuration.resource_overrides(self) + overrides.each do |over| + self.merge(over) + end + + # Remove the overrides, so that the configuration knows there + # are none left. + overrides.clear + end + end + + # Accept a parameter from an override. + def override_parameter(param) + # This can happen if the override is defining a new parameter, rather + # than replacing an existing one. + unless current = @params[param.name] + @params[param.name] = param + return + end + + # The parameter is already set. See if they're allowed to override it. + if param.source.child_of?(current.source) + if param.add + # Merge with previous value. + param.value = [ current.value, param.value ].flatten + end + + # Replace it, keeping all of its info. + @params[param.name] = param + else + if Puppet[:trace] + puts caller + end + msg = "Parameter '%s' is already set on %s" % + [param.name, self.to_s] + if current.source.to_s != "" + msg += " by %s" % current.source + end + if current.file or current.line + fields = [] + fields << current.file if current.file + fields << current.line.to_s if current.line + msg += " at %s" % fields.join(":") + end + msg += "; cannot redefine" + raise Puppet::ParseError.new(msg, param.line, param.file) + end + end + + # Verify that all passed parameters are valid. This throws an error if + # there's a problem, so we don't have to worry about the return value. + def paramcheck(param) + param = param.to_s + # Now make sure it's a valid argument to our class. These checks + # are organized in order of commonhood -- most types, it's a valid + # argument and paramcheck is enabled. + if @ref.typeclass.validattr?(param) + true + elsif %w{name title}.include?(param) # always allow these + true + elsif paramcheck? + self.fail Puppet::ParseError, "Invalid parameter '%s' for type '%s'" % + [param, @ref.type] + end + end + def rails_args return [:type, :title, :line, :exported].inject({}) do |hash, param| # 'type' isn't a valid column name, so we have to use another name. @@ -400,6 +393,26 @@ class Puppet::Parser::Resource hash end end -end -# $Id$ + # Define a parameter in our resource. + def set_parameter(param, value = nil) + if value + param = Puppet::Parser::Resource::Param.new( + :name => param, :value => value, :source => self.source + ) + elsif ! param.is_a?(Puppet::Parser::Resource::Param) + raise ArgumentError, "Must pass a parameter or all necessary values" + end + + # And store it in our parameter hash. + @params[param.name] = param + end + + # Make sure the resource's parameters are all valid for the type. + def validate + @params.each do |name, param| + # Make sure it's a valid parameter. + paramcheck(name) + end + end +end diff --git a/lib/puppet/parser/resource/reference.rb b/lib/puppet/parser/resource/reference.rb index 19d179660..b19dd2258 100644 --- a/lib/puppet/parser/resource/reference.rb +++ b/lib/puppet/parser/resource/reference.rb @@ -67,5 +67,3 @@ class Puppet::Parser::Resource::Reference @typeclass end end - -# $Id$ diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 6feeefc46..9e6739c53 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -15,36 +15,18 @@ class Puppet::Parser::Scope include Enumerable include Puppet::Util::Errors - attr_accessor :parent, :level, :interp, :source, :host - attr_accessor :name, :type, :topscope, :base, :keyword - attr_accessor :top, :translated, :exported, :virtual + attr_accessor :parent, :level, :parser, :source + attr_accessor :name, :type, :base, :keyword + attr_accessor :top, :translated, :exported, :virtual, :configuration - # Whether we behave declaratively. Note that it's a class variable, - # so all scopes behave the same. - @@declarative = true - - # Retrieve and set the declarative setting. - def self.declarative - return @@declarative - end - - def self.declarative=(val) - @@declarative = val + # Proxy accessors + def host + @configuration.node.name end - - # This handles the shared tables that all scopes have. They're effectively - # global tables, except that they're only global for a single scope tree, - # which is why I can't use class variables for them. - def self.sharedtable(*names) - attr_accessor(*names) - @@sharedtables ||= [] - @@sharedtables += names + def interpreter + @configuration.interpreter end - # This is probably not all that good of an idea, but... - # This way a parent can share its tables with all of its children. - sharedtable :classtable, :definedtable, :exportable, :overridetable, :collecttable - # Is the value true? This allows us to control the definition of truth # in one place. def self.true?(value) @@ -74,96 +56,9 @@ class Puppet::Parser::Scope end end - # Create a new child scope. - def child=(scope) - @children.push(scope) - - # Copy all of the shared tables over to the child. - @@sharedtables.each do |name| - scope.send(name.to_s + "=", self.send(name)) - end - end - - # Verify that the given object isn't defined elsewhere. - def chkobjectclosure(obj) - if exobj = @definedtable[obj.ref] - typeklass = Puppet::Type.type(obj.type) - if typeklass and ! typeklass.isomorphic? - Puppet.info "Allowing duplicate %s" % type - else - # Either it's a defined type, which are never - # isomorphic, or it's a non-isomorphic type. - msg = "Duplicate definition: %s is already defined" % obj.ref - - if exobj.file and exobj.line - msg << " in file %s at line %s" % - [exobj.file, exobj.line] - end - - if obj.line or obj.file - msg << "; cannot redefine" - end - - raise Puppet::ParseError.new(msg) - end - end - - return true - end - - # Return the scope associated with a class. This is just here so - # that subclasses can set their parent scopes to be the scope of - # their parent class. + # Retrieve a given class scope from the configuration. def class_scope(klass) - scope = if klass.respond_to?(:classname) - @classtable[klass.classname] - else - @classtable[klass] - end - - return nil unless scope - - if scope.nodescope? and ! klass.is_a?(AST::Node) - raise Puppet::ParseError, "Node %s has already been evaluated; cannot evaluate class with same name" % [klass.classname] - end - - scope - end - - # Return the list of collections. - def collections - @collecttable - end - - def declarative=(val) - self.class.declarative = val - end - - def declarative - self.class.declarative - end - - # Test whether a given scope is declarative. Even though it's - # a global value, the calling objects don't need to know that. - def declarative? - @@declarative - end - - # Remove a specific child. - def delete(child) - @children.delete(child) - end - - # Remove a resource from the various tables. This is only used when - # a resource maps to a definition and gets evaluated. - def deleteresource(resource) - if @definedtable[resource.ref] - @definedtable.delete(resource.ref) - end - - if @children.include?(resource) - @children.delete(resource) - end + configuration.class_scope(klass) end # Are we the top scope? @@ -171,40 +66,13 @@ class Puppet::Parser::Scope @level == 1 end - # Return a list of all of the defined classes. - def classlist - unless defined? @classtable - raise Puppet::DevError, "Scope did not receive class table" - end - return @classtable.keys.reject { |k| k == "" } - end - - # Yield each child scope in turn - def each - @children.each { |child| - yield child - } - end - - # Evaluate a list of classes. - def evalclasses(*classes) - retval = [] - classes.each do |klass| - if obj = findclass(klass) - obj.safeevaluate :scope => self - retval << klass - end - end - retval - end - def exported? self.exported end def findclass(name) @namespaces.each do |namespace| - if r = interp.findclass(namespace, name) + if r = parser.findclass(namespace, name) return r end end @@ -213,7 +81,7 @@ class Puppet::Parser::Scope def finddefine(name) @namespaces.each do |namespace| - if r = interp.finddefine(namespace, name) + if r = parser.finddefine(namespace, name) return r end end @@ -221,28 +89,11 @@ class Puppet::Parser::Scope end def findresource(string, name = nil) - if name - string = "%s[%s]" % [string.capitalize, name] - end - - @definedtable[string] - end - - # Recursively complete the whole tree, in preparation for - # translation or storage. - def finish - self.each do |obj| - obj.finish - end + configuration.findresource(string, name) end - # Initialize our new scope. Defaults to having no parent and to - # being declarative. + # Initialize our new scope. Defaults to having no parent. def initialize(hash = {}) - @parent = nil - @type = nil - @name = nil - @finished = false if hash.include?(:namespace) if n = hash[:namespace] @namespaces = [n] @@ -262,94 +113,15 @@ class Puppet::Parser::Scope @tags = [] - if @parent.nil? - unless hash.include?(:declarative) - hash[:declarative] = true - end - self.istop(hash[:declarative]) - @inside = nil - else - # This is here, rather than in newchild(), so that all - # of the later variable initialization works. - @parent.child = self - - @level = @parent.level + 1 - @interp = @parent.interp - @source = hash[:source] || @parent.source - @topscope = @parent.topscope - #@inside = @parent.inside # Used for definition inheritance - @host = @parent.host - @type ||= @parent.type - end - - # Our child scopes and objects - @children = [] - # The symbol table for this scope. This is where we store variables. @symtable = {} # All of the defaults set for types. It's a hash of hashes, # with the first key being the type, then the second key being # the parameter. - @defaultstable = Hash.new { |dhash,type| + @defaults = Hash.new { |dhash,type| dhash[type] = {} } - - unless @interp - raise Puppet::DevError, "Scopes require an interpreter" - end - end - - # Associate the object directly with the scope, so that contained objects - # can look up what container they're running within. - def inside(arg = nil) - return @inside unless arg - - old = @inside - @inside = arg - yield - ensure - #Puppet.warning "exiting %s" % @inside.name - @inside = old - end - - # Mark that we're the top scope, and set some hard-coded info. - def istop(declarative = true) - # the level is mostly used for debugging - @level = 1 - - # The table for storing class singletons. This will only actually - # be used by top scopes and node scopes. - @classtable = {} - - self.class.declarative = declarative - - # The table for all defined objects. - @definedtable = {} - - # The list of objects that will available for export. - @exportable = {} - - # The list of overrides. This is used to cache overrides on objects - # that don't exist yet. We store an array of each override. - @overridetable = Hash.new do |overs, ref| - overs[ref] = [] - end - - # Eventually, if we support sites, this will allow definitions - # of nodes with the same name in different sites. For now - # the top-level scope is always the only site scope. - @sitescope = true - - @namespaces = [""] - - # The list of collections that have been created. This is a global list, - # but they each refer back to the scope that created them. - @collecttable = [] - - @topscope = self - @type = "puppet" - @name = "top" end # Collect all of the defaults set at any higher scopes. @@ -360,16 +132,16 @@ class Puppet::Parser::Scope values = {} # first collect the values from the parents - unless @parent.nil? - @parent.lookupdefaults(type).each { |var,value| + unless parent.nil? + parent.lookupdefaults(type).each { |var,value| values[var] = value } end # then override them with any current values # this should probably be done differently - if @defaultstable.include?(type) - @defaultstable[type].each { |var,value| + if @defaults.include?(type) + @defaults[type].each { |var,value| values[var] = value } end @@ -379,17 +151,6 @@ class Puppet::Parser::Scope return values end - # Look up all of the exported objects of a given type. - def lookupexported(type) - @definedtable.find_all do |name, r| - r.type == type and r.exported? - end - end - - def lookupoverrides(obj) - @overridetable[obj.ref] - end - # Look up a defined type. def lookuptype(name) finddefine(name) || findclass(name) @@ -426,7 +187,7 @@ class Puppet::Parser::Scope return @symtable[name] end elsif self.parent - return @parent.lookupvar(name, usestring) + return parent.lookupvar(name, usestring) elsif usestring return "" else @@ -438,16 +199,9 @@ class Puppet::Parser::Scope @namespaces.dup end - # Add a collection to the global list. - def newcollection(coll) - @collecttable << coll - end - - # Create a new scope. - def newscope(hash = {}) - hash[:parent] = self - #debug "Creating new scope, level %s" % [self.level + 1] - return Puppet::Parser::Scope.new(hash) + # Create a new scope and set these options. + def newscope(options = {}) + configuration.newscope(self, options) end # Is this class for a node? This is used to make sure that @@ -458,10 +212,23 @@ class Puppet::Parser::Scope defined?(@nodescope) and @nodescope end - # Return the list of remaining overrides. - def overrides - #@overridetable.collect { |name, overs| overs }.flatten - @overridetable.values.flatten + # We probably shouldn't cache this value... But it's a lot faster + # than doing lots of queries. + def parent + unless defined?(@parent) + @parent = configuration.parent(self) + end + @parent + end + + # Return the list of scopes up to the top scope, ordered with our own first. + # This is used for looking up variables and defaults. + def scope_path + if parent + [self, parent.scope_path].flatten.compact + else + [self] + end end def resources @@ -472,66 +239,49 @@ class Puppet::Parser::Scope # that gets inherited from the top scope down, rather than a global # hash. We store the object ID, not class name, so that we # can support multiple unrelated classes with the same name. - def setclass(obj) - if obj.is_a?(AST::HostClass) - unless obj.classname + def setclass(klass) + if klass.is_a?(AST::HostClass) + unless name = klass.classname raise Puppet::DevError, "Got a %s with no fully qualified name" % - obj.class + klass.class end - @classtable[obj.classname] = self + @configuration.class_set(name, self) else - raise Puppet::DevError, "Invalid class %s" % obj.inspect + raise Puppet::DevError, "Invalid class %s" % klass.inspect end - if obj.is_a?(AST::Node) + if klass.is_a?(AST::Node) @nodescope = true end nil end - # Set all of our facts in the top-level scope. - def setfacts(facts) - facts.each { |var, value| - self.setvar(var, value) - } - end - # Add a new object to our object table and the global list, and do any necessary # checks. - def setresource(obj) - self.chkobjectclosure(obj) - - @children << obj + def setresource(resource) + @configuration.store_resource(self, resource) # Mark the resource as virtual or exported, as necessary. if self.exported? - obj.exported = true + resource.exported = true elsif self.virtual? - obj.virtual = true + resource.virtual = true end - # The global table - @definedtable[obj.ref] = obj - - return obj + return resource end # Override a parameter in an existing object. If the object does not yet # exist, then cache the override in a global table, so it can be flushed # at the end. def setoverride(resource) - resource.override = true - if obj = @definedtable[resource.ref] - obj.merge(resource) - else - @overridetable[resource.ref] << resource - end + @configuration.store_override(resource) end # Set defaults for a type. The typename should already be downcased, # so that the syntax is isolated. We don't do any kind of type-checking # here; instead we let the resource do it when the defaults are used. def setdefaults(type, params) - table = @defaultstable[type] + table = @defaults[type] # if we got a single param, it'll be in its own array params = [params] unless params.is_a?(Array) @@ -539,17 +289,8 @@ class Puppet::Parser::Scope params.each { |param| #Puppet.debug "Default for %s is %s => %s" % # [type,ary[0].inspect,ary[1].inspect] - if @@declarative - if table.include?(param.name) - self.fail "Default already defined for %s { %s }" % - [type,param.name] - end - else - if table.include?(param.name) - # we should maybe allow this warning to be turned off... - Puppet.warning "Replacing default for %s { %s }" % - [type,param.name] - end + if table.include?(param.name) + raise Puppet::ParseError.new("Default already defined for %s { %s }; cannot redefine" % [type, param.name], param.line, param.file) end table[param.name] = param } @@ -557,23 +298,19 @@ class Puppet::Parser::Scope # Set a variable in the current scope. This will override settings # in scopes above, but will not allow variables in the current scope - # to be reassigned if we're declarative (which is the default). + # to be reassigned. def setvar(name,value, file = nil, line = nil) #Puppet.debug "Setting %s to '%s' at level %s" % # [name.inspect,value,self.level] if @symtable.include?(name) - if @@declarative - error = Puppet::ParseError.new("Cannot reassign variable %s" % name) - if file - error.file = file - end - if line - error.line = line - end - raise error - else - Puppet.warning "Reassigning %s to %s" % [name,value] + error = Puppet::ParseError.new("Cannot reassign variable %s" % name) + if file + error.file = file + end + if line + error.line = line end + raise error end @symtable[name] = value end @@ -665,9 +402,9 @@ class Puppet::Parser::Scope unless ! defined? @type or @type.nil? or @type == "" tmp << @type.to_s end - if @parent - #info "Looking for tags in %s" % @parent.type - @parent.tags.each { |tag| + if parent + #info "Looking for tags in %s" % parent.type + parent.tags.each { |tag| if tag.nil? or tag == "" Puppet.debug "parent returned tag %s" % tag.inspect next @@ -689,19 +426,9 @@ class Puppet::Parser::Scope end end - # Convert all of our objects as necessary. - def translate - ret = @children.collect do |child| - case child - when Puppet::Parser::Resource - child.to_trans - when self.class - child.translate - else - devfail "Got %s for translation" % child.class - end - end.reject { |o| o.nil? } - bucket = Puppet::TransBucket.new ret + # Convert our resource to a TransBucket. + def to_trans + bucket = Puppet::TransBucket.new([]) case self.type when "": bucket.type = "main" @@ -722,19 +449,6 @@ class Puppet::Parser::Scope end end - # Return an array of all of the unevaluated objects - def unevaluated - ary = @definedtable.find_all do |name, object| - ! object.builtin? and ! object.evaluated? - end.collect { |name, object| object } - - if ary.empty? - return nil - else - return ary - end - end - def virtual? self.virtual || self.exported? end diff --git a/lib/puppet/parser/templatewrapper.rb b/lib/puppet/parser/templatewrapper.rb index 3b8cc3a3a..9c3c6c0d0 100644 --- a/lib/puppet/parser/templatewrapper.rb +++ b/lib/puppet/parser/templatewrapper.rb @@ -15,8 +15,8 @@ class Puppet::Parser::TemplateWrapper end # We'll only ever not have an interpreter in testing, but, eh. - if @scope.interp - @scope.interp.newfile(@file) + if @scope.parser + @scope.parser.watch_file(@file) end end diff --git a/lib/puppet/rails/host.rb b/lib/puppet/rails/host.rb index ca1e10c93..b7ca4c2e4 100644 --- a/lib/puppet/rails/host.rb +++ b/lib/puppet/rails/host.rb @@ -28,7 +28,7 @@ class Puppet::Rails::Host < ActiveRecord::Base end # Store our host in the database. - def self.store(hash) + def self.store(node, resources) unless name = hash[:name] raise ArgumentError, "You must specify the hostname for storage" end @@ -40,23 +40,19 @@ class Puppet::Rails::Host < ActiveRecord::Base #unless host = find_by_name(name) seconds = Benchmark.realtime { unless host = find_by_name(name) - host = new(:name => name) + host = new(:name => node.name) end } Puppet.notice("Searched for host in %0.2f seconds" % seconds) if defined?(Puppet::TIME_DEBUG) - if ip = hash[:facts]["ipaddress"] + if ip = node.parameters["ipaddress"] host.ip = ip end # Store the facts into the database. - host.setfacts(hash[:facts]) - - unless hash[:resources] - raise ArgumentError, "You must pass resources" - end + host.setfacts node.parameters seconds = Benchmark.realtime { - host.setresources(hash[:resources]) + host.setresources(resources) } Puppet.notice("Handled resources in %0.2f seconds" % seconds) if defined?(Puppet::TIME_DEBUG) diff --git a/lib/puppet/rails/resource.rb b/lib/puppet/rails/resource.rb index 19aeb9205..785c63419 100644 --- a/lib/puppet/rails/resource.rb +++ b/lib/puppet/rails/resource.rb @@ -104,16 +104,16 @@ class Puppet::Rails::Resource < ActiveRecord::Base end hash[:scope] = scope hash[:source] = scope.source - obj = Puppet::Parser::Resource.new(hash) - + hash[:params] = [] names = [] self.param_names.each do |pname| # We can get the same name multiple times because of how the # db layout works. next if names.include?(pname.name) names << pname.name - obj.set(pname.to_resourceparam(self, scope.source)) + hash[:params] << pname.to_resourceparam(self, scope.source) end + obj = Puppet::Parser::Resource.new(hash) # Store the ID, so we can check if we're re-collecting the same resource. obj.rails_id = self.id @@ -121,5 +121,3 @@ class Puppet::Rails::Resource < ActiveRecord::Base return obj end end - -# $Id$ diff --git a/lib/puppet/reference/node_sources.rb b/lib/puppet/reference/node_sources.rb new file mode 100644 index 000000000..33a4c539c --- /dev/null +++ b/lib/puppet/reference/node_sources.rb @@ -0,0 +1,7 @@ +noderef = Puppet::Util::Reference.newreference :node_source, :doc => "Sources of node configuration information" do + Puppet::Network::Handler.node.sourcedocs +end + +nodref.header = " +Nodes can be searched for in different locations. This document describes those different locations. +" diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index d1d14977c..5a10f5344 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -201,7 +201,7 @@ module Util raise Puppet::DevError, "Failed to provide level to :benchmark" end - unless object.respond_to? level + unless level == :none or object.respond_to? level raise Puppet::DevError, "Benchmarked object does not respond to %s" % level end diff --git a/test/language/ast.rb b/test/language/ast.rb index 9e00c610d..38e658edb 100755 --- a/test/language/ast.rb +++ b/test/language/ast.rb @@ -49,24 +49,21 @@ class TestAST < Test::Unit::TestCase # Make sure our override object behaves "correctly" def test_override - interp, scope, source = mkclassframing + scope = mkscope ref = nil assert_nothing_raised do - ref = resourceoverride("resource", "yaytest", "one" => "yay", "two" => "boo") + ref = resourceoverride("file", "/yayness", "owner" => "blah", "group" => "boo") end + Puppet::Parser::Resource.expects(:new).with { |o| o.is_a?(Hash) }.returns(:override) + scope.expects(:setoverride).with(:override) ret = nil assert_nothing_raised do ret = ref.evaluate :scope => scope end - assert_instance_of(Puppet::Parser::Resource, ret) - - assert(ret.override?, "Resource was not an override resource") - - assert(scope.overridetable[ret.ref].include?(ret), - "Was not stored in the override table") + assert_equal(:override, ret, "Did not return override") end # make sure our resourcedefaults ast object works correctly. @@ -97,16 +94,16 @@ class TestAST < Test::Unit::TestCase end def test_node - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope + parser = scope.configuration.parser # Define a base node - basenode = interp.newnode "basenode", :code => AST::ASTArray.new(:children => [ + basenode = parser.newnode "basenode", :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/base", "owner" => "root") ]) # Now define a subnode - nodes = interp.newnode ["mynode", "othernode"], + nodes = parser.newnode ["mynode", "othernode"], :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/mynode", "owner" => "root"), resourcedef("file", "/tmp/basenode", "owner" => "daemon") @@ -116,9 +113,9 @@ class TestAST < Test::Unit::TestCase # Make sure we can find them all. %w{mynode othernode}.each do |node| - assert(interp.nodesearch_code(node), "Could not find %s" % node) + assert(parser.nodes[node], "Could not find %s" % node) end - mynode = interp.nodesearch_code("mynode") + mynode = parser.nodes["mynode"] # Now try evaluating the node assert_nothing_raised do @@ -135,9 +132,9 @@ class TestAST < Test::Unit::TestCase assert_equal("daemon", basefile[:owner]) # Now make sure we can evaluate nodes with parents - child = interp.newnode(%w{child}, :parent => "basenode").shift + child = parser.newnode(%w{child}, :parent => "basenode").shift - newscope = mkscope :interp => interp + newscope = mkscope :parser => parser assert_nothing_raised do child.evaluate :scope => newscope end @@ -147,8 +144,7 @@ class TestAST < Test::Unit::TestCase end def test_collection - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope coll = nil assert_nothing_raised do @@ -165,7 +161,8 @@ class TestAST < Test::Unit::TestCase assert_instance_of(Puppet::Parser::Collector, ret) # Now make sure we get it back from the scope - assert_equal([ret], scope.collections) + colls = scope.configuration.instance_variable_get("@collections") + assert_equal([ret], colls, "Did not store collector in config's collection list") end def test_virtual_collexp diff --git a/test/language/ast/component.rb b/test/language/ast/component.rb index 40543e9ab..cf0cce976 100755 --- a/test/language/ast/component.rb +++ b/test/language/ast/component.rb @@ -16,11 +16,11 @@ class TestASTComponent < Test::Unit::TestCase include PuppetTest::ResourceTesting AST = Puppet::Parser::AST - def test_component - interp, scope, source = mkclassframing + def test_initialize + parser = mkparser # Create a new definition - klass = interp.newdefine "yayness", + klass = parser.newdefine "yayness", :arguments => [["owner", stringobj("nobody")], %w{mode}], :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/$name", @@ -35,27 +35,41 @@ class TestASTComponent < Test::Unit::TestCase [:random, "random"].each do |var| assert(! klass.validattr?(var), "%s was considered valid" % var.inspect) end + + end + + def test_evaluate + parser = mkparser + config = mkconfig + scope = config.topscope + klass = parser.newdefine "yayness", + :arguments => [["owner", stringobj("nobody")], %w{mode}], + :code => AST::ASTArray.new( + :children => [resourcedef("file", "/tmp/$name", + "owner" => varref("owner"), "mode" => varref("mode"))] + ) + # Now call it a couple of times # First try it without a required param - assert_raise(Puppet::ParseError) do - klass.evaluate(:scope => scope, + assert_raise(Puppet::ParseError, "Did not fail when a required parameter was not provided") do + klass.evaluate_resource(:scope => scope, :name => "bad", :arguments => {"owner" => "nobody"} ) end # And make sure it didn't create the file - assert_nil(scope.findresource("File[/tmp/bad]"), + assert_nil(config.findresource("File[/tmp/bad]"), "Made file with invalid params") assert_nothing_raised do - klass.evaluate(:scope => scope, + klass.evaluate_resource(:scope => scope, :title => "first", :arguments => {"mode" => "755"} ) end - firstobj = scope.findresource("File[/tmp/first]") + firstobj = config.findresource("File[/tmp/first]") assert(firstobj, "Did not create /tmp/first obj") assert_equal("file", firstobj.type) @@ -65,7 +79,7 @@ class TestASTComponent < Test::Unit::TestCase # Make sure we can't evaluate it with the same args assert_raise(Puppet::ParseError) do - klass.evaluate(:scope => scope, + klass.evaluate_resource(:scope => scope, :title => "first", :arguments => {"mode" => "755"} ) @@ -73,13 +87,13 @@ class TestASTComponent < Test::Unit::TestCase # Now create another with different args assert_nothing_raised do - klass.evaluate(:scope => scope, + klass.evaluate_resource(:scope => scope, :title => "second", :arguments => {"mode" => "755", "owner" => "daemon"} ) end - secondobj = scope.findresource("File[/tmp/second]") + secondobj = config.findresource("File[/tmp/second]") assert(secondobj, "Did not create /tmp/second obj") assert_equal("file", secondobj.type) @@ -90,7 +104,7 @@ class TestASTComponent < Test::Unit::TestCase # #539 - definitions should support both names and titles def test_names_and_titles - interp, scope, source = mkclassframing + parser, scope, source = mkclassframing [ {:name => "one", :title => "two"}, @@ -98,7 +112,7 @@ class TestASTComponent < Test::Unit::TestCase ].each_with_index do |hash, i| # Create a definition that uses both name and title - klass = interp.newdefine "yayness%s" % i + klass = parser.newdefine "yayness%s" % i subscope = klass.subscope(scope, "yayness%s" % i) @@ -110,7 +124,7 @@ class TestASTComponent < Test::Unit::TestCase end args[:scope] = scope assert_nothing_raised("Could not evaluate definition with %s" % hash.inspect) do - klass.evaluate(args) + klass.evaluate_resource(args) end name = hash[:name] || hash[:title] @@ -133,8 +147,8 @@ class TestASTComponent < Test::Unit::TestCase # Testing the root cause of #615. We should be using the fqname for the type, instead # of just the short name. def test_fully_qualified_types - interp = mkinterp - klass = interp.newclass("one::two") + parser = mkparser + klass = parser.newclass("one::two") assert_equal("one::two", klass.classname, "Class did not get fully qualified class name") end diff --git a/test/language/ast/hostclass.rb b/test/language/ast/hostclass.rb index 051bee36c..f093504ec 100755 --- a/test/language/ast/hostclass.rb +++ b/test/language/ast/hostclass.rb @@ -17,10 +17,11 @@ class TestASTHostClass < Test::Unit::TestCase AST = Puppet::Parser::AST def test_hostclass - interp, scope, source = mkclassframing + scope = mkscope + parser = scope.configuration.parser # Create the class we're testing, first with no parent - klass = interp.newclass "first", + klass = parser.newclass "first", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp", "owner" => "nobody", "mode" => "755")] @@ -43,13 +44,13 @@ class TestASTHostClass < Test::Unit::TestCase assert_equal("755", tmp[:mode]) # Now create a couple more classes. - newbase = interp.newclass "newbase", + newbase = parser.newclass "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/other", "owner" => "nobody", "mode" => "644")] ) - newsub = interp.newclass "newsub", + newsub = parser.newclass "newsub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourcedef("file", "/tmp/yay", @@ -60,7 +61,7 @@ class TestASTHostClass < Test::Unit::TestCase ) # Override a different variable in the top scope. - moresub = interp.newclass "moresub", + moresub = parser.newclass "moresub", :parent => "newbase", :code => AST::ASTArray.new( :children => [resourceoverride("file", "/tmp/other", @@ -92,19 +93,20 @@ class TestASTHostClass < Test::Unit::TestCase # Make sure that classes set their namespaces to themselves. This # way they start looking for definitions in their own namespace. def test_hostclass_namespace - interp, scope, source = mkclassframing + scope = mkscope + parser = scope.configuration.parser # Create a new class klass = nil assert_nothing_raised do - klass = interp.newclass "funtest" + klass = parser.newclass "funtest" end # Now define a definition in that namespace define = nil assert_nothing_raised do - define = interp.newdefine "funtest::mydefine" + define = parser.newdefine "funtest::mydefine" end assert_equal("funtest", klass.namespace, @@ -127,17 +129,18 @@ class TestASTHostClass < Test::Unit::TestCase # At the same time, make sure definitions in the parent class can be # found within the subclass (#517). def test_parent_scope_from_parentclass - interp = mkinterp + scope = mkscope + parser = scope.configuration.parser - interp.newclass("base") - fun = interp.newdefine("base::fun") - interp.newclass("middle", :parent => "base") - interp.newclass("sub", :parent => "middle") - scope = mkscope :interp => interp + parser.newclass("base") + fun = parser.newdefine("base::fun") + parser.newclass("middle", :parent => "base") + parser.newclass("sub", :parent => "middle") + scope = mkscope :parser => parser ret = nil assert_nothing_raised do - ret = scope.evalclasses("sub") + ret = scope.configuration.evaluate_classes(["sub"]) end subscope = scope.class_scope(scope.findclass("sub")) diff --git a/test/language/ast/resourceref.rb b/test/language/ast/resourceref.rb index 7b7889dc1..a3d6775a2 100755 --- a/test/language/ast/resourceref.rb +++ b/test/language/ast/resourceref.rb @@ -19,13 +19,13 @@ class TestASTResourceRef < Test::Unit::TestCase def setup super - @interp = mkinterp - @scope = mkscope :interp => @interp + @scope = mkscope + @parser = @scope.configuration.parser end def test_evaluate - @interp.newdefine "one::two" - @interp.newdefine "one-two" + @parser.newdefine "one::two" + @parser.newdefine "one-two" [%w{file /tmp/yay}, %w{one::two three}, %w{one-two three}].each do |type, title| ref = newref(type, title) @@ -41,9 +41,9 @@ class TestASTResourceRef < Test::Unit::TestCase # Related to #706, make sure resource references correctly translate to qualified types. def test_scoped_references - @interp.newdefine "one" - @interp.newdefine "one::two" - @interp.newdefine "three" + @parser.newdefine "one" + @parser.newdefine "one::two" + @parser.newdefine "three" twoscope = @scope.newscope(:type => "one", :namespace => "one") assert(twoscope.finddefine("two"), "Could not find 'two' definition") title = "title" @@ -70,8 +70,8 @@ class TestASTResourceRef < Test::Unit::TestCase end # Now run the same tests, but with the classes - @interp.newclass "four" - @interp.newclass "one::five" + @parser.newclass "four" + @parser.newclass "one::five" # First try an unqualified type assert_equal("four", newref("class", "four").evaluate(:scope => twoscope).title, diff --git a/test/language/collector.rb b/test/language/collector.rb index bdcaf4aec..a4119929f 100755 --- a/test/language/collector.rb +++ b/test/language/collector.rb @@ -16,7 +16,8 @@ class TestCollector < Test::Unit::TestCase def setup super Puppet[:trace] = false - @interp, @scope, @source = mkclassframing + @scope = mkscope + @config = @scope.configuration end # Test just collecting a specific resource. This is used by the 'realize' @@ -32,7 +33,7 @@ class TestCollector < Test::Unit::TestCase assert_nothing_raised do coll.resources = ["File[/tmp/virtual1]", "File[/tmp/virtual3]"] end - @scope.newcollection(coll) + @config.add_collection(coll) # Evaluate the collector and make sure it doesn't fail with no resources # found yet @@ -62,7 +63,7 @@ class TestCollector < Test::Unit::TestCase "Resource got realized") # Make sure that the collection is still there - assert(@scope.collections.include?(coll), "collection was deleted too soon") + assert(@config.collections.include?(coll), "collection was deleted too soon") # Now add our third resource three = mkresource(:type => "file", :title => "/tmp/virtual3", @@ -76,7 +77,7 @@ class TestCollector < Test::Unit::TestCase assert(! three.virtual?, "three is still virtual") # And make sure that the collection got deleted from the scope's list - assert(@scope.collections.empty?, "collection was not deleted") + assert(@config.collections.empty?, "collection was not deleted") end def test_virtual @@ -102,10 +103,10 @@ class TestCollector < Test::Unit::TestCase end # Set it in our scope - @scope.newcollection(coll) + @config.add_collection(coll) # Make sure it's in the collections - assert(@scope.collections.include?(coll), "collection was not added") + assert(@config.collections.include?(coll), "collection was not added") # And try to collect the virtual resources. ret = nil @@ -148,7 +149,7 @@ class TestCollector < Test::Unit::TestCase coll = Puppet::Parser::Collector.new(@scope, "file", nil, nil, :virtual) end - @scope.newcollection(coll) + @config.add_collection(coll) # run the collection and make sure it doesn't get deleted, since it # didn't return anything @@ -157,7 +158,7 @@ class TestCollector < Test::Unit::TestCase "Evaluate returned incorrect value") end - assert_equal([coll], @scope.collections, "Collection was deleted") + assert_equal([coll], @config.collections, "Collection was deleted") # Make a resource one = mkresource(:type => "file", :title => "/tmp/virtual1", @@ -170,7 +171,7 @@ class TestCollector < Test::Unit::TestCase "Evaluate returned incorrect value") end - assert_equal([coll], @scope.collections, "Collection was deleted") + assert_equal([coll], @config.collections, "Collection was deleted") assert_equal(false, one.virtual?, "One was not realized") end diff --git a/test/language/configuration.rb b/test/language/configuration.rb new file mode 100755 index 000000000..a17b5a7ae --- /dev/null +++ b/test/language/configuration.rb @@ -0,0 +1,745 @@ +#!/usr/bin/env ruby + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'mocha' +require 'puppettest' +require 'puppettest/parsertesting' +require 'puppet/parser/configuration' + +# Test our configuration object. +class TestConfiguration < Test::Unit::TestCase + include PuppetTest + include PuppetTest::ParserTesting + + Config = Puppet::Parser::Configuration + Scope = Puppet::Parser::Scope + Node = Puppet::Network::Handler.handler(:node) + SimpleNode = Puppet::Node + + def mknode(name = "foo") + @node = SimpleNode.new(name) + end + + def mkparser + # This should mock an interpreter + @parser = mock 'parser' + end + + def mkconfig(options = {}) + if node = options[:node] + options.delete(:node) + else + node = mknode + end + @config = Config.new(node, mkparser, options) + end + + def test_initialize + config = nil + assert_nothing_raised("Could not init config with all required options") do + config = Config.new("foo", "parser") + end + + assert_equal("foo", config.node, "Did not set node correctly") + assert_equal("parser", config.parser, "Did not set parser correctly") + + # We're not testing here whether we call initvars, because it's too difficult to + # mock. + + # Now try it with some options + assert_nothing_raised("Could not init config with extra options") do + config = Config.new("foo", "parser", :ast_nodes => false) + end + + assert_equal(false, config.ast_nodes?, "Did not set ast_nodes? correctly") + end + + def test_initvars + config = mkconfig + [:class_scopes, :resource_table, :exported_resources, :resource_overrides].each do |table| + assert_instance_of(Hash, config.send(:instance_variable_get, "@#{table}"), "Did not set %s table correctly" % table) + end + assert_instance_of(Scope, config.topscope, "Did not create a topscope") + graph = config.instance_variable_get("@scope_graph") + assert_instance_of(GRATR::Digraph, graph, "Did not create scope graph") + assert(graph.vertex?(config.topscope), "Did not add top scope as a vertex in the graph") + end + + # Make sure we store and can retrieve references to classes and their scopes. + def test_class_set_and_class_scope + klass = mock 'ast_class' + klass.expects(:classname).returns("myname") + + config = mkconfig + config.expects(:tag).with("myname") + + assert_nothing_raised("Could not set class") do + config.class_set "myname", "myscope" + end + # First try to retrieve it by name. + assert_equal("myscope", config.class_scope("myname"), "Could not retrieve class scope by name") + + # Then by object + assert_equal("myscope", config.class_scope(klass), "Could not retrieve class scope by object") + end + + def test_classlist + config = mkconfig + + config.class_set "", "empty" + config.class_set "one", "yep" + config.class_set "two", "nope" + + # Make sure our class list is correct + assert_equal(%w{one two}.sort, config.classlist.sort, "Did not get correct class list") + end + + # Make sure collections get added to our internal array + def test_add_collection + config = mkconfig + assert_nothing_raised("Could not add collection") do + config.add_collection "nope" + end + assert_equal(%w{nope}, config.instance_variable_get("@collections"), "Did not add collection") + end + + # Make sure we create a graph of scopes. + def test_newscope + config = mkconfig + graph = config.instance_variable_get("@scope_graph") + assert_instance_of(Scope, config.topscope, "Did not create top scope") + assert_instance_of(GRATR::Digraph, graph, "Did not create graph") + + assert(graph.vertex?(config.topscope), "The top scope is not a vertex in the graph") + + # Now that we've got the top scope, create a new, subscope + subscope = nil + assert_nothing_raised("Could not create subscope") do + subscope = config.newscope(config.topscope) + end + assert_instance_of(Scope, subscope, "Did not create subscope") + assert(graph.edge?(config.topscope, subscope), "An edge between top scope and subscope was not added") + + # Make sure a scope can find its parent. + assert(config.parent(subscope), "Could not look up parent scope on configuration") + assert_equal(config.topscope.object_id, config.parent(subscope).object_id, "Did not get correct parent scope from configuration") + assert_equal(config.topscope.object_id, subscope.parent.object_id, "Scope did not correctly retrieve its parent scope") + + # Now create another, this time specifying options + another = nil + assert_nothing_raised("Could not create subscope") do + another = config.newscope(subscope, :name => "testing") + end + assert_equal("testing", another.name, "did not set scope option correctly") + assert_instance_of(Scope, another, "Did not create second subscope") + assert(graph.edge?(subscope, another), "An edge between parent scope and second subscope was not added") + + # Make sure it can find its parent. + assert(config.parent(another), "Could not look up parent scope of second subscope on configuration") + assert_equal(subscope.object_id, config.parent(another).object_id, "Did not get correct parent scope of second subscope from configuration") + assert_equal(subscope.object_id, another.parent.object_id, "Second subscope did not correctly retrieve its parent scope") + + # And make sure both scopes show up in the right order in the search path + assert_equal([another.object_id, subscope.object_id, config.topscope.object_id], another.scope_path.collect { |p| p.object_id }, + "Did not get correct scope path") + end + + # The heart of the action. + def test_compile + config = mkconfig + [:set_node_parameters, :evaluate_main, :evaluate_ast_node, :evaluate_classes, :evaluate_generators, :fail_on_unevaluated, :finish].each do |method| + config.expects(method) + end + config.expects(:extract).returns(:config) + assert_equal(:config, config.compile, "Did not return the results of the extraction") + end + + # Test setting the node's parameters into the top scope. + def test_set_node_parameters + config = mkconfig + @node.parameters = {"a" => "b", "c" => "d"} + scope = config.topscope + @node.parameters.each do |param, value| + scope.expects(:setvar).with(param, value) + end + + assert_nothing_raised("Could not call 'set_node_parameters'") do + config.send(:set_node_parameters) + end + end + + # Test that we can evaluate the main class, which is the one named "" in namespace + # "". + def test_evaluate_main + config = mkconfig + main = mock 'main_class' + config.topscope.expects(:source=).with(main) + main.expects(:safeevaluate).with(:scope => config.topscope, :nosubscope => true) + @parser.expects(:findclass).with("", "").returns(main) + + assert_nothing_raised("Could not call evaluate_main") do + config.send(:evaluate_main) + end + end + + # Make sure we either don't look for nodes, or that we find and evaluate the right object. + def test_evaluate_ast_node + # First try it with ast_nodes disabled + config = mkconfig :ast_nodes => false + config.expects(:ast_nodes?).returns(false) + config.parser.expects(:nodes).never + + assert_nothing_raised("Could not call evaluate_ast_node when ast nodes are disabled") do + config.send(:evaluate_ast_node) + end + + # Now try it with them enabled, but no node found. + nodes = mock 'node_hash' + config = mkconfig :ast_nodes => true + config.expects(:ast_nodes?).returns(true) + config.parser.expects(:nodes).returns(nodes).times(4) + + # Set some names for our test + @node.names = %w{a b c} + nodes.expects(:[]).with("a").returns(nil) + nodes.expects(:[]).with("b").returns(nil) + nodes.expects(:[]).with("c").returns(nil) + + # It should check this last, of course. + nodes.expects(:[]).with("default").returns(nil) + + # And make sure the lack of a node throws an exception + assert_raise(Puppet::ParseError, "Did not fail when we couldn't find an ast node") do + config.send(:evaluate_ast_node) + end + + # Finally, make sure it works dandily when we have a node + nodes = mock 'hash' + config = mkconfig :ast_nodes => true + config.expects(:ast_nodes?).returns(true) + config.parser.expects(:nodes).returns(nodes).times(3) + + node = mock 'node' + node.expects(:safeevaluate).with(:scope => config.topscope) + # Set some names for our test + @node.names = %w{a b c} + nodes.expects(:[]).with("a").returns(nil) + nodes.expects(:[]).with("b").returns(nil) + nodes.expects(:[]).with("c").returns(node) + nodes.expects(:[]).with("default").never + + # And make sure the lack of a node throws an exception + assert_nothing_raised("Failed when a node was found") do + config.send(:evaluate_ast_node) + end + + # Lastly, check when we actually find the default. + nodes = mock 'hash' + config = mkconfig :ast_nodes => true + config.expects(:ast_nodes?).returns(true) + config.parser.expects(:nodes).returns(nodes).times(4) + + node = mock 'node' + node.expects(:safeevaluate).with(:scope => config.topscope) + # Set some names for our test + @node.names = %w{a b c} + nodes.expects(:[]).with("a").returns(nil) + nodes.expects(:[]).with("b").returns(nil) + nodes.expects(:[]).with("c").returns(nil) + nodes.expects(:[]).with("default").returns(node) + + # And make sure the lack of a node throws an exception + assert_nothing_raised("Failed when a node was found") do + config.send(:evaluate_ast_node) + end + end + + # Make sure our config object handles tags appropriately. + def test_tags + config = mkconfig + config.send(:tag, "one") + assert_equal(%w{one}, config.send(:tags), "Did not add tag") + + config.send(:tag, "two", "three") + assert_equal(%w{one two three}, config.send(:tags), "Did not add new tags") + + config.send(:tag, "two") + assert_equal(%w{one two three}, config.send(:tags), "Allowed duplicate tag") + end + + def test_evaluate_classes + config = mkconfig + classes = { + "one" => mock('class one'), + "three" => mock('class three') + } + + classes.each do |name, obj| + config.parser.expects(:findclass).with("", name).returns(obj) + obj.expects(:safeevaluate).with(:scope => config.topscope) + end + %w{two four}.each do |name| + config.parser.expects(:findclass).with("", name).returns(nil) + end + + config.expects(:tag).with("two") + config.expects(:tag).with("four") + + @node.classes = %w{one two three four} + result = nil + assert_nothing_raised("could not call evaluate_classes") do + result = config.send(:evaluate_classes) + end + assert_equal(%w{one three}, result, "Did not return the list of evaluated classes") + end + + def test_evaluate_collections + config = mkconfig + + colls = [] + + # Make sure we return false when there's nothing there. + assert(! config.send(:evaluate_collections), "Returned true when there were no collections") + + # And when the collections fail to evaluate. + colls << mock("coll1-false") + colls << mock("coll2-false") + colls.each { |c| c.expects(:evaluate).returns(false) } + + config.instance_variable_set("@collections", colls) + assert(! config.send(:evaluate_collections), "Returned true when collections both evaluated nothing") + + # Now have one of the colls evaluate + colls.clear + colls << mock("coll1-one-true") + colls << mock("coll2-one-true") + colls[0].expects(:evaluate).returns(true) + colls[1].expects(:evaluate).returns(false) + assert(config.send(:evaluate_collections), "Did not return true when one collection evaluated true") + + # And have them both eval true + colls.clear + colls << mock("coll1-both-true") + colls << mock("coll2-both-true") + colls[0].expects(:evaluate).returns(true) + colls[1].expects(:evaluate).returns(true) + assert(config.send(:evaluate_collections), "Did not return true when both collections evaluated true") + end + + def test_unevaluated_resources + config = mkconfig + resources = {} + config.instance_variable_set("@resource_table", resources) + + # First test it when the table is empty + assert_nil(config.send(:unevaluated_resources), "Somehow found unevaluated resources in an empty table") + + # Then add a builtin resources + resources["one"] = mock("builtin only") + resources["one"].expects(:builtin?).returns(true) + assert_nil(config.send(:unevaluated_resources), "Considered a builtin resource unevaluated") + + # And do both builtin and non-builtin but already evaluated + resources.clear + resources["one"] = mock("builtin (with eval)") + resources["one"].expects(:builtin?).returns(true) + resources["two"] = mock("evaled (with builtin)") + resources["two"].expects(:builtin?).returns(false) + resources["two"].expects(:evaluated?).returns(true) + assert_nil(config.send(:unevaluated_resources), "Considered either a builtin or evaluated resource unevaluated") + + # Now a single unevaluated resource. + resources.clear + resources["one"] = mock("unevaluated") + resources["one"].expects(:builtin?).returns(false) + resources["one"].expects(:evaluated?).returns(false) + assert_equal([resources["one"]], config.send(:unevaluated_resources), "Did not find unevaluated resource") + + # With two uneval'ed resources, and an eval'ed one thrown in + resources.clear + resources["one"] = mock("unevaluated one") + resources["one"].expects(:builtin?).returns(false) + resources["one"].expects(:evaluated?).returns(false) + resources["two"] = mock("unevaluated two") + resources["two"].expects(:builtin?).returns(false) + resources["two"].expects(:evaluated?).returns(false) + resources["three"] = mock("evaluated") + resources["three"].expects(:builtin?).returns(false) + resources["three"].expects(:evaluated?).returns(true) + + result = config.send(:unevaluated_resources) + %w{one two}.each do |name| + assert(result.include?(resources[name]), "Did not find %s in the unevaluated list" % name) + end + end + + def test_evaluate_definitions + # First try the case where there's nothing to return + config = mkconfig + config.expects(:unevaluated_resources).returns(nil) + + assert_nothing_raised("Could not test for unevaluated resources") do + assert(! config.send(:evaluate_definitions), "evaluate_definitions returned true when no resources were evaluated") + end + + # Now try it with resources left to evaluate + resources = [] + res1 = mock("resource1") + res1.expects(:evaluate) + res2 = mock("resource2") + res2.expects(:evaluate) + resources << res1 << res2 + config = mkconfig + config.expects(:unevaluated_resources).returns(resources) + + assert_nothing_raised("Could not test for unevaluated resources") do + assert(config.send(:evaluate_definitions), "evaluate_definitions returned false when resources were evaluated") + end + end + + def test_evaluate_generators + # First try the case where we have nothing to do + config = mkconfig + config.expects(:evaluate_definitions).returns(false) + config.expects(:evaluate_collections).returns(false) + + assert_nothing_raised("Could not call :eval_iterate") do + config.send(:evaluate_generators) + end + + # FIXME I could not get this test to work, but the code is short + # enough that I'm ok with it. + # It's important that collections are evaluated before definitions, + # so make sure that's the case by verifying that collections get tested + # twice but definitions only once. + #config = mkconfig + #config.expects(:evaluate_collections).returns(true).returns(false) + #config.expects(:evaluate_definitions).returns(false) + #config.send(:eval_iterate) + end + + def test_store + config = mkconfig + Puppet.features.expects(:rails?).returns(true) + Puppet::Rails.expects(:connect) + + args = {:name => "yay"} + config.expects(:store_to_active_record).with(args) + config.send(:store, args) + end + + def test_store_to_active_record + config = mkconfig + args = {:name => "yay"} + Puppet::Rails::Host.stubs(:transaction).yields + Puppet::Rails::Host.expects(:store).with(args) + config.send(:store_to_active_record, args) + end + + # Make sure that 'finish' gets called on all of our resources. + def test_finish + config = mkconfig + table = config.instance_variable_get("@resource_table") + + # Add a resource that does respond to :finish + yep = mock("finisher") + yep.expects(:respond_to?).with(:finish).returns(true) + yep.expects(:finish) + table["yep"] = yep + + # And one that does not + dnf = mock("dnf") + dnf.expects(:respond_to?).with(:finish).returns(false) + table["dnf"] = dnf + + config.send(:finish) + end + + def test_extract + config = mkconfig + config.expects(:extraction_format).returns(:whatever) + config.expects(:extract_to_whatever).returns(:result) + assert_equal(:result, config.send(:extract), "Did not return extraction result as the method result") + end + + # We want to make sure that the scope and resource graphs translate correctly + def test_extract_to_transportable_simple + # Start with a really simple graph -- one scope, one resource. + config = mkconfig + resources = config.instance_variable_get("@resource_graph") + scopes = config.instance_variable_get("@scope_graph") + + # Get rid of the topscope + scopes.vertices.each { |v| scopes.remove_vertex!(v) } + + scope = mock("scope") + scope.expects(:to_trans).returns([]) + scopes.add_vertex! scope + + # The topscope is the key to picking out the top of the graph. + config.instance_variable_set("@topscope", scope) + + resource = mock "resource" + resource.expects(:to_trans).returns(:resource) + resources.add_edge! scope, resource + + result = nil + assert_nothing_raised("Could not extract transportable configuration") do + result = config.send :extract_to_transportable + end + assert_equal([:resource], result, "Did not translate simple configuration correctly") + end + + def test_extract_to_transportable_complex + # Now try it with a more complicated graph -- a three tier graph, each tier + # having a scope and a resource. + config = mkconfig + resources = config.instance_variable_get("@resource_graph") + scopes = config.instance_variable_get("@scope_graph") + + # Get rid of the topscope + scopes.vertices.each { |v| scopes.remove_vertex!(v) } + + fakebucket = Class.new(Array) do + attr_accessor :name + def initialize(n) + @name = n + end + end + + # Create our scopes. + top = mock("top") + top.expects(:to_trans).returns(fakebucket.new("top")) + # The topscope is the key to picking out the top of the graph. + config.instance_variable_set("@topscope", top) + middle = mock("middle") + middle.expects(:to_trans).returns(fakebucket.new("middle")) + scopes.add_edge! top, middle + bottom = mock("bottom") + bottom.expects(:to_trans).returns(fakebucket.new("bottom")) + scopes.add_edge! middle, bottom + + topres = mock "topres" + topres.expects(:to_trans).returns(:topres) + resources.add_edge! top, topres + + midres = mock "midres" + midres.expects(:to_trans).returns(:midres) + resources.add_edge! middle, midres + + botres = mock "botres" + botres.expects(:to_trans).returns(:botres) + resources.add_edge! bottom, botres + + result = nil + assert_nothing_raised("Could not extract transportable configuration") do + result = config.send :extract_to_transportable + end + assert_equal([[[:botres], :midres], :topres], result, "Did not translate medium configuration correctly") + end + + def test_verify_uniqueness + config = mkconfig + + resources = config.instance_variable_get("@resource_table") + resource = mock("noconflict") + resource.expects(:ref).returns("File[yay]") + assert_nothing_raised("Raised an exception when there should have been no conflict") do + config.send(:verify_uniqueness, resource) + end + + # Now try the case where our type is isomorphic + resources["thing"] = true + + isoconflict = mock("isoconflict") + isoconflict.expects(:ref).returns("thing") + isoconflict.expects(:type).returns("testtype") + faketype = mock("faketype") + faketype.expects(:isomorphic?).returns(false) + faketype.expects(:name).returns("whatever") + Puppet::Type.expects(:type).with("testtype").returns(faketype) + assert_nothing_raised("Raised an exception when was a conflict in non-isomorphic types") do + config.send(:verify_uniqueness, isoconflict) + end + + # Now test for when we actually have an exception + initial = mock("initial") + resources["thing"] = initial + initial.expects(:file).returns(false) + + conflict = mock("conflict") + conflict.expects(:ref).returns("thing").times(2) + conflict.expects(:type).returns("conflict") + conflict.expects(:file).returns(false) + conflict.expects(:line).returns(false) + + faketype = mock("faketype") + faketype.expects(:isomorphic?).returns(true) + Puppet::Type.expects(:type).with("conflict").returns(faketype) + assert_raise(Puppet::ParseError, "Did not fail when two isomorphic resources conflicted") do + config.send(:verify_uniqueness, conflict) + end + end + + def test_store_resource + # Run once when there's no conflict + config = mkconfig + table = config.instance_variable_get("@resource_table") + resource = mock("resource") + resource.expects(:ref).returns("yay") + config.expects(:verify_uniqueness).with(resource) + scope = mock("scope") + + graph = config.instance_variable_get("@resource_graph") + graph.expects(:add_edge!).with(scope, resource) + + assert_nothing_raised("Could not store resource") do + config.store_resource(scope, resource) + end + assert_equal(resource, table["yay"], "Did not store resource in table") + + # Now for conflicts + config = mkconfig + table = config.instance_variable_get("@resource_table") + resource = mock("resource") + config.expects(:verify_uniqueness).with(resource).raises(ArgumentError) + + assert_raise(ArgumentError, "Did not raise uniqueness exception") do + config.store_resource(scope, resource) + end + assert(table.empty?, "Conflicting resource was stored in table") + end + + def test_fail_on_unevaluated + config = mkconfig + config.expects(:fail_on_unevaluated_overrides) + config.expects(:fail_on_unevaluated_resource_collections) + config.send :fail_on_unevaluated + end + + def test_store_override + # First test the case when the resource is not present. + config = mkconfig + overrides = config.instance_variable_get("@resource_overrides") + override = Object.new + override.expects(:ref).returns(:myref).times(2) + override.expects(:override=).with(true) + + assert_nothing_raised("Could not call store_override") do + config.store_override(override) + end + assert_instance_of(Array, overrides[:myref], "Overrides table is not a hash of arrays") + assert_equal(override, overrides[:myref][0], "Did not store override in appropriately named array") + + # And when the resource already exists. + resource = mock 'resource' + resources = config.instance_variable_get("@resource_table") + resources[:resref] = resource + + override = mock 'override' + resource.expects(:merge).with(override) + override.expects(:override=).with(true) + override.expects(:ref).returns(:resref) + assert_nothing_raised("Could not call store_override when the resource already exists.") do + config.store_override(override) + end + end + + def test_resource_overrides + config = mkconfig + overrides = config.instance_variable_get("@resource_overrides") + overrides[:test] = :yay + resource = mock 'resource' + resource.expects(:ref).returns(:test) + + assert_equal(:yay, config.resource_overrides(resource), "Did not return overrides from table") + end + + def test_fail_on_unevaluated_resource_collections + config = mkconfig + collections = config.instance_variable_get("@collections") + + # Make sure we're fine when the list is empty + assert_nothing_raised("Failed when no collections were present") do + config.send :fail_on_unevaluated_resource_collections + end + + # And that we're fine when we've got collections but with no resources + collections << mock('coll') + collections[0].expects(:resources).returns(nil) + assert_nothing_raised("Failed when no resource collections were present") do + config.send :fail_on_unevaluated_resource_collections + end + + # But that we do fail when we've got resource collections left. + collections.clear + + # return both an array and a string, because that's tested internally + collections << mock('coll returns one') + collections[0].expects(:resources).returns(:something) + + collections << mock('coll returns many') + collections[1].expects(:resources).returns([:one, :two]) + + assert_raise(Puppet::ParseError, "Did not fail on unevaluated resource collections") do + config.send :fail_on_unevaluated_resource_collections + end + end + + def test_fail_on_unevaluated_overrides + config = mkconfig + overrides = config.instance_variable_get("@resource_overrides") + + # Make sure we're fine when the list is empty + assert_nothing_raised("Failed when no collections were present") do + config.send :fail_on_unevaluated_overrides + end + + # But that we fail if there are any overrides left in the table. + overrides[:yay] = [] + overrides[:foo] = [] + overrides[:bar] = [mock("override")] + overrides[:bar][0].expects(:ref).returns("yay") + assert_raise(Puppet::ParseError, "Failed to fail when overrides remain") do + config.send :fail_on_unevaluated_overrides + end + end + + def test_find_resource + config = mkconfig + resources = config.instance_variable_get("@resource_table") + + assert_nothing_raised("Could not call findresource when the resource table was empty") do + assert_nil(config.findresource("yay", "foo"), "Returned a non-existent resource") + assert_nil(config.findresource("yay[foo]"), "Returned a non-existent resource") + end + + resources["Foo[bar]"] = :yay + assert_nothing_raised("Could not call findresource when the resource table was not empty") do + assert_equal(:yay, config.findresource("foo", "bar"), "Returned a non-existent resource") + assert_equal(:yay, config.findresource("Foo[bar]"), "Returned a non-existent resource") + end + end + + # #620 - Nodes and classes should conflict, else classes don't get evaluated + def test_nodes_and_classes_name_conflict + # Test node then class + config = mkconfig + node = stub :nodescope? => true + klass = stub :nodescope? => false + config.class_set("one", node) + assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do + config.class_set("one", klass) + end + + # and class then node + config = mkconfig + node = stub :nodescope? => true + klass = stub :nodescope? => false + config.class_set("two", klass) + assert_raise(Puppet::ParseError, "Did not fail when replacing node with class") do + config.class_set("two", node) + end + end +end diff --git a/test/language/functions.rb b/test/language/functions.rb index 34207de17..42d8d7585 100755 --- a/test/language/functions.rb +++ b/test/language/functions.rb @@ -3,7 +3,6 @@ $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppet' -require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/network/client' require 'puppettest' @@ -204,16 +203,17 @@ class TestLangFunctions < Test::Unit::TestCase f.puts %{file { "#{file}": content => template("#{template}") }} end - interpreter = Puppet::Parser::Interpreter.new( + interp = Puppet::Parser::Interpreter.new( :Manifest => manifest, :UseNodes => false ) + node = mknode - parsedate = interpreter.parsedate() + parsedate = interp.parsedate() objects = nil assert_nothing_raised { - objects = interpreter.run("myhost", {}) + objects = interp.compile(node) } fileobj = objects[0] @@ -221,7 +221,7 @@ class TestLangFunctions < Test::Unit::TestCase assert_equal("original text\n", fileobj["content"], "Template did not work") - Puppet[:filetimeout] = 0 + Puppet[:filetimeout] = -5 # Have to sleep because one second is the fs's time granularity. sleep(1) @@ -231,9 +231,9 @@ class TestLangFunctions < Test::Unit::TestCase end assert_nothing_raised { - objects = interpreter.run("myhost", {}) + objects = interp.compile(node) } - newdate = interpreter.parsedate() + newdate = interp.parsedate() assert(parsedate != newdate, "Parse date did not change") end @@ -306,35 +306,36 @@ class TestLangFunctions < Test::Unit::TestCase end def test_realize - @interp, @scope, @source = mkclassframing + scope = mkscope + parser = scope.configuration.parser # Make a definition - @interp.newdefine("mytype") + parser.newdefine("mytype") [%w{file /tmp/virtual}, %w{mytype yay}].each do |type, title| # Make a virtual resource virtual = mkresource(:type => type, :title => title, :virtual => true, :params => {}) - @scope.setresource virtual + scope.setresource virtual ref = Puppet::Parser::Resource::Reference.new( :type => type, :title => title, - :scope => @scope + :scope => scope ) # Now call the realize function assert_nothing_raised do - @scope.function_realize(ref) + scope.function_realize(ref) end # Make sure it created a collection - assert_equal(1, @scope.collections.length, + assert_equal(1, scope.configuration.collections.length, "Did not set collection") assert_nothing_raised do - @scope.collections.each do |coll| coll.evaluate end + scope.configuration.collections.each do |coll| coll.evaluate end end - @scope.collections.clear + scope.configuration.collections.clear # Now make sure the virtual resource is no longer virtual assert(! virtual.virtual?, "Did not make virtual resource real") @@ -343,29 +344,29 @@ class TestLangFunctions < Test::Unit::TestCase # Make sure we puke on any resource that doesn't exist none = Puppet::Parser::Resource::Reference.new( :type => "file", :title => "/tmp/nosuchfile", - :scope => @scope + :scope => scope ) # The function works assert_nothing_raised do - @scope.function_realize(none.to_s) + scope.function_realize(none.to_s) end # Make sure it created a collection - assert_equal(1, @scope.collections.length, + assert_equal(1, scope.configuration.collections.length, "Did not set collection") # And the collection has our resource in it - assert_equal([none.to_s], @scope.collections[0].resources, + assert_equal([none.to_s], scope.configuration.collections[0].resources, "Did not set resources in collection") end def test_defined - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope + parser = scope.configuration.parser - interp.newclass("yayness") - interp.newdefine("rahness") + parser.newclass("yayness") + parser.newdefine("rahness") assert_nothing_raised do assert(scope.function_defined("yayness"), "yayness class was not considered defined") @@ -395,11 +396,11 @@ class TestLangFunctions < Test::Unit::TestCase end def test_search - interp = mkinterp - scope = mkscope(:interp => interp) + parser = mkparser + scope = mkscope(:parser => parser) - fun = interp.newdefine("yay::ness") - foo = interp.newdefine("foo::bar") + fun = parser.newdefine("yay::ness") + foo = parser.newdefine("foo::bar") search = Puppet::Parser::Functions.function(:search) assert_nothing_raised do @@ -417,36 +418,36 @@ class TestLangFunctions < Test::Unit::TestCase end def test_include - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope + parser = scope.configuration.parser assert_raise(Puppet::ParseError, "did not throw error on missing class") do scope.function_include("nosuchclass") end - interp.newclass("myclass") + parser.newclass("myclass") assert_nothing_raised do scope.function_include "myclass" end - assert(scope.classlist.include?("myclass"), + assert(scope.configuration.classlist.include?("myclass"), "class was not evaluated") # Now try multiple classes at once - classes = %w{one two three}.each { |c| interp.newclass(c) } + classes = %w{one two three}.each { |c| parser.newclass(c) } assert_nothing_raised do scope.function_include classes end classes.each do |c| - assert(scope.classlist.include?(c), + assert(scope.configuration.classlist.include?(c), "class %s was not evaluated" % c) end # Now try a scoped class - interp.newclass("os::redhat") + parser.newclass("os::redhat") assert_nothing_raised("Could not include qualified class name") do scope.function_include("os::redhat") @@ -454,8 +455,8 @@ class TestLangFunctions < Test::Unit::TestCase end def test_file - interp = mkinterp - scope = mkscope(:interp => interp) + parser = mkparser + scope = mkscope(:parser => parser) file1 = tempfile file2 = tempfile @@ -497,8 +498,8 @@ class TestLangFunctions < Test::Unit::TestCase assert_equal("yay\n", %x{#{command}}, "command did not work") assert_equal("yay-foo\n", %x{#{command} foo}, "command did not work") - interp = mkinterp - scope = mkscope(:interp => interp) + scope = mkscope + parser = scope.configuration.parser val = nil assert_nothing_raised("Could not call generator with no args") do diff --git a/test/language/interpreter.rb b/test/language/interpreter.rb index 75800cc41..ebbc3f87f 100755 --- a/test/language/interpreter.rb +++ b/test/language/interpreter.rb @@ -20,7 +20,6 @@ class TestInterpreter < PuppetTest::TestCase include PuppetTest::ParserTesting include PuppetTest::ResourceTesting AST = Puppet::Parser::AST - NodeDef = Puppet::Parser::Interpreter::NodeDef # create a simple manifest that uses nodes to create a file def mknodemanifest(node, file) @@ -34,23 +33,13 @@ class TestInterpreter < PuppetTest::TestCase return [file, createdfile] end - def test_simple - file = tempfile() - File.open(file, "w") { |f| - f.puts "file { \"/etc\": owner => root }" - } - assert_nothing_raised { - Puppet::Parser::Interpreter.new(:Manifest => file) - } - end - def test_reloadfiles - hostname = Facter["hostname"].value + node = mknode(Facter["hostname"].value) file = tempfile() # Create a first version - createdfile = mknodemanifest(hostname, file) + createdfile = mknodemanifest(node.name, file) interp = nil assert_nothing_raised { @@ -59,61 +48,21 @@ class TestInterpreter < PuppetTest::TestCase config = nil assert_nothing_raised { - config = interp.run(hostname, {}) + config = interp.compile(node) } - sleep(1) + Puppet[:filetimeout] = -5 # Now create a new file - createdfile = mknodemanifest(hostname, file) + createdfile = mknodemanifest(node.name, file) newconfig = nil assert_nothing_raised { - newconfig = interp.run(hostname, {}) + newconfig = interp.compile(node) } assert(config != newconfig, "Configs are somehow the same") end - # Make sure searchnode behaves as we expect. - def test_nodesearch - # We use two sources here to catch a weird bug where the default - # node is used if the host isn't in the first source. - interp = mkinterp - - # Make some nodes - names = %w{node1 node2 node2.domain.com} - interp.newnode names - interp.newnode %w{default} - - nodes = {} - # Make sure we can find them all, using the direct method - names.each do |name| - nodes[name] = interp.nodesearch_code(name) - assert(nodes[name], "Could not find %s" % name) - nodes[name].file = __FILE__ - end - - # Now let's try it with the nodesearch method - names.each do |name| - node = interp.nodesearch(name) - assert(node, "Could not find #{name} via nodesearch") - end - - # Make sure we find the default node when we search for nonexistent nodes - assert_nothing_raised do - default = interp.nodesearch("nosuchnode") - assert(default, "Did not find default node") - assert_equal("default", default.classname) - end - - # Now make sure the longest match always wins - node = interp.nodesearch(*%w{node2 node2.domain.com}) - - assert(node, "Did not find node2") - assert_equal("node2.domain.com", node.classname, - "Did not get longest match") - end - def test_parsedate Puppet[:filetimeout] = 0 main = tempfile() @@ -160,500 +109,18 @@ class TestInterpreter < PuppetTest::TestCase newdate = interp.parsedate assert(date != newdate, "Parsedate was not updated") end - - # Make sure class, node, and define methods are case-insensitive - def test_structure_case_insensitivity - interp = mkinterp - - result = nil - assert_nothing_raised do - result = interp.newclass "Yayness" - end - assert_equal(result, interp.findclass("", "yayNess")) - - assert_nothing_raised do - result = interp.newdefine "FunTest" - end - assert_equal(result, interp.finddefine("", "fUntEst"), - "%s was not matched" % "fUntEst") - - assert_nothing_raised do - result = interp.newnode("MyNode").shift - end - assert_equal(result, interp.nodesearch("mYnOde"), - "mYnOde was not matched") - - assert_nothing_raised do - result = interp.newnode("YayTest.Domain.Com").shift - end - assert_equal(result, interp.nodesearch("yaYtEst.domAin.cOm"), - "yaYtEst.domAin.cOm was not matched") - end # Make sure our whole chain works. - def test_evaluate - interp, scope, source = mkclassframing - - # Create a define that we'll be using - interp.newdefine("wrapper", :code => AST::ASTArray.new(:children => [ - resourcedef("file", varref("name"), "owner" => "root") - ])) - - # Now create a resource that uses that define - define = mkresource(:type => "wrapper", :title => "/tmp/testing", - :scope => scope, :source => source, :params => :none) - - scope.setresource define - - # And a normal resource - scope.setresource mkresource(:type => "file", :title => "/tmp/rahness", - :scope => scope, :source => source, - :params => {:owner => "root"}) - - # Now evaluate everything - objects = nil - interp.usenodes = false - assert_nothing_raised do - objects = interp.evaluate(nil, {}) - end - - assert_instance_of(Puppet::TransBucket, objects) - end - - # Test evaliterate. It's a very simple method, but it's pretty tough - # to test. It iterates over collections and instances of defined types - # until there's no more work to do. - def test_evaliterate - interp, scope, source = mkclassframing - - # Create a top-level definition that creates a builtin object - interp.newdefine("one", :arguments => [%w{owner}], - :code => AST::ASTArray.new(:children => [ - resourcedef("file", varref("name"), - "owner" => varref("owner") - ) - ]) - ) - - # Create another definition to call that one - interp.newdefine("two", :arguments => [%w{owner}], - :code => AST::ASTArray.new(:children => [ - resourcedef("one", varref("name"), - "owner" => varref("owner") - ) - ]) - ) - - # And then a third - interp.newdefine("three", :arguments => [%w{owner}], - :code => AST::ASTArray.new(:children => [ - resourcedef("two", varref("name"), - "owner" => varref("owner") - ) - ]) - ) - - # And create a definition that creates a virtual resource - interp.newdefine("virtualizer", :arguments => [%w{owner}], - :code => AST::ASTArray.new(:children => [ - virt_resourcedef("one", varref("name"), - "owner" => varref("owner") - ) - ]) - ) - - # Now create an instance of three - three = Puppet::Parser::Resource.new( - :type => "three", :title => "one", - :scope => scope, :source => source, - :params => paramify(source, :owner => "root") - ) - scope.setresource(three) - - # An instance of the virtualizer - virt = Puppet::Parser::Resource.new( - :type => "virtualizer", :title => "two", - :scope => scope, :source => source, - :params => paramify(source, :owner => "root") - ) - scope.setresource(virt) - - # And a virtual instance of three - virt_three = Puppet::Parser::Resource.new( - :type => "three", :title => "three", - :scope => scope, :source => source, - :params => paramify(source, :owner => "root") - ) - virt_three.virtual = true - scope.setresource(virt_three) - - # Create a normal, virtual resource - plainvirt = Puppet::Parser::Resource.new( - :type => "user", :title => "five", - :scope => scope, :source => source, - :params => paramify(source, :uid => "root") - ) - plainvirt.virtual = true - scope.setresource(plainvirt) - - # Now create some collections for our virtual resources - %w{Three[three] One[two]}.each do |ref| - coll = Puppet::Parser::Collector.new(scope, "file", nil, nil, :virtual) - coll.resources = [ref] - scope.newcollection(coll) - end - - # And create a generic user collector for our plain resource - coll = Puppet::Parser::Collector.new(scope, "user", nil, nil, :virtual) - scope.newcollection(coll) - - ret = nil - assert_nothing_raised do - ret = scope.unevaluated - end - - - assert_instance_of(Array, ret) - assert_equal(3, ret.length, - "did not get the correct number of unevaled resources") - - # Now translate the whole tree - assert_nothing_raised do - Timeout::timeout(2) do - interp.evaliterate(scope) - end - end - - # Now make sure we've got all of our files - %w{one two three}.each do |name| - file = scope.findresource("File[%s]" % name) - assert(file, "Could not find file %s" % name) - - assert_equal("root", file[:owner]) - assert(! file.virtual?, "file %s is still virtual" % name) - end - - # Now make sure we found the user - assert(! plainvirt.virtual?, "user was not realized") - end - - # Make sure we fail if there are any leftover overrides to perform. - # This would normally mean that someone is trying to override an object - # that does not exist. - def test_failonleftovers - interp, scope, source = mkclassframing - - # Make sure we don't fail, since there are no overrides - assert_nothing_raised do - interp.failonleftovers(scope) - end - - # Add an override, and make sure it causes a failure - over1 = mkresource :scope => scope, :source => source, - :params => {:one => "yay"} - - scope.setoverride(over1) - - assert_raise(Puppet::ParseError) do - interp.failonleftovers(scope) - end - - # Make a new scope to test leftover collections - scope = mkscope :interp => interp - interp.meta_def(:check_resource_collections) do - raise ArgumentError, "yep" - end - - assert_raise(ArgumentError, "did not call check_resource_colls") do - interp.failonleftovers(scope) - end - end - - def test_evalnode + def test_compile interp = mkinterp - interp.usenodes = false - scope = Parser::Scope.new(:interp => interp) - facts = Facter.to_hash - - # First make sure we get no failures when client is nil - assert_nothing_raised do - interp.evalnode(nil, scope, facts) - end + interp.expects(:parsefiles) + parser = interp.instance_variable_get("@parser") - # Now define a node - interp.newnode "mynode", :code => AST::ASTArray.new(:children => [ - resourcedef("file", "/tmp/testing", "owner" => "root") - ]) - - # Eval again, and make sure it does nothing - assert_nothing_raised do - interp.evalnode("mynode", scope, facts) - end - - assert_nil(scope.findresource("File[/tmp/testing]"), - "Eval'ed node with nodes off") - - # Now enable usenodes and make sure it works. - interp.usenodes = true - assert_nothing_raised do - interp.evalnode("mynode", scope, facts) - end - file = scope.findresource("File[/tmp/testing]") - - assert_instance_of(Puppet::Parser::Resource, file, - "Could not find file") - end - - # This is mostly used for the cfengine module - def test_specificclasses - interp = mkinterp :Classes => %w{klass1 klass2}, :UseNodes => false - - # Make sure it's not a failure to be missing classes, since - # we're using the cfengine class list, which is huge. - assert_nothing_raised do - interp.evaluate(nil, {}) - end - - interp.newclass("klass1", :code => AST::ASTArray.new(:children => [ - resourcedef("file", "/tmp/klass1", "owner" => "root") - ])) - interp.newclass("klass2", :code => AST::ASTArray.new(:children => [ - resourcedef("file", "/tmp/klass2", "owner" => "root") - ])) - - ret = nil - assert_nothing_raised do - ret = interp.evaluate(nil, {}) - end - - found = ret.flatten.collect do |res| res.name end - - assert(found.include?("/tmp/klass1"), "Did not evaluate klass1") - assert(found.include?("/tmp/klass2"), "Did not evaluate klass2") - end - - def mk_node_mapper - # First, make sure our nodesearch command works as we expect - # Make a nodemapper - mapper = tempfile() - ruby = %x{which ruby}.chomp - File.open(mapper, "w") { |f| - f.puts "#!#{ruby} - require 'yaml' - name = ARGV.last.chomp - result = {} - - if name =~ /a/ - result[:parameters] = {'one' => ARGV.last + '1', 'two' => ARGV.last + '2'} - end - - if name =~ /p/ - result['classes'] = [1,2,3].collect { |n| ARGV.last + n.to_s } - end - - puts YAML.dump(result) - " - } - File.chmod(0755, mapper) - mapper - end - - def test_nodesearch_external - interp = mkinterp - - mapper = mk_node_mapper - # Make sure it gives the right response - assert_equal({'classes' => %w{apple1 apple2 apple3}, :parameters => {"one" => "apple1", "two" => "apple2"}}, - YAML.load(%x{#{mapper} apple})) - - # First make sure we get nil back by default - assert_nothing_raised { - assert_nil(interp.nodesearch_external("apple"), - "Interp#nodesearch_external defaulted to a non-nil response") - } - assert_nothing_raised { Puppet[:external_nodes] = mapper } - - node = nil - # Both 'a' and 'p', so we get classes and parameters - assert_nothing_raised { node = interp.nodesearch_external("apple") } - assert_equal("apple", node.name, "node name was not set correctly for apple") - assert_equal(%w{apple1 apple2 apple3}, node.classes, "node classes were not set correctly for apple") - assert_equal( {"one" => "apple1", "two" => "apple2"}, node.parameters, "node parameters were not set correctly for apple") - - # A 'p' but no 'a', so we only get classes - assert_nothing_raised { node = interp.nodesearch_external("plum") } - assert_equal("plum", node.name, "node name was not set correctly for plum") - assert_equal(%w{plum1 plum2 plum3}, node.classes, "node classes were not set correctly for plum") - assert_equal({}, node.parameters, "node parameters were not set correctly for plum") - - # An 'a' but no 'p', so we only get parameters. - assert_nothing_raised { node = interp.nodesearch_external("guava")} # no p's, thus no classes - assert_equal("guava", node.name, "node name was not set correctly for guava") - assert_equal([], node.classes, "node classes were not set correctly for guava") - assert_equal({"one" => "guava1", "two" => "guava2"}, node.parameters, "node parameters were not set correctly for guava") - - assert_nothing_raised { node = interp.nodesearch_external("honeydew")} # neither, thus nil - assert_nil(node) - 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" - interp = mkinterp - node = nil - assert_nothing_raised do - node = interp.nodesearch("apple") - end - assert_instance_of(NodeDef, 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 - - Puppet[:external_nodes] = mapper - interp = mkinterp - - node = nil - assert_nothing_raised do - node = interp.nodesearch("apple") - end - assert_instance_of(NodeDef, node, "did not create node") - end - - def test_check_resource_collections - interp = mkinterp - scope = mkscope :interp => interp - coll = Puppet::Parser::Collector.new(scope, "file", nil, nil, :virtual) - coll.resources = ["File[/tmp/virtual1]", "File[/tmp/virtual2]"] - scope.newcollection(coll) - - assert_raise(Puppet::ParseError, "Did not fail on remaining resource colls") do - interp.check_resource_collections(scope) - end - end - - def test_nodedef - interp = mkinterp - interp.newclass("base") - interp.newclass("sub", :parent => "base") - interp.newclass("other") - - node = nil - assert_nothing_raised("Could not create a node definition") do - node = NodeDef.new :name => "yay", :classes => "sub", :parameters => {"one" => "two", "three" => "four"} - end - - scope = mkscope :interp => interp - assert_nothing_raised("Could not evaluate the node definition") do - node.evaluate(:scope => scope) - end - - assert_equal("two", scope.lookupvar("one"), "NodeDef did not set variable") - assert_equal("four", scope.lookupvar("three"), "NodeDef did not set variable") - - assert(scope.classlist.include?("sub"), "NodeDef did not evaluate class") - assert(scope.classlist.include?("base"), "NodeDef did not evaluate base class") - - # Now try a node def with multiple classes - assert_nothing_raised("Could not create a node definition") do - node = NodeDef.new :name => "yay", :classes => %w{sub other base}, :parameters => {"one" => "two", "three" => "four"} - end - - scope = mkscope :interp => interp - assert_nothing_raised("Could not evaluate the node definition") do - node.evaluate(:scope => scope) - end - - assert_equal("two", scope.lookupvar("one"), "NodeDef did not set variable") - assert_equal("four", scope.lookupvar("three"), "NodeDef did not set variable") - - assert(scope.classlist.include?("sub"), "NodeDef did not evaluate class") - assert(scope.classlist.include?("other"), "NodeDef did not evaluate other class") - - # And a node def with no params - assert_nothing_raised("Could not create a node definition with no params") do - node = NodeDef.new :name => "yay", :classes => %w{sub other base} - end - - scope = mkscope :interp => interp - assert_nothing_raised("Could not evaluate the node definition") do - node.evaluate(:scope => scope) - end - - assert(scope.classlist.include?("sub"), "NodeDef did not evaluate class") - assert(scope.classlist.include?("other"), "NodeDef did not evaluate other class") - - # Now make sure nodedef doesn't fail when some classes are not defined (#687). - assert_nothing_raised("Could not create a node definition with some invalid classes") do - node = NodeDef.new :name => "yay", :classes => %w{base unknown} - end - - scope = mkscope :interp => interp - assert_nothing_raised("Could not evaluate the node definition with some invalid classes") do - node.evaluate(:scope => scope) - end - - assert(scope.classlist.include?("base"), "NodeDef did not evaluate class") - 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_ldapnodes - interp = mkinterp - - nodetable = {} - - # Override the ldapsearch definition, so we don't have to actually set it up. - interp.meta_def(:ldapsearch) do |name| - nodetable[name] - end - - # Make sure we get nothing for nonexistent hosts - node = nil - assert_nothing_raised do - node = interp.nodesearch_ldap("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 = interp.nodesearch_ldap("base") - end - - assert_instance_of(NodeDef, 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 = interp.nodesearch_ldap("middle") - end - - assert_instance_of(NodeDef, 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 = interp.nodesearch_ldap("top") - end - - assert_instance_of(NodeDef, 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") + node = mock('node') + config = mock('config') + config.expects(:compile).returns(:config) + Puppet::Parser::Configuration.expects(:new).with(node, parser).returns(config) + assert_equal(:config, interp.compile(node), "Did not return the results of config.compile") end # Make sure that reparsing is atomic -- failures don't cause a broken state, and we aren't subject @@ -665,7 +132,7 @@ class TestInterpreter < PuppetTest::TestCase interp = mkinterp :Manifest => file, :UseNodes => false assert_nothing_raised("Could not compile the first time") do - interp.run("yay", {}) + interp.compile(mknode("yay")) end oldparser = interp.send(:instance_variable_get, "@parser") @@ -673,7 +140,7 @@ class TestInterpreter < PuppetTest::TestCase # Now add a syntax failure File.open(file, "w") { |f| f.puts %{file { /tmp: ensure => directory }} } assert_nothing_raised("Could not compile the first time") do - interp.run("yay", {}) + interp.compile(mknode("yay")) end # And make sure the old parser is still there @@ -682,135 +149,4 @@ class TestInterpreter < PuppetTest::TestCase end end -class LdapNodeTest < PuppetTest::TestCase - include PuppetTest - include PuppetTest::ServerTest - include PuppetTest::ParserTesting - include PuppetTest::ResourceTesting - AST = Puppet::Parser::AST - NodeDef = Puppet::Parser::Interpreter::NodeDef - 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 = NodeDef.new(:name => 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 - - def test_ldapsearch - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - ldapconnect() - - interp = mkinterp :NodeSources => [:ldap, :code] - - # 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 = interp.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 = interp.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 = interp.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 - -class LdapReconnectTests < PuppetTest::TestCase - include PuppetTest - include PuppetTest::ServerTest - include PuppetTest::ParserTesting - include PuppetTest::ResourceTesting - AST = Puppet::Parser::AST - NodeDef = Puppet::Parser::Interpreter::NodeDef - confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") - - def test_ldapreconnect - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - interp = nil - assert_nothing_raised { - interp = Puppet::Parser::Interpreter.new( - :Manifest => mktestmanifest() - ) - } - hostname = "culain.madstop.com" - - # look for our host - assert_nothing_raised { - parent, classes = interp.nodesearch_ldap(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 = interp.nodesearch_ldap(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 = interp.nodesearch_ldap(hostname) - } - end -end - # $Id$ diff --git a/test/language/node.rb b/test/language/node.rb deleted file mode 100755 index 61983df92..000000000 --- a/test/language/node.rb +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppet' -require 'puppet/parser/parser' -require 'puppettest' - -class TestParser < Test::Unit::TestCase - include PuppetTest::ParserTesting - - def setup - super - Puppet[:parseonly] = true - end - - def test_simple_hostname - check_parseable "host1" - check_parseable "'host2'" - check_parseable "\"host3\"" - check_parseable [ "'host1'", "host2" ] - check_parseable [ "'host1'", "'host2'" ] - check_parseable [ "'host1'", "\"host2\"" ] - check_parseable [ "\"host1\"", "host2" ] - check_parseable [ "\"host1\"", "'host2'" ] - check_parseable [ "\"host1\"", "\"host2\"" ] - end - - def test_qualified_hostname - check_parseable "'host.example.com'" - check_parseable "\"host.example.com\"" - check_parseable [ "'host.example.com'", "host1" ] - check_parseable [ "\"host.example.com\"", "host1" ] - check_parseable "'host-1.37examples.example.com'" - check_parseable "\"host-1.37examples.example.com\"" - check_parseable "'svn.23.nu'" - check_parseable "\"svn.23.nu\"" - check_parseable "'HOST'" - check_parseable "\"HOST\"" - end - - def test_inherits_from_default - check_parseable(["default", "host1"], "node default {}\nnode host1 inherits default {}") - end - - def test_reject_hostname - check_nonparseable "host.example.com" - check_nonparseable "host@example.com" - check_nonparseable "'$foo.example.com'" - check_nonparseable "\"$foo.example.com\"" - check_nonparseable "'host1 host2'" - check_nonparseable "\"host1 host2\"" - check_nonparseable "HOST" - end - - AST = Puppet::Parser::AST - - def check_parseable(hostnames, code = nil) - unless hostnames.is_a?(Array) - hostnames = [ hostnames ] - end - interp = nil - code ||= "node #{hostnames.join(", ")} { }" - parser = mkparser - parser.string = code - # Strip quotes - hostnames.map! { |s| s.sub(/^['"](.*)['"]$/, "\\1") } - - # parse - assert_nothing_raised("Could not parse '%s'" % code) { - parser.parse - } - - # Now make sure we can look up each of the names - hostnames.each do |name| - assert(parser.findnode(name), - "Could not find node %s" % name.inspect) - end - end - - def check_nonparseable(hostname) - interp = nil - parser = mkparser - parser.string = "node #{hostname} { }" - assert_raise(Puppet::DevError, Puppet::ParseError, "#{hostname} passed") { - parser.parse - } - end - - # Make sure we can find default nodes if there's no other entry - def test_default_node - Puppet[:parseonly] = false - - fileA = tempfile() - fileB = tempfile() - code = %{ -node mynode { - file { "#{fileA}": ensure => file } -} - -node default { - file { "#{fileB}": ensure => file } -} -} - interp = nil - assert_nothing_raised { - interp = mkinterp :Code => code - } - - # First make sure it parses - assert_nothing_raised { - interp.send(:parsefiles) - } - - # Make sure we find our normal node - assert(interp.nodesearch("mynode"), - "Did not find normal node") - - # Now look for the default node - default = interp.nodesearch("someother") - assert(default, - "Did not find default node") - - assert_equal("default", default.classname) - end -end diff --git a/test/language/parser.rb b/test/language/parser.rb index afffa9293..c172aafca 100755 --- a/test/language/parser.rb +++ b/test/language/parser.rb @@ -38,13 +38,13 @@ class TestParser < Test::Unit::TestCase def test_failers failers { |file| parser = mkparser - interp = mkinterp Puppet.debug("parsing failer %s" % file) if __FILE__ == $0 - assert_raise(Puppet::ParseError) { + assert_raise(Puppet::ParseError, "Did not fail while parsing %s" % file) { parser.file = file ast = parser.parse - scope = mkscope :interp => interp - ast.classes[""].evaluate :scope => scope + config = mkconfig(parser) + config.compile + #ast.classes[""].evaluate :scope => config.topscope } Puppet::Type.allclear } @@ -622,7 +622,7 @@ file { "/tmp/yayness": code = nil assert_nothing_raised do - code = interp.run("hostname.domain.com", {}).flatten + code = interp.compile(mknode).flatten end assert(code.length == 1, "Did not get the file") assert_instance_of(Puppet::TransObject, code[0]) @@ -871,7 +871,8 @@ file { "/tmp/yayness": end def test_newclass - parser = mkparser + scope = mkscope + parser = scope.configuration.parser mkcode = proc do |ary| classes = ary.collect do |string| @@ -880,7 +881,6 @@ file { "/tmp/yayness": AST::ASTArray.new(:children => classes) end - scope = Puppet::Parser::Scope.new(:interp => mkinterp) # First make sure that code is being appended code = mkcode.call(%w{original code}) @@ -1175,6 +1175,23 @@ file { "/tmp/yayness": assert_instance_of(AST::HostClass, klass, "Did not autoload sub class from alone file with no namespace") assert_equal("alone::sub", klass.classname, "Incorrect class was returned") end + + # Make sure class, node, and define methods are case-insensitive + def test_structure_case_insensitivity + parser = mkparser + + result = nil + assert_nothing_raised do + result = parser.newclass "Yayness" + end + assert_equal(result, parser.findclass("", "yayNess")) + + assert_nothing_raised do + result = parser.newdefine "FunTest" + end + assert_equal(result, parser.finddefine("", "fUntEst"), + "%s was not matched" % "fUntEst") + end end # $Id$ diff --git a/test/language/resource.rb b/test/language/resource.rb index 039c67216..50d58cf32 100755 --- a/test/language/resource.rb +++ b/test/language/resource.rb @@ -11,159 +11,215 @@ class TestResource < PuppetTest::TestCase include PuppetTest::ResourceTesting Parser = Puppet::Parser AST = Parser::AST + Resource = Puppet::Parser::Resource Reference = Puppet::Parser::Resource::Reference def setup super Puppet[:trace] = false - @interp, @scope, @source = mkclassframing + end + + def teardown + mocha_verify end def test_initialize args = {:type => "resource", :title => "testing", - :source => @source, :scope => @scope} + :source => "source", :scope => "scope"} # Check our arg requirements args.each do |name, value| try = args.dup try.delete(name) - assert_raise(Puppet::DevError) do + assert_raise(ArgumentError, "Did not fail when %s was missing" % name) do Parser::Resource.new(try) end end - args[:params] = paramify @source, :one => "yay", :three => "rah" + Reference.expects(:new).with(:type => "resource", :title => "testing", :scope => "scope").returns(:ref) res = nil assert_nothing_raised do res = Parser::Resource.new(args) end - - # Make sure it got the parameters correctly. - assert_equal("yay", res[:one]) - assert_equal("rah", res[:three]) - - assert_equal({:one => "yay", :three => "rah"}, res.to_hash) end - def test_override + def test_merge res = mkresource + other = mkresource - # Now verify we can't override with any random class - assert_raise(Puppet::ParseError) do - res.set paramify(@scope.findclass("other"), "one" => "boo").shift + # First try the case where the resource is not allowed to override + res.source = "source1" + other.source = "source2" + other.source.expects(:child_of?).with("source1").returns(false) + assert_raise(Puppet::ParseError, "Allowed unrelated resources to override") do + res.merge(other) end - # And that we can with a subclass - assert_nothing_raised do - res.set paramify(@scope.findclass("sub1"), "one" => "boo").shift - end + # Next try it when the sources are equal. + res.source = "source3" + other.source = res.source + other.source.expects(:child_of?).with("source3").never + params = {:a => :b, :c => :d} + other.expects(:params).returns(params) + res.expects(:override_parameter).with(:b) + res.expects(:override_parameter).with(:d) + res.merge(other) + + # And then parentage is involved + other = mkresource + res.source = "source3" + other.source = "source4" + other.source.expects(:child_of?).with("source3").returns(true) + params = {:a => :b, :c => :d} + other.expects(:params).returns(params) + res.expects(:override_parameter).with(:b) + res.expects(:override_parameter).with(:d) + res.merge(other) + end - # And that a different subclass can override a different parameter - assert_nothing_raised do - res.set paramify(@scope.findclass("sub2"), "three" => "boo").shift - end + # the [] method + def test_array_accessors + res = mkresource + params = res.instance_variable_get("@params") + assert_nil(res[:missing], "Found a missing parameter somehow") + params[:something] = stub(:value => "yay") + assert_equal("yay", res[:something], "Did not correctly call value on the parameter") - # But not the same one - assert_raise(Puppet::ParseError) do - res.set paramify(@scope.findclass("sub2"), "one" => "something").shift - end + res.expects(:title).returns(:mytitle) + assert_equal(:mytitle, res[:title], "Did not call title when asked for it as a param") end - def check_paramadd(val1, val2, merged_val) - res = mkresource :params => {"one" => val1} - assert_nothing_raised do - res.set Parser::Resource::Param.new( - :name => "one", :value => val2, - :add => true, :source => @scope.findclass("sub1")) - end - assert_equal(merged_val, res[:one]) + # Make sure any defaults stored in the scope get added to our resource. + def test_add_defaults + res = mkresource + params = res.instance_variable_get("@params") + params[:a] = :b + res.scope.expects(:lookupdefaults).with(res.type).returns(:a => :replaced, :c => :d) + res.expects(:debug) + + res.send(:add_defaults) + assert_equal(:d, params[:c], "Did not set default") + assert_equal(:b, params[:a], "Replaced parameter with default") end - def test_paramadd - check_paramadd([], [], []) - check_paramadd([], "rah", ["rah"]) - check_paramadd([], ["rah", "bah"], ["rah", "bah"]) - - check_paramadd("yay", [], ["yay"]) - check_paramadd("yay", "rah", ["yay", "rah"]) - check_paramadd("yay", ["rah", "bah"], ["yay", "rah", "bah"]) - - check_paramadd(["yay", "boo"], [], ["yay", "boo"]) - check_paramadd(["yay", "boo"], "rah", ["yay", "boo", "rah"]) - check_paramadd(["yay", "boo"], ["rah", "bah"], - ["yay", "boo", "rah", "bah"]) + def test_finish + res = mkresource + res.expects(:add_overrides) + res.expects(:add_defaults) + res.expects(:add_metaparams) + res.expects(:validate) + res.finish end - def test_merge - # Start with the normal one + # Make sure we paramcheck our params + def test_validate res = mkresource + params = res.instance_variable_get("@params") + params[:one] = :two + params[:three] = :four + res.expects(:paramcheck).with(:one) + res.expects(:paramcheck).with(:three) + res.send(:validate) + end - # Now create a resource from a different scope - other = mkresource :source => other, :params => {"one" => "boo"} - - # Make sure we can't merge it - assert_raise(Puppet::ParseError) do - res.merge(other) - end - - # Make one from a subscope - other = mkresource :source => "sub1", :params => {"one" => "boo"} - - # Make sure it merges - assert_nothing_raised do - res.merge(other) - end + def test_override_parameter + res = mkresource + params = res.instance_variable_get("@params") + + # There are three cases, with the second having two options: + + # No existing parameter. + param = stub(:name => "myparam") + res.send(:override_parameter, param) + assert_equal(param, params["myparam"], "Override was not added to param list") + + # An existing parameter that we can override. + source = stub(:child_of? => true) + # Start out without addition + params["param2"] = stub(:source => :whatever) + param = stub(:name => "param2", :source => source, :add => false) + res.send(:override_parameter, param) + assert_equal(param, params["param2"], "Override was not added to param list") + + # Try with addition. + params["param2"] = stub(:value => :a, :source => :whatever) + param = stub(:name => "param2", :source => source, :add => true, :value => :b) + param.expects(:value=).with([:a, :b]) + res.send(:override_parameter, param) + assert_equal(param, params["param2"], "Override was not added to param list") + + # And finally, make sure we throw an exception when the sources aren't related + source = stub(:child_of? => false) + params["param2"] = stub(:source => :whatever, :file => :f, :line => :l) + old = params["param2"] + param = stub(:name => "param2", :source => source, :file => :f, :line => :l) + assert_raise(Puppet::ParseError, "Did not fail when params conflicted") do + res.send(:override_parameter, param) + end + assert_equal(old, params["param2"], "Param was replaced irrespective of conflict") + end - assert_equal("boo", res["one"]) + def test_set_parameter + res = mkresource + params = res.instance_variable_get("@params") + + # First test the simple case: It's already a parameter + param = mock('param') + param.expects(:is_a?).with(Resource::Param).returns(true) + param.expects(:name).returns("pname") + res.send(:set_parameter, param) + assert_equal(param, params["pname"], "Parameter was not added to hash") + + # Now the case where there's no value but it's not a param + param = mock('param') + param.expects(:is_a?).with(Resource::Param).returns(false) + assert_raise(ArgumentError, "Did not fail when a non-param was passed") do + res.send(:set_parameter, param) + end + + # and the case where a value is passed in + param = stub :name => "pname", :value => "whatever" + Resource::Param.expects(:new).with(:name => "pname", :value => "myvalue", :source => res.source).returns(param) + res.send(:set_parameter, "pname", "myvalue") + assert_equal(param, params["pname"], "Did not put param in hash") end def test_paramcheck - # First make a builtin resource - res = nil - assert_nothing_raised do - res = Parser::Resource.new :type => "file", :title => tempfile(), - :source => @source, :scope => @scope - end - - %w{path group source schedule subscribe}.each do |param| - assert_nothing_raised("Param %s was considered invalid" % param) do - res.paramcheck(param) - end - end - - %w{this bad noness}.each do |param| - assert_raise(Puppet::ParseError, "%s was considered valid" % param) do - res.paramcheck(param) - end - end - - # Now create a defined resource - assert_nothing_raised do - res = Parser::Resource.new :type => "resource", :title => "yay", - :source => @source, :scope => @scope - end - - %w{one two three schedule subscribe}.each do |param| - assert_nothing_raised("Param %s was considered invalid" % param) do - res.paramcheck(param) - end - end + # There are three cases here: - %w{this bad noness}.each do |param| - assert_raise(Puppet::ParseError, "%s was considered valid" % param) do - res.paramcheck(param) - end - end + # It's a valid parameter + res = mkresource + ref = mock('ref') + res.instance_variable_set("@ref", ref) + klass = mock("class") + ref.expects(:typeclass).returns(klass).times(4) + klass.expects(:validattr?).with("good").returns(true) + assert(res.send(:paramcheck, :good), "Did not allow valid param") + + # It's name or title + klass.expects(:validattr?).with("name").returns(false) + assert(res.send(:paramcheck, :name), "Did not allow name") + klass.expects(:validattr?).with("title").returns(false) + assert(res.send(:paramcheck, :title), "Did not allow title") + + # It's not actually allowed + klass.expects(:validattr?).with("other").returns(false) + res.expects(:fail) + ref.expects(:type) + res.send(:paramcheck, :other) end def test_to_trans # First try translating a builtin resource. Make sure we use some references # and arrays, to make sure they translate correctly. + source = mock("source") + scope = mock("scope") + scope.expects(:tags).returns([]) refs = [] 4.times { |i| refs << Puppet::Parser::Resource::Reference.new(:title => "file%s" % i, :type => "file") } res = Parser::Resource.new :type => "file", :title => "/tmp", - :source => @source, :scope => @scope, - :params => paramify(@source, :owner => "nobody", :group => %w{you me}, + :source => source, :scope => scope, + :params => paramify(source, :owner => "nobody", :group => %w{you me}, :require => refs[0], :ignore => %w{svn}, :subscribe => [refs[1], refs[2]], :notify => [refs[3]]) @@ -186,168 +242,81 @@ class TestResource < PuppetTest::TestCase assert_equal(["file", refs[3].title], obj["notify"], "Array with single resource reference was not turned into single value") end - def test_adddefaults - # Set some defaults at the top level - top = {:one => "fun", :two => "shoe"} - - @scope.setdefaults("resource", paramify(@source, top)) - - # Make a resource at that level - res = Parser::Resource.new :type => "resource", :title => "yay", - :source => @source, :scope => @scope - - # Add the defaults - assert_nothing_raised do - res.adddefaults - end - - # And make sure we got them - top.each do |p, v| - assert_equal(v, res[p]) - end - - # Now got a bit lower - other = @scope.newscope - - # And create a resource - lowerres = Parser::Resource.new :type => "resource", :title => "funtest", - :source => @source, :scope => other - - assert_nothing_raised do - lowerres.adddefaults - end - - # And check - top.each do |p, v| - assert_equal(v, lowerres[p]) - end - - # Now add some of our own defaults - lower = {:one => "shun", :three => "free"} - other.setdefaults("resource", paramify(@source, lower)) - otherres = Parser::Resource.new :type => "resource", :title => "yaytest", - :source => @source, :scope => other - - should = top.dup - # Make sure the lower defaults beat the higher ones. - lower.each do |p, v| should[p] = v end - - otherres.adddefaults - - should.each do |p,v| - assert_equal(v, otherres[p]) - end - end - def test_evaluate - # Make a definition that we know will, um, do something - @interp.newdefine "evaltest", - :arguments => [%w{one}, ["two", stringobj("755")]], - :code => resourcedef("file", "/tmp", - "owner" => varref("one"), "mode" => varref("two")) - - res = Parser::Resource.new :type => "evaltest", :title => "yay", - :source => @source, :scope => @scope, - :params => paramify(@source, :one => "nobody") - - # Now try evaluating - ret = nil - assert_nothing_raised do - ret = res.evaluate - end - - # Make sure we can find our object now - result = @scope.findresource("File[/tmp]") - - # Now make sure we got the code we expected. - assert_instance_of(Puppet::Parser::Resource, result) - - assert_equal("file", result.type) - assert_equal("/tmp", result.title) - assert_equal("nobody", result["owner"]) - assert_equal("755", result["mode"]) - - # And that we cannot find the old resource - assert_nil(@scope.findresource("Evaltest[yay]"), - "Evaluated resource was not deleted") + # First try the most common case, we're not a builtin type. + res = mkresource + ref = res.instance_variable_get("@ref") + type = mock("type") + ref.expects(:definedtype).returns(type) + res.expects(:finish) + res.scope = mock("scope") + config = mock("config") + res.scope.expects(:configuration).returns(config) + config.expects(:delete_resource).with(res) + + args = {:scope => res.scope, :arguments => res.to_hash} + # This is insane; FIXME we need to redesign how classes and components are evaluated. + [:type, :title, :virtual, :exported].each do |param| + args[param] = res.send(param) + end + type.expects(:evaluate_resource).with(args) + + res.evaluate end - def test_addoverrides - # First create an override for an object that doesn't yet exist - over1 = mkresource :source => "sub1", :params => {:one => "yay"} - - assert_nothing_raised do - @scope.setoverride(over1) - end - - assert(over1.override, "Override was not marked so") - - # Now make the resource - res = mkresource :source => "base", :params => {:one => "rah", - :three => "foo"} - - # And add it to our scope - @scope.setresource(res) - - # And make sure over1 has not yet taken affect - assert_equal("foo", res[:three], "Lost value") - - # Now add an immediately binding override - over2 = mkresource :source => "sub1", :params => {:three => "yay"} - - assert_nothing_raised do - @scope.setoverride(over2) - end - - # And make sure it worked - assert_equal("yay", res[:three], "Override 2 was ignored") - - # Now add our late-binding override - assert_nothing_raised do - res.addoverrides - end - - # And make sure they're still around - assert_equal("yay", res[:one], "Override 1 lost") - assert_equal("yay", res[:three], "Override 2 lost") - - # And finally, make sure that there are no remaining overrides - assert_nothing_raised do - res.addoverrides - end + def test_add_overrides + # Try it with nil + res = mkresource + res.scope = mock('scope') + config = mock("config") + res.scope.expects(:configuration).returns(config) + config.expects(:resource_overrides).with(res).returns(nil) + res.expects(:merge).never + res.send(:add_overrides) + + # And an empty array + res = mkresource + res.scope = mock('scope') + config = mock("config") + res.scope.expects(:configuration).returns(config) + config.expects(:resource_overrides).with(res).returns([]) + res.expects(:merge).never + res.send(:add_overrides) + + # And with some overrides + res = mkresource + res.scope = mock('scope') + config = mock("config") + res.scope.expects(:configuration).returns(config) + returns = %w{a b} + config.expects(:resource_overrides).with(res).returns(returns) + res.expects(:merge).with("a") + res.expects(:merge).with("b") + res.send(:add_overrides) + assert(returns.empty?, "Did not clear overrides") end def test_proxymethods res = Parser::Resource.new :type => "evaltest", :title => "yay", - :source => @source, :scope => @scope + :source => mock("source"), :scope => mock('scope') assert_equal("evaltest", res.type) assert_equal("yay", res.title) assert_equal(false, res.builtin?) end - def test_addmetaparams - mkevaltest @interp - res = Parser::Resource.new :type => "evaltest", :title => "yay", - :source => @source, :scope => @scope, - :params => paramify(@source, :tag => "yay") - - assert_nil(res[:schedule], "Got schedule already") - assert_nothing_raised do - res.addmetaparams - end - @scope.setvar("schedule", "daily") + def test_add_metaparams + res = mkresource + params = res.instance_variable_get("@params") + params[:a] = :b + Puppet::Type.expects(:eachmetaparam).multiple_yields(:a, :b, :c) + res.scope.expects(:lookupvar).with("b", false).returns(:something) + res.scope.expects(:lookupvar).with("c", false).returns(:undefined) + res.expects(:set_parameter).with(:b, :something) - # This is so we can test that it won't override already-set metaparams - @scope.setvar("tag", "funtest") + res.send(:add_metaparams) - assert_nothing_raised do - res.addmetaparams - end - - assert_equal("daily", res[:schedule], "Did not get metaparam") - assert_equal("yay", res[:tag], "Overrode explicitly-set metaparam") - assert_nil(res[:noop], "Got invalid metaparam") + assert_nil(params[:c], "A value was created somehow for an unset metaparam") end def test_reference_conversion @@ -357,6 +326,7 @@ class TestResource < PuppetTest::TestCase # Now create an obj that uses it res = mkresource :type => "file", :title => "/tmp/resource", :params => {:require => ref} + res.scope = stub(:tags => []) trans = nil assert_nothing_raised do @@ -370,6 +340,7 @@ class TestResource < PuppetTest::TestCase two = Parser::Resource::Reference.new(:type => "file", :title => "/tmp/ref2") res = mkresource :type => "file", :title => "/tmp/resource2", :params => {:require => [ref, two]} + res.scope = stub(:tags => []) trans = nil assert_nothing_raised do @@ -394,60 +365,24 @@ class TestResource < PuppetTest::TestCase assert_nil(ref.builtintype, "Component was considered builtin") end - # #472. Really, this still isn't the best behaviour, but at least - # it's consistent with what we have elsewhere. - def test_defaults_from_parent_classes - # Make a parent class with some defaults in it - @interp.newclass("base", - :code => defaultobj("file", :owner => "root", :group => "root") - ) - - # Now a mid-level class with some different values - @interp.newclass("middle", :parent => "base", - :code => defaultobj("file", :owner => "bin", :mode => "755") - ) - - # Now a lower class with its own defaults plus a resource - @interp.newclass("bottom", :parent => "middle", - :code => AST::ASTArray.new(:children => [ - defaultobj("file", :owner => "adm", :recurse => "true"), - resourcedef("file", "/tmp/yayness", {}) - ]) - ) - - # Now evaluate the class. - assert_nothing_raised("Failed to evaluate class tree") do - @scope.evalclasses("bottom") - end - - # Make sure our resource got created. - res = @scope.findresource("File[/tmp/yayness]") - assert_nothing_raised("Could not add defaults") do - res.adddefaults - end - assert(res, "could not find resource") - {:owner => "adm", :recurse => "true", :group => "root", :mode => "755"}.each do |param, value| - assert_equal(value, res[param], "%s => %s did not inherit correctly" % - [param, value]) - end - end - # The second part of #539 - make sure resources pass the arguments # correctly. def test_title_with_definitions - define = @interp.newdefine "yayness", + parser = mkparser + define = parser.newdefine "yayness", :code => resourcedef("file", "/tmp", "owner" => varref("name"), "mode" => varref("title")) - klass = @interp.findclass("", "") + + klass = parser.findclass("", "") should = {:name => :owner, :title => :mode} [ {:name => "one", :title => "two"}, {:title => "three"}, ].each do |hash| - scope = mkscope :interp => @interp + config = mkconfig parser args = {:type => "yayness", :title => hash[:title], - :source => klass, :scope => scope} + :source => klass, :scope => config.topscope} if hash[:name] args[:params] = {:name => hash[:name]} else @@ -462,7 +397,7 @@ class TestResource < PuppetTest::TestCase res.evaluate end - made = scope.findresource("File[/tmp]") + made = config.topscope.findresource("File[/tmp]") assert(made, "Did not create resource with %s" % hash.inspect) should.each do |orig, param| assert_equal(hash[orig] || hash[:title], made[param], @@ -474,7 +409,7 @@ class TestResource < PuppetTest::TestCase # part of #629 -- the undef keyword. Make sure 'undef' params get skipped. def test_undef_and_to_hash res = mkresource :type => "file", :title => "/tmp/testing", - :source => @source, :scope => @scope, + :source => mock("source"), :scope => mock("scope"), :params => {:owner => :undef, :mode => "755"} hash = nil @@ -487,11 +422,14 @@ class TestResource < PuppetTest::TestCase # #643 - Make sure virtual defines result in virtual resources def test_virtual_defines - define = @interp.newdefine("yayness", + parser = mkparser + define = parser.newdefine("yayness", :code => resourcedef("file", varref("name"), "mode" => "644")) - res = mkresource :type => "yayness", :title => "foo", :params => {} + config = mkconfig(parser) + + res = mkresource :type => "yayness", :title => "foo", :params => {}, :scope => config.topscope res.virtual = true result = nil @@ -506,7 +444,7 @@ class TestResource < PuppetTest::TestCase assert(newres.virtual?, "Virtual defined resource generated non-virtual resources") # Now try it with exported resources - res = mkresource :type => "yayness", :title => "bar", :params => {} + res = mkresource :type => "yayness", :title => "bar", :params => {}, :scope => config.topscope res.exported = true result = nil diff --git a/test/language/scope.rb b/test/language/scope.rb index f0feee156..fc5e085d4 100755 --- a/test/language/scope.rb +++ b/test/language/scope.rb @@ -27,79 +27,37 @@ class TestScope < Test::Unit::TestCase end def test_variables - scope = nil - over = "over" + config = mkconfig + topscope = config.topscope + midscope = config.newscope(topscope) + botscope = config.newscope(midscope) - scopes = [] - vars = [] - values = {} - ovalues = [] + scopes = {:top => topscope, :mid => midscope, :bot => botscope} - 10.times { |index| - # slap some recursion in there - scope = mkscope(:parent => scope) - scopes.push scope - - var = "var%s" % index - value = rand(1000) - ovalue = rand(1000) - - ovalues.push ovalue - - vars.push var - values[var] = value - - # set the variable in the current scope - assert_nothing_raised { - scope.setvar(var,value) - } + # Set a variable in the top and make sure all three can get it + topscope.setvar("first", "topval") + scopes.each do |name, scope| + assert_equal("topval", scope.lookupvar("first", false), "Could not find var in %s" % name) + end - # this should override previous values - assert_nothing_raised { - scope.setvar(over,ovalue) - } + # Now set a var in the midscope and make sure the mid and bottom can see it but not the top + midscope.setvar("second", "midval") + assert_equal(:undefined, scopes[:top].lookupvar("second", false), "Found child var in top scope") + [:mid, :bot].each do |name| + assert_equal("midval", scopes[name].lookupvar("second", false), "Could not find var in %s" % name) + end - assert_equal(value,scope.lookupvar(var)) - - #puts "%s vars, %s scopes" % [vars.length,scopes.length] - i = 0 - vars.zip(scopes) { |v,s| - # this recurses all the way up the tree as necessary - val = nil - oval = nil - - # look up the values using the bottom scope - assert_nothing_raised { - val = scope.lookupvar(v) - oval = scope.lookupvar(over) - } - - # verify they're correct - assert_equal(values[v],val) - assert_equal(ovalue,oval) - - # verify that we get the most recent value - assert_equal(ovalue,scope.lookupvar(over)) - - # verify that they aren't available in upper scopes - if parent = s.parent - val = nil - assert_nothing_raised { - val = parent.lookupvar(v) - } - assert_equal("", val, "Did not get empty string on missing var") - - # and verify that the parent sees its correct value - assert_equal(ovalues[i - 1],parent.lookupvar(over)) - end - i += 1 - } - } + # And set something in the bottom, and make sure we only find it there. + botscope.setvar("third", "botval") + [:top, :mid].each do |name| + assert_equal(:undefined, scopes[name].lookupvar("third", false), "Found child var in top scope") + end + assert_equal("botval", scopes[:bot].lookupvar("third", false), "Could not find var in bottom scope") end def test_lookupvar - interp = mkinterp - scope = mkscope :interp => interp + parser = mkparser + scope = mkscope :parser => parser # first do the plain lookups assert_equal("", scope.lookupvar("var"), "scope did not default to string") @@ -111,7 +69,7 @@ class TestScope < Test::Unit::TestCase assert_equal("yep", scope.lookupvar("var"), "did not retrieve value correctly") # Now test the parent lookups - subscope = mkscope :interp => interp + subscope = mkscope :parser => parser subscope.parent = scope assert_equal("", subscope.lookupvar("nope"), "scope did not default to string with parent") assert_equal("", subscope.lookupvar("nope", true), "scope ignored usestring setting with parent") @@ -129,12 +87,12 @@ class TestScope < Test::Unit::TestCase end def test_lookup_qualified_var - interp = mkinterp - scope = mkscope :interp => interp + parser = mkparser + scope = mkscope :parser => parser scopes = {} classes = ["", "one", "one::two", "one::two::three"].each do |name| - klass = interp.newclass(name) + klass = parser.newclass(name) klass.evaluate(:scope => scope) scopes[name] = scope.class_scope(klass) end @@ -149,7 +107,7 @@ class TestScope < Test::Unit::TestCase def test_declarative # set to declarative - top = mkscope(:declarative => true) + top = mkscope sub = mkscope(:parent => top) assert_nothing_raised { @@ -166,93 +124,89 @@ class TestScope < Test::Unit::TestCase } end - def test_notdeclarative - # set to not declarative - top = mkscope(:declarative => false) - sub = mkscope(:parent => top) + def test_setdefaults + config = mkconfig - assert_nothing_raised { - top.setvar("test","value") - } - assert_nothing_raised { - top.setvar("test","other") - } - assert_nothing_raised { - sub.setvar("test","later") - } - assert_nothing_raised { - sub.setvar("test","yayness") - } - end + scope = config.topscope - def test_setdefaults - interp, scope, source = mkclassframing + defaults = scope.instance_variable_get("@defaults") - # The setdefaults method doesn't really check what we're doing, - # so we're just going to use fake defaults here. + # First the case where there are no defaults and we pass a single param + param = stub :name => "myparam", :file => "f", :line => "l" + scope.setdefaults(:mytype, param) + assert_equal({"myparam" => param}, defaults[:mytype], "Did not set default correctly") - # First do a simple local lookup - params = paramify(source, :one => "fun", :two => "shoe") - origshould = {} - params.each do |p| origshould[p.name] = p end - assert_nothing_raised do - scope.setdefaults(:file, params) - end + # Now the case where we pass in multiple parameters + param1 = stub :name => "one", :file => "f", :line => "l" + param2 = stub :name => "two", :file => "f", :line => "l" + scope.setdefaults(:newtype, [param1, param2]) + assert_equal({"one" => param1, "two" => param2}, defaults[:newtype], "Did not set multiple defaults correctly") - ret = nil - assert_nothing_raised do - ret = scope.lookupdefaults(:file) + # And the case where there's actually a conflict. Use the first default for this. + newparam = stub :name => "myparam", :file => "f", :line => "l" + assert_raise(Puppet::ParseError, "Allowed resetting of defaults") do + scope.setdefaults(:mytype, param) end + assert_equal({"myparam" => param}, defaults[:mytype], "Replaced default even though there was a failure") + end - assert_equal(origshould, ret) + def test_lookupdefaults + config = mkconfig + top = config.topscope - # Now create a subscope and add some more params. - newscope = scope.newscope + # Make a subscope + sub = config.newscope(top) - newparams = paramify(source, :one => "shun", :three => "free") - assert_nothing_raised { - newscope.setdefaults(:file, newparams) - } - - # And make sure we get the appropriate ones back - should = {} - params.each do |p| should[p.name] = p end - newparams.each do |p| should[p.name] = p end + topdefs = top.instance_variable_get("@defaults") + subdefs = sub.instance_variable_get("@defaults") - assert_nothing_raised do - ret = newscope.lookupdefaults(:file) - end + # First add some defaults to our top scope + topdefs[:t1] = {:p1 => :p2, :p3 => :p4} + topdefs[:t2] = {:p5 => :p6} - assert_equal(should, ret) + # Then the sub scope + subdefs[:t1] = {:p1 => :p7, :p8 => :p9} + subdefs[:t2] = {:p5 => :p10, :p11 => :p12} - # Make sure we still only get the originals from the top scope - assert_nothing_raised do - ret = scope.lookupdefaults(:file) + # Now make sure we get the correct list back + result = nil + assert_nothing_raised("Could not get defaults") do + result = sub.lookupdefaults(:t1) end + assert_equal(:p9, result[:p8], "Did not get child defaults") + assert_equal(:p4, result[:p3], "Did not override parent defaults with child default") + assert_equal(:p7, result[:p1], "Did not get parent defaults") + end - assert_equal(origshould, ret) + def test_parent + config = mkconfig + top = config.topscope - # Now create another scope and make sure we only get the top defaults - otherscope = scope.newscope - assert_equal(origshould, otherscope.lookupdefaults(:file)) + # Make a subscope + sub = config.newscope(top) - # And make sure none of the scopes has defaults for other types - [scope, newscope, otherscope].each do |sc| - assert_equal({}, sc.lookupdefaults(:exec)) - end + assert_equal(top, sub.parent, "Did not find parent scope correctly") + assert_equal(top, sub.parent, "Did not find parent scope on second call") + end + + def test_class_scope + config = mkconfig + scope = config.topscope + config.expects(:class_scope).with(:testing).returns(:myscope) + assert_equal(:myscope, scope.class_scope(:testing), "Did not pass back the results of config.class_scope") end def test_strinterp # Make and evaluate our classes so the qualified lookups work - interp = mkinterp - klass = interp.newclass("") - scope = mkscope(:interp => interp) + parser = mkparser + klass = parser.newclass("") + scope = mkscope(:parser => parser) klass.evaluate(:scope => scope) - klass = interp.newclass("one") + klass = parser.newclass("one") klass.evaluate(:scope => scope) - klass = interp.newclass("one::two") + klass = parser.newclass("one::two") klass.evaluate(:scope => scope) @@ -264,7 +218,7 @@ class TestScope < Test::Unit::TestCase scopes = {"" => scope} %w{one one::two one::two::three}.each do |name| - klass = interp.newclass(name) + klass = parser.newclass(name) klass.evaluate(:scope => scope) scopes[name] = scope.class_scope(klass) scopes[name].setvar("test", "value-%s" % name.sub(/.+::/,'')) @@ -298,7 +252,7 @@ class TestScope < Test::Unit::TestCase tests.each do |input, output| assert_nothing_raised("Failed to scan %s" % input.inspect) do assert_equal(output, scope.strinterp(input), - 'did not interpret %s correctly' % input.inspect) + 'did not parserret %s correctly' % input.inspect) end end @@ -311,7 +265,7 @@ class TestScope < Test::Unit::TestCase string = "\\" + l assert_nothing_raised do assert_equal(string, scope.strinterp(string), - 'did not interpret %s correctly' % string) + 'did not parserret %s correctly' % string) end assert(logs.detect { |m| m.message =~ /Unrecognised escape/ }, @@ -321,37 +275,35 @@ class TestScope < Test::Unit::TestCase end def test_setclass - interp, scope, source = mkclassframing - - base = scope.findclass("base") - assert(base, "Could not find base class") - assert(! scope.class_scope(base), "Class incorrectly set") - assert(! scope.classlist.include?("base"), "Class incorrectly in classlist") - assert_nothing_raised do - scope.setclass base - end - - assert(scope.class_scope(base), "Class incorrectly unset") - assert(scope.classlist.include?("base"), "Class not in classlist") - - # Make sure we can retrieve the scope. - assert_equal(scope, scope.class_scope(base), - "class scope was not set correctly") - - # Now try it with a normal string - Puppet[:trace] = false - assert_raise(Puppet::DevError) do - scope.setclass "string" + # Run through it when we're a normal class + config = mkconfig + scope = config.topscope + klass = mock("class") + klass.expects(:classname).returns(:myclass) + klass.expects(:is_a?).with(AST::HostClass).returns(true) + klass.expects(:is_a?).with(AST::Node).returns(false) + config.expects(:class_set).with(:myclass, scope) + scope.setclass(klass) + + # And when we're a node + config = mkconfig + scope = config.topscope + klass = mock("class2") + klass.expects(:classname).returns(:myclass) + klass.expects(:is_a?).with(AST::HostClass).returns(true) + klass.expects(:is_a?).with(AST::Node).returns(true) + config.expects(:class_set).with(:myclass, scope) + scope.setclass(klass) + assert(scope.nodescope?, "Did not set the scope as a node scope when evaluating a node") + + # And when we're invalid + config = mkconfig + scope = config.topscope + klass = mock("class3") + klass.expects(:is_a?).with(AST::HostClass).returns(false) + assert_raise(Puppet::DevError, "Did not fail when scope got passed a non-component") do + scope.setclass(klass) end - - assert(! scope.class_scope("string"), "string incorrectly set") - - # Set "" in the class list, and make sure it doesn't show up in the return - top = scope.findclass("") - assert(top, "Could not find top class") - scope.setclass top - - assert(! scope.classlist.include?(""), "Class list included empty") end def test_validtags @@ -372,7 +324,7 @@ class TestScope < Test::Unit::TestCase end def test_tagfunction - scope = mkscope() + scope = mkscope assert_nothing_raised { scope.function_tag(["yayness", "booness"]) @@ -392,11 +344,11 @@ class TestScope < Test::Unit::TestCase end def test_includefunction - interp = mkinterp - scope = mkscope :interp => interp + parser = mkparser + scope = mkscope :parser => parser - myclass = interp.newclass "myclass" - otherclass = interp.newclass "otherclass" + myclass = parser.newclass "myclass" + otherclass = parser.newclass "otherclass" function = Puppet::Parser::AST::Function.new( :name => "include", @@ -417,12 +369,12 @@ class TestScope < Test::Unit::TestCase end def test_definedfunction - interp = mkinterp + parser = mkparser %w{one two}.each do |name| - interp.newdefine name + parser.newdefine name end - scope = mkscope :interp => interp + scope = mkscope :parser => parser assert_nothing_raised { %w{one two file user}.each do |type| @@ -449,62 +401,15 @@ class TestScope < Test::Unit::TestCase "undef considered true") end - # Verify scope context is handled correctly. - def test_scopeinside - scope = mkscope() - - one = :one - two = :two - - # First just test the basic functionality. - assert_nothing_raised { - scope.inside :one do - assert_equal(:one, scope.inside, "Context did not get set") - end - assert_nil(scope.inside, "Context did not revert") - } - - # Now make sure error settings work. - assert_raise(RuntimeError) { - scope.inside :one do - raise RuntimeError, "This is a failure, yo" - end - } - assert_nil(scope.inside, "Context did not revert") - - # Now test it a bit deeper in. - assert_nothing_raised { - scope.inside :one do - scope.inside :two do - assert_equal(:two, scope.inside, "Context did not get set") - end - assert_equal(:one, scope.inside, "Context did not get set") - end - assert_nil(scope.inside, "Context did not revert") - } - - # And lastly, check errors deeper in - assert_nothing_raised { - scope.inside :one do - begin - scope.inside :two do - raise "a failure" - end - rescue - end - assert_equal(:one, scope.inside, "Context did not get set") - end - assert_nil(scope.inside, "Context did not revert") - } - - end - if defined? ActiveRecord # Verify that we recursively mark as exported the results of collectable # components. def test_exportedcomponents - interp, scope, source = mkclassframing - children = [] + config = mkconfig + parser = config.parser + + # Create a default source + config.topscope.source = parser.newclass "", "" args = AST::ASTArray.new( :file => tempfile(), @@ -513,7 +418,7 @@ class TestScope < Test::Unit::TestCase ) # Create a top-level component - interp.newdefine "one", :arguments => [%w{arg}], + parser.newdefine "one", :arguments => [%w{arg}], :code => AST::ASTArray.new( :children => [ resourcedef("file", "/tmp", {"owner" => varref("arg")}) @@ -521,7 +426,7 @@ class TestScope < Test::Unit::TestCase ) # And a component that calls it - interp.newdefine "two", :arguments => [%w{arg}], + parser.newdefine "two", :arguments => [%w{arg}], :code => AST::ASTArray.new( :children => [ resourcedef("one", "ptest", {"arg" => varref("arg")}) @@ -529,7 +434,7 @@ class TestScope < Test::Unit::TestCase ) # And then a third component that calls the second - interp.newdefine "three", :arguments => [%w{arg}], + parser.newdefine "three", :arguments => [%w{arg}], :code => AST::ASTArray.new( :children => [ resourcedef("two", "yay", {"arg" => varref("arg")}) @@ -542,13 +447,14 @@ class TestScope < Test::Unit::TestCase # And mark it as exported obj.exported = true - obj.evaluate :scope => scope - # And then evaluate it - interp.evaliterate(scope) + obj.evaluate :scope => config.topscope + + # And run the loop. + config.send(:evaluate_generators) %w{file}.each do |type| - objects = scope.lookupexported(type) + objects = config.resources.find_all { |r| r.type == type and r.exported } assert(!objects.empty?, "Did not get an exported %s" % type) end @@ -585,9 +491,11 @@ Host <<||>>" objects = nil # We run it twice because we want to make sure there's no conflict # if we pull it up from the database. + node = mknode + node.parameters = {"hostname" => node.name} 2.times { |i| assert_nothing_raised { - objects = interp.run("localhost", {"hostname" => "localhost"}) + objects = interp.compile(node) } flat = objects.flatten @@ -603,7 +511,7 @@ Host <<||>>" # Make sure tags behave appropriately. def test_tags - interp, scope, source = mkclassframing + parser, scope, source = mkclassframing # First make sure we can only set legal tags ["an invalid tag", "-anotherinvalid", "bad*tag"].each do |tag| @@ -637,58 +545,22 @@ Host <<||>>" assert_equal((ptags + %w{onemore subscope}).sort, newscope.tags.sort) end - # Make sure we successfully translate objects - def test_translate - interp, scope, source = mkclassframing - - # Create a define that we'll be using - interp.newdefine("wrapper", :code => AST::ASTArray.new(:children => [ - resourcedef("file", varref("name"), "owner" => "root") - ])) - - # Now create a resource that uses that define - define = mkresource(:type => "wrapper", :title => "/tmp/testing", - :scope => scope, :source => source, :params => :none) - - scope.setresource define - - # And a normal resource - scope.setresource mkresource(:type => "file", :title => "/tmp/rahness", - :scope => scope, :source => source, - :params => {:owner => "root"}) - - # Evaluate the the define thing. - define.evaluate - - # Now the scope should have a resource and a subscope. Translate the - # whole thing. - ret = nil - assert_nothing_raised do - ret = scope.translate - end - - assert_instance_of(Puppet::TransBucket, ret) - - ret.each do |obj| - assert(obj.is_a?(Puppet::TransBucket) || obj.is_a?(Puppet::TransObject), - "Got a non-transportable object %s" % obj.class) - end - - rahness = ret.find { |c| c.type == "file" and c.name == "/tmp/rahness" } - assert(rahness, "Could not find top-level file") - assert_equal("root", rahness["owner"]) - - bucket = ret.find { |c| c.class == Puppet::TransBucket and c.name == "/tmp/testing" } - assert(bucket, "Could not find define bucket") + # FIXME This isn't a great test, but I need to move on. + def test_to_trans + bucket = mock("transbucket") + Puppet::TransBucket.expects(:new).with([]).returns(bucket) + scope = mkscope + scope.type = "mytype" + scope.name = "myname" - testing = bucket.find { |c| c.type == "file" and c.name == "/tmp/testing" } - assert(testing, "Could not find define file") - assert_equal("root", testing["owner"]) + bucket.expects(:name=).with("myname") + bucket.expects(:type=).with("mytype") + scope.to_trans end def test_namespaces - interp, scope, source = mkclassframing + parser, scope, source = mkclassframing assert_equal([""], scope.namespaces, "Started out with incorrect namespaces") @@ -701,17 +573,17 @@ Host <<||>>" end def test_findclass_and_finddefine - interp = mkinterp + parser = mkparser - # Make sure our scope calls the interp findclass method with + # Make sure our scope calls the parser findclass method with # the right namespaces - scope = mkscope :interp => interp + scope = mkscope :parser => parser - interp.metaclass.send(:attr_accessor, :last) + parser.metaclass.send(:attr_accessor, :last) methods = [:findclass, :finddefine] methods.each do |m| - interp.meta_def(m) do |namespace, name| + parser.meta_def(m) do |namespace, name| @checked ||= [] @checked << [namespace, name] @@ -727,7 +599,7 @@ Host <<||>>" end test = proc do |should| - interp.last = scope.namespaces[-1] + parser.last = scope.namespaces[-1] methods.each do |method| result = scope.send(method, "testing") assert_equal(should, result, @@ -760,26 +632,6 @@ Host <<||>>" assert_equal("", scope.lookupvar("testing", true), "undef was not returned as '' when string") end - - # #620 - Nodes and classes should conflict, else classes don't get evaluated - def test_nodes_and_classes_name_conflict - scope = mkscope - - node = AST::Node.new :classname => "test", :namespace => "" - scope.setclass(node) - - assert(scope.nodescope?, "Scope was not marked a node scope when a node was set") - - # Now make a subscope that will be a class scope - klass = AST::HostClass.new :classname => "test", :namespace => "" - kscope = klass.subscope(scope) - - # Now make sure we throw a failure, because we're trying to do a class and node - # with the same name - assert_raise(Puppet::ParseError, "Did not fail on class and node with same name") do - kscope.class_scope(klass) - end - end end # $Id$ diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index 8b85966f5..b56bc563e 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -4,6 +4,7 @@ $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../lib'))) require 'puppet' +require 'mocha' require 'test/unit' # Yay; hackish but it works @@ -282,6 +283,7 @@ module PuppetTest 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 diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index d66367ada..3e2930728 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -5,6 +5,8 @@ module PuppetTest::ParserTesting include PuppetTest AST = Puppet::Parser::AST + Config = Puppet::Parser::Configuration + # A fake class that we can use for testing evaluation. class FakeAST attr_writer :evaluate @@ -39,6 +41,19 @@ module PuppetTest::ParserTesting ) end + def mkconfig(parser = nil) + require 'puppet/network/handler/node' + parser ||= mkparser + node = mknode + return Config.new(node, parser) + end + + def mknode(name = nil) + name ||= "nodename" + Puppet::Network::Handler.handler(:node) + Puppet::Network::Handler::Node::SimpleNode.new(name) + end + def mkinterp(args = {}) args[:Code] ||= "" unless args.include?(:Manifest) args[:Local] ||= true @@ -50,14 +65,14 @@ module PuppetTest::ParserTesting end def mkscope(hash = {}) - hash[:interp] ||= mkinterp - hash[:source] ||= (hash[:interp].findclass("", "") || - hash[:interp].newclass("")) + hash[:parser] ||= mkparser + config ||= mkconfig(hash[:parser]) + config.topscope.source = (hash[:parser].findclass("", "") || hash[:parser].newclass("")) - unless hash[:source] + unless config.topscope.source raise "Could not find source for scope" end - Puppet::Parser::Scope.new(hash) + config.topscope end def classobj(name, hash = {}) @@ -293,7 +308,7 @@ module PuppetTest::ParserTesting config = nil assert_nothing_raised { - config = interp.run(Facter["hostname"].value, {}) + config = interp.compile(mknode) } comp = nil diff --git a/test/lib/puppettest/railstesting.rb b/test/lib/puppettest/railstesting.rb index 8b5f074a2..403bdb756 100644 --- a/test/lib/puppettest/railstesting.rb +++ b/test/lib/puppettest/railstesting.rb @@ -40,12 +40,10 @@ module PuppetTest::RailsTesting # Now try storing our crap host = nil + node = mknode(facts["hostname"]) + node.parameters = facts assert_nothing_raised { - host = Puppet::Rails::Host.store( - :resources => resources, - :facts => facts, - :name => facts["hostname"] - ) + host = Puppet::Rails::Host.store(node, resources) } # Now save the whole thing diff --git a/test/lib/puppettest/resourcetesting.rb b/test/lib/puppettest/resourcetesting.rb index 8cb59b83d..e2176d5ef 100644 --- a/test/lib/puppettest/resourcetesting.rb +++ b/test/lib/puppettest/resourcetesting.rb @@ -1,25 +1,25 @@ module PuppetTest::ResourceTesting Parser = Puppet::Parser AST = Puppet::Parser::AST - def mkclassframing(interp = nil) - interp ||= mkinterp + def mkclassframing(parser = nil) + parser ||= mkparser - interp.newdefine("resource", :arguments => [%w{one}, %w{two value}, %w{three}]) - interp.newclass("") - source = interp.newclass("base") - interp.newclass("sub1", :parent => "base") - interp.newclass("sub2", :parent => "base") - interp.newclass("other") + parser.newdefine("resource", :arguments => [%w{one}, %w{two value}, %w{three}]) + parser.newclass("") + source = parser.newclass("base") + parser.newclass("sub1", :parent => "base") + parser.newclass("sub2", :parent => "base") + parser.newclass("other") - scope = Parser::Scope.new(:interp => interp) - scope.source = source + config = mkconfig(:parser => parser) + config.topscope.source = source - return interp, scope, source + return parser, config.topscope, source end - def mkevaltest(interp = nil) - interp ||= mkinterp - @interp.newdefine("evaltest", + def mkevaltest(parser = nil) + parser ||= mkparser + @parser.newdefine("evaltest", :arguments => [%w{one}, ["two", stringobj("755")]], :code => resourcedef("file", "/tmp", "owner" => varref("one"), "mode" => varref("two")) @@ -27,26 +27,14 @@ module PuppetTest::ResourceTesting end def mkresource(args = {}) - - if args[:scope] and ! args[:source] - args[:source] = args[:scope].source - end - - unless args[:scope] - unless defined? @scope - raise "Must set @scope to mkresource" - end - end + args[:source] ||= "source" + args[:scope] ||= stub :tags => [] {:type => "resource", :title => "testing", - :source => @source, :scope => @scope}.each do |param, value| + :source => "source", :scope => "scope"}.each do |param, value| args[param] ||= value end - unless args[:source].is_a?(Puppet::Parser::AST::HostClass) - args[:source] = args[:scope].findclass(args[:source]) - end - params = args[:params] || {:one => "yay", :three => "rah"} if args[:params] == :none args.delete(:params) diff --git a/test/lib/spec/version.rb b/test/lib/spec/version.rb index 924d8458a..a0c0fdbbe 100644 --- a/test/lib/spec/version.rb +++ b/test/lib/spec/version.rb @@ -15,7 +15,7 @@ module Spec # RELEASE_CANDIDATE = "RC1"
# RANDOM_TOKEN: 0.375509844656552 - REV = "$LastChangedRevision$".match(/LastChangedRevision: (\d+)/)[1]
+ REV = "LastChangedRevision: 2283".match(/LastChangedRevision: (\d+)/)[1]
STRING = [MAJOR, MINOR, TINY].join('.')
FULL_VERSION = "#{STRING} (r#{REV})"
@@ -27,4 +27,4 @@ module Spec DESCRIPTION = "#{NAME}-#{FULL_VERSION} - BDD for Ruby\n#{URL}"
end
end
-end
\ No newline at end of file +end diff --git a/test/network/client/client.rb b/test/network/client/client.rb index 14c90f2a9..3f540d10f 100755 --- a/test/network/client/client.rb +++ b/test/network/client/client.rb @@ -164,15 +164,10 @@ class TestClient < Test::Unit::TestCase # Fake that it's local, so it creates the class file client.local = false + client.expects(:setclasses).with(%w{yaytest bootest}) assert_nothing_raised { client.getconfig } - - assert(FileTest.exists?(Puppet[:classfile]), "Class file does not exist") - - classes = File.read(Puppet[:classfile]).split("\n") - - assert_equal(%w{bootest yaytest}, classes.sort) end def test_client_loading diff --git a/test/network/client/master.rb b/test/network/client/master.rb index 78a1a0a11..7746d20ff 100755 --- a/test/network/client/master.rb +++ b/test/network/client/master.rb @@ -75,7 +75,7 @@ class TestMasterClient < Test::Unit::TestCase def mk_fake_client server = Puppet::Network::Handler.master.new :Code => "" - master = Puppet::Network::Client.master.new :Server => server, :Local => true + master = Puppet::Network::Client.master.new :Master => server, :Local => true # Now create some objects objects = FakeComponent.new @@ -532,6 +532,7 @@ end master.local = false driver = master.send(:instance_variable_get, "@driver") driver.local = false + driver.send(:config_handler).local = false # Retrieve the configuration master.getconfig diff --git a/test/network/handler/configuration.rb b/test/network/handler/configuration.rb new file mode 100755 index 000000000..98c3bdcde --- /dev/null +++ b/test/network/handler/configuration.rb @@ -0,0 +1,189 @@ +#!/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 + + # Make sure we create the node handler when necessary. + def test_node_handler + config = Config.new + handler = nil + assert_nothing_raised("Could not create node handler") do + handler = config.send(:node_handler) + end + assert_instance_of(Puppet::Network::Handler.handler(:node), handler, "Did not create node handler") + + # Now make sure we get the same object back again + assert_equal(handler.object_id, config.send(:node_handler).object_id, "Did not cache node handler") + 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?, :Manifest => Puppet[:manifest]).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) + 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(: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 = Object.new + node.expects(:name).returns(:mynode) + + interp = Object.new + 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 + config = Config.new(:Local => true) + + node = Object.new + node.expects(:name).returns(:mynode) + + interp = Object.new + interp.expects(:compile).with(node).returns(:config) + 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 + handler = Object.new + handler.expects(:details).with(:client).returns(false) + config.expects(:node_handler).returns(handler) + interp = Object.new + interp.expects(:parsedate).returns(:version) + config.expects(:interpreter).returns(interp) + assert_equal(:version, config.version(:client), "Did not return configuration version") + + # And then when we find the node. + config = Config.new + node = Object.new + handler = Object.new + handler.expects(:details).with(:client).returns(node) + config.expects(:update_node_check).with(node) + config.expects(:node_handler).returns(handler) + interp = Object.new + interp.expects(:parsedate).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/master.rb b/test/network/handler/master.rb index 08e17373b..5ac8cbbbc 100755 --- a/test/network/handler/master.rb +++ b/test/network/handler/master.rb @@ -8,56 +8,6 @@ require 'puppet/network/handler/master' class TestMaster < Test::Unit::TestCase include PuppetTest::ServerTest - # run through all of the existing test files and make sure everything - # works - def test_files - count = 0 - textfiles { |file| - Puppet.debug("parsing %s" % file) - client = nil - master = nil - - # create our master - assert_nothing_raised() { - # this is the default server setup - master = Puppet::Network::Handler.master.new( - :Manifest => file, - :UseNodes => false, - :Local => true - ) - } - - # and our client - assert_nothing_raised() { - client = Puppet::Network::Client.master.new( - :Master => master - ) - } - - # pull our configuration a few times - assert_nothing_raised() { - client.getconfig - stopservices - Puppet::Type.allclear - } - assert_nothing_raised() { - client.getconfig - stopservices - Puppet::Type.allclear - } - assert_nothing_raised() { - client.getconfig - stopservices - Puppet::Type.allclear - } - # only test three files; that's plenty - if count > 3 - break - end - count += 1 - } - end - def test_defaultmanifest textfiles { |file| Puppet[:manifest] = file @@ -166,138 +116,39 @@ class TestMaster < Test::Unit::TestCase assert(FileTest.exists?(file2), "Second file %s does not exist" % file2) end - def test_addfacts - master = nil - file = mktestmanifest() - # create our master - assert_nothing_raised() { - # this is the default server setup - master = Puppet::Network::Handler.master.new( - :Manifest => file, - :UseNodes => false, - :Local => true - ) - } - - facts = {} - - assert_nothing_raised { - master.addfacts(facts) - } - - %w{serverversion servername serverip}.each do |fact| - assert(facts.include?(fact), "Fact %s was not set" % fact) - end - end - - # Make sure we're using the hostname as configured with :node_name - def test_hostname_in_getconfig - master = nil - file = tempfile() - #@createdfile = File.join(tmpdir(), self.class.to_s + "manifesttesting" + - # "_" + @method_name) - file_cert = tempfile() - file_fact = tempfile() - - certname = "y4yn3ss" - factname = Facter.value("hostname") - - File.open(file, "w") { |f| - f.puts %{ - node #{certname} { file { "#{file_cert}": ensure => file, mode => 755 } } - node #{factname} { file { "#{file_fact}": ensure => file, mode => 755 } } -} - } - # create our master - assert_nothing_raised() { - # this is the default server setup - master = Puppet::Network::Handler.master.new( - :Manifest => file, - :UseNodes => true, - :Local => true - ) - } - - result = nil - - # Use the hostname from facter - Puppet[:node_name] = 'facter' - assert_nothing_raised { - result = master.getconfig({"hostname" => factname}, "yaml", certname, "127.0.0.1") - } - - result = result.flatten - - assert(result.find { |obj| obj.name == file_fact }, - "Could not find correct file") - assert(!result.find { |obj| obj.name == file_cert }, - "Found incorrect file") - - # Use the hostname from the cert - Puppet[:node_name] = 'cert' - assert_nothing_raised { - result = master.getconfig({"hostname" => factname}, "yaml", certname, "127.0.0.1") - } - - result = result.flatten - - assert(!result.find { |obj| obj.name == file_fact }, - "Could not find correct file") - assert(result.find { |obj| obj.name == file_cert }, - "Found incorrect file") - end - # Make sure we're correctly doing clientname manipulations. # Testing to make sure we always get a hostname and IP address. def test_clientname - master = nil - file = tempfile() - - File.open(file, "w") { |f| - f.puts %{ - node yay { file { "/something": ensure => file, mode => 755 } } -} - } # create our master - assert_nothing_raised() { - # this is the default server setup - master = Puppet::Network::Handler.master.new( - :Manifest => file, - :UseNodes => true, - :Local => true - ) - } + master = Puppet::Network::Handler.master.new( + :Manifest => tempfile, + :UseNodes => true, + :Local => true + ) - Puppet[:node_name] = "cert" - # First act like we're local - fakename = nil - fakeip = nil - name = ip = nil - facts = Facter.to_hash - assert_nothing_raised do - name, ip = master.clientname(fakename, fakeip, facts) - end - - assert(facts["hostname"], "Removed hostname fact") - assert(facts["ipaddress"], "Removed ipaddress fact") - - assert_equal(facts["hostname"], name) - assert_equal(facts["ipaddress"], ip) - - # Now set them to something real, and make sure we get them back - fakename = "yayness" - fakeip = "192.168.0.1" - facts = Facter.to_hash - assert_nothing_raised do - name, ip = master.clientname(fakename, fakeip, facts) - end - - assert(facts["hostname"], "Removed hostname fact") - assert(facts["ipaddress"], "Removed ipaddress fact") + # First check that 'cert' works + Puppet[:node_name] = "cert" - assert_equal(fakename, name) - assert_equal(fakeip, ip) + # 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 diff --git a/test/network/handler/node.rb b/test/network/handler/node.rb new file mode 100755 index 000000000..d5c98fec6 --- /dev/null +++ b/test/network/handler/node.rb @@ -0,0 +1,637 @@ +#!/usr/bin/env ruby + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'mocha' +require 'puppettest' +require 'puppettest/resourcetesting' +require 'puppettest/parsertesting' +require 'puppettest/servertest' +require 'puppet/network/handler/node' + +module NodeTesting + include PuppetTest + Node = Puppet::Network::Handler::Node + SimpleNode = Puppet::Node + + def mk_node_mapper + # First, make sure our nodesearch command works as we expect + # Make a nodemapper + mapper = tempfile() + ruby = %x{which ruby}.chomp + File.open(mapper, "w") { |f| + f.puts "#!#{ruby} + require 'yaml' + name = ARGV.last.chomp + result = {} + + if name =~ /a/ + result[:parameters] = {'one' => ARGV.last + '1', 'two' => ARGV.last + '2'} + end + + if name =~ /p/ + result['classes'] = [1,2,3].collect { |n| ARGV.last + n.to_s } + end + + puts YAML.dump(result) + " + } + File.chmod(0755, mapper) + mapper + end + + def mk_searcher(name) + searcher = Object.new + searcher.extend(Node.node_source(name)) + searcher.meta_def(:newnode) do |name, *args| + SimpleNode.new(name, *args) + end + searcher + end + + def mk_node_source + @node_info = {} + @node_source = Node.newnode_source(:testing, :fact_merge => true) do + def nodesearch(key) + if info = @node_info[key] + SimpleNode.new(info) + else + nil + end + end + end + Puppet[:node_source] = "testing" + + cleanup { Node.rm_node_source(:testing) } + end +end + +class TestNodeHandler < Test::Unit::TestCase + include NodeTesting + + def setup + super + mk_node_source + end + + # Make sure that the handler includes the appropriate + # node source. + def test_initialize + # First try it when passing in the node source + handler = nil + assert_nothing_raised("Could not specify a node source") do + handler = Node.new(:Source => :testing) + end + assert(handler.metaclass.included_modules.include?(@node_source), "Handler did not include node source") + + # Now use the Puppet[:node_source] + Puppet[:node_source] = "testing" + assert_nothing_raised("Could not specify a node source") do + handler = Node.new() + end + assert(handler.metaclass.included_modules.include?(@node_source), "Handler did not include node source") + + # And make sure we throw an exception when an invalid node source is used + assert_raise(ArgumentError, "Accepted an invalid node source") do + handler = Node.new(:Source => "invalid") + end + end + + # Make sure we can find and we cache a fact handler. + def test_fact_handler + handler = Node.new + fhandler = nil + assert_nothing_raised("Could not retrieve the fact handler") do + fhandler = handler.send(:fact_handler) + end + assert_instance_of(Puppet::Network::Handler::Facts, fhandler, "Did not get a fact handler back") + + # Now call it again, making sure we're caching the value. + fhandler2 = nil + assert_nothing_raised("Could not retrieve the fact handler") do + fhandler2 = handler.send(:fact_handler) + end + assert_instance_of(Puppet::Network::Handler::Facts, fhandler2, "Did not get a fact handler on the second run") + assert_equal(fhandler.object_id, fhandler2.object_id, "Did not cache fact handler") + end + + # Make sure we can get node facts from the fact handler. + def test_node_facts + # Check the case where we find the node. + handler = Node.new + fhandler = handler.send(:fact_handler) + fhandler.expects(:get).with("present").returns("a" => "b") + + result = nil + assert_nothing_raised("Could not get facts from fact handler") do + result = handler.send(:node_facts, "present") + end + assert_equal({"a" => "b"}, result, "Did not get correct facts back") + + # Now try the case where the fact handler knows nothing about our host + fhandler.expects(:get).with('missing').returns(nil) + result = nil + assert_nothing_raised("Could not get facts from fact handler when host is missing") do + result = handler.send(:node_facts, "missing") + end + assert_equal({}, result, "Did not get empty hash when no facts are known") + end + + # Test our simple shorthand + def test_newnode + SimpleNode.expects(:new).with("stuff") + handler = Node.new + handler.send(:newnode, "stuff") + end + + # Make sure we can build up the correct node names to search for + def test_node_names + handler = Node.new + + # Verify that the handler asks for the facts if we don't pass them in + handler.expects(:node_facts).with("testing").returns({}) + handler.send(:node_names, "testing") + + handler = Node.new + # Test it first with no parameters + assert_equal(%w{testing}, handler.send(:node_names, "testing"), "Node names did not default to an array including just the node name") + + # Now test it with a fully qualified name + assert_equal(%w{testing.domain.com testing}, handler.send(:node_names, "testing.domain.com"), + "Fully qualified names did not get turned into multiple names, longest first") + + # And try it with a short name + domain fact + assert_equal(%w{testing host.domain.com host}, handler.send(:node_names, "testing", "domain" => "domain.com", "hostname" => "host"), + "The domain fact was not used to build up an fqdn") + + # And with an fqdn + assert_equal(%w{testing host.domain.com host}, handler.send(:node_names, "testing", "fqdn" => "host.domain.com"), + "The fqdn was not used") + + # And make sure the fqdn beats the domain + assert_equal(%w{testing host.other.com host}, handler.send(:node_names, "testing", "domain" => "domain.com", "fqdn" => "host.other.com"), + "The domain was used in preference to the fqdn") + end + + # Make sure we can retrieve a whole node by name. + def test_details_when_we_find_nodes + handler = Node.new + + # Make sure we get the facts first + handler.expects(:node_facts).with("host").returns(:facts) + + # Find the node names + handler.expects(:node_names).with("host", :facts).returns(%w{a b c}) + + # Iterate across them + handler.expects(:nodesearch).with("a").returns(nil) + handler.expects(:nodesearch).with("b").returns(nil) + + # Create an example node to return + node = SimpleNode.new("host") + + # Make sure its source is set + node.expects(:source=).with(handler.source) + + # And that the names are retained + node.expects(:names=).with(%w{a b c}) + + # And make sure we actually get it back + handler.expects(:nodesearch).with("c").returns(node) + + handler.expects(:fact_merge?).returns(true) + + # Make sure we merge the facts with the node's parameters. + node.expects(:fact_merge).with(:facts) + + # Now call the method + result = nil + assert_nothing_raised("could not call 'details'") do + result = handler.details("host") + end + assert_equal(node, result, "Did not get correct node back") + end + + # But make sure we pass through to creating default nodes when appropriate. + def test_details_using_default_node + handler = Node.new + + # Make sure we get the facts first + handler.expects(:node_facts).with("host").returns(:facts) + + # Find the node names + handler.expects(:node_names).with("host", :facts).returns([]) + + # Create an example node to return + node = SimpleNode.new("host") + + # Make sure its source is set + node.expects(:source=).with(handler.source) + + # And make sure we actually get it back + handler.expects(:nodesearch).with("default").returns(node) + + # This time, have it return false + handler.expects(:fact_merge?).returns(false) + + # And because fact_merge was false, we don't merge them. + node.expects(:fact_merge).never + + # Now call the method + result = nil + assert_nothing_raised("could not call 'details'") do + result = handler.details("host") + end + assert_equal(node, result, "Did not get correct node back") + end + + # Make sure our handler behaves rationally when it comes to getting environment data. + def test_environment + # What happens when we can't find the node + handler = Node.new + handler.expects(:details).with("fake").returns(nil) + + result = nil + assert_nothing_raised("Could not call 'Node.environment'") do + result = handler.environment("fake") + end + assert_nil(result, "Got an environment for a node we could not find") + + # Now for nodes we can find + handler = Node.new + node = SimpleNode.new("fake") + handler.expects(:details).with("fake").returns(node) + node.expects(:environment).returns("dev") + + result = nil + assert_nothing_raised("Could not call 'Node.environment'") do + result = handler.environment("fake") + end + assert_equal("dev", result, "Did not get environment back") + end + + # Make sure our handler behaves rationally when it comes to getting parameter data. + def test_parameters + # What happens when we can't find the node + handler = Node.new + handler.expects(:details).with("fake").returns(nil) + + result = nil + assert_nothing_raised("Could not call 'Node.parameters'") do + result = handler.parameters("fake") + end + assert_nil(result, "Got parameters for a node we could not find") + + # Now for nodes we can find + handler = Node.new + node = SimpleNode.new("fake") + handler.expects(:details).with("fake").returns(node) + node.expects(:parameters).returns({"a" => "b"}) + + result = nil + assert_nothing_raised("Could not call 'Node.parameters'") do + result = handler.parameters("fake") + end + assert_equal({"a" => "b"}, result, "Did not get parameters back") + end + + def test_classes + # What happens when we can't find the node + handler = Node.new + handler.expects(:details).with("fake").returns(nil) + + result = nil + assert_nothing_raised("Could not call 'Node.classes'") do + result = handler.classes("fake") + end + assert_nil(result, "Got classes for a node we could not find") + + # Now for nodes we can find + handler = Node.new + node = SimpleNode.new("fake") + handler.expects(:details).with("fake").returns(node) + node.expects(:classes).returns(%w{yay foo}) + + result = nil + assert_nothing_raised("Could not call 'Node.classes'") do + result = handler.classes("fake") + end + assert_equal(%w{yay foo}, result, "Did not get classes back") + end + + # We reuse the filetimeout for the node caching timeout. + def test_node_caching + handler = Node.new + + node = Object.new + node.metaclass.instance_eval do + attr_accessor :time, :name + end + node.time = Time.now + node.name = "yay" + + # Make sure caching works normally + assert_nothing_raised("Could not cache node") do + handler.send(:cache, node) + end + assert_equal(node.object_id, handler.send(:cached?, "yay").object_id, "Did not get node back from the cache") + + # Now set the node's time to be a long time ago + node.time = Time.now - 50000 + assert(! handler.send(:cached?, "yay"), "Timed-out node was returned from cache") + end +end + +# Test our configuration object. +class TestNodeSources < Test::Unit::TestCase + include NodeTesting + + def test_node_sources + mod = nil + assert_nothing_raised("Could not add new search type") do + mod = Node.newnode_source(:testing) do + def nodesearch(name) + end + end + end + assert_equal(mod, Node.node_source(:testing), "Did not get node_source back") + + cleanup do + Node.rm_node_source(:testing) + assert(! Node.const_defined?("Testing"), "Did not remove constant") + end + end + + def test_external_node_source + source = Node.node_source(:external) + assert(source, "Could not find external node source") + mapper = mk_node_mapper + searcher = mk_searcher(:external) + assert(searcher.fact_merge?, "External node source does not merge facts") + + # Make sure it gives the right response + assert_equal({'classes' => %w{apple1 apple2 apple3}, :parameters => {"one" => "apple1", "two" => "apple2"}}, + YAML.load(%x{#{mapper} apple})) + + # First make sure we get nil back by default + assert_nothing_raised { + assert_nil(searcher.nodesearch("apple"), + "Interp#nodesearch_external defaulted to a non-nil response") + } + assert_nothing_raised { Puppet[:external_nodes] = mapper } + + node = nil + # Both 'a' and 'p', so we get classes and parameters + assert_nothing_raised { node = searcher.nodesearch("apple") } + assert_equal("apple", node.name, "node name was not set correctly for apple") + assert_equal(%w{apple1 apple2 apple3}, node.classes, "node classes were not set correctly for apple") + assert_equal( {"one" => "apple1", "two" => "apple2"}, node.parameters, "node parameters were not set correctly for apple") + + # A 'p' but no 'a', so we only get classes + assert_nothing_raised { node = searcher.nodesearch("plum") } + assert_equal("plum", node.name, "node name was not set correctly for plum") + assert_equal(%w{plum1 plum2 plum3}, node.classes, "node classes were not set correctly for plum") + assert_equal({}, node.parameters, "node parameters were not set correctly for plum") + + # An 'a' but no 'p', so we only get parameters. + assert_nothing_raised { node = searcher.nodesearch("guava")} # no p's, thus no classes + assert_equal("guava", node.name, "node name was not set correctly for guava") + assert_equal([], node.classes, "node classes were not set correctly for guava") + assert_equal({"one" => "guava1", "two" => "guava2"}, node.parameters, "node parameters were not set correctly for guava") + + assert_nothing_raised { node = searcher.nodesearch("honeydew")} # neither, thus nil + assert_nil(node) + 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 + + # 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 + source = Node.node_source(:ldap) + assert(source, "Could not find ldap node source") + searcher = mk_searcher(:ldap) + assert(searcher.fact_merge?, "LDAP node source does not merge facts") + + nodetable = {} + + # Override the ldapsearch definition, so we don't have to actually set it up. + searcher.meta_def(:ldapsearch) do |name| + nodetable[name] + end + + # 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 + + # Make sure we always get a node back from the 'none' nodesource. + def test_nodesource_none + source = Node.node_source(:none) + assert(source, "Could not find 'none' node source") + searcher = mk_searcher(:none) + assert(searcher.fact_merge?, "'none' node source does not merge facts") + + # Run a couple of node names through it + node = nil + %w{192.168.0.1 0:0:0:3:a:f host host.domain.com}.each do |name| + assert_nothing_raised("Could not create an empty node with name '%s'" % name) do + node = searcher.nodesearch(name) + end + assert_instance_of(SimpleNode, node, "Did not get a simple node back for %s" % name) + assert_equal(name, node.name, "Name was not set correctly") + end + end +end + +class LdapNodeTest < PuppetTest::TestCase + include NodeTesting + include PuppetTest::ServerTest + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + AST = Puppet::Parser::AST + 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 = NodeDef.new(:name => 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 + + 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 + +class LdapReconnectTests < PuppetTest::TestCase + include NodeTesting + include PuppetTest::ServerTest + include PuppetTest::ParserTesting + include PuppetTest::ResourceTesting + AST = Puppet::Parser::AST + confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") + + 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/test/network/handler/resource.rb b/test/network/handler/resource.rb index 589d22d83..18f52dbd6 100755 --- a/test/network/handler/resource.rb +++ b/test/network/handler/resource.rb @@ -236,7 +236,7 @@ class TestResourceServer < Test::Unit::TestCase def test_apply server = nil assert_nothing_raised do - server = Puppet::Network::Handler.resource.new() + server = Puppet::Network::Handler.resource.new(:Local => false) end file = tempfile() diff --git a/test/network/xmlrpc/processor.rb b/test/network/xmlrpc/processor.rb index 101d268b2..6808d0100 100755 --- a/test/network/xmlrpc/processor.rb +++ b/test/network/xmlrpc/processor.rb @@ -64,7 +64,7 @@ class TestXMLRPCProcessor < Test::Unit::TestCase request.expects(:handler=).with("myhandler") request.expects(:method=).with("mymethod") - @processor.expects(:verify).times(2) + @processor.stubs(:verify) @processor.expects(:handle).with(request.call, "params", request.name, request.ip) diff --git a/test/other/node.rb b/test/other/node.rb new file mode 100755 index 000000000..b3f12d11d --- /dev/null +++ b/test/other/node.rb @@ -0,0 +1,75 @@ +#!/usr/bin/env ruby + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'mocha' +require 'puppettest' +require 'puppet/node' + +class TestNode < Test::Unit::TestCase + include PuppetTest + Node = Puppet::Node + + # Make sure we get all the defaults correctly. + def test_initialize + node = nil + assert_nothing_raised("could not create a node without classes or parameters") do + node = Node.new("testing") + end + assert_equal("testing", node.name, "Did not set name correctly") + assert_equal({}, node.parameters, "Node parameters did not default correctly") + assert_equal([], node.classes, "Node classes did not default correctly") + assert_instance_of(Time, node.time, "Did not set the creation time") + + # Now test it with values for both + params = {"a" => "b"} + classes = %w{one two} + assert_nothing_raised("could not create a node with classes and parameters") do + node = Node.new("testing", :parameters => params, :classes => classes) + end + assert_equal("testing", node.name, "Did not set name correctly") + assert_equal(params, node.parameters, "Node parameters did not get set correctly") + assert_equal(classes, node.classes, "Node classes did not get set correctly") + + # And make sure a single class gets turned into an array + assert_nothing_raised("could not create a node with a class as a string") do + node = Node.new("testing", :classes => "test") + end + assert_equal(%w{test}, node.classes, "A node class string was not converted to an array") + + # Make sure we get environments + assert_nothing_raised("could not create a node with an environment") do + node = Node.new("testing", :environment => "test") + end + assert_equal("test", node.environment, "Environment was not set") + + # Now make sure we get the default env + Puppet[:environment] = "prod" + assert_nothing_raised("could not create a node with no environment") do + node = Node.new("testing") + end + assert_equal("prod", node.environment, "Did not get default environment") + + # But that it stays nil if there's no default env set + Puppet[:environment] = "" + assert_nothing_raised("could not create a node with no environment and no default env") do + node = Node.new("testing") + end + assert_nil(node.environment, "Got a default env when none was set") + + end + + # Verify that the node source wins over facter. + def test_fact_merge + node = Node.new("yay", :parameters => {"a" => "one", "b" => "two"}) + + assert_nothing_raised("Could not merge parameters") do + node.fact_merge("b" => "three", "c" => "yay") + end + params = node.parameters + assert_equal("one", params["a"], "Lost nodesource parameters in parameter merge") + assert_equal("two", params["b"], "Overrode nodesource parameters in parameter merge") + assert_equal("yay", params["c"], "Did not get facts in parameter merge") + end +end + diff --git a/test/rails/collection.rb b/test/rails/collection.rb index d878641be..18de3c77b 100755 --- a/test/rails/collection.rb +++ b/test/rails/collection.rb @@ -22,15 +22,12 @@ class TestRailsCollection < PuppetTest::TestCase def setup super Puppet[:trace] = false - @interp, @scope, @source = mkclassframing + @scope = mkscope end def test_collect_exported railsinit - # Set a hostname - @scope.host = Facter.value(:hostname) - # make an exported resource exported = mkresource(:type => "file", :title => "/tmp/exported", :exported => true, :params => {:owner => "root"}) @@ -51,10 +48,10 @@ class TestRailsCollection < PuppetTest::TestCase end # Set it in our scope - @scope.newcollection(coll) + @scope.configuration.add_collection(coll) # Make sure it's in the collections - assert_equal([coll], @scope.collections) + assert_equal([coll], @scope.configuration.collections) # And try to collect the virtual resources. ret = nil @@ -111,39 +108,39 @@ class TestRailsCollection < PuppetTest::TestCase # Now try storing our crap # Remark this as exported exported.exported = true - host = Puppet::Rails::Host.store( - :resources => [exported], - :facts => facts, - :name => facts["hostname"] - ) + exported.scope.stubs(:tags).returns([]) + node = mknode(facts["hostname"]) + node.parameters = facts + host = Puppet::Rails::Host.store(node, [exported]) assert(host, "did not get rails host") host.save # And make sure it's in there newres = host.resources.find_by_restype_and_title_and_exported("file", "/tmp/exported", true) assert(newres, "Did not find resource in db") - interp, scope, source = mkclassframing - scope.host = "two" + assert(newres.exported?, "Resource was not exported") + + # Make a new set with a different node name + node = mknode("other") + config = Puppet::Parser::Configuration.new(node, mkparser) + config.topscope.source = mock("source") + + # It's important that it's a different name, since same-name resources are ignored. + assert_equal("other", config.node.name, "Did not get correct node name") # Now make a collector coll = nil assert_nothing_raised do - coll = Puppet::Parser::Collector.new(scope, "file", nil, nil, :exported) + coll = Puppet::Parser::Collector.new(config.topscope, "file", nil, nil, :exported) end - # Set it in our scope - scope.newcollection(coll) - - # Make sure it's in the collections - assert_equal([coll], scope.collections) - # And try to collect the virtual resources. ret = nil - assert_nothing_raised do + assert_nothing_raised("Could not collect exported resources") do ret = coll.collect_exported end - assert_equal(["/tmp/exported"], ret.collect { |f| f.title }) + assert_equal(["/tmp/exported"], ret.collect { |f| f.title }, "Did not find resource in collction") # Make sure we can evaluate the same collection multiple times and # that later collections do nothing @@ -167,7 +164,6 @@ class TestRailsCollection < PuppetTest::TestCase normal = mkresource(:type => "file", :title => "/tmp/conflicttest", :params => {:owner => "root"}) @scope.setresource normal - @scope.host = "otherhost" # Now make a collector coll = nil @@ -186,15 +182,13 @@ class TestRailsCollection < PuppetTest::TestCase railsinit # Make our configuration - host = Puppet::Rails::Host.new(:name => "myhost") + host = Puppet::Rails::Host.new(:name => @scope.host) host.resources.build(:title => "/tmp/hosttest", :restype => "file", :exported => true) host.save - @scope.host = "myhost" - # Now make a collector coll = nil assert_nothing_raised do @@ -224,8 +218,6 @@ class TestRailsCollection < PuppetTest::TestCase host.save - @scope.host = "otherhost" - # Now make a collector coll = nil assert_nothing_raised do diff --git a/test/rails/interpreter.rb b/test/rails/configuration.rb index 0eba3f590..31d1cf779 100755 --- a/test/rails/interpreter.rb +++ b/test/rails/configuration.rb @@ -3,7 +3,6 @@ $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' -require 'puppet/parser/interpreter' require 'puppet/parser/parser' require 'puppet/network/client' require 'puppet/rails' @@ -13,49 +12,39 @@ require 'puppettest/servertest' require 'puppettest/railstesting' -class InterpreterRailsTests < PuppetTest::TestCase +class ConfigurationRailsTests < PuppetTest::TestCase include PuppetTest include PuppetTest::ServerTest include PuppetTest::ParserTesting include PuppetTest::ResourceTesting include PuppetTest::RailsTesting AST = Puppet::Parser::AST - NodeDef = Puppet::Parser::Interpreter::NodeDef confine "No rails support" => Puppet.features.rails? # We need to make sure finished objects are stored in the db. def test_finish_before_store railsinit - interp = mkinterp + config = mkconfig + config.ast_nodes = true + parser = config.parser - node = interp.newnode ["myhost"], :code => AST::ASTArray.new(:children => [ + node = parser.newnode [config.node.name], :code => AST::ASTArray.new(:children => [ resourcedef("file", "/tmp/yay", :group => "root"), defaultobj("file", :owner => "root") ]) - interp.newclass "myclass", :code => AST::ASTArray.new(:children => [ - ]) - - interp.newclass "sub", :parent => "myclass", - :code => AST::ASTArray.new(:children => [ - resourceoverride("file", "/tmp/yay", :owner => "root") - ] - ) - # Now do the rails crap Puppet[:storeconfigs] = true - interp.evaluate("myhost", {}) - - # And then retrieve the object from rails - #res = Puppet::Rails::Resource.find_by_restype_and_title("file", "/tmp/yay", :include => {:param_values => :param_names}) - res = Puppet::Rails::Resource.find_by_restype_and_title("file", "/tmp/yay") - - assert(res, "Did not get resource from rails") - - params = res.parameters - - assert_equal(["root"], params["owner"], "Did not get correct value for owner param") + Puppet::Rails::Host.expects(:store).with do |node, resources| + if res = resources.find { |r| r.type == "file" and r.title == "/tmp/yay" } + assert_equal("root", res["owner"], "Did not set default on resource") + true + else + raise "Resource was not passed to store()" + end + end + config.compile end def test_hoststorage @@ -79,13 +68,15 @@ class InterpreterRailsTests < PuppetTest::TestCase facts = {} Facter.each { |fact, val| facts[fact] = val } + node = mknode(facts["hostname"]) + node.parameters = facts objects = nil assert_nothing_raised { - objects = interp.run(facts["hostname"], facts) + objects = interp.compile(node) } - obj = Puppet::Rails::Host.find_by_name(facts["hostname"]) + obj = Puppet::Rails::Host.find_by_name(node.name) assert(obj, "Could not find host object") end end diff --git a/test/rails/host.rb b/test/rails/host.rb index f3190c047..67095a18a 100755 --- a/test/rails/host.rb +++ b/test/rails/host.rb @@ -56,13 +56,10 @@ class TestRailsHost < PuppetTest::TestCase # Now try storing our crap host = nil + node = mknode(facts["hostname"]) + node.parameters = facts assert_nothing_raised { - host = Puppet::Rails::Host.store( - :resources => resources, - :facts => facts, - :name => facts["hostname"], - :classes => ["one", "two::three", "four"] - ) + host = Puppet::Rails::Host.store(node, resources) } assert(host, "Did not create host") @@ -110,7 +107,7 @@ class TestRailsHost < PuppetTest::TestCase # Change a few resources resources.find_all { |r| r.title =~ /file2/ }.each do |r| - r.set("loglevel", "notice", r.source) + r.send(:set_parameter, "loglevel", "notice") end # And add a new resource @@ -124,13 +121,10 @@ class TestRailsHost < PuppetTest::TestCase facts["test1"] = "changedfact" facts.delete("ipaddress") host = nil + node = mknode(facts["hostname"]) + node.parameters = facts assert_nothing_raised { - host = Puppet::Rails::Host.store( - :resources => resources, - :facts => facts, - :name => facts["hostname"], - :classes => ["one", "two::three", "four"] - ) + host = Puppet::Rails::Host.store(node, resources) } # Make sure it sets the last_compile time @@ -162,7 +156,7 @@ class TestRailsHost < PuppetTest::TestCase Puppet[:storeconfigs] = true # this is the default server setup - master = Puppet::Network::Handler.master.new( + master = Puppet::Network::Handler.configuration.new( :Code => "", :UseNodes => true, :Local => true @@ -172,7 +166,7 @@ class TestRailsHost < PuppetTest::TestCase Puppet::Rails::Host.new(:name => "test", :ip => "192.168.0.3").save assert_nothing_raised("Failed to update last_connect for unknown host") do - master.freshness("created",'192.168.0.1') + master.version("created",'192.168.0.1') end # Make sure it created the host @@ -180,12 +174,10 @@ class TestRailsHost < PuppetTest::TestCase assert(created, "Freshness did not create host") assert(created.last_freshcheck, "Did not set last_freshcheck on created host") - assert_equal("192.168.0.1", created.ip, - "Did not set IP address on created host") # Now check on the existing host assert_nothing_raised("Failed to update last_connect for unknown host") do - master.freshness("test",'192.168.0.2') + master.version("test",'192.168.0.2') end # Recreate it, so we're not using the cached object. @@ -194,8 +186,6 @@ class TestRailsHost < PuppetTest::TestCase # Make sure it created the host assert(host.last_freshcheck, "Did not set last_freshcheck on existing host") - assert_equal("192.168.0.3", host.ip, - "Overrode IP on found host") end end diff --git a/test/rails/railsparameter.rb b/test/rails/railsparameter.rb index 82d978bb4..89c81ad30 100755 --- a/test/rails/railsparameter.rb +++ b/test/rails/railsparameter.rb @@ -21,8 +21,8 @@ class TestRailsParameter < Test::Unit::TestCase railsinit # Now create a source - interp = mkinterp - source = interp.newclass "myclass" + parser = mkparser + source = parser.newclass "myclass" host = Puppet::Rails::Host.new(:name => "myhost") |
