diff options
author | Seth Chisamore <schisamo@opscode.com> | 2012-10-29 15:33:14 -0400 |
---|---|---|
committer | Seth Chisamore <schisamo@opscode.com> | 2012-10-30 09:47:37 -0400 |
commit | 862d971eed1c11e6990b1c08f00cac67a10a2e79 (patch) | |
tree | 88a383894926c0c56a25d7a31ec37e821b7e7b97 /chef | |
parent | f9d960515e7aa736c439cb914cb0498a415e5c5a (diff) | |
download | chef-862d971eed1c11e6990b1c08f00cac67a10a2e79.tar.gz |
[OC-3564] remove Chef::SolrQuery
This class was only used by Chef server components and has been
succeeded by Erchef.
Diffstat (limited to 'chef')
-rw-r--r-- | chef/chef.gemspec | 1 | ||||
-rw-r--r-- | chef/lib/chef/config.rb | 8 | ||||
-rw-r--r-- | chef/lib/chef/solr_query.rb | 187 | ||||
-rw-r--r-- | chef/lib/chef/solr_query/lucene.treetop | 150 | ||||
-rw-r--r-- | chef/lib/chef/solr_query/lucene_nodes.rb | 285 | ||||
-rw-r--r-- | chef/lib/chef/solr_query/query_transform.rb | 65 | ||||
-rw-r--r-- | chef/lib/chef/solr_query/solr_http_request.rb | 132 | ||||
-rw-r--r-- | chef/spec/unit/solr_query/query_transform_spec.rb | 454 | ||||
-rw-r--r-- | chef/spec/unit/solr_query/solr_http_request_spec.rb | 244 | ||||
-rw-r--r-- | chef/spec/unit/solr_query_spec.rb | 203 |
10 files changed, 0 insertions, 1729 deletions
diff --git a/chef/chef.gemspec b/chef/chef.gemspec index 732601cc79..e40a0cf8ee 100644 --- a/chef/chef.gemspec +++ b/chef/chef.gemspec @@ -24,7 +24,6 @@ Gem::Specification.new do |s| s.add_dependency "bunny", ">= 0.6.0", "< 0.8.0" s.add_dependency "json", ">= 1.4.4", "<= 1.6.1" s.add_dependency "yajl-ruby", "~> 1.1" - s.add_dependency "treetop", "~> 1.4.9" s.add_dependency "net-ssh", "~> 2.2.2" s.add_dependency "net-ssh-multi", "~> 1.1.0" # CHEF-3027: The knife-cloud plugins require newer features from highline, core chef should not. diff --git a/chef/lib/chef/config.rb b/chef/lib/chef/config.rb index 924a20ab3b..5fd50d3bda 100644 --- a/chef/lib/chef/config.rb +++ b/chef/lib/chef/config.rb @@ -173,7 +173,6 @@ class Chef ## Daemonization Settings ## # What user should Chef run as? user nil - # What group should the chef-server, -solr, -solr-indexer run as group nil umask 0022 @@ -230,13 +229,6 @@ class Chef # Where should chef-solo download recipes from? recipe_url nil - solr_url "http://localhost:8983/solr" - solr_jetty_path "/var/chef/solr-jetty" - solr_data_path "/var/chef/solr/data" - solr_home_path "/var/chef/solr" - solr_heap_size "256M" - solr_java_opts nil - # Parameters for connecting to RabbitMQ amqp_host '0.0.0.0' amqp_port '5672' diff --git a/chef/lib/chef/solr_query.rb b/chef/lib/chef/solr_query.rb deleted file mode 100644 index 3360ecc24d..0000000000 --- a/chef/lib/chef/solr_query.rb +++ /dev/null @@ -1,187 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2009-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'chef/mixin/xml_escape' -require 'chef/log' -require 'chef/config' -require 'chef/couchdb' -require 'chef/solr_query/solr_http_request' -require 'chef/solr_query/query_transform' - -class Chef - class SolrQuery - - ID_KEY = "X_CHEF_id_CHEF_X" - DEFAULT_PARAMS = Mash.new(:start => 0, :rows => 1000, :sort => "#{ID_KEY} asc", :wt => 'json', :indent => 'off').freeze - FILTER_PARAM_MAP = {:database => 'X_CHEF_database_CHEF_X', :type => "X_CHEF_type_CHEF_X", :data_bag => 'data_bag'} - VALID_PARAMS = [:start,:rows,:sort,:q,:type] - BUILTIN_SEARCH_TYPES = ["role","node","client","environment"] - DATA_BAG_ITEM = 'data_bag_item' - - include Chef::Mixin::XMLEscape - - attr_accessor :query - attr_accessor :params - - # Create a new Query object - takes the solr_url and optional - # Chef::CouchDB object to inflate objects into. - def initialize(couchdb = nil) - @filter_query = {} - @params = {} - - if couchdb.nil? - @database = Chef::Config[:couchdb_database] - @couchdb = Chef::CouchDB.new(nil, Chef::Config[:couchdb_database]) - else - unless couchdb.respond_to?(:couchdb_database) - Chef::Log.warn("Passing the database name to Chef::Solr::Query initialization is deprecated. Please pass in the Chef::CouchDB object instead.") - @database = couchdb - @couchdb = Chef::CouchDB.new(nil, couchdb) - else - @database = couchdb.couchdb_database - @couchdb = couchdb - end - end - end - - def self.from_params(params, couchdb=nil) - query = new(couchdb) - query.params = VALID_PARAMS.inject({}) do |p, param_name| - p[param_name] = params[param_name] if params.key?(param_name) - p - end - query.update_filter_query_from_params - query.update_query_from_params - query - end - - def filter_by(filter_query_params) - filter_query_params.each do |key, value| - @filter_query[FILTER_PARAM_MAP[key]] = value - end - end - - def filter_query - @filter_query.map { |param, value| "+#{param}:#{value}" }.join(' ') - end - - def filter_by_type(type) - case type - when *BUILTIN_SEARCH_TYPES - filter_by(:type => type) - else - filter_by(:type => DATA_BAG_ITEM, :data_bag => type) - end - end - - def update_filter_query_from_params - filter_by(:database => @database) - filter_by_type(params.delete(:type)) - end - - def update_query_from_params - original_query = URI.decode(params.delete(:q) || "*:*") - @query = Chef::SolrQuery::QueryTransform.transform(original_query) - end - - def objects - if !object_ids.empty? - @bulk_objects ||= @couchdb.bulk_get(object_ids) - Chef::Log.debug { "Bulk get of objects: #{@bulk_objects.inspect}" } - @bulk_objects - else - [] - end - end - - def object_ids - @object_ids ||= results["response"]["docs"].map { |d| d[ID_KEY] } - end - - def results - @results ||= SolrHTTPRequest.select(self.to_hash) - end - - # Search Solr for objects of a given type, for a given query. - def search - { "rows" => objects, "start" => results["response"]["start"], - "total" => results["response"]["numFound"] } - end - - def to_hash - options = DEFAULT_PARAMS.merge(params) - options[:fq] = filter_query - options[:q] = @query - options - end - - START_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n".freeze - START_DELETE_BY_QUERY = "<delete><query>".freeze - END_DELETE_BY_QUERY = "</query></delete>\n".freeze - COMMIT = "<commit/>\n".freeze - - def commit(opts={}) - SolrHTTPRequest.update("#{START_XML}#{COMMIT}") - end - - def delete_database(db) - query_data = xml_escape("X_CHEF_database_CHEF_X:#{db}") - xml = "#{START_XML}#{START_DELETE_BY_QUERY}#{query_data}#{END_DELETE_BY_QUERY}" - SolrHTTPRequest.update(xml) - commit - end - - def rebuild_index(db=Chef::Config[:couchdb_database]) - delete_database(db) - - results = {} - [Chef::ApiClient, Chef::Node, Chef::Role, Chef::Environment].each do |klass| - results[klass.name] = reindex_all(klass) ? "success" : "failed" - end - databags = Chef::DataBag.cdb_list(true) - Chef::Log.info("Reloading #{databags.size.to_s} #{Chef::DataBag} objects into the indexer") - databags.each { |i| i.add_to_index; i.list(true).each { |x| x.add_to_index } } - results[Chef::DataBag.name] = "success" - results - end - - def reindex_all(klass, metadata={}) - begin - items = klass.cdb_list(true) - Chef::Log.info("Reloading #{items.size.to_s} #{klass.name} objects into the indexer") - items.each { |i| i.add_to_index } - rescue Net::HTTPServerException => e - # 404s are okay, there might not be any of that kind of object... - if e.message =~ /Not Found/ - Chef::Log.warn("Could not load #{klass.name} objects from couch for re-indexing (this is ok if you don't have any of these)") - return false - else - raise e - end - rescue Exception => e - Chef::Log.fatal("Chef encountered an error while attempting to load #{klass.name} objects back into the index") - raise e - end - true - end - - - end -end diff --git a/chef/lib/chef/solr_query/lucene.treetop b/chef/lib/chef/solr_query/lucene.treetop deleted file mode 100644 index 99d3e1f709..0000000000 --- a/chef/lib/chef/solr_query/lucene.treetop +++ /dev/null @@ -1,150 +0,0 @@ -# -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -grammar Lucene - - rule body - (expression / space)* <Body> - end - - rule expression - operation / group / field / field_range / term / string - end - - rule term - keyword valid_letter+ <Term> / !keyword !"?" valid_letter <Term> - end - - rule field - field_name ":" (term/string/group) <Field> - end - - rule field_range - field_name ":" "[" range_value " TO " range_value "]" <InclFieldRange> - / - field_name ":" "{" range_value " TO " range_value "}" <ExclFieldRange> - end - - rule field_name - !keyword valid_letter+ <FieldName> - end - - rule range_value - valid_letter+ <RangeValue> / "*" <RangeValue> - end - - rule group - space? '(' body ')' space? <Group> - end - - rule operation - binary_op / unary_op / fuzzy_op / boost_op - end - - rule unary_op - not_op / required_op / prohibited_op - end - - rule binary_op - (group / field / field_range / term) space? boolean_operator space+ body <BinaryOp> - end - - rule boolean_operator - and_operator / or_operator - end - - rule and_operator - 'AND' <AndOperator> / '&&' <AndOperator> - end - - rule or_operator - 'OR' <OrOperator> / '||' <OrOperator> - end - - rule not_op - not_operator space (group / field / field_range / term / string) <UnaryOp> - / - bang_operator space? (group / field / field_range / term / string) <UnaryOp> - end - - rule not_operator - 'NOT' <NotOperator> - end - - rule bang_operator - '!' <NotOperator> - end - - rule required_op - !valid_letter required_operator (term/string) <UnaryOp> - / - required_operator (term/string) <UnaryOp> - end - - rule required_operator - '+' <RequiredOperator> - end - - rule prohibited_op - !valid_letter prohibited_operator (field/field_range/term/string) <UnaryOp> - end - - rule prohibited_operator - '-' <ProhibitedOperator> - end - - rule boost_op - (term/string) '^' fuzzy_param <BoostOp> - end - - rule fuzzy_op - (term/string) '~' fuzzy_param? (space / !valid_letter) <FuzzyOp> - end - - rule fuzzy_param - [0-9] '.'? [0-9] <FuzzyParam> / [0-9]+ <FuzzyParam> - end - - rule string - '"' term (space term)* '"' <Phrase> - end - - rule keyword - 'AND' / 'OR' / 'NOT' - end - - rule valid_letter - start_letter+ ([a-zA-Z0-9@*?_.-] / '\\' special_char)* - end - - rule start_letter - [a-zA-Z0-9@._*] / '\\' special_char - end - - rule end_letter - [a-zA-Z0-9*?_.] / '\\' special_char - end - - rule special_char - [-+&|!(){}\[\]^"~*?:\\] - end - - rule space - [\s]+ - end -end diff --git a/chef/lib/chef/solr_query/lucene_nodes.rb b/chef/lib/chef/solr_query/lucene_nodes.rb deleted file mode 100644 index c2d7777365..0000000000 --- a/chef/lib/chef/solr_query/lucene_nodes.rb +++ /dev/null @@ -1,285 +0,0 @@ -# -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'treetop' - -module Lucene - SEP = "__=__" - - class Term < Treetop::Runtime::SyntaxNode - def to_array - "T:#{self.text_value}" - end - - def transform - self.text_value - end - end - - class Field < Treetop::Runtime::SyntaxNode - def to_array - field = self.elements[0].text_value - term = self.elements[1].to_array - "(F:#{field} #{term})" - end - - def transform - field = self.elements[0].text_value - term = self.elements[1] - if term.is_a? Phrase - str = term.transform - # remove quotes - str = str[1 ... (str.length - 1)] - "content:\"#{field}#{SEP}#{str}\"" - else - "content:#{field}#{SEP}#{term.transform}" - end - end - end - - class FieldRange < Treetop::Runtime::SyntaxNode - - def to_array - field = self.elements[0].text_value - range_start = self.elements[1].to_array - range_end = self.elements[2].to_array - "(FR:#{field} #{left}#{range_start}#{right} #{left}#{range_end}#{right})" - end - - def transform - field = self.elements[0].text_value - range_start = self.elements[1].transform - range_end = self.elements[2].transform - # FIXME: handle special cases for missing start/end - if ("*" == range_start && "*" == range_end) - "content:#{field}#{SEP}*" - elsif "*" == range_end - "content:#{left}#{field}#{SEP}#{range_start} TO #{field}#{SEP}\\ufff0#{right}" - elsif "*" == range_start - "content:#{left}#{field}#{SEP} TO #{field}#{SEP}#{range_end}#{right}" - else - "content:#{left}#{field}#{SEP}#{range_start} TO #{field}#{SEP}#{range_end}#{right}" - end - end - - end - - class InclFieldRange < FieldRange - def left - "[" - end - def right - "]" - end - end - - class ExclFieldRange < FieldRange - def left - "{" - end - def right - "}" - end - end - - class RangeValue < Treetop::Runtime::SyntaxNode - def to_array - self.text_value - end - - def transform - to_array - end - end - - class FieldName < Treetop::Runtime::SyntaxNode - def to_array - self.text_value - end - - def transform - to_array - end - end - - - class Body < Treetop::Runtime::SyntaxNode - def to_array - self.elements.map { |x| x.to_array }.join(" ") - end - - def transform - self.elements.map { |x| x.transform }.join(" ") - end - end - - class Group < Treetop::Runtime::SyntaxNode - def to_array - "(" + self.elements[0].to_array + ")" - end - - def transform - "(" + self.elements[0].transform + ")" - end - end - - class BinaryOp < Treetop::Runtime::SyntaxNode - def to_array - op = self.elements[1].to_array - a = self.elements[0].to_array - b = self.elements[2].to_array - "(#{op} #{a} #{b})" - end - - def transform - op = self.elements[1].transform - a = self.elements[0].transform - b = self.elements[2].transform - "#{a} #{op} #{b}" - end - end - - class AndOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:AND" - end - - def transform - "AND" - end - end - - class OrOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:OR" - end - - def transform - "OR" - end - end - - class FuzzyOp < Treetop::Runtime::SyntaxNode - def to_array - a = self.elements[0].to_array - param = self.elements[1] - if param - "(OP:~ #{a} #{param.to_array})" - else - "(OP:~ #{a})" - end - end - - def transform - a = self.elements[0].transform - param = self.elements[1] - if param - "#{a}~#{param.transform}" - else - "#{a}~" - end - end - end - - class BoostOp < Treetop::Runtime::SyntaxNode - def to_array - a = self.elements[0].to_array - param = self.elements[1] - "(OP:^ #{a} #{param.to_array})" - end - - def transform - a = self.elements[0].transform - param = self.elements[1] - "#{a}^#{param.transform}" - end - end - - class FuzzyParam < Treetop::Runtime::SyntaxNode - def to_array - self.text_value - end - - def transform - self.text_value - end - end - - class UnaryOp < Treetop::Runtime::SyntaxNode - def to_array - op = self.elements[0].to_array - a = self.elements[1].to_array - "(#{op} #{a})" - end - - def transform - op = self.elements[0].transform - a = self.elements[1].transform - spc = case op - when "+", "-" - "" - else - " " - end - "#{op}#{spc}#{a}" - end - - end - - class NotOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:NOT" - end - - def transform - "NOT" - end - - end - - class RequiredOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:+" - end - - def transform - "+" - end - - end - - class ProhibitedOperator < Treetop::Runtime::SyntaxNode - def to_array - "OP:-" - end - - def transform - "-" - end - end - - class Phrase < Treetop::Runtime::SyntaxNode - def to_array - "STR:#{self.text_value}" - end - - def transform - "#{self.text_value}" - end - end -end diff --git a/chef/lib/chef/solr_query/query_transform.rb b/chef/lib/chef/solr_query/query_transform.rb deleted file mode 100644 index 529f9de482..0000000000 --- a/chef/lib/chef/solr_query/query_transform.rb +++ /dev/null @@ -1,65 +0,0 @@ -# -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -require 'treetop' -require 'chef/solr_query/lucene_nodes' - -class Chef - class Exceptions - class QueryParseError < StandardError - end - end -end - -class Chef - class SolrQuery - class QueryTransform - @@base_path = File.expand_path(File.dirname(__FILE__)) - Treetop.load(File.join(@@base_path, 'lucene.treetop')) - @@parser = LuceneParser.new - - def self.parse(data) - tree = @@parser.parse(data) - msg = "Parse error at offset: #{@@parser.index}\n" - msg += "Reason: #{@@parser.failure_reason}" - raise Chef::Exceptions::QueryParseError, msg if tree.nil? - self.clean_tree(tree) - tree.to_array - end - - def self.transform(data) - return "*:*" if data == "*:*" - tree = @@parser.parse(data) - msg = "Parse error at offset: #{@@parser.index}\n" - msg += "Reason: #{@@parser.failure_reason}" - raise Chef::Exceptions::QueryParseError, msg if tree.nil? - self.clean_tree(tree) - tree.transform - end - - private - - def self.clean_tree(root_node) - return if root_node.elements.nil? - root_node.elements.delete_if do |node| - node.class.name == "Treetop::Runtime::SyntaxNode" - end - root_node.elements.each { |node| self.clean_tree(node) } - end - end - end -end diff --git a/chef/lib/chef/solr_query/solr_http_request.rb b/chef/lib/chef/solr_query/solr_http_request.rb deleted file mode 100644 index e7ce1d5675..0000000000 --- a/chef/lib/chef/solr_query/solr_http_request.rb +++ /dev/null @@ -1,132 +0,0 @@ -# -# Author:: Adam Jacob (<adam@opscode.com>) -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2009-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'net/http' -require 'uri' -require 'chef/json_compat' -require 'chef/config' - -class Chef - class SolrQuery - class SolrHTTPRequest - CLASS_FOR_METHOD = {:GET => Net::HTTP::Get, :POST => Net::HTTP::Post} - - TEXT_XML = {"Content-Type" => "text/xml"} - - def self.solr_url=(solr_url) - @solr_url = solr_url - @http_client = nil - @url_prefix = nil - end - - def self.solr_url - @solr_url || Chef::Config[:solr_url] - end - - def self.http_client - @http_client ||= begin - uri = URI.parse(solr_url) - http_client = Net::HTTP.new(uri.host, uri.port) - http_client.use_ssl = true if uri.port == 443 - http_client - end - end - - def self.url_prefix - @url_prefix ||= begin - uri = URI.parse(solr_url) - if uri.path == "" - "/solr" - else - uri.path.gsub(%r{/$}, '') - end - end - end - - def self.select(params={}) - url = "#{url_prefix}/select?#{url_join(params)}" - Chef::Log.debug("Sending #{url} to Solr") - request = new(:GET, url) - json_response = request.run("Search Query to Solr '#{solr_url}#{url}'") - Chef::JSONCompat.from_json(json_response) - end - - def self.update(doc) - url = "#{url_prefix}/update" - Chef::Log.debug("POSTing document to SOLR:\n#{doc}") - request = new(:POST, url, TEXT_XML) { |req| req.body = doc.to_s } - request.run("POST to Solr '#{solr_url}#{url}', data: #{doc}") - end - - def self.url_join(params_hash={}) - params = params_hash.inject("") do |param_str, params| - param_str << "#{params[0]}=#{escape(params[1])}&" - end - params.chop! # trailing & - params - end - - def self.escape(s) - s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) { - '%'+$1.unpack('H2'*$1.size).join('%').upcase - }.tr(' ', '+') - end - - def initialize(method, url, headers=nil) - args = headers ? [url, headers] : url - @request = CLASS_FOR_METHOD[method].new(*args) - yield @request if block_given? - end - - def http_client - self.class.http_client - end - - def solr_url - self.class.solr_url - end - - def run(description="HTTP Request to Solr") - response = http_client.request(@request) - request_failed!(response, description) unless response.kind_of?(Net::HTTPSuccess) - response.body - rescue NoMethodError => e - # http://redmine.ruby-lang.org/issues/show/2708 - # http://redmine.ruby-lang.org/issues/show/2758 - if e.to_s =~ /#{Regexp.escape(%q|undefined method 'closed?' for nil:NilClass|)}/ - Chef::Log.fatal("#{description} failed. Chef::Exceptions::SolrConnectionError exception: Errno::ECONNREFUSED (net/http undefined method closed?) attempting to contact #{solr_url}") - Chef::Log.debug("Rescued error in http connect, treating it as Errno::ECONNREFUSED to hide bug in net/http") - Chef::Log.debug(e.backtrace.join("\n")) - raise Chef::Exceptions::SolrConnectionError, "Errno::ECONNREFUSED: Connection refused attempting to contact #{solr_url}" - else - raise - end - end - - def request_failed!(response, description='HTTP call') - Chef::Log.fatal("#{description} failed (#{response.class} #{response.code} #{response.message})") - response.error! - rescue Timeout::Error, Errno::EINVAL, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT => e - Chef::Log.debug(e.backtrace.join("\n")) - raise Chef::Exceptions::SolrConnectionError, "#{e.class.name}: #{e.to_s}" - end - - end - end -end diff --git a/chef/spec/unit/solr_query/query_transform_spec.rb b/chef/spec/unit/solr_query/query_transform_spec.rb deleted file mode 100644 index f3fc746746..0000000000 --- a/chef/spec/unit/solr_query/query_transform_spec.rb +++ /dev/null @@ -1,454 +0,0 @@ -# -# Author:: Seth Falcon (<seth@opscode.com>) -# Copyright:: Copyright (c) 2010-2011 Opscode, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' -require 'chef/solr_query/query_transform' - -describe "Chef::SolrQuery::QueryTransform" do - before(:each) do - @parser = Chef::SolrQuery::QueryTransform - @parseError = Chef::Exceptions::QueryParseError - end - - describe "single term queries" do - basic_terms = %w(a ab 123 a1 2b foo_bar baz-baz) - basic_terms << " leading" - basic_terms << "trailing " - basic_terms += %w(XAND ANDX XOR ORX XNOT NOTX) - basic_terms.each do |term| - expect = "T:#{term.strip}" - it "'#{term}' => #{expect}" do - @parser.parse(term).should == expect - end - end - describe "invalid" do - %w(AND OR NOT :).each do |t| - it "'#{t}' => ParseError" do - lambda { @parser.parse(t) }.should raise_error(@parseError) - end - end - end - - describe "wildcards in terms" do - it "allows * as a wildcard" do - @parser.parse("foo*bar").should == "T:foo*bar" - end - - it "allows a single ? as a wildcard" do - @parser.parse("foo?bar").should == "T:foo?bar" - end - - it "allows multiple ? as fixed length wildcards" do - @parser.parse("foo???bar").should == "T:foo???bar" - end - - it "allows a leading wildcard with *" do - # NOTE: This is not valid lucene query syntax. However, our - # index format and query transformation can allow it because - # the transformed query ends up with the '*' not in leading - # position. We decided that allowing it makes sense because - # queries like ec2:* are useful and many users expect this - # behavior to work. - @parser.parse("*foobar").should == "T:*foobar" - end - - it "does not allow a leading wildcard with ?" do - lambda { @parser.parse("?foobar") }.should raise_error(@parseError) - end - - it "does not allow a leading wildcard with ?" do - lambda { @parser.parse("afield:?foobar") }.should raise_error(@parseError) - end - - end - - describe "escaped special characters in terms" do - special_chars = ["!", "(", ")", "{", "}", "[", "]", "^", "\"", - "~", "*", "?", ":", "\\", "&", "|", "+", "-"] - example_fmts = ['foo%sbar', '%sb', 'a%s'] - special_chars.each do |char| - example_fmts.each do |fmt| - input = fmt % ("\\" + char) - expect = "T:#{input}" - it "'#{input}' => #{expect}" do - @parser.parse(input).should == expect - end - end - end - end - - describe "special characters in terms are not allowed" do - # NOTE: '*' is not a valid start letter for a lucene search - # term, however, we can support it because of our index - # structure and query transformation. We decided to keep this - # flexibility because queries like ec2:* are common and useful. - prefix_ok = ["!", "+", "-", "*"] - suffix_ok = ["*", "?", "~", "-"] - # FIXME: ideally, '!' would not be allowed in the middle of a - # term. Currently we parse foo!bar the same as foo !bar. - # Also '+' might be nice to disallow - embed_ok = ["*", "?", ":", "-", "!", "+"] - special_chars = ["!", "(", ")", "{", "}", "[", "]", "^", "\"", - "~", "*", "?", ":", "\\", "&", "|", "+", "-"] - example_fmts = { - :prefix => '%sb', - :middle => 'foo%sbar', - :suffix => 'a%s' - } - special_chars.each do |char| - example_fmts.keys.each do |key| - fmt = example_fmts[key] - if key == :prefix && prefix_ok.include?(char) - :pass - elsif key == :middle && embed_ok.include?(char) - :pass - elsif key == :suffix && suffix_ok.include?(char) - :pass - else - input = fmt % char - it "disallows: '#{input}'" do - lambda { @parser.parse(input) }.should raise_error(@parseError) - end - end - end - end - end - - end - - describe "multiple terms" do - it "should allow multiple terms" do - @parser.parse("a b cdefg").should == "T:a T:b T:cdefg" - end - end - - describe "boolean queries" do - describe "two term basic and/or" do - binary_operators = [['AND', 'AND'], ['&&', 'AND'], ['OR', 'OR'], ['||', 'OR']] - binary_operators.each do |op, op_name| - expect = "(OP:#{op_name} T:t1 T:t2)" - it "should parse 't1 #{op} t2' => #{expect}" do - @parser.parse("t1 #{op} t2").should == expect - end - end - end - - it "should allow a string of terms with ands and ors" do - expect = "(OP:AND T:t1 (OP:OR T:t2 (OP:AND T:t3 T:t4)))" - @parser.parse("t1 AND t2 OR t3 AND t4").should == expect - end - end - - describe "grouping with parens" do - it "should create a single group for (aterm)" do - @parser.parse("(aterm)").should == "(T:aterm)" - end - - describe "and booleans" do - - %w(AND &&).each do |op| - expect = "((OP:AND T:a T:b))" - input = "(a #{op} b)" - it "parses #{input} => #{expect}" do - @parser.parse(input).should == expect - end - end - - %w(OR ||).each do |op| - expect = "((OP:OR T:a T:b))" - input = "(a #{op} b)" - it "parses #{input} => #{expect}" do - @parser.parse(input).should == expect - end - end - - it "should handle a LHS group" do - expect = "(OP:OR ((OP:AND T:a T:b)) T:c)" - @parser.parse("(a && b) OR c").should == expect - @parser.parse("(a && b) || c").should == expect - end - - it "should handle a RHS group" do - expect = "(OP:OR T:c ((OP:AND T:a T:b)))" - @parser.parse("c OR (a && b)").should == expect - @parser.parse("c OR (a AND b)").should == expect - end - - it "should handle both sides as groups" do - expect = "(OP:OR ((OP:AND T:c T:d)) ((OP:AND T:a T:b)))" - @parser.parse("(c AND d) OR (a && b)").should == expect - end - end - end - - describe "NOT queries" do - # input, output - [ - ["a NOT b", "T:a (OP:NOT T:b)"], - ["a ! b", "T:a (OP:NOT T:b)"], - ["a !b", "T:a (OP:NOT T:b)"], - ["a NOT (b || c)", "T:a (OP:NOT ((OP:OR T:b T:c)))"], - ["a ! (b || c)", "T:a (OP:NOT ((OP:OR T:b T:c)))"], - ["a !(b || c)", "T:a (OP:NOT ((OP:OR T:b T:c)))"] - ].each do |input, expected| - it "should parse '#{input}' => #{expected.inspect}" do - @parser.parse(input).should == expected - end - end - - ["NOT", "a NOT", "(NOT)"].each do |d| - it "should raise a ParseError on '#{d}'" do - lambda { @parser.parse(d) }.should raise_error(@parseError) - end - end - end - - describe 'required and prohibited prefixes (+/-)' do - ["+", "-"].each do |kind| - [ - ["#{kind}foo", "(OP:#{kind} T:foo)"], - ["bar #{kind}foo", "T:bar (OP:#{kind} T:foo)"], - ["(#{kind}oneA twoA) b", "((OP:#{kind} T:oneA) T:twoA) T:b"] - ].each do |input, expect| - it "should parse '#{input} => #{expect.inspect}" do - @parser.parse(input).should == expect - end - end - end - - # it 'ignores + embedded in a term' do - # @parser.parse("one+two").should == "T:one+two" - # end - - it 'ignores - embedded in a term' do - @parser.parse("one-two").should == "T:one-two" - end - - it "allows a trailing dash" do - @parser.parse("one-").should == "T:one-" - end - - end - - describe "phrases (strings)" do - phrases = [['"single"', 'STR:"single"'], - ['"two term"', 'STR:"two term"'], - ['"has \"escaped\" quote\"s"', 'STR:"has \"escaped\" quote\"s"'] - ] - phrases.each do |phrase, expect| - it "'#{phrase}' => #{expect}" do - @parser.parse(phrase).should == expect - end - end - - describe "invalid" do - bad = ['""', '":not:a:term"', '"a :bad:'] - bad.each do |t| - it "'#{t}' => ParseError" do - lambda { @parser.parse(t) }.should raise_error(@parseError) - end - end - end - - it "allows phrases to be required with '+'" do - @parser.parse('+"a b c"').should == '(OP:+ STR:"a b c")' - end - - it "allows phrases to be prohibited with '-'" do - @parser.parse('-"a b c"').should == '(OP:- STR:"a b c")' - end - - it "allows phrases to be excluded with NOT" do - @parser.parse('a NOT "b c"').should == 'T:a (OP:NOT STR:"b c")' - end - - end - - describe "fields" do - it "parses a term annotated with a field" do - @parser.parse("afield:aterm").should == "(F:afield T:aterm)" - end - - it "allows underscore in a field name" do - @parser.parse("a_field:aterm").should == "(F:a_field T:aterm)" - end - - it "parses a group annotated with a field" do - @parser.parse("afield:(a b c)").should == "(F:afield (T:a T:b T:c))" - end - - it "parses a phrase annotated with a field" do - @parser.parse('afield:"a b c"').should == '(F:afield STR:"a b c")' - end - - it "allows @ in a term" do - @parser.parse('afield:foo@acme.org').should == '(F:afield T:foo@acme.org)' - end - - describe "and binary operators" do - examples = [ - ['term1 AND afield:term2', "(OP:AND T:term1 (F:afield T:term2))"], - ['afield:term1 AND term2', "(OP:AND (F:afield T:term1) T:term2)"], - ['afield:term1 AND bfield:term2', - "(OP:AND (F:afield T:term1) (F:bfield T:term2))"]] - examples.each do |input, want| - it "'#{input}' => '#{want}'" do - @parser.parse(input).should == want - end - end - end - - describe "and unary operators" do - examples = [ - ['term1 AND NOT afield:term2', - "(OP:AND T:term1 (OP:NOT (F:afield T:term2)))"], - ['term1 AND ! afield:term2', - "(OP:AND T:term1 (OP:NOT (F:afield T:term2)))"], - ['term1 AND !afield:term2', - "(OP:AND T:term1 (OP:NOT (F:afield T:term2)))"], - ['term1 AND -afield:term2', - "(OP:AND T:term1 (OP:- (F:afield T:term2)))"], - ['-afield:[* TO *]', - "(OP:- (FR:afield [*] [*]))"] - ] - examples.each do |input, want| - it "#{input} => #{want}" do - @parser.parse(input).should == want - end - end - end - end - - describe "range queries" do - before(:each) do - @kinds = { - "inclusive" => {:left => "[", :right => "]"}, - "exclusive" => {:left => "{", :right => "}"} - } - end - - def make_expect(kind, field, s, e) - expect_fmt = "(FR:%s %s%s%s %s%s%s)" - left = @kinds[kind][:left] - right = @kinds[kind][:right] - expect_fmt % [field, left, s, right, left, e, right] - end - - def make_query(kind, field, s, e) - query_fmt = "%s:%s%s TO %s%s" - left = @kinds[kind][:left] - right = @kinds[kind][:right] - query_fmt % [field, left, s, e, right] - end - - ["inclusive", "exclusive"].each do |kind| - tests = [["afield", "start", "end"], - ["afield", "start", "*"], - ["afield", "*", "end"], - ["afield", "*", "*"] - ] - tests.each do |field, s, e| - it "parses an #{kind} range query #{s} TO #{e}" do - expect = make_expect(kind, field, s, e) - query = make_query(kind, field, s, e) - @parser.parse(query).should == expect - end - end - end - - describe "and binary operators" do - [["afield:[start TO end] AND term", - "(OP:AND (FR:afield [start] [end]) T:term)"], - ["term OR afield:[start TO end]", - "(OP:OR T:term (FR:afield [start] [end]))"], - ["f1:[s1 TO e1] OR f2:[s2 TO e2]", - "(OP:OR (FR:f1 [s1] [e1]) (FR:f2 [s2] [e2]))"] - ].each do |q, want| - it "parses '#{q}'" do - @parser.parse(q).should == want - end - end - end - - describe "and unary operators" do - [["t1 NOT afield:[start TO end]", - "T:t1 (OP:NOT (FR:afield [start] [end]))"] - ].each do |input, want| - it "#{input} => #{want}" do - @parser.parse(input).should == want - end - end - end - end - - describe "proximity query" do - [ - ['"one two"~10', '(OP:~ STR:"one two" 10)'], - ['word~', '(OP:~ T:word)'], - ['word~0.5', '(OP:~ T:word 0.5)'] - ].each do |input, expect| - it "'#{input}' => #{expect}" do - @parser.parse(input).should == expect - end - end - end - - describe "term boosting" do - [ - ['"one two"^10', '(OP:^ STR:"one two" 10)'], - ['word^0.5', '(OP:^ T:word 0.5)'] - ].each do |input, expect| - it "'#{input}' => #{expect}" do - @parser.parse(input).should == expect - end - end - - it "should fail to parse if no boosting argument is given" do - lambda { @parser.parse("foo^")}.should raise_error(@parseError) - end - end - - describe "examples" do - examples = [['tags:apples*.for.eating.com', "(F:tags T:apples*.for.eating.com)"], - ['ohai_time:[1234.567 TO *]', "(FR:ohai_time [1234.567] [*])"], - ['ohai_time:[* TO 1234.567]', "(FR:ohai_time [*] [1234.567])"], - ['ohai_time:[* TO *]', "(FR:ohai_time [*] [*])"]] - # ['aterm AND afield:aterm', "((OP:AND T:aterm ((F:afield T:aterm))))"], - # ['role:prod AND aterm', "blah"], - # ['role:prod AND xy:true', "blah"]] - examples.each do |input, want| - it "'#{input}' => '#{want}'" do - @parser.parse(input).should == want - end - end - end - - describe "transform queries for solr schema" do - testcase_file = "#{CHEF_SPEC_DATA}/search_queries_to_transform.txt" - lines = File.readlines(testcase_file).map { |line| line.strip } - lines = lines.select { |line| !line.empty? } - testcases = Hash[*(lines)] - testcases.keys.sort.each do |input| - expected = testcases[input] - it "from> #{input}\n to> #{expected}\n" do - @parser.transform(input).should == expected - end - end - end - -end diff --git a/chef/spec/unit/solr_query/solr_http_request_spec.rb b/chef/spec/unit/solr_query/solr_http_request_spec.rb deleted file mode 100644 index c70c347a14..0000000000 --- a/chef/spec/unit/solr_query/solr_http_request_spec.rb +++ /dev/null @@ -1,244 +0,0 @@ -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2011 Opscode, inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -require 'chef/solr_query' -require 'net/http' - -describe Chef::SolrQuery::SolrHTTPRequest do - before do - Chef::Config[:solr_url] = "http://example.com:8983" - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@solr_url, nil) - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@url_prefix, nil) - - @request = Chef::SolrQuery::SolrHTTPRequest.new(:GET, '/solr/select') - end - - it "defaults to using the configured solr_url" do - Chef::SolrQuery::SolrHTTPRequest.solr_url.should == "http://example.com:8983" - end - - it "supports solr_url with a path" do - Chef::Config[:solr_url] = "http://example.com:8983/test" - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@solr_url, nil) - - Chef::SolrQuery::SolrHTTPRequest.solr_url.should == "http://example.com:8983/test" - end - - it "updates the Solr URL as you like" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234" - Chef::SolrQuery::SolrHTTPRequest.solr_url.should == "http://chunkybacon.org:1234" - end - - it "updates the URL prefix with a path" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234/something" - Chef::SolrQuery::SolrHTTPRequest.url_prefix.should == "/something" - end - - it "removes extra / at the end of solr_url" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234/extra/" - Chef::SolrQuery::SolrHTTPRequest.url_prefix.should == "/extra" - end - - it "creates a Net::HTTP client for the base Solr URL" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234" - http_client = Chef::SolrQuery::SolrHTTPRequest.http_client - http_client.address.should == "chunkybacon.org" - http_client.port.should == 1234 - end - - it "creates a Net::HTTP client for the base Solr URL ignoring the path" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234/test" - http_client = Chef::SolrQuery::SolrHTTPRequest.http_client - http_client.address.should == "chunkybacon.org" - http_client.port.should == 1234 - end - - it "defaults url_prefix to /solr if the configured solr_url has no path" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234" - Chef::SolrQuery::SolrHTTPRequest.url_prefix.should == "/solr" - end - - it "defaults url_prefix to the path from the configured solr_url" do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://chunkybacon.org:1234/test" - Chef::SolrQuery::SolrHTTPRequest.url_prefix.should == "/test" - end - - describe "when configured with the Solr URL" do - before do - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => "{ :some => :hash }" - ) - @http_request = mock( - "Net::HTTP::Request", - :body= => true - ) - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - end - - describe "when executing a select query" do - before(:each) do - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => '{"some": "hash" }' - ) - @solr = Chef::SolrQuery.from_params(:type => 'node', - :q => "hostname:latte") - @params = @solr.to_hash - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - end - - describe "when the HTTP call is successful" do - it "should call get to /solr/select with the escaped query" do - txfm_query = "q=content%3Ahostname__%3D__latte" - Net::HTTP::Get.should_receive(:new).with(%r(/solr/select?.+#{txfm_query})) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - - it "uses Solr's JSON response format" do - Net::HTTP::Get.should_receive(:new).with(%r(wt=json)) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - - it "uses indent=off to get a compact response" do - Net::HTTP::Get.should_receive(:new).with(%r(indent=off)) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - - it "uses the filter query to restrict the result set" do - filter_query =@solr.filter_query.gsub('+', '%2B').gsub(':', "%3A").gsub(' ', '+') - Net::HTTP::Get.should_receive(:new).with(/fq=#{Regexp.escape(filter_query)}/) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - - it "returns the evaluated response body" do - res = Chef::SolrQuery::SolrHTTPRequest.select(@params) - res.should == {"some" => "hash" } - end - end - end - - describe "when updating" do - before do - Net::HTTP::Post.stub!(:new).and_return(@http_request) - end - - it "should post to /solr/update" do - @doc = "<xml is the old tldr>" - Net::HTTP::Post.should_receive(:new).with("/solr/update", "Content-Type" => "text/xml").and_return(@http_request) - Chef::SolrQuery::SolrHTTPRequest.update(@doc) - end - - it "should set the body of the request to the stringified doc" do - @http_request.should_receive(:body=).with("foo") - Chef::SolrQuery::SolrHTTPRequest.update(:foo) - end - - it "should send the request to solr" do - @http.should_receive(:request).with(@http_request).and_return(@http_response) - Chef::SolrQuery::SolrHTTPRequest.update(:foo) - end - - end - - describe "when the HTTP call is unsuccessful" do - [Timeout::Error, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EINVAL].each do |exception| - it "should rescue, log an error message, and raise a SolrConnectionError encountering exception #{exception}" do - response = mock("Net:HTTPResponse test double", :code => 500, :message => "oops", :class => exception) - @http.should_receive(:request).with(instance_of(Net::HTTP::Get)).and_return(response) - response.should_receive(:error!).and_raise(exception) - Chef::Log.should_receive(:fatal).with("Search Query to Solr failed (#{exception} 500 oops)") - - lambda {@request.run('Search Query to Solr')}.should raise_error(Chef::Exceptions::SolrConnectionError) - end - end - - it "should rescue, log an error message, and raise a SolrConnectionError when encountering exception NoMethodError and net/http closed? bug" do - @no_method_error = NoMethodError.new("undefined method 'closed\?' for nil:NilClass") - @http.should_receive(:request).with(instance_of(Net::HTTP::Get)).and_raise(@no_method_error) - Chef::Log.should_receive(:fatal).with("HTTP Request to Solr failed. Chef::Exceptions::SolrConnectionError exception: Errno::ECONNREFUSED (net/http undefined method closed?) attempting to contact http://example.com:8983") - lambda { - @request.run - }.should raise_error(Chef::Exceptions::SolrConnectionError) - end - end - - end - - describe "when configured with the Solr URL with a path" do - before do - Chef::Config[:solr_url] = "http://example.com:8983/test" - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@solr_url, nil) - Chef::SolrQuery::SolrHTTPRequest.instance_variable_set(:@url_prefix, nil) - - @request = Chef::SolrQuery::SolrHTTPRequest.new(:GET, '/solr/select') - - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => "{ :some => :hash }" - ) - @http_request = mock( - "Net::HTTP::Request", - :body= => true - ) - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - end - - describe "when executing a select query" do - before(:each) do - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => '{"some": "hash" }' - ) - @solr = Chef::SolrQuery.from_params(:type => 'node', - :q => "hostname:latte") - @params = @solr.to_hash - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - end - - describe "when the HTTP call is successful" do - it "should call get to /test/select with the escaped query" do - txfm_query = "q=content%3Ahostname__%3D__latte" - Net::HTTP::Get.should_receive(:new).with(%r(/test/select?.+#{txfm_query})) - Chef::SolrQuery::SolrHTTPRequest.select(@params) - end - end - end - - describe "when updating" do - before do - Net::HTTP::Post.stub!(:new).and_return(@http_request) - end - - it "should post to /test/update" do - @doc = "<xml is the old tldr>" - Net::HTTP::Post.should_receive(:new).with("/test/update", "Content-Type" => "text/xml").and_return(@http_request) - Chef::SolrQuery::SolrHTTPRequest.update(@doc) - end - end - end -end diff --git a/chef/spec/unit/solr_query_spec.rb b/chef/spec/unit/solr_query_spec.rb deleted file mode 100644 index 8b48011713..0000000000 --- a/chef/spec/unit/solr_query_spec.rb +++ /dev/null @@ -1,203 +0,0 @@ -# Author:: Daniel DeLeo (<dan@opscode.com>) -# Copyright:: Copyright (c) 2010, 2011 Opscode, inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'spec_helper' - -require 'chef/solr_query' -require 'net/http' - -#require 'rspec/mocks' - -describe Chef::SolrQuery do - before do - Chef::SolrQuery::SolrHTTPRequest.solr_url = "http://example.com:8983" - - @http_response = mock( - "Net::HTTP::Response", - :kind_of? => Net::HTTPSuccess, - :body => "{ :some => :hash }" - ) - @http_request = mock( - "Net::HTTP::Request", - :body= => true - ) - @http = mock("Net::HTTP", :request => @http_response) - Chef::SolrQuery::SolrHTTPRequest.stub!(:http_client).and_return(@http) - Net::HTTP::Post.stub!(:new).and_return(@http_request) - Net::HTTP::Get.stub!(:new).and_return(@http_request) - @doc = { "foo" => "bar" } - end - - before(:each) do - @solr = Chef::SolrQuery.new - end - - it "sets filter query params" do - @solr.filter_by(:database => 'chef') - @solr.filter_query.should == "+X_CHEF_database_CHEF_X:chef" - end - - it "filters by type when querying for a builtin type" do - @solr.filter_by_type("node") - @solr.filter_query.should == "+X_CHEF_type_CHEF_X:node" - end - - it "filters by type for data bag items" do - @solr.filter_by_type("users") - @solr.filter_query.split(" ").sort.should == ['+X_CHEF_type_CHEF_X:data_bag_item', '+data_bag:users'] - end - - it "stores the main query" do - @solr.query = "role:prod AND tags:chef-server" - @solr.query.should == "role:prod AND tags:chef-server" - end - - describe "when generating query params for select" do - before(:each) do - @solr = Chef::SolrQuery.from_params(:type => 'node', :q => "hostname:latte") - @params = @solr.to_hash - end - - it "includes the query as q" do - @params[:q].should == "content:hostname__=__latte" - end - - it "sets the response format to json" do - @params[:wt].should == "json" - end - - it "uses indent=off to get a compact response" do - @params[:indent].should == "off" - end - - it "includes the filter query to restrict the result set" do - @params[:fq].should == @solr.filter_query - end - - it "defaults to returning 1000 rows" do - @params[:rows].should == 1000 - end - - it "returns the number of rows requested" do - @solr.params[:rows] = 500 - @solr.to_hash[:rows].should == 500 - end - - it "offsets the row selection if requested" do - @solr.params[:start] = 500 - @solr.to_hash[:start].should == 500 - end - - end - - describe "when querying solr" do - before do - @couchdb = mock("CouchDB Test Double", :couchdb_database => "chunky_bacon") - @couchdb.stub!(:kind_of?).with(Chef::CouchDB).and_return(true) #ugh. - @solr = Chef::SolrQuery.from_params({:type => 'node', :q => "hostname:latte", :start => 10, :rows => 5}, @couchdb) - @docs = [1,2,3,4,5].map { |doc_id| {'X_CHEF_id_CHEF_X' => doc_id} } - @solr_response = {"response" => {"docs" => @docs, "start" => 10, "results" => 123}} - Chef::SolrQuery::SolrHTTPRequest.should_receive(:select).with(@solr.to_hash).and_return(@solr_response) - end - - it "it collects the document ids from the response" do - @solr.object_ids.should == [1,2,3,4,5] - end - - it "does a bulk get of the objects from CouchDB" do - @couchdb.should_receive(:bulk_get).with([1,2,3,4,5]).and_return(%w{obj1 obj2 obj3 obj4 obj5}) - @solr.objects.should == %w{obj1 obj2 obj3 obj4 obj5} - end - - end - - describe "when forcing a Solr commit" do - it "sends valid commit xml to solr" do - Chef::SolrQuery::SolrHTTPRequest.should_receive(:update).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<commit/>\n") - @solr.commit - end - end - - describe "when deleting a database from Solr" do - it "sends a valid delete query to solr and forces a commit" do - Chef::SolrQuery::SolrHTTPRequest.should_receive(:update).with("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<delete><query>X_CHEF_database_CHEF_X:chef</query></delete>\n") - @solr.should_receive(:commit) - @solr.delete_database("chef") - end - end - - describe "rebuilding the index" do - before do - Chef::Config[:couchdb_database] = "chunky_bacon" - end - - it "deletes the index and commits" do - @solr.should_receive(:delete_database).with("chunky_bacon") - @solr.stub!(:reindex_all) - Chef::DataBag.stub!(:cdb_list).and_return([]) - @solr.rebuild_index - end - - it "reindexes Chef::ApiClient, Chef::Node, and Chef::Role objects, reporting the results as a hash" do - @solr.should_receive(:delete_database).with("chunky_bacon") - @solr.should_receive(:reindex_all).with(Chef::ApiClient).and_return(true) - @solr.should_receive(:reindex_all).with(Chef::Environment).and_return(true) - @solr.should_receive(:reindex_all).with(Chef::Node).and_return(true) - @solr.should_receive(:reindex_all).with(Chef::Role).and_return(true) - Chef::DataBag.stub!(:cdb_list).and_return([]) - - result = @solr.rebuild_index - result["Chef::ApiClient"].should == "success" - result["Chef::Node"].should == "success" - result["Chef::Role"].should == "success" - end - - it "does not reindex Chef::OpenIDRegistration or Chef::WebUIUser objects" do - # hi there. the reason we're specifying this behavior is because these objects - # are not properly indexed in the first place and trying to reindex them - # tickles a bug in our CamelCase to snake_case code. See CHEF-1009. - @solr.should_receive(:delete_database).with("chunky_bacon") - @solr.stub!(:reindex_all).with(Chef::ApiClient) - @solr.stub!(:reindex_all).with(Chef::Node) - @solr.stub!(:reindex_all).with(Chef::Role) - @solr.should_not_receive(:reindex_all).with(Chef::OpenIDRegistration) - @solr.should_not_receive(:reindex_all).with(Chef::WebUIUser) - Chef::DataBag.stub!(:cdb_list).and_return([]) - - @solr.rebuild_index - end - - it "reindexes databags" do - one_data_item = Chef::DataBagItem.new - one_data_item.raw_data = {"maybe"=>"snakes actually are evil", "id" => "just_sayin"} - two_data_item = Chef::DataBagItem.new - two_data_item.raw_data = {"tone_depth"=>"rumble_fish", "id" => "eff_yes"} - data_bag = Chef::DataBag.new - data_bag.stub!(:list).and_return([one_data_item, two_data_item]) - - @solr.should_receive(:delete_database).with("chunky_bacon") - @solr.stub!(:reindex_all) - Chef::DataBag.stub!(:cdb_list).and_return([data_bag]) - - data_bag.should_receive(:add_to_index) - one_data_item.should_receive(:add_to_index) - two_data_item.should_receive(:add_to_index) - - @solr.rebuild_index["Chef::DataBag"].should == "success" - end - end -end |