Commit c7268434 authored by Bob Van Landuyt's avatar Bob Van Landuyt

Merge branch '51963-support-prometheus-for-group-level-clusters-backend-ee' into 'master'

EE Backport: Support Prometheus for group-level clusters (backend)

See merge request gitlab-org/gitlab-ee!12115
parents 66b0fad6 f0a7653a
...@@ -235,3 +235,5 @@ class Clusters::ClustersController < Clusters::BaseController ...@@ -235,3 +235,5 @@ class Clusters::ClustersController < Clusters::BaseController
@cluster.applications.each(&:schedule_status_update) @cluster.applications.each(&:schedule_status_update)
end end
end end
Clusters::ClustersController.prepend(EE::Clusters::ClustersController)
...@@ -22,5 +22,3 @@ class Projects::ClustersController < Clusters::ClustersController ...@@ -22,5 +22,3 @@ class Projects::ClustersController < Clusters::ClustersController
@repository ||= project.repository @repository ||= project.repository
end end
end end
Projects::ClustersController.prepend(EE::Projects::ClustersController)
...@@ -56,6 +56,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated ...@@ -56,6 +56,10 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
raise NotImplementedError raise NotImplementedError
end end
def clusters_path(params = {})
raise NotImplementedError
end
def empty_state_help_text def empty_state_help_text
nil nil
end end
...@@ -68,3 +72,5 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated ...@@ -68,3 +72,5 @@ class ClusterablePresenter < Gitlab::View::Presenter::Delegated
raise NotImplementedError raise NotImplementedError
end end
end end
ClusterablePresenter.prepend EE::ClusterablePresenter
...@@ -44,3 +44,5 @@ class GroupClusterablePresenter < ClusterablePresenter ...@@ -44,3 +44,5 @@ class GroupClusterablePresenter < ClusterablePresenter
link_to(s_('ClusterIntegration|Learn more about group Kubernetes clusters'), help_page_path('user/group/clusters/index'), target: '_blank', rel: 'noopener noreferrer') link_to(s_('ClusterIntegration|Learn more about group Kubernetes clusters'), help_page_path('user/group/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end end
end end
GroupClusterablePresenter.prepend EE::GroupClusterablePresenter
...@@ -39,3 +39,5 @@ class ProjectClusterablePresenter < ClusterablePresenter ...@@ -39,3 +39,5 @@ class ProjectClusterablePresenter < ClusterablePresenter
link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') link_to(s_('ClusterIntegration|Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
end end
end end
ProjectClusterablePresenter.prepend EE::ProjectClusterablePresenter
# frozen_string_literal: true # frozen_string_literal: true
module EE module EE
module Projects module Clusters
module ClustersController module ClustersController
extend ActiveSupport::Concern
def metrics def metrics
return render_404 unless prometheus_adapter&.can_query? return render_404 unless prometheus_adapter&.can_query?
......
...@@ -9,17 +9,14 @@ module EE ...@@ -9,17 +9,14 @@ module EE
clusterable.feature_available?(:multiple_clusters) clusterable.feature_available?(:multiple_clusters)
end end
override :show_cluster_health_graphs? def show_cluster_health_graphs?
def show_cluster_health_graphs?(cluster) clusterable.feature_available?(:cluster_health)
cluster.project_type? && cluster.project.feature_available?(:cluster_health)
end end
def cluster_health_data(cluster) def cluster_health_data(cluster)
project = cluster.project
{ {
'metrics-endpoint': metrics_project_cluster_path(project, cluster, format: :json), 'clusters-path': clusterable.clusters_path,
'clusters-path': project_clusters_path(project), 'metrics-endpoint': clusterable.metrics_cluster_path(cluster, format: :json),
'documentation-path': help_page_path('administration/monitoring/prometheus/index.md'), 'documentation-path': help_page_path('administration/monitoring/prometheus/index.md'),
'empty-getting-started-svg-path': image_path('illustrations/monitoring/getting_started.svg'), 'empty-getting-started-svg-path': image_path('illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path': image_path('illustrations/monitoring/loading.svg'), 'empty-loading-svg-path': image_path('illustrations/monitoring/loading.svg'),
......
# frozen_string_literal: true
module EE
module ClusterablePresenter
def metrics_cluster_path(cluster, params = {})
raise NotImplementedError
end
end
end
# frozen_string_literal: true
module EE
module GroupClusterablePresenter
extend ::Gitlab::Utils::Override
override :metrics_cluster_path
def metrics_cluster_path(cluster, params = {})
metrics_group_cluster_path(clusterable, cluster, params)
end
end
end
# frozen_string_literal: true
module EE
module ProjectClusterablePresenter
extend ::Gitlab::Utils::Override
override :metrics_cluster_path
def metrics_cluster_path(cluster, params = {})
metrics_project_cluster_path(clusterable, cluster, params)
end
end
end
- return unless show_cluster_health_graphs?
%section.settings.no-animate.expanded.cluster-health-graphs#cluster-health %section.settings.no-animate.expanded.cluster-health-graphs#cluster-health
%h4= s_('ClusterIntegration|Cluster health') %h4= s_('ClusterIntegration|Cluster health')
......
# frozen_string_literal: true
require 'spec_helper'
describe Groups::ClustersController do
set(:group) { create(:group) }
it_behaves_like 'cluster metrics' do
let(:clusterable) { group }
let(:cluster) do
create(:cluster, :group, :provided_by_gcp, groups: [group])
end
let(:metrics_params) do
{
group_id: group,
id: cluster
}
end
end
end
# frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
describe Projects::ClustersController do describe Projects::ClustersController do
include AccessMatchersForController
set(:project) { create(:project) } set(:project) { create(:project) }
describe 'GET metrics' do it_behaves_like 'cluster metrics' do
let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } let(:clusterable) { project }
describe 'functionality' do
let(:user) { create(:user) }
before do let(:cluster) do
project.add_maintainer(user) create(:cluster, :project, :provided_by_gcp, projects: [project])
sign_in(user)
end end
context "Can't query Prometheus" do let(:metrics_params) do
it 'returns not found' do {
go
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'can query Prometheus' do
let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true, query: nil) }
before do
allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter)
end
it 'queries cluster metrics' do
go
expect(prometheus_adapter).to have_received(:query).with(:cluster)
end
context 'when response has content' do
let(:query_response) { { response: nil } }
before do
allow(prometheus_adapter).to receive(:query).and_return(query_response)
end
it 'returns prometheus query response' do
go
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(query_response.to_json)
end
end
context 'when response has no content' do
let(:query_response) { {} }
before do
allow(prometheus_adapter).to receive(:query).and_return(query_response)
end
it 'returns prometheus query response' do
go
expect(response).to have_gitlab_http_status(:no_content)
end
end
end
end
def go
get :metrics, params: {
namespace_id: project.namespace, namespace_id: project.namespace,
project_id: project, project_id: project,
id: cluster id: cluster
}, }
format: :json
end
describe 'security' do
let(:prometheus_adapter) { double('prometheus_adapter', can_query?: true, query: nil) }
before do
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(project) }
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
it { expect { go }.to be_denied_for(:developer).of(project) }
it { expect { go }.to be_denied_for(:reporter).of(project) }
it { expect { go }.to be_denied_for(:guest).of(project) }
it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) }
end end
end end
end end
...@@ -3,84 +3,105 @@ ...@@ -3,84 +3,105 @@
require 'spec_helper' require 'spec_helper'
describe ClustersHelper do describe ClustersHelper do
describe '#has_multiple_clusters?' do shared_examples 'feature availablilty' do |feature|
let(:project) { build(:project) }
subject { helper.has_multiple_clusters? }
before do before do
# clusterable is provided as a `helper_method` # clusterable is provided as a `helper_method`
allow(helper).to receive(:clusterable).and_return(project) allow(helper).to receive(:clusterable).and_return(clusterable)
end
context 'license is premium' do expect(clusterable)
before do .to receive(:feature_available?)
expect(project).to receive(:feature_available?).with(:multiple_clusters).and_return(true) .with(feature)
.and_return(feature_available)
end end
context 'feature unavailable' do
let(:feature_available) { true }
it { is_expected.to be_truthy } it { is_expected.to be_truthy }
end end
context 'license is starter' do context 'feature available' do
before do let(:feature_available) { false }
expect(project).to receive(:feature_available?).with(:multiple_clusters).and_return(false)
end
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
end end
describe '#show_cluster_health_graphs?' do describe '#has_multiple_clusters?' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) } subject { helper.has_multiple_clusters? }
let(:cluster_presenter) { cluster.present }
before do context 'project level' do
stub_licensed_features(cluster_health: true) let(:clusterable) { instance_double(Project) }
end
context 'with project level cluster' do it_behaves_like 'feature availablilty', :multiple_clusters
it 'returns true' do
expect(helper.show_cluster_health_graphs?(cluster_presenter)).to eq(true)
end
end end
context 'with group level cluster' do context 'group level' do
let(:cluster) { create(:cluster, :group, :provided_by_gcp) } let(:clusterable) { instance_double(Group) }
it 'returns false' do it_behaves_like 'feature availablilty', :multiple_clusters
expect(helper.show_cluster_health_graphs?(cluster_presenter)).to eq(false)
end end
end end
context 'without cluster_health license' do describe '#show_cluster_health_graphs?' do
before do subject { helper.show_cluster_health_graphs? }
stub_licensed_features(cluster_health: false)
end
it 'returns false' do context 'project level' do
expect(helper.show_cluster_health_graphs?(cluster_presenter)).to eq(false) let(:clusterable) { instance_double(Project) }
it_behaves_like 'feature availablilty', :cluster_health
end end
context 'group level' do
let(:clusterable) { instance_double(Group) }
it_behaves_like 'feature availablilty', :cluster_health
end end
end end
describe '#cluster_health_data' do describe '#cluster_health_data' do
let(:project) { cluster.project } shared_examples 'cluster health data' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:user) { create(:user) }
let(:cluster_presenter) { cluster.present } let(:cluster_presenter) { cluster.present(current_user: user) }
let(:clusterable_presenter) do
ClusterablePresenter.fabricate(clusterable, current_user: user)
end
subject { helper.cluster_health_data(cluster_presenter) }
before do
allow(helper).to receive(:clusterable).and_return(clusterable_presenter)
end
it 'returns graph configuration' do it do
expect(cluster_health_data(cluster_presenter)).to eq( is_expected.to match(
'clusters-path': project_clusters_path(project), 'clusters-path': clusterable_presenter.clusters_path,
'metrics-endpoint': clusterable_presenter.metrics_cluster_path(cluster, format: :json),
'documentation-path': help_page_path('administration/monitoring/prometheus/index.md'), 'documentation-path': help_page_path('administration/monitoring/prometheus/index.md'),
'empty-getting-started-svg-path': image_path('illustrations/monitoring/getting_started.svg'), 'empty-getting-started-svg-path': match_asset_path('/assets/illustrations/monitoring/getting_started.svg'),
'empty-loading-svg-path': image_path('illustrations/monitoring/loading.svg'), 'empty-loading-svg-path': match_asset_path('/assets/illustrations/monitoring/loading.svg'),
'empty-no-data-svg-path': image_path('illustrations/monitoring/no_data.svg'), 'empty-no-data-svg-path': match_asset_path('/assets/illustrations/monitoring/no_data.svg'),
'empty-unable-to-connect-svg-path': image_path('illustrations/monitoring/unable_to_connect.svg'), 'empty-unable-to-connect-svg-path': match_asset_path('/assets/illustrations/monitoring/unable_to_connect.svg'),
'metrics-endpoint': metrics_project_cluster_path(project, cluster, format: :json),
'settings-path': '', 'settings-path': '',
'project-path': '', 'project-path': '',
'tags-path': '' 'tags-path': ''
) )
end end
end end
context 'with project cluster' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:clusterable) { cluster.project }
it_behaves_like 'cluster health data'
end
context 'with group cluster' do
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
let(:clusterable) { cluster.group }
it_behaves_like 'cluster health data'
end
end
end end
# frozen_string_literal: true
require 'spec_helper'
describe GroupClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(group) }
let(:cluster) { create(:cluster, :provided_by_gcp, :group) }
let(:group) { cluster.group }
describe '#metrics_cluster_path' do
subject { presenter.metrics_cluster_path(cluster) }
it { is_expected.to eq(metrics_group_cluster_path(group, cluster)) }
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ProjectClusterablePresenter do
include Gitlab::Routing.url_helpers
let(:presenter) { described_class.new(project) }
let(:cluster) { create(:cluster, :provided_by_gcp, :project) }
let(:project) { cluster.project }
describe '#metrics_cluster_path' do
subject { presenter.metrics_cluster_path(cluster) }
it { is_expected.to eq(metrics_project_cluster_path(project, cluster)) }
end
end
# frozen_string_literal: true
require 'spec_helper'
shared_examples 'cluster metrics' do
include AccessMatchersForController
describe 'GET #metrics' do
before do
allow(controller).to receive(:prometheus_adapter).and_return(prometheus_adapter)
end
describe 'functionality' do
let(:user) { create(:user) }
before do
clusterable.add_maintainer(user)
sign_in(user)
end
context 'can query Prometheus' do
let(:prometheus_adapter) { double(:prometheus_adapter, can_query?: true, query: nil) }
it 'queries cluster metrics' do
go
expect(prometheus_adapter).to have_received(:query).with(:cluster)
end
context 'when response has content' do
let(:query_response) { { non_empty: :response } }
before do
allow(prometheus_adapter).to receive(:query).and_return(query_response)
end
it 'returns prometheus query response' do
go
expect(response).to have_gitlab_http_status(:ok)
expect(response.body).to eq(query_response.to_json)
end
end
context 'when response has no content' do
let(:query_response) { nil }
before do
allow(prometheus_adapter).to receive(:query).and_return(query_response)
end
it 'returns prometheus query response' do
go
expect(response).to have_gitlab_http_status(:no_content)
end
end
end
context 'without Prometheus' do
let(:prometheus_adapter) { nil }
it 'returns not found' do
go
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'cannot query Prometheus' do
let(:prometheus_adapter) { double(:prometheus_adapter, can_query?: false) }
it 'returns not found' do
go
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) }
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
private
def go
get :metrics, params: metrics_params, format: :json
end
end
end
...@@ -3,24 +3,21 @@ ...@@ -3,24 +3,21 @@
require 'spec_helper' require 'spec_helper'
describe 'clusters/clusters/show' do describe 'clusters/clusters/show' do
let(:user) { create(:user) } set(:user) { create(:user) }
let(:project) { create(:project) }
before do shared_examples 'cluster health section' do
allow(controller).to receive(:current_user).and_return(user) let(:cluster_presenter) { cluster.present(current_user: user) }
let(:clusterable_presenter) do
ClusterablePresenter.fabricate(clusterable, current_user: user)
end end
context 'when the cluster details page is opened' do
before do before do
assign(:cluster, cluster_presenter) assign(:cluster, cluster_presenter)
allow(view).to receive(:clusterable).and_return(clusterable) allow(view).to receive(:clusterable).and_return(clusterable_presenter)
end end
context 'with project level cluster' do context 'with feature cluster_health available' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:clusterable) { ClusterablePresenter.fabricate(project, current_user: user) }
let(:cluster_presenter) { cluster.present(current_user: user) }
before do before do
stub_licensed_features(cluster_health: true) stub_licensed_features(cluster_health: true)
end end
...@@ -33,16 +30,12 @@ describe 'clusters/clusters/show' do ...@@ -33,16 +30,12 @@ describe 'clusters/clusters/show' do
end end
end end
context 'with group level cluster' do context 'without feature cluster_health available' do
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
let(:clusterable) { ClusterablePresenter.fabricate(cluster.group, current_user: user) }
let(:cluster_presenter) { cluster.present(current_user: user) }
before do before do
stub_licensed_features(cluster_health: true) stub_licensed_features(cluster_health: false)
end end
it 'does not display cluster health section' do it 'does not show the Cluster health section' do
render render
expect(rendered).not_to have_selector('#cluster-health') expect(rendered).not_to have_selector('#cluster-health')
...@@ -50,4 +43,24 @@ describe 'clusters/clusters/show' do ...@@ -50,4 +43,24 @@ describe 'clusters/clusters/show' do
end end
end end
end end
before do
allow(controller).to receive(:current_user).and_return(user)
end
context 'when the cluster details page is opened' do
context 'with project level cluster' do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:clusterable) { cluster.project }
it_behaves_like 'cluster health section'
end
context 'with group level cluster' do
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
let(:clusterable) { cluster.group }
it_behaves_like 'cluster health section'
end
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