Commit 3f0a8b36 authored by Stan Hu's avatar Stan Hu

Merge branch '63044-cluster-dashboards' into 'master'

Add cluster metrics dashboard processing

See merge request gitlab-org/gitlab!15709
parents a5cfa8f0 765038c7
...@@ -11,3 +11,5 @@ class Admin::ClustersController < Clusters::ClustersController ...@@ -11,3 +11,5 @@ class Admin::ClustersController < Clusters::ClustersController
@clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user) @clusterable ||= InstanceClusterablePresenter.fabricate(Clusters::Instance.new, current_user: current_user)
end end
end end
Admin::ClustersController.prepend_if_ee('EE::Admin::ClustersController')
...@@ -24,3 +24,5 @@ class Projects::ClustersController < Clusters::ClustersController ...@@ -24,3 +24,5 @@ class Projects::ClustersController < Clusters::ClustersController
@repository ||= project.repository @repository ||= project.repository
end end
end end
Projects::ClustersController.prepend_if_ee('EE::Projects::ClustersController')
...@@ -164,7 +164,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -164,7 +164,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
result = dashboard_finder.find( result = dashboard_finder.find(
project, project,
current_user, current_user,
environment, environment: environment,
dashboard_path: params[:dashboard], dashboard_path: params[:dashboard],
**dashboard_params.to_h.symbolize_keys **dashboard_params.to_h.symbolize_keys
) )
...@@ -172,13 +172,13 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -172,13 +172,13 @@ class Projects::EnvironmentsController < Projects::ApplicationController
result = dashboard_finder.find( result = dashboard_finder.find(
project, project,
current_user, current_user,
environment, environment: environment,
dashboard_path: params[:dashboard] dashboard_path: params[:dashboard]
) )
result[:all_dashboards] = dashboard_finder.find_all_paths(project) result[:all_dashboards] = dashboard_finder.find_all_paths(project)
else else
result = dashboard_finder.find(project, current_user, environment) result = dashboard_finder.find(project, current_user, environment: environment)
end end
respond_to do |format| respond_to do |format|
......
...@@ -7,6 +7,13 @@ module Metrics ...@@ -7,6 +7,13 @@ module Metrics
class BaseService < ::BaseService class BaseService < ::BaseService
include Gitlab::Metrics::Dashboard::Errors include Gitlab::Metrics::Dashboard::Errors
STAGES = ::Gitlab::Metrics::Dashboard::Stages
SEQUENCE = [
STAGES::CommonMetricsInserter,
STAGES::EndpointInserter,
STAGES::Sorter
].freeze
def get_dashboard def get_dashboard
return error('Insufficient permissions.', :unauthorized) unless allowed? return error('Insufficient permissions.', :unauthorized) unless allowed?
...@@ -31,14 +38,20 @@ module Metrics ...@@ -31,14 +38,20 @@ module Metrics
# Determines whether users should be able to view # Determines whether users should be able to view
# dashboards at all. # dashboards at all.
def allowed? def allowed?
Ability.allowed?(current_user, :read_environment, project) if params[:environment]
Ability.allowed?(current_user, :read_environment, project)
elsif params[:cluster]
true # Authorization handled at controller level
else
false
end
end end
# Returns a new dashboard Hash, supplemented with DB info # Returns a new dashboard Hash, supplemented with DB info
def process_dashboard def process_dashboard
Gitlab::Metrics::Dashboard::Processor ::Gitlab::Metrics::Dashboard::Processor
.new(project, params[:environment], raw_dashboard) .new(project, raw_dashboard, sequence, params)
.process(insert_project_metrics: insert_project_metrics?) .process
end end
# @return [String] Relative filepath of the dashboard yml # @return [String] Relative filepath of the dashboard yml
...@@ -56,12 +69,11 @@ module Metrics ...@@ -56,12 +69,11 @@ module Metrics
raise NotImplementedError raise NotImplementedError
end end
# Determines whether custom metrics should be included def sequence
# in the processed output. SEQUENCE
# @return [Boolean]
def insert_project_metrics?
false
end end
end end
end end
end end
Metrics::Dashboard::BaseService.prepend_if_ee('EE::Metrics::Dashboard::BaseService')
...@@ -8,6 +8,13 @@ module Metrics ...@@ -8,6 +8,13 @@ module Metrics
SYSTEM_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml' SYSTEM_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
SYSTEM_DASHBOARD_NAME = 'Default' SYSTEM_DASHBOARD_NAME = 'Default'
SEQUENCE = [
STAGES::CommonMetricsInserter,
STAGES::ProjectMetricsInserter,
STAGES::EndpointInserter,
STAGES::Sorter
].freeze
class << self class << self
def all_dashboard_paths(_project) def all_dashboard_paths(_project)
[{ [{
...@@ -24,6 +31,10 @@ module Metrics ...@@ -24,6 +31,10 @@ module Metrics
private private
def cache_key
"metrics_dashboard_#{dashboard_path}"
end
def dashboard_path def dashboard_path
SYSTEM_DASHBOARD_PATH SYSTEM_DASHBOARD_PATH
end end
...@@ -35,13 +46,11 @@ module Metrics ...@@ -35,13 +46,11 @@ module Metrics
YAML.safe_load(yml) YAML.safe_load(yml)
end end
def cache_key def sequence
"metrics_dashboard_#{dashboard_path}" SEQUENCE
end
def insert_project_metrics?
true
end end
end end
end end
end end
Metrics::Dashboard::SystemDashboardService.prepend_if_ee('EE::Metrics::Dashboard::SystemDashboardService')
...@@ -143,7 +143,8 @@ Rails.application.routes.draw do ...@@ -143,7 +143,8 @@ 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 get :metrics_dashboard
get :'/prometheus/api/v1/*proxy_path', to: 'clusters#prometheus_proxy', as: :prometheus_api
end end
scope :applications do scope :applications do
......
# frozen_string_literal: true
module EE
module Admin
module ClustersController
def metrics_dashboard_params
{
cluster: cluster,
cluster_type: :admin
}
end
end
end
end
...@@ -50,6 +50,26 @@ module EE ...@@ -50,6 +50,26 @@ module EE
end end
end end
def metrics_dashboard
project_for_dashboard = defined?(project) ? project : nil # Project is not defined for group and admin level clusters
dashboard = ::Gitlab::Metrics::Dashboard::Finder.find(project_for_dashboard, current_user, metrics_dashboard_params)
respond_to do |format|
if dashboard[:status] == :success
format.json do
render status: :ok, json: dashboard.slice(:dashboard, :status)
end
else
format.json do
render(
status: dashboard[:http_status],
json: dashboard.slice(:message, :status)
)
end
end
end
end
private private
def prometheus_adapter def prometheus_adapter
......
...@@ -42,6 +42,14 @@ module EE ...@@ -42,6 +42,14 @@ module EE
.with_pagination(request, response) .with_pagination(request, response)
.represent(environments) .represent(environments)
end end
def metrics_dashboard_params
{
cluster: cluster,
cluster_type: :group,
group: group
}
end
end end
end end
end end
# frozen_string_literal: true
module EE
module Projects
module ClustersController
def metrics_dashboard_params
{
cluster: cluster,
cluster_type: :project
}
end
end
end
end
...@@ -24,6 +24,10 @@ module EE ...@@ -24,6 +24,10 @@ module EE
true true
end end
def prometheus_adapter
application_prometheus
end
end end
end end
end end
# frozen_string_literal: true
module EE
module Metrics
module Dashboard
module BaseService
extend ::Gitlab::Utils::Override
EE_SEQUENCE = [
::EE::Gitlab::Metrics::Dashboard::Stages::AlertsInserter
].freeze
override :sequence
def sequence
super + EE_SEQUENCE
end
end
end
end
end
# frozen_string_literal: true
module EE
module Metrics
module Dashboard
module SystemDashboardService
extend ::Gitlab::Utils::Override
EE_SEQUENCE = [
::EE::Gitlab::Metrics::Dashboard::Stages::AlertsInserter
].freeze
override :sequence
def sequence
super + EE_SEQUENCE
end
end
end
end
end
# frozen_string_literal: true
# Fetches the system metrics dashboard and formats the output.
# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
module Metrics
module Dashboard
class ClusterDashboardService < ::Metrics::Dashboard::BaseService
CLUSTER_DASHBOARD_PATH = 'ee/config/prometheus/cluster_metrics_new.yml'
CLUSTER_DASHBOARD_NAME = 'Cluster'
SEQUENCE = [
STAGES::CommonMetricsInserter,
STAGES::ClusterEndpointInserter,
STAGES::Sorter
].freeze
private
def cache_key
"metrics_dashboard_#{dashboard_path}"
end
def dashboard_path
CLUSTER_DASHBOARD_PATH
end
# Returns the base metrics shipped with every GitLab service.
def get_raw_dashboard
yml = File.read(Rails.root.join(dashboard_path))
YAML.safe_load(yml)
end
def sequence
SEQUENCE
end
end
end
end
dashboard: 'Cluster health'
priority: 1
panel_groups:
- group: Cluster Health
priority: 10
panels:
- title: "CPU Usage"
type: "area-chart"
y_label: "CPU (cores)"
weight: 1
metrics:
- id: cluster_health_cpu_usage
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{id="/"}[15m])) by (job)) without (job)'
unit: cores
label: Usage (cores)
- id: cluster_health_cpu_requested
query_range: 'sum(kube_pod_container_resource_requests_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
unit: cores
label: Requested (cores)
- id: cluster_health_cpu_capacity
query_range: 'sum(kube_node_status_capacity_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
unit: cores
label: Capacity (cores)
- title: "Memory Usage"
type: "area-chart"
y_label: "Memory (GiB)"
weight: 1
metrics:
- id: cluster_health_memory_usage
query_range: 'avg(sum(container_memory_usage_bytes{id="/"}) by (job)) without (job) / 2^30'
unit: GiB
label: Usage (GiB)
- id: cluster_health_memory_requested
query_range: 'sum(kube_pod_container_resource_requests_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
unit: GiB
label: Requested (GiB)
- id: cluster_health_memory_capacity
query_range: 'sum(kube_node_status_capacity_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
unit: GiB
label: Capacity (GiB)
...@@ -4,16 +4,15 @@ module EE ...@@ -4,16 +4,15 @@ module EE
module Gitlab module Gitlab
module Metrics module Metrics
module Dashboard module Dashboard
module Processor module ServiceSelector
extend ::Gitlab::Utils::Override extend ActiveSupport::Concern
EE_SEQUENCE = [ class_methods do
Stages::AlertsInserter def call(params)
].freeze return ::Metrics::Dashboard::ClusterDashboardService if params[:cluster]
override :sequence super
def sequence(_insert_project_metrics) end
super + EE_SEQUENCE
end end
end end
end end
......
...@@ -16,7 +16,7 @@ module EE ...@@ -16,7 +16,7 @@ module EE
for_metrics do |metric| for_metrics do |metric|
next unless metrics_with_alerts.include?(metric[:metric_id]) next unless metrics_with_alerts.include?(metric[:metric_id])
metric[:alert_path] = alert_path(metric[:metric_id], project, environment) metric[:alert_path] = alert_path(metric[:metric_id], project, params[:environment])
end end
end end
...@@ -25,7 +25,7 @@ module EE ...@@ -25,7 +25,7 @@ module EE
def metrics_with_alerts def metrics_with_alerts
strong_memoize(:metrics_with_alerts) do strong_memoize(:metrics_with_alerts) do
alerts = ::Projects::Prometheus::AlertsFinder alerts = ::Projects::Prometheus::AlertsFinder
.new(project: project, environment: environment) .new(project: project, environment: params[:environment])
.execute .execute
Set.new(alerts.map(&:prometheus_metric_id)) Set.new(alerts.map(&:prometheus_metric_id))
......
# frozen_string_literal: true
module Gitlab
module Metrics
module Dashboard
module Stages
class ClusterEndpointInserter < BaseStage
def transform!
verify_params
for_metrics do |metric|
metric[:prometheus_endpoint_path] = endpoint_for_metric(metric)
end
end
private
def admin_url(metric)
Gitlab::Routing.url_helpers.prometheus_api_admin_cluster_path(
params[:cluster],
proxy_path: query_type(metric),
query: query_for_metric(metric)
)
end
def endpoint_for_metric(metric)
case params[:cluster_type]
when :admin
admin_url(metric)
when :group
error!('Group is required when cluster_type is :group') unless params[:group]
group_url(metric)
when :project
error!('Project is required when cluster_type is :project') unless project
project_url(metric)
else
error!('Unrecognized cluster type')
end
end
def error!(message)
raise Errors::DashboardProcessingError.new(message)
end
def group_url(metric)
Gitlab::Routing.url_helpers.prometheus_api_group_cluster_path(
params[:group],
params[:cluster],
proxy_path: query_type(metric),
query: query_for_metric(metric)
)
end
def project_url(metric)
Gitlab::Routing.url_helpers.prometheus_api_project_cluster_path(
project,
params[:cluster],
proxy_path: query_type(metric),
query: query_for_metric(metric)
)
end
def query_type(metric)
metric[:query] ? :query : :query_range
end
def query_for_metric(metric)
query = metric[query_type(metric)]
raise Errors::MissingQueryError.new('Each "metric" must define one of :query or :query_range') unless query
query
end
def verify_params
raise Errors::DashboardProcessingError.new('Cluster is required for Stages::ClusterEndpointInserter') unless params[:cluster]
raise Errors::DashboardProcessingError.new('Cluster type must be specificed for Stages::ClusterEndpointInserter') unless params[:cluster_type]
end
end
end
end
end
end
...@@ -48,6 +48,16 @@ describe Admin::ClustersController do ...@@ -48,6 +48,16 @@ describe Admin::ClustersController do
end end
end end
end end
describe 'GET #metrics_dashboard' do
let(:user) { create(:admin) }
before do
sign_in(user)
end
it_behaves_like 'the default dashboard'
end
end end
private private
......
...@@ -78,6 +78,17 @@ describe Groups::ClustersController do ...@@ -78,6 +78,17 @@ describe Groups::ClustersController do
it { expect { go }.to be_denied_for(:user) } it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) } it { expect { go }.to be_denied_for(:external) }
end end
describe 'GET #metrics_dashboard' do
let(:user) { create(:user) }
before do
clusterable.add_maintainer(user)
sign_in(user)
end
it_behaves_like 'the default dashboard'
end
end end
private private
......
...@@ -70,6 +70,17 @@ describe Projects::ClustersController do ...@@ -70,6 +70,17 @@ describe Projects::ClustersController do
it { expect { go }.to be_denied_for(:user) } it { expect { go }.to be_denied_for(:user) }
it { expect { go }.to be_denied_for(:external) } it { expect { go }.to be_denied_for(:external) }
end end
describe 'GET #metrics_dashboard' do
let(:user) { create(:user) }
before do
clusterable.add_maintainer(user)
sign_in(user)
end
it_behaves_like 'the default dashboard'
end
end end
private private
......
...@@ -6,19 +6,19 @@ describe Gitlab::Metrics::Dashboard::Processor do ...@@ -6,19 +6,19 @@ describe Gitlab::Metrics::Dashboard::Processor do
let(:project) { build(:project) } let(:project) { build(:project) }
let(:environment) { create(:environment, project: project) } let(:environment) { create(:environment, project: project) }
let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') } let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
let(:params) { [project, environment, dashboard_yml] } let(:params) { [project, dashboard_yml, sequence, { environment: environment }] }
describe 'sequence' do
let(:environment) { build(:environment) }
let(:sequence) { described_class.new(*params).__send__(:sequence, insert_project_metrics: true) }
it 'includes the alerts processing stage' do
expect(sequence.length).to eq(5)
end
end
describe 'process' do describe 'process' do
let(:dashboard) { described_class.new(*params).process(insert_project_metrics: true) } let(:dashboard) { described_class.new(*params).process }
let(:sequence) do
[
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::ProjectMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter,
::EE::Gitlab::Metrics::Dashboard::Stages::AlertsInserter
]
end
context 'when the dashboard references persisted metrics with alerts' do context 'when the dashboard references persisted metrics with alerts' do
let!(:alert) do let!(:alert) do
......
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Metrics::Dashboard::ServiceSelector do
include MetricsDashboardHelpers
describe '#call' do
subject { described_class.call(arguments) }
context 'when cluster is provided' do
let(:arguments) { { cluster: "some cluster" } }
it { is_expected.to be Metrics::Dashboard::ClusterDashboardService }
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Metrics::Dashboard::ClusterDashboardService, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
set(:user) { create(:user) }
set(:cluster_project) { create(:cluster_project) }
set(:cluster) { cluster_project.cluster }
set(:project) { cluster_project.project }
before do
project.add_maintainer(user)
end
describe 'get_dashboard' do
let(:dashboard_path) { described_class::CLUSTER_DASHBOARD_PATH }
let(:service_params) { [project, user, { cluster: cluster, cluster_type: :project, dashboard_path: dashboard_path }] }
let(:service_call) { described_class.new(*service_params).get_dashboard }
it_behaves_like 'valid dashboard service response'
it 'caches the unprocessed dashboard for subsequent calls' do
expect(YAML).to receive(:safe_load).once.and_call_original
described_class.new(*service_params).get_dashboard
described_class.new(*service_params).get_dashboard
end
context 'when called with a non-system dashboard' do
let(:dashboard_path) { 'garbage/dashboard/path' }
# We want to always return the cluster dashboard.
it_behaves_like 'valid dashboard service response'
end
end
end
...@@ -188,6 +188,47 @@ shared_examples 'cluster metrics' do ...@@ -188,6 +188,47 @@ shared_examples 'cluster metrics' do
end end
end end
shared_examples_for 'correctly formatted response' do |status_code|
it 'returns a json object with the correct keys' do
get :metrics_dashboard, params: metrics_params, format: :json
found_keys = json_response.keys - ['all_dashboards']
expect(response).to have_gitlab_http_status(status_code)
expect(found_keys).to contain_exactly(*expected_keys)
end
end
shared_examples_for '200 response' do
let(:expected_keys) { %w(dashboard status) }
it_behaves_like 'correctly formatted response', :ok
end
shared_context 'error response' do |status_code|
let(:expected_keys) { %w(message status) }
it_behaves like 'correctly formatted response', status_code
end
shared_examples_for 'includes all dashboards' do
it 'includes info for all findable dashboards' do
expect(json_response).to have_key('all_dashboards')
expect(json_response['all_dashboards']).to be_an_instance_of(Array)
expect(json_response['all_dashboards']).to all( include('path', 'default', 'display_name') )
end
end
shared_examples_for 'the default dashboard' do
it_behaves_like '200 response'
it 'is the default dashboard' do
get :metrics_dashboard, params: metrics_params, format: :json
expect(json_response['dashboard']['dashboard']).to eq('Cluster health')
end
end
private private
def go def go
......
...@@ -7,14 +7,16 @@ module Gitlab ...@@ -7,14 +7,16 @@ module Gitlab
module Metrics module Metrics
module Dashboard module Dashboard
module Errors module Errors
DashboardProcessingError = Class.new(StandardError)
PanelNotFoundError = Class.new(StandardError) PanelNotFoundError = Class.new(StandardError)
LayoutError = Class.new(DashboardProcessingError)
MissingQueryError = Class.new(DashboardProcessingError)
PROCESSING_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError
NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
def handle_errors(error) def handle_errors(error)
case error case error
when PROCESSING_ERROR when DashboardProcessingError
error(error.message, :unprocessable_entity) error(error.message, :unprocessable_entity)
when NOT_FOUND_ERROR when NOT_FOUND_ERROR
error("#{dashboard_path} could not be found.", :not_found) error("#{dashboard_path} could not be found.", :not_found)
......
...@@ -28,9 +28,9 @@ module Gitlab ...@@ -28,9 +28,9 @@ module Gitlab
# @param options - y_label [String] Y-Axis label of # @param options - y_label [String] Y-Axis label of
# a panel. Used by embedded dashboards. # a panel. Used by embedded dashboards.
# @return [Hash] # @return [Hash]
def find(project, user, environment, options = {}) def find(project, user, options = {})
service_for(options) service_for(options)
.new(project, user, options.merge(environment: environment)) .new(project, user, options)
.get_dashboard .get_dashboard
end end
......
...@@ -8,43 +8,23 @@ module Gitlab ...@@ -8,43 +8,23 @@ module Gitlab
# the UI. These includes shared metric info, custom metrics # the UI. These includes shared metric info, custom metrics
# info, and alerts (only in EE). # info, and alerts (only in EE).
class Processor class Processor
SYSTEM_SEQUENCE = [ def initialize(project, dashboard, sequence, params)
Stages::CommonMetricsInserter,
Stages::ProjectMetricsInserter,
Stages::EndpointInserter,
Stages::Sorter
].freeze
PROJECT_SEQUENCE = [
Stages::CommonMetricsInserter,
Stages::EndpointInserter,
Stages::Sorter
].freeze
def initialize(project, environment, dashboard)
@project = project @project = project
@environment = environment
@dashboard = dashboard @dashboard = dashboard
@sequence = sequence
@params = params
end end
# Returns a new dashboard hash with the results of # Returns a new dashboard hash with the results of
# running transforms on the dashboard. # running transforms on the dashboard.
def process(insert_project_metrics:) def process
@dashboard.deep_symbolize_keys.tap do |dashboard| @dashboard.deep_symbolize_keys.tap do |dashboard|
sequence(insert_project_metrics).each do |stage| @sequence.each do |stage|
stage.new(@project, @environment, dashboard).transform! stage.new(@project, dashboard, @params).transform!
end end
end end
end end
private
def sequence(insert_project_metrics)
insert_project_metrics ? SYSTEM_SEQUENCE : PROJECT_SEQUENCE
end
end end
end end
end end
end end
Gitlab::Metrics::Dashboard::Processor.prepend_if_ee('EE::Gitlab::Metrics::Dashboard::Processor')
...@@ -48,3 +48,5 @@ module Gitlab ...@@ -48,3 +48,5 @@ module Gitlab
end end
end end
end end
Gitlab::Metrics::Dashboard::ServiceSelector.prepend_if_ee('EE::Gitlab::Metrics::Dashboard::ServiceSelector')
...@@ -7,15 +7,12 @@ module Gitlab ...@@ -7,15 +7,12 @@ module Gitlab
class BaseStage class BaseStage
include Gitlab::Metrics::Dashboard::Defaults include Gitlab::Metrics::Dashboard::Defaults
DashboardProcessingError = Class.new(StandardError) attr_reader :project, :dashboard, :params
LayoutError = Class.new(DashboardProcessingError)
attr_reader :project, :environment, :dashboard def initialize(project, dashboard, params)
def initialize(project, environment, dashboard)
@project = project @project = project
@environment = environment
@dashboard = dashboard @dashboard = dashboard
@params = params
end end
# Entry-point to the stage # Entry-point to the stage
...@@ -26,15 +23,15 @@ module Gitlab ...@@ -26,15 +23,15 @@ module Gitlab
protected protected
def missing_panel_groups! def missing_panel_groups!
raise LayoutError.new('Top-level key :panel_groups must be an array') raise Errors::LayoutError.new('Top-level key :panel_groups must be an array')
end end
def missing_panels! def missing_panels!
raise LayoutError.new('Each "panel_group" must define an array :panels') raise Errors::LayoutError.new('Each "panel_group" must define an array :panels')
end end
def missing_metrics! def missing_metrics!
raise LayoutError.new('Each "panel" must define an array :metrics') raise Errors::LayoutError.new('Each "panel" must define an array :metrics')
end end
def for_metrics def for_metrics
......
...@@ -5,9 +5,9 @@ module Gitlab ...@@ -5,9 +5,9 @@ module Gitlab
module Dashboard module Dashboard
module Stages module Stages
class EndpointInserter < BaseStage class EndpointInserter < BaseStage
MissingQueryError = Class.new(DashboardProcessingError)
def transform! def transform!
raise Errors::DashboardProcessingError.new('Environment is required for Stages::EndpointInserter') unless params[:environment]
for_metrics do |metric| for_metrics do |metric|
metric[:prometheus_endpoint_path] = endpoint_for_metric(metric) metric[:prometheus_endpoint_path] = endpoint_for_metric(metric)
end end
...@@ -18,7 +18,7 @@ module Gitlab ...@@ -18,7 +18,7 @@ module Gitlab
def endpoint_for_metric(metric) def endpoint_for_metric(metric)
Gitlab::Routing.url_helpers.prometheus_api_project_environment_path( Gitlab::Routing.url_helpers.prometheus_api_project_environment_path(
project, project,
environment, params[:environment],
proxy_path: query_type(metric), proxy_path: query_type(metric),
query: query_for_metric(metric) query: query_for_metric(metric)
) )
...@@ -31,7 +31,7 @@ module Gitlab ...@@ -31,7 +31,7 @@ module Gitlab
def query_for_metric(metric) def query_for_metric(metric)
query = metric[query_type(metric)] query = metric[query_type(metric)]
raise MissingQueryError.new('Each "metric" must define one of :query or :query_range') unless query raise Errors::MissingQueryError.new('Each "metric" must define one of :query or :query_range') unless query
query query
end end
......
...@@ -15,7 +15,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi ...@@ -15,7 +15,7 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
describe '.find' do describe '.find' do
let(:dashboard_path) { '.gitlab/dashboards/test.yml' } let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
let(:service_call) { described_class.find(project, user, environment, dashboard_path: dashboard_path) } let(:service_call) { described_class.find(project, user, environment: environment, dashboard_path: dashboard_path) }
it_behaves_like 'misconfigured dashboard service response', :not_found it_behaves_like 'misconfigured dashboard service response', :not_found
...@@ -45,19 +45,19 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi ...@@ -45,19 +45,19 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
end end
context 'when no dashboard is specified' do context 'when no dashboard is specified' do
let(:service_call) { described_class.find(project, user, environment) } let(:service_call) { described_class.find(project, user, environment: environment) }
it_behaves_like 'valid dashboard service response' it_behaves_like 'valid dashboard service response'
end end
context 'when the dashboard is expected to be embedded' do context 'when the dashboard is expected to be embedded' do
let(:service_call) { described_class.find(project, user, environment, **params) } let(:service_call) { described_class.find(project, user, **params) }
let(:params) { { embedded: true } } let(:params) { { environment: environment, embedded: true } }
it_behaves_like 'valid embedded dashboard service response' it_behaves_like 'valid embedded dashboard service response'
context 'when params are incomplete' do context 'when params are incomplete' do
let(:params) { { embedded: true, dashboard_path: system_dashboard_path } } let(:params) { { environment: environment, embedded: true, dashboard_path: system_dashboard_path } }
it_behaves_like 'valid embedded dashboard service response' it_behaves_like 'valid embedded dashboard service response'
end end
...@@ -65,11 +65,14 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi ...@@ -65,11 +65,14 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
context 'when the panel is specified' do context 'when the panel is specified' do
context 'as a custom metric' do context 'as a custom metric' do
let(:params) do let(:params) do
{ embedded: true, {
environment: environment,
embedded: true,
dashboard_path: system_dashboard_path, dashboard_path: system_dashboard_path,
group: business_metric_title, group: business_metric_title,
title: 'title', title: 'title',
y_label: 'y_label' } y_label: 'y_label'
}
end end
it_behaves_like 'misconfigured dashboard service response', :not_found it_behaves_like 'misconfigured dashboard service response', :not_found
...@@ -86,11 +89,14 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi ...@@ -86,11 +89,14 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
context 'as a project-defined panel' do context 'as a project-defined panel' do
let(:dashboard_path) { '.gitlab/dashboard/test.yml' } let(:dashboard_path) { '.gitlab/dashboard/test.yml' }
let(:params) do let(:params) do
{ embedded: true, {
environment: environment,
embedded: true,
dashboard_path: dashboard_path, dashboard_path: dashboard_path,
group: 'Group A', group: 'Group A',
title: 'Super Chart A1', title: 'Super Chart A1',
y_label: 'y_label' } y_label: 'y_label'
}
end end
it_behaves_like 'misconfigured dashboard service response', :not_found it_behaves_like 'misconfigured dashboard service response', :not_found
......
...@@ -8,8 +8,16 @@ describe Gitlab::Metrics::Dashboard::Processor do ...@@ -8,8 +8,16 @@ describe Gitlab::Metrics::Dashboard::Processor do
let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') } let(:dashboard_yml) { YAML.load_file('spec/fixtures/lib/gitlab/metrics/dashboard/sample_dashboard.yml') }
describe 'process' do describe 'process' do
let(:process_params) { [project, environment, dashboard_yml] } let(:sequence) do
let(:dashboard) { described_class.new(*process_params).process(insert_project_metrics: true) } [
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::ProjectMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter
]
end
let(:process_params) { [project, dashboard_yml, sequence, { environment: environment }] }
let(:dashboard) { described_class.new(*process_params).process }
it 'includes a path for the prometheus endpoint with each metric' do it 'includes a path for the prometheus endpoint with each metric' do
expect(all_metrics).to satisfy_all do |metric| expect(all_metrics).to satisfy_all do |metric|
...@@ -54,7 +62,14 @@ describe Gitlab::Metrics::Dashboard::Processor do ...@@ -54,7 +62,14 @@ describe Gitlab::Metrics::Dashboard::Processor do
end end
context 'when the dashboard should not include project metrics' do context 'when the dashboard should not include project metrics' do
let(:dashboard) { described_class.new(*process_params).process(insert_project_metrics: false) } let(:sequence) do
[
Gitlab::Metrics::Dashboard::Stages::CommonMetricsInserter,
Gitlab::Metrics::Dashboard::Stages::EndpointInserter,
Gitlab::Metrics::Dashboard::Stages::Sorter
]
end
let(:dashboard) { described_class.new(*process_params).process }
it 'includes only dashboard metrics' do it 'includes only dashboard metrics' do
metrics = all_metrics.map { |m| m[:id] } metrics = all_metrics.map { |m| m[:id] }
...@@ -67,7 +82,7 @@ describe Gitlab::Metrics::Dashboard::Processor do ...@@ -67,7 +82,7 @@ describe Gitlab::Metrics::Dashboard::Processor do
shared_examples_for 'errors with message' do |expected_message| shared_examples_for 'errors with message' do |expected_message|
it 'raises a DashboardLayoutError' do it 'raises a DashboardLayoutError' do
error_class = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError error_class = Gitlab::Metrics::Dashboard::Errors::DashboardProcessingError
expect { dashboard }.to raise_error(error_class, expected_message) expect { dashboard }.to raise_error(error_class, expected_message)
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