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
access_denied! unless can?(current_user, :create_cluster, clusterable)
end
def authorize_read_prometheus!
access_denied! unless can?(current_user, :read_prometheus, clusterable)
end
def clusterable
raise NotImplementedError
end
......
......@@ -8,6 +8,7 @@ module Clusters
enable :create_cluster
enable :update_cluster
enable :admin_cluster
enable :read_prometheus
end
end
end
......@@ -141,6 +141,7 @@ Rails.application.routes.draw do
member do
Gitlab.ee do
get :metrics, format: :json
get '/prometheus/api/v1/*proxy_path', to: 'clusters#prometheus_proxy', as: :prometheus_api
end
scope :applications do
......
......@@ -3,6 +3,12 @@
module EE
module Clusters
module ClustersController
extend ActiveSupport::Concern
prepended do
before_action :authorize_read_prometheus!, only: :prometheus_proxy
end
def metrics
return render_404 unless prometheus_adapter&.can_query?
......@@ -19,6 +25,31 @@ module EE
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
def prometheus_adapter
......@@ -26,6 +57,18 @@ module EE
cluster.application_prometheus
end
def proxy_method
request.method
end
def proxy_path
params[:proxy_path]
end
def proxy_params
params.permit!
end
end
end
end
......@@ -48,6 +48,7 @@ module EE
rule { reporter }.policy do
enable :admin_list
enable :admin_board
enable :read_prometheus
end
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
end
it_behaves_like 'cluster metrics' do
let(:user) { create(:user) }
let(:clusterable) { group }
let(:cluster) do
......@@ -27,6 +28,67 @@ describe Groups::ClustersController do
id: cluster
}
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
describe 'GET environments' do
......
......@@ -6,6 +6,7 @@ describe Projects::ClustersController do
set(:project) { create(:project) }
it_behaves_like 'cluster metrics' do
let(:user) { create(:user) }
let(:clusterable) { project }
let(:cluster) do
......@@ -19,5 +20,67 @@ describe Projects::ClustersController do
id: cluster
}
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
......@@ -11,10 +11,7 @@ shared_examples 'cluster metrics' do
end
describe 'functionality' do
let(:user) { create(:user) }
before do
clusterable.add_maintainer(user)
sign_in(user)
end
......@@ -77,24 +74,123 @@ shared_examples 'cluster metrics' do
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
let(:prometheus_adapter) { double(:prometheus_adapter, can_query?: true, query: nil) }
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) }
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
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
get :metrics, params: metrics_params, format: :json
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
private
def go
get :metrics, params: metrics_params, format: :json
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