Class: Chef::Expander::Solrizer

Inherits:
Object
  • Object
show all
Includes:
Loggable
Defined in:
lib/chef/expander/solrizer.rb

Constant Summary collapse

ADD =
"add"
DELETE =
"delete"
SKIP =
"skip"
ITEM =
"item"
ID =
"id"
TYPE =
"type"
DATABASE =
"database"
ENQUEUED_AT =
"enqueued_at"
DATA_BAG_ITEM =
"data_bag_item"
DATA_BAG =
"data_bag"
X_CHEF_id_CHEF_X =
'X_CHEF_id_CHEF_X'
X_CHEF_database_CHEF_X =
'X_CHEF_database_CHEF_X'
X_CHEF_type_CHEF_X =
'X_CHEF_type_CHEF_X'
CONTENT_TYPE_XML =
{"Content-Type" => "text/xml"}
START_XML =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
ADD_DOC =
"<add><doc>"
DELETE_DOC =
"<delete>"
ID_OPEN =
"<id>"
ID_CLOSE =
"</id>"
END_ADD_DOC =
"</doc></add>\n"
END_DELETE =
"</delete>\n"
START_CONTENT =
'<field name="content">'
CLOSE_FIELD =
"</field>"
FLD_CHEF_ID_FMT =
'<field name="X_CHEF_id_CHEF_X">%s</field>'
FLD_CHEF_DB_FMT =
'<field name="X_CHEF_database_CHEF_X">%s</field>'
FLD_CHEF_TY_FMT =
'<field name="X_CHEF_type_CHEF_X">%s</field>'
FLD_DATA_BAG =
'<field name="data_bag">%s</field>'
KEYVAL_FMT =
"%s__=__%s "

Constants included from Loggable

Loggable::LOGGER

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Loggable

#log

Constructor Details

#initialize(object_command_json, &on_completion_block) ⇒ Solrizer

Returns a new instance of Solrizer.



85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/chef/expander/solrizer.rb', line 85

def initialize(object_command_json, &on_completion_block)
  @start_time = Time.now.to_f
  @on_completion_block = on_completion_block
  if parsed_message    = parse(object_command_json)
    @action           = parsed_message["action"]
    @indexer_payload  = parsed_message["payload"]

    extract_object_fields if @indexer_payload
  else
    @action = SKIP
  end
end

Instance Attribute Details

#actionObject (readonly)

Returns the value of attribute action.



71
72
73
# File 'lib/chef/expander/solrizer.rb', line 71

def action
  @action
end

#chef_objectObject (readonly)

Returns the value of attribute chef_object.



75
76
77
# File 'lib/chef/expander/solrizer.rb', line 75

def chef_object
  @chef_object
end

#databaseObject (readonly)

Returns the value of attribute database.



81
82
83
# File 'lib/chef/expander/solrizer.rb', line 81

def database
  @database
end

#enqueued_atObject (readonly)

Returns the value of attribute enqueued_at.



83
84
85
# File 'lib/chef/expander/solrizer.rb', line 83

def enqueued_at
  @enqueued_at
end

#indexer_payloadObject (readonly)

Returns the value of attribute indexer_payload.



73
74
75
# File 'lib/chef/expander/solrizer.rb', line 73

def indexer_payload
  @indexer_payload
end

#obj_idObject (readonly)

Returns the value of attribute obj_id.



77
78
79
# File 'lib/chef/expander/solrizer.rb', line 77

def obj_id
  @obj_id
end

#obj_typeObject (readonly)

Returns the value of attribute obj_type.



79
80
81
# File 'lib/chef/expander/solrizer.rb', line 79

def obj_type
  @obj_type
end

Class Method Details

.clear_http_requestsObject



46
47
48
# File 'lib/chef/expander/solrizer.rb', line 46

def self.clear_http_requests
  @active_http_requests.clear
end

.http_request_completed(instance) ⇒ Object



38
39
40
# File 'lib/chef/expander/solrizer.rb', line 38

def self.http_request_completed(instance)
  @active_http_requests.delete(instance)
end

.http_request_started(instance) ⇒ Object



34
35
36
# File 'lib/chef/expander/solrizer.rb', line 34

def self.http_request_started(instance)
  @active_http_requests << instance
end

.http_requests_active?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'lib/chef/expander/solrizer.rb', line 42

def self.http_requests_active?
  !@active_http_requests.empty?
end

Instance Method Details

#addObject



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/chef/expander/solrizer.rb', line 128

def add
  post_to_solr(pointyize_add) do
    ["indexed #{indexed_object}",
     "transit,xml,solr-post |",
     [transit_time, @xml_time, @solr_post_time].join(","),
     "|"
    ].join(" ")
  end
rescue Exception => e
  log.error { "#{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}"}
end

#completedObject



243
244
245
246
247
# File 'lib/chef/expander/solrizer.rb', line 243

def completed
  @solr_post_time = Time.now.to_f - @start_time
  self.class.http_request_completed(self)
  @on_completion_block.call
end

#deleteObject



140
141
142
143
144
# File 'lib/chef/expander/solrizer.rb', line 140

def delete
  post_to_solr(pointyize_delete) { "deleted #{indexed_object} transit-time[#{transit_time}s]"}
rescue Exception => e
  log.error { "#{e.class.name}: #{e.message}\n#{e.backtrace.join("\n")}"}
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


265
266
267
# File 'lib/chef/expander/solrizer.rb', line 265

def eql?(other)
  other.hash == hash
end

#extract_object_fieldsObject



98
99
100
101
102
103
104
105
# File 'lib/chef/expander/solrizer.rb', line 98

def extract_object_fields
  @chef_object = @indexer_payload[ITEM]
  @database    = @indexer_payload[DATABASE]
  @obj_id      = @indexer_payload[ID]
  @obj_type    = @indexer_payload[TYPE]
  @enqueued_at = @indexer_payload[ENQUEUED_AT]
  @data_bag = @obj_type == DATA_BAG_ITEM ? @chef_object[DATA_BAG] : nil
end

#flattened_objectObject



146
147
148
149
150
151
152
153
154
155
156
# File 'lib/chef/expander/solrizer.rb', line 146

def flattened_object
  flattened_object = Flattener.new(@chef_object).flattened_item
 
  flattened_object[X_CHEF_id_CHEF_X]        = [@obj_id]
  flattened_object[X_CHEF_database_CHEF_X]  = [@database]
  flattened_object[X_CHEF_type_CHEF_X]      = [@obj_type]

  log.debug {"adding flattened object to Solr: #{flattened_object.inspect}"}

  flattened_object
end

#hashObject



269
270
271
# File 'lib/chef/expander/solrizer.rb', line 269

def hash
  "#{action}#{indexed_object}#@enqueued_at#{self.class.name}".hash
end

#http_request_startedObject



261
262
263
# File 'lib/chef/expander/solrizer.rb', line 261

def http_request_started
  self.class.http_request_started(self)
end

#indexed_objectObject



257
258
259
# File 'lib/chef/expander/solrizer.rb', line 257

def indexed_object
  "#{@obj_type}[#{@obj_id}] database[#{@database}]"
end

#parse(serialized_object) ⇒ Object



107
108
109
110
111
# File 'lib/chef/expander/solrizer.rb', line 107

def parse(serialized_object)
  Yajl::Parser.parse(serialized_object)
rescue Yajl::ParseError
  log.error { "cannot index object because it is invalid JSON: #{serialized_object}" }
end

#pointyize_addObject

Takes a flattened hash where the values are arrays and converts it into a dignified XML document suitable for POST to Solr. The general structure of the output document is like this:

<?xml version="1.0" encoding="UTF-8"?>
<add>
  <doc>
    <field name="content">
        key__=__value
        key__=__another_value
        other_key__=__yet another value
    </field>
  </doc>
</add>

The document as generated has minimal newlines and formatting, however.



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/chef/expander/solrizer.rb', line 189

def pointyize_add
  xml = ""
  xml << START_XML << ADD_DOC
  xml << (FLD_CHEF_ID_FMT % @obj_id)
  xml << (FLD_CHEF_DB_FMT % @database)
  xml << (FLD_CHEF_TY_FMT % @obj_type)
  xml << START_CONTENT
  content = ""
  flattened_object.each do |field, values|
    values.each do |v|
      content << (KEYVAL_FMT % [field, v])
    end
  end
  xml << content.fast_xs
  xml << CLOSE_FIELD      # ends content
  xml << (FLD_DATA_BAG % @data_bag.fast_xs) if @data_bag
  xml << END_ADD_DOC
  @xml_time = Time.now.to_f - @start_time
  xml
end

#pointyize_deleteObject

Takes a succinct document id, like 2342, and turns it into something even more compact, like

"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<delete><id>2342</id></delete>\n"


213
214
215
216
217
218
219
220
221
222
# File 'lib/chef/expander/solrizer.rb', line 213

def pointyize_delete
  xml = ""
  xml << START_XML
  xml << DELETE_DOC
  xml << ID_OPEN
  xml << @obj_id.to_s
  xml << ID_CLOSE
  xml << END_DELETE
  xml
end

#post_to_solr(document, &logger_block) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/chef/expander/solrizer.rb', line 224

def post_to_solr(document, &logger_block)
  log.debug("POSTing document to SOLR:\n#{document}")
  http_req = EventMachine::HttpRequest.new(solr_url).post(:body => document, :timeout => 1200, :head => CONTENT_TYPE_XML)
  http_request_started

  http_req.callback do
    completed
    if http_req.response_header.status == 200
      log.info(&logger_block)
    else
      log.error { "Failed to post to solr: #{indexed_object}" }
    end
  end
  http_req.errback do
    completed
    log.error { "Failed to post to solr (connection error): #{indexed_object}" }
  end
end

#runObject



113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/chef/expander/solrizer.rb', line 113

def run
  case @action
  when ADD
    add
  when DELETE
    delete
  when SKIP
    completed
    log.info { "not indexing this item because of malformed JSON"}
  else
    completed
    log.error { "cannot index object becuase it has an invalid action #{@action}" }
  end
end

#solr_urlObject



253
254
255
# File 'lib/chef/expander/solrizer.rb', line 253

def solr_url
  "#{Expander.config.solr_url}/update"
end

#transit_timeObject



249
250
251
# File 'lib/chef/expander/solrizer.rb', line 249

def transit_time
  Time.now.utc.to_i - @enqueued_at
end