Commit 6ce79b1a authored by Ryan Cobb's avatar Ryan Cobb Committed by Kamil Trzciński

Add prometheus proxies

This adds prometheus proxies for project and group clusters. This will
be used for cluster metrics dashboards similar to how we currently do
environment metrics.
parent 882d22c9
...@@ -31,6 +31,10 @@ class Clusters::BaseController < ApplicationController ...@@ -31,6 +31,10 @@ class Clusters::BaseController < ApplicationController
access_denied! unless can?(current_user, :create_cluster, clusterable) access_denied! unless can?(current_user, :create_cluster, clusterable)
end end
def authorize_read_prometheus!
access_denied! unless can?(current_user, :read_prometheus, clusterable)
end
def clusterable def clusterable
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -8,6 +8,7 @@ module Clusters ...@@ -8,6 +8,7 @@ module Clusters
enable :create_cluster enable :create_cluster
enable :update_cluster enable :update_cluster
enable :admin_cluster enable :admin_cluster
enable :read_prometheus
end end
end end
end end
...@@ -141,6 +141,7 @@ Rails.application.routes.draw do ...@@ -141,6 +141,7 @@ Rails.application.routes.draw do
member do member do
Gitlab.ee do Gitlab.ee do
get :metrics, format: :json get :metrics, format: :json
get '/prometheus/api/v1/*proxy_path', to: 'clusters#prometheus_proxy', as: :prometheus_api
end end
scope :applications do scope :applications do
......
...@@ -3,6 +3,12 @@ ...@@ -3,6 +3,12 @@
module EE module EE
module Clusters module Clusters
module ClustersController module ClustersController
extend ActiveSupport::Concern
prepended do
before_action :authorize_read_prometheus!, only: :prometheus_proxy
end
def metrics def metrics
return render_404 unless prometheus_adapter&.can_query? return render_404 unless prometheus_adapter&.can_query?
...@@ -19,6 +25,31 @@ module EE ...@@ -19,6 +25,31 @@ module EE
end end
end end
def prometheus_proxy
result = ::Prometheus::ProxyService.new(
clusterable.clusterable,
proxy_method,
proxy_path,
proxy_params
).execute
if result.nil?
return render status: :no_content, json: {
status: _('processing'),
message: _('Not ready yet. Try again later.')
}
end
if result[:status] == :success
render status: result[:http_status], json: result[:body]
else
render(
status: result[:http_status] || :bad_request,
json: { status: result[:status], message: result[:message] }
)
end
end
private private
def prometheus_adapter def prometheus_adapter
...@@ -26,6 +57,18 @@ module EE ...@@ -26,6 +57,18 @@ module EE
cluster.application_prometheus cluster.application_prometheus
end end
def proxy_method
request.method
end
def proxy_path
params[:proxy_path]
end
def proxy_params
params.permit!
end
end end
end end
end end
...@@ -48,6 +48,7 @@ module EE ...@@ -48,6 +48,7 @@ module EE
rule { reporter }.policy do rule { reporter }.policy do
enable :admin_list enable :admin_list
enable :admin_board enable :admin_board
enable :read_prometheus
end end
rule { maintainer }.policy do rule { maintainer }.policy do
......
# frozen_string_literal: true
require 'spec_helper'
describe Admin::ClustersController do
it_behaves_like 'cluster metrics' do
let(:user) { create(:admin) }
let(:clusterable) { Clusters::Instance.new }
let(:cluster) do
create(:cluster, :instance, :provided_by_gcp)
end
let(:metrics_params) do
{
id: cluster
}
end
before do
allow(::Clusters::Instance).to receive(:new).and_return(cluster.instance)
end
context 'with inappropriate requests' do
context 'with anoymous user' do
before do
sign_out(user)
end
it 'renders not found' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to have_gitlab_http_status(404)
end
end
context 'with non-admin user' do
let(:user) { create(:user) }
before do
sign_in(user)
end
it 'renders not found' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to have_gitlab_http_status(404)
end
end
end
end
private
def prometheus_proxy_params(params = {})
{
id: cluster.id.to_s,
proxy_path: 'query',
query: '1'
}.merge(params)
end
end
...@@ -15,6 +15,7 @@ describe Groups::ClustersController do ...@@ -15,6 +15,7 @@ describe Groups::ClustersController do
end end
it_behaves_like 'cluster metrics' do it_behaves_like 'cluster metrics' do
let(:user) { create(:user) }
let(:clusterable) { group } let(:clusterable) { group }
let(:cluster) do let(:cluster) do
...@@ -27,6 +28,67 @@ describe Groups::ClustersController do ...@@ -27,6 +28,67 @@ describe Groups::ClustersController do
id: cluster id: cluster
} }
end end
before do
clusterable.add_maintainer(user)
end
context 'with inappropriate requests' do
context 'with anoymous user' do
before do
sign_out(user)
end
it 'renders not found' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to have_gitlab_http_status(404)
end
context 'with invalid clusterable id' do
before do
sign_in(user)
end
let(:other_clusterable) { create(:group) }
it 'returns 404' do
get :prometheus_proxy, params: prometheus_proxy_params(id: other_clusterable.id)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
describe 'security' do
let(:prometheus_adapter) { double(:prometheus_adapter, can_query?: true, query: nil) }
before do
sign_in(user)
allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter)
end
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(clusterable) }
it { expect { go }.to be_allowed_for(:maintainer).of(clusterable) }
it { expect { go }.to be_denied_for(:developer).of(clusterable) }
it { expect { go }.to be_denied_for(:reporter).of(clusterable) }
it { expect { go }.to be_denied_for(:guest).of(clusterable) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
end
private
def prometheus_proxy_params(params = {})
{
id: cluster.id.to_s,
group_id: group.name,
proxy_path: 'query',
query: '1'
}.merge(params)
end end
describe 'GET environments' do describe 'GET environments' do
......
...@@ -6,6 +6,7 @@ describe Projects::ClustersController do ...@@ -6,6 +6,7 @@ describe Projects::ClustersController do
set(:project) { create(:project) } set(:project) { create(:project) }
it_behaves_like 'cluster metrics' do it_behaves_like 'cluster metrics' do
let(:user) { create(:user) }
let(:clusterable) { project } let(:clusterable) { project }
let(:cluster) do let(:cluster) do
...@@ -19,5 +20,67 @@ describe Projects::ClustersController do ...@@ -19,5 +20,67 @@ describe Projects::ClustersController do
id: cluster id: cluster
} }
end end
before do
clusterable.add_maintainer(user)
end
context 'with inappropriate requests' do
context 'with annoymous user' do
before do
sign_out(user)
end
it 'redirects to signin page' do
get :prometheus_proxy, params: prometheus_proxy_params
expect(response).to redirect_to(new_user_session_path)
end
end
context 'with invalid clusterable id' do
before do
sign_in(user)
end
let(:other_clusterable) { create(:project) }
it 'returns 404' do
get :prometheus_proxy, params: prometheus_proxy_params(id: other_clusterable.id)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'security' do
let(:prometheus_adapter) { double(:prometheus_adapter, can_query?: true, query: nil) }
before do
sign_in(user)
allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter)
end
it { expect { go }.to be_allowed_for(:admin) }
it { expect { go }.to be_allowed_for(:owner).of(clusterable) }
it { expect { go }.to be_allowed_for(:maintainer).of(clusterable) }
it { expect { go }.to be_denied_for(:developer).of(clusterable) }
it { expect { go }.to be_denied_for(:reporter).of(clusterable) }
it { expect { go }.to be_denied_for(:guest).of(clusterable) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end
end
private
def prometheus_proxy_params(params = {})
{
id: cluster.id.to_s,
namespace_id: project.namespace.full_path,
project_id: project.name,
proxy_path: 'query',
query: '1'
}.merge(params)
end end
end end
...@@ -11,10 +11,7 @@ shared_examples 'cluster metrics' do ...@@ -11,10 +11,7 @@ shared_examples 'cluster metrics' do
end end
describe 'functionality' do describe 'functionality' do
let(:user) { create(:user) }
before do before do
clusterable.add_maintainer(user)
sign_in(user) sign_in(user)
end end
...@@ -77,24 +74,123 @@ shared_examples 'cluster metrics' do ...@@ -77,24 +74,123 @@ shared_examples 'cluster metrics' do
end end
end end
end end
end
describe 'GET #prometheus_proxy' do
let(:prometheus_proxy_service) { instance_double(Prometheus::ProxyService) }
let(:expected_params) do
ActionController::Parameters.new(
prometheus_proxy_params(
proxy_path: 'query',
controller: subject.controller_path,
action: 'prometheus_proxy'
)
).permit!
end
before do
sign_in(user)
end
context 'with valid requests' do
before do
allow(Prometheus::ProxyService).to receive(:new)
.with(clusterable, 'GET', 'query', expected_params)
.and_return(prometheus_proxy_service)
allow(prometheus_proxy_service).to receive(:execute)
.and_return(service_result)
end
context 'with success result' do
let(:service_result) { { status: :success, body: prometheus_body } }
let(:prometheus_body) { '{"status":"success"}' }
it 'returns prometheus response' do
prometheus_json_body = JSON.parse(prometheus_body)
get :prometheus_proxy, params: prometheus_proxy_params
expect(Prometheus::ProxyService).to have_received(:new)
.with(clusterable, 'GET', 'query', expected_params)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response).to eq(prometheus_json_body)
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
describe 'security' do expect(response).to have_gitlab_http_status(:not_found)
let(:prometheus_adapter) { double(:prometheus_adapter, can_query?: true, query: nil) } expect(json_response['body']).to eq('value')
end
it { expect { go }.to be_allowed_for(:admin) } end
it { expect { go }.to be_allowed_for(:owner).of(clusterable) }
it { expect { go }.to be_allowed_for(:maintainer).of(clusterable) } context 'with error result' do
it { expect { go }.to be_denied_for(:developer).of(clusterable) } context 'with http_status' do
it { expect { go }.to be_denied_for(:reporter).of(clusterable) } let(:service_result) do
it { expect { go }.to be_denied_for(:guest).of(clusterable) } { http_status: :service_unavailable, status: :error, message: 'error message' }
it { expect { go }.to be_denied_for(:user) } end
it { expect { go }.to be_denied_for(:external) }
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 end
private context 'with inappropriate requests' do
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
def go expect(response).to have_gitlab_http_status(:not_found)
get :metrics, params: metrics_params, format: :json end
end
end end
end end
private
def go
get :metrics, params: metrics_params, format: :json
end
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