summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG16
-rw-r--r--lib/puppet/indirector/facts/memory.rb9
-rw-r--r--lib/puppet/indirector/indirection.rb8
-rw-r--r--lib/puppet/metatype/instances.rb11
-rw-r--r--lib/puppet/metatype/metaparams.rb35
-rw-r--r--lib/puppet/network/client/master.rb171
-rwxr-xr-xlib/puppet/network/handler/runner.rb2
-rw-r--r--lib/puppet/network/http.rb13
-rw-r--r--lib/puppet/network/http/handler.rb109
-rw-r--r--lib/puppet/network/http/mongrel.rb54
-rw-r--r--lib/puppet/network/http/mongrel/rest.rb37
-rw-r--r--lib/puppet/network/http/mongrel/xmlrpc.rb4
-rw-r--r--lib/puppet/network/http/webrick.rb51
-rw-r--r--lib/puppet/network/http/webrick/rest.rb41
-rw-r--r--lib/puppet/network/http/webrick/xmlrpc.rb4
-rw-r--r--lib/puppet/network/server.rb105
-rw-r--r--lib/puppet/node/configuration.rb20
-rw-r--r--lib/puppet/parser/compile.rb30
-rw-r--r--lib/puppet/transaction.rb2
-rw-r--r--lib/puppet/transportable.rb93
-rw-r--r--lib/puppet/type.rb21
-rw-r--r--lib/puppet/type/component.rb4
-rwxr-xr-xlib/puppet/type/pfile/group.rb4
-rw-r--r--spec/lib/autotest/discover.rb9
-rw-r--r--spec/lib/autotest/puppet_rspec.rb46
-rw-r--r--spec/lib/autotest/rspec.rb95
-rw-r--r--spec/lib/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb30
-rw-r--r--spec/lib/spec/dsl/behaviour.rb3
-rw-r--r--spec/lib/spec/runner/behaviour_runner.rb2
-rw-r--r--spec/spec_helper.rb13
-rwxr-xr-xspec/unit/indirector/indirection.rb12
-rwxr-xr-xspec/unit/indirector/node/memory.rb5
-rwxr-xr-xspec/unit/network/client/master.rb365
-rw-r--r--spec/unit/network/http.rb29
-rw-r--r--spec/unit/network/http/mongrel.rb124
-rw-r--r--spec/unit/network/http/mongrel/rest.rb287
-rw-r--r--spec/unit/network/http/mongrel/xmlrpc.rb0
-rw-r--r--spec/unit/network/http/webrick.rb115
-rw-r--r--spec/unit/network/http/webrick/rest.rb286
-rw-r--r--spec/unit/network/http/webrick/xmlrpc.rb0
-rw-r--r--spec/unit/network/rest_controller.rb65
-rw-r--r--spec/unit/network/server.rb419
-rwxr-xr-xspec/unit/node/configuration.rb34
-rwxr-xr-xspec/unit/other/transbucket.rb12
-rwxr-xr-xspec/unit/other/transobject.rb147
-rwxr-xr-xspec/unit/parser/compile.rb100
-rwxr-xr-xtest/language/compile.rb86
-rwxr-xr-xtest/language/parser.rb1
-rwxr-xr-xtest/lib/puppettest.rb11
-rw-r--r--test/lib/puppettest/support/assertions.rb2
-rwxr-xr-xtest/network/client/master.rb144
-rwxr-xr-xtest/network/client/resource.rb8
-rwxr-xr-xtest/network/handler/master.rb68
-rwxr-xr-xtest/network/handler/runner.rb72
-rwxr-xr-xtest/other/relationships.rb10
-rwxr-xr-xtest/ral/manager/instances.rb3
-rwxr-xr-xtest/ral/manager/type.rb63
-rwxr-xr-xtest/ral/types/file.rb6
-rwxr-xr-xtest/ral/types/host.rb6
-rwxr-xr-xtest/ral/types/parameter.rb5
-rwxr-xr-xtest/ral/types/sshkey.rb5
61 files changed, 2599 insertions, 933 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 5a7103403..7ecdb75b9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,19 @@
+ Fixed #800 -- invalid configurations are no longer
+ cached. This was done partially by adding a relationship
+ validation step once the entire configuration is created,
+ but it also required the previously-mentioned changes
+ to how the configuration retrieval process works.
+
+ Removed some functionality from the Master client,
+ since the local functionality has been replaced
+ with the Indirector already, and rearranging how configuration
+ retrieval is done to fix ordering and caching bugs.
+
+ The node scope is now above all other scopes besides
+ the 'main' scope, which should help make its variables
+ visible to other classes, assuming those classes were
+ not included in the node's parent.
+
Replaced GRATR::Digraph with Puppet::SimpleGraph as
the base class for Puppet's graphing. Functionality
should be equivalent but with dramatically better
diff --git a/lib/puppet/indirector/facts/memory.rb b/lib/puppet/indirector/facts/memory.rb
new file mode 100644
index 000000000..3c10d5964
--- /dev/null
+++ b/lib/puppet/indirector/facts/memory.rb
@@ -0,0 +1,9 @@
+require 'puppet/node/facts'
+require 'puppet/indirector/memory'
+
+class Puppet::Node::Facts::Memory < Puppet::Indirector::Memory
+ desc "Keep track of facts in memory but nowhere else. This is used for
+ one-time compiles, such as what the stand-alone ``puppet`` does.
+ To use this terminus, you must load it with the data you want it
+ to contain."
+end
diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb
index 9a9b1c0bf..816b4ffc5 100644
--- a/lib/puppet/indirector/indirection.rb
+++ b/lib/puppet/indirector/indirection.rb
@@ -13,6 +13,14 @@ class Puppet::Indirector::Indirection
@@indirections.find { |i| i.name == name }
end
+ # Find an indirected model by name. This is provided so that Terminus classes
+ # can specifically hook up with the indirections they are associated with.
+ def self.model(name)
+ match = @@indirections.find { |i| i.name == name }
+ return nil unless match
+ match.model
+ end
+
attr_accessor :name, :model
# Create and return our cache terminus.
diff --git a/lib/puppet/metatype/instances.rb b/lib/puppet/metatype/instances.rb
index 2f9918067..8cc648e8f 100644
--- a/lib/puppet/metatype/instances.rb
+++ b/lib/puppet/metatype/instances.rb
@@ -99,6 +99,8 @@ class Puppet::Type
end
end
+ # If they've specified a type and called on the base, then
+ # delegate to the subclass.
if type
if typeklass = self.type(type)
return typeklass.create(hash)
@@ -233,19 +235,22 @@ class Puppet::Type
hash.delete :name
end
- unless title
- raise Puppet::Error,
- "You must specify a title for objects of type %s" % self.to_s
+ if configuration = hash[:configuration]
+ hash.delete(:configuration)
end
+ raise(Puppet::Error, "You must specify a title for objects of type %s" % self.to_s) unless title
+
if hash.include? :type
unless self.validattr? :type
hash.delete :type
end
end
+
# okay, now make a transobject out of hash
begin
trans = Puppet::TransObject.new(title, self.name.to_s)
+ trans.configuration = configuration if configuration
hash.each { |param, value|
trans[param] = value
}
diff --git a/lib/puppet/metatype/metaparams.rb b/lib/puppet/metatype/metaparams.rb
index 1ab3f26c1..eb158a47d 100644
--- a/lib/puppet/metatype/metaparams.rb
+++ b/lib/puppet/metatype/metaparams.rb
@@ -94,7 +94,7 @@ class Puppet::Type
# We've got four relationship metaparameters, so this method is used
# to reduce code duplication between them.
- def store_relationship(param, values)
+ def munge_relationship(param, values)
# We need to support values passed in as an array or as a
# resource reference.
result = []
@@ -194,20 +194,24 @@ class Puppet::Type
unless aliases.is_a?(Array)
aliases = [aliases]
end
- @resource.info "Adding aliases %s" % aliases.collect { |a|
- a.inspect
- }.join(", ")
+
+ raise(ArgumentError, "Cannot add aliases without a configuration") unless @resource.configuration
+
+ @resource.info "Adding aliases %s" % aliases.collect { |a| a.inspect }.join(", ")
+
aliases.each do |other|
- if obj = @resource.class[other]
- unless obj == @resource
- self.fail(
- "%s can not create alias %s: object already exists" %
- [@resource.title, other]
- )
+ if obj = @resource.configuration.resource(@resource.class.name, other)
+ unless obj.object_id == @resource.object_id
+ self.fail("%s can not create alias %s: object already exists" % [@resource.title, other])
end
next
end
+
+ # LAK:FIXME Old-school, add the alias to the class.
@resource.class.alias(other, @resource)
+
+ # Newschool, add it to the configuration.
+ @resource.configuration.alias(@resource, other)
end
end
end
@@ -247,7 +251,16 @@ class Puppet::Type
end
def munge(rels)
- @resource.store_relationship(self.class.name, rels)
+ @resource.munge_relationship(self.class.name, rels)
+ end
+
+ def validate_relationship
+ @value.each do |value|
+ unless @resource.configuration.resource(*value)
+ description = self.class.direction == :in ? "dependency" : "dependent"
+ raise Puppet::Error, "Could not find #{description} %s[%s]" % [value[0].to_s.capitalize, value[1]]
+ end
+ end
end
# Create edges from each of our relationships. :in
diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb
index 5408cabe4..ea351ddc3 100644
--- a/lib/puppet/network/client/master.rb
+++ b/lib/puppet/network/client/master.rb
@@ -139,63 +139,57 @@ class Puppet::Network::Client::Master < Puppet::Network::Client
facts = self.class.facts
end
- if self.configuration or FileTest.exists?(self.cachefile)
- if self.fresh?(facts)
- Puppet.info "Config is up to date"
- if self.configuration
- return
- end
- if oldtext = self.retrievecache
- begin
- @configuration = YAML.load(oldtext).to_configuration
- rescue => detail
- Puppet.warning "Could not load cached configuration: %s" % detail
- end
- return
- end
- end
- end
- Puppet.debug("getting config")
+ raise Puppet::Network::ClientError.new("Could not retrieve any facts") unless facts.length > 0
# Retrieve the plugins.
- if Puppet[:pluginsync]
- getplugins()
- end
+ getplugins() if Puppet[:pluginsync]
- unless facts.length > 0
- raise Puppet::Network::ClientError.new(
- "Could not retrieve any facts"
- )
+ if (self.configuration or FileTest.exist?(self.cachefile)) and self.fresh?(facts)
+ Puppet.info "Configuration is up to date"
+ return if use_cached_config
end
- unless objects = get_actual_config(facts)
- @configuration = nil
+ Puppet.debug("Retrieving configuration")
+
+ # If we can't retrieve the configuration, just return, which will either
+ # fail, or use the in-memory configuration.
+ unless yaml_objects = get_actual_config(facts)
+ use_cached_config(true)
return
end
- unless objects.is_a?(Puppet::TransBucket)
- raise NetworkClientError,
- "Invalid returned objects of type %s" % objects.class
+ begin
+ objects = YAML.load(yaml_objects)
+ rescue => detail
+ msg = "Configuration could not be translated from yaml"
+ msg += "; using cached configuration" if use_cached_config(true)
+ Puppet.warning msg
+ return
end
self.setclasses(objects.classes)
# Clear all existing objects, so we can recreate our stack.
- if self.configuration
- clear()
- end
+ clear() if self.configuration
# Now convert the objects to a puppet configuration graph.
- @configuration = objects.to_configuration
+ begin
+ @configuration = objects.to_configuration
+ rescue => detail
+ clear()
+ puts detail.backtrace if Puppet[:trace]
+ msg = "Configuration could not be instantiated: %s" % detail
+ msg += "; using cached configuration" if use_cached_config(true)
+ Puppet.warning msg
+ return
+ end
- if @configuration.nil?
- raise Puppet::Error, "Configuration could not be processed"
+ if ! @configuration.from_cache
+ self.cache(yaml_objects)
end
# Keep the state database up to date.
@configuration.host_config = true
-
- return @configuration
end
# A simple proxy method, so it's easy to test.
@@ -270,11 +264,9 @@ class Puppet::Network::Client::Master < Puppet::Network::Client
Puppet.err "Could not retrieve configuration: %s" % detail
end
- if defined? @configuration and @configuration
+ if self.configuration
@configuration.retrieval_duration = duration
- unless @local
- Puppet.notice "Starting configuration run"
- end
+ Puppet.notice "Starting configuration run" unless @local
benchmark(:notice, "Finished configuration run") do
@configuration.apply(options)
end
@@ -500,34 +492,16 @@ class Puppet::Network::Client::Master < Puppet::Network::Client
# Actually retrieve the configuration, either from the server or from a
# local master.
def get_actual_config(facts)
- if @local
- return get_local_config(facts)
- else
- begin
- Timeout::timeout(self.class.timeout) do
- return get_remote_config(facts)
- end
- rescue Timeout::Error
- Puppet.err "Configuration retrieval timed out"
- return nil
+ begin
+ Timeout::timeout(self.class.timeout) do
+ return get_remote_config(facts)
end
+ rescue Timeout::Error
+ Puppet.err "Configuration retrieval timed out"
+ return nil
end
end
- # Retrieve a configuration from a local master.
- def get_local_config(facts)
- # If we're local, we don't have to do any of the conversion
- # stuff.
- objects = @driver.getconfig(facts, "yaml")
- @compile_time = Time.now
-
- if objects == ""
- raise Puppet::Error, "Could not retrieve configuration"
- end
-
- return objects
- end
-
# Retrieve a config from a remote master.
def get_remote_config(facts)
textobjects = ""
@@ -545,45 +519,18 @@ class Puppet::Network::Client::Master < Puppet::Network::Client
end
rescue => detail
- puts detail.backtrace
Puppet.err "Could not retrieve configuration: %s" % detail
-
- unless Puppet[:usecacheonfailure]
- @configuration = nil
- Puppet.warning "Not using cache on failed configuration"
- return
- end
+ return nil
end
end
- fromcache = false
- if textobjects == ""
- unless textobjects = self.retrievecache
- raise Puppet::Error.new(
- "Cannot connect to server and there is no cached configuration"
- )
- end
- Puppet.warning "Could not get config; using cached copy"
- fromcache = true
- else
- @compile_time = Time.now
- Puppet::Util::Storage.cache(:configuration)[:facts] = facts
- Puppet::Util::Storage.cache(:configuration)[:compile_time] = @compile_time
- end
+ return nil if textobjects == ""
- begin
- objects = YAML.load(textobjects)
- rescue => detail
- raise Puppet::Error,
- "Could not understand configuration: %s" %
- detail.to_s
- end
+ @compile_time = Time.now
+ Puppet::Util::Storage.cache(:configuration)[:facts] = facts
+ Puppet::Util::Storage.cache(:configuration)[:compile_time] = @compile_time
- if @cache and ! fromcache
- self.cache(textobjects)
- end
-
- return objects
+ return textobjects
end
def lockfile
@@ -609,4 +556,32 @@ class Puppet::Network::Client::Master < Puppet::Network::Client
Puppet.info "Sleeping for %s seconds (splay is enabled)" % time
sleep(time)
end
+
+ private
+
+ # Use our cached config, optionally specifying whether this is
+ # necessary because of a failure.
+ def use_cached_config(because_of_failure = false)
+ return true if self.configuration
+
+ if because_of_failure and ! Puppet[:usecacheonfailure]
+ @configuration = nil
+ Puppet.warning "Not using cache on failed configuration"
+ return false
+ end
+
+ return false unless oldtext = self.retrievecache
+
+ begin
+ @configuration = YAML.load(oldtext).to_configuration
+ @configuration.from_cache = true
+ @configuration.host_config = true
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.warning "Could not load cached configuration: %s" % detail
+ clear
+ return false
+ end
+ return true
+ end
end
diff --git a/lib/puppet/network/handler/runner.rb b/lib/puppet/network/handler/runner.rb
index a8d0da9ce..c97e4791a 100755
--- a/lib/puppet/network/handler/runner.rb
+++ b/lib/puppet/network/handler/runner.rb
@@ -43,7 +43,7 @@ class Puppet::Network::Handler
end
if ignoreschedules
- msg += " without schedules"
+ msg += " ignoring schedules"
end
Puppet.notice msg
diff --git a/lib/puppet/network/http.rb b/lib/puppet/network/http.rb
new file mode 100644
index 000000000..062c67c71
--- /dev/null
+++ b/lib/puppet/network/http.rb
@@ -0,0 +1,13 @@
+class Puppet::Network::HTTP
+ def self.server_class_by_type(kind)
+ return Puppet::Network::HTTP::WEBrick if kind.to_sym == :webrick
+ if kind.to_sym == :mongrel
+ raise ArgumentError, "Mongrel is not installed on this platform" unless Puppet.features.mongrel?
+ return Puppet::Network::HTTP::Mongrel
+ end
+ raise ArgumentError, "Unknown HTTP server name [#{kind}]"
+ end
+end
+
+require 'puppet/network/http/webrick'
+require 'puppet/network/http/mongrel'
diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb
new file mode 100644
index 000000000..773381c8d
--- /dev/null
+++ b/lib/puppet/network/http/handler.rb
@@ -0,0 +1,109 @@
+class Puppet::Network::HTTP::Handler
+ def initialize(args = {})
+ raise ArgumentError unless @server = args[:server]
+ raise ArgumentError unless @handler = args[:handler]
+ @model = find_model_for_handler(@handler)
+ register_handler
+ end
+
+ # handle an HTTP request
+ def process(request, response)
+ return do_find(request, response) if get?(request) and singular?(request)
+ return do_search(request, response) if get?(request) and plural?(request)
+ return do_destroy(request, response) if delete?(request) and singular?(request)
+ return do_save(request, response) if put?(request) and singular?(request)
+ raise ArgumentError, "Did not understand HTTP #{http_method(request)} request for '#{path(request)}'"
+ rescue Exception => e
+ return do_exception(request, response, e)
+ end
+
+ private
+
+ def do_find(request, response)
+ key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path}]")
+ args = params(request)
+ result = @model.find(key, args).to_yaml
+ encode_result(request, response, result)
+ end
+
+ def do_search(request, response)
+ args = params(request)
+ result = @model.search(args).collect {|obj| obj.to_yaml }
+ encode_result(request, response, result)
+ end
+
+ def do_destroy(request, response)
+ key = request_key(request) || raise(ArgumentError, "Could not locate lookup key in request path [#{path}]")
+ args = params(request)
+ result = @model.destroy(key, args)
+ encode_result(request, response, YAML.dump(result))
+ end
+
+ def do_save(request, response)
+ data = body(request)
+ raise ArgumentError, "No data to save" if !data or data.empty?
+ args = params(request)
+ obj = @model.new
+ result = obj.save(args.merge(:data => data)).to_yaml
+ encode_result(request, response, result)
+ end
+
+ def do_exception(request, response, exception, status=404)
+ encode_result(request, response, exception.to_s, status)
+ end
+
+ def find_model_for_handler(handler)
+ Puppet::Indirector::Indirection.model(handler) ||
+ raise(ArgumentError, "Cannot locate indirection [#{handler}].")
+ end
+
+ def get?(request)
+ http_method(request) == 'GET'
+ end
+
+ def put?(request)
+ http_method(request) == 'PUT'
+ end
+
+ def delete?(request)
+ http_method(request) == 'DELETE'
+ end
+
+ def singular?(request)
+ %r{/#{@handler.to_s}$}.match(path(request))
+ end
+
+ def plural?(request)
+ %r{/#{@handler.to_s}s$}.match(path(request))
+ end
+
+ # methods specific to a given web server
+
+ def register_handler
+ raise NotImplementedError
+ end
+
+ def http_method(request)
+ raise NotImplementedError
+ end
+
+ def path(request)
+ raise NotImplementedError
+ end
+
+ def request_key(request)
+ raise NotImplementedError
+ end
+
+ def body(request)
+ raise NotImplementedError
+ end
+
+ def params(request)
+ raise NotImplementedError
+ end
+
+ def encode_result(request, response, result, status = 200)
+ raise NotImplementedError
+ end
+end
diff --git a/lib/puppet/network/http/mongrel.rb b/lib/puppet/network/http/mongrel.rb
new file mode 100644
index 000000000..8ea669531
--- /dev/null
+++ b/lib/puppet/network/http/mongrel.rb
@@ -0,0 +1,54 @@
+require 'mongrel' if Puppet.features.mongrel?
+
+require 'puppet/network/http/mongrel/rest'
+require 'puppet/network/http/mongrel/xmlrpc'
+
+class Puppet::Network::HTTP::Mongrel
+ def initialize(args = {})
+ @listening = false
+ end
+
+ def listen(args = {})
+ raise ArgumentError, ":handlers must be specified." if !args[:handlers] or args[:handlers].empty?
+ raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty?
+ raise ArgumentError, ":address must be specified." unless args[:address]
+ raise ArgumentError, ":port must be specified." unless args[:port]
+ raise "Mongrel server is already listening" if listening?
+
+ @protocols = args[:protocols]
+ @handlers = args[:handlers]
+ @server = Mongrel::HttpServer.new(args[:address], args[:port])
+
+ setup_handlers
+
+ @server.run
+ @listening = true
+ end
+
+ def unlisten
+ raise "Mongrel server is not listening" unless listening?
+ @server.graceful_shutdown
+ @listening = false
+ end
+
+ def listening?
+ @listening
+ end
+
+ private
+
+ def setup_handlers
+ @protocols.each do |protocol|
+ @handlers.each do |handler|
+ class_for_protocol(protocol).new(:server => @server, :handler => handler)
+ end
+ end
+ end
+
+ # TODO/FIXME: need a spec which forces delegation to the real class
+ def class_for_protocol(protocol)
+ return Puppet::Network::HTTP::MongrelREST if protocol.to_sym == :rest
+ return Puppet::Network::HTTP::MongrelXMLRPC if protocol.to_sym == :xmlrpc
+ raise ArgumentError, "Unknown protocol [#{protocol}]."
+ end
+end
diff --git a/lib/puppet/network/http/mongrel/rest.rb b/lib/puppet/network/http/mongrel/rest.rb
new file mode 100644
index 000000000..db63613ab
--- /dev/null
+++ b/lib/puppet/network/http/mongrel/rest.rb
@@ -0,0 +1,37 @@
+require 'puppet/network/http/handler'
+
+class Puppet::Network::HTTP::MongrelREST < Puppet::Network::HTTP::Handler
+
+ private
+
+ def register_handler
+ @server.register('/' + @handler.to_s, self)
+ @server.register('/' + @handler.to_s + 's', self)
+ end
+
+ def http_method(request)
+ request.params[Mongrel::Const::REQUEST_METHOD]
+ end
+
+ def path(request)
+ '/' + request.params[Mongrel::Const::REQUEST_PATH].split('/')[1]
+ end
+
+ def request_key(request)
+ request.params[Mongrel::Const::REQUEST_PATH].split('/')[2]
+ end
+
+ def body(request)
+ request.body
+ end
+
+ def params(request)
+ Mongrel::HttpRequest.query_parse(request.params["QUERY_STRING"])
+ end
+
+ def encode_result(request, response, result, status = 200)
+ response.start(status) do |head, body|
+ body.write(result)
+ end
+ end
+end
diff --git a/lib/puppet/network/http/mongrel/xmlrpc.rb b/lib/puppet/network/http/mongrel/xmlrpc.rb
new file mode 100644
index 000000000..92acd4f0e
--- /dev/null
+++ b/lib/puppet/network/http/mongrel/xmlrpc.rb
@@ -0,0 +1,4 @@
+class Puppet::Network::HTTP::MongrelXMLRPC
+ def initialize(args = {})
+ end
+end
diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb
new file mode 100644
index 000000000..c4b2ed3c6
--- /dev/null
+++ b/lib/puppet/network/http/webrick.rb
@@ -0,0 +1,51 @@
+require 'webrick'
+require 'webrick/https'
+require 'puppet/network/http/webrick/rest'
+require 'puppet/network/http/webrick/xmlrpc'
+
+class Puppet::Network::HTTP::WEBrick
+ def initialize(args = {})
+ @listening = false
+ end
+
+ def listen(args = {})
+ raise ArgumentError, ":handlers must be specified." if !args[:handlers] or args[:handlers].empty?
+ raise ArgumentError, ":protocols must be specified." if !args[:protocols] or args[:protocols].empty?
+ raise ArgumentError, ":address must be specified." unless args[:address]
+ raise ArgumentError, ":port must be specified." unless args[:port]
+ raise "WEBrick server is already listening" if listening?
+
+ @protocols = args[:protocols]
+ @handlers = args[:handlers]
+ @server = WEBrick::HTTPServer.new(:BindAddress => args[:address], :Port => args[:port])
+ setup_handlers
+ @server.start
+ @listening = true
+ end
+
+ def unlisten
+ raise "WEBrick server is not listening" unless listening?
+ @server.shutdown
+ @listening = false
+ end
+
+ def listening?
+ @listening
+ end
+
+ private
+
+ def setup_handlers
+ @protocols.each do |protocol|
+ @handlers.each do |handler|
+ class_for_protocol(protocol).new(:server => @server, :handler => handler)
+ end
+ end
+ end
+
+ def class_for_protocol(protocol)
+ return Puppet::Network::HTTP::WEBrickREST if protocol.to_sym == :rest
+ return Puppet::Network::HTTP::WEBrickXMLRPC if protocol.to_sym == :xmlrpc
+ raise ArgumentError, "Unknown protocol [#{protocol}]."
+ end
+end
diff --git a/lib/puppet/network/http/webrick/rest.rb b/lib/puppet/network/http/webrick/rest.rb
new file mode 100644
index 000000000..dd0c84d61
--- /dev/null
+++ b/lib/puppet/network/http/webrick/rest.rb
@@ -0,0 +1,41 @@
+require 'puppet/network/http/handler'
+
+class Puppet::Network::HTTP::WEBrickREST < Puppet::Network::HTTP::Handler
+
+ # WEBrick uses a service() method to respond to requests. Simply delegate to the handler response() method.
+ def service(request, response)
+ process(request, response)
+ end
+
+ private
+
+ def register_handler
+ @server.mount('/' + @handler.to_s, self)
+ @server.mount('/' + @handler.to_s + 's', self)
+ end
+
+ def http_method(request)
+ request.request_method
+ end
+
+ def path(request)
+ '/' + request.path.split('/')[1]
+ end
+
+ def request_key(request)
+ request.path.split('/')[2]
+ end
+
+ def body(request)
+ request.body
+ end
+
+ def params(request)
+ request.query
+ end
+
+ def encode_result(request, response, result, status = 200)
+ response.status = status
+ response.body = result
+ end
+end \ No newline at end of file
diff --git a/lib/puppet/network/http/webrick/xmlrpc.rb b/lib/puppet/network/http/webrick/xmlrpc.rb
new file mode 100644
index 000000000..793708f8a
--- /dev/null
+++ b/lib/puppet/network/http/webrick/xmlrpc.rb
@@ -0,0 +1,4 @@
+class Puppet::Network::HTTP::WEBrickXMLRPC
+ def initialize(args = {})
+ end
+end
diff --git a/lib/puppet/network/server.rb b/lib/puppet/network/server.rb
index 84a71a6b4..50e3bd686 100644
--- a/lib/puppet/network/server.rb
+++ b/lib/puppet/network/server.rb
@@ -1,66 +1,65 @@
class Puppet::Network::Server
- attr_reader :server_type
+ attr_reader :server_type, :protocols, :address, :port
- # which HTTP server subclass actually handles web requests of a certain type? (e.g., :rest => RESTServer)
- def self.server_class_by_name(name)
- klass = (name.to_s + 'Server').to_sym
- const_get klass
- end
-
- # we will actually return an instance of the Server subclass which handles the HTTP web server, instead of
- # an instance of this generic Server class. A tiny bit of sleight-of-hand is necessary to make this happen.
- def self.new(args = {})
- server_type = Puppet[:servertype] or raise "No servertype configuration found."
- obj = self.server_class_by_name(server_type).allocate
- obj.send :initialize, args.merge(:server_type => server_type)
- obj
- end
-
- def initialize(args = {})
- @routes = {}
- @listening = false
- @server_type = args[:server_type]
- self.register(args[:handlers]) if args[:handlers]
- end
+ def initialize(args = {})
+ @server_type = Puppet[:servertype] or raise "No servertype configuration found." # e.g., WEBrick, Mongrel, etc.
+ http_server_class || raise(ArgumentError, "Could not determine HTTP Server class for server type [#{@server_type}]")
+ @address = args[:address] || Puppet[:bindaddress] ||
+ raise(ArgumentError, "Must specify :address or configure Puppet :bindaddress.")
+ @port = args[:port] || Puppet[:masterport] ||
+ raise(ArgumentError, "Must specify :port or configure Puppet :masterport")
+ @protocols = []
+ @listening = false
+ @routes = {}
+ self.register(args[:handlers]) if args[:handlers]
+ end
- def register(*indirections)
- raise ArgumentError, "indirection names are required" if indirections.empty?
- indirections.flatten.each { |i| @routes[i.to_sym] = true }
- end
+ def register(*indirections)
+ raise ArgumentError, "Indirection names are required." if indirections.empty?
+ indirections.flatten.each { |i| @routes[i.to_sym] = true }
+ end
- def unregister(*indirections)
- indirections = @routes.keys if indirections.empty?
- indirections.flatten.each do |i|
- raise(ArgumentError, "indirection [%s] is not known" % i) unless @routes[i.to_sym]
- @routes.delete(i.to_sym)
+ def unregister(*indirections)
+ raise "Cannot unregister indirections while server is listening." if listening?
+ indirections = @routes.keys if indirections.empty?
+
+ indirections.flatten.each do |i|
+ raise(ArgumentError, "Indirection [%s] is unknown." % i) unless @routes[i.to_sym]
+ end
+
+ indirections.flatten.each do |i|
+ @routes.delete(i.to_sym)
+ end
end
- end
- def listening?
- @listening
- end
+ def listening?
+ @listening
+ end
- def listen
- raise "Cannot listen -- already listening" if listening?
- start_web_server
- @listening = true
- end
+ def listen
+ raise "Cannot listen -- already listening." if listening?
+ http_server.listen(@routes.dup)
+ @listening = true
+ end
- def unlisten
- raise "Cannot unlisten -- not currently listening" unless listening?
- stop_web_server
- @listening = false
- end
+ def unlisten
+ raise "Cannot unlisten -- not currently listening." unless listening?
+ http_server.unlisten
+ @listening = false
+ end
+
+ def http_server_class
+ http_server_class_by_type(@server_type)
+ end
private
- def start_web_server
- raise NotImplementedError, "this method needs to be implemented by the actual web server (sub)class"
- end
-
- def stop_web_server
- raise NotImplementedError, "this method needs to be implemented by the actual web server (sub)class"
- end
+ def http_server
+ @http_server ||= http_server_class.new
+ end
+
+ def http_server_class_by_type(kind)
+ Puppet::Network::HTTP.server_class_by_type(kind)
+ end
end
-
diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb
index e49090d70..804f357d1 100644
--- a/lib/puppet/node/configuration.rb
+++ b/lib/puppet/node/configuration.rb
@@ -38,6 +38,10 @@ class Puppet::Node::Configuration < Puppet::PGraph
# relationship graph.
attr_accessor :is_relationship_graph
+ # Whether this configuration was retrieved from the cache, which affects
+ # whether it is written back out again.
+ attr_accessor :from_cache
+
# Add classes to our class list.
def add_class(*classes)
classes.each do |klass|
@@ -66,6 +70,16 @@ class Puppet::Node::Configuration < Puppet::PGraph
end
end
+ # Create an alias for a resource.
+ def alias(resource, name)
+ resource.ref =~ /^(.+)\[/
+
+ newref = "%s[%s]" % [$1 || resource.class.name, name]
+ raise(ArgumentError, "Cannot alias %s to %s; resource %s already exists" % [resource.ref, name, newref]) if @resource_table[newref]
+ @resource_table[newref] = resource
+ @aliases[resource.ref] << newref
+ end
+
# Apply our configuration to the local host. Valid options
# are:
# :tags - set the tags that restrict what resources run
@@ -274,6 +288,8 @@ class Puppet::Node::Configuration < Puppet::PGraph
@applying = false
@relationship_graph = nil
+ @aliases = Hash.new { |hash, key| hash[key] = [] }
+
if block_given?
yield(self)
finalize()
@@ -331,7 +347,9 @@ class Puppet::Node::Configuration < Puppet::PGraph
# references to the resource instances.
def remove_resource(*resources)
resources.each do |resource|
- @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref)
+ @resource_table.delete(resource.ref)
+ @aliases[resource.ref].each { |res_alias| @resource_table.delete(res_alias) }
+ @aliases[resource.ref].clear
remove_vertex!(resource) if vertex?(resource)
@relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource)
resource.remove
diff --git a/lib/puppet/parser/compile.rb b/lib/puppet/parser/compile.rb
index f23b42a35..7f568f1b3 100644
--- a/lib/puppet/parser/compile.rb
+++ b/lib/puppet/parser/compile.rb
@@ -14,7 +14,7 @@ require 'puppet/util/errors'
class Puppet::Parser::Compile
include Puppet::Util
include Puppet::Util::Errors
- attr_reader :topscope, :parser, :node, :facts, :collections, :configuration
+ attr_reader :parser, :node, :facts, :collections, :configuration, :node_scope
# Add a collection to the global list.
def add_collection(coll)
@@ -107,7 +107,7 @@ class Puppet::Parser::Compile
# Evaluate all of the classes specified by the node.
def evaluate_node_classes
- evaluate_classes(@node.classes, @topscope)
+ evaluate_classes(@node.classes, topscope)
end
# Evaluate each specified class in turn. If there are any classes we can't
@@ -142,9 +142,7 @@ class Puppet::Parser::Compile
# 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
+ string = "%s[%s]" % [string.capitalize, name] if name
@resource_table[string]
end
@@ -173,7 +171,7 @@ class Puppet::Parser::Compile
# using the top scope. Adds an edge between the scope and
# its parent to the graph.
def newscope(parent, options = {})
- parent ||= @topscope
+ parent ||= topscope
options[:compile] = self
options[:parser] ||= self.parser
scope = Puppet::Parser::Scope.new(options)
@@ -229,6 +227,12 @@ class Puppet::Parser::Compile
@configuration.add_edge!(scope.resource, resource)
end
+ # The top scope is usually the top-level scope, but if we're using AST nodes,
+ # then it is instead the node's scope.
+ def topscope
+ node_scope || @topscope
+ end
+
private
# If ast nodes are enabled, then see if we can find and evaluate one.
@@ -241,10 +245,7 @@ class Puppet::Parser::Compile
break if astnode = @parser.nodes[name.to_s.downcase]
end
- unless astnode
- astnode = @parser.nodes["default"]
- end
- unless astnode
+ unless (astnode ||= @parser.nodes["default"])
raise Puppet::ParseError, "Could not find default node or by name with '%s'" % node.names.join(", ")
end
@@ -253,6 +254,12 @@ class Puppet::Parser::Compile
resource = Puppet::Parser::Resource.new(:type => "node", :title => astnode.classname, :scope => topscope, :source => topscope.source)
store_resource(topscope, resource)
@configuration.tag(astnode.classname)
+
+ resource.evaluate
+
+ # Now set the node scope appropriately, so that :topscope can
+ # behave differently.
+ @node_scope = class_scope(astnode)
end
# Evaluate our collections and return true if anything returned an object.
@@ -318,6 +325,8 @@ class Puppet::Parser::Compile
@configuration.add_vertex!(@main_resource)
@resource_table["Class[main]"] = @main_resource
+
+ @main_resource.evaluate
end
# Make sure the entire configuration is evaluated.
@@ -408,7 +417,6 @@ class Puppet::Parser::Compile
# A graph for maintaining scope relationships.
@scope_graph = GRATR::Digraph.new
- @scope_graph.add_vertex!(@topscope)
# For maintaining the relationship between scopes and their resources.
@configuration = Puppet::Node::Configuration.new(@node.name)
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index c8fc2f199..ef53889cf 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -311,7 +311,7 @@ class Transaction
ret = eval_resource(resource)
end
- if Puppet[:evaltrace]
+ if Puppet[:evaltrace] and @configuration.host_config?
resource.info "Evaluated in %0.2f seconds" % seconds
end
ret
diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb
index 281ad00d3..6a573489c 100644
--- a/lib/puppet/transportable.rb
+++ b/lib/puppet/transportable.rb
@@ -59,15 +59,11 @@ module Puppet
tmpname = @type
end
trans = TransObject.new(tmpname, :component)
- if defined? @parameters
- @parameters.each { |param,value|
- Puppet.debug "Defining %s on %s of type %s" %
- [param,@name,@type]
- trans[param] = value
- }
- else
- #Puppet.debug "%s[%s] has no parameters" % [@type, @name]
- end
+ @params.each { |param,value|
+ next unless Puppet::Type::Component.validattr?(param)
+ Puppet.debug "Defining %s on %s of type %s" % [param,@name,@type]
+ trans[param] = value
+ }
Puppet::Type::Component.create(trans)
end
@@ -107,16 +103,7 @@ module Puppet
def to_type
retobj = nil
if typeklass = Puppet::Type.type(self.type)
- # FIXME This should really be done differently, but...
- if retobj = typeklass[self.name]
- self.each do |param, val|
- retobj[param] = val
- end
- else
- unless retobj = typeklass.create(self)
- return nil
- end
- end
+ return typeklass.create(self)
else
return to_component
end
@@ -135,7 +122,7 @@ module Puppet
class TransBucket
include Enumerable
- attr_accessor :name, :type, :file, :line, :classes, :keyword, :top
+ attr_accessor :name, :type, :file, :line, :classes, :keyword, :top, :configuration
%w{delete shift include? length empty? << []}.each { |method|
define_method(method) do |*args|
@@ -218,11 +205,13 @@ module Puppet
def to_configuration
configuration = Puppet::Node::Configuration.new(Facter.value("hostname")) do |config|
delver = proc do |obj|
+ obj.configuration = config
unless container = config.resource(obj.to_ref)
container = obj.to_type
config.add_resource container
end
obj.each do |child|
+ child.configuration = config
unless resource = config.resource(child.to_ref)
next unless resource = child.to_type
config.add_resource resource
@@ -252,65 +241,25 @@ module Puppet
end
def to_type
- # this container will contain the equivalent of all objects at
- # this level
- #container = Puppet::Component.new(:name => @name, :type => @type)
- #unless defined? @name
- # raise Puppet::DevError, "TransBuckets must have names"
- #end
unless defined? @type
Puppet.debug "TransBucket '%s' has no type" % @name
end
- usetrans = true
- if usetrans
- tmpname = nil
-
- # Nodes have the same name and type
- if self.name
- tmpname = "%s[%s]" % [@type, self.name]
- else
- tmpname = @type
- end
- trans = TransObject.new(tmpname, :component)
- if defined? @parameters
- @parameters.each { |param,value|
- Puppet.debug "Defining %s on %s of type %s" %
- [param,@name,@type]
- trans[param] = value
- }
- else
- #Puppet.debug "%s[%s] has no parameters" % [@type, @name]
- end
- container = Puppet::Type::Component.create(trans)
+ # Nodes have the same name and type
+ if self.name
+ tmpname = "%s[%s]" % [@type, self.name]
else
- hash = {
- :name => self.name,
- :type => @type
- }
- if defined? @parameters
- @parameters.each { |param,value|
- Puppet.debug "Defining %s on %s of type %s" %
- [param,@name,@type]
- hash[param] = value
- }
- else
- #Puppet.debug "%s[%s] has no parameters" % [@type, @name]
- end
-
- container = Puppet::Type::Component.create(hash)
+ tmpname = @type
end
- #Puppet.info container.inspect
-
- # unless we successfully created the container, return an error
- unless container
- Puppet.warning "Got no container back"
- return nil
+ trans = TransObject.new(tmpname, :component)
+ if defined? @parameters
+ @parameters.each { |param,value|
+ Puppet.debug "Defining %s on %s of type %s" %
+ [param,@name,@type]
+ trans[param] = value
+ }
end
-
- # at this point, no objects at are level are still Transportable
- # objects
- return container
+ return Puppet::Type::Component.create(trans)
end
def param(param,value)
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index b7ff1f664..f5dd0f8dd 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -182,7 +182,7 @@ class Type
# directly from it. This is the main object instantiation mechanism.
if hash.is_a?(Puppet::TransObject)
#self[:name] = hash[:name]
- [:file, :line, :tags].each { |getter|
+ [:file, :line, :tags, :configuration].each { |getter|
if hash.respond_to?(getter)
setter = getter.to_s + "="
if val = hash.send(getter)
@@ -194,10 +194,11 @@ class Type
# XXX This will need to change when transobjects change to titles.
@title = hash.name
hash = hash.to_hash
- elsif hash[:title]
- # XXX This should never happen
- @title = hash[:title]
- hash.delete(:title)
+ else
+ if hash[:title]
+ @title = hash[:title]
+ hash.delete(:title)
+ end
end
# Before anything else, set our parent if it was included
@@ -221,7 +222,7 @@ class Type
if attrs.include?(namevar)
attrs.delete(namevar)
else
- self.devfail "My namevar isn\'t a valid attribute...?"
+ self.devfail "My namevar isn't a valid attribute...?"
end
else
self.devfail "I was not passed a namevar"
@@ -284,6 +285,14 @@ class Type
# Scheduling has to be done when the whole config is instantiated, so
# that file order doesn't matter in finding them.
self.schedule
+
+ # Make sure all of our relationships are valid. Again, must be done
+ # when the entire configuration is instantiated.
+ self.class.relationship_params.collect do |klass|
+ if param = @parameters[klass.name]
+ param.validate_relationship
+ end
+ end.flatten.reject { |r| r.nil? }
end
# Return a cached value
diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb
index 4dc542a65..7aa24a302 100644
--- a/lib/puppet/type/component.rb
+++ b/lib/puppet/type/component.rb
@@ -93,9 +93,9 @@ Puppet::Type.newtype(:component) do
end
# Initialize a new component
- def initialize(args)
+ def initialize(*args)
@children = []
- super(args)
+ super
# If the title isn't a full resource reference, assume
# we're a class and make an alias for that.
diff --git a/lib/puppet/type/pfile/group.rb b/lib/puppet/type/pfile/group.rb
index 9625b6354..5f7caf342 100755
--- a/lib/puppet/type/pfile/group.rb
+++ b/lib/puppet/type/pfile/group.rb
@@ -6,6 +6,10 @@ module Puppet
name or group ID."
@event = :file_changed
+ validate do |group|
+ raise(Puppet::Error, "Invalid group name '%s'" % group.inspect) unless group and group != ""
+ end
+
def id2name(id)
if id > 70000
return nil
diff --git a/spec/lib/autotest/discover.rb b/spec/lib/autotest/discover.rb
new file mode 100644
index 000000000..0ac563724
--- /dev/null
+++ b/spec/lib/autotest/discover.rb
@@ -0,0 +1,9 @@
+require 'autotest'
+
+Autotest.add_discovery do
+ "rspec"
+end
+
+Autotest.add_discovery do
+ "puppet"
+end
diff --git a/spec/lib/autotest/puppet_rspec.rb b/spec/lib/autotest/puppet_rspec.rb
new file mode 100644
index 000000000..8536f3912
--- /dev/null
+++ b/spec/lib/autotest/puppet_rspec.rb
@@ -0,0 +1,46 @@
+require 'autotest'
+require 'autotest/rspec'
+
+class Autotest::PuppetRspec < Autotest::Rspec
+ def initialize # :nodoc:
+ super
+ @test_mappings = {
+ # the libraries under lib/puppet
+ %r%^lib/puppet/(.*)\.rb$% => proc { |filename, m|
+ files_matching %r!spec/(unit|integration)/#{m[1]}.rb!
+ },
+
+ # the actual spec files themselves
+ %r%^spec/(unit|integration)/.*\.rb$% => proc { |filename, _|
+ filename
+ },
+
+ # force a complete re-run for all of these:
+
+ # main puppet lib
+ %r!^lib/puppet\.rb$! => proc { |filename, _|
+ files_matching %r!spec/(unit|integration)/.*\.rb!
+ },
+
+ # the spec_helper
+ %r!^spec/spec_helper\.rb$! => proc { |filename, _|
+ files_matching %r!spec/(unit|integration)/.*\.rb!
+ },
+
+ # the puppet test libraries
+ %r!^test/lib/puppettest/.*! => proc { |filename, _|
+ files_matching %r!spec/(unit|integration)/.*\.rb!
+ },
+
+ # the puppet spec libraries
+ %r!^spec/lib/spec.*! => proc { |filename, _|
+ files_matching %r!spec/(unit|integration)/.*\.rb!
+ },
+
+ # the monkey patches for rspec
+ %r!^spec/lib/monkey_patches/.*! => proc { |filename, _|
+ files_matching %r!spec/(unit|integration)/.*\.rb!
+ },
+ }
+ end
+end
diff --git a/spec/lib/autotest/rspec.rb b/spec/lib/autotest/rspec.rb
new file mode 100644
index 000000000..d4b77ea6b
--- /dev/null
+++ b/spec/lib/autotest/rspec.rb
@@ -0,0 +1,95 @@
+require 'autotest'
+
+class RspecCommandError < StandardError; end
+
+class Autotest::Rspec < Autotest
+
+ def initialize(kernel=Kernel, separator=File::SEPARATOR, alt_separator=File::ALT_SEPARATOR) # :nodoc:
+ super()
+ @kernel, @separator, @alt_separator = kernel, separator, alt_separator
+ @spec_command = spec_command
+
+ # watch out: Ruby bug (1.8.6):
+ # %r(/) != /\//
+ # since Ruby compares the REGEXP source, not the resulting pattern
+ @test_mappings = {
+ %r%^spec/.*\.rb$% => kernel.proc { |filename, _|
+ filename
+ },
+ %r%^lib/(.*)\.rb$% => kernel.proc { |_, m|
+ ["spec/#{m[1]}_spec.rb"]
+ },
+ %r%^spec/(spec_helper|shared/.*)\.rb$% => kernel.proc {
+ files_matching %r%^spec/.*_spec\.rb$%
+ }
+ }
+ end
+
+ def tests_for_file(filename)
+ super.select { |f| @files.has_key? f }
+ end
+
+ alias :specs_for_file :tests_for_file
+
+ def failed_results(results)
+ results.scan(/^\d+\)\n(?:\e\[\d*m)?(?:.*?Error in )?'([^\n]*)'(?: FAILED)?(?:\e\[\d*m)?\n(.*?)\n\n/m)
+ end
+
+ def handle_results(results)
+ @files_to_test = consolidate_failures failed_results(results)
+ unless @files_to_test.empty? then
+ hook :red
+ else
+ hook :green
+ end unless $TESTING
+ @tainted = true unless @files_to_test.empty?
+ end
+
+ def consolidate_failures(failed)
+ filters = Hash.new { |h,k| h[k] = [] }
+ failed.each do |spec, failed_trace|
+ @files.keys.select{|f| f =~ /spec\//}.each do |f|
+ if failed_trace =~ Regexp.new(f)
+ filters[f] << spec
+ break
+ end
+ end
+ end
+ return filters
+ end
+
+ def make_test_cmd(files_to_test)
+ return "#{ruby} -S #{@spec_command} #{add_options_if_present} #{files_to_test.keys.flatten.join(' ')}"
+ end
+
+ def add_options_if_present
+ File.exist?("spec/spec.opts") ? "-O spec/spec.opts " : ""
+ end
+
+ # Finds the proper spec command to use. Precendence
+ # is set in the lazily-evaluated method spec_commands. Alias + Override
+ # that in ~/.autotest to provide a different spec command
+ # then the default paths provided.
+ def spec_command
+ spec_commands.each do |command|
+ if File.exists?(command)
+ return @alt_separator ? (command.gsub @separator, @alt_separator) : command
+ end
+ end
+
+ raise RspecCommandError, "No spec command could be found!"
+ end
+
+ # Autotest will look for spec commands in the following
+ # locations, in this order:
+ #
+ # * bin/spec
+ # * default spec bin/loader installed in Rubygems
+ def spec_commands
+ [
+ File.join('bin', 'spec'),
+ File.join(Config::CONFIG['bindir'], 'spec')
+ ]
+ end
+
+end
diff --git a/spec/lib/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb b/spec/lib/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb
new file mode 100644
index 000000000..a2467eac8
--- /dev/null
+++ b/spec/lib/monkey_patches/add_confine_and_runnable_to_rspec_dsl.rb
@@ -0,0 +1,30 @@
+dir = File.expand_path(File.dirname(__FILE__))
+$LOAD_PATH.unshift("#{dir}/../../lib")
+$LOAD_PATH.unshift("#{dir}/../../../lib")
+$LOAD_PATH.unshift("#{dir}/../../../test/lib") # Add the old test dir, so that we can still find our local mocha and spec
+
+require 'spec'
+require 'puppettest'
+require 'puppettest/runnable_test'
+
+module Spec
+ module Runner
+ class BehaviourRunner
+ def run_behaviours
+ @behaviours.each do |behaviour|
+ # LAK:NOTE: this 'runnable' test is Puppet-specific.
+ next unless behaviour.runnable?
+ behaviour.run(@options.reporter, @options.dry_run, @options.reverse, @options.timeout)
+ end
+ end
+ end
+ end
+end
+
+module Spec
+ module DSL
+ class EvalModule < Module
+ include PuppetTest::RunnableTest
+ end
+ end
+end
diff --git a/spec/lib/spec/dsl/behaviour.rb b/spec/lib/spec/dsl/behaviour.rb
index 159a0ba7e..cc71ccffe 100644
--- a/spec/lib/spec/dsl/behaviour.rb
+++ b/spec/lib/spec/dsl/behaviour.rb
@@ -2,9 +2,6 @@ require(File.expand_path(File.dirname(__FILE__) + '../../../../../test/lib/puppe
module Spec
module DSL
- class EvalModule < Module;
- include PuppetTest::RunnableTest
- end
class Behaviour
extend BehaviourCallbacks
diff --git a/spec/lib/spec/runner/behaviour_runner.rb b/spec/lib/spec/runner/behaviour_runner.rb
index 078490e92..1ac891f3c 100644
--- a/spec/lib/spec/runner/behaviour_runner.rb
+++ b/spec/lib/spec/runner/behaviour_runner.rb
@@ -55,8 +55,6 @@ module Spec
def run_behaviours
@behaviours.each do |behaviour|
- # LAK:NOTE: this 'runnable' test is Puppet-specific.
- next unless behaviour.runnable?
behaviour.run(@options.reporter, @options.dry_run, @options.reverse, @options.timeout)
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3017f272a..bfac9095f 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,12 +1,11 @@
dir = File.expand_path(File.dirname(__FILE__))
-$:.unshift("#{dir}/lib")
-$:.unshift("#{dir}/../lib")
+$LOAD_PATH.unshift("#{dir}/lib")
+$LOAD_PATH.unshift("#{dir}/../lib")
+$LOAD_PATH.unshift("#{dir}/../test/lib") # Add the old test dir, so that we can still find our local mocha and spec
-# Add the old test dir, so that we can still find mocha and spec
-$:.unshift("#{dir}/../test/lib")
-
-require 'mocha'
require 'puppettest'
+require 'puppettest/runnable_test'
+require 'mocha'
require 'spec'
Spec::Runner.configure do |config|
@@ -19,3 +18,5 @@ Spec::Runner.configure do |config|
teardown() if respond_to? :teardown
end
end
+
+require "#{dir}/lib/monkey_patches/add_confine_and_runnable_to_rspec_dsl"
diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb
index 0ac2356d6..5d8453905 100755
--- a/spec/unit/indirector/indirection.rb
+++ b/spec/unit/indirector/indirection.rb
@@ -156,11 +156,21 @@ describe Puppet::Indirector::Indirection, " when managing indirection instances"
@indirection = Puppet::Indirector::Indirection.new(mock('model'), :test)
Puppet::Indirector::Indirection.instance(:test).should equal(@indirection)
end
-
+
it "should return nil when the named indirection has not been created" do
Puppet::Indirector::Indirection.instance(:test).should be_nil
end
+ it "should allow an indirection's model to be retrieved by name" do
+ mock_model = mock('model')
+ @indirection = Puppet::Indirector::Indirection.new(mock_model, :test)
+ Puppet::Indirector::Indirection.model(:test).should equal(mock_model)
+ end
+
+ it "should return nil when no model matches the requested name" do
+ Puppet::Indirector::Indirection.model(:test).should be_nil
+ end
+
after do
@indirection.delete if defined? @indirection
end
diff --git a/spec/unit/indirector/node/memory.rb b/spec/unit/indirector/node/memory.rb
index f57cae818..a924c6209 100755
--- a/spec/unit/indirector/node/memory.rb
+++ b/spec/unit/indirector/node/memory.rb
@@ -4,9 +4,8 @@ require File.dirname(__FILE__) + '/../../../spec_helper'
require 'puppet/indirector/node/memory'
-# All of our behaviour is described here, so we always have to
-# include it.
-require 'unit/indirector/memory'
+# All of our behaviour is described here, so we always have to include it.
+require File.dirname(__FILE__) + '/../memory'
describe Puppet::Node::Memory do
before do
diff --git a/spec/unit/network/client/master.rb b/spec/unit/network/client/master.rb
new file mode 100755
index 000000000..dca923994
--- /dev/null
+++ b/spec/unit/network/client/master.rb
@@ -0,0 +1,365 @@
+#!/usr/bin/env ruby
+#
+# Created by Luke Kanies on 2007-11-12.
+# Copyright (c) 2007. All rights reserved.
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+require 'puppet/network/client/master'
+
+describe Puppet::Network::Client::Master, " when retrieving the configuration" do
+ before do
+ @master = mock 'master'
+ @client = Puppet::Network::Client.master.new(
+ :Master => @master
+ )
+ @facts = {"one" => "two", "three" => "four"}
+ end
+
+ it "should initialize the metadata store" do
+ @client.class.stubs(:facts).returns(@facts)
+ @client.expects(:dostorage)
+ @master.stubs(:getconfig).returns(nil)
+ @client.getconfig
+ end
+
+ it "should collect facts to use for configuration retrieval" do
+ @client.stubs(:dostorage)
+ @client.class.expects(:facts).returns(@facts)
+ @master.stubs(:getconfig).returns(nil)
+ @client.getconfig
+ end
+
+ it "should fail if no facts could be collected" do
+ @client.stubs(:dostorage)
+ @client.class.expects(:facts).returns({})
+ @master.stubs(:getconfig).returns(nil)
+ proc { @client.getconfig }.should raise_error(Puppet::Network::ClientError)
+ end
+
+ it "should use the cached configuration if it is up to date" do
+ file = "/path/to/cachefile"
+ @client.stubs(:cachefile).returns(file)
+ FileTest.expects(:exist?).with(file).returns(true)
+ @client.expects(:fresh?).with(@facts).returns true
+ @client.class.stubs(:facts).returns(@facts)
+ @client.expects(:use_cached_config).returns(true)
+ Puppet.stubs(:info)
+
+ @client.getconfig
+ end
+
+ it "should log that the configuration does not need a recompile" do
+ file = "/path/to/cachefile"
+ @client.stubs(:cachefile).returns(file)
+ FileTest.stubs(:exist?).with(file).returns(true)
+ @client.stubs(:fresh?).with(@facts).returns true
+ @client.stubs(:use_cached_config).returns(true)
+ @client.class.stubs(:facts).returns(@facts)
+ Puppet.expects(:info).with { |m| m.include?("up to date") }
+
+ @client.getconfig
+ end
+
+ it "should retrieve plugins if :pluginsync is enabled" do
+ file = "/path/to/cachefile"
+ @client.stubs(:cachefile).returns(file)
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ Puppet.settings.expects(:value).with(:pluginsync).returns(true)
+ @client.expects(:getplugins)
+ @client.stubs(:get_actual_config).returns(nil)
+ FileTest.stubs(:exist?).with(file).returns(true)
+ @client.stubs(:fresh?).with(@facts).returns true
+ @client.stubs(:use_cached_config).returns(true)
+ @client.class.stubs(:facts).returns(@facts)
+ @client.getconfig
+ end
+
+ it "should use the cached configuration if no configuration could be retrieved" do
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ @master.stubs(:getconfig).raises(ArgumentError.new("whev"))
+ @client.expects(:use_cached_config).with(true)
+ @client.getconfig
+ end
+
+ it "should load the retrieved configuration using YAML" do
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ @master.stubs(:getconfig).returns("myconfig")
+
+ config = mock 'config'
+ YAML.expects(:load).with("myconfig").returns(config)
+
+ @client.stubs(:setclasses)
+
+ config.stubs(:classes)
+ config.stubs(:to_configuration).returns(config)
+ config.stubs(:host_config=)
+ config.stubs(:from_cache).returns(true)
+
+ @client.getconfig
+ end
+
+ it "should use the cached configuration if the retrieved configuration cannot be converted from YAML" do
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ @master.stubs(:getconfig).returns("myconfig")
+
+ YAML.expects(:load).with("myconfig").raises(ArgumentError)
+
+ @client.expects(:use_cached_config).with(true)
+
+ @client.getconfig
+ end
+
+ it "should set the classes.txt file with the classes listed in the retrieved configuration" do
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ @master.stubs(:getconfig).returns("myconfig")
+
+ config = mock 'config'
+ YAML.expects(:load).with("myconfig").returns(config)
+
+ config.expects(:classes).returns(:myclasses)
+ @client.expects(:setclasses).with(:myclasses)
+
+ config.stubs(:to_configuration).returns(config)
+ config.stubs(:host_config=)
+ config.stubs(:from_cache).returns(true)
+
+ @client.getconfig
+ end
+
+ it "should convert the retrieved configuration to a RAL configuration" do
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ @master.stubs(:getconfig).returns("myconfig")
+
+ yamlconfig = mock 'yaml config'
+ YAML.stubs(:load).returns(yamlconfig)
+
+ @client.stubs(:setclasses)
+
+ config = mock 'config'
+
+ yamlconfig.stubs(:classes)
+ yamlconfig.expects(:to_configuration).returns(config)
+
+ config.stubs(:host_config=)
+ config.stubs(:from_cache).returns(true)
+
+ @client.getconfig
+ end
+
+ it "should use the cached configuration if the retrieved configuration cannot be converted to a RAL configuration" do
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ @master.stubs(:getconfig).returns("myconfig")
+
+ yamlconfig = mock 'yaml config'
+ YAML.stubs(:load).returns(yamlconfig)
+
+ @client.stubs(:setclasses)
+
+ config = mock 'config'
+
+ yamlconfig.stubs(:classes)
+ yamlconfig.expects(:to_configuration).raises(ArgumentError)
+
+ @client.expects(:use_cached_config).with(true)
+
+ @client.getconfig
+ end
+
+ it "should clear the failed configuration if using the cached configuration after failing to instantiate the retrieved configuration" do
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ @master.stubs(:getconfig).returns("myconfig")
+
+ yamlconfig = mock 'yaml config'
+ YAML.stubs(:load).returns(yamlconfig)
+
+ @client.stubs(:setclasses)
+
+ config = mock 'config'
+
+ yamlconfig.stubs(:classes)
+ yamlconfig.stubs(:to_configuration).raises(ArgumentError)
+
+ @client.stubs(:use_cached_config).with(true)
+
+ @client.expects(:clear)
+
+ @client.getconfig
+ end
+
+ it "should cache the retrieved yaml configuration if it is not from the cache and is valid" do
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ @master.stubs(:getconfig).returns("myconfig")
+
+ yamlconfig = mock 'yaml config'
+ YAML.stubs(:load).returns(yamlconfig)
+
+ @client.stubs(:setclasses)
+
+ config = mock 'config'
+
+ yamlconfig.stubs(:classes)
+ yamlconfig.expects(:to_configuration).returns(config)
+
+ config.stubs(:host_config=)
+
+ config.expects(:from_cache).returns(false)
+
+ @client.expects(:cache).with("myconfig")
+
+ @client.getconfig
+ end
+
+ it "should mark the configuration as a host configuration" do
+ @client.stubs(:dostorage)
+ @client.class.stubs(:facts).returns(@facts)
+ @master.stubs(:getconfig).returns("myconfig")
+
+ yamlconfig = mock 'yaml config'
+ YAML.stubs(:load).returns(yamlconfig)
+
+ @client.stubs(:setclasses)
+
+ config = mock 'config'
+
+ yamlconfig.stubs(:classes)
+ yamlconfig.expects(:to_configuration).returns(config)
+
+ config.stubs(:from_cache).returns(true)
+
+ config.expects(:host_config=).with(true)
+
+ @client.getconfig
+ end
+end
+
+describe Puppet::Network::Client::Master, " when using the cached configuration" do
+ before do
+ @master = mock 'master'
+ @client = Puppet::Network::Client.master.new(
+ :Master => @master
+ )
+ @facts = {"one" => "two", "three" => "four"}
+ end
+
+ it "should return do nothing and true if there is already an in-memory configuration" do
+ @client.configuration = :whatever
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config.should be_true
+ end
+ end
+
+ it "should return do nothing and false if it has been told there is a failure and :nocacheonfailure is enabled" do
+ Puppet.settings.expects(:value).with(:usecacheonfailure).returns(false)
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config(true).should be_false
+ end
+ end
+
+ it "should return false if no cached configuration can be found" do
+ @client.expects(:retrievecache).returns(nil)
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config().should be_false
+ end
+ end
+
+ it "should return false if the cached configuration cannot be instantiated" do
+ YAML.expects(:load).raises(ArgumentError)
+ @client.expects(:retrievecache).returns("whatever")
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config().should be_false
+ end
+ end
+
+ it "should warn if the cached configuration cannot be instantiated" do
+ YAML.stubs(:load).raises(ArgumentError)
+ @client.stubs(:retrievecache).returns("whatever")
+ Puppet.expects(:warning).with { |m| m.include?("Could not load cache") }
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config().should be_false
+ end
+ end
+
+ it "should clear the client if the cached configuration cannot be instantiated" do
+ YAML.stubs(:load).raises(ArgumentError)
+ @client.stubs(:retrievecache).returns("whatever")
+ @client.expects(:clear)
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config().should be_false
+ end
+ end
+
+ it "should return true if the cached configuration can be instantiated" do
+ config = mock 'config'
+ YAML.stubs(:load).returns(config)
+
+ ral_config = mock 'ral config'
+ ral_config.stubs(:from_cache=)
+ ral_config.stubs(:host_config=)
+ config.expects(:to_configuration).returns(ral_config)
+
+ @client.stubs(:retrievecache).returns("whatever")
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config().should be_true
+ end
+ end
+
+ it "should set the configuration instance variable if the cached configuration can be instantiated" do
+ config = mock 'config'
+ YAML.stubs(:load).returns(config)
+
+ ral_config = mock 'ral config'
+ ral_config.stubs(:from_cache=)
+ ral_config.stubs(:host_config=)
+ config.expects(:to_configuration).returns(ral_config)
+
+ @client.stubs(:retrievecache).returns("whatever")
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config()
+ end
+
+ @client.configuration.should equal(ral_config)
+ end
+
+ it "should mark the configuration as a host_config if valid" do
+ config = mock 'config'
+ YAML.stubs(:load).returns(config)
+
+ ral_config = mock 'ral config'
+ ral_config.stubs(:from_cache=)
+ ral_config.expects(:host_config=).with(true)
+ config.expects(:to_configuration).returns(ral_config)
+
+ @client.stubs(:retrievecache).returns("whatever")
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config()
+ end
+
+ @client.configuration.should equal(ral_config)
+ end
+
+ it "should mark the configuration as from the cache if valid" do
+ config = mock 'config'
+ YAML.stubs(:load).returns(config)
+
+ ral_config = mock 'ral config'
+ ral_config.expects(:from_cache=).with(true)
+ ral_config.stubs(:host_config=)
+ config.expects(:to_configuration).returns(ral_config)
+
+ @client.stubs(:retrievecache).returns("whatever")
+ Puppet::Network::Client::Master.publicize_methods :use_cached_config do
+ @client.use_cached_config()
+ end
+
+ @client.configuration.should equal(ral_config)
+ end
+end
diff --git a/spec/unit/network/http.rb b/spec/unit/network/http.rb
new file mode 100644
index 000000000..79a0a88d4
--- /dev/null
+++ b/spec/unit/network/http.rb
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+#
+# Created by Rick Bradley on 2007-10-03.
+# Copyright (c) 2007. All rights reserved.
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'puppet/network/http'
+
+describe Puppet::Network::HTTP do
+ it "should return the webrick HTTP server class when asked for a webrick server" do
+ Puppet::Network::HTTP.server_class_by_type(:webrick).should be(Puppet::Network::HTTP::WEBrick)
+ end
+
+ if Puppet.features.mongrel?
+ it "should return the mongrel HTTP server class when asked for a mongrel server" do
+ Puppet::Network::HTTP.server_class_by_type(:mongrel).should be(Puppet::Network::HTTP::Mongrel)
+ end
+ end
+
+ it "should fail to return the mongrel HTTP server class if mongrel is not available " do
+ Puppet.features.expects(:mongrel?).returns(false)
+ Proc.new { Puppet::Network::HTTP.server_class_by_type(:mongrel) }.should raise_error(ArgumentError)
+ end
+
+ it "should return an error when asked for an unknown server" do
+ Proc.new { Puppet::Network::HTTP.server_class_by_type :foo }.should raise_error(ArgumentError)
+ end
+end
diff --git a/spec/unit/network/http/mongrel.rb b/spec/unit/network/http/mongrel.rb
new file mode 100644
index 000000000..b6ad07567
--- /dev/null
+++ b/spec/unit/network/http/mongrel.rb
@@ -0,0 +1,124 @@
+#!/usr/bin/env ruby
+#
+# Created by Rick Bradley on 2007-10-15.
+# Copyright (c) 2007. All rights reserved.
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+require 'puppet/network/http'
+
+describe Puppet::Network::HTTP::Mongrel, "after initializing" do
+ confine "Mongrel is not available" => Puppet.features.mongrel?
+
+ it "should not be listening" do
+ Puppet::Network::HTTP::Mongrel.new.should_not be_listening
+ end
+end
+
+describe Puppet::Network::HTTP::Mongrel, "when turning on listening" do
+ confine "Mongrel is not available" => Puppet.features.mongrel?
+
+ before do
+ @server = Puppet::Network::HTTP::Mongrel.new
+ @mock_mongrel = mock('mongrel')
+ @mock_mongrel.stubs(:run)
+ @mock_mongrel.stubs(:register)
+ Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel)
+ @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :configuration ], :protocols => [ :rest, :xmlrpc ] }
+ end
+
+ it "should fail if already listening" do
+ @server.listen(@listen_params)
+ Proc.new { @server.listen(@listen_params) }.should raise_error(RuntimeError)
+ end
+
+ it "should require at least one handler" do
+ Proc.new { @server.listen(@listen_params.delete_if {|k,v| :handlers == k}) }.should raise_error(ArgumentError)
+ end
+
+ it "should require at least one protocol" do
+ Proc.new { @server.listen(@listen_params.delete_if {|k,v| :protocols == k}) }.should raise_error(ArgumentError)
+ end
+
+ it "should require a listening address to be specified" do
+ Proc.new { @server.listen(@listen_params.delete_if {|k,v| :address == k})}.should raise_error(ArgumentError)
+ end
+
+ it "should require a listening port to be specified" do
+ Proc.new { @server.listen(@listen_params.delete_if {|k,v| :port == k})}.should raise_error(ArgumentError)
+ end
+
+ it "should order a mongrel server to start" do
+ @mock_mongrel.expects(:run)
+ @server.listen(@listen_params)
+ end
+
+ it "should tell mongrel to listen on the specified address and port" do
+ Mongrel::HttpServer.expects(:new).with("127.0.0.1", 31337).returns(@mock_mongrel)
+ @server.listen(@listen_params)
+ end
+
+ it "should be listening" do
+ Mongrel::HttpServer.expects(:new).returns(@mock_mongrel)
+ @server.listen(@listen_params)
+ @server.should be_listening
+ end
+
+ it "should instantiate a handler for each protocol+handler pair to configure web server routing" do
+ @listen_params[:protocols].each do |protocol|
+ mock_handler = mock("handler instance for [#{protocol}]")
+ mock_handler_class = mock("handler class for [#{protocol}]")
+ @listen_params[:handlers].each do |handler|
+ mock_handler_class.expects(:new).with {|args|
+ args[:server] == @mock_mongrel and args[:handler] == handler
+ }.returns(mock_handler)
+ end
+ @server.expects(:class_for_protocol).with(protocol).at_least_once.returns(mock_handler_class)
+ end
+ @server.listen(@listen_params)
+ end
+
+ it "should use a Mongrel + REST class to configure Mongrel when REST services are requested" do
+ Puppet::Network::HTTP::MongrelREST.expects(:new).at_least_once
+ @server.listen(@listen_params.merge(:protocols => [:rest]))
+ end
+
+ it "should use a Mongrel + XMLRPC class to configure Mongrel when XMLRPC services are requested" do
+ Puppet::Network::HTTP::MongrelXMLRPC.expects(:new).at_least_once
+ @server.listen(@listen_params.merge(:protocols => [:xmlrpc]))
+ end
+
+ it "should fail if services from an unknown protocol are requested" do
+ Proc.new { @server.listen(@listen_params.merge(:protocols => [ :foo ]))}.should raise_error(ArgumentError)
+ end
+
+end
+
+describe Puppet::Network::HTTP::Mongrel, "when turning off listening" do
+ confine "Mongrel is not available" => Puppet.features.mongrel?
+
+ before do
+ @mock_mongrel = mock('mongrel httpserver')
+ @mock_mongrel.stubs(:run)
+ @mock_mongrel.stubs(:register)
+ Mongrel::HttpServer.stubs(:new).returns(@mock_mongrel)
+ @server = Puppet::Network::HTTP::Mongrel.new
+ @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :configuration ], :protocols => [ :rest, :xmlrpc ] }
+ end
+
+ it "should fail unless listening" do
+ Proc.new { @server.unlisten }.should raise_error(RuntimeError)
+ end
+
+ it "should order mongrel server to stop" do
+ @server.listen(@listen_params)
+ @mock_mongrel.expects(:graceful_shutdown)
+ @server.unlisten
+ end
+
+ it "should not be listening" do
+ @server.listen(@listen_params)
+ @mock_mongrel.stubs(:graceful_shutdown)
+ @server.unlisten
+ @server.should_not be_listening
+ end
+end
diff --git a/spec/unit/network/http/mongrel/rest.rb b/spec/unit/network/http/mongrel/rest.rb
new file mode 100644
index 000000000..9b2feb6ee
--- /dev/null
+++ b/spec/unit/network/http/mongrel/rest.rb
@@ -0,0 +1,287 @@
+#!/usr/bin/env ruby
+#
+# Created by Rick Bradley on 2007-10-16.
+# Copyright (c) 2007. All rights reserved.
+
+require File.dirname(__FILE__) + '/../../../../spec_helper'
+require 'puppet/network/http'
+
+describe Puppet::Network::HTTP::MongrelREST, "when initializing" do
+ confine "Mongrel is not available" => Puppet.features.mongrel?
+
+ before do
+ @mock_mongrel = mock('Mongrel server')
+ @mock_mongrel.stubs(:register)
+ @mock_model = mock('indirected model')
+ Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@mock_model)
+ @params = { :server => @mock_mongrel, :handler => :foo }
+ end
+
+ it "should require access to a Mongrel server" do
+ Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params.delete_if {|k,v| :server == k })}.should raise_error(ArgumentError)
+ end
+
+ it "should require an indirection name" do
+ Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params.delete_if {|k,v| :handler == k })}.should raise_error(ArgumentError)
+ end
+
+ it "should look up the indirection model from the indirection name" do
+ Puppet::Indirector::Indirection.expects(:model).with(:foo).returns(@mock_model)
+ Puppet::Network::HTTP::MongrelREST.new(@params)
+ end
+
+ it "should fail if the indirection is not known" do
+ Puppet::Indirector::Indirection.expects(:model).with(:foo).returns(nil)
+ Proc.new { Puppet::Network::HTTP::MongrelREST.new(@params) }.should raise_error(ArgumentError)
+ end
+
+ it "should register itself with the mongrel server for the singular HTTP methods" do
+ @mock_mongrel.expects(:register).with do |*args|
+ args.first == '/foo' and args.last.is_a? Puppet::Network::HTTP::MongrelREST
+ end
+ Puppet::Network::HTTP::MongrelREST.new(@params)
+ end
+
+ it "should register itself with the mongrel server for the plural GET method" do
+ @mock_mongrel.expects(:register).with do |*args|
+ args.first == '/foos' and args.last.is_a? Puppet::Network::HTTP::MongrelREST
+ end
+ Puppet::Network::HTTP::MongrelREST.new(@params)
+ end
+end
+
+describe Puppet::Network::HTTP::MongrelREST, "when receiving a request" do
+ confine "Mongrel is not available" => Puppet.features.mongrel?
+
+ before do
+ @mock_request = stub('mongrel http request')
+ @mock_head = stub('response head')
+ @mock_body = stub('response body', :write => true)
+ @mock_response = stub('mongrel http response')
+ @mock_response.stubs(:start).yields(@mock_head, @mock_body)
+ @mock_model_class = stub('indirected model class')
+ @mock_mongrel = stub('mongrel http server', :register => true)
+ Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@mock_model_class)
+ @handler = Puppet::Network::HTTP::MongrelREST.new(:server => @mock_mongrel, :handler => :foo)
+ end
+
+ def setup_find_request(params = {})
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET',
+ Mongrel::Const::REQUEST_PATH => '/foo/key',
+ 'QUERY_STRING' => ''}.merge(params))
+ @mock_model_class.stubs(:find)
+ end
+
+ def setup_search_request(params = {})
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET',
+ Mongrel::Const::REQUEST_PATH => '/foos',
+ 'QUERY_STRING' => '' }.merge(params))
+ @mock_model_class.stubs(:search).returns([])
+ end
+
+ def setup_destroy_request(params = {})
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE',
+ Mongrel::Const::REQUEST_PATH => '/foo/key',
+ 'QUERY_STRING' => '' }.merge(params))
+ @mock_model_class.stubs(:destroy)
+ end
+
+ def setup_save_request(params = {})
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT',
+ Mongrel::Const::REQUEST_PATH => '/foo',
+ 'QUERY_STRING' => '' }.merge(params))
+ @mock_request.stubs(:body).returns('this is a fake request body')
+ @mock_model_instance = stub('indirected model instance', :save => true)
+ @mock_model_class.stubs(:new).returns(@mock_model_instance)
+ end
+
+ def setup_bad_request
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'POST', Mongrel::Const::REQUEST_PATH => '/foos'})
+ end
+
+ it "should call the model find method if the request represents a singular HTTP GET" do
+ setup_find_request
+ @mock_model_class.expects(:find).with('key', {})
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should call the model search method if the request represents a plural HTTP GET" do
+ setup_search_request
+ @mock_model_class.expects(:search).with({}).returns([])
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should call the model destroy method if the request represents an HTTP DELETE" do
+ setup_destroy_request
+ @mock_model_class.expects(:destroy).with('key', {})
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should call the model save method if the request represents an HTTP PUT" do
+ setup_save_request
+ @mock_model_instance.expects(:save).with(:data => 'this is a fake request body')
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail if the HTTP method isn't supported" do
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'POST', Mongrel::Const::REQUEST_PATH => '/foo'})
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail if the request's pluralization is wrong" do
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE', Mongrel::Const::REQUEST_PATH => '/foos/key'})
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT', Mongrel::Const::REQUEST_PATH => '/foos/key'})
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail if the request is for an unknown path" do
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET',
+ Mongrel::Const::REQUEST_PATH => '/bar/key',
+ 'QUERY_STRING' => '' })
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail to find model if key is not specified" do
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'GET', Mongrel::Const::REQUEST_PATH => '/foo'})
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail to destroy model if key is not specified" do
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'DELETE', Mongrel::Const::REQUEST_PATH => '/foo'})
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail to save model if data is not specified" do
+ @mock_request.stubs(:params).returns({ Mongrel::Const::REQUEST_METHOD => 'PUT', Mongrel::Const::REQUEST_PATH => '/foo'})
+ @mock_request.stubs(:body).returns('')
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should pass HTTP request parameters to model find" do
+ setup_find_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
+ @mock_model_class.expects(:find).with do |key, args|
+ key == 'key' and args['foo'] == 'baz' and args['bar'] == 'xyzzy'
+ end
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should pass HTTP request parameters to model search" do
+ setup_search_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
+ @mock_model_class.expects(:search).with do |args|
+ args['foo'] == 'baz' and args['bar'] == 'xyzzy'
+ end.returns([])
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should pass HTTP request parameters to model delete" do
+ setup_destroy_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
+ @mock_model_class.expects(:destroy).with do |key, args|
+ key == 'key' and args['foo'] == 'baz' and args['bar'] == 'xyzzy'
+ end
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should pass HTTP request parameters to model save" do
+ setup_save_request('QUERY_STRING' => 'foo=baz&bar=xyzzy')
+ @mock_model_instance.expects(:save).with do |args|
+ args[:data] == 'this is a fake request body' and args['foo'] == 'baz' and args['bar'] == 'xyzzy'
+ end
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should generate a 200 response when a model find call succeeds" do
+ setup_find_request
+ @mock_response.expects(:start).with(200)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should generate a 200 response when a model search call succeeds" do
+ setup_search_request
+ @mock_response.expects(:start).with(200)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should generate a 200 response when a model destroy call succeeds" do
+ setup_destroy_request
+ @mock_response.expects(:start).with(200)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should generate a 200 response when a model save call succeeds" do
+ setup_save_request
+ @mock_response.expects(:start).with(200)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should return a serialized object when a model find call succeeds" do
+ setup_find_request
+ @mock_model_instance = stub('model instance')
+ @mock_model_instance.expects(:to_yaml)
+ @mock_model_class.stubs(:find).returns(@mock_model_instance)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should return a list of serialized objects when a model search call succeeds" do
+ setup_search_request
+ mock_matches = [1..5].collect {|i| mock("model instance #{i}", :to_yaml => "model instance #{i}") }
+ @mock_model_class.stubs(:search).returns(mock_matches)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should return a serialized success result when a model destroy call succeeds" do
+ setup_destroy_request
+ @mock_model_class.stubs(:destroy).returns(true)
+ @mock_body.expects(:write).with("--- true\n")
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should return a serialized object when a model save call succeeds" do
+ setup_save_request
+ @mock_model_instance.stubs(:save).returns(@mock_model_instance)
+ @mock_model_instance.expects(:to_yaml).returns('foo')
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception when an exception is thrown by find" do
+ setup_find_request
+ @mock_model_class.expects(:find).raises(ArgumentError)
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception when an exception is thrown by search" do
+ setup_search_request
+ @mock_model_class.expects(:search).raises(ArgumentError)
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception when an exception is thrown by destroy" do
+ setup_destroy_request
+ @mock_model_class.expects(:destroy).raises(ArgumentError)
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception when an exception is thrown by save" do
+ setup_save_request
+ @mock_model_instance.expects(:save).raises(ArgumentError)
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception if the request fails" do
+ setup_bad_request
+ @mock_response.expects(:start).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+end
diff --git a/spec/unit/network/http/mongrel/xmlrpc.rb b/spec/unit/network/http/mongrel/xmlrpc.rb
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/spec/unit/network/http/mongrel/xmlrpc.rb
diff --git a/spec/unit/network/http/webrick.rb b/spec/unit/network/http/webrick.rb
new file mode 100644
index 000000000..3ed223e7e
--- /dev/null
+++ b/spec/unit/network/http/webrick.rb
@@ -0,0 +1,115 @@
+#!/usr/bin/env ruby
+#
+# Created by Rick Bradley on 2007-10-15.
+# Copyright (c) 2007. All rights reserved.
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+require 'puppet/network/http'
+
+describe Puppet::Network::HTTP::WEBrick, "after initializing" do
+ it "should not be listening" do
+ Puppet::Network::HTTP::WEBrick.new.should_not be_listening
+ end
+end
+
+describe Puppet::Network::HTTP::WEBrick, "when turning on listening" do
+ before do
+ @mock_webrick = mock('webrick')
+ [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)}
+ WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick)
+ @server = Puppet::Network::HTTP::WEBrick.new
+ @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :configuration ], :protocols => [ :rest, :xmlrpc ] }
+ end
+
+ it "should fail if already listening" do
+ @server.listen(@listen_params)
+ Proc.new { @server.listen(@listen_params) }.should raise_error(RuntimeError)
+ end
+
+ it "should require at least one handler" do
+ Proc.new { @server.listen(@listen_params.delete_if {|k,v| :handlers == k}) }.should raise_error(ArgumentError)
+ end
+
+ it "should require at least one protocol" do
+ Proc.new { @server.listen(@listen_params.delete_if {|k,v| :protocols == k}) }.should raise_error(ArgumentError)
+ end
+
+ it "should require a listening address to be specified" do
+ Proc.new { @server.listen(@listen_params.delete_if {|k,v| :address == k})}.should raise_error(ArgumentError)
+ end
+
+ it "should require a listening port to be specified" do
+ Proc.new { @server.listen(@listen_params.delete_if {|k,v| :port == k})}.should raise_error(ArgumentError)
+ end
+
+ it "should order a webrick server to start" do
+ @mock_webrick.expects(:start)
+ @server.listen(@listen_params)
+ end
+
+ it "should tell webrick to listen on the specified address and port" do
+ WEBrick::HTTPServer.expects(:new).with {|args|
+ args[:Port] == 31337 and args[:BindAddress] == "127.0.0.1"
+ }.returns(@mock_webrick)
+ @server.listen(@listen_params)
+ end
+
+ it "should be listening" do
+ @server.listen(@listen_params)
+ @server.should be_listening
+ end
+
+ it "should instantiate a handler for each protocol+handler pair to configure web server routing" do
+ @listen_params[:protocols].each do |protocol|
+ mock_handler = mock("handler instance for [#{protocol}]")
+ mock_handler_class = mock("handler class for [#{protocol}]")
+ @listen_params[:handlers].each do |handler|
+ mock_handler_class.expects(:new).with {|args|
+ args[:server] == @mock_webrick and args[:handler] == handler
+ }.returns(mock_handler)
+ end
+ @server.expects(:class_for_protocol).with(protocol).at_least_once.returns(mock_handler_class)
+ end
+ @server.listen(@listen_params)
+ end
+
+ it "should use a WEBrick + REST class to configure WEBrick when REST services are requested" do
+ Puppet::Network::HTTP::WEBrickREST.expects(:new).at_least_once
+ @server.listen(@listen_params.merge(:protocols => [:rest]))
+ end
+
+ it "should use a WEBrick + XMLRPC class to configure WEBrick when XMLRPC services are requested" do
+ Puppet::Network::HTTP::WEBrickXMLRPC.expects(:new).at_least_once
+ @server.listen(@listen_params.merge(:protocols => [:xmlrpc]))
+ end
+
+ it "should fail if services from an unknown protocol are requested" do
+ Proc.new { @server.listen(@listen_params.merge(:protocols => [ :foo ]))}.should raise_error(ArgumentError)
+ end
+end
+
+describe Puppet::Network::HTTP::WEBrick, "when turning off listening" do
+ before do
+ @mock_webrick = mock('webrick')
+ [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)}
+ WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick)
+ @server = Puppet::Network::HTTP::WEBrick.new
+ @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :configuration ], :protocols => [ :rest, :xmlrpc ] }
+ end
+
+ it "should fail unless listening" do
+ Proc.new { @server.unlisten }.should raise_error(RuntimeError)
+ end
+
+ it "should order webrick server to stop" do
+ @mock_webrick.expects(:shutdown)
+ @server.listen(@listen_params)
+ @server.unlisten
+ end
+
+ it "should no longer be listening" do
+ @server.listen(@listen_params)
+ @server.unlisten
+ @server.should_not be_listening
+ end
+end
diff --git a/spec/unit/network/http/webrick/rest.rb b/spec/unit/network/http/webrick/rest.rb
new file mode 100644
index 000000000..aa7a3d53a
--- /dev/null
+++ b/spec/unit/network/http/webrick/rest.rb
@@ -0,0 +1,286 @@
+#!/usr/bin/env ruby
+#
+# Created by Rick Bradley on 2007-10-16.
+# Copyright (c) 2007. All rights reserved.
+
+require File.dirname(__FILE__) + '/../../../../spec_helper'
+require 'puppet/network/http'
+
+describe Puppet::Network::HTTP::WEBrickREST, "when initializing" do
+ before do
+ @mock_webrick = stub('WEBrick server', :mount => true)
+ @mock_model = mock('indirected model')
+ Puppet::Indirector::Indirection.stubs(:model).returns(@mock_model)
+ @params = { :server => @mock_webrick, :handler => :foo }
+ end
+
+ it "should require access to a WEBrick server" do
+ Proc.new { Puppet::Network::HTTP::WEBrickREST.new(@params.delete_if {|k,v| :server == k })}.should raise_error(ArgumentError)
+ end
+
+ it "should require an indirection name" do
+ Proc.new { Puppet::Network::HTTP::WEBrickREST.new(@params.delete_if {|k,v| :handler == k })}.should raise_error(ArgumentError)
+ end
+
+ it "should look up the indirection model from the indirection name" do
+ Puppet::Indirector::Indirection.expects(:model).returns(@mock_model)
+ Puppet::Network::HTTP::WEBrickREST.new(@params)
+ end
+
+ it "should fail if the indirection is not known" do
+ Puppet::Indirector::Indirection.expects(:model).returns(nil)
+ Proc.new { Puppet::Network::HTTP::WEBrickREST.new(@params) }.should raise_error(ArgumentError)
+ end
+
+ it "should register itself with the WEBrick server for the singular HTTP methods" do
+ @mock_webrick.expects(:mount).with do |*args|
+ args.first == '/foo' and args.last.is_a?(Puppet::Network::HTTP::WEBrickREST)
+ end
+ Puppet::Network::HTTP::WEBrickREST.new(@params)
+ end
+
+ it "should register itself with the WEBrick server for the plural GET method" do
+ @mock_webrick.expects(:mount).with do |*args|
+ args.first == '/foos' and args.last.is_a?(Puppet::Network::HTTP::WEBrickREST)
+ end
+ Puppet::Network::HTTP::WEBrickREST.new(@params)
+ end
+end
+
+describe Puppet::Network::HTTP::WEBrickREST, "when receiving a request" do
+ before do
+ @mock_request = stub('webrick http request', :query => {})
+ @mock_response = stub('webrick http response', :status= => true, :body= => true)
+ @mock_model_class = stub('indirected model class')
+ @mock_webrick = stub('mongrel http server', :mount => true)
+ Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@mock_model_class)
+ @handler = Puppet::Network::HTTP::WEBrickREST.new(:server => @mock_webrick, :handler => :foo)
+ end
+
+ def setup_find_request
+ @mock_request.stubs(:request_method).returns('GET')
+ @mock_request.stubs(:path).returns('/foo/key')
+ @mock_model_class.stubs(:find)
+ end
+
+ def setup_search_request
+ @mock_request.stubs(:request_method).returns('GET')
+ @mock_request.stubs(:path).returns('/foos')
+ @mock_model_class.stubs(:search).returns([])
+ end
+
+ def setup_destroy_request
+ @mock_request.stubs(:request_method).returns('DELETE')
+ @mock_request.stubs(:path).returns('/foo/key')
+ @mock_model_class.stubs(:destroy)
+ end
+
+ def setup_save_request
+ @mock_request.stubs(:request_method).returns('PUT')
+ @mock_request.stubs(:path).returns('/foo')
+ @mock_request.stubs(:body).returns('This is a fake request body')
+ @mock_model_instance = stub('indirected model instance', :save => true)
+ @mock_model_class.stubs(:new).returns(@mock_model_instance)
+ end
+
+ def setup_bad_request
+ @mock_request.stubs(:request_method).returns('POST')
+ @mock_request.stubs(:path).returns('/foos')
+ end
+
+ it "should call the model find method if the request represents a singular HTTP GET" do
+ setup_find_request
+ @mock_model_class.expects(:find).with('key', {})
+ @handler.service(@mock_request, @mock_response)
+ end
+
+ it "should call the model search method if the request represents a plural HTTP GET" do
+ setup_search_request
+ @mock_model_class.expects(:search).returns([])
+ @handler.service(@mock_request, @mock_response)
+ end
+
+ it "should call the model destroy method if the request represents an HTTP DELETE" do
+ setup_destroy_request
+ @mock_model_class.expects(:destroy).with('key', {})
+ @handler.service(@mock_request, @mock_response)
+ end
+
+ it "should call the model save method if the request represents an HTTP PUT" do
+ setup_save_request
+ @mock_model_instance.expects(:save).with(:data => 'This is a fake request body')
+ @mock_model_class.expects(:new).returns(@mock_model_instance)
+ @handler.service(@mock_request, @mock_response)
+ end
+
+ it "should fail if the HTTP method isn't supported" do
+ @mock_request.stubs(:request_method).returns('POST')
+ @mock_request.stubs(:path).returns('/foo')
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail if the request's pluralization is wrong" do
+ @mock_request.stubs(:request_method).returns('DELETE')
+ @mock_request.stubs(:path).returns('/foos/key')
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+
+ @mock_request.stubs(:request_method).returns('PUT')
+ @mock_request.stubs(:path).returns('/foos/key')
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail if the request is for an unknown path" do
+ @mock_request.stubs(:request_method).returns('GET')
+ @mock_request.stubs(:path).returns('/bar/key')
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail to find model if key is not specified" do
+ @mock_request.stubs(:request_method).returns('GET')
+ @mock_request.stubs(:path).returns('/foo')
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail to destroy model if key is not specified" do
+ @mock_request.stubs(:request_method).returns('DELETE')
+ @mock_request.stubs(:path).returns('/foo')
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should fail to save model if data is not specified" do
+ @mock_request.stubs(:request_method).returns('PUT')
+ @mock_request.stubs(:path).returns('/foo')
+ @mock_request.stubs(:body).returns('')
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should pass HTTP request parameters to model find" do
+ setup_find_request
+ @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy)
+ @mock_model_class.expects(:find).with do |key, args|
+ key == 'key' and args[:foo] == :baz and args[:bar] == :xyzzy
+ end
+ @handler.service(@mock_request, @mock_response)
+ end
+
+ it "should pass HTTP request parameters to model search" do
+ setup_search_request
+ @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy)
+ @mock_model_class.expects(:search).with do |args|
+ args[:foo] == :baz and args[:bar] == :xyzzy
+ end.returns([])
+ @handler.service(@mock_request, @mock_response)
+ end
+
+ it "should pass HTTP request parameters to model destroy" do
+ setup_destroy_request
+ @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy)
+ @mock_model_class.expects(:destroy).with do |key, args|
+ key == 'key' and args[:foo] == :baz and args[:bar] == :xyzzy
+ end
+ @handler.service(@mock_request, @mock_response)
+ end
+
+ it "should pass HTTP request parameters to model save" do
+ setup_save_request
+ @mock_request.stubs(:query).returns(:foo => :baz, :bar => :xyzzy)
+ @mock_model_instance.expects(:save).with do |args|
+ args[:data] == 'This is a fake request body' and args[:foo] == :baz and args[:bar] == :xyzzy
+ end
+ @handler.service(@mock_request, @mock_response)
+ end
+
+ it "should generate a 200 response when a model find call succeeds" do
+ setup_find_request
+ @mock_response.expects(:status=).with(200)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should generate a 200 response when a model search call succeeds" do
+ setup_search_request
+ @mock_response.expects(:status=).with(200)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should generate a 200 response when a model destroy call succeeds" do
+ setup_destroy_request
+ @mock_response.expects(:status=).with(200)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should generate a 200 response when a model save call succeeds" do
+ setup_save_request
+ @mock_response.expects(:status=).with(200)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should return a serialized object when a model find call succeeds" do
+ setup_find_request
+ @mock_model_instance = stub('model instance')
+ @mock_model_instance.expects(:to_yaml)
+ @mock_model_class.stubs(:find).returns(@mock_model_instance)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should return a list of serialized objects when a model search call succeeds" do
+ setup_search_request
+ mock_matches = [1..5].collect {|i| mock("model instance #{i}", :to_yaml => "model instance #{i}") }
+ @mock_model_class.stubs(:search).returns(mock_matches)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should return a serialized success result when a model destroy call succeeds" do
+ setup_destroy_request
+ @mock_model_class.stubs(:destroy).returns(true)
+ @mock_response.expects(:body=).with("--- true\n")
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should return a serialized object when a model save call succeeds" do
+ setup_save_request
+ @mock_model_instance.stubs(:save).returns(@mock_model_instance)
+ @mock_model_instance.expects(:to_yaml).returns('foo')
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception when an exception is thrown by find" do
+ setup_find_request
+ @mock_model_class.expects(:find).raises(ArgumentError)
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception when an exception is thrown by search" do
+ setup_search_request
+ @mock_model_class.expects(:search).raises(ArgumentError)
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception when an exception is thrown by destroy" do
+ setup_destroy_request
+ @mock_model_class.expects(:destroy).raises(ArgumentError)
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception when an exception is thrown by save" do
+ setup_save_request
+ @mock_model_instance.expects(:save).raises(ArgumentError)
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+
+ it "should serialize a controller exception if the request fails" do
+ setup_bad_request
+ @mock_response.expects(:status=).with(404)
+ @handler.process(@mock_request, @mock_response)
+ end
+end
diff --git a/spec/unit/network/http/webrick/xmlrpc.rb b/spec/unit/network/http/webrick/xmlrpc.rb
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/spec/unit/network/http/webrick/xmlrpc.rb
diff --git a/spec/unit/network/rest_controller.rb b/spec/unit/network/rest_controller.rb
deleted file mode 100644
index 0bcc0abf2..000000000
--- a/spec/unit/network/rest_controller.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-#!/usr/bin/env ruby
-#
-# Created by Rick Bradley on 2007-10-03.
-# Copyright (c) 2007. All rights reserved.
-
-require File.dirname(__FILE__) + '/../../spec_helper'
-
-require 'puppet/network/rest_controller'
-
-describe Puppet::Network::RESTController, "in general" do
- it "should route GET requests on indirector's name to indirector find for the model class"
- it "should route GET requests on indirector's plural name to indirector search for the model class"
- it "should route DELETE requests on indirector's name to indirector destroy for the model class"
- it "should route POST requests on indirector's name to indirector save for the model class"
- it "should serialize result data when methods are handled"
- it "should serialize an error condition when indirection method call generates an exception"
-end
-
-__END__
-
-# possible implementation of the satisfying class
-
-class RESTController
- def initialize(klass)
- @klass = klass
- end
-
- # TODO: is it possible to distinguish from the request object the path which we were called by?
-
- def do_GET(request, response)
- return do_GETS(request, response) if asked_for_plural?(request)
- args = request.something
- result = @klass.find args
- return serialize(result)
- end
-
- def do_GETS(request, response)
- args = request.something
- result = @klass.search args
- return serialize(result)
- end
-
- def do_DELETE(request, response)
- args = request.something
- result = @klass.destroy args
- return serialize(result)
- end
-
- def do_PUT(request, response)
- args = request.something
- obj = @klass.new(args)
- result = obj.save
- return serialize(result)
- end
-
- def do_POST(request, response)
- do_PUT(request, response)
- end
-
- private
-
- def asked_for_plural?(request)
- # TODO: pick apart the request and see if this was trying to do a plural or singular GET
- end
-end
diff --git a/spec/unit/network/server.rb b/spec/unit/network/server.rb
index 17ed336de..3e29807ad 100644
--- a/spec/unit/network/server.rb
+++ b/spec/unit/network/server.rb
@@ -4,181 +4,286 @@
# Copyright (c) 2007. All rights reserved.
require File.dirname(__FILE__) + '/../../spec_helper'
-
require 'puppet/network/server'
-# a fake server class, so we don't have to implement full autoloading etc. (or at least just yet) just to do testing
-class TestServer < Puppet::Network::Server
- def start_web_server
- end
-
- def stop_web_server
- end
-end
-
describe Puppet::Network::Server, "when initializing" do
- before do
- Puppet::Network::Server.stubs(:server_class_by_name).returns(TestServer)
- end
-
- it "should use the Puppet configurator to determine which HTTP server will be used to provide access to clients" do
- Puppet.expects(:[]).with(:servertype).returns(:suparserver)
- @server = Puppet::Network::Server.new
- @server.server_type.should == :suparserver
- end
-
- it "should fail to initialize if there is no HTTP server known to the Puppet configurator" do
- Puppet.expects(:[]).with(:servertype).returns(nil)
- Proc.new { Puppet::Network::Server.new }.should raise_error
- end
-
- it "should use the Puppet Configurator to determine what style of service we will offer to clients (e.g., REST, XMLRPC, ...)"
- it "should fail to initialize if there is no style of service known to the Puppet configurator"
-
- it "should allow registering indirections" do
- @server = Puppet::Network::Server.new(:handlers => [ :foo, :bar, :baz])
- Proc.new { @server.unregister(:foo, :bar, :baz) }.should_not raise_error
- end
-
- it "should not be listening after initialization" do
- Puppet::Network::Server.new.should_not be_listening
- end
+ before do
+ @mock_http_server_class = mock('http server class')
+ Puppet.stubs(:[]).with(:servertype).returns(:suparserver)
+ Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class)
+ end
+
+ it "should allow specifying a listening address" do
+ Puppet.stubs(:[]).with(:masterport).returns('')
+ @server = Puppet::Network::Server.new(:address => "127.0.0.1")
+ @server.address.should == "127.0.0.1"
+ end
+
+ it "should allow specifying a listening port" do
+ Puppet.stubs(:[]).with(:bindaddress).returns('')
+ @server = Puppet::Network::Server.new(:port => 31337)
+ @server.port.should == 31337
+ end
+
+ it "should use the Puppet configurator to find a default listening address" do
+ Puppet.stubs(:[]).with(:masterport).returns('')
+ Puppet.expects(:[]).with(:bindaddress).returns("10.0.0.1")
+ @server = Puppet::Network::Server.new
+ @server.address.should == "10.0.0.1"
+ end
+
+ it "should use the Puppet configurator to find a default listening port" do
+ Puppet.stubs(:[]).with(:bindaddress).returns('')
+ Puppet.expects(:[]).with(:masterport).returns(6667)
+ @server = Puppet::Network::Server.new
+ @server.port.should == 6667
+ end
+
+ it "should fail to initialize if no listening address can be found" do
+ Puppet.stubs(:[]).with(:masterport).returns(6667)
+ Puppet.stubs(:[]).with(:bindaddress).returns(nil)
+ Proc.new { Puppet::Network::Server.new }.should raise_error(ArgumentError)
+ end
+
+ it "should fail to initialize if no listening port can be found" do
+ Puppet.stubs(:[]).with(:bindaddress).returns("127.0.0.1")
+ Puppet.stubs(:[]).with(:masterport).returns(nil)
+ Proc.new { Puppet::Network::Server.new }.should raise_error(ArgumentError)
+ end
+
+ it "should use the Puppet configurator to determine which HTTP server will be used to provide access to clients" do
+ Puppet.expects(:[]).with(:servertype).returns(:suparserver)
+ @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337)
+ @server.server_type.should == :suparserver
+ end
+
+ it "should fail to initialize if there is no HTTP server known to the Puppet configurator" do
+ Puppet.expects(:[]).with(:servertype).returns(nil)
+ Proc.new { Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) }.should raise_error
+ end
+
+ it "should ask the Puppet::Network::HTTP class to fetch the proper HTTP server class" do
+ Puppet::Network::HTTP.expects(:server_class_by_type).with(:suparserver).returns(@mock_http_server_class)
+ @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337)
+ end
+
+ it "should fail if the HTTP server class is unknown" do
+ Puppet::Network::HTTP.stubs(:server_class_by_type).returns(nil)
+ Proc.new { Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337) }.should raise_error(ArgumentError)
+ end
+
+ it "should allow registering indirections" do
+ @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337, :handlers => [ :foo, :bar, :baz])
+ Proc.new { @server.unregister(:foo, :bar, :baz) }.should_not raise_error
+ end
+
+ it "should not be listening after initialization" do
+ Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337).should_not be_listening
+ end
end
describe Puppet::Network::Server, "in general" do
- before do
- Puppet::Network::Server.stubs(:server_class_by_name).returns(TestServer)
- Puppet.stubs(:[]).with(:servertype).returns(:suparserver)
- @server = Puppet::Network::Server.new
- end
-
- it "should allow registering an indirection for client access by specifying its indirection name" do
- Proc.new { @server.register(:foo) }.should_not raise_error
- end
-
- it "should require at least one indirection name when registering indirections for client access" do
- Proc.new { @server.register }.should raise_error(ArgumentError)
- end
-
- it "should allow for numerous indirections to be registered at once for client access" do
- Proc.new { @server.register(:foo, :bar, :baz) }.should_not raise_error
- end
-
- it "should allow the use of indirection names to specify which indirections are to be no longer accessible to clients" do
- @server.register(:foo)
- Proc.new { @server.unregister(:foo) }.should_not raise_error
- end
-
- it "should leave other indirections accessible to clients when turning off indirections" do
- @server.register(:foo, :bar)
- @server.unregister(:foo)
- Proc.new { @server.unregister(:bar)}.should_not raise_error
- end
-
- it "should allow specifying numerous indirections which are to be no longer accessible to clients" do
- @server.register(:foo, :bar)
- Proc.new { @server.unregister(:foo, :bar) }.should_not raise_error
- end
-
- it "should not allow turning off unknown indirection names" do
- @server.register(:foo, :bar)
- Proc.new { @server.unregister(:baz) }.should raise_error(ArgumentError)
- end
-
- it "should disable client access immediately when turning off indirections" do
- @server.register(:foo, :bar)
- @server.unregister(:foo)
- Proc.new { @server.unregister(:foo) }.should raise_error(ArgumentError)
- end
-
- it "should allow turning off all indirections at once" do
- @server.register(:foo, :bar)
- @server.unregister
- [ :foo, :bar, :baz].each do |indirection|
- Proc.new { @server.unregister(indirection) }.should raise_error(ArgumentError)
- end
- end
-
- it "should provide a means of determining whether it is listening" do
- @server.should respond_to(:listening?)
- end
-
- it "should provide a means of determining which HTTP server will be used to provide access to clients" do
- @server.server_type.should == :suparserver
- end
-
- it "should provide a means of determining which style of service is being offered to clients"
-
- it "should allow for multiple configurations, each handling different indirections" do
- @server2 = Puppet::Network::Server.new
- @server.register(:foo, :bar)
- @server2.register(:foo, :xyzzy)
- @server.unregister(:foo, :bar)
- @server2.unregister(:foo, :xyzzy)
- Proc.new { @server.unregister(:xyzzy) }.should raise_error(ArgumentError)
- Proc.new { @server2.unregister(:bar) }.should raise_error(ArgumentError)
- end
-end
+ before do
+ @mock_http_server_class = mock('http server class')
+ Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class)
+ Puppet.stubs(:[]).with(:servertype).returns(:suparserver)
+ @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337)
+ end
+
+ it "should allow registering an indirection for client access by specifying its indirection name" do
+ Proc.new { @server.register(:foo) }.should_not raise_error
+ end
+
+ it "should require at least one indirection name when registering indirections for client access" do
+ Proc.new { @server.register }.should raise_error(ArgumentError)
+ end
+
+ it "should allow for numerous indirections to be registered at once for client access" do
+ Proc.new { @server.register(:foo, :bar, :baz) }.should_not raise_error
+ end
-describe Puppet::Network::Server, "when listening is turned off" do
- before do
- Puppet::Network::Server.stubs(:server_class_by_name).returns(TestServer)
- Puppet.stubs(:[]).with(:servertype).returns(:suparserver)
- @server = Puppet::Network::Server.new
- end
+ it "should allow the use of indirection names to specify which indirections are to be no longer accessible to clients" do
+ @server.register(:foo)
+ Proc.new { @server.unregister(:foo) }.should_not raise_error
+ end
+
+ it "should leave other indirections accessible to clients when turning off indirections" do
+ @server.register(:foo, :bar)
+ @server.unregister(:foo)
+ Proc.new { @server.unregister(:bar)}.should_not raise_error
+ end
- it "should allow listening to be turned on" do
- Proc.new { @server.listen }.should_not raise_error
- end
+ it "should allow specifying numerous indirections which are to be no longer accessible to clients" do
+ @server.register(:foo, :bar)
+ Proc.new { @server.unregister(:foo, :bar) }.should_not raise_error
+ end
+
+ it "should not turn off any indirections if given unknown indirection names to turn off" do
+ @server.register(:foo, :bar)
+ Proc.new { @server.unregister(:foo, :bar, :baz) }.should raise_error(ArgumentError)
+ Proc.new { @server.unregister(:foo, :bar) }.should_not raise_error
+ end
- it "should not allow listening to be turned off" do
- Proc.new { @server.unlisten }.should raise_error(RuntimeError)
- end
+ it "should not allow turning off unknown indirection names" do
+ @server.register(:foo, :bar)
+ Proc.new { @server.unregister(:baz) }.should raise_error(ArgumentError)
+ end
- it "should indicate that it is not listening" do
- @server.should_not be_listening
- end
+ it "should disable client access immediately when turning off indirections" do
+ @server.register(:foo, :bar)
+ @server.unregister(:foo)
+ Proc.new { @server.unregister(:foo) }.should raise_error(ArgumentError)
+ end
- it "should cause the HTTP server to listen when listening is turned on" do
- @server.expects(:start_web_server)
- @server.listen
- end
+ it "should allow turning off all indirections at once" do
+ @server.register(:foo, :bar)
+ @server.unregister
+ [ :foo, :bar, :baz].each do |indirection|
+ Proc.new { @server.unregister(indirection) }.should raise_error(ArgumentError)
+ end
+ end
+
+ it "should provide a means of determining whether it is listening" do
+ @server.should respond_to(:listening?)
+ end
- it "should not route HTTP GET requests to a controller for the registered indirection"
- it "should not route HTTP DELETE requests to a controller for the registered indirection"
- it "should not route HTTP POST requests to a controller for the registered indirection"
+ it "should provide a means of determining which HTTP server will be used to provide access to clients" do
+ @server.server_type.should == :suparserver
+ end
+
+ it "should allow for multiple configurations, each handling different indirections" do
+ @server2 = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337)
+ @server.register(:foo, :bar)
+ @server2.register(:foo, :xyzzy)
+ @server.unregister(:foo, :bar)
+ @server2.unregister(:foo, :xyzzy)
+ Proc.new { @server.unregister(:xyzzy) }.should raise_error(ArgumentError)
+ Proc.new { @server2.unregister(:bar) }.should raise_error(ArgumentError)
+ end
- # TODO: FIXME write integrations which fire up actual webrick / mongrel servers and are thus webrick / mongrel specific?]
+ it "should provide a means of determining which style of service is being offered to clients" do
+ @server.protocols.should == []
+ end
+
+ it "should provide a means of determining the listening address" do
+ @server.address.should == "127.0.0.1"
+ end
+
+ it "should provide a means of determining the listening port" do
+ @server.port.should == 31337
+ end
end
-describe Puppet::Network::Server, "when listening is turned on" do
- before do
- Puppet::Network::Server.stubs(:server_class_by_name).returns(TestServer)
- Puppet.stubs(:[]).with(:servertype).returns(:suparserver)
- @server = Puppet::Network::Server.new
- @server.listen
- end
-
- it "should allow listening to be turned off" do
- Proc.new { @server.unlisten }.should_not raise_error
- end
+describe Puppet::Network::Server, "when listening is off" do
+ before do
+ @mock_http_server_class = mock('http server class')
+ Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class)
+ Puppet.stubs(:[]).with(:servertype).returns(:suparserver)
+ @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337)
+ @mock_http_server = mock('http server')
+ @mock_http_server.stubs(:listen)
+ @server.stubs(:http_server).returns(@mock_http_server)
+ end
+
+ it "should indicate that it is not listening" do
+ @server.should_not be_listening
+ end
- it "should not allow listening to be turned on" do
- Proc.new { @server.listen }.should raise_error(RuntimeError)
- end
+ it "should not allow listening to be turned off" do
+ Proc.new { @server.unlisten }.should raise_error(RuntimeError)
+ end
- it "should indicate that listening is turned off" do
- @server.should be_listening
- end
+ it "should allow listening to be turned on" do
+ Proc.new { @server.listen }.should_not raise_error
+ end
+
+end
- it "should cause the HTTP server to stop listening when listening is turned off" do
- @server.expects(:stop_web_server)
- @server.unlisten
- end
+describe Puppet::Network::Server, "when listening is on" do
+ before do
+ @mock_http_server_class = mock('http server class')
+ Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class)
+ Puppet.stubs(:[]).with(:servertype).returns(:suparserver)
+ @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337)
+ @mock_http_server = mock('http server')
+ @mock_http_server.stubs(:listen)
+ @mock_http_server.stubs(:unlisten)
+ @server.stubs(:http_server).returns(@mock_http_server)
+ @server.listen
+ end
+
+ it "should indicate that listening is turned off" do
+ @server.should be_listening
+ end
+
+ it "should not allow listening to be turned on" do
+ Proc.new { @server.listen }.should raise_error(RuntimeError)
+ end
- it "should route HTTP GET requests to a controller for the registered indirection"
- it "should route HTTP DELETE requests to a controller for the registered indirection"
- it "should route HTTP POST requests to a controller for the registered indirection"
+ it "should allow listening to be turned off" do
+ Proc.new { @server.unlisten }.should_not raise_error
+ end
+end
+
+describe Puppet::Network::Server, "when listening is being turned on" do
+ before do
+ @mock_http_server_class = mock('http server class')
+ Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class)
+ Puppet.stubs(:[]).with(:servertype).returns(:suparserver)
+ @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337)
+ @mock_http_server = mock('http server')
+ @mock_http_server.stubs(:listen)
+ end
+
+ it "should fetch an instance of an HTTP server when listening is turned on" do
+ mock_http_server_class = mock('http server class')
+ mock_http_server_class.expects(:new).returns(@mock_http_server)
+ @server.expects(:http_server_class).returns(mock_http_server_class)
+ @server.listen
+ end
+
+ it "should cause the HTTP server to listen when listening is turned on" do
+ @mock_http_server.expects(:listen)
+ @server.expects(:http_server).returns(@mock_http_server)
+ @server.listen
+ end
+end
+
+describe Puppet::Network::Server, "when listening is being turned off" do
+ before do
+ @mock_http_server_class = mock('http server class')
+ Puppet::Network::HTTP.stubs(:server_class_by_type).returns(@mock_http_server_class)
+ Puppet.stubs(:[]).with(:servertype).returns(:suparserver)
+ @server = Puppet::Network::Server.new(:address => "127.0.0.1", :port => 31337)
+ @mock_http_server = mock('http server')
+ @mock_http_server.stubs(:listen)
+ @server.stubs(:http_server).returns(@mock_http_server)
+ @server.listen
+ end
+
+ it "should cause the HTTP server to stop listening when listening is turned off" do
+ @mock_http_server.expects(:unlisten)
+ @server.unlisten
+ end
+
+ it "should not allow for indirections to be turned off" do
+ @server.register(:foo)
+ Proc.new { @server.unregister(:foo) }.should raise_error(RuntimeError)
+ end
+end
+
+
+describe Class.new, "put these somewhere" do
+ it "should allow indirections to deny access to services based upon which client is connecting, or whether the client is authorized"
+ it "should deny access to clients based upon rules"
+ it "should have the ability to use a class-level from_ hook (from_yaml, from_text, etc.) that can be called, based on content-type header, to allow for different deserializations of an object"
+ it "should allow from_* on the inbound :data packet (look at its content_type) when doing a PUT/.new.save"
+ it "should prepend a rest version number on the path (w00t)"
+ it "should ... on server side, .save should from_yaml, then foo.save(args) instead of just Foo.new.save(args)"
+ it "should have a from_yaml class_method in the indirector (... default: yaml.load(data) => instance, but can be overridden)"
+end
- # TODO: FIXME [ write integrations which fire up actual webrick / mongrel servers and are thus webrick / mongrel specific?]
+describe Puppet::Indirector, "stuff required by HTTP servers" do
+ it "should provide the model with the ability to serialize to XML"
+ it "should provide the model with the ability to deserialize from XML"
end
diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb
index e9dc6b85d..5780d4fbb 100755
--- a/spec/unit/node/configuration.rb
+++ b/spec/unit/node/configuration.rb
@@ -301,9 +301,9 @@ end
describe Puppet::Node::Configuration, " when functioning as a resource container" do
before do
@config = Puppet::Node::Configuration.new("host")
- @one = stub 'resource1', :ref => "Me[you]", :configuration= => nil
- @two = stub 'resource2', :ref => "Me[him]", :configuration= => nil
- @dupe = stub 'resource3', :ref => "Me[you]", :configuration= => nil
+ @one = stub 'resource1', :ref => "Me[one]", :configuration= => nil
+ @two = stub 'resource2', :ref => "Me[two]", :configuration= => nil
+ @dupe = stub 'resource3', :ref => "Me[one]", :configuration= => nil
end
it "should provide a method to add one or more resources" do
@@ -376,7 +376,7 @@ describe Puppet::Node::Configuration, " when functioning as a resource container
it "should be able to find resources by reference or by type/title tuple" do
@config.add_resource @one
- @config.resource("me", "you").should equal(@one)
+ @config.resource("me", "one").should equal(@one)
end
it "should have a mechanism for removing resources" do
@@ -386,6 +386,32 @@ describe Puppet::Node::Configuration, " when functioning as a resource container
@config.resource(@one.ref).should be_nil
@config.vertex?(@one).should be_false
end
+
+ it "should have a method for creating aliases for resources" do
+ @config.add_resource @one
+ @config.alias(@one, "other")
+ @config.resource("me", "other").should equal(@one)
+ end
+
+ # This test is the same as the previous, but the behaviour should be explicit.
+ it "should alias using the class name from the resource reference, not the resource class name" do
+ @config.add_resource @one
+ @config.alias(@one, "other")
+ @config.resource("me", "other").should equal(@one)
+ end
+
+ it "should fail to add an alias if the aliased name already exists" do
+ @config.add_resource @one
+ proc { @config.alias @two, "one" }.should raise_error(ArgumentError)
+ end
+
+ it "should remove resource aliases when the target resource is removed" do
+ @config.add_resource @one
+ @config.alias(@one, "other")
+ @one.expects :remove
+ @config.remove_resource(@one)
+ @config.resource("me", "other").should be_nil
+ end
end
module ApplyingConfigurations
diff --git a/spec/unit/other/transbucket.rb b/spec/unit/other/transbucket.rb
index 8cb9abaa4..0da808460 100755
--- a/spec/unit/other/transbucket.rb
+++ b/spec/unit/other/transbucket.rb
@@ -102,6 +102,18 @@ describe Puppet::TransBucket, " when generating a configuration" do
@top.to_configuration
end
+ it "should set each TransObject's configuration before converting to a RAL resource" do
+ @middleobj.expects(:configuration=).with { |c| c.is_a?(Puppet::Node::Configuration) }
+ @top.to_configuration
+ end
+
+ it "should set each TransBucket's configuration before converting to a RAL resource" do
+ # each bucket is seen twice in the loop, so we have to handle the case where the config
+ # is set twice
+ @bottom.expects(:configuration=).with { |c| c.is_a?(Puppet::Node::Configuration) }.at_least_once
+ @top.to_configuration
+ end
+
after do
Puppet::Type.allclear
end
diff --git a/spec/unit/other/transobject.rb b/spec/unit/other/transobject.rb
index 144940b7e..eaca855db 100755
--- a/spec/unit/other/transobject.rb
+++ b/spec/unit/other/transobject.rb
@@ -2,114 +2,73 @@
require File.dirname(__FILE__) + '/../../spec_helper'
-describe Puppet::TransObject, " when building its search path" do
-end
-
-describe Puppet::TransObject, " when building its search path" do
-end
-#!/usr/bin/env ruby
-
-$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
-
-require 'puppet'
require 'puppet/transportable'
-require 'puppettest'
-require 'puppettest/parsertesting'
-require 'yaml'
-class TestTransportable < Test::Unit::TestCase
- include PuppetTest::ParserTesting
-
- def test_yamldumpobject
- obj = mk_transobject
- obj.to_yaml_properties
- str = nil
- assert_nothing_raised {
- str = YAML.dump(obj)
- }
-
- newobj = nil
- assert_nothing_raised {
- newobj = YAML.load(str)
- }
-
- assert(newobj.name, "Object has no name")
- assert(newobj.type, "Object has no type")
+describe Puppet::TransObject, " when serializing" do
+ before do
+ @resource = Puppet::TransObject.new("/my/file", "file")
+ @resource["one"] = "test"
+ @resource["two"] = "other"
end
- def test_yamldumpbucket
- objects = %w{/etc/passwd /etc /tmp /var /dev}.collect { |d|
- mk_transobject(d)
- }
- bucket = mk_transbucket(*objects)
- str = nil
- assert_nothing_raised {
- str = YAML.dump(bucket)
- }
-
- newobj = nil
- assert_nothing_raised {
- newobj = YAML.load(str)
- }
-
- assert(newobj.name, "Bucket has no name")
- assert(newobj.type, "Bucket has no type")
+ it "should be able to be dumped to yaml" do
+ proc { YAML.dump(@resource) }.should_not raise_error
end
- # Verify that we correctly strip out collectable objects, since they should
- # not be sent to the client.
- def test_collectstrip
- top = mk_transtree do |object, depth, width|
- if width % 2 == 1
- object.collectable = true
- end
- end
+ it "should produce an equivalent yaml object" do
+ text = YAML.dump(@resource)
- assert(top.flatten.find_all { |o| o.collectable }.length > 0,
- "Could not find any collectable objects")
+ newresource = YAML.load(text)
+ newresource.name.should == "/my/file"
+ newresource.type.should == "file"
+ %w{one two}.each do |param|
+ newresource[param].should == @resource[param]
+ end
+ end
+end
- # Now strip out the collectable objects
- top.collectstrip!
+describe Puppet::TransObject, " when converting to a RAL resource" do
+ before do
+ @resource = Puppet::TransObject.new("/my/file", "file")
+ @resource["one"] = "test"
+ @resource["two"] = "other"
+ end
- # And make sure they're actually gone
- assert_equal(0, top.flatten.find_all { |o| o.collectable }.length,
- "Still found collectable objects")
+ it "should use the resource type's :create method to create the resource" do
+ type = mock 'resource type'
+ type.expects(:create).with(@resource).returns(:myresource)
+ Puppet::Type.expects(:type).with("file").returns(type)
+ @resource.to_type.should == :myresource
end
- # Make sure our 'delve' command is working
- def test_delve
- top = mk_transtree do |object, depth, width|
- if width % 2 == 1
- object.collectable = true
- end
- end
+ it "should convert to a component instance if the resource type cannot be found" do
+ Puppet::Type.expects(:type).with("file").returns(nil)
+ @resource.expects(:to_component).returns(:mycomponent)
+ @resource.to_type.should == :mycomponent
+ end
+end
- objects = []
- buckets = []
- collectable = []
+describe Puppet::TransObject, " when converting to a RAL component instance" do
+ before do
+ @resource = Puppet::TransObject.new("/my/file", "one::two")
+ @resource["one"] = "test"
+ @resource["noop"] = "other"
+ end
- count = 0
- assert_nothing_raised {
- top.delve do |object|
- count += 1
- if object.is_a? Puppet::TransBucket
- buckets << object
- else
- objects << object
- if object.collectable
- collectable << object
- end
- end
- end
- }
+ it "should use a new TransObject whose name is a resource reference of the type and title of the original TransObject" do
+ Puppet::Type::Component.expects(:create).with { |resource| resource.type == :component and resource.name == "One::Two[/my/file]" }.returns(:yay)
+ @resource.to_component.should == :yay
+ end
- top.flatten.each do |obj|
- assert(objects.include?(obj), "Missing obj %s[%s]" % [obj.type, obj.name])
- end
+ it "should pass the resource parameters on to the newly created TransObject" do
+ Puppet::Type::Component.expects(:create).with { |resource| resource["noop"] == "other" }.returns(:yay)
+ @resource.to_component.should == :yay
+ end
- assert_equal(collectable.length,
- top.flatten.find_all { |o| o.collectable }.length,
- "Found incorrect number of collectable objects")
+ # LAK:FIXME This really isn't the design we want going forward, but it's
+ # good enough for now.
+ it "should not pass resource paramaters that are not metaparams" do
+ Puppet::Type::Component.expects(:create).with { |resource| resource["one"].nil? }.returns(:yay)
+ @resource.to_component.should == :yay
end
end
-
diff --git a/spec/unit/parser/compile.rb b/spec/unit/parser/compile.rb
index 93c440417..5f239636b 100755
--- a/spec/unit/parser/compile.rb
+++ b/spec/unit/parser/compile.rb
@@ -141,3 +141,103 @@ describe Puppet::Parser::Compile, " when evaluating found classes" do
@compile.evaluate_classes(%w{myclass notfound}, @scope).should == %w{myclass}
end
end
+
+describe Puppet::Parser::Compile, " when evaluating AST nodes with no AST nodes present" do
+ before do
+ @node = stub 'node', :name => "foo"
+ @parser = stub 'parser', :version => "1.0", :nodes => {}
+ @compile = Puppet::Parser::Compile.new(@node, @parser)
+ end
+
+ it "should do nothing" do
+ @compile.expects(:ast_nodes?).returns(false)
+ @compile.parser.expects(:nodes).never
+ Puppet::Parser::Resource.expects(:new).never
+
+ @compile.send(:evaluate_ast_node)
+ end
+end
+
+describe Puppet::Parser::Compile, " when evaluating AST nodes with AST nodes present" do
+ before do
+ @node = stub 'node', :name => "foo"
+ @parser = stub 'parser', :version => "1.0", :nodes => {}
+ @compile = Puppet::Parser::Compile.new(@node, @parser)
+
+ @nodes = mock 'node_hash'
+ @compile.stubs(:ast_nodes?).returns(true)
+ @compile.parser.stubs(:nodes).returns(@nodes)
+
+ # Set some names for our test
+ @node.stubs(:names).returns(%w{a b c})
+ @nodes.stubs(:[]).with("a").returns(nil)
+ @nodes.stubs(:[]).with("b").returns(nil)
+ @nodes.stubs(:[]).with("c").returns(nil)
+
+ # It should check this last, of course.
+ @nodes.stubs(:[]).with("default").returns(nil)
+ end
+
+ it "should fail if the named node cannot be found" do
+ proc { @compile.send(:evaluate_ast_node) }.should raise_error(Puppet::ParseError)
+ end
+
+ it "should create a resource for the first node class matching the node name" do
+ node_class = stub 'node', :classname => "c"
+ @nodes.stubs(:[]).with("c").returns(node_class)
+
+ node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil
+ Puppet::Parser::Resource.expects(:new).with { |args| args[:title] == "c" and args[:type] == "node" }.returns(node_resource)
+
+ @compile.send(:evaluate_ast_node)
+ end
+
+ it "should match the default node if no matching node can be found" do
+ node_class = stub 'node', :classname => "default"
+ @nodes.stubs(:[]).with("default").returns(node_class)
+
+ node_resource = stub 'node resource', :ref => "Node[default]", :evaluate => nil
+ Puppet::Parser::Resource.expects(:new).with { |args| args[:title] == "default" and args[:type] == "node" }.returns(node_resource)
+
+ @compile.send(:evaluate_ast_node)
+ end
+
+ it "should tag the configuration with the found node name" do
+ node_class = stub 'node', :classname => "c"
+ @nodes.stubs(:[]).with("c").returns(node_class)
+
+ node_resource = stub 'node resource', :ref => "Node[c]", :evaluate => nil
+ Puppet::Parser::Resource.stubs(:new).returns(node_resource)
+
+ @compile.configuration.expects(:tag).with("c")
+ @compile.send(:evaluate_ast_node)
+ end
+
+ it "should evaluate the node resource immediately rather than using lazy evaluation" do
+ node_class = stub 'node', :classname => "c"
+ @nodes.stubs(:[]).with("c").returns(node_class)
+
+ node_resource = stub 'node resource', :ref => "Node[c]"
+ Puppet::Parser::Resource.stubs(:new).returns(node_resource)
+
+ node_resource.expects(:evaluate)
+
+ @compile.send(:evaluate_ast_node)
+ end
+
+ it "should set the node's scope as the top scope" do
+ node_class = stub 'node', :classname => "c"
+ @nodes.stubs(:[]).with("c").returns(node_class)
+
+ node_resource = stub 'node resource', :ref => "Node[c]"
+ Puppet::Parser::Resource.stubs(:new).returns(node_resource)
+
+ # The #evaluate method normally does this.
+ @compile.class_set(node_class.classname, :my_node_scope)
+ node_resource.stubs(:evaluate)
+
+ @compile.send(:evaluate_ast_node)
+
+ @compile.topscope.should == :my_node_scope
+ end
+end
diff --git a/test/language/compile.rb b/test/language/compile.rb
index 380da3da5..50b16a24d 100755
--- a/test/language/compile.rb
+++ b/test/language/compile.rb
@@ -174,88 +174,18 @@ class TestCompile < Test::Unit::TestCase
# "".
def test_evaluate_main
compile = mkcompile
- main = mock 'main_class'
- compile.topscope.expects(:source=).with(main)
- @parser.expects(:findclass).with("", "").returns(main)
+ main_class = mock 'main_class'
+ compile.topscope.expects(:source=).with(main_class)
+ @parser.expects(:findclass).with("", "").returns(main_class)
- assert_nothing_raised("Could not call evaluate_main") do
- compile.send(:evaluate_main)
- end
-
- assert(compile.resources.find { |r| r.to_s == "Class[main]" }, "Did not create a 'main' resource")
- 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
- compile = mkcompile
- name = compile.node.name
- compile.expects(:ast_nodes?).returns(false)
- compile.parser.expects(:nodes).never
-
- assert_nothing_raised("Could not call evaluate_ast_node when ast nodes are disabled") do
- compile.send(:evaluate_ast_node)
- end
-
- assert_nil(compile.resources.find { |r| r.to_s == "Node[#{name}]" }, "Created node object when ast_nodes was false")
-
- # Now try it with them enabled, but no node found.
- nodes = mock 'node_hash'
- compile = mkcompile
- name = compile.node.name
- compile.expects(:ast_nodes?).returns(true)
- compile.parser.stubs(:nodes).returns(nodes)
-
- # 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)
+ main_resource = mock 'main resource'
+ Puppet::Parser::Resource.expects(:new).with { |args| args[:title] == :main }.returns(main_resource)
- # 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
- compile.send(:evaluate_ast_node)
- end
-
- # Finally, make sure it works dandily when we have a node
- compile = mkcompile
- compile.expects(:ast_nodes?).returns(true)
-
- node = stub 'node', :classname => "c"
- nodes = {"c" => node}
- compile.parser.stubs(:nodes).returns(nodes)
-
- # Set some names for our test
- @node.names = %w{a b c}
-
- # And make sure we throw no exceptions.
- assert_nothing_raised("Failed when a node was found") do
- compile.send(:evaluate_ast_node)
- end
+ main_resource.expects(:evaluate)
- assert_instance_of(Puppet::Parser::Resource, compile.resources.find { |r| r.to_s == "Node[c]" },
- "Did not create node resource")
-
- # Lastly, check when we actually find the default.
- compile = mkcompile
- compile.expects(:ast_nodes?).returns(true)
-
- node = stub 'node', :classname => "default"
- nodes = {"default" => node}
- compile.parser.stubs(:nodes).returns(nodes)
-
- # Set some names for our test
- @node.names = %w{a b c}
-
- # And make sure the lack of a node throws an exception
- assert_nothing_raised("Failed when a node was found") do
- compile.send(:evaluate_ast_node)
+ assert_nothing_raised("Could not call evaluate_main") do
+ compile.send(:evaluate_main)
end
- assert_instance_of(Puppet::Parser::Resource, compile.resources.find { |r| r.to_s == "Node[default]" },
- "Did not create default node resource")
end
def test_evaluate_node_classes
diff --git a/test/language/parser.rb b/test/language/parser.rb
index 04cd3a095..bc89eff20 100755
--- a/test/language/parser.rb
+++ b/test/language/parser.rb
@@ -694,6 +694,7 @@ file { "/tmp/yayness":
manifest = File.join(modpath, "manifest.pp")
manifest_texts.each do |txt|
+ Puppet::Type.allclear
File.open(manifest, "w") { |f| f.puts txt }
assert_nothing_raised {
diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb
index 440ba3ba2..6447b80fb 100755
--- a/test/lib/puppettest.rb
+++ b/test/lib/puppettest.rb
@@ -20,6 +20,17 @@ if ARGV.include?("-d")
$console = true
end
+# Some monkey-patching to allow us to test private methods.
+class Class
+ def publicize_methods(*methods)
+ saved_private_instance_methods = methods.empty? ? self.private_instance_methods : methods
+
+ self.class_eval { public *saved_private_instance_methods }
+ yield
+ self.class_eval { private *saved_private_instance_methods }
+ end
+end
+
module PuppetTest
# Munge cli arguments, so we can enable debugging if we want
# and so we can run just specific methods.
diff --git a/test/lib/puppettest/support/assertions.rb b/test/lib/puppettest/support/assertions.rb
index 7e3e5ca2b..906bb3c76 100644
--- a/test/lib/puppettest/support/assertions.rb
+++ b/test/lib/puppettest/support/assertions.rb
@@ -1,7 +1,9 @@
require 'puppettest'
+require 'puppettest/support/utils'
require 'fileutils'
module PuppetTest
+ include PuppetTest::Support::Utils
def assert_logged(level, regex, msg = nil)
# Skip verifying logs that we're not supposed to send.
return unless Puppet::Util::Log.sendlevel?(level)
diff --git a/test/network/client/master.rb b/test/network/client/master.rb
index 4f6956470..7216af936 100755
--- a/test/network/client/master.rb
+++ b/test/network/client/master.rb
@@ -7,38 +7,6 @@ require 'mocha'
class TestMasterClient < Test::Unit::TestCase
include PuppetTest::ServerTest
-
- class FakeTrans
- def initialize
- @counters = Hash.new { |h,k| h[k] = 0 }
- end
- [:evaluate, :report, :cleanup, :addtimes, :tags, :ignoreschedules].each do |m|
- define_method(m.to_s + "=") do |*args|
- @counters[m] += 1
- end
- define_method(m) do |*args|
- @counters[m] += 1
- end
- define_method(m.to_s + "?") do
- @counters[m]
- end
- end
- end
- class FakeComponent
- attr_accessor :trans
- def evaluate
- @trans = FakeTrans.new
- @trans
- end
-
- def finalize
- @finalized = true
- end
-
- def finalized?
- @finalized
- end
- end
def setup
super
@@ -67,66 +35,6 @@ class TestMasterClient < Test::Unit::TestCase
return client
end
-
- def mk_fake_client
- server = Puppet::Network::Handler.master.new :Code => ""
- master = Puppet::Network::Client.master.new :Master => server, :Local => true
-
- # Now create some objects
- objects = FakeComponent.new
-
- master.send(:instance_variable_set, "@objects", objects)
-
- class << master
- def report(r)
- @reported ||= 0
- @reported += 1
- end
- def reported
- @reported ||= 0
- @reported
- end
- end
- return master, objects
- end
-
- def test_getconfig
- client = mkclient
-
- $methodsrun = []
- cleanup { $methodsrun = nil }
- client.meta_def(:getplugins) do
- $methodsrun << :getplugins
- end
- client.meta_def(:get_actual_config) do
- $methodsrun << :get_actual_config
- result = Puppet::TransBucket.new()
- result.type = "testing"
- result.name = "yayness"
- result
- end
-
- assert_nothing_raised do
- client.getconfig
- end
- [:get_actual_config].each do |method|
- assert($methodsrun.include?(method), "method %s was not run" % method)
- end
- assert(! $methodsrun.include?(:getplugins), "plugins were synced even tho disabled")
-
- # Now set pluginsync
- Puppet[:pluginsync] = true
- $methodsrun.clear
-
- assert_nothing_raised do
- client.getconfig
- end
- [:getplugins, :get_actual_config].each do |method|
- assert($methodsrun.include?(method), "method %s was not run" % method)
- end
-
- assert_instance_of(Puppet::Node::Configuration, client.configuration, "Configuration was not created")
- end
def test_disable
FileUtils.mkdir_p(Puppet[:statedir])
@@ -136,27 +44,22 @@ class TestMasterClient < Test::Unit::TestCase
client = mkclient(master)
- assert(! FileTest.exists?(@createdfile))
-
- assert_nothing_raised {
+ assert_nothing_raised("Could not disable client") {
client.disable
}
- assert_nothing_raised {
- client.run
- }
+ client.expects(:getconfig).never
- assert(! FileTest.exists?(@createdfile), "Disabled client ran")
+ client.run
- assert_nothing_raised {
- client.enable
- }
+ client = mkclient(master)
- assert_nothing_raised {
- client.run
- }
+ client.expects(:getconfig)
- assert(FileTest.exists?(@createdfile), "Enabled client did not run")
+ assert_nothing_raised("Could not enable client") {
+ client.enable
+ }
+ client.run
end
# Make sure we're getting the client version in our list of facts
@@ -652,15 +555,16 @@ end
client = mkclient
ftype = Puppet::Type.type(:file)
+ file = ftype.create :title => "/what/ever", :ensure => :present
+ config = Puppet::Node::Configuration.new
+ config.add_resource(file)
- assert_nil(ftype[@createdfile], "file object already exists")
- assert(! FileTest.exists?(@createdfile), "File already exists on disk")
+ config.expects :apply
- assert_nothing_raised("Could not apply config") do
- client.run
- end
+ client.configuration = config
+ client.expects(:getconfig)
+ client.run
- assert(FileTest.exists?(@createdfile), "File does not exist on disk")
assert_nil(ftype[@createdfile], "file object was not removed from memory")
end
@@ -675,20 +579,20 @@ end
end
end
- # #800 -- we cannot fix this using the current design.
- def disabled_test_invalid_relationships_do_not_get_cached
- # Make a master with an invalid relationship
+ def test_invalid_configurations_do_not_get_cached
master = mkmaster :Code => "notify { one: require => File[yaytest] }"
master.local = false # so it gets cached
client = mkclient(master)
+ client.stubs(:facts).returns({})
client.local = false
- client.getconfig
- # Doesn't throw an exception, but definitely fails.
- client.apply
+ Puppet::Node::Facts.indirection.stubs(:terminus_class).returns(:memory)
# Make sure the config is not cached.
- config = Puppet.settings[:localconfig] + ".yaml"
- assert(! File.exists?(config), "Cached an invalid configuration")
+ client.expects(:cache).never
+
+ client.getconfig
+ # Doesn't throw an exception, but definitely fails.
+ client.run
end
end
diff --git a/test/network/client/resource.rb b/test/network/client/resource.rb
index 83c195035..eb8e829eb 100755
--- a/test/network/client/resource.rb
+++ b/test/network/client/resource.rb
@@ -3,10 +3,13 @@
require File.dirname(__FILE__) + '/../../lib/puppettest'
require 'puppettest'
+require 'puppettest/support/utils'
+require 'puppettest/support/assertions'
require 'puppet/network/client/resource'
class TestResourceClient < Test::Unit::TestCase
include PuppetTest::ServerTest
+ include PuppetTest::Support::Utils
def mkresourceserver
Puppet::Network::Handler.resource.new
@@ -35,6 +38,7 @@ class TestResourceClient < Test::Unit::TestCase
assert_instance_of(Puppet::TransObject, tobj)
+ Puppet::Type.allclear
obj = nil
assert_nothing_raised {
obj = tobj.to_type
@@ -45,6 +49,7 @@ class TestResourceClient < Test::Unit::TestCase
File.unlink(file)
# Now test applying
+ Puppet::Type.allclear
result = nil
assert_nothing_raised {
result = client.apply(tobj)
@@ -52,6 +57,7 @@ class TestResourceClient < Test::Unit::TestCase
assert(FileTest.exists?(file), "File was not created on apply")
# Lastly, test "list"
+ Puppet::Type.allclear
list = nil
assert_nothing_raised {
list = client.list("user")
@@ -64,12 +70,14 @@ class TestResourceClient < Test::Unit::TestCase
break if count > 3
assert_instance_of(Puppet::TransObject, tobj)
+ Puppet::Type.allclear
tobj2 = nil
assert_nothing_raised {
tobj2 = client.describe(tobj.type, tobj.name)
}
obj = nil
+ Puppet::Type.allclear
assert_nothing_raised {
obj = tobj2.to_type
}
diff --git a/test/network/handler/master.rb b/test/network/handler/master.rb
index 694888f4d..25117030e 100755
--- a/test/network/handler/master.rb
+++ b/test/network/handler/master.rb
@@ -13,39 +13,11 @@ class TestMaster < Test::Unit::TestCase
Puppet::Indirector::Indirection.clear_cache
end
- def test_defaultmanifest
- textfiles { |file|
- Puppet[:manifest] = file
- client = nil
- master = nil
- assert_nothing_raised() {
- # this is the default server setup
- master = Puppet::Network::Handler.master.new(
- :Manifest => file,
- :UseNodes => false,
- :Local => true
- )
- }
- assert_nothing_raised() {
- client = Puppet::Network::Client.master.new(
- :Master => master
- )
- }
-
- # pull our configuration
- assert_nothing_raised() {
- client.getconfig
- stopservices
- Puppet::Type.allclear
- }
-
- break
- }
- end
-
+ # Make sure that files are reread when they change.
def test_filereread
# Start with a normal setting
Puppet[:filetimeout] = 15
+
manifest = mktestmanifest()
facts = Puppet::Network::Client.master.facts
@@ -63,35 +35,12 @@ class TestMaster < Test::Unit::TestCase
:Local => true
)
}
- assert_nothing_raised() {
- client = Puppet::Network::Client.master.new(
- :Master => master
- )
- }
- assert(client, "did not create master client")
- # The client doesn't have a config, so it can't be up to date
- assert(! client.fresh?(facts),
- "Client is incorrectly up to date")
-
- Puppet.settings.use(:main)
- config = nil
- assert_nothing_raised {
- config = client.getconfig
- config.apply
- }
-
- # Now it should be up to date
- assert(client.fresh?(facts), "Client is not up to date")
+ config = master.getconfig({"hostname" => "blah"})
# Cache this value for later
parse1 = master.freshness("mynode")
- # Verify the config got applied
- assert(FileTest.exists?(@createdfile),
- "Created file %s does not exist" % @createdfile)
- Puppet::Type.allclear
-
sleep 1.5
# Create a new manifest
File.open(manifest, "w") { |f|
@@ -101,7 +50,6 @@ class TestMaster < Test::Unit::TestCase
# Verify that the master doesn't immediately reparse the file; we
# want to wait through the timeout
assert_equal(parse1, master.freshness("mynode"), "Master did not wait through timeout")
- assert(client.fresh?(facts), "Client is not up to date")
# Then eliminate it
Puppet[:filetimeout] = 0
@@ -109,16 +57,8 @@ class TestMaster < Test::Unit::TestCase
# Now make sure the master does reparse
#Puppet.notice "%s vs %s" % [parse1, master.freshness]
assert(parse1 != master.freshness("mynode"), "Master did not reparse file")
- assert(! client.fresh?(facts), "Client is incorrectly up to date")
-
- # Retrieve and apply the new config
- assert_nothing_raised {
- config = client.getconfig
- config.apply
- }
- assert(client.fresh?(facts), "Client is not up to date")
- assert(FileTest.exists?(file2), "Second file %s does not exist" % file2)
+ assert(master.getconfig({"hostname" => "blah"}) != config, "Did not use reloaded config")
end
# Make sure we're correctly doing clientname manipulations.
diff --git a/test/network/handler/runner.rb b/test/network/handler/runner.rb
index 402b27ffc..171458ffa 100755
--- a/test/network/handler/runner.rb
+++ b/test/network/handler/runner.rb
@@ -29,7 +29,8 @@ class TestHandlerRunner < Test::Unit::TestCase
client
end
- def test_runner
+ def setup
+ super
FileUtils.mkdir_p(Puppet[:statedir])
Puppet[:ignoreschedules] = false
# Okay, make our manifest
@@ -37,7 +38,7 @@ class TestHandlerRunner < Test::Unit::TestCase
created = tempfile()
# We specify the schedule here, because I was having problems with
# using default schedules.
- code = %{
+ @code = %{
class yayness {
schedule { "yayness": period => weekly }
file { "#{created}": ensure => file, schedule => yayness }
@@ -46,59 +47,24 @@ class TestHandlerRunner < Test::Unit::TestCase
include yayness
}
- client = mkclient(code)
+ @client = mkclient(@code)
- runner = nil
- assert_nothing_raised {
- runner = Puppet::Network::Handler.runner.new
- }
- # First: tags
- # Second: ignore schedules true/false
- # Third: background true/false
- # Fourth: whether file should exist true/false
- [
- ["with no backgrounding",
- nil, true, true, true],
- ["in the background",
- nil, true, false, true],
- ["with a bad tag",
- ["coolness"], true, false, false],
- ["with another bad tag",
- "coolness", true, false, false],
- ["with a good tag",
- ["coolness", "yayness"], true, false, true],
- ["with another good tag",
- ["yayness"], true, false, true],
- ["with a third good tag",
- "yayness", true, false, true],
- ["with no tags",
- "", true, false, true],
- ["not ignoring schedules",
- nil, false, false, false],
- ["ignoring schedules",
- nil, true, false, true],
- ].each do |msg, tags, ignore, fg, shouldexist|
- if FileTest.exists?(created)
- File.unlink(created)
- end
- assert_nothing_raised {
- # Try it without backgrounding
- runner.run(tags, ignore, fg)
- }
+ @runner = Puppet::Network::Handler.runner.new
+ end
+
+ def test_runner_when_in_foreground
+ @client.expects(:run).with(:tags => "mytags", :ignoreschedules => true)
+
+ Process.expects(:newthread).never
- unless fg
- Puppet.join
- end
-
- if shouldexist
- assert(FileTest.exists?(created), "File did not get created " +
- msg)
- else
- assert(!FileTest.exists?(created), "File got created incorrectly " +
- msg)
- end
- end
+ @runner.run("mytags", true, true)
end
-end
+ def test_runner_when_in_background
+ @client.expects(:run).with(:tags => "mytags", :ignoreschedules => true)
+
+ Puppet.expects(:newthread).yields
+ @runner.run("mytags", true, false)
+ end
+end
diff --git a/test/other/relationships.rb b/test/other/relationships.rb
index bfe7e88d7..0f2a103fe 100755
--- a/test/other/relationships.rb
+++ b/test/other/relationships.rb
@@ -111,7 +111,7 @@ class TestRelationships < Test::Unit::TestCase
end
end
- def test_store_relationship
+ def test_munge_relationship
file = Puppet::Type.newfile :path => tempfile(), :mode => 0755
execs = []
3.times do |i|
@@ -122,7 +122,7 @@ class TestRelationships < Test::Unit::TestCase
result = nil
[execs[0], [:exec, "yay0"], ["exec", "yay0"]].each do |target|
assert_nothing_raised do
- result = file.send(:store_relationship, :require, target)
+ result = file.send(:munge_relationship, :require, target)
end
assert_equal([[:exec, "yay0"]], result)
@@ -133,7 +133,7 @@ class TestRelationships < Test::Unit::TestCase
strings = execs.collect { |e| [e.class.name.to_s, e.title] }
[execs, symbols, strings].each do |target|
assert_nothing_raised do
- result = file.send(:store_relationship, :require, target)
+ result = file.send(:munge_relationship, :require, target)
end
assert_equal(symbols, result)
@@ -141,7 +141,7 @@ class TestRelationships < Test::Unit::TestCase
# Make sure we can mix it up, even though this shouldn't happen
assert_nothing_raised do
- result = file.send(:store_relationship, :require, [execs[0], [execs[1].class.name, execs[1].title]])
+ result = file.send(:munge_relationship, :require, [execs[0], [execs[1].class.name, execs[1].title]])
end
assert_equal([[:exec, "yay0"], [:exec, "yay1"]], result)
@@ -151,7 +151,7 @@ class TestRelationships < Test::Unit::TestCase
file[:require] = execs[0]
assert_nothing_raised do
- result = file.send(:store_relationship, :require, [execs[1], execs[2]])
+ result = file.send(:munge_relationship, :require, [execs[1], execs[2]])
end
assert_equal(symbols, result)
diff --git a/test/ral/manager/instances.rb b/test/ral/manager/instances.rb
index 6ac4322f5..88f766038 100755
--- a/test/ral/manager/instances.rb
+++ b/test/ral/manager/instances.rb
@@ -93,7 +93,8 @@ class TestTypeInstances < Test::Unit::TestCase
# Make sure resources are entirely deleted.
def test_delete
aliases = %w{one}
- obj = @type.create(:name => "testing", :alias => "two")
+ config = mk_configuration
+ obj = @type.create(:name => "testing", :alias => "two", :configuration => config)
aliases << "two"
@type.alias("two", obj)
diff --git a/test/ral/manager/type.rb b/test/ral/manager/type.rb
index 57248159b..350d3dd15 100755
--- a/test/ral/manager/type.rb
+++ b/test/ral/manager/type.rb
@@ -131,27 +131,60 @@ class TestType < Test::Unit::TestCase
}
end
- # Verify that aliasing works
- def test_aliasing
- file = tempfile()
+ def test_aliases_to_self_are_not_failures
+ resource = Puppet.type(:file).create(
+ :name => "/path/to/some/missing/file",
+ :ensure => "file"
+ )
+ resource.stubs(:path).returns("")
- baseobj = nil
- assert_nothing_raised {
- baseobj = Puppet.type(:file).create(
- :name => file,
- :ensure => "file",
- :alias => ["funtest"]
- )
- }
+ configuration = stub 'configuration'
+ configuration.expects(:resource).with(:file, "/path/to/some/missing/file").returns(resource)
+ resource.configuration = configuration
# Verify our adding ourselves as an alias isn't an error.
- assert_nothing_raised {
- baseobj[:alias] = file
+ assert_nothing_raised("Could not add alias") {
+ resource[:alias] = "/path/to/some/missing/file"
+ }
+
+ assert_equal(resource.object_id, Puppet.type(:file)["/path/to/some/missing/file"].object_id, "Could not retrieve alias to self")
+ end
+
+ def test_aliases_are_added_to_class_and_configuration
+ resource = Puppet.type(:file).create(
+ :name => "/path/to/some/missing/file",
+ :ensure => "file"
+ )
+ resource.stubs(:path).returns("")
+
+ configuration = stub 'configuration'
+ configuration.stubs(:resource).returns(nil)
+ configuration.expects(:alias).with(resource, "funtest")
+ resource.configuration = configuration
+
+ assert_nothing_raised("Could not add alias") {
+ resource[:alias] = "funtest"
}
- assert_instance_of(Puppet.type(:file), Puppet.type(:file)["funtest"],
- "Could not retrieve alias")
+ assert_equal(resource.object_id, Puppet.type(:file)["funtest"].object_id, "Could not retrieve alias")
+ end
+
+ def test_aliasing_fails_without_a_configuration
+ resource = Puppet.type(:file).create(
+ :name => "/no/such/file",
+ :ensure => "file"
+ )
+
+ assert_raise(Puppet::Error, "Did not fail to alias when no configuration was available") {
+ resource[:alias] = "funtest"
+ }
+ end
+ def test_configurations_are_set_during_initialization_if_present_on_the_transobject
+ trans = Puppet::TransObject.new("/path/to/some/file", :file)
+ trans.configuration = :my_config
+ resource = trans.to_type
+ assert_equal(resource.configuration, trans.configuration, "Did not set configuration on initialization")
end
# Verify that requirements don't depend on file order
diff --git a/test/ral/types/file.rb b/test/ral/types/file.rb
index aca9d3b9c..73095a783 100755
--- a/test/ral/types/file.rb
+++ b/test/ral/types/file.rb
@@ -104,6 +104,12 @@ class TestFile < Test::Unit::TestCase
}
end
+ def test_groups_fails_when_invalid
+ assert_raise(Puppet::Error, "did not fail when the group was empty") do
+ Puppet::Type.type(:file).create :path => "/some/file", :group => ""
+ end
+ end
+
if Puppet::Util::SUIDManager.uid == 0
def test_createasuser
dir = tmpdir()
diff --git a/test/ral/types/host.rb b/test/ral/types/host.rb
index 69e37da0f..1013651c5 100755
--- a/test/ral/types/host.rb
+++ b/test/ral/types/host.rb
@@ -38,12 +38,16 @@ class TestHost < Test::Unit::TestCase
else
@hcount = 1
end
+
+ @configuration ||= mk_configuration
+
host = nil
assert_nothing_raised {
host = Puppet.type(:host).create(
:name => "fakehost%s" % @hcount,
:ip => "192.168.27.%s" % @hcount,
- :alias => "alias%s" % @hcount
+ :alias => "alias%s" % @hcount,
+ :configuration => @configuration
)
}
diff --git a/test/ral/types/parameter.rb b/test/ral/types/parameter.rb
index 89c8b944d..1d402cb85 100755
--- a/test/ral/types/parameter.rb
+++ b/test/ral/types/parameter.rb
@@ -113,6 +113,9 @@ class TestParameter < Test::Unit::TestCase
inst = type.create(:name => "test")
+ config = mk_configuration
+ inst.configuration = config
+
assert_nothing_raised("Could not create shadowed param") {
inst[:alias] = "foo"
}
@@ -133,7 +136,7 @@ class TestParameter < Test::Unit::TestCase
# Now try it during initialization
other = nil
assert_nothing_raised("Could not create instance with shadow") do
- other = type.create(:name => "rah", :alias => "one")
+ other = type.create(:name => "rah", :alias => "one", :configuration => config)
end
params = other.instance_variable_get("@parameters")
obj = params[:alias]
diff --git a/test/ral/types/sshkey.rb b/test/ral/types/sshkey.rb
index c610eb9e9..c99f7562a 100755
--- a/test/ral/types/sshkey.rb
+++ b/test/ral/types/sshkey.rb
@@ -47,12 +47,15 @@ class TestSSHKey < Test::Unit::TestCase
@kcount = 1
end
+ @config ||= mk_configuration
+
assert_nothing_raised {
key = @sshkeytype.create(
:name => "host%s.madstop.com" % @kcount,
:key => "%sAAAAB3NzaC1kc3MAAACBAMnhSiku76y3EGkNCDsUlvpO8tRgS9wL4Eh54WZfQ2lkxqfd2uT/RTT9igJYDtm/+UHuBRdNGpJYW1Nw2i2JUQgQEEuitx4QKALJrBotejGOAWxxVk6xsh9xA0OW8Q3ZfuX2DDitfeC8ZTCl4xodUMD8feLtP+zEf8hxaNamLlt/AAAAFQDYJyf3vMCWRLjTWnlxLtOyj/bFpwAAAIEAmRxxXb4jjbbui9GYlZAHK00689DZuX0EabHNTl2yGO5KKxGC6Esm7AtjBd+onfu4Rduxut3jdI8GyQCIW8WypwpJofCIyDbTUY4ql0AQUr3JpyVytpnMijlEyr41FfIb4tnDqnRWEsh2H7N7peW+8DWZHDFnYopYZJ9Yu4/jHRYAAACAERG50e6aRRb43biDr7Ab9NUCgM9bC0SQscI/xdlFjac0B/kSWJYTGVARWBDWug705hTnlitY9cLC5Ey/t/OYOjylTavTEfd/bh/8FkAYO+pWdW3hx6p97TBffK0b6nrc6OORT2uKySbbKOn0681nNQh4a6ueR3JRppNkRPnTk5c=" % @kcount,
:type => "ssh-dss",
- :alias => ["192.168.0.%s" % @kcount]
+ :alias => ["192.168.0.%s" % @kcount],
+ :configuration => @config
)
}