Skip to content
This repository has been archived by the owner on Oct 9, 2018. It is now read-only.

Commit

Permalink
Ref #9492 - serve gutterball reports
Browse files Browse the repository at this point in the history
  • Loading branch information
komidore64 committed Feb 20, 2015
1 parent 3c84b55 commit e6b1bc1
Show file tree
Hide file tree
Showing 13 changed files with 201 additions and 60 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Expand Up @@ -11,6 +11,7 @@ capybara-*.html
rerun.txt
pickle-email-*.html
pkg
.*.sw?

# TODO Comment out these rules if you are OK with secrets being uploaded to the repo
config/initializers/secret_token.rb
Expand All @@ -33,4 +34,8 @@ Gemfile.lock
*.bowerrc
bower.json

# rpm build artifacts
SRPMs

# config stuff
config/foreman_gutterball.yaml
Empty file removed app/controllers/.gitkeep
Empty file.
Expand Up @@ -2,46 +2,70 @@ module ForemanGutterball
module Api
module V2
class ContentReportsController < ::Katello::Api::V2::ApiController
api :GET, '/content_reports', 'List available reports'
def index
render :json => service.report_details
before_filter :find_organization, :only => [:system_status, :system_trend, :status_trend]

api :GET, '/content_reports/system_status', N_('Show the latest subscription status for a list of content ' \
'hosts that have reported their subscription status during a specified time period. Running this report ' \
'with minimal parameters will return all status records for all reported content hosts.')
param :system_id, :identifier, :desc => N_('Filters the results by the given content host UUID.')
param :organization_id, :identifier, :desc => N_('Organization ID'), :required => true
param :status, ['valid', 'invalid', 'partial'], :desc => N_('Filter results on content host status.')
param :on_date, Date, :desc => N_('Date to filter on. If not given, defaults to NOW. Results will be limited ' \
'to status records that were last reported before or on the given date. ' \
'Must be a date in the form of YYYY-MM-DD.')
def system_status
zomg_reports!('consumer_status')
end

api :GET, '/content_reports/:id', 'Report details'
def show
report = service.report_details params[:id]
render :json => report
api :GET, '/content_reports/system_trend', N_('Show a listing of all subscription status snapshots from ' \
'content hosts which have reported their subscription status in the specified time period.')
param :system_id,
:identifier,
:desc => N_('Filters the results by the given content host UUID.'),
:required => true
param :organization_id, :identifier, :desc => N_('Organization ID'), :required => true
param :start_date, Date, :desc => N_('Start date. Used in conjunction with end_date. ' \
'Must be a date in the form of YYYY-MM-DD.')
param :end_date, Date, :desc => N_('End date. Used in conjunction with start_date. ' \
'Must be a date in the form of YYYY-MM-DD.')
param :hours, Integer,
:desc => N_('Show a trend between HOURS and now. Used independently of start_date/end_date.')
def system_trend
zomg_reports!('consumer_trend')
end

api :GET, '/content_reports/:id/run', 'Generate reports'
def run
query_params = request.query_parameters.symbolize_keys
validate_params(query_params.keys) unless query_params.empty?
report = service.run_reports(params[:id], query_params)
render :json => report
api :GET, '/content_reports/status_trend', N_('Show the per-day counts of content-hosts, grouped by ' \
'subscription status, optionally limited to a date range.')
param :organization_id, :identifier, :desc => N_('Organization ID'), :required => true
param :start_date, Date, :desc => N_('Start date. Used in conjunction with end_date. ' \
'Must be a date in the form of YYYY-MM-DD.')
param :end_date, Date, :desc => N_('End date. Used in conjunction with start_date. ' \
'Must be a date in the form of YYYY-MM-DD.')
def status_trend
zomg_reports!('status_trend')
end

private

def consumer_status
[:consumer_uuid, :owner, :status, :on_date, :page, :per_page, :custom]
def zomg_reports!(report_type)
task = async_task(::Actions::ForemanGutterball::ContentReports::Report, report_type, param_filter(params))
respond_for_async :resource => task
end

def consumer_trend
[:consumer_uuid, :hours, :start_date, :end_date, :custom]
def param_filter(params)
send("#{params[:action]}_filter", params)
end

def status_trend
[:start_date, :end_date, :owner, :sku, :subscription_name, :management_enabled, :timezone]
def system_status_filter(params)
params.permit(*%w(system_id organization_id status on_date))
end

def service
GutterballService.new
def system_trend_filter(params)
params.permit(*%w(system_id organization_id hours start_date end_date))
end

def validate_params(query_keys)
report_keys = send(params[:id])
fail Katello::HttpErrors::BadRequest, _('Invalid parameters') unless report_keys & query_keys == query_keys
def status_trend_filter(params)
params.permit(*%w(organization_id start_date end_date))
end
end
end
Expand Down
20 changes: 20 additions & 0 deletions app/lib/actions/foreman_gutterball/content_reports/report.rb
@@ -0,0 +1,20 @@
module Actions
module ForemanGutterball
module ContentReports
class Report < Actions::EntryAction
def plan(report_type, params)
plan_self(:report_type => report_type, :params => params)
end

def run
service = ::ForemanGutterball::GutterballService.new
output[:report_data] = service.report(input[:report_type], input[:params])
end

def humanized_output
''
end
end
end
end
end
82 changes: 65 additions & 17 deletions app/services/foreman_gutterball/gutterball_service.rb
@@ -1,14 +1,16 @@
require 'rest_client'

module ForemanGutterball
class GutterballService < ::Katello::HttpResource
cfg = SETTINGS.with_indifferent_access
url = cfg[:foreman_gutterball][:url]
self.prefix = URI.parse(url).path
self.site = url.gsub(prefix, '')
self.consumer_secret = cfg[:oauth_consumer_secret]
self.consumer_key = cfg[:oauth_consumer_key]
self.ca_cert_file = cfg[:ca_cert_file]
def initialize
cfg = SETTINGS.with_indifferent_access
url = cfg['foreman_gutterball']['url']
@uri = URI.parse(url)
self.prefix = @uri.path
self.site = "#{@uri.scheme}://#{@uri.host}:#{@uri.port}"
self.class.site = site
self.consumer_secret = cfg[:oauth_consumer_secret]
self.consumer_key = cfg[:oauth_consumer_key]
self.ca_cert_file = cfg[:ca_cert_file]
end

def self.default_headers
{ 'accept' => 'application/json',
Expand All @@ -20,20 +22,66 @@ def self.logger
::Logging.logger['gutterball_service']
end

def report_details(report_key = nil)
report_path = "#{prefix}reports/#{report_key}"
get_reports(report_path, default_headers)
def report_details(report_key)
path = self.class.join_path(prefix, 'reports', report_key)
JSON.parse self.class.get(path, default_headers)
end

def hash_to_query(query_parameters)
query_parameters.reduce('?') do |result, (current_key, current_value) |
result << '&' unless result == '?'
if current_value.is_a?(Array)
result << current_value.map { |value| "#{current_key}=#{self.class.url_encode(value)}" }.join('&')
else
result << "#{current_key}=#{self.class.url_encode(current_value)}"
end
end
end

def run_reports(report_key, query_params = nil)
report_path = "#{prefix}reports/#{report_key}/run?#{query_params.to_query}"
get_reports(report_path, default_headers)
def report(report_key, query_params)
format_query(query_params)
path = self.class.join_path(prefix, 'reports', report_key, 'run', hash_to_query(query_params))
raw = self.class.get(path, default_headers)
resp = JSON.parse(raw)
send("format_#{report_key}_response", resp)
end

private

def get_reports(path, headers = {})
self.class.get(path, headers)
def format_query(params)
if params[:system_id]
params[:consumer_uuid] = params.delete(:system_id)
end

params[:owner] = Organization.find(params[:organization_id]).label
params.delete(:organization_id)
end

def format_consumer_status_response(response)
response.map do |member|
{ :name => member['consumer']['name'],
:status => member['status']['status'],
:date => iso8601_to_yyyy_mm_dd(member['status']['date']) }
end
end

def format_consumer_trend_response(response)
response.map do |member|
{ :date => iso8601_to_yyyy_mm_dd(member['status']['date']),
:status => member['status']['status'] }
end
end

def format_status_trend_response(response)
template = { :valid => 0, :invalid => 0, :partial => 0 }
response.reduce([]) do |resp, (datetime, values)|
resp << template.merge(values).merge(:date => iso8601_to_yyyy_mm_dd(datetime))
resp
end
end

def iso8601_to_yyyy_mm_dd(datetime)
DateTime.iso8601(datetime).strftime('%Y-%m-%d')
end
end
end
Empty file removed config/foreman_gutterball.yaml
Empty file.
2 changes: 2 additions & 0 deletions config/foreman_gutterball.yaml.example
@@ -0,0 +1,2 @@
foreman_gutterball:
url: https://localhost:8443/gutterball
18 changes: 12 additions & 6 deletions config/routes.rb
@@ -1,11 +1,17 @@
Rails.application.routes.draw do
scope :module => 'foreman_gutterball', :path => '/katello' do
scope :module => 'api' do
scope :module => 'v2' do
resources :content_reports,
:only => [:index, :show],
:constraints => { :id => /(status_trend|consumer_trend|consumer_status)/i } do
get :run, :on => :member
namespace :api do
scope '(:api_version)',
:module => :v2,
:defaults => { :api_version => 'v2' },
:api_version => /v2/,
:constraints => ApiConstraints.new(:version => 2, :default => true) do
resources :content_reports, :only => [] do
collection do
get :system_status
get :system_trend
get :status_trend
end
end
end
end
Expand Down
33 changes: 33 additions & 0 deletions lib/foreman_gutterball/apipie/validators.rb
@@ -0,0 +1,33 @@
module ForemanGutterball
module Apipie
module Validators
class DateValidator < ::Apipie::Validator::BaseValidator
def initialize(param_description, argument)
super(param_description)
@type = argument
end

def validate(value)
return false if value.nil?
return false unless value =~ /\A\d{4}(-\d{1,2}){2}\z/
begin
Date.parse(value) # make sure this is a valid date and not 2015-02-45, etc.
rescue
return false
end
true
end

def self.build(param_description, argument, _options, _block)
if argument == Date
new(param_description, argument)
end
end

def description
"Must be a #{@type} in the form of YYYY-MM-DD."
end
end
end
end
end
20 changes: 12 additions & 8 deletions lib/foreman_gutterball/engine.rb
Expand Up @@ -12,17 +12,21 @@ class Engine < ::Rails::Engine

initializer 'foreman_gutterball.register_plugin', :after => :finisher_hook do |_app|
Foreman::Plugin.register :foreman_gutterball do
requires_foreman '>= 1.4'
requires_foreman '>= 1.7'
end
end

# Include concerns in this config.to_prepare block
config.to_prepare do
begin
# TODO: remove rubocop disable once begin block has content
rescue => e # rubocop:disable Style/IndentationWidth
Rails.logger.warn "ForemanGutterball: skipping engine hook (#{e})"
end
initializer 'foreman_gutterball.apipie' do
Apipie.configuration.api_controllers_matcher << "#{ForemanGutterball::Engine.root}" \
'/app/controllers/foreman_gutterball/api/v2/*.rb'
Apipie.configuration.checksum_path += ['/foreman_gutterball/api/']
require 'foreman_gutterball/apipie/validators'
end

initializer 'foreman_gutterball.register_actions', :before => 'foreman_tasks.initialize_dynflow' do
ForemanTasks.dynflow.require!
ForemanTasks.dynflow.config.eager_load_paths.concat(
["#{ForemanGutterball::Engine.root}/app/lib/foreman_gutterball/actions"])
end

rake_tasks do
Expand Down
6 changes: 3 additions & 3 deletions lib/tasks/foreman_gutterball_tasks.rake
Expand Up @@ -10,10 +10,10 @@ end

# Tests
namespace :test do
desc "Test ForemanGutterball"
desc 'Test ForemanGutterball'
Rake::TestTask.new(:foreman_gutterball) do |t|
test_dir = File.join(File.dirname(__FILE__), '../..', 'test')
t.libs << ["test",test_dir]
t.libs << ['test', test_dir]
t.pattern = "#{test_dir}/**/*_test.rb"
t.verbose = true
end
Expand All @@ -25,7 +25,7 @@ end

load 'tasks/jenkins.rake'
if Rake::Task.task_defined?(:'jenkins:unit')
Rake::Task["jenkins:unit"].enhance do
Rake::Task['jenkins:unit'].enhance do
Rake::Task['test:foreman_gutterball'].invoke
end
end
2 changes: 0 additions & 2 deletions rubygem-foreman_gutterball.spec
Expand Up @@ -74,7 +74,6 @@ gem install --local --install-dir .%{gem_dir} --force %{SOURCE0} --no-rdoc --no-
%foreman_bundlerd_file

%{__install} --directory %{buildroot}%{foreman_dir}/plugins
%{__install} .%{gem_instdir}/config/%{gem_name}.yaml %{buildroot}%{foreman_dir}/plugins/

%clean
%{__rm} --recursive --force %{buildroot} .%{gem_dir}
Expand All @@ -91,7 +90,6 @@ gem install --local --install-dir .%{gem_dir} --force %{SOURCE0} --no-rdoc --no-
%{gem_spec}
%{gem_cache}
%{foreman_bundlerd_plugin}
%config(noreplace) %{foreman_dir}/plugins/%{gem_name}.yaml

%changelog
* Wed Dec 03 2014 Adam Price <komidore64@gmail.com> - 0.0.1-1
Expand Down
1 change: 1 addition & 0 deletions test/test_plugin_helper.rb
@@ -1,5 +1,6 @@
# This calls the main test_helper in Foreman-core
require 'test_helper'
require 'rails/test_help'

# Add plugin to FactoryGirl's paths
FactoryGirl.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
Expand Down

0 comments on commit e6b1bc1

Please sign in to comment.