Commit 1b748440 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'pawel/connect_to_prometheus_through_proxy-30480' into 'master'

Deploy prometheus through kubernetes and autoconnect to cluster

Closes #30480 and #28916

See merge request gitlab-org/gitlab-ce!16182
parents bb6895ec 0e90284c
......@@ -32,6 +32,7 @@ export default class Clusters {
installIngressPath,
installRunnerPath,
installPrometheusPath,
managePrometheusPath,
clusterStatus,
clusterStatusReason,
helpPath,
......@@ -40,6 +41,7 @@ export default class Clusters {
this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath);
this.store.updateStatus(clusterStatus);
this.store.updateStatusReason(clusterStatusReason);
this.service = new ClustersService({
......@@ -95,6 +97,7 @@ export default class Clusters {
applications: this.state.applications,
helpPath: this.state.helpPath,
ingressHelpPath: this.state.ingressHelpPath,
managePrometheusPath: this.state.managePrometheusPath,
},
});
},
......
......@@ -32,6 +32,10 @@
type: String,
required: false,
},
manageLink: {
type: String,
required: false,
},
description: {
type: String,
required: true,
......@@ -89,6 +93,12 @@
return label;
},
showManageButton() {
return this.manageLink && this.status === APPLICATION_INSTALLED;
},
manageButtonLabel() {
return s__('ClusterIntegration|Manage');
},
hasError() {
return this.status === APPLICATION_ERROR ||
this.requestStatus === REQUEST_FAILURE;
......@@ -141,9 +151,21 @@
<div v-html="description"></div>
</div>
<div
class="table-section table-button-footer section-15 section-align-top"
class="table-section table-button-footer section-align-top"
:class="{ 'section-20': showManageButton, 'section-15': !showManageButton }"
role="gridcell"
>
<div
v-if="showManageButton"
class="btn-group table-action-buttons"
>
<a
class="btn"
:href="manageLink"
>
{{ manageButtonLabel }}
</a>
</div>
<div class="btn-group table-action-buttons">
<loading-button
class="js-cluster-application-install-button"
......
......@@ -23,13 +23,19 @@
required: false,
default: '',
},
managePrometheusPath: {
type: String,
required: false,
default: '',
},
},
computed: {
generalApplicationDescription() {
return sprintf(
_.escape(s__(`ClusterIntegration|Install applications on your Kubernetes cluster.
Read more about %{helpLink}`)),
{
_.escape(s__(
`ClusterIntegration|Install applications on your Kubernetes cluster.
Read more about %{helpLink}`,
)), {
helpLink: `<a href="${this.helpPath}">
${_.escape(s__('ClusterIntegration|installing applications'))}
</a>`,
......@@ -96,11 +102,12 @@
},
prometheusDescription() {
return sprintf(
_.escape(s__(`ClusterIntegration|Prometheus is an open-source monitoring system
with %{gitlabIntegrationLink} to monitor deployed applications.`)),
{
_.escape(s__(
`ClusterIntegration|Prometheus is an open-source monitoring system
with %{gitlabIntegrationLink} to monitor deployed applications.`,
)), {
gitlabIntegrationLink: `<a href="https://docs.gitlab.com/ce/user/project/integrations/prometheus.html"
target="_blank" rel="noopener noreferrer">
target="_blank" rel="noopener noreferrer">
${_.escape(s__('ClusterIntegration|GitLab Integration'))}</a>`,
},
false,
......@@ -149,6 +156,7 @@ target="_blank" rel="noopener noreferrer">
id="prometheus"
:title="applications.prometheus.title"
title-link="https://prometheus.io/docs/introduction/overview/"
:manage-link="managePrometheusPath"
:description="prometheusDescription"
:status="applications.prometheus.status"
:status-reason="applications.prometheus.statusReason"
......
......@@ -45,6 +45,10 @@ export default class ClusterStore {
this.state.ingressHelpPath = ingressHelpPath;
}
setManagePrometheusPath(managePrometheusPath) {
this.state.managePrometheusPath = managePrometheusPath;
}
updateStatus(status) {
this.state.status = status;
}
......
......@@ -27,6 +27,7 @@
hasMetrics: convertPermissionToBoolean(metricsData.hasMetrics),
documentationPath: metricsData.documentationPath,
settingsPath: metricsData.settingsPath,
clustersPath: metricsData.clustersPath,
tagsPath: metricsData.tagsPath,
projectPath: metricsData.projectPath,
metricsEndpoint: metricsData.additionalMetrics,
......@@ -132,6 +133,7 @@
:selected-state="state"
:documentation-path="documentationPath"
:settings-path="settingsPath"
:clusters-path="clustersPath"
:empty-getting-started-svg-path="emptyGettingStartedSvgPath"
:empty-loading-svg-path="emptyLoadingSvgPath"
:empty-unable-to-connect-svg-path="emptyUnableToConnectSvgPath"
......
......@@ -10,6 +10,11 @@
required: false,
default: '',
},
clustersPath: {
type: String,
required: false,
default: '',
},
selectedState: {
type: String,
required: true,
......@@ -35,7 +40,10 @@
title: 'Get started with performance monitoring',
description: `Stay updated about the performance and health
of your environment by configuring Prometheus to monitor your deployments.`,
buttonText: 'Configure Prometheus',
buttonText: 'Install Prometheus on clusters',
buttonPath: this.clustersPath,
secondaryButtonText: 'Configure existing Prometheus',
secondaryButtonPath: this.settingsPath,
},
loading: {
svgUrl: this.emptyLoadingSvgPath,
......@@ -43,6 +51,7 @@
description: `Creating graphs uses the data from the Prometheus server.
If this takes a long time, ensure that data is available.`,
buttonText: 'View documentation',
buttonPath: this.documentationPath,
},
noData: {
svgUrl: this.emptyUnableToConnectSvgPath,
......@@ -50,12 +59,14 @@
description: `You are connected to the Prometheus server, but there is currently
no data to display.`,
buttonText: 'Configure Prometheus',
buttonPath: this.settingsPath,
},
unableToConnect: {
svgUrl: this.emptyUnableToConnectSvgPath,
title: 'Unable to connect to Prometheus server',
description: 'Ensure connectivity is available from the GitLab server to the ',
buttonText: 'View documentation',
buttonPath: this.documentationPath,
},
},
};
......@@ -65,13 +76,6 @@
return this.states[this.selectedState];
},
buttonPath() {
if (this.selectedState === 'gettingStarted') {
return this.settingsPath;
}
return this.documentationPath;
},
showButtonDescription() {
if (this.selectedState === 'unableToConnect') return true;
return false;
......@@ -99,11 +103,21 @@
</p>
<div class="state-button">
<a
v-if="currentState.buttonPath"
class="btn btn-success"
:href="buttonPath"
:href="currentState.buttonPath"
>
{{ currentState.buttonText }}
</a>
</div>
<div class="state-button">
<a
v-if="currentState.secondaryButtonPath"
class="btn"
:href="currentState.secondaryButtonPath"
>
{{ currentState.secondaryButtonText }}
</a>
</div>
</div>
</template>
......@@ -205,7 +205,7 @@
}
.prometheus-state {
max-width: 430px;
max-width: 460px;
margin: 10px auto;
text-align: center;
......@@ -213,6 +213,10 @@
max-width: 80vw;
margin: 0 auto;
}
.state-button {
padding: $gl-padding / 2;
}
}
.environments-actions {
......
......@@ -135,6 +135,17 @@
padding-top: 0;
}
.integration-settings-form {
.well {
padding: $gl-padding / 2;
box-shadow: none;
}
.svg-container {
max-width: 150px;
}
}
.token-token-container {
#impersonation-token-token {
width: 80%;
......
......@@ -32,6 +32,7 @@ module ServiceParams
:issues_events,
:issues_url,
:jira_issue_transition_id,
:manual_configuration,
:merge_requests_events,
:mock_service_url,
:namespace,
......
......@@ -10,10 +10,26 @@ module Clusters
default_value_for :version, VERSION
state_machine :status do
after_transition any => [:installed] do |application|
application.cluster.projects.each do |project|
project.find_or_initialize_service('prometheus').update(active: true)
end
end
end
def chart
'stable/prometheus'
end
def service_name
'prometheus-prometheus-server'
end
def service_port
80
end
def chart_values_file
"#{Rails.root}/vendor/#{name}/values.yaml"
end
......@@ -21,6 +37,22 @@ module Clusters
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(name, chart: chart, chart_values_file: chart_values_file)
end
def proxy_client
return unless kube_client
proxy_url = kube_client.proxy_url('service', service_name, service_port, Gitlab::Kubernetes::Helm::NAMESPACE)
# ensures headers containing auth data are appended to original k8s client options
options = kube_client.rest_client.options.merge(headers: kube_client.headers)
RestClient::Resource.new(proxy_url, options)
end
private
def kube_client
cluster&.kubeclient
end
end
end
end
......@@ -49,6 +49,9 @@ module Clusters
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
scope :for_environment, -> (env) { where(environment_scope: ['*', '', env.slug]) }
scope :for_all_environments, -> { where(environment_scope: ['*', '']) }
def status_name
if provider
provider.status_name
......
......@@ -7,11 +7,14 @@ class PrometheusService < MonitoringService
# Access to prometheus is directly through the API
prop_accessor :api_url
boolean_accessor :manual_configuration
with_options presence: true, if: :activated? do
with_options presence: true, if: :manual_configuration? do
validates :api_url, url: true
end
before_save :synchronize_service_state!
after_save :clear_reactive_cache!
def initialize_properties
......@@ -20,12 +23,20 @@ class PrometheusService < MonitoringService
end
end
def show_active_box?
false
end
def editable?
manual_configuration? || !prometheus_installed?
end
def title
'Prometheus'
end
def description
s_('PrometheusService|Prometheus monitoring')
s_('PrometheusService|Time-series monitoring service')
end
def self.to_param
......@@ -33,7 +44,15 @@ class PrometheusService < MonitoringService
end
def fields
return [] unless editable?
[
{
type: 'checkbox',
name: 'manual_configuration',
title: s_('PrometheusService|Active'),
required: true
},
{
type: 'text',
name: 'api_url',
......@@ -59,7 +78,7 @@ class PrometheusService < MonitoringService
end
def deployment_metrics(deployment)
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.id, &method(:rename_data_to_metrics))
metrics = with_reactive_cache(Gitlab::Prometheus::Queries::DeploymentQuery.name, deployment.environment.id, deployment.id, &method(:rename_data_to_metrics))
metrics&.merge(deployment_time: deployment.created_at.to_i) || {}
end
......@@ -68,7 +87,7 @@ class PrometheusService < MonitoringService
end
def additional_deployment_metrics(deployment)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.id, &:itself)
with_reactive_cache(Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery.name, deployment.environment.id, deployment.id, &:itself)
end
def matched_metrics
......@@ -79,6 +98,9 @@ class PrometheusService < MonitoringService
def calculate_reactive_cache(query_class_name, *args)
return unless active? && project && !project.pending_delete?
environment_id = args.first
client = client(environment_id)
data = Kernel.const_get(query_class_name).new(client).query(*args)
{
success: true,
......@@ -89,14 +111,55 @@ class PrometheusService < MonitoringService
{ success: false, result: err.message }
end
def client
@prometheus ||= Gitlab::PrometheusClient.new(api_url: api_url)
def client(environment_id = nil)
if manual_configuration?
Gitlab::PrometheusClient.new(RestClient::Resource.new(api_url))
else
cluster = cluster_with_prometheus(environment_id)
raise Gitlab::PrometheusError, "couldn't find cluster with Prometheus installed" unless cluster
rest_client = client_from_cluster(cluster)
raise Gitlab::PrometheusError, "couldn't create proxy Prometheus client" unless rest_client
Gitlab::PrometheusClient.new(rest_client)
end
end
def prometheus_installed?
return false if template?
return false unless project
project.clusters.enabled.any? { |cluster| cluster.application_prometheus&.installed? }
end
private
def cluster_with_prometheus(environment_id = nil)
clusters = if environment_id
::Environment.find_by(id: environment_id).try do |env|
# sort results by descending order based on environment_scope being longer
# thus more closely matching environment slug
project.clusters.enabled.for_environment(env).sort_by { |c| c.environment_scope&.length }.reverse!
end
else
project.clusters.enabled.for_all_environments
end
clusters&.detect { |cluster| cluster.application_prometheus&.installed? }
end
def client_from_cluster(cluster)
cluster.application_prometheus.proxy_client
end
def rename_data_to_metrics(metrics)
metrics[:metrics] = metrics.delete :data
metrics
end
def synchronize_service_state!
self.active = prometheus_installed? || manual_configuration?
true
end
end
......@@ -14,7 +14,8 @@
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
help_path: help_page_path('user/project/clusters/index.md', anchor: 'installing-applications'),
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address') } }
ingress_help_path: help_page_path('user/project/clusters/index.md', anchor: 'getting-the-external-ip-address'),
manage_prometheus_path: edit_project_service_path(@cluster.project, 'prometheus') } }
.js-cluster-application-notice
.flash-container
......
......@@ -13,6 +13,7 @@
= link_to @environment.name, environment_path(@environment)
#prometheus-graphs{ data: { "settings-path": edit_project_service_path(@project, 'prometheus'),
"clusters-path": project_clusters_path(@project),
"documentation-path": help_page_path('administration/monitoring/prometheus/index.md'),
"empty-getting-started-svg-path": image_path('illustrations/monitoring/getting_started.svg'),
"empty-loading-svg-path": image_path('illustrations/monitoring/loading.svg'),
......
......@@ -9,7 +9,7 @@
%p= @service.description
.col-lg-9
= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'gl-show-field-errors form-horizontal integration-settings-form js-integration-settings-form', data: { 'can-test' => @service.can_test?, 'test-url' => test_project_service_path(@project, @service) } }) do |form|
= render 'shared/service_settings', form: form, subject: @service
- if @service.editable?
.footer-block.row-content-block
......
%h4
= s_('PrometheusService|Auto configuration')
- if @service.manual_configuration?
.well
= s_('PrometheusService|To enable the installation of Prometheus on your clusters, deactivate the manual configuration below')
- else
.container-fluid
.row
- if @service.prometheus_installed?
.col-sm-2
.svg-container
= image_tag 'illustrations/monitoring/getting_started.svg'
.col-sm-10
%p.text-success.prepend-top-default
= s_('PrometheusService|Prometheus is being automatically managed on your clusters')
= link_to s_('PrometheusService|Manage clusters'), project_clusters_path(@project), class: 'btn'
- else
.col-sm-2
= image_tag 'illustrations/monitoring/loading.svg'
.col-sm-10
%p.prepend-top-default
= s_('PrometheusService|Automatically deploy and configure Prometheus on your clusters to monitor your project’s environments')
= link_to s_('PrometheusService|Install Prometheus on clusters'), project_clusters_path(@project), class: 'btn btn-success'
%hr
%h4.append-bottom-default
= s_('PrometheusService|Manual configuration')
- unless @service.editable?
.well
= s_('PrometheusService|To enable manual configuration, uninstall Prometheus from your clusters')
---
title: Implement multi server support and use kube proxy to connect to Prometheus
servers inside K8S cluster
merge_request: 16182
author:
type: added
......@@ -4,7 +4,7 @@ module Gitlab
class AdditionalMetricsDeploymentQuery < BaseQuery
include QueryAdditionalMetrics
def query(deployment_id)
def query(environment_id, deployment_id)
Deployment.find_by(id: deployment_id).try do |deployment|
query_metrics(
common_query_context(
......
......@@ -2,7 +2,7 @@ module Gitlab
module Prometheus
module Queries
class DeploymentQuery < BaseQuery
def query(deployment_id)
def query(environment_id, deployment_id)
Deployment.find_by(id: deployment_id).try do |deployment|
environment_slug = deployment.environment.slug
......
......@@ -3,10 +3,10 @@ module Gitlab
# Helper methods to interact with Prometheus network services & resources
class PrometheusClient
attr_reader :api_url
attr_reader :rest_client, :headers
def initialize(api_url:)
@api_url = api_url
def initialize(rest_client)
@rest_client = rest_client
end
def ping
......@@ -40,37 +40,40 @@ module Gitlab
private
def json_api_get(type, args = {})
get(join_api_url(type, args))
path = ['api', 'v1', type].join('/')
get(path, args)
rescue JSON::ParserError
raise PrometheusError, 'Parsing response failed'
rescue Errno::ECONNREFUSED
raise PrometheusError, 'Connection refused'
end
def join_api_url(type, args = {})
url = URI.parse(api_url)
rescue URI::Error
raise PrometheusError, "Invalid API URL: #{api_url}"
else
url.path = [url.path.sub(%r{/+\z}, ''), 'api', 'v1', type].join('/')
url.query = args.to_query
url.to_s
end
def get(url)
handle_response(HTTParty.get(url))
def get(path, args)
response = rest_client[path].get(params: args)
handle_response(response)
rescue SocketError
raise PrometheusError, "Can't connect to #{url}"
raise PrometheusError, "Can't connect to #{rest_client.url}"
rescue OpenSSL::SSL::SSLError
raise PrometheusError, "#{url} contains invalid SSL data"
rescue HTTParty::Error
raise PrometheusError, "#{rest_client.url} contains invalid SSL data"
rescue RestClient::ExceptionWithResponse => ex
handle_exception_response(ex.response)
rescue RestClient::Exception
raise PrometheusError, "Network connection error"
end
def handle_response(response)
if response.code == 200 && response['status'] == 'success'
response['data'] || {}
elsif response.code == 400
raise PrometheusError, response['error'] || 'Bad data received'
json_data = JSON.parse(response.body)
if response.code == 200 && json_data['status'] == 'success'
json_data['data'] || {}
else
raise PrometheusError, "#{response.code} - #{response.body}"
end
end
def handle_exception_response(response)
if response.code == 400
json_data = JSON.parse(response.body)
raise PrometheusError, json_data['error'] || 'Bad data received'
else
raise PrometheusError, "#{response.code} - #{response.body}"
end
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2141,7 +2141,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr "Nessuna metrica è stata monitorata. Per iniziare a monitorare, rilascia
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2032,7 +2032,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2018,7 +2018,7 @@ msgstr "Nenhuma métrica está sendo monitorada. Para inicar o monitoramento, fa
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr "URL da API base do Prometheus. como http://prometheus.example.com/"
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr "Monitoramento com Prometheus"
msgid "PrometheusService|View environments"
......
......@@ -2032,7 +2032,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2032,7 +2032,7 @@ msgstr "Жодні метрики не відслідковуються. Для
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr "Базова адреса Prometheus API, наприклад http://prometheus.example.com/"
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr "Моніторинг Prometheus"
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr "没有监测指标。要开始监测,请部署到环境中。"
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr "Prometheus API 地址,例如 http://prometheus.example.com/"
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr "Prometheus 监测"
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -2004,7 +2004,7 @@ msgstr ""
msgid "PrometheusService|Prometheus API Base URL, like http://prometheus.example.com/"
msgstr ""
msgid "PrometheusService|Prometheus monitoring"
msgid "PrometheusService|Time-series monitoring service"
msgstr ""
msgid "PrometheusService|View environments"
......
......@@ -249,7 +249,8 @@ FactoryBot.define do
project.create_prometheus_service(
active: true,
properties: {
api_url: 'https://prometheus.example.com'
api_url: 'https://prometheus.example.com/',
manual_configuration: true
}
)
end
......
......@@ -30,7 +30,8 @@ FactoryBot.define do
project
active true
properties({
api_url: 'https://prometheus.example.com/'
api_url: 'https://prometheus.example.com/',
manual_configuration: true
})
end
......
......@@ -29,34 +29,6 @@ describe('EmptyState', () => {
expect(component.currentState).toBe(component.states.gettingStarted);
});
it('buttonPath returns settings path for the state "gettingStarted"', () => {
const component = createComponent({
selectedState: 'gettingStarted',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
emptyGettingStartedSvgPath: 'foo',
emptyLoadingSvgPath: 'foo',
emptyUnableToConnectSvgPath: 'foo',
});
expect(component.buttonPath).toEqual(statePaths.settingsPath);
expect(component.buttonPath).not.toEqual(statePaths.documentationPath);
});
it('buttonPath returns documentation path for any of the other states', () => {
const component = createComponent({
selectedState: 'loading',
settingsPath: statePaths.settingsPath,
documentationPath: statePaths.documentationPath,
emptyGettingStartedSvgPath: 'foo',
emptyLoadingSvgPath: 'foo',
emptyUnableToConnectSvgPath: 'foo',
});
expect(component.buttonPath).toEqual(statePaths.documentationPath);
expect(component.buttonPath).not.toEqual(statePaths.settingsPath);
});
it('showButtonDescription returns a description with a link for the unableToConnect state', () => {
const component = createComponent({
selectedState: 'unableToConnect',
......@@ -88,6 +60,7 @@ describe('EmptyState', () => {
const component = createComponent({
selectedState: 'gettingStarted',
settingsPath: statePaths.settingsPath,
clustersPath: statePaths.clustersPath,
documentationPath: statePaths.documentationPath,
emptyGettingStartedSvgPath: 'foo',
emptyLoadingSvgPath: 'foo',
......
......@@ -2471,6 +2471,7 @@ export const deploymentData = [
export const statePaths = {
settingsPath: '/root/hello-prometheus/services/prometheus/edit',
clustersPath: '/root/hello-prometheus/clusters',
documentationPath: '/help/administration/monitoring/prometheus/index.md',
};
......
......@@ -7,7 +7,7 @@ describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
include_examples 'additional metrics query' do
let(:deployment) { create(:deployment, environment: environment) }
let(:query_params) { [deployment.id] }
let(:query_params) { [environment.id, deployment.id] }
it 'queries using specific time' do
expect(client).to receive(:query_range).with(anything,
......
......@@ -31,7 +31,7 @@ describe Gitlab::Prometheus::Queries::DeploymentQuery do
expect(client).to receive(:query).with('avg(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="environment-slug"}[30m])) * 100',
time: stop_time)
expect(subject.query(deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil,
expect(subject.query(environment.id, deployment.id)).to eq(memory_values: nil, memory_before: nil, memory_after: nil,
cpu_values: nil, cpu_before: nil, cpu_after: nil)
end
end
......@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::PrometheusClient do
include PrometheusHelpers
subject { described_class.new(api_url: 'https://prometheus.example.com') }
subject { described_class.new(RestClient::Resource.new('https://prometheus.example.com')) }
describe '#ping' do
it 'issues a "query" request to the API endpoint' do
......@@ -47,16 +47,28 @@ describe Gitlab::PrometheusClient do
expect(req_stub).to have_been_requested
end
end
context 'when request returns non json data' do
it 'raises a Gitlab::PrometheusError error' do
req_stub = stub_prometheus_request(query_url, status: 200, body: 'not json')
expect { execute_query }
.to raise_error(Gitlab::PrometheusError, 'Parsing response failed')
expect(req_stub).to have_been_requested
end
end
end
describe 'failure to reach a provided prometheus url' do
let(:prometheus_url) {"https://prometheus.invalid.example.com"}
subject { described_class.new(RestClient::Resource.new(prometheus_url)) }
context 'exceptions are raised' do
it 'raises a Gitlab::PrometheusError error when a SocketError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, SocketError)
expect { subject.send(:get, prometheus_url) }
expect { subject.send(:get, '/', {}) }
.to raise_error(Gitlab::PrometheusError, "Can't connect to #{prometheus_url}")
expect(req_stub).to have_been_requested
end
......@@ -64,15 +76,15 @@ describe Gitlab::PrometheusClient do
it 'raises a Gitlab::PrometheusError error when a SSLError is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, OpenSSL::SSL::SSLError)
expect { subject.send(:get, prometheus_url) }
expect { subject.send(:get, '/', {}) }
.to raise_error(Gitlab::PrometheusError, "#{prometheus_url} contains invalid SSL data")
expect(req_stub).to have_been_requested
end
it 'raises a Gitlab::PrometheusError error when a HTTParty::Error is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, HTTParty::Error)
it 'raises a Gitlab::PrometheusError error when a RestClient::Exception is rescued' do
req_stub = stub_prometheus_request_with_exception(prometheus_url, RestClient::Exception)
expect { subject.send(:get, prometheus_url) }
expect { subject.send(:get, '/', {}) }
.to raise_error(Gitlab::PrometheusError, "Network connection error")
expect(req_stub).to have_been_requested
end
......
......@@ -6,6 +6,24 @@ describe Clusters::Applications::Prometheus do
include_examples 'cluster application specs', described_class
describe 'transition to installed' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, projects: [project]) }
let(:prometheus_service) { double('prometheus_service') }
subject { create(:clusters_applications_prometheus, :installing, cluster: cluster) }
before do
allow(project).to receive(:find_or_initialize_service).with('prometheus').and_return prometheus_service
end
it 'ensures Prometheus service is activated' do
expect(prometheus_service).to receive(:update).with(active: true)
subject.make_installed
end
end
describe "#chart_values_file" do
subject { create(:clusters_applications_prometheus).chart_values_file }
......@@ -13,4 +31,58 @@ describe Clusters::Applications::Prometheus do
expect(subject).to eq("#{Rails.root}/vendor/prometheus/values.yaml")
end
end
describe '#proxy_client' do
context 'cluster is nil' do
it 'returns nil' do
expect(subject.cluster).to be_nil
expect(subject.proxy_client).to be_nil
end
end
context "cluster doesn't have kubeclient" do
let(:cluster) { create(:cluster) }
subject { create(:clusters_applications_prometheus, cluster: cluster) }
it 'returns nil' do
expect(subject.proxy_client).to be_nil
end
end
context 'cluster has kubeclient' do
let(:kubernetes_url) { 'http://example.com' }
let(:k8s_discover_response) do
{
resources: [
{
name: 'service',
kind: 'Service'
}
]
}
end
let(:kube_client) { Kubeclient::Client.new(kubernetes_url) }
let(:cluster) { create(:cluster) }
subject { create(:clusters_applications_prometheus, cluster: cluster) }
before do
allow(kube_client.rest_client).to receive(:get).and_return(k8s_discover_response.to_json)
allow(subject.cluster).to receive(:kubeclient).and_return(kube_client)
end
it 'creates proxy prometheus rest client' do
expect(subject.proxy_client).to be_instance_of(RestClient::Resource)
end
it 'creates proper url' do
expect(subject.proxy_client.url).to eq('http://example.com/api/v1/proxy/namespaces/gitlab-managed-apps/service/prometheus-prometheus-server:80')
end
it 'copies options and headers from kube client to proxy client' do
expect(subject.proxy_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers))
end
end
end
end
......@@ -13,17 +13,17 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end
describe 'Validations' do
context 'when service is active' do
context 'when manual_configuration is enabled' do
before do
subject.active = true
subject.manual_configuration = true
end
it { is_expected.to validate_presence_of(:api_url) }
end
context 'when service is inactive' do
context 'when manual configuration is disabled' do
before do
subject.active = false
subject.manual_configuration = false
end
it { is_expected.not_to validate_presence_of(:api_url) }
......@@ -31,12 +31,17 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end
describe '#test' do
before do
service.manual_configuration = true
end
let!(:req_stub) { stub_prometheus_request(prometheus_query_url('1'), body: prometheus_value_body('vector')) }
context 'success' do
it 'reads the discovery endpoint' do
expect(service.test[:result]).to eq('Checked API endpoint')
expect(service.test[:success]).to be_truthy
expect(req_stub).to have_been_requested
expect(req_stub).to have_been_requested.twice
end
end
......@@ -70,6 +75,25 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end
end
describe '#matched_metrics' do
let(:matched_metrics_query) { Gitlab::Prometheus::Queries::MatchedMetricsQuery }
let(:client) { double(:client, label_values: nil) }
context 'with valid data' do
subject { service.matched_metrics }
before do
allow(service).to receive(:client).and_return(client)
synchronous_reactive_cache(service)
end
it 'returns reactive data' do
expect(subject[:success]).to be_truthy
expect(subject[:data]).to eq([])
end
end
end
describe '#deployment_metrics' do
let(:deployment) { build_stubbed(:deployment) }
let(:deployment_query) { Gitlab::Prometheus::Queries::DeploymentQuery }
......@@ -83,7 +107,7 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
let(:fake_deployment_time) { 10 }
before do
stub_reactive_cache(service, prometheus_data, deployment_query, deployment.id)
stub_reactive_cache(service, prometheus_data, deployment_query, deployment.environment.id, deployment.id)
end
it 'returns reactive data' do
......@@ -96,13 +120,17 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
describe '#calculate_reactive_cache' do
let(:environment) { create(:environment, slug: 'env-slug') }
around do |example|
Timecop.freeze { example.run }
before do
service.manual_configuration = true
service.active = true
end
subject do
service.calculate_reactive_cache(environment_query.to_s, environment.id)
service.calculate_reactive_cache(environment_query.name, environment.id)
end
around do |example|
Timecop.freeze { example.run }
end
context 'when service is inactive' do
......@@ -132,4 +160,193 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
end
end
end
describe '#client' do
context 'manual configuration is enabled' do
let(:api_url) { 'http://some_url' }
before do
subject.manual_configuration = true
subject.api_url = api_url
end
it 'returns simple rest client from api_url' do
expect(subject.client).to be_instance_of(Gitlab::PrometheusClient)
expect(subject.client.rest_client.url).to eq(api_url)
end
end
context 'manual configuration is disabled' do
let!(:cluster_for_all) { create(:cluster, environment_scope: '*', projects: [project]) }
let!(:cluster_for_dev) { create(:cluster, environment_scope: 'dev', projects: [project]) }
let!(:prometheus_for_dev) { create(:clusters_applications_prometheus, :installed, cluster: cluster_for_dev) }
let(:proxy_client) { double('proxy_client') }
before do
service.manual_configuration = false
end
context 'with cluster for all environments with prometheus installed' do
let!(:prometheus_for_all) { create(:clusters_applications_prometheus, :installed, cluster: cluster_for_all) }
context 'without environment supplied' do
it 'returns client handling all environments' do
expect(service).to receive(:client_from_cluster).with(cluster_for_all).and_return(proxy_client).twice
expect(service.client).to be_instance_of(Gitlab::PrometheusClient)
expect(service.client.rest_client).to eq(proxy_client)
end
end
context 'with dev environment supplied' do
let!(:environment) { create(:environment, project: project, name: 'dev') }
it 'returns dev cluster client' do
expect(service).to receive(:client_from_cluster).with(cluster_for_dev).and_return(proxy_client).twice
expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient)
expect(service.client(environment.id).rest_client).to eq(proxy_client)
end
end
context 'with prod environment supplied' do
let!(:environment) { create(:environment, project: project, name: 'prod') }
it 'returns dev cluster client' do
expect(service).to receive(:client_from_cluster).with(cluster_for_all).and_return(proxy_client).twice
expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient)
expect(service.client(environment.id).rest_client).to eq(proxy_client)
end
end
end
context 'with cluster for all environments without prometheus installed' do
context 'without environment supplied' do
it 'raises PrometheusError because cluster was not found' do
expect { service.client }.to raise_error(Gitlab::PrometheusError, /couldn't find cluster with Prometheus installed/)
end
end
context 'with dev environment supplied' do
let!(:environment) { create(:environment, project: project, name: 'dev') }
it 'returns dev cluster client' do
expect(service).to receive(:client_from_cluster).with(cluster_for_dev).and_return(proxy_client).twice
expect(service.client(environment.id)).to be_instance_of(Gitlab::PrometheusClient)
expect(service.client(environment.id).rest_client).to eq(proxy_client)
end
end
context 'with prod environment supplied' do
let!(:environment) { create(:environment, project: project, name: 'prod') }
it 'raises PrometheusError because cluster was not found' do
expect { service.client }.to raise_error(Gitlab::PrometheusError, /couldn't find cluster with Prometheus installed/)
end
end
end
end
end
describe '#prometheus_installed?' do
context 'clusters with installed prometheus' do
let!(:cluster) { create(:cluster, projects: [project]) }
let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
it 'returns true' do
expect(service.prometheus_installed?).to be(true)
end
end
context 'clusters without prometheus installed' do
let(:cluster) { create(:cluster, projects: [project]) }
let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
it 'returns false' do
expect(service.prometheus_installed?).to be(false)
end
end
context 'clusters without prometheus' do
let(:cluster) { create(:cluster, projects: [project]) }
it 'returns false' do
expect(service.prometheus_installed?).to be(false)
end
end
context 'no clusters' do
it 'returns false' do
expect(service.prometheus_installed?).to be(false)
end
end
end
describe '#synchronize_service_state! before_save callback' do
context 'no clusters with prometheus are installed' do
context 'when service is inactive' do
before do
service.active = false
end
it 'activates service when manual_configuration is enabled' do
expect { service.update!(manual_configuration: true) }.to change { service.active }.from(false).to(true)
end
it 'keeps service inactive when manual_configuration is disabled' do
expect { service.update!(manual_configuration: false) }.not_to change { service.active }.from(false)
end
end
context 'when service is active' do
before do
service.active = true
end
it 'keeps the service active when manual_configuration is enabled' do
expect { service.update!(manual_configuration: true) }.not_to change { service.active }.from(true)
end
it 'inactivates the service when manual_configuration is disabled' do
expect { service.update!(manual_configuration: false) }.to change { service.active }.from(true).to(false)
end
end
end
context 'with prometheus installed in the cluster' do
before do
allow(service).to receive(:prometheus_installed?).and_return(true)
end
context 'when service is inactive' do
before do
service.active = false
end
it 'activates service when manual_configuration is enabled' do
expect { service.update!(manual_configuration: true) }.to change { service.active }.from(false).to(true)
end
it 'activates service when manual_configuration is disabled' do
expect { service.update!(manual_configuration: false) }.to change { service.active }.from(false).to(true)
end
end
context 'when service is active' do
before do
service.active = true
end
it 'keeps service active when manual_configuration is enabled' do
expect { service.update!(manual_configuration: true) }.not_to change { service.active }.from(true)
end
it 'keeps service active when manual_configuration is disabled' do
expect { service.update!(manual_configuration: false) }.not_to change { service.active }.from(true)
end
end
end
end
end
......@@ -13,6 +13,12 @@ module ReactiveCachingHelpers
write_reactive_cache(subject, data, *qualifiers) if data
end
def synchronous_reactive_cache(subject)
allow(service).to receive(:with_reactive_cache) do |*args, &block|
block.call(service.calculate_reactive_cache(*args))
end
end
def read_reactive_cache(subject, *qualifiers)
Rails.cache.read(reactive_cache_key(subject, *qualifiers))
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