Skip to content

Commit

Permalink
Merge pull request #1102 from agrare/add_metrics_api
Browse files Browse the repository at this point in the history
Add a GET /api/metrics endpoint
  • Loading branch information
Fryguy authored Nov 18, 2021
2 parents de79030 + d67408f commit fb2dabd
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 0 deletions.
13 changes: 13 additions & 0 deletions app/controllers/api/metrics_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Api
class MetricsController < BaseController
def index
metrics_service = MetricsService.new(params)

resources = metrics_service.query_metrics
res = collection_filterer(resources, :metrics, Metric).flatten
counts = Api::QueryCounts.new(Metric.count, res.count, resources.count)

render_collection(:metrics, res, :counts => counts, :expand_resources => @req.expand?(:resources))
end
end
end
17 changes: 17 additions & 0 deletions app/controllers/api/subcollections/metrics.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Api
module Subcollections
module Metrics
RESOURCE_TYPES = {
'vms' => 'VmOrTemplate'
}.freeze

def metrics_query_resource(object)
params[:resource_type] = RESOURCE_TYPES[@req.collection] || object.class.to_s
params[:resource_ids] ||= [object.id]

metrics_service = MetricsService.new(params)
metrics_service.query_metrics
end
end
end
end
1 change: 1 addition & 0 deletions app/controllers/api/vms_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class VmsController < BaseController
include Subcollections::Compliances
include Subcollections::CustomAttributes
include Subcollections::Disks
include Subcollections::Metrics
include Subcollections::MetricRollups
include Subcollections::Policies
include Subcollections::PolicyProfiles
Expand Down
21 changes: 21 additions & 0 deletions config/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2255,6 +2255,26 @@
:identifier:
- metrics_view
- sui_metrics
:metrics:
:description: Metrics
:identifier: metrics
:options:
- :collection
- :subcollection
:verbs: *g
:klass: Metric
:collection_actions:
:get:
- :name: read
:identifier:
- metrics_view
- sui_metrics
:subcollection_actions:
:get:
- :name: read
:identifier:
- metrics_view
- sui_metrics
:network_routers:
:description: Network_Routers
:identifier: network_router
Expand Down Expand Up @@ -4676,6 +4696,7 @@
- :security_groups
- :software
- :snapshots
- :metrics
- :metric_rollups
:collection_actions:
:get:
Expand Down
26 changes: 26 additions & 0 deletions lib/services/api/metrics_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
module Api
class MetricsService
REQUIRED_FILTER_PARAMETERS = %w[resource_type start_date].freeze
QUERY_FILTER_PARAMETERS = %w[resource_ids end_date].freeze

attr_reader :filter_parameters

def initialize(parameters)
@filter_parameters = parameters.slice(*(REQUIRED_FILTER_PARAMETERS + QUERY_FILTER_PARAMETERS))
validate_required_filter_parameters
end

def query_metrics
start_date = filter_parameters[:start_date].to_date
end_date = filter_parameters[:end_date].try(:to_date)
Metric.metrics_in_range(filter_parameters[:resource_type], filter_parameters[:resource_ids], start_date, end_date)
end

private

def validate_required_filter_parameters
not_specified = REQUIRED_FILTER_PARAMETERS - filter_parameters.keys
raise BadRequestError, "Must specify #{not_specified.join(', ')}" unless not_specified.empty?
end
end
end
140 changes: 140 additions & 0 deletions spec/requests/metrics_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
RSpec.describe 'Metrics API' do
describe 'GET /api/metrics' do
before do
FactoryBot.create(:metric_vm_rt)
FactoryBot.create(:metric_host_rt)
FactoryBot.create(:metric_container_node_rt)
end

it 'returns metrics for a specific resource_type' do
api_basic_authorize collection_action_identifier(:metrics, :read, :get)

get(api_metrics_url, :params => {:resource_type => 'VmOrTemplate', :start_date => Time.zone.today.to_s})

expected = {
'count' => 3,
'subcount' => 1
}
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(expected)
end

it 'returns metrics for specific resources' do
vm = FactoryBot.create(:vm_or_template)
vm_metric = FactoryBot.create(:metric_vm_rt, :resource => vm)
api_basic_authorize collection_action_identifier(:metrics, :read, :get)

get(
api_metrics_url,
:params => {
:resource_type => 'VmOrTemplate',
:resource_ids => [vm.id],
:start_date => Time.zone.today.to_s
}
)

expected = {
'count' => 4,
'subcount' => 1,
'resources' => [
{'href' => a_string_including(api_metric_url(nil, vm_metric))}
]
}
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(expected)
end

let(:today) { Time.zone.today }
let(:tomorrow) { today + 1.day }
let(:next_week) { today + 7.days }

it 'returns metrics between specific dates' do
vm = FactoryBot.create(:vm_or_template)
vm_metric = FactoryBot.create(:metric_vm_rt, :resource => vm)
FactoryBot.create(:metric_vm_rt, :resource => vm, :timestamp => next_week)

api_basic_authorize collection_action_identifier(:metrics, :read, :get)

get(
api_metrics_url,
:params => {
:resource_type => 'VmOrTemplate',
:resource_ids => [vm.id],
:start_date => today.to_s,
:end_date => tomorrow.to_s,
}
)

expected = {
'count' => 5,
'subcount' => 1,
'resources' => [
{'href' => a_string_including(api_metric_url(nil, vm_metric))}
]
}
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(expected)
end

it 'requires parameters' do
api_basic_authorize collection_action_identifier(:metrics, :read, :get)

get api_metrics_url

expected = {
'error' => a_hash_including(
'message' => 'Must specify resource_type, start_date'
)
}
expect(response).to have_http_status(:bad_request)
expect(response.parsed_body).to include(expected)
end

it 'pages the request by default' do
api_basic_authorize collection_action_identifier(:metrics, :read, :get)

get(
api_metrics_url,
:params => {
:resource_type => 'VmOrTemplate',
:capture_interval => 'daily',
:start_date => Time.zone.today.to_s
}
)
expected = {
'count' => 3,
'subcount' => 1,
'subquery_count' => 1,
'pages' => 1
}
expect(response.parsed_body).to include(expected)
expect(response.parsed_body['links'].keys).to match_array(%w[self first last])
end

it 'can override the default limit' do
vm = FactoryBot.create(:vm_or_template)
FactoryBot.create_list(:metric_vm_rt, 3, :resource => vm)
api_basic_authorize collection_action_identifier(:metrics, :read, :get)

get(
api_metrics_url,
:params => {
:resource_type => 'VmOrTemplate',
:resource_ids => [vm.id],
:capture_interval => 'hourly',
:start_date => Time.zone.today.to_s,
:limit => 1
}
)

expected = {
'count' => 6,
'subcount' => 1,
'subquery_count' => 3,
'pages' => 3
}
expect(response.parsed_body).to include(expected)
expect(response.parsed_body['links'].keys).to match_array(%w[self next first last])
end
end
end
31 changes: 31 additions & 0 deletions spec/requests/vms_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2148,6 +2148,37 @@ def query_match_regexp(*tables)
end
end

describe "metrics subcollection" do
let(:url) { api_vm_metrics_url(nil, vm) }

before do
FactoryBot.create_list(:metric_vm_rt, 3, :resource => vm)
end

it 'returns the metrics for the vm' do
api_basic_authorize subcollection_action_identifier(:vms, :metrics, :read, :get)

get(url, :params => {:start_date => Time.zone.today.to_s})

expected = {
'count' => 3,
'subcount' => 3,
'pages' => 1
}
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to include(expected)
expect(response.parsed_body['links'].keys).to match_array(%w[self first last])
end

it 'will not return metrics without an appropriate role' do
api_basic_authorize

get(url, :params => {:start_date => Time.zone.today.to_s})

expect(response).to have_http_status(:forbidden)
end
end

describe "metric rollups subcollection" do
let(:url) { api_vm_metric_rollups_url(nil, vm) }

Expand Down

0 comments on commit fb2dabd

Please sign in to comment.