gitlab-org--gitlab-foss/app/services/grafana/proxy_service.rb

94 lines
2.9 KiB
Ruby

# frozen_string_literal: true
# Proxies calls to a Grafana-integrated Prometheus instance
# through the Grafana proxy API
# This allows us to fetch and render metrics in GitLab from a Prometheus
# instance for which dashboards are configured in Grafana
module Grafana
class ProxyService < BaseService
include ReactiveCaching
self.reactive_cache_key = ->(service) { service.cache_key }
self.reactive_cache_lease_timeout = 30.seconds
self.reactive_cache_refresh_interval = 30.seconds
self.reactive_cache_work_type = :external_dependency
self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
SUPPORTED_DATASOURCE_PATTERN = %r{\A\d+\z}.freeze
SUPPORTED_PROXY_PATH = Gitlab::Metrics::Dashboard::Stages::GrafanaFormatter::PROXY_PATH
attr_accessor :project, :datasource_id, :proxy_path, :query_params
# @param project_id [Integer] Project id for which grafana is configured.
#
# See #initialize for other parameters.
def self.from_cache(project_id, datasource_id, proxy_path, query_params)
project = Project.find(project_id)
new(project, datasource_id, proxy_path, query_params)
end
# @param project [Project] Project for which grafana is configured.
# @param datasource_id [String] Grafana datasource id for Prometheus instance
# @param proxy_path [String] Path to Prometheus endpoint; EX) 'api/v1/query_range'
# @param query_params [Hash<String, String>] Supported params: [query, start, end, step]
def initialize(project, datasource_id, proxy_path, query_params)
@project = project
@datasource_id = datasource_id
@proxy_path = proxy_path
@query_params = query_params
end
def execute
return cannot_proxy_response unless can_proxy?
return cannot_proxy_response unless client
with_reactive_cache(*cache_key) { |result| result }
end
def calculate_reactive_cache(*)
return cannot_proxy_response unless client
response = client.proxy_datasource(
datasource_id: datasource_id,
proxy_path: proxy_path,
query: query_params
)
success(http_status: response.code, body: response.body)
rescue ::Grafana::Client::Error => error
service_unavailable_response(error)
end
# Required for ReactiveCaching; Usage overridden by
# self.reactive_cache_worker_finder
def id
nil
end
def cache_key
[project.id, datasource_id, proxy_path, query_params]
end
private
def can_proxy?
SUPPORTED_PROXY_PATH == proxy_path &&
SUPPORTED_DATASOURCE_PATTERN.match?(datasource_id)
end
def client
project.grafana_integration&.client
end
def service_unavailable_response(exception)
error(exception.message, :service_unavailable)
end
def cannot_proxy_response
error('Proxy support for this API is not available currently')
end
end
end