class Kriterion::Worker

Attributes

backend[R]
metrics[R]
queue_uri[R]
standards[R]

Public Class Methods

new(opts = {}) click to toggle source
# File lib/kriterion/worker.rb, line 22
def initialize(opts = {})
  @queue_uri, @metrics, @backend = Kriterion::Connector.connections(opts)

  # TODO: Work out how workers are going to get the list of standards frmo
  # the API runner
  # TODO: Remove placeholder code
  standards_dir   = File.expand_path('standards', Kriterion::ROOT)
  @standards      = Kriterion.standards([standards_dir])
  logger.info "Initialised Kritioner worker version #{Kriterion::VERSION}"
end

Public Instance Methods

process_report(report) click to toggle source
# File lib/kriterion/worker.rb, line 33
def process_report(report)
  report = Kriterion::Report.new(report)

  # Check if the report contains any relevant resources
  standard_names     = standards.keys
  relevant_resources = report.resources_with_tags(standard_names)

  return nil if relevant_resources.empty?

  logger.info "Processing report with #{relevant_resources.count} relevant resources"

  # Purge all old events relevant to this node, they will be re-added
  backend.purge_events! report.certname

  # Process the report
  affected_standards = relevant_resources.group_by do |resource|
    # Select the standard tag
    stds = (resource.tags & standard_names)

    raise 'Found a resource that was relevant to more than one standard. This is not yet supported' if stds.length > 1

    stds[0]
  end

  affected_standards.each do |name, resources|
    standard = backend.find_standard({ name: name }, recurse: true)
    unless standard
      # If the standard doesn't yet exist in the backed, add it
      standard = Kriterion::Standard.new(@standards[name])
      logger.debug "Adding starndard #{standard.name} to backend"
      backend.ensure_standard(standard)
      # TODO: See if there is a better way to deal with this, the reason I'm
      # doing this is that I want to make sure that there is not difference
      # between a newly created object and one that came from the database
      standard = backend.find_standard(
        { name: name },
        recurse: true
      )
    end

    resources.each do |resource|
      # Get the section tag
      section_tag = resource.tags.select do |t|
        standard.item_syntax.match(t)
      end

      # TODO: Make this work
      raise 'Found a resource relevant to multiple sections' if section_tag.length > 1

      section_tag = section_tag.first

      # Go though all sections and subsections and create them if required
      captures = standard.item_syntax.match(section_tag).captures - [nil]

      # Convert the captures to a list of sections, but excluse the last one
      # because that will be the name of the item
      parent_sections = captures_to_sections(standard, captures[0..-2])

      # If there are no captures then this is a direct child of a standard
      if captures.nil?
        section = standard
      else
        section = parent_sections.reduce(standard) do |previous, current|
          # If the section already exists return it
          if previous.find_section(current)
            previous.find_section(current)
          else
            # Get the details from the standards database (name, description
            # etc.)
            current_section = @standards[name]['sections'].select do |s|
              s['name'] == current
            end[0]

            if current_section.nil?
              previous
            else
              # Create the new section object
              current_section['standard']    = standard.name
              current_section['parent_type'] = previous.type
              current_section['parent_uuid'] = previous.uuid
              current_section = Kriterion::Section.new(current_section)

              # Add the section to the backend
              backend.ensure_section(current_section)
              current_section
            end
          end
        end
      end

      # Create and add the item if it doesn't yet exist
      item = case section.items.select { |i| i.id == section_tag }.count
             when 1
               # The item already exists, return it
               section.items.select { |i| i.id == section_tag }[0]
             when 0
               # The item does not exist, create it, add to the database,
               # then return it
               item_details = @standards[name]['items'].select do |i|
                 i['id'].upcase == section_tag.upcase
               end[0]
               item_details['parent_uuid']  = section.uuid
               item_details['parent_type']  = section.type
               item_details['section_path'] = captures
               backend.ensure_item(Kriterion::Item.new(item_details))
             else
               raise "Found muliple sections with the id #{section_tag}"
             end

      # Add extra contextual data to that resource
      resource.parent_uuid = item.uuid

      # Add the new resource to the backend if it doesn't exist
      unless item.resources.include? resource
        item.resources << resource
        backend.ensure_resource(resource)
      end

      # Inform the database that this node is unchanged if we have no events
      if resource.events.empty?
        backend.add_unchanged_node(resource, report.certname)
      end

      # Add all events to the database
      resource.events = resource.events.map do |event|
        event          = Kriterion::Event.new(event)
        event.certname = report.certname
        event.resource = resource.resource
        backend.ensure_event(event)
        event
      end
    end

    # Reload the standard as new sections may have been added
    standard = backend.find_standard(
      { name: name },
      recurse: true
    )

    metrics[:update_compliance] += Benchmark.realtime do
      # Recalculate the compliance of a given standard once it is done (This
      # also calculates the compliacne of all children and yeilds them to
      # block)
      standard.flush_compliance! do |child|
        backend.update_compliance! child
      end
    end
  end
end
run() click to toggle source
# File lib/kriterion/worker.rb, line 183
def run
  while true do
    # Connect and check if there is anythong on the queue
    # TODO: Change this so that they listen properly
    logger.debug "GET #{queue_uri}"
    begin
      response = Net::HTTP.get_response(queue_uri)

      case response.code
      when '204'
        logger.debug 'Queue empty, sleeping...'
        sleep 3
      when '200'
        logger.debug 'Got a report, parsing...'
        report = JSON.parse(JSON.parse(response.body)['value'])
        logger.info "Processing report: #{report['host']} #{report['time']}"

        metrics[:total_processing] += Benchmark.realtime do
          process_report(report)
        end

        metrics.print
        metrics.reset!
      end
    rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError,
           Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
           Net::ProtocolError, Errno::ECONNREFUSED, SocketError => e
      logger.error "Error while running: #{e}"
      logger.info 'Sleeping...'
      sleep 3
    end
  end
end

Private Instance Methods

captures_to_sections(standard, captures) click to toggle source
# File lib/kriterion/worker.rb, line 219
def captures_to_sections(standard, captures)
  sections = []

  captures.each_index do |index|
    sections << captures[0..index].join(standard.section_separator)
  end

  sections
end