diff --git a/lib/puppet/application/puppetd.rb b/lib/puppet/application/puppetd.rb index 3efb0ef0a..cacb84361 100644 --- a/lib/puppet/application/puppetd.rb +++ b/lib/puppet/application/puppetd.rb @@ -1,255 +1,255 @@ require 'puppet' require 'puppet/application' require 'puppet/agent' require 'puppet/daemon' require 'puppet/configurer' require 'puppet/network/client' Puppet::Application.new(:puppetd) do should_parse_config attr_accessor :explicit_waitforcert, :args, :agent, :daemon preinit do # Do an initial trap, so that cancels don't get a stack trace. trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end { :waitforcert => 120, # Default to checking for certs every 5 minutes :onetime => false, :verbose => false, :debug => false, :centrallogs => false, :setdest => false, :enable => false, :disable => false, :client => true, :fqdn => nil, :serve => [] }.each do |opt,val| options[opt] = val end @explicit_waitforcert = false @args = {} @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup end option("--centrallogging") option("--disable") option("--enable") option("--debug","-d") option("--fqdn FQDN","-f") option("--test","-t") option("--verbose","-v") option("--serve HANDLER", "-s") do |arg| if Puppet::Network::Handler.handler(arg) options[:serve] << arg.to_sym else raise "Could not find handler for %s" % arg end end option("--no-client") do |arg| options[:client] = false end option("--onetime", "-o") do |arg| options[:onetime] = true options[:waitforcert] = 0 unless @explicit_waitforcert end option("--logdest", "-l") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail if Puppet[:debug] puts detail.backtrace end $stderr.puts detail.to_s end end option("--waitforcert WAITFORCERT", "-w") do |arg| options[:waitforcert] = arg.to_i @explicit_waitforcert = true end option("--port PORT","-p") do |arg| @args[:Port] = arg end dispatch do return :onetime if options[:onetime] return :main end command(:onetime) do unless options[:client] $stderr.puts "onetime is specified but there is no client" exit(43) end @daemon.set_signal_traps begin @agent.run rescue => detail if Puppet[:trace] puts detail.backtrace end Puppet.err detail.to_s end exit(0) end command(:main) do Puppet.notice "Starting Puppet client version %s" % [Puppet.version] @daemon.start end # Enable all of the most common test options. def setup_test Puppet.settings.handlearg("--ignorecache") Puppet.settings.handlearg("--no-usecacheonfailure") Puppet.settings.handlearg("--no-splay") Puppet.settings.handlearg("--show_diff") Puppet.settings.handlearg("--no-daemonize") options[:verbose] = true options[:onetime] = true options[:waitforcert] = 0 end # Handle the logging settings. def setup_logs if options[:debug] or options[:verbose] Puppet::Util::Log.newdestination(:console) if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end end unless options[:setdest] Puppet::Util::Log.newdestination(:syslog) end end def enable_disable_client(agent) if options[:enable] agent.enable elsif options[:disable] agent.disable end exit(0) end def setup_listen unless FileTest.exists?(Puppet[:authconfig]) Puppet.err "Will not start without authorization file %s" % Puppet[:authconfig] exit(14) end # FIXME: we should really figure out how to distribute the CRL # to clients. In the meantime, we just disable CRL checking if # the CRL file doesn't exist unless File::exist?(Puppet[:cacrl]) Puppet[:cacrl] = 'false' end handlers = nil if options[:serve].empty? handlers = [:Runner] else handlers = options[:serve] end require 'puppet/network/server' # No REST handlers yet. - server = Puppet::Network::Server.new(:handlers => [:facts], :xmlrpc_handlers => handlers, :port => Puppet[:puppetport]) + server = Puppet::Network::Server.new(:xmlrpc_handlers => handlers, :port => Puppet[:puppetport]) @daemon.server = server end setup do setup_test if options[:test] setup_logs if Puppet.settings.print_configs? exit(Puppet.settings.print_configs ? 0 : 1) end # If noop is set, then also enable diffs if Puppet[:noop] Puppet[:show_diff] = true end args[:Server] = Puppet[:server] if options[:fqdn] args[:FQDN] = options[:fqdn] Puppet[:certname] = options[:fqdn] end if options[:centrallogs] logdest = args[:Server] if args.include?(:Port) logdest += ":" + args[:Port] end Puppet::Util::Log.newdestination(logdest) end # We need to specify a ca location for things to work, but # until the REST cert transfers are working, it needs to # be local. Puppet::SSL::Host.ca_location = :remote Puppet::Transaction::Report.terminus_class = :rest Puppet::Resource::Catalog.terminus_class = :rest Puppet::Resource::Catalog.cache_class = :yaml Puppet::Node::Facts.terminus_class = :facter Puppet::Node::Facts.cache_class = :rest # We need tomake the client either way, we just don't start it # if --no-client is set. @agent = Puppet::Agent.new(Puppet::Configurer) enable_disable_client(@agent) if options[:enable] or options[:disable] @daemon.agent = agent # It'd be nice to daemonize later, but we have to daemonize before the # waitforcert happens. if Puppet[:daemonize] @daemon.daemonize end host = Puppet::SSL::Host.new cert = host.wait_for_cert(options[:waitforcert]) @objects = [] # This has to go after the certs are dealt with. if Puppet[:listen] unless options[:onetime] setup_listen else Puppet.notice "Ignoring --listen on onetime run" end end end end diff --git a/lib/puppet/application/puppetmasterd.rb b/lib/puppet/application/puppetmasterd.rb index 15426ad96..fe92bca7a 100644 --- a/lib/puppet/application/puppetmasterd.rb +++ b/lib/puppet/application/puppetmasterd.rb @@ -1,133 +1,130 @@ require 'puppet' require 'puppet/application' require 'puppet/daemon' require 'puppet/network/server' Puppet::Application.new(:puppetmasterd) do should_parse_config option("--debug", "-d") option("--verbose", "-v") option("--logdest", "-l") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail if Puppet[:debug] puts detail.backtrace end $stderr.puts detail.to_s end end preinit do trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end # Create this first-off, so we have ARGV @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup end dispatch do return Puppet[:parseonly] ? :parseonly : :main end command(:parseonly) do begin Puppet::Parser::Interpreter.new.parser(Puppet[:environment]) rescue => detail Puppet.err detail exit 1 end exit(0) end command(:main) do require 'etc' require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' require 'puppet/checksum' xmlrpc_handlers = [:Status, :FileServer, :Master, :Report, :Filebucket] - # Just set up serving to all of the indirected classes. - rest_handlers = Puppet::Indirector::Indirection.instances - if Puppet[:ca] xmlrpc_handlers << :CA end - @daemon.server = Puppet::Network::Server.new(:handlers => rest_handlers, :xmlrpc_handlers => xmlrpc_handlers) + @daemon.server = Puppet::Network::Server.new(:xmlrpc_handlers => xmlrpc_handlers) # Make sure we've got a localhost ssl cert Puppet::SSL::Host.localhost # And now configure our server to *only* hit the CA for data, because that's # all it will have write access to. if Puppet::SSL::CertificateAuthority.ca? Puppet::SSL::Host.ca_location = :only end if Process.uid == 0 begin Puppet::Util.chuser rescue => detail puts detail.backtrace if Puppet[:trace] $stderr.puts "Could not change user to %s: %s" % [Puppet[:user], detail] exit(39) end end @daemon.daemonize if Puppet[:daemonize] Puppet.notice "Starting Puppet server version %s" % [Puppet.version] @daemon.start end setup do # Handle the logging settings. if options[:debug] or options[:verbose] if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end unless Puppet[:daemonize] Puppet::Util::Log.newdestination(:console) options[:setdest] = true end end unless options[:setdest] Puppet::Util::Log.newdestination(:syslog) end if Puppet.settings.print_configs? exit(Puppet.settings.print_configs ? 0 : 1) end Puppet.settings.use :main, :puppetmasterd, :ssl # A temporary solution, to at least make the master work for now. Puppet::Node::Facts.terminus_class = :yaml # Cache our nodes in yaml. Currently not configurable. Puppet::Node.cache_class = :yaml # Configure all of the SSL stuff. if Puppet::SSL::CertificateAuthority.ca? Puppet::SSL::Host.ca_location = :local Puppet.settings.use :ca Puppet::SSL::CertificateAuthority.instance else Puppet::SSL::Host.ca_location = :none end end end diff --git a/lib/puppet/network/http/api/v1.rb b/lib/puppet/network/http/api/v1.rb index 2ee1a815f..13df7c3d0 100644 --- a/lib/puppet/network/http/api/v1.rb +++ b/lib/puppet/network/http/api/v1.rb @@ -1,60 +1,65 @@ require 'puppet/network/http/api' module Puppet::Network::HTTP::API::V1 # How we map http methods and the indirection name in the URI # to an indirection method. METHOD_MAP = { "GET" => { :plural => :search, :singular => :find }, "PUT" => { :singular => :save }, "DELETE" => { :singular => :destroy } } def uri2indirection(http_method, uri, params) environment, indirection, key = uri.split("/", 4)[1..-1] # the first field is always nil because of the leading slash raise ArgumentError, "The environment must be purely alphanumeric, not '%s'" % environment unless environment =~ /^\w+$/ raise ArgumentError, "The indirection name must be purely alphanumeric, not '%s'" % indirection unless indirection =~ /^\w+$/ method = indirection_method(http_method, indirection) params[:environment] = environment raise ArgumentError, "No request key specified in %s" % uri if key == "" or key.nil? key = URI.unescape(key) Puppet::Indirector::Request.new(indirection, method, key, params) end def indirection2uri(request) indirection = request.method == :search ? request.indirection_name.to_s + "s" : request.indirection_name.to_s "/#{request.environment.to_s}/#{indirection}/#{request.escaped_key}#{request.query_string}" end def indirection_method(http_method, indirection) unless METHOD_MAP[http_method] raise ArgumentError, "No support for http method %s" % http_method end unless method = METHOD_MAP[http_method][plurality(indirection)] raise ArgumentError, "No support for plural %s operations" % http_method end return method end def plurality(indirection) - result = (indirection == handler.to_s + "s") ? :plural : :singular + # NOTE This specific hook for facts is ridiculous, but it's a *many*-line + # fix to not need this, and our goal is to move away from the complication + # that leads to the fix being too long. + return :singular if indirection == "facts" + + result = (indirection =~ /s$/) ? :plural : :singular indirection.sub!(/s$/, '') if result result end end diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb index 76f07ed73..db12a8b67 100644 --- a/lib/puppet/network/http/handler.rb +++ b/lib/puppet/network/http/handler.rb @@ -1,184 +1,176 @@ module Puppet::Network::HTTP end require 'puppet/network/http/api/v1' module Puppet::Network::HTTP::Handler include Puppet::Network::HTTP::API::V1 - attr_reader :model, :server, :handler + attr_reader :server, :handler # Retrieve the accept header from the http request. def accept_header(request) raise NotImplementedError end # Which format to use when serializing our response. Just picks # the first value in the accept header, at this point. def format_to_use(request) unless header = accept_header(request) raise ArgumentError, "An Accept header must be provided to pick the right format" end format = nil header.split(/,\s*/).each do |name| next unless format = Puppet::Network::FormatHandler.format(name) next unless format.suitable? return name end raise "No specified acceptable formats (%s) are functional on this machine" % header end - def initialize_for_puppet(args = {}) - raise ArgumentError unless @server = args[:server] - raise ArgumentError unless @handler = args[:handler] - @model = find_model_for_handler(@handler) + def initialize_for_puppet(server) + @server = server end # handle an HTTP request def process(request, response) - indirection_request = uri2indirection(path(request), params(request), http_method(request)) + indirection_request = uri2indirection(http_method(request), path(request), params(request)) send("do_%s" % indirection_request.method, indirection_request, request, response) rescue Exception => e - puts e.backtrace return do_exception(response, e) end # Set the response up, with the body and status. def set_response(response, body, status = 200) raise NotImplementedError end # Set the specified format as the content type of the response. def set_content_type(response, format) raise NotImplementedError end def do_exception(response, exception, status=400) if exception.is_a?(Exception) puts exception.backtrace if Puppet[:trace] Puppet.err(exception) end set_content_type(response, "text/plain") set_response(response, exception.to_s, status) end # Execute our find. def do_find(indirection_request, request, response) - unless result = model.find(indirection_request.key, indirection_request.options) - return do_exception(response, "Could not find %s %s" % [model.name, indirection_request.key], 404) + unless result = indirection_request.model.find(indirection_request.key, indirection_request.to_hash) + return do_exception(response, "Could not find %s %s" % [indirection_request.indirection_name, indirection_request.key], 404) end # The encoding of the result must include the format to use, # and it needs to be used for both the rendering and as # the content type. format = format_to_use(request) set_content_type(response, format) set_response(response, result.render(format)) end # Execute our search. def do_search(indirection_request, request, response) - result = model.search(indirection_request.key, indirection_request.options) + result = indirection_request.model.search(indirection_request.key, indirection_request.to_hash) if result.nil? or (result.is_a?(Array) and result.empty?) - return do_exception(response, "Could not find instances in %s with '%s'" % [model.name, indirection_request.options.inspect], 404) + return do_exception(response, "Could not find instances in %s with '%s'" % [indirection_request.indirection_name, indirection_request.to_hash.inspect], 404) end format = format_to_use(request) set_content_type(response, format) - set_response(response, model.render_multiple(format, result)) + set_response(response, indirection_request.model.render_multiple(format, result)) end # Execute our destroy. def do_destroy(indirection_request, request, response) - result = model.destroy(indirection_request.key, indirection_request.options) + result = indirection_request.model.destroy(indirection_request.key, indirection_request.to_hash) set_content_type(response, "yaml") set_response(response, result.to_yaml) end # Execute our save. def do_save(indirection_request, request, response) data = body(request).to_s raise ArgumentError, "No data to save" if !data or data.empty? format = format_to_use(request) - obj = model.convert_from(format_to_use(request), data) + obj = indirection_request.model.convert_from(format_to_use(request), data) result = save_object(indirection_request, obj) set_content_type(response, "yaml") set_response(response, result.to_yaml) end private # LAK:NOTE This has to be here for testing; it's a stub-point so # we keep infinite recursion from happening. def save_object(ind_request, object) - object.save(ind_request.options) - end - - def find_model_for_handler(handler) - Puppet::Indirector::Indirection.model(handler) || - raise(ArgumentError, "Cannot locate indirection [#{handler}].") + object.save(ind_request.to_hash) 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 # methods to be overridden by the including web server class 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 decode_params(params) params.inject({}) do |result, ary| param, value = ary value = URI.unescape(value) if value =~ /^---/ value = YAML.load(value) else value = true if value == "true" value = false if value == "false" value = Integer(value) if value =~ /^\d+$/ value = value.to_f if value =~ /^\d+\.\d+$/ end result[param.to_sym] = value result end end end diff --git a/lib/puppet/network/http/mongrel.rb b/lib/puppet/network/http/mongrel.rb index 847781cf2..356402545 100644 --- a/lib/puppet/network/http/mongrel.rb +++ b/lib/puppet/network/http/mongrel.rb @@ -1,64 +1,57 @@ require 'mongrel' if Puppet.features.mongrel? require 'puppet/network/http/mongrel/rest' 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] @xmlrpc_handlers = args[:xmlrpc_handlers] @server = Mongrel::HttpServer.new(args[:address], args[:port]) setup_handlers @listening = true @server.run end def unlisten raise "Mongrel server is not listening" unless listening? @server.stop @server = nil @listening = false end def listening? @listening end private def setup_handlers - @protocols.each do |protocol| - next if protocol == :xmlrpc - klass = class_for_protocol(protocol) - @handlers.each do |handler| - @server.register('/' + handler.to_s, klass.new(:server => @server, :handler => handler)) - @server.register('/' + handler.to_s + 's', klass.new(:server => @server, :handler => handler)) - end - end + # Register our REST support at / + klass = class_for_protocol(:rest) + @server.register('/', klass.new(:server => @server)) if @protocols.include?(:xmlrpc) and ! @xmlrpc_handlers.empty? setup_xmlrpc_handlers end end # Use our existing code to provide the xmlrpc backward compatibility. def setup_xmlrpc_handlers @server.register('/RPC2', Puppet::Network::HTTPServer::Mongrel.new(@xmlrpc_handlers)) end def class_for_protocol(protocol) return Puppet::Network::HTTP::MongrelREST if protocol.to_sym == :rest 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 index 04f413937..d9913dc45 100644 --- a/lib/puppet/network/http/mongrel/rest.rb +++ b/lib/puppet/network/http/mongrel/rest.rb @@ -1,84 +1,79 @@ require 'puppet/network/http/handler' class Puppet::Network::HTTP::MongrelREST < Mongrel::HttpHandler include Puppet::Network::HTTP::Handler ACCEPT_HEADER = "HTTP_ACCEPT".freeze # yay, zed's a crazy-man def initialize(args={}) super() initialize_for_puppet(args) end def accept_header(request) request.params[ACCEPT_HEADER] end # which HTTP verb was used in this request def http_method(request) request.params[Mongrel::Const::REQUEST_METHOD] end # Return the query params for this request. We had to expose this method for # testing purposes. def params(request) params = Mongrel::HttpRequest.query_parse(request.params["QUERY_STRING"]) params = decode_params(params) params.merge(client_info(request)) end # what path was requested? def path(request) # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] - x = '/' + request.params[Mongrel::Const::REQUEST_PATH].split('/')[1] - end - - # return the key included in the request path - def request_key(request) - # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] - x = request.params[Mongrel::Const::REQUEST_PATH].split('/', 3)[2] + #x = '/' + request.params[Mongrel::Const::REQUEST_PATH] + request.params[Mongrel::Const::REQUEST_PATH] end # return the request body def body(request) request.body end def set_content_type(response, format) response.header['Content-Type'] = format end # produce the body of the response def set_response(response, result, status = 200) args = [status] # Set the 'reason' (or 'message', as it's called in Webrick), when # we have a failure. if status >= 300 args << false << result end response.start(*args) do |head, body| body.write(result) end end def client_info(request) result = {} params = request.params result[:ip] = params["REMOTE_ADDR"] # JJM #906 The following dn.match regular expression is forgiving # enough to match the two Distinguished Name string contents # coming from Apache, Pound or other reverse SSL proxies. if dn = params[Puppet[:ssl_client_header]] and dn_matchdata = dn.match(/^.*?CN\s*=\s*(.*)/) result[:node] = dn_matchdata[1].to_str result[:authenticated] = (params[Puppet[:ssl_client_verify_header]] == 'SUCCESS') else result[:authenticated] = false end return result end end diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb index 972ebc2e2..46735c0f5 100644 --- a/lib/puppet/network/http/webrick.rb +++ b/lib/puppet/network/http/webrick.rb @@ -1,143 +1,135 @@ require 'webrick' require 'webrick/https' require 'puppet/network/http/webrick/rest' require 'puppet/network/xmlrpc/webrick_servlet' require 'thread' require 'puppet/ssl/certificate' require 'puppet/ssl/certificate_revocation_list' class Puppet::Network::HTTP::WEBrick def initialize(args = {}) @listening = false @mutex = Mutex.new end def self.class_for_protocol(protocol) return Puppet::Network::HTTP::WEBrickREST if protocol.to_sym == :rest raise "Unknown protocol [#{protocol}]." 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] @protocols = args[:protocols] - @handlers = args[:handlers] @xmlrpc_handlers = args[:xmlrpc_handlers] arguments = {:BindAddress => args[:address], :Port => args[:port]} arguments.merge!(setup_logger) arguments.merge!(setup_ssl) @server = WEBrick::HTTPServer.new(arguments) setup_handlers @mutex.synchronize do raise "WEBrick server is already listening" if @listening @listening = true @thread = Thread.new { @server.start } end end def unlisten @mutex.synchronize do raise "WEBrick server is not listening" unless @listening @server.shutdown @thread.join @server = nil @listening = false end end def listening? @mutex.synchronize do @listening end end # Configure our http log file. def setup_logger # Make sure the settings are all ready for us. Puppet.settings.use(:main, :ssl, Puppet[:name]) if Puppet[:name] == "puppetmasterd" file = Puppet[:masterhttplog] else file = Puppet[:httplog] end # open the log manually to prevent file descriptor leak file_io = ::File.open(file, "a+") file_io.sync file_io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) args = [file_io] args << WEBrick::Log::DEBUG if Puppet::Util::Log.level == :debug logger = WEBrick::Log.new(*args) return :Logger => logger, :AccessLog => [ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT ], [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT ] ] end # Add all of the ssl cert information. def setup_ssl results = {} # Get the cached copy. We know it's been generated, too. host = Puppet::SSL::Host.localhost raise Puppet::Error, "Could not retrieve certificate for %s and not running on a valid certificate authority" % host.name unless host.certificate results[:SSLPrivateKey] = host.key.content results[:SSLCertificate] = host.certificate.content results[:SSLStartImmediately] = true results[:SSLEnable] = true unless Puppet::SSL::Certificate.find("ca") raise Puppet::Error, "Could not find CA certificate" end results[:SSLCACertificateFile] = Puppet[:localcacert] results[:SSLVerifyClient] = OpenSSL::SSL::VERIFY_PEER results[:SSLCertificateStore] = host.ssl_store results end private def setup_handlers # Set up the new-style protocols. - @protocols.each do |protocol| - next if protocol == :xmlrpc - klass = self.class.class_for_protocol(protocol) - @handlers.each do |handler| - @server.mount('/' + handler.to_s, klass, handler) - @server.mount('/' + handler.to_s + 's', klass, handler) - end - end + klass = self.class.class_for_protocol(:rest) + @server.mount('/', klass, :this_value_is_apparently_necessary_but_unused) # And then set up xmlrpc, if configured. if @protocols.include?(:xmlrpc) and ! @xmlrpc_handlers.empty? @server.mount("/RPC2", xmlrpc_servlet) end end # Create our xmlrpc servlet, which provides backward compatibility. def xmlrpc_servlet handlers = @xmlrpc_handlers.collect { |handler| unless hclass = Puppet::Network::Handler.handler(handler) raise "Invalid xmlrpc handler %s" % handler end hclass.new({}) } Puppet::Network::XMLRPC::WEBrickServlet.new handlers end end diff --git a/lib/puppet/network/http/webrick/rest.rb b/lib/puppet/network/http/webrick/rest.rb index e34f0d2e8..8120c87db 100644 --- a/lib/puppet/network/http/webrick/rest.rb +++ b/lib/puppet/network/http/webrick/rest.rb @@ -1,79 +1,73 @@ require 'puppet/network/http/handler' class Puppet::Network::HTTP::WEBrickREST < WEBrick::HTTPServlet::AbstractServlet include Puppet::Network::HTTP::Handler def initialize(server, handler) raise ArgumentError, "server is required" unless server super(server) initialize_for_puppet(:server => server, :handler => handler) end # Retrieve the request parameters, including authentication information. def params(request) result = request.query result = decode_params(result) result.merge(client_information(request)) end # WEBrick uses a service() method to respond to requests. Simply delegate to the handler response() method. def service(request, response) process(request, response) end def accept_header(request) request["accept"] end def http_method(request) request.request_method end def path(request) - # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] - x = '/' + request.path.split('/')[1] - end - - def request_key(request) - # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] - x = request.path.split('/', 3)[2] + request.path end def body(request) request.body end # Set the specified format as the content type of the response. def set_content_type(response, format) response["content-type"] = format end def set_response(response, result, status = 200) response.status = status if status >= 200 and status < 300 response.body = result else response.reason_phrase = result end end # Retrieve node/cert/ip information from the request object. def client_information(request) result = {} if peer = request.peeraddr and ip = peer[3] result[:ip] = ip end # If they have a certificate (which will almost always be true) # then we get the hostname from the cert, instead of via IP # info result[:authenticated] = false if cert = request.client_cert and nameary = cert.subject.to_a.find { |ary| ary[0] == "CN" } result[:node] = nameary[1] result[:authenticated] = true end result end end diff --git a/spec/integration/indirector/certificate/rest.rb b/spec/integration/indirector/certificate/rest.rb index 5ddb0026a..e1f15d9c9 100755 --- a/spec/integration/indirector/certificate/rest.rb +++ b/spec/integration/indirector/certificate/rest.rb @@ -1,67 +1,67 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/ssl/certificate' require 'puppet/network/server' require 'puppet/network/http/webrick/rest' describe "Certificate REST Terminus" do before do Puppet[:masterport] = 34343 Puppet[:server] = "localhost" # Get a safe temporary file @tmpfile = Tempfile.new("webrick_integration_testing") @dir = @tmpfile.path + "_dir" Puppet.settings[:confdir] = @dir Puppet.settings[:vardir] = @dir Puppet.settings[:server] = "127.0.0.1" Puppet.settings[:masterport] = "34343" Puppet.settings[:http_enable_post_connection_check] = false Puppet::Util::Cacher.expire Puppet[:servertype] = 'webrick' Puppet[:server] = '127.0.0.1' Puppet[:certname] = '127.0.0.1' # Generate the certificate with a local CA Puppet::SSL::Host.ca_location = :local ca = Puppet::SSL::CertificateAuthority.new ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname]) ca.generate("foo.madstop.com") unless Puppet::SSL::Certificate.find(Puppet[:certname]) @host = Puppet::SSL::Host.new(Puppet[:certname]) @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :certificate ] } @server = Puppet::Network::Server.new(@params) @server.listen # Then switch to a remote CA, so that we go through REST. Puppet::SSL::Host.ca_location = :remote # LAK:NOTE We need to have a fake model here so that our indirected methods get # passed through REST; otherwise we'd be stubbing 'find', which would cause an immediate # return. @mock_model = stub('faked model', :name => "certificate") - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end after do Puppet::Network::HttpPool.expire Puppet::SSL::Host.ca_location = :none Puppet.settings.clear @server.unlisten end it "should be able to retrieve a remote certificate" do @mock_model.expects(:find).returns @host.certificate result = Puppet::SSL::Certificate.find('bar') # There's no good '==' method on certs. result.content.to_s.should == @host.certificate.content.to_s result.name.should == @host.certificate.name end end diff --git a/spec/integration/indirector/certificate_request/rest.rb b/spec/integration/indirector/certificate_request/rest.rb index f8ca1ecb6..59d2a5486 100755 --- a/spec/integration/indirector/certificate_request/rest.rb +++ b/spec/integration/indirector/certificate_request/rest.rb @@ -1,87 +1,87 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/ssl/certificate_request' require 'puppet/network/server' require 'puppet/network/http/webrick/rest' describe "Certificate Request REST Terminus" do before do Puppet::Util::Cacher.expire Puppet[:masterport] = 34343 Puppet[:server] = "localhost" # Get a safe temporary file @tmpfile = Tempfile.new("webrick_integration_testing") @dir = @tmpfile.path + "_dir" Puppet.settings[:confdir] = @dir Puppet.settings[:vardir] = @dir Puppet.settings[:server] = "127.0.0.1" Puppet.settings[:masterport] = "34343" Puppet.settings[:http_enable_post_connection_check] = false Puppet[:servertype] = 'webrick' Puppet[:server] = '127.0.0.1' Puppet[:certname] = '127.0.0.1' # Generate the certificate with a local CA Puppet::SSL::Host.ca_location = :local ca = Puppet::SSL::CertificateAuthority.new ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname]) # Create the CSR and write it to disk @host = Puppet::SSL::Host.new("foo.madstop.com") @host.generate_certificate_request # Now remove the cached csr Puppet::SSL::Host.ca_location = :none Puppet::SSL::Host.destroy("foo.madstop.com") @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :certificate_request ] } @server = Puppet::Network::Server.new(@params) @server.listen # Then switch to a remote CA, so that we go through REST. Puppet::SSL::Host.ca_location = :remote # LAK:NOTE We need to have a fake model here so that our indirected methods get # passed through REST; otherwise we'd be stubbing 'find', which would cause an immediate # return. @mock_model = stub('faked model', :name => "certificate request") - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end after do Puppet::Network::HttpPool.expire Puppet::SSL::Host.ca_location = :none Puppet.settings.clear @server.unlisten end it "should be able to save a certificate request to the CA" do key = Puppet::SSL::Key.new("bar.madstop.com") key.generate csr = Puppet::SSL::CertificateRequest.new("bar.madstop.com") csr.generate(key.content) server_csr = mock 'csr' server_csr.expects(:save) @mock_model.expects(:convert_from).with("s", csr.content.to_s).returns server_csr csr.save end it "should be able to retrieve a remote certificate request" do # We're finding the cached value :/ @mock_model.expects(:find).returns @host.certificate_request result = Puppet::SSL::CertificateRequest.find('foo.madstop.com') # There's no good '==' method on certs. result.content.to_s.should == @host.certificate_request.content.to_s result.name.should == @host.certificate_request.name end end diff --git a/spec/integration/indirector/certificate_revocation_list/rest.rb b/spec/integration/indirector/certificate_revocation_list/rest.rb index 8df4ad3e1..5ad2d8c90 100755 --- a/spec/integration/indirector/certificate_revocation_list/rest.rb +++ b/spec/integration/indirector/certificate_revocation_list/rest.rb @@ -1,75 +1,75 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/ssl/certificate' require 'puppet/network/server' require 'puppet/network/http/webrick/rest' describe "Certificate REST Terminus" do before do Puppet[:masterport] = 34343 Puppet[:server] = "localhost" # Get a safe temporary file @tmpfile = Tempfile.new("webrick_integration_testing") @dir = @tmpfile.path + "_dir" Puppet.settings[:confdir] = @dir Puppet.settings[:vardir] = @dir Puppet.settings[:server] = "127.0.0.1" Puppet.settings[:masterport] = "34343" Puppet.settings[:http_enable_post_connection_check] = false Puppet::Util::Cacher.expire Puppet[:servertype] = 'webrick' Puppet[:server] = '127.0.0.1' Puppet[:certname] = '127.0.0.1' # Generate the certificate with a local CA Puppet::SSL::Host.ca_location = :local ca = Puppet::SSL::CertificateAuthority.new ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname]) @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :certificate_revocation_list ] } @server = Puppet::Network::Server.new(@params) @server.listen # And make sure we've generated the CRL @crl = ca.crl # Now remove the cached crl Puppet::SSL::Host.ca_location = :none Puppet::SSL::CertificateRevocationList.destroy("ca") # This is necessary so that we create the SSL store before we start # using REST. This is necessary to prevent an infinite loop, # which only occurs during testing. Puppet::Network::HttpPool.ssl_host.ssl_store # Then switch to a remote CA, so that we go through REST. Puppet::SSL::Host.ca_location = :remote # LAK:NOTE We need to have a fake model here so that our indirected methods get # passed through REST; otherwise we'd be stubbing 'find', which would cause an immediate # return. @mock_model = stub('faked model', :name => "certificate") - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end after do Puppet::Network::HttpPool.expire Puppet::SSL::Host.ca_location = :none Puppet.settings.clear @server.unlisten end it "should be able to retrieve a remote CRL" do @mock_model.expects(:find).returns @crl result = Puppet::SSL::CertificateRevocationList.find('bar') # There's no good '==' method on certs. result.content.to_s.should == @crl.content.to_s end end diff --git a/spec/integration/indirector/report/rest.rb b/spec/integration/indirector/report/rest.rb index 5b3755426..6bbfbee66 100644 --- a/spec/integration/indirector/report/rest.rb +++ b/spec/integration/indirector/report/rest.rb @@ -1,93 +1,93 @@ #!/usr/bin/env ruby Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } require 'puppet/transaction/report' require 'puppet/network/server' require 'puppet/network/http/webrick/rest' describe "Report REST Terminus" do before do Puppet[:masterport] = 34343 Puppet[:server] = "localhost" # Get a safe temporary file @tmpfile = Tempfile.new("webrick_integration_testing") @dir = @tmpfile.path + "_dir" Puppet.settings[:confdir] = @dir Puppet.settings[:vardir] = @dir Puppet.settings[:server] = "127.0.0.1" Puppet.settings[:masterport] = "34343" Puppet.settings[:http_enable_post_connection_check] = false Puppet::Util::Cacher.expire Puppet[:servertype] = 'webrick' Puppet[:server] = '127.0.0.1' Puppet[:certname] = '127.0.0.1' # Generate the certificate with a local CA Puppet::SSL::Host.ca_location = :local ca = Puppet::SSL::CertificateAuthority.new ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname]) ca.generate("foo.madstop.com") unless Puppet::SSL::Certificate.find(Puppet[:certname]) @host = Puppet::SSL::Host.new(Puppet[:certname]) @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :report ] } @server = Puppet::Network::Server.new(@params) @server.listen # Let's use REST for our reports :-) @old_terminus = Puppet::Transaction::Report.indirection.terminus_class Puppet::Transaction::Report.terminus_class = :rest # LAK:NOTE We need to have a fake model here so that our indirected methods get # passed through REST; otherwise we'd be stubbing 'save', which would cause an immediate # return. @report = stub_everything 'report' @mock_model = stub_everything 'faked model', :name => "report", :convert_from => @report - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end after do Puppet::Network::HttpPool.expire Puppet::SSL::Host.ca_location = :none Puppet.settings.clear @server.unlisten Puppet::Transaction::Report.terminus_class = @old_terminus end it "should be able to send a report to the server" do @report.expects(:save) report = Puppet::Transaction::Report.new resourcemetrics = { :total => 12, :out_of_sync => 20, :applied => 45, :skipped => 1, :restarted => 23, :failed_restarts => 1, :scheduled => 10 } report.newmetric(:resources, resourcemetrics) timemetrics = { :resource1 => 10, :resource2 => 50, :resource3 => 40, :resource4 => 20, } report.newmetric(:times, timemetrics) report.newmetric(:changes, :total => 20 ) report.time = Time.now report.save end end diff --git a/spec/integration/indirector/rest.rb b/spec/integration/indirector/rest.rb index cfaaa5027..95fb34bd9 100755 --- a/spec/integration/indirector/rest.rb +++ b/spec/integration/indirector/rest.rb @@ -1,520 +1,520 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/network/server' require 'puppet/indirector' require 'puppet/indirector/rest' # a fake class that will be indirected via REST class Puppet::TestIndirectedFoo extend Puppet::Indirector indirects :test_indirected_foo, :terminus_setting => :test_indirected_foo_terminus attr_reader :value def initialize(value = 0) @value = value end def self.from_yaml(yaml) YAML.load(yaml) end def name "bob" end end # empty Terminus class -- this would normally have to be in a directory findable by the autoloader, but we short-circuit that below class Puppet::TestIndirectedFoo::Rest < Puppet::Indirector::REST end describe Puppet::Indirector::REST do before do Puppet[:masterport] = 34343 Puppet[:server] = "localhost" # Get a safe temporary file @tmpfile = Tempfile.new("webrick_integration_testing") @dir = @tmpfile.path + "_dir" Puppet.settings[:confdir] = @dir Puppet.settings[:vardir] = @dir Puppet.settings[:server] = "127.0.0.1" Puppet.settings[:masterport] = "34343" Puppet.settings[:http_enable_post_connection_check] = false Puppet::SSL::Host.ca_location = :local Puppet::TestIndirectedFoo.terminus_class = :rest end after do Puppet::Network::HttpPool.expire Puppet::SSL::Host.ca_location = :none Puppet.settings.clear end describe "when using webrick" do before :each do Puppet::Util::Cacher.expire Puppet[:servertype] = 'webrick' Puppet[:server] = '127.0.0.1' Puppet[:certname] = '127.0.0.1' ca = Puppet::SSL::CertificateAuthority.new ca.generate(Puppet[:certname]) unless Puppet::SSL::Certificate.find(Puppet[:certname]) @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :test_indirected_foo ], :xmlrpc_handlers => [ :status ] } @server = Puppet::Network::Server.new(@params) @server.listen # LAK:NOTE We need to have a fake model here so that our indirected methods get # passed through REST; otherwise we'd be stubbing 'find', which would cause an immediate # return. @mock_model = stub('faked model', :name => "foo") - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end describe "when finding a model instance over REST" do describe "when a matching model instance can be found" do before :each do @model_instance = Puppet::TestIndirectedFoo.new(23) @mock_model.stubs(:find).returns @model_instance end it "should not fail" do lambda { Puppet::TestIndirectedFoo.find('bar') }.should_not raise_error end it 'should return an instance of the model class' do Puppet::TestIndirectedFoo.find('bar').class.should == Puppet::TestIndirectedFoo end it "should pass options all the way through" do @mock_model.expects(:find).with { |key, args| args[:one] == "two" and args[:three] == "four" }.returns @model_instance Puppet::TestIndirectedFoo.find('bar', :one => "two", :three => "four") end it 'should return the instance of the model class associated with the provided lookup key' do Puppet::TestIndirectedFoo.find('bar').value.should == @model_instance.value end it 'should set an expiration on model instance' do Puppet::TestIndirectedFoo.find('bar').expiration.should_not be_nil end it "should use a supported format" do Puppet::TestIndirectedFoo.expects(:supported_formats).returns ["marshal"] text = Marshal.dump(@model_instance) @model_instance.expects(:render).with("marshal").returns text Puppet::TestIndirectedFoo.find('bar') end end describe "when no matching model instance can be found" do before :each do @mock_model = stub('faked model', :name => "foo", :find => nil) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end it "should return nil" do Puppet::TestIndirectedFoo.find('bar').should be_nil end end describe "when an exception is encountered in looking up a model instance" do before :each do @mock_model = stub('faked model', :name => "foo") @mock_model.stubs(:find).raises(RuntimeError) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end it "should raise an exception" do lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(Net::HTTPError) end end end describe "when searching for model instances over REST" do describe "when matching model instances can be found" do before :each do @model_instances = [ Puppet::TestIndirectedFoo.new(23), Puppet::TestIndirectedFoo.new(24) ] @mock_model.stubs(:search).returns @model_instances # Force yaml, because otherwise our mocks can't work correctly Puppet::TestIndirectedFoo.stubs(:supported_formats).returns %w{yaml} @mock_model.stubs(:render_multiple).returns @model_instances.to_yaml end it "should not fail" do lambda { Puppet::TestIndirectedFoo.search('bar') }.should_not raise_error end it 'should return all matching results' do Puppet::TestIndirectedFoo.search('bar').length.should == @model_instances.length end it "should pass options all the way through" do @mock_model.expects(:search).with { |key, args| args[:one] == "two" and args[:three] == "four" }.returns @model_instances Puppet::TestIndirectedFoo.search("foo", :one => "two", :three => "four") end it 'should return model instances' do Puppet::TestIndirectedFoo.search('bar').each do |result| result.class.should == Puppet::TestIndirectedFoo end end it 'should return the instance of the model class associated with the provided lookup key' do Puppet::TestIndirectedFoo.search('bar').collect { |i| i.value }.should == @model_instances.collect { |i| i.value } end end describe "when no matching model instance can be found" do before :each do @mock_model = stub('faked model', :name => "foo", :find => nil) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end it "should return nil" do Puppet::TestIndirectedFoo.find('bar').should be_nil end end describe "when an exception is encountered in looking up a model instance" do before :each do @mock_model = stub('faked model') @mock_model.stubs(:find).raises(RuntimeError) - Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end it "should raise an exception" do lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(Net::HTTPError) end end end describe "when destroying a model instance over REST" do describe "when a matching model instance can be found" do before :each do @mock_model.stubs(:destroy).returns true end it "should not fail" do lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should_not raise_error end it 'should return success' do Puppet::TestIndirectedFoo.destroy('bar').should == true end end describe "when no matching model instance can be found" do before :each do @mock_model.stubs(:destroy).returns false end it "should return failure" do Puppet::TestIndirectedFoo.destroy('bar').should == false end end describe "when an exception is encountered in destroying a model instance" do before :each do @mock_model.stubs(:destroy).raises(RuntimeError) end it "should raise an exception" do lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(Net::HTTPError) end end end describe "when saving a model instance over REST" do before :each do @instance = Puppet::TestIndirectedFoo.new(42) @mock_model.stubs(:save_object).returns @instance @mock_model.stubs(:convert_from).returns @instance Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).returns(@instance) end describe "when a successful save can be performed" do before :each do end it "should not fail" do lambda { @instance.save }.should_not raise_error end it 'should return an instance of the model class' do @instance.save.class.should == Puppet::TestIndirectedFoo end it 'should return a matching instance of the model class' do @instance.save.value.should == @instance.value end end describe "when a save cannot be completed" do before :each do Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).returns(false) end it "should return failure" do @instance.save.should == false end end describe "when an exception is encountered in performing a save" do before :each do Puppet::Network::HTTP::WEBrickREST.any_instance.stubs(:save_object).raises(RuntimeError) end it "should raise an exception" do lambda { @instance.save }.should raise_error(Net::HTTPError) end end end after :each do @server.unlisten end end describe "when using mongrel" do confine "Mongrel is not available" => Puppet.features.mongrel? before :each do Puppet[:servertype] = 'mongrel' @params = { :address => "127.0.0.1", :port => 34343, :handlers => [ :test_indirected_foo ] } # Make sure we never get a cert, since mongrel can't speak ssl Puppet::SSL::Certificate.stubs(:find).returns nil # We stub ssl to be off, since mongrel can't speak ssl Net::HTTP.any_instance.stubs(:use_ssl?).returns false @server = Puppet::Network::Server.new(@params) @server.listen # LAK:NOTE We need to have a fake model here so that our indirected methods get # passed through REST; otherwise we'd be stubbing 'find', which would cause an immediate # return. @mock_model = stub('faked model', :name => "foo") - Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:model).returns(@mock_model) + Puppet::Indirector::Request.any_instance.stubs(:model).returns(@mock_model) end after do @server.unlisten end describe "when finding a model instance over REST" do describe "when a matching model instance can be found" do before :each do @model_instance = Puppet::TestIndirectedFoo.new(23) @mock_model.stubs(:find).returns @model_instance end it "should not fail" do lambda { Puppet::TestIndirectedFoo.find('bar') }.should_not raise_error end it 'should return an instance of the model class' do Puppet::TestIndirectedFoo.find('bar').class.should == Puppet::TestIndirectedFoo end it "should pass options all the way through" do @mock_model.expects(:find).with { |key, args| args[:one] == "two" and args[:three] == "four" }.returns @model_instance Puppet::TestIndirectedFoo.find('bar', :one => "two", :three => "four") end it 'should return the instance of the model class associated with the provided lookup key' do Puppet::TestIndirectedFoo.find('bar').value.should == @model_instance.value end it 'should set an expiration on model instance' do Puppet::TestIndirectedFoo.find('bar').expiration.should_not be_nil end it "should use a supported format" do Puppet::TestIndirectedFoo.expects(:supported_formats).returns ["marshal"] text = Marshal.dump(@model_instance) @model_instance.expects(:render).with("marshal").returns text Puppet::TestIndirectedFoo.find('bar') end end describe "when no matching model instance can be found" do before :each do @mock_model.stubs(:find).returns nil end it "should return nil" do Puppet::TestIndirectedFoo.find('bar').should be_nil end end describe "when an exception is encountered in looking up a model instance" do before :each do @mock_model.stubs(:find).raises(RuntimeError) end it "should raise an exception" do lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(Net::HTTPError) end end end describe "when searching for model instances over REST" do describe "when matching model instances can be found" do before :each do @model_instances = [ Puppet::TestIndirectedFoo.new(23), Puppet::TestIndirectedFoo.new(24) ] # Force yaml, because otherwise our mocks can't work correctly Puppet::TestIndirectedFoo.stubs(:supported_formats).returns %w{yaml} @mock_model.stubs(:search).returns @model_instances @mock_model.stubs(:render_multiple).returns @model_instances.to_yaml end it "should not fail" do lambda { Puppet::TestIndirectedFoo.search('bar') }.should_not raise_error end it 'should return all matching results' do Puppet::TestIndirectedFoo.search('bar').length.should == @model_instances.length end it "should pass options all the way through" do @mock_model.expects(:search).with { |key, args| args[:one] == "two" and args[:three] == "four" }.returns @model_instances Puppet::TestIndirectedFoo.search('bar', :one => "two", :three => "four") end it 'should return model instances' do Puppet::TestIndirectedFoo.search('bar').each do |result| result.class.should == Puppet::TestIndirectedFoo end end it 'should return the instance of the model class associated with the provided lookup key' do Puppet::TestIndirectedFoo.search('bar').collect { |i| i.value }.should == @model_instances.collect { |i| i.value } end it 'should set an expiration on model instances' do Puppet::TestIndirectedFoo.search('bar').each do |result| result.expiration.should_not be_nil end end end describe "when no matching model instance can be found" do before :each do @mock_model.stubs(:search).returns nil @mock_model.stubs(:render_multiple).returns nil.to_yaml end it "should return nil" do Puppet::TestIndirectedFoo.search('bar').should == [] end end describe "when an exception is encountered in looking up a model instance" do before :each do @mock_model.stubs(:find).raises(RuntimeError) end it "should raise an exception" do lambda { Puppet::TestIndirectedFoo.find('bar') }.should raise_error(Net::HTTPError) end end end describe "when destroying a model instance over REST" do describe "when a matching model instance can be found" do before :each do @mock_model.stubs(:destroy).returns true end it "should not fail" do lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should_not raise_error end it 'should return success' do Puppet::TestIndirectedFoo.destroy('bar').should == true end end describe "when no matching model instance can be found" do before :each do @mock_model.stubs(:destroy).returns false end it "should return failure" do Puppet::TestIndirectedFoo.destroy('bar').should == false end end describe "when an exception is encountered in destroying a model instance" do before :each do @mock_model.stubs(:destroy).raises(RuntimeError) end it "should raise an exception" do lambda { Puppet::TestIndirectedFoo.destroy('bar') }.should raise_error(Net::HTTPError) end end end describe "when saving a model instance over REST" do before :each do @instance = Puppet::TestIndirectedFoo.new(42) @mock_model.stubs(:convert_from).returns @instance # LAK:NOTE This stub is necessary to prevent the REST call from calling # REST.save again, thus producing painful infinite recursion. Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).returns(@instance) end describe "when a successful save can be performed" do before :each do end it "should not fail" do lambda { @instance.save }.should_not raise_error end it 'should return an instance of the model class' do @instance.save.class.should == Puppet::TestIndirectedFoo end it 'should return a matching instance of the model class' do @instance.save.value.should == @instance.value end end describe "when a save cannot be completed" do before :each do Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).returns(false) end it "should return failure" do @instance.save.should == false end end describe "when an exception is encountered in performing a save" do before :each do Puppet::Network::HTTP::MongrelREST.any_instance.stubs(:save_object).raises(RuntimeError) end it "should raise an exception" do lambda { @instance.save }.should raise_error(Net::HTTPError) end end end end end diff --git a/spec/unit/application/puppetd.rb b/spec/unit/application/puppetd.rb index 592b499ca..d93a52761 100755 --- a/spec/unit/application/puppetd.rb +++ b/spec/unit/application/puppetd.rb @@ -1,486 +1,480 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/application/puppetd' require 'puppet/network/server' describe "puppetd" do before :each do @puppetd = Puppet::Application[:puppetd] @puppetd.stubs(:puts) @daemon = stub_everything 'daemon' Puppet::Daemon.stubs(:new).returns(@daemon) @agent = stub_everything 'agent' Puppet::Agent.stubs(:new).returns(@agent) @puppetd.run_preinit Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) Puppet::Node::Facts.stubs(:terminus_class=) Puppet::Node::Facts.stubs(:cache_class=) end it "should ask Puppet::Application to parse Puppet configuration file" do @puppetd.should_parse_config?.should be_true end it "should declare a main command" do @puppetd.should respond_to(:main) end it "should declare a onetime command" do @puppetd.should respond_to(:onetime) end it "should declare a preinit block" do @puppetd.should respond_to(:run_preinit) end describe "in preinit" do before :each do @pupetd.stubs(:trap) end it "should catch INT" do @puppetd.expects(:trap).with { |arg,block| arg == :INT } @puppetd.run_preinit end it "should set waitforcert to 120" do @puppetd.run_preinit @puppetd.options[:waitforcert].should == 120 end it "should init client to true" do @puppetd.run_preinit @puppetd.options[:client].should be_true end it "should init fqdn to nil" do @puppetd.run_preinit @puppetd.options[:fqdn].should be_nil end it "should init serve to []" do @puppetd.run_preinit @puppetd.options[:serve].should == [] end end describe "when handling options" do [:centrallogging, :disable, :enable, :debug, :fqdn, :test, :verbose].each do |option| it "should declare handle_#{option} method" do @puppetd.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @puppetd.options.expects(:[]=).with(option, 'arg') @puppetd.send("handle_#{option}".to_sym, 'arg') end end it "should set an existing handler on server" do Puppet::Network::Handler.stubs(:handler).with("handler").returns(true) @puppetd.handle_serve("handler") @puppetd.options[:serve].should == [ :handler ] end it "should set client to false with --no-client" do @puppetd.handle_no_client(nil) @puppetd.options[:client].should be_false end it "should set onetime to ture with --onetime" do @puppetd.handle_onetime(nil) @puppetd.options[:onetime].should be_true end it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do @puppetd.explicit_waitforcert = false @puppetd.handle_onetime(nil) @puppetd.options[:waitforcert].should == 0 end it "should not reset waitforcert with --onetime when --waitforcert is used" do @puppetd.explicit_waitforcert = true @puppetd.handle_onetime(nil) @puppetd.options[:waitforcert].should_not == 0 end it "should set the log destination with --logdest" do @puppetd.options.stubs(:[]=).with { |opt,val| opt == :setdest } Puppet::Log.expects(:newdestination).with("console") @puppetd.handle_logdest("console") end it "should put the setdest options to true" do @puppetd.options.expects(:[]=).with(:setdest,true) @puppetd.handle_logdest("console") end it "should store the waitforcert options with --waitforcert" do @puppetd.options.expects(:[]=).with(:waitforcert,42) @puppetd.handle_waitforcert("42") end it "should mark explicit_waitforcert to true with --waitforcert" do @puppetd.options.stubs(:[]=) @puppetd.handle_waitforcert("42") @puppetd.explicit_waitforcert.should be_true end it "should set args[:Port] with --port" do @puppetd.handle_port("42") @puppetd.args[:Port].should == "42" end end describe "during setup" do before :each do @puppetd.options.stubs(:[]) Puppet.stubs(:info) FileTest.stubs(:exists?).returns(true) Puppet.stubs(:[]) Puppet.settings.stubs(:print_config?) Puppet.settings.stubs(:print_config) Puppet::SSL::Host.stubs(:ca_location=) Puppet::Transaction::Report.stubs(:terminus_class=) Puppet::Resource::Catalog.stubs(:terminus_class=) Puppet::Resource::Catalog.stubs(:cache_class=) Puppet::Node::Facts.stubs(:terminus_class=) Puppet::Node::Facts.stubs(:cache_class=) @host = stub_everything 'host' Puppet::SSL::Host.stubs(:new).returns(@host) Puppet.stubs(:settraps) end describe "with --test" do before :each do Puppet.settings.stubs(:handlearg) @puppetd.options.stubs(:[]=) end it "should call setup_test" do @puppetd.options.stubs(:[]).with(:test).returns(true) @puppetd.expects(:setup_test) @puppetd.run_setup end it "should set options[:verbose] to true" do @puppetd.options.expects(:[]=).with(:verbose,true) @puppetd.setup_test end it "should set options[:onetime] to true" do @puppetd.options.expects(:[]=).with(:onetime,true) @puppetd.setup_test end it "should set waitforcert to 0" do @puppetd.options.expects(:[]=).with(:waitforcert,0) @puppetd.setup_test end end it "should call setup_logs" do @puppetd.expects(:setup_logs) @puppetd.run_setup end describe "when setting up logs" do before :each do Puppet::Util::Log.stubs(:newdestination) end it "should set log level to debug if --debug was passed" do @puppetd.options.stubs(:[]).with(:debug).returns(true) Puppet::Util::Log.expects(:level=).with(:debug) @puppetd.setup_logs end it "should set log level to info if --verbose was passed" do @puppetd.options.stubs(:[]).with(:verbose).returns(true) Puppet::Util::Log.expects(:level=).with(:info) @puppetd.setup_logs end [:verbose, :debug].each do |level| it "should set console as the log destination with level #{level}" do @puppetd.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.expects(:newdestination).with(:console) @puppetd.setup_logs end end it "should set syslog as the log destination if no --logdest" do @puppetd.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:newdestination).with(:syslog) @puppetd.setup_logs end end it "should print puppet config if asked to in Puppet config" do @puppetd.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs) @puppetd.run_setup end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) lambda { @puppetd.run_setup }.should raise_error(SystemExit) end it "should set a central log destination with --centrallogs" do @puppetd.options.stubs(:[]).with(:centrallogs).returns(true) Puppet.stubs(:[]).with(:server).returns("puppet.reductivelabs.com") Puppet::Util::Log.stubs(:newdestination).with(:syslog) Puppet::Util::Log.expects(:newdestination).with("puppet.reductivelabs.com") @puppetd.run_setup end it "should install a remote ca location" do Puppet::SSL::Host.expects(:ca_location=).with(:remote) @puppetd.run_setup end it "should tell the report handler to use REST" do Puppet::Transaction::Report.expects(:terminus_class=).with(:rest) @puppetd.run_setup end it "should tell the catalog handler to use REST" do Puppet::Resource::Catalog.expects(:terminus_class=).with(:rest) @puppetd.run_setup end it "should tell the catalog handler to use cache" do Puppet::Resource::Catalog.expects(:cache_class=).with(:yaml) @puppetd.run_setup end it "should tell the facts to use facter" do Puppet::Node::Facts.expects(:terminus_class=).with(:facter) @puppetd.run_setup end it "should tell the facts cache facts through REST" do Puppet::Node::Facts.expects(:cache_class=).with(:rest) @puppetd.run_setup end it "should create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer) @puppetd.run_setup end [:enable, :disable].each do |action| it "should delegate to enable_disable_client if we #{action} the agent" do @puppetd.options.stubs(:[]).with(action).returns(true) @puppetd.expects(:enable_disable_client).with(@agent) @puppetd.run_setup end end describe "when enabling or disabling agent" do [:enable, :disable].each do |action| it "should call client.#{action}" do @puppetd.stubs(:exit) @puppetd.options.stubs(:[]).with(action).returns(true) @agent.expects(action) @puppetd.enable_disable_client(@agent) end end it "should finally exit" do lambda { @puppetd.enable_disable_client(@agent) }.should raise_error(SystemExit) end end it "should inform the daemon about our agent" do @daemon.expects(:agent=).with(@agent) @puppetd.run_setup end it "should daemonize if needed" do Puppet.stubs(:[]).with(:daemonize).returns(true) @daemon.expects(:daemonize) @puppetd.run_setup end it "should wait for a certificate" do @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) @host.expects(:wait_for_cert).with(123) @puppetd.run_setup end it "should setup listen if told to and not onetime" do Puppet.stubs(:[]).with(:listen).returns(true) @puppetd.options.stubs(:[]).with(:onetime).returns(false) @puppetd.expects(:setup_listen) @puppetd.run_setup end describe "when setting up listen" do before :each do Puppet.stubs(:[]).with(:authconfig).returns('auth') FileTest.stubs(:exists?).with('auth').returns(true) File.stubs(:exist?).returns(true) @puppetd.options.stubs(:[]).with(:serve).returns([]) @puppetd.stubs(:exit) @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) end it "should exit if no authorization file" do Puppet.stubs(:err) FileTest.stubs(:exists?).with('auth').returns(false) @puppetd.expects(:exit) @puppetd.setup_listen end it "should create a server to listen on at least the Runner handler" do Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Runner] } @puppetd.setup_listen end it "should create a server to listen for specific handlers" do @puppetd.options.stubs(:[]).with(:serve).returns([:handler]) Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:handler] } @puppetd.setup_listen end - it "should create a server with facts REST handler" do - Puppet::Network::Server.expects(:new).with { |args| args[:handlers] == [:facts] } - - @puppetd.setup_listen - end - it "should use puppet default port" do Puppet.stubs(:[]).with(:puppetport).returns(:port) Puppet::Network::Server.expects(:new).with { |args| args[:port] == :port } @puppetd.setup_listen end end end describe "when running" do before :each do @puppetd.agent = @agent @puppetd.daemon = @daemon end it "should dispatch to onetime if --onetime is used" do @puppetd.options.stubs(:[]).with(:onetime).returns(true) @puppetd.get_command.should == :onetime end it "should dispatch to main if --onetime is not used" do @puppetd.options.stubs(:[]).with(:onetime).returns(false) @puppetd.get_command.should == :main end describe "with --onetime" do before :each do @puppetd.options.stubs(:[]).with(:client).returns(:client) @puppetd.stubs(:exit).with(0) Puppet.stubs(:newservice) end it "should exit if no defined --client" do $stderr.stubs(:puts) @puppetd.options.stubs(:[]).with(:client).returns(nil) @puppetd.expects(:exit).with(43) @puppetd.onetime end it "should setup traps" do @daemon.expects(:set_signal_traps) @puppetd.onetime end it "should let the agent run" do @agent.expects(:run) @puppetd.onetime end it "should finish by exiting with 0 error code" do @puppetd.expects(:exit).with(0) @puppetd.onetime end end describe "without --onetime" do before :each do Puppet.stubs(:notice) @puppetd.options.stubs(:[]).with(:client) end it "should start our daemon" do @daemon.expects(:start) @puppetd.main end end end end diff --git a/spec/unit/application/puppetmasterd.rb b/spec/unit/application/puppetmasterd.rb index da1b1dd1c..5b193ebbf 100644 --- a/spec/unit/application/puppetmasterd.rb +++ b/spec/unit/application/puppetmasterd.rb @@ -1,337 +1,330 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/application/puppetmasterd' describe "PuppetMaster" do before :each do @puppetmasterd = Puppet::Application[:puppetmasterd] @daemon = stub_everything 'daemon' Puppet::Daemon.stubs(:new).returns(@daemon) Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) Puppet::Node::Facts.stubs(:terminus_class=) Puppet::Node::Facts.stubs(:cache_class=) Puppet::Transaction::Report.stubs(:terminus_class=) Puppet::Resource::Catalog.stubs(:terminus_class=) end it "should ask Puppet::Application to parse Puppet configuration file" do @puppetmasterd.should_parse_config?.should be_true end it "should declare a main command" do @puppetmasterd.should respond_to(:main) end it "should declare a parseonly command" do @puppetmasterd.should respond_to(:parseonly) end it "should declare a preinit block" do @puppetmasterd.should respond_to(:run_preinit) end describe "during preinit" do before :each do @puppetmasterd.stubs(:trap) end it "should catch INT" do @puppetmasterd.stubs(:trap).with { |arg,block| arg == :INT } @puppetmasterd.run_preinit end it "should create a Puppet Daemon" do Puppet::Daemon.expects(:new).returns(@daemon) @puppetmasterd.run_preinit end it "should give ARGV to the Daemon" do argv = stub 'argv' ARGV.stubs(:dup).returns(argv) @daemon.expects(:argv=).with(argv) @puppetmasterd.run_preinit end end [:debug,:verbose].each do |option| it "should declare handle_#{option} method" do @puppetmasterd.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @puppetmasterd.options.expects(:[]=).with(option, 'arg') @puppetmasterd.send("handle_#{option}".to_sym, 'arg') end end describe "when applying options" do it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @puppetmasterd.handle_logdest("console") end it "should put the setdest options to true" do @puppetmasterd.options.expects(:[]=).with(:setdest,true) @puppetmasterd.handle_logdest("console") end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet.stubs(:settraps) Puppet::Log.stubs(:level=) Puppet::SSL::CertificateAuthority.stubs(:instance) Puppet::SSL::CertificateAuthority.stubs(:ca?) Puppet.settings.stubs(:use) @puppetmasterd.options.stubs(:[]).with(any_parameters) end it "should set log level to debug if --debug was passed" do @puppetmasterd.options.stubs(:[]).with(:debug).returns(true) Puppet::Log.expects(:level=).with(:debug) @puppetmasterd.run_setup end it "should set log level to info if --verbose was passed" do @puppetmasterd.options.stubs(:[]).with(:verbose).returns(true) Puppet::Log.expects(:level=).with(:info) @puppetmasterd.run_setup end it "should set console as the log destination if no --logdest and --daemonize" do @puppetmasterd.stubs(:[]).with(:daemonize).returns(:false) Puppet::Log.expects(:newdestination).with(:syslog) @puppetmasterd.run_setup end it "should set syslog as the log destination if no --logdest and not --daemonize" do Puppet::Log.expects(:newdestination).with(:syslog) @puppetmasterd.run_setup end it "should print puppet config if asked to in Puppet config" do @puppetmasterd.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs) @puppetmasterd.run_setup end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) lambda { @puppetmasterd.run_setup }.should raise_error(SystemExit) end it "should tell Puppet.settings to use :main,:ssl and :puppetmasterd category" do Puppet.settings.expects(:use).with(:main,:puppetmasterd,:ssl) @puppetmasterd.run_setup end it "should set node facst terminus to yaml" do Puppet::Node::Facts.expects(:terminus_class=).with(:yaml) @puppetmasterd.run_setup end it "should cache class in yaml" do Puppet::Node.expects(:cache_class=).with(:yaml) @puppetmasterd.run_setup end describe "with no ca" do it "should set the ca_location to none" do Puppet::SSL::Host.expects(:ca_location=).with(:none) @puppetmasterd.run_setup end end describe "with a ca configured" do before :each do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) end it "should set the ca_location to local" do Puppet::SSL::Host.expects(:ca_location=).with(:local) @puppetmasterd.run_setup end it "should tell Puppet.settings to use :ca category" do Puppet.settings.expects(:use).with(:ca) @puppetmasterd.run_setup end it "should instantiate the CertificateAuthority singleton" do Puppet::SSL::CertificateAuthority.expects(:instance) @puppetmasterd.run_setup end end end describe "when running" do it "should dispatch to parseonly if parseonly is set" do Puppet.stubs(:[]).with(:parseonly).returns(true) @puppetmasterd.get_command.should == :parseonly end it "should dispatch to main if parseonly is not set" do Puppet.stubs(:[]).with(:parseonly).returns(false) @puppetmasterd.get_command.should == :main end describe "the parseonly command" do before :each do Puppet.stubs(:[]).with(:environment) Puppet.stubs(:[]).with(:manifest).returns("site.pp") @interpreter = stub_everything Puppet.stubs(:err) @puppetmasterd.stubs(:exit) Puppet::Parser::Interpreter.stubs(:new).returns(@interpreter) end it "should delegate to the Puppet Parser" do @interpreter.expects(:parser) @puppetmasterd.parseonly end it "should exit with exit code 0 if no error" do @puppetmasterd.expects(:exit).with(0) @puppetmasterd.parseonly end it "should exit with exit code 1 if error" do @interpreter.stubs(:parser).raises(Puppet::ParseError) @puppetmasterd.expects(:exit).with(1) @puppetmasterd.parseonly end end describe "the main command" do before :each do @puppetmasterd.run_preinit @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) Puppet::SSL::Host.stubs(:localhost) Puppet::SSL::CertificateAuthority.stubs(:ca?) Process.stubs(:uid).returns(1000) Puppet.stubs(:service) Puppet.stubs(:[]) Puppet.stubs(:notice) Puppet.stubs(:start) end it "should create a Server" do Puppet::Network::Server.expects(:new) @puppetmasterd.main end it "should give the server to the daemon" do @daemon.expects(:server=).with(@server) @puppetmasterd.main end it "should create the server with the right XMLRPC handlers" do Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Status, :FileServer, :Master, :Report, :Filebucket]} @puppetmasterd.main end it "should create the server with a :ca xmlrpc handler if needed" do Puppet.stubs(:[]).with(:ca).returns(true) Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers].include?(:CA) } @puppetmasterd.main end - it "should create the server with the right REST handlers" do - Puppet::Indirector::Indirection.stubs(:instances).returns("handlers") - Puppet::Network::Server.expects(:new).with { |args| args[:handlers] == "handlers"} - - @puppetmasterd.main - end - it "should generate a SSL cert for localhost" do Puppet::SSL::Host.expects(:localhost) @puppetmasterd.main end it "should make sure to *only* hit the CA for data" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:only) @puppetmasterd.main end it "should drop privileges if running as root" do Process.stubs(:uid).returns(0) Puppet::Util.expects(:chuser) @puppetmasterd.main end it "should daemonize if needed" do Puppet.stubs(:[]).with(:daemonize).returns(true) @daemon.expects(:daemonize) @puppetmasterd.main end it "should start the service" do @daemon.expects(:start) @puppetmasterd.main end end end end diff --git a/spec/unit/network/http/handler.rb b/spec/unit/network/http/handler.rb index 85149b642..84b87025f 100755 --- a/spec/unit/network/http/handler.rb +++ b/spec/unit/network/http/handler.rb @@ -1,359 +1,365 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/network/http/handler' class HttpHandled include Puppet::Network::HTTP::Handler end describe Puppet::Network::HTTP::Handler do before do @handler = HttpHandled.new end it "should include the v1 REST API" do Puppet::Network::HTTP::Handler.ancestors.should be_include(Puppet::Network::HTTP::API::V1) end it "should have a method for initializing" do @handler.should respond_to(:initialize_for_puppet) end describe "when initializing" do - before do - Puppet::Indirector::Indirection.stubs(:model).returns "eh" - end - it "should fail when no server type has been provided" do - lambda { @handler.initialize_for_puppet :handler => "foo" }.should raise_error(ArgumentError) + lambda { @handler.initialize_for_puppet }.should raise_error(ArgumentError) end - it "should fail when no handler has been provided" do - lambda { @handler.initialize_for_puppet :server => "foo" }.should raise_error(ArgumentError) - end - - it "should set the handler and server type" do - @handler.initialize_for_puppet :server => "foo", :handler => "bar" + it "should set server type" do + @handler.initialize_for_puppet("foo") @handler.server.should == "foo" - @handler.handler.should == "bar" - end - - it "should use the indirector to find the appropriate model" do - Puppet::Indirector::Indirection.expects(:model).with("bar").returns "mymodel" - @handler.initialize_for_puppet :server => "foo", :handler => "bar" - @handler.model.should == "mymodel" end end it "should be able to process requests" do @handler.should respond_to(:process) end describe "when processing a request" do before do @request = stub('http request') @request.stubs(:[]).returns "foo" @response = stub('http response') @model_class = stub('indirected model class') @result = stub 'result', :render => "mytext" - @handler.stubs(:model).returns @model_class - @handler.stubs(:handler).returns :my_handler - stub_server_interface end # Stub out the interface we require our including classes to # implement. def stub_server_interface @handler.stubs(:accept_header ).returns "format_one,format_two" @handler.stubs(:set_content_type).returns "my_result" @handler.stubs(:set_response ).returns "my_result" @handler.stubs(:path ).returns "/my_handler/my_result" @handler.stubs(:http_method ).returns("GET") @handler.stubs(:params ).returns({}) @handler.stubs(:content_type ).returns("text/plain") end it "should create an indirection request from the path, parameters, and http method" do @handler.expects(:path).with(@request).returns "mypath" @handler.expects(:http_method).with(@request).returns "mymethod" @handler.expects(:params).with(@request).returns "myparams" - @handler.expects(:uri2indirection).with("mypath", "myparams", "mymethod").returns stub("request", :method => :find) + @handler.expects(:uri2indirection).with("mymethod", "mypath", "myparams").returns stub("request", :method => :find) @handler.stubs(:do_find) @handler.process(@request, @response) end it "should call the 'do' method associated with the indirection method" do request = stub 'request' @handler.expects(:uri2indirection).returns request request.expects(:method).returns "mymethod" @handler.expects(:do_mymethod).with(request, @request, @response) @handler.process(@request, @response) end it "should serialize a controller exception when an exception is thrown while finding the model instance" do @handler.expects(:uri2indirection).returns stub("request", :method => :find) @handler.expects(:do_find).raises(ArgumentError, "The exception") @handler.expects(:set_response).with { |response, body, status| body == "The exception" and status == 400 } @handler.process(@request, @response) end it "should set the format to text/plain when serializing an exception" do @handler.expects(:set_content_type).with(@response, "text/plain") @handler.do_exception(@response, "A test", 404) end describe "when finding a model instance" do before do - @irequest = stub 'indirection_request', :method => :find, :indirection_name => "my_handler", :options => {}, :key => "my_result" + @irequest = stub 'indirection_request', :method => :find, :indirection_name => "my_handler", :to_hash => {}, :key => "my_result", :model => @model_class @model_class.stubs(:find).returns @result @format = stub 'format', :suitable? => true Puppet::Network::FormatHandler.stubs(:format).returns @format end + it "should use the indirection request to find the model class" do + @irequest.expects(:model).returns @model_class + + @handler.do_find(@irequest, @request, @response) + end + it "should use the escaped request key" do @model_class.expects(:find).with do |key, args| key == "my_result" end.returns @result @handler.do_find(@irequest, @request, @response) end it "should use a common method for determining the request parameters" do - @irequest.stubs(:options).returns(:foo => :baz, :bar => :xyzzy) + @irequest.stubs(:to_hash).returns(:foo => :baz, :bar => :xyzzy) @model_class.expects(:find).with do |key, args| args[:foo] == :baz and args[:bar] == :xyzzy end.returns @result @handler.do_find(@irequest, @request, @response) end it "should set the content type to the first format specified in the accept header" do @handler.expects(:accept_header).with(@request).returns "one,two" @handler.expects(:set_content_type).with(@response, "one") @handler.do_find(@irequest, @request, @response) end it "should fail if no accept header is provided" do @handler.expects(:accept_header).with(@request).returns nil lambda { @handler.do_find(@irequest, @request, @response) }.should raise_error(ArgumentError) end it "should fail if the accept header does not contain a valid format" do @handler.expects(:accept_header).with(@request).returns "" lambda { @handler.do_find(@irequest, @request, @response) }.should raise_error(RuntimeError) end it "should not use an unsuitable format" do @handler.expects(:accept_header).with(@request).returns "foo,bar" foo = mock 'foo', :suitable? => false bar = mock 'bar', :suitable? => true Puppet::Network::FormatHandler.expects(:format).with("foo").returns foo Puppet::Network::FormatHandler.expects(:format).with("bar").returns bar @handler.expects(:set_content_type).with(@response, "bar") # the suitable one @handler.do_find(@irequest, @request, @response) end it "should render the result using the first format specified in the accept header" do @handler.expects(:accept_header).with(@request).returns "one,two" @result.expects(:render).with("one") @handler.do_find(@irequest, @request, @response) end it "should use the default status when a model find call succeeds" do @handler.expects(:set_response).with { |response, body, status| status.nil? } @handler.do_find(@irequest, @request, @response) end it "should return a serialized object when a model find call succeeds" do @model_instance = stub('model instance') @model_instance.expects(:render).returns "my_rendered_object" @handler.expects(:set_response).with { |response, body, status| body == "my_rendered_object" } @model_class.stubs(:find).returns(@model_instance) @handler.do_find(@irequest, @request, @response) end it "should return a 404 when no model instance can be found" do @model_class.stubs(:name).returns "my name" @handler.expects(:set_response).with { |response, body, status| status == 404 } @model_class.stubs(:find).returns(nil) @handler.do_find(@irequest, @request, @response) end it "should serialize the result in with the appropriate format" do @model_instance = stub('model instance') @handler.expects(:format_to_use).returns "one" @model_instance.expects(:render).with("one").returns "my_rendered_object" @model_class.stubs(:find).returns(@model_instance) @handler.do_find(@irequest, @request, @response) end end describe "when searching for model instances" do before do - @irequest = stub 'indirection_request', :method => :find, :indirection_name => "my_handler", :options => {}, :key => "key" + @irequest = stub 'indirection_request', :method => :find, :indirection_name => "my_handler", :to_hash => {}, :key => "key", :model => @model_class @result1 = mock 'result1' @result2 = mock 'results' @result = [@result1, @result2] @model_class.stubs(:render_multiple).returns "my rendered instances" @model_class.stubs(:search).returns(@result) @format = stub 'format', :suitable? => true Puppet::Network::FormatHandler.stubs(:format).returns @format end + it "should use the indirection request to find the model" do + @irequest.expects(:model).returns @model_class + + @handler.do_search(@irequest, @request, @response) + end + it "should use a common method for determining the request parameters" do - @irequest.stubs(:options).returns(:foo => :baz, :bar => :xyzzy) + @irequest.stubs(:to_hash).returns(:foo => :baz, :bar => :xyzzy) @model_class.expects(:search).with do |key, args| args[:foo] == :baz and args[:bar] == :xyzzy end.returns @result @handler.do_search(@irequest, @request, @response) end it "should use the default status when a model search call succeeds" do @model_class.stubs(:search).returns(@result) @handler.do_search(@irequest, @request, @response) end it "should set the content type to the first format returned by the accept header" do @handler.expects(:accept_header).with(@request).returns "one,two" @handler.expects(:set_content_type).with(@response, "one") @handler.do_search(@irequest, @request, @response) end it "should return a list of serialized objects when a model search call succeeds" do @handler.expects(:accept_header).with(@request).returns "one,two" @model_class.stubs(:search).returns(@result) @model_class.expects(:render_multiple).with("one", @result).returns "my rendered instances" @handler.expects(:set_response).with { |response, data| data == "my rendered instances" } @handler.do_search(@irequest, @request, @response) end it "should return a 404 when searching returns an empty array" do @model_class.stubs(:name).returns "my name" @handler.expects(:set_response).with { |response, body, status| status == 404 } @model_class.stubs(:search).returns([]) @handler.do_search(@irequest, @request, @response) end it "should return a 404 when searching returns nil" do @model_class.stubs(:name).returns "my name" @handler.expects(:set_response).with { |response, body, status| status == 404 } @model_class.stubs(:search).returns([]) @handler.do_search(@irequest, @request, @response) end end describe "when destroying a model instance" do before do - @irequest = stub 'indirection_request', :method => :destroy, :indirection_name => "my_handler", :options => {}, :key => "key" + @irequest = stub 'indirection_request', :method => :destroy, :indirection_name => "my_handler", :to_hash => {}, :key => "key", :model => @model_class @result = stub 'result', :render => "the result" @model_class.stubs(:destroy).returns @result end + it "should use the indirection request to find the model" do + @irequest.expects(:model).returns @model_class + + @handler.do_destroy(@irequest, @request, @response) + end + it "should use the escaped request key to destroy the instance in the model" do @irequest.expects(:key).returns "foo bar" @model_class.expects(:destroy).with do |key, args| key == "foo bar" end @handler.do_destroy(@irequest, @request, @response) end it "should use a common method for determining the request parameters" do - @irequest.stubs(:options).returns(:foo => :baz, :bar => :xyzzy) + @irequest.stubs(:to_hash).returns(:foo => :baz, :bar => :xyzzy) @model_class.expects(:destroy).with do |key, args| args[:foo] == :baz and args[:bar] == :xyzzy end @handler.do_destroy(@irequest, @request, @response) end it "should use the default status code a model destroy call succeeds" do @handler.expects(:set_response).with { |response, body, status| status.nil? } @handler.do_destroy(@irequest, @request, @response) end it "should return a yaml-encoded result when a model destroy call succeeds" do @result = stub 'result', :to_yaml => "the result" @model_class.expects(:destroy).returns(@result) @handler.expects(:set_response).with { |response, body, status| body == "the result" } @handler.do_destroy(@irequest, @request, @response) end end describe "when saving a model instance" do before do - @irequest = stub 'indirection_request', :method => :save, :indirection_name => "my_handler", :options => {}, :key => "key" + @irequest = stub 'indirection_request', :method => :save, :indirection_name => "my_handler", :to_hash => {}, :key => "key", :model => @model_class @handler.stubs(:body).returns('my stuff') @result = stub 'result', :render => "the result" @model_instance = stub('indirected model instance', :save => true) @model_class.stubs(:convert_from).returns(@model_instance) @format = stub 'format', :suitable? => true Puppet::Network::FormatHandler.stubs(:format).returns @format end + it "should use the indirection request to find the model" do + @irequest.expects(:model).returns @model_class + + @handler.do_save(@irequest, @request, @response) + end + it "should use the 'body' hook to retrieve the body of the request" do @handler.expects(:body).returns "my body" @model_class.expects(:convert_from).with { |format, body| body == "my body" }.returns @model_instance @handler.do_save(@irequest, @request, @response) end it "should fail to save model if data is not specified" do @handler.stubs(:body).returns('') lambda { @handler.do_save(@irequest, @request, @response) }.should raise_error(ArgumentError) end it "should use a common method for determining the request parameters" do - @irequest.stubs(:options).returns(:foo => :baz, :bar => :xyzzy) + @irequest.stubs(:to_hash).returns(:foo => :baz, :bar => :xyzzy) @model_instance.expects(:save).with do |args| args[:foo] == :baz and args[:bar] == :xyzzy end @handler.do_save(@irequest, @request, @response) end it "should use the default status when a model save call succeeds" do @handler.expects(:set_response).with { |response, body, status| status.nil? } @handler.do_save(@irequest, @request, @response) end it "should return the yaml-serialized result when a model save call succeeds" do @model_instance.stubs(:save).returns(@model_instance) @model_instance.expects(:to_yaml).returns('foo') @handler.do_save(@irequest, @request, @response) end it "should set the content to yaml" do @handler.expects(:set_content_type).with(@response, "yaml") @handler.do_save(@irequest, @request, @response) end end end end diff --git a/spec/unit/network/http/mongrel.rb b/spec/unit/network/http/mongrel.rb index 9c708067a..59f893251 100755 --- a/spec/unit/network/http/mongrel.rb +++ b/spec/unit/network/http/mongrel.rb @@ -1,144 +1,131 @@ #!/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 require 'puppet/network/http/mongrel' 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 require 'puppet/network/http/mongrel' @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) @mock_puppet_mongrel = mock('puppet_mongrel') Puppet::Network::HTTPServer::Mongrel.stubs(:new).returns(@mock_puppet_mongrel) - @listen_params = { :address => "127.0.0.1", :port => 31337, - :handlers => [ :node, :catalog ], :protocols => [ :rest, :xmlrpc ], - :xmlrpc_handlers => [ :status, :fileserver ] - } + @listen_params = { :address => "127.0.0.1", :port => 31337, :protocols => [ :rest, :xmlrpc ], :xmlrpc_handlers => [ :status, :fileserver ] } 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 describe "when providing REST services" do - it "should instantiate a handler for each protocol+handler pair to configure web server routing" do - @listen_params[:protocols].each do |protocol| - @listen_params[:handlers].each do |handler| - @mock_mongrel.expects(:register) - end - end + it "should instantiate a handler at / for handling REST calls" do + Puppet::Network::HTTP::MongrelREST.expects(:new).returns "myhandler" + @mock_mongrel.expects(:register).with("/", "myhandler") + @server.listen(@listen_params) end it "should use a Mongrel + REST class to configure Mongrel when REST services are requested" do @server.expects(:class_for_protocol).with(:rest).at_least_once.returns(Puppet::Network::HTTP::MongrelREST) @server.listen(@listen_params) end end describe "when providing XMLRPC services" do it "should do nothing if no xmlrpc handlers have been provided" do Puppet::Network::HTTPServer::Mongrel.expects(:new).never @server.listen(@listen_params.merge(:xmlrpc_handlers => [])) end it "should create an instance of the existing Mongrel http server with the right handlers" do Puppet::Network::HTTPServer::Mongrel.expects(:new).with([:status, :master]).returns(@mock_puppet_mongrel) @server.listen(@listen_params.merge(:xmlrpc_handlers => [:status, :master])) end it "should register the Mongrel server instance at /RPC2" do @mock_mongrel.expects(:register).with("/RPC2", @mock_puppet_mongrel) @server.listen(@listen_params.merge(:xmlrpc_handlers => [:status, :master])) end 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, :catalog ], :protocols => [ :rest ] } 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(:stop) @server.unlisten end it "should not be listening" do @server.listen(@listen_params) @mock_mongrel.stubs(:stop) @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 index c03698dbd..84a7e7f64 100755 --- a/spec/unit/network/http/mongrel/rest.rb +++ b/spec/unit/network/http/mongrel/rest.rb @@ -1,205 +1,195 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/network/http' describe "Puppet::Network::HTTP::MongrelREST" do confine "Mongrel is not available" => Puppet.features.mongrel? before do require 'puppet/network/http/mongrel/rest' end it "should include the Puppet::Network::HTTP::Handler module" do Puppet::Network::HTTP::MongrelREST.ancestors.should be_include(Puppet::Network::HTTP::Handler) end describe "when initializing" do it "should call the Handler's initialization hook with its provided arguments as the server and handler" do Puppet::Network::HTTP::MongrelREST.any_instance.expects(:initialize_for_puppet).with(:server => "my", :handler => "arguments") Puppet::Network::HTTP::MongrelREST.new(:server => "my", :handler => "arguments") end end describe "when receiving a request" do before do @params = {} @request = stub('mongrel http request', :params => @params) @head = stub('response head') @body = stub('response body', :write => true) @response = stub('mongrel http response') @response.stubs(:start).yields(@head, @body) @model_class = stub('indirected model class') @mongrel = stub('mongrel http server', :register => true) Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) @handler = Puppet::Network::HTTP::MongrelREST.new(:server => @mongrel, :handler => :foo) end describe "and using the HTTP Handler interface" do it "should return the HTTP_ACCEPT parameter as the accept header" do @params.expects(:[]).with("HTTP_ACCEPT").returns "myaccept" @handler.accept_header(@request).should == "myaccept" end it "should use the REQUEST_METHOD as the http method" do @params.expects(:[]).with(Mongrel::Const::REQUEST_METHOD).returns "mymethod" @handler.http_method(@request).should == "mymethod" end - it "should use the first part of the request path as the path" do + it "should return the request path as the path" do @params.expects(:[]).with(Mongrel::Const::REQUEST_PATH).returns "/foo/bar" - @handler.path(@request).should == "/foo" - end - - it "should use the remainder of the request path as the request key" do - @params.expects(:[]).with(Mongrel::Const::REQUEST_PATH).returns "/foo/bar/baz" - @handler.request_key(@request).should == "bar/baz" - end - - it "should return nil as the request key if no second field is present" do - @params.expects(:[]).with(Mongrel::Const::REQUEST_PATH).returns "/foo" - @handler.request_key(@request).should be_nil + @handler.path(@request).should == "/foo/bar" end it "should return the request body as the body" do @request.expects(:body).returns "mybody" @handler.body(@request).should == "mybody" end it "should set the response's content-type header when setting the content type" do @header = mock 'header' @response.expects(:header).returns @header @header.expects(:[]=).with('Content-Type', "mytype") @handler.set_content_type(@response, "mytype") end it "should set the status and write the body when setting the response for a successful request" do head = mock 'head' body = mock 'body' @response.expects(:start).with(200).yields(head, body) body.expects(:write).with("mybody") @handler.set_response(@response, "mybody", 200) end it "should set the status and reason and write the body when setting the response for a successful request" do head = mock 'head' body = mock 'body' @response.expects(:start).with(400, false, "mybody").yields(head, body) body.expects(:write).with("mybody") @handler.set_response(@response, "mybody", 400) end end describe "and determining the request parameters" do before do @request.stubs(:params).returns({}) end it "should include the HTTP request parameters, with the keys as symbols" do @request.expects(:params).returns('QUERY_STRING' => 'foo=baz&bar=xyzzy') result = @handler.params(@request) result[:foo].should == "baz" result[:bar].should == "xyzzy" end it "should URI-decode the HTTP parameters" do encoding = URI.escape("foo bar") @request.expects(:params).returns('QUERY_STRING' => "foo=#{encoding}") result = @handler.params(@request) result[:foo].should == "foo bar" end it "should convert the string 'true' to the boolean" do @request.expects(:params).returns('QUERY_STRING' => 'foo=true') result = @handler.params(@request) result[:foo].should be_true end it "should convert the string 'false' to the boolean" do @request.expects(:params).returns('QUERY_STRING' => 'foo=false') result = @handler.params(@request) result[:foo].should be_false end it "should convert integer arguments to Integers" do @request.expects(:params).returns('QUERY_STRING' => 'foo=15') result = @handler.params(@request) result[:foo].should == 15 end it "should convert floating point arguments to Floats" do @request.expects(:params).returns('QUERY_STRING' => 'foo=1.5') result = @handler.params(@request) result[:foo].should == 1.5 end it "should YAML-load and URI-decode values that are YAML-encoded" do escaping = URI.escape(YAML.dump(%w{one two})) @request.expects(:params).returns('QUERY_STRING' => "foo=#{escaping}") result = @handler.params(@request) result[:foo].should == %w{one two} end it "should pass the client's ip address to model find" do @request.stubs(:params).returns("REMOTE_ADDR" => "ipaddress") @handler.params(@request)[:ip].should == "ipaddress" end it "should use the :ssl_client_header to determine the parameter when looking for the certificate" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" @request.stubs(:params).returns("myheader" => "/CN=host.domain.com") @handler.params(@request) end it "should retrieve the hostname by matching the certificate parameter" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" @request.stubs(:params).returns("myheader" => "/CN=host.domain.com") @handler.params(@request)[:node].should == "host.domain.com" end it "should use the :ssl_client_header to determine the parameter for checking whether the host certificate is valid" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.expects(:value).with(:ssl_client_verify_header).returns "myheader" @request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") @handler.params(@request) end it "should consider the host authenticated if the validity parameter contains 'SUCCESS'" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" @request.stubs(:params).returns("myheader" => "SUCCESS", "certheader" => "/CN=host.domain.com") @handler.params(@request)[:authenticated].should be_true end it "should consider the host unauthenticated if the validity parameter does not contain 'SUCCESS'" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" @request.stubs(:params).returns("myheader" => "whatever", "certheader" => "/CN=host.domain.com") @handler.params(@request)[:authenticated].should be_false end it "should consider the host unauthenticated if no certificate information is present" do Puppet.settings.stubs(:value).with(:ssl_client_header).returns "certheader" Puppet.settings.stubs(:value).with(:ssl_client_verify_header).returns "myheader" @request.stubs(:params).returns("myheader" => nil, "certheader" => "SUCCESS") @handler.params(@request)[:authenticated].should be_false end it "should not pass a node name to model method if no certificate information is present" do Puppet.settings.stubs(:value).returns "eh" Puppet.settings.expects(:value).with(:ssl_client_header).returns "myheader" @request.stubs(:params).returns("myheader" => nil) @handler.params(@request).should_not be_include(:node) end end end end diff --git a/spec/unit/network/http/webrick.rb b/spec/unit/network/http/webrick.rb index e02d5c213..a6fbfc83f 100755 --- a/spec/unit/network/http/webrick.rb +++ b/spec/unit/network/http/webrick.rb @@ -1,354 +1,337 @@ #!/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' require 'puppet/network/http/webrick' 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 = stub('webrick', :[] => {}) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging - @listen_params = { :address => "127.0.0.1", :port => 31337, - :handlers => [ :node, :catalog ], :xmlrpc_handlers => [], :protocols => [ :rest ] - } + @listen_params = { :address => "127.0.0.1", :port => 31337, :xmlrpc_handlers => [], :protocols => [ :rest ] } 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 configure a logger for webrick" do @server.expects(:setup_logger).returns(:Logger => :mylogger) WEBrick::HTTPServer.expects(:new).with {|args| args[:Logger] == :mylogger }.returns(@mock_webrick) @server.listen(@listen_params) end it "should configure SSL for webrick" do @server.expects(:setup_ssl).returns(:Ssl => :testing, :Other => :yay) WEBrick::HTTPServer.expects(:new).with {|args| args[:Ssl] == :testing and args[:Other] == :yay }.returns(@mock_webrick) @server.listen(@listen_params) end it "should be listening" do @server.listen(@listen_params) @server.should be_listening end describe "when the REST protocol is requested" do - it "should use a WEBrick + REST class to configure WEBrick" do - Puppet::Network::HTTP::WEBrick.expects(:class_for_protocol).with(:rest).at_least_once - @server.listen(@listen_params.merge(:protocols => [:rest])) - end + it "should register the REST handler at /" do + # We don't care about the options here. + @mock_webrick.expects(:mount).with { |path, klass, options| path == "/" and klass == Puppet::Network::HTTP::WEBrickREST } - it "should instantiate a handler for each protocol+handler pair to configure web server routing" do - @listen_params[:protocols].each do |protocol| - @listen_params[:handlers].each do |handler| - @mock_webrick.expects(:mount) - end - end - @server.listen(@listen_params) + @server.listen(@listen_params.merge(:protocols => [:rest])) end end describe "when the XMLRPC protocol is requested" do before do @servlet = mock 'servlet' Puppet::Network::XMLRPC::WEBrickServlet.stubs(:new).returns @servlet @master_handler = mock('master_handler') @file_handler = mock('file_handler') @master = mock 'master' @file = mock 'file' @master_handler.stubs(:new).returns @master @file_handler.stubs(:new).returns @file Puppet::Network::Handler.stubs(:handler).with(:master).returns @master_handler Puppet::Network::Handler.stubs(:handler).with(:fileserver).returns @file_handler end it "should do nothing if no xmlrpc handlers have been specified" do Puppet::Network::Handler.expects(:handler).never @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [])) end it "should look the handler classes up via their base class" do Puppet::Network::Handler.expects(:handler).with(:master).returns @master_handler Puppet::Network::Handler.expects(:handler).with(:fileserver).returns @file_handler @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end it "should create an instance for each requested xmlrpc handler" do @master_handler.expects(:new).returns @master @file_handler.expects(:new).returns @file @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end it "should create a webrick servlet with the xmlrpc handler instances" do Puppet::Network::XMLRPC::WEBrickServlet.expects(:new).with([@master, @file]).returns @servlet @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end it "should mount the webrick servlet at /RPC2" do @mock_webrick.stubs(:mount) @mock_webrick.expects(:mount).with("/RPC2", @servlet) @server.listen(@listen_params.merge(:protocols => [:xmlrpc], :xmlrpc_handlers => [:master, :fileserver])) end 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 - end end describe Puppet::Network::HTTP::WEBrick, "when looking up the class to handle a protocol" do it "should require a protocol" do lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol }.should raise_error(ArgumentError) end it "should accept a protocol" do lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("bob") }.should_not raise_error(ArgumentError) end it "should use a WEBrick + REST class when a REST protocol is specified" do Puppet::Network::HTTP::WEBrick.class_for_protocol("rest").should == Puppet::Network::HTTP::WEBrickREST end it "should fail when an unknown protocol is specified" do lambda { Puppet::Network::HTTP::WEBrick.class_for_protocol("abcdefg") }.should raise_error end end describe Puppet::Network::HTTP::WEBrick, "when turning off listening" do before do @mock_webrick = stub('webrick', :[] => {}) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new [:setup_logger, :setup_ssl].each {|meth| @server.stubs(meth).returns({})} # the empty hash is required because of how we're merging @listen_params = { :address => "127.0.0.1", :port => 31337, :handlers => [ :node, :catalog ], :protocols => [ :rest ] } 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 describe Puppet::Network::HTTP::WEBrick do before do @mock_webrick = stub('webrick', :[] => {}) [:mount, :start, :shutdown].each {|meth| @mock_webrick.stubs(meth)} WEBrick::HTTPServer.stubs(:new).returns(@mock_webrick) @server = Puppet::Network::HTTP::WEBrick.new end describe "when configuring an http logger" do before do Puppet.settings.stubs(:value).returns "something" Puppet.settings.stubs(:use) @filehandle = stub 'handle', :fcntl => nil, :sync => nil File.stubs(:open).returns @filehandle end it "should use the settings for :main, :ssl, and the process name" do Puppet.settings.stubs(:value).with(:name).returns "myname" Puppet.settings.expects(:use).with(:main, :ssl, "myname") @server.setup_logger end it "should use the masterlog if the process name is 'puppetmasterd'" do Puppet.settings.stubs(:value).with(:name).returns "puppetmasterd" Puppet.settings.expects(:value).with(:masterhttplog).returns "/master/log" File.expects(:open).with("/master/log", "a+").returns @filehandle @server.setup_logger end it "should use the httplog if the process name is not 'puppetmasterd'" do Puppet.settings.stubs(:value).with(:name).returns "other" Puppet.settings.expects(:value).with(:httplog).returns "/other/log" File.expects(:open).with("/other/log", "a+").returns @filehandle @server.setup_logger end describe "and creating the logging filehandle" do it "should set fcntl to 'Fcntl::F_SETFD, Fcntl::FD_CLOEXEC'" do @filehandle.expects(:fcntl).with(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) @server.setup_logger end it "should sync the filehandle" do @filehandle.expects(:sync) @server.setup_logger end end it "should create a new WEBrick::Log instance with the open filehandle" do WEBrick::Log.expects(:new).with(@filehandle) @server.setup_logger end it "should set debugging if the current loglevel is :debug" do Puppet::Util::Log.expects(:level).returns :debug WEBrick::Log.expects(:new).with { |handle, debug| debug == WEBrick::Log::DEBUG } @server.setup_logger end it "should return the logger as the main log" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger @server.setup_logger[:Logger].should == logger end it "should return the logger as the access log using both the Common and Referer log format" do logger = mock 'logger' WEBrick::Log.expects(:new).returns logger @server.setup_logger[:AccessLog].should == [ [logger, WEBrick::AccessLog::COMMON_LOG_FORMAT], [logger, WEBrick::AccessLog::REFERER_LOG_FORMAT] ] end end describe "when configuring ssl" do before do @key = stub 'key', :content => "mykey" @cert = stub 'cert', :content => "mycert" @host = stub 'host', :key => @key, :certificate => @cert, :name => "yay", :ssl_store => "mystore" Puppet::SSL::Certificate.stubs(:find).with('ca').returns @cert Puppet::SSL::Host.stubs(:localhost).returns @host end it "should use the key from the localhost SSL::Host instance" do Puppet::SSL::Host.expects(:localhost).returns @host @host.expects(:key).returns @key @server.setup_ssl[:SSLPrivateKey].should == "mykey" end it "should configure the certificate" do @server.setup_ssl[:SSLCertificate].should == "mycert" end it "should fail if no CA certificate can be found" do Puppet::SSL::Certificate.stubs(:find).with('ca').returns nil lambda { @server.setup_ssl }.should raise_error(Puppet::Error) end it "should specify the path to the CA certificate" do Puppet.settings.stubs(:value).returns "whatever" Puppet.settings.stubs(:value).with(:hostcrl).returns 'false' Puppet.settings.stubs(:value).with(:localcacert).returns '/ca/crt' @server.setup_ssl[:SSLCACertificateFile].should == "/ca/crt" end it "should start ssl immediately" do @server.setup_ssl[:SSLStartImmediately].should be_true end it "should enable ssl" do @server.setup_ssl[:SSLEnable].should be_true end it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do @server.setup_ssl[:SSLVerifyClient].should == OpenSSL::SSL::VERIFY_PEER end it "should add an x509 store" do Puppet.settings.stubs(:value).returns "whatever" Puppet.settings.stubs(:value).with(:hostcrl).returns '/my/crl' @host.expects(:ssl_store).returns "mystore" @server.setup_ssl[:SSLCertificateStore].should == "mystore" end it "should set the certificate name to 'nil'" do @server.setup_ssl[:SSLCertName].should be_nil end end end diff --git a/spec/unit/network/http/webrick/rest.rb b/spec/unit/network/http/webrick/rest.rb index b32cf49c5..bb0918131 100755 --- a/spec/unit/network/http/webrick/rest.rb +++ b/spec/unit/network/http/webrick/rest.rb @@ -1,147 +1,137 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../../../spec_helper' require 'puppet/network/http' require 'puppet/network/http/webrick/rest' describe Puppet::Network::HTTP::WEBrickREST do it "should include the Puppet::Network::HTTP::Handler module" do Puppet::Network::HTTP::WEBrickREST.ancestors.should be_include(Puppet::Network::HTTP::Handler) end describe "when initializing" do it "should call the Handler's initialization hook with its provided arguments as the server and handler" do Puppet::Network::HTTP::WEBrickREST.any_instance.expects(:initialize_for_puppet).with(:server => "my", :handler => "arguments") Puppet::Network::HTTP::WEBrickREST.new("my", "arguments") end end describe "when receiving a request" do before do @request = stub('webrick http request', :query => {}, :peeraddr => %w{eh boo host ip}, :client_cert => nil) @response = stub('webrick http response', :status= => true, :body= => true) @model_class = stub('indirected model class') @webrick = stub('webrick http server', :mount => true, :[] => {}) Puppet::Indirector::Indirection.stubs(:model).with(:foo).returns(@model_class) @handler = Puppet::Network::HTTP::WEBrickREST.new(@webrick, :foo) end it "should delegate its :service method to its :process method" do @handler.expects(:process).with(@request, @response).returns "stuff" @handler.service(@request, @response).should == "stuff" end describe "when using the Handler interface" do it "should use the 'accept' request parameter as the Accept header" do @request.expects(:[]).with("accept").returns "foobar" @handler.accept_header(@request).should == "foobar" end it "should use the request method as the http method" do @request.expects(:request_method).returns "FOO" @handler.http_method(@request).should == "FOO" end - it "should return the first argument of the request path as the path" do + it "should return the request path as the path" do @request.expects(:path).returns "/foo/bar" - @handler.path(@request).should == "/foo" - end - - it "should return the remainder of the path as the request key" do - @request.expects(:path).returns "/foo/bar/baz" - @handler.request_key(@request).should == "bar/baz" - end - - it "should return nil as the request key if there is no second field" do - @request.expects(:path).returns "/foo" - @handler.request_key(@request).should be_nil + @handler.path(@request).should == "/foo/bar" end it "should return the request body as the body" do @request.expects(:body).returns "my body" @handler.body(@request).should == "my body" end it "should set the response's 'content-type' header when setting the content type" do @response.expects(:[]=).with("content-type", "text/html") @handler.set_content_type(@response, "text/html") end it "should set the status and body on the response when setting the response for a successful query" do @response.expects(:status=).with 200 @response.expects(:body=).with "mybody" @handler.set_response(@response, "mybody", 200) end it "should set the status and message on the response when setting the response for a failed query" do @response.expects(:status=).with 400 @response.expects(:reason_phrase=).with "mybody" @handler.set_response(@response, "mybody", 400) end end describe "and determining the request parameters" do it "should include the HTTP request parameters, with the keys as symbols" do @request.stubs(:query).returns("foo" => "baz", "bar" => "xyzzy") result = @handler.params(@request) result[:foo].should == "baz" result[:bar].should == "xyzzy" end it "should URI-decode the HTTP parameters" do encoding = URI.escape("foo bar") @request.expects(:query).returns('foo' => encoding) result = @handler.params(@request) result[:foo].should == "foo bar" end it "should convert the string 'true' to the boolean" do @request.expects(:query).returns('foo' => "true") result = @handler.params(@request) result[:foo].should be_true end it "should convert the string 'false' to the boolean" do @request.expects(:query).returns('foo' => "false") result = @handler.params(@request) result[:foo].should be_false end it "should YAML-load and URI-decode values that are YAML-encoded" do escaping = URI.escape(YAML.dump(%w{one two})) @request.expects(:query).returns('foo' => escaping) result = @handler.params(@request) result[:foo].should == %w{one two} end it "should pass the client's ip address to model find" do @request.stubs(:peeraddr).returns(%w{noidea dunno hostname ipaddress}) @handler.params(@request)[:ip].should == "ipaddress" end it "should set 'authenticated' to true if a certificate is present" do cert = stub 'cert', :subject => [%w{CN host.domain.com}] @request.stubs(:client_cert).returns cert @handler.params(@request)[:authenticated].should be_true end it "should set 'authenticated' to false if no certificate is present" do @request.stubs(:client_cert).returns nil @handler.params(@request)[:authenticated].should be_false end it "should pass the client's certificate name to model method if a certificate is present" do cert = stub 'cert', :subject => [%w{CN host.domain.com}] @request.stubs(:client_cert).returns cert @handler.params(@request)[:node].should == "host.domain.com" end it "should not pass a node name to model method if no certificate is present" do @request.stubs(:client_cert).returns nil @handler.params(@request).should_not be_include(:node) end end end end diff --git a/spec/unit/ssl/certificate.rb b/spec/unit/ssl/certificate.rb index a092c6abe..0d48991ad 100755 --- a/spec/unit/ssl/certificate.rb +++ b/spec/unit/ssl/certificate.rb @@ -1,128 +1,124 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/ssl/certificate' describe Puppet::SSL::Certificate do before do @class = Puppet::SSL::Certificate end after do @class.instance_variable_set("@ca_location", nil) end it "should be extended with the Indirector module" do @class.metaclass.should be_include(Puppet::Indirector) end it "should indirect certificate" do @class.indirection.name.should == :certificate end - it "should default to the :file terminus" do - @class.indirection.terminus_class.should == :file - end - it "should only support the text format" do @class.supported_formats.should == [:s] end describe "when converting from a string" do it "should create a certificate instance with its name set to the certificate subject and its content set to the extracted certificate" do cert = stub 'certificate', :subject => "/CN=Foo.madstop.com" OpenSSL::X509::Certificate.expects(:new).with("my certificate").returns(cert) mycert = stub 'sslcert' mycert.expects(:content=).with(cert) @class.expects(:new).with("foo.madstop.com").returns mycert @class.from_s("my certificate") end it "should create multiple certificate instances when asked" do cert1 = stub 'cert1' @class.expects(:from_s).with("cert1").returns cert1 cert2 = stub 'cert2' @class.expects(:from_s).with("cert2").returns cert2 @class.from_multiple_s("cert1\n---\ncert2").should == [cert1, cert2] end end describe "when converting to a string" do before do @certificate = @class.new("myname") end it "should return an empty string when it has no certificate" do @certificate.to_s.should == "" end it "should convert the certificate to pem format" do certificate = mock 'certificate', :to_pem => "pem" @certificate.content = certificate @certificate.to_s.should == "pem" end it "should be able to convert multiple instances to a string" do cert2 = @class.new("foo") @certificate.expects(:to_s).returns "cert1" cert2.expects(:to_s).returns "cert2" @class.to_multiple_s([@certificate, cert2]).should == "cert1\n---\ncert2" end end describe "when managing instances" do before do @certificate = @class.new("myname") end it "should have a name attribute" do @certificate.name.should == "myname" end it "should convert its name to a string and downcase it" do @class.new(:MyName).name.should == "myname" end it "should have a content attribute" do @certificate.should respond_to(:content) end it "should return a nil expiration if there is no actual certificate" do @certificate.stubs(:content).returns nil @certificate.expiration.should be_nil end it "should use the expiration of the certificate as its expiration date" do cert = stub 'cert' @certificate.stubs(:content).returns cert cert.expects(:not_after).returns "sometime" @certificate.expiration.should == "sometime" end it "should be able to read certificates from disk" do path = "/my/path" File.expects(:read).with(path).returns("my certificate") certificate = mock 'certificate' OpenSSL::X509::Certificate.expects(:new).with("my certificate").returns(certificate) @certificate.read(path).should equal(certificate) @certificate.content.should equal(certificate) end it "should have a :to_text method that it delegates to the actual key" do real_certificate = mock 'certificate' real_certificate.expects(:to_text).returns "certificatetext" @certificate.content = real_certificate @certificate.to_text.should == "certificatetext" end end end diff --git a/spec/unit/ssl/certificate_revocation_list.rb b/spec/unit/ssl/certificate_revocation_list.rb index 3963b407f..eb25268e6 100755 --- a/spec/unit/ssl/certificate_revocation_list.rb +++ b/spec/unit/ssl/certificate_revocation_list.rb @@ -1,184 +1,180 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/ssl/certificate_revocation_list' describe Puppet::SSL::CertificateRevocationList do before do @cert = stub 'cert', :subject => "mysubject" @key = stub 'key', :private? => true @class = Puppet::SSL::CertificateRevocationList end - it "should default to the :file terminus" do - @class.indirection.terminus_class.should == :file - end - it "should only support the text format" do @class.supported_formats.should == [:s] end describe "when converting from a string" do it "should create a CRL instance with its name set to 'foo' and its content set to the extracted CRL" do crl = stub 'crl' OpenSSL::X509::CRL.expects(:new).returns(crl) mycrl = stub 'sslcrl' mycrl.expects(:content=).with(crl) @class.expects(:new).with("foo").returns mycrl @class.from_s("my crl").should == mycrl end end describe "when an instance" do before do @class.any_instance.stubs(:read_or_generate) @crl = @class.new("whatever") end it "should always use 'crl' for its name" do @crl.name.should == "crl" end it "should have a content attribute" do @crl.should respond_to(:content) end end describe "when initializing" do it "should fail if :cacrl is set to false" do Puppet.settings.expects(:value).with(:cacrl).returns false lambda { @class.new("crl") }.should raise_error(Puppet::Error) end it "should fail if :cacrl is set to the string 'false'" do Puppet.settings.expects(:value).with(:cacrl).returns "false" lambda { @class.new("crl") }.should raise_error(Puppet::Error) end end describe "when generating the crl" do before do @real_crl = mock 'crl' @real_crl.stub_everything OpenSSL::X509::CRL.stubs(:new).returns(@real_crl) @class.any_instance.stubs(:read_or_generate) @crl = @class.new("crl") end it "should set its issuer to the subject of the passed certificate" do @real_crl.expects(:issuer=).with(@cert.subject) @crl.generate(@cert, @key) end it "should set its version to 1" do @real_crl.expects(:version=).with(1) @crl.generate(@cert, @key) end it "should create an instance of OpenSSL::X509::CRL" do OpenSSL::X509::CRL.expects(:new).returns(@real_crl) @crl.generate(@cert, @key) end # The next three tests aren't good, but at least they # specify the behaviour. it "should add an extension for the CRL number" do @real_crl.expects(:extensions=) @crl.generate(@cert, @key) end it "should set the last update time" do @real_crl.expects(:last_update=) @crl.generate(@cert, @key) end it "should set the next update time" do @real_crl.expects(:next_update=) @crl.generate(@cert, @key) end it "should sign the CRL" do @real_crl.expects(:sign).with { |key, digest| key == @key } @crl.generate(@cert, @key) end it "should set the content to the generated crl" do @crl.generate(@cert, @key) @crl.content.should equal(@real_crl) end it "should return the generated crl" do @crl.generate(@cert, @key).should equal(@real_crl) end end # This test suite isn't exactly complete, because the # SSL stuff is very complicated. It just hits the high points. describe "when revoking a certificate" do before do @class.wrapped_class.any_instance.stubs(:issuer=) @class.wrapped_class.any_instance.stubs(:sign) @crl = @class.new("crl") @crl.generate(@cert, @key) @crl.content.stubs(:sign) @crl.stubs :save @key = mock 'key' end it "should require a serial number and the CA's private key" do lambda { @crl.revoke }.should raise_error(ArgumentError) end it "should default to OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE as the revocation reason" do # This makes it a bit more of an integration test than we'd normally like, but that's life # with openssl. reason = OpenSSL::ASN1::Enumerated(OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE) OpenSSL::ASN1.expects(:Enumerated).with(OpenSSL::OCSP::REVOKED_STATUS_KEYCOMPROMISE).returns reason @crl.revoke(1, @key) end it "should mark the CRL as updated" do time = Time.now Time.stubs(:now).returns time @crl.content.expects(:last_update=).with(time) @crl.revoke(1, @key) end it "should mark the CRL valid for five years" do time = Time.now Time.stubs(:now).returns time @crl.content.expects(:next_update=).with(time + (5 * 365*24*60*60)) @crl.revoke(1, @key) end it "should sign the CRL with the CA's private key and a digest instance" do @crl.content.expects(:sign).with { |key, digest| key == @key and digest.is_a?(OpenSSL::Digest::SHA1) } @crl.revoke(1, @key) end it "should save the CRL" do @crl.expects :save @crl.revoke(1, @key) end end end