Commit 3dc77500 authored by Mikolaj Wawrzyniak's avatar Mikolaj Wawrzyniak

Extract metrics prometheus api proxy into module

To avoid duplicated code when implementing prometheus api proxy
behavior we need to extrac common logic into dedicated module
parent 318a5f9d
# frozen_string_literal: true
module Metrics::Dashboard::PrometheusApiProxy
extend ActiveSupport::Concern
include RenderServiceResults
included do
before_action :authorize_read_prometheus!, only: [:prometheus_proxy]
end
def prometheus_proxy
variable_substitution_result =
proxy_variable_substitution_service.new(proxyable, permit_params).execute
if variable_substitution_result[:status] == :error
return error_response(variable_substitution_result)
end
prometheus_result = Prometheus::ProxyService.new(
proxyable,
proxy_method,
proxy_path,
variable_substitution_result[:params]
).execute
return continue_polling_response if prometheus_result.nil?
return error_response(prometheus_result) if prometheus_result[:status] == :error
success_response(prometheus_result)
end
private
def proxyable
raise NotImplementedError, "#{self.class} must implement method: #{__callee__}"
end
def proxy_variable_substitution_service
raise NotImplementedError, "#{self.class} must implement method: #{__callee__}"
end
def permit_params
params.permit!
end
def proxy_method
request.method
end
def proxy_path
params[:proxy_path]
end
end
# frozen_string_literal: true # frozen_string_literal: true
class Projects::Environments::PrometheusApiController < Projects::ApplicationController class Projects::Environments::PrometheusApiController < Projects::ApplicationController
include RenderServiceResults include Metrics::Dashboard::PrometheusApiProxy
before_action :authorize_read_prometheus! before_action :proxyable
before_action :environment
def proxy
variable_substitution_result =
variable_substitution_service.new(environment, permit_params).execute
if variable_substitution_result[:status] == :error
return error_response(variable_substitution_result)
end
prometheus_result = Prometheus::ProxyService.new(
environment,
proxy_method,
proxy_path,
variable_substitution_result[:params]
).execute
return continue_polling_response if prometheus_result.nil?
return error_response(prometheus_result) if prometheus_result[:status] == :error
success_response(prometheus_result)
end
private private
def variable_substitution_service def proxyable
Prometheus::ProxyVariableSubstitutionService @proxyable ||= project.environments.find(params[:id])
end
def permit_params
params.permit!
end
def environment
@environment ||= project.environments.find(params[:id])
end end
def proxy_method def proxy_variable_substitution_service
request.method Prometheus::ProxyVariableSubstitutionService
end
def proxy_path
params[:proxy_path]
end end
end end
...@@ -257,7 +257,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -257,7 +257,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# This route is also defined in gitlab-workhorse. Make sure to update accordingly. # This route is also defined in gitlab-workhorse. Make sure to update accordingly.
get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', format: false get '/terminal.ws/authorize', to: 'environments#terminal_websocket_authorize', format: false
get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#proxy', as: :prometheus_api get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#prometheus_proxy', as: :prometheus_api
get '/sample_metrics', to: 'environments/sample_metrics#query' get '/sample_metrics', to: 'environments/sample_metrics#query'
end end
......
...@@ -3,215 +3,73 @@ ...@@ -3,215 +3,73 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Projects::Environments::PrometheusApiController do RSpec.describe Projects::Environments::PrometheusApiController do
let_it_be(:project) { create(:project) }
let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:proxyable) { create(:environment, project: project) }
before do before do
project.add_reporter(user) project.add_reporter(user)
sign_in(user) sign_in(user)
end end
describe 'GET #proxy' do describe 'GET #prometheus_proxy' do
let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) } it_behaves_like 'metrics dashboard prometheus api proxy' do
let(:proxyable_params) do
let(:expected_params) do {
ActionController::Parameters.new( id: proxyable.id.to_s,
environment_params( namespace_id: project.namespace.full_path,
proxy_path: 'query', project_id: project.name
controller: 'projects/environments/prometheus_api', }
action: 'proxy'
)
).permit!
end
context 'with valid requests' do
before do
allow(Prometheus::ProxyService).to receive(:new)
.with(environment, 'GET', 'query', expected_params)
.and_return(prometheus_proxy_service)
allow(prometheus_proxy_service).to receive(:execute)
.and_return(service_result)
end end
context 'with success result' do context 'with variables' do
let(:service_result) { { status: :success, body: prometheus_body } }
let(:prometheus_body) { '{"status":"success"}' } let(:prometheus_body) { '{"status":"success"}' }
let(:prometheus_json_body) { Gitlab::Json.parse(prometheus_body) } let(:pod_name) { "pod1" }
it 'returns prometheus response' do before do
get :proxy, params: environment_params expected_params[:query] = %{up{pod_name="#{pod_name}"}}
expected_params[:variables] = { 'pod_name' => pod_name }
expect(Prometheus::ProxyService).to have_received(:new)
.with(environment, 'GET', 'query', expected_params)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(prometheus_json_body)
end end
context 'with format string' do it 'replaces variables with values' do
before do get :prometheus_proxy, params: prometheus_proxy_params.merge(
expected_params[:query] = %{up{environment="#{environment.slug}"}} query: 'up{pod_name="{{pod_name}}"}', variables: { 'pod_name' => pod_name }
end )
it 'replaces variables with values' do
get :proxy, params: environment_params.merge(query: 'up{environment="{{ci_environment_slug}}"}')
expect(Prometheus::ProxyService).to have_received(:new)
.with(environment, 'GET', 'query', expected_params)
end
context 'with nil query' do
let(:params_without_query) do
environment_params.except(:query)
end
before do
expected_params.delete(:query)
end
it 'does not raise error' do
get :proxy, params: params_without_query
expect(Prometheus::ProxyService).to have_received(:new) expect(response).to have_gitlab_http_status(:success)
.with(environment, 'GET', 'query', expected_params) expect(Prometheus::ProxyService).to have_received(:new)
end .with(proxyable, 'GET', 'query', expected_params)
end
end end
context 'with variables' do context 'with invalid variables' do
let(:pod_name) { "pod1" } let(:params_with_invalid_variables) do
prometheus_proxy_params.merge(
before do query: 'up{pod_name="{{pod_name}}"}', variables: ['a']
expected_params[:query] = %{up{pod_name="#{pod_name}"}}
expected_params[:variables] = { 'pod_name' => pod_name }
end
it 'replaces variables with values' do
get :proxy, params: environment_params.merge(
query: 'up{pod_name="{{pod_name}}"}', variables: { 'pod_name' => pod_name }
) )
expect(response).to have_gitlab_http_status(:success)
expect(Prometheus::ProxyService).to have_received(:new)
.with(environment, 'GET', 'query', expected_params)
end
context 'with invalid variables' do
let(:params_with_invalid_variables) do
environment_params.merge(
query: 'up{pod_name="{{pod_name}}"}', variables: ['a']
)
end
it 'returns 400' do
get :proxy, params: params_with_invalid_variables
expect(response).to have_gitlab_http_status(:bad_request)
expect(Prometheus::ProxyService).not_to receive(:new)
end
end
end
end
context 'with nil result' do
let(:service_result) { nil }
it 'returns 204 no_content' do
get :proxy, params: environment_params
expect(json_response['status']).to eq(_('processing'))
expect(json_response['message']).to eq(_('Not ready yet. Try again later.'))
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'with 404 result' do
let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
it 'returns body' do
get :proxy, params: environment_params
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['body']).to eq('value')
end
end
context 'with error result' do
context 'with http_status' do
let(:service_result) do
{ http_status: :service_unavailable, status: :error, message: 'error message' }
end
it 'sets the http response status code' do
get :proxy, params: environment_params
expect(response).to have_gitlab_http_status(:service_unavailable)
expect(json_response['status']).to eq('error')
expect(json_response['message']).to eq('error message')
end end
end
context 'without http_status' do
let(:service_result) { { status: :error, message: 'error message' } }
it 'returns bad_request' do it 'returns 400' do
get :proxy, params: environment_params get :prometheus_proxy, params: params_with_invalid_variables
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['status']).to eq('error') expect(Prometheus::ProxyService).not_to receive(:new)
expect(json_response['message']).to eq('error message')
end end
end end
end end
end
context 'with inappropriate requests' do
context 'with anonymous user' do context 'with anonymous user' do
let(:prometheus_body) { nil }
before do before do
sign_out(user) sign_out(user)
end end
it 'redirects to signin page' do it 'redirects to signin page' do
get :proxy, params: environment_params get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to redirect_to(new_user_session_path) expect(response).to redirect_to(new_user_session_path)
end end
end end
context 'without correct permissions' do
before do
project.team.truncate
end
it 'returns 404' do
get :proxy, params: environment_params
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
context 'with invalid environment id' do
let(:other_environment) { create(:environment) }
it 'returns 404' do
get :proxy, params: environment_params(id: other_environment.id)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
private
def environment_params(params = {})
{
id: environment.id.to_s,
namespace_id: project.namespace.full_path,
project_id: project.name,
proxy_path: 'query',
query: '1'
}.merge(params)
end end
end end
# frozen_string_literal: true
RSpec.shared_examples_for 'metrics dashboard prometheus api proxy' do
let(:service_params) { [proxyable, 'GET', 'query', expected_params] }
let(:service_result) { { status: :success, body: prometheus_body } }
let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
let(:proxyable_params) do
{
id: proxyable.id.to_s
}
end
let(:expected_params) do
ActionController::Parameters.new(
prometheus_proxy_params(
proxy_path: 'query',
controller: described_class.controller_path,
action: 'prometheus_proxy'
)
).permit!
end
before do
allow_next_instance_of(Prometheus::ProxyService, *service_params) do |proxy_service|
allow(proxy_service).to receive(:execute).and_return(service_result)
end
end
context 'with valid requests' do
context 'with success result' do
let(:prometheus_body) { '{"status":"success"}' }
let(:prometheus_json_body) { Gitlab::Json.parse(prometheus_body) }
it 'returns prometheus response' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(prometheus_json_body)
end
context 'with nil query' do
let(:params_without_query) do
prometheus_proxy_params.except(:query)
end
before do
expected_params.delete(:query)
end
it 'does not raise error' do
get :prometheus_proxy, params: params_without_query
expect(Prometheus::ProxyService).to have_received(:new).with(*service_params)
end
end
end
context 'with nil result' do
let(:service_result) { nil }
it 'returns 204 no_content' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(json_response['status']).to eq(_('processing'))
expect(json_response['message']).to eq(_('Not ready yet. Try again later.'))
expect(response).to have_gitlab_http_status(:no_content)
end
end
context 'with 404 result' do
let(:service_result) { { http_status: 404, status: :success, body: '{"body": "value"}' } }
it 'returns body' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to have_gitlab_http_status(:not_found)
expect(json_response['body']).to eq('value')
end
end
context 'with error result' do
context 'with http_status' do
let(:service_result) do
{ http_status: :service_unavailable, status: :error, message: 'error message' }
end
it 'sets the http response status code' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to have_gitlab_http_status(:service_unavailable)
expect(json_response['status']).to eq('error')
expect(json_response['message']).to eq('error message')
end
end
context 'without http_status' do
let(:service_result) { { status: :error, message: 'error message' } }
it 'returns bad_request' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['status']).to eq('error')
expect(json_response['message']).to eq('error message')
end
end
end
end
context 'with inappropriate requests' do
let(:prometheus_body) { nil }
context 'without correct permissions' do
let(:user2) { create(:user) }
before do
sign_out(user)
sign_in(user2)
end
it 'returns 404' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'with invalid proxyable id' do
let(:prometheus_body) { nil }
it 'returns 404' do
get :prometheus_proxy, params: prometheus_proxy_params(id: proxyable.id + 1)
expect(response).to have_gitlab_http_status(:not_found)
end
end
private
def prometheus_proxy_params(params = {})
{
proxy_path: 'query',
query: '1'
}.merge(proxyable_params).merge(params)
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment