summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/services/clusters/applications/prometheus_health_check_service.rb85
-rw-r--r--changelogs/unreleased/197955-projects-api-improve-api-response-time-for-archived-true.yml5
-rw-r--r--changelogs/unreleased/abrowne-prometheus-healthy-column.yml5
-rw-r--r--db/migrate/20200303181648_add_healthy_to_clusters_applications_prometheus.rb15
-rw-r--r--db/migrate/20200323134519_add_api_indexes_for_archived_projects.rb27
-rw-r--r--db/structure.sql9
-rw-r--r--doc/user/admin_area/broadcast_messages.md36
-rw-r--r--doc/user/admin_area/img/broadcast_messages.pngbin21458 -> 0 bytes
-rw-r--r--doc/user/admin_area/img/broadcast_messages_banner_v12_10.pngbin0 -> 34040 bytes
-rw-r--r--doc/user/admin_area/img/broadcast_messages_notification_v12_10.pngbin0 -> 71249 bytes
-rw-r--r--doc/user/gitlab_com/index.md3
-rw-r--r--lib/gitlab/prometheus_client.rb28
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb20
-rw-r--r--spec/models/merge_request_spec.rb8
-rw-r--r--spec/services/clusters/applications/prometheus_health_check_service_spec.rb113
16 files changed, 348 insertions, 8 deletions
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 7934b0f8f59..2a6f498269c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -189,7 +189,7 @@ class MergeRequest < ApplicationRecord
end
# rubocop: disable CodeReuse/ServiceClass
- after_transition unchecked: :cannot_be_merged do |merge_request, transition|
+ after_transition [:unchecked, :checking] => :cannot_be_merged do |merge_request, transition|
if merge_request.notify_conflict?
NotificationService.new.merge_request_unmergeable(merge_request)
TodoService.new.merge_request_became_unmergeable(merge_request)
diff --git a/app/services/clusters/applications/prometheus_health_check_service.rb b/app/services/clusters/applications/prometheus_health_check_service.rb
new file mode 100644
index 00000000000..e609d9f0b7b
--- /dev/null
+++ b/app/services/clusters/applications/prometheus_health_check_service.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class PrometheusHealthCheckService
+ include Gitlab::Utils::StrongMemoize
+ include Gitlab::Routing
+
+ def initialize(cluster)
+ @cluster = cluster
+ @logger = Gitlab::AppJsonLogger.build
+ end
+
+ def execute
+ raise 'Invalid cluster type. Only project types are allowed.' unless @cluster.project_type?
+
+ return unless prometheus_application.installed?
+
+ project = @cluster.clusterable
+
+ @logger.info(
+ message: 'Prometheus health check',
+ cluster_id: @cluster.id,
+ newly_unhealthy: became_unhealthy?,
+ currently_healthy: currently_healthy?,
+ was_healthy: was_healthy?
+ )
+
+ send_notification(project) if became_unhealthy?
+
+ prometheus_application.update_columns(healthy: currently_healthy?) if health_changed?
+ end
+
+ private
+
+ def prometheus_application
+ strong_memoize(:prometheus_application) do
+ @cluster.application_prometheus
+ end
+ end
+
+ def currently_healthy?
+ strong_memoize(:currently_healthy) do
+ prometheus_application.prometheus_client.healthy?
+ end
+ end
+
+ def became_unhealthy?
+ strong_memoize(:became_unhealthy) do
+ (was_healthy? || was_healthy?.nil?) && !currently_healthy?
+ end
+ end
+
+ def was_healthy?
+ strong_memoize(:was_healthy) do
+ prometheus_application.healthy
+ end
+ end
+
+ def health_changed?
+ was_healthy? != currently_healthy?
+ end
+
+ def send_notification(project)
+ notification_payload = build_notification_payload(project)
+ token = project.alerts_service.data.token
+ Projects::Alerting::NotifyService.new(project, nil, notification_payload).execute(token)
+ @logger.info(message: 'Successfully notified of Prometheus newly unhealthy', cluster_id: @cluster.id, project_id: project.id)
+ end
+
+ def build_notification_payload(project)
+ cluster_path = namespace_project_cluster_path(
+ project_id: project.path,
+ namespace_id: project.namespace.path,
+ id: @cluster.id
+ )
+
+ {
+ title: "Prometheus is Unhealthy. Cluster Name: #{@cluster.name}",
+ description: "Prometheus is unhealthy for the cluster: [#{@cluster.name}](#{cluster_path}) attached to project #{project.name}."
+ }
+ end
+ end
+ end
+end
diff --git a/changelogs/unreleased/197955-projects-api-improve-api-response-time-for-archived-true.yml b/changelogs/unreleased/197955-projects-api-improve-api-response-time-for-archived-true.yml
new file mode 100644
index 00000000000..65470d467ec
--- /dev/null
+++ b/changelogs/unreleased/197955-projects-api-improve-api-response-time-for-archived-true.yml
@@ -0,0 +1,5 @@
+---
+title: Improve API response for archived project searchs
+merge_request: 27717
+author:
+type: performance
diff --git a/changelogs/unreleased/abrowne-prometheus-healthy-column.yml b/changelogs/unreleased/abrowne-prometheus-healthy-column.yml
new file mode 100644
index 00000000000..0e6b7bb9393
--- /dev/null
+++ b/changelogs/unreleased/abrowne-prometheus-healthy-column.yml
@@ -0,0 +1,5 @@
+---
+title: Add healthy column to clusters_applications_prometheus table
+merge_request: 26168
+author:
+type: added
diff --git a/db/migrate/20200303181648_add_healthy_to_clusters_applications_prometheus.rb b/db/migrate/20200303181648_add_healthy_to_clusters_applications_prometheus.rb
new file mode 100644
index 00000000000..402cdbbdc14
--- /dev/null
+++ b/db/migrate/20200303181648_add_healthy_to_clusters_applications_prometheus.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddHealthyToClustersApplicationsPrometheus < ActiveRecord::Migration[6.0]
+ DOWNTIME = false
+
+ def up
+ # Default is null to indicate that a health check has not run for a project
+ # For now, health checks will only run on monitor demo projects
+ add_column :clusters_applications_prometheus, :healthy, :boolean
+ end
+
+ def down
+ remove_column :clusters_applications_prometheus, :healthy
+ end
+end
diff --git a/db/migrate/20200323134519_add_api_indexes_for_archived_projects.rb b/db/migrate/20200323134519_add_api_indexes_for_archived_projects.rb
new file mode 100644
index 00000000000..cacf427dbe4
--- /dev/null
+++ b/db/migrate/20200323134519_add_api_indexes_for_archived_projects.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+class AddApiIndexesForArchivedProjects < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ PUBLIC_AND_ARCHIVED_INDEX_NAME = "index_projects_api_created_at_id_for_archived_vis20"
+ ARCHIVED_INDEX_NAME = "index_projects_api_created_at_id_for_archived"
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :projects, [:created_at, :id],
+ where: "archived = true AND visibility_level = 20 AND pending_delete = false",
+ name: PUBLIC_AND_ARCHIVED_INDEX_NAME
+
+ add_concurrent_index :projects, [:created_at, :id], where: "archived = true AND pending_delete = false",
+ name: ARCHIVED_INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :projects, ARCHIVED_INDEX_NAME
+
+ remove_concurrent_index_by_name :projects, PUBLIC_AND_ARCHIVED_INDEX_NAME
+ end
+end
diff --git a/db/structure.sql b/db/structure.sql
index 6301ec6df80..adaa0a594e6 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1748,7 +1748,8 @@ CREATE TABLE public.clusters_applications_prometheus (
updated_at timestamp with time zone NOT NULL,
last_update_started_at timestamp with time zone,
encrypted_alert_manager_token character varying,
- encrypted_alert_manager_token_iv character varying
+ encrypted_alert_manager_token_iv character varying,
+ healthy boolean
);
CREATE SEQUENCE public.clusters_applications_prometheus_id_seq
@@ -9654,6 +9655,10 @@ CREATE UNIQUE INDEX index_project_tracing_settings_on_project_id ON public.proje
CREATE INDEX index_projects_api_created_at_id_desc ON public.projects USING btree (created_at, id DESC);
+CREATE INDEX index_projects_api_created_at_id_for_archived ON public.projects USING btree (created_at, id) WHERE ((archived = true) AND (pending_delete = false));
+
+CREATE INDEX index_projects_api_created_at_id_for_archived_vis20 ON public.projects USING btree (created_at, id) WHERE ((archived = true) AND (visibility_level = 20) AND (pending_delete = false));
+
CREATE INDEX index_projects_api_last_activity_at_id_desc ON public.projects USING btree (last_activity_at, id DESC);
CREATE INDEX index_projects_api_name_id_desc ON public.projects USING btree (name, id DESC);
@@ -12731,6 +12736,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200302152516
20200303055348
20200303074328
+20200303181648
20200304085423
20200304090155
20200304121828
@@ -12793,6 +12799,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200320123839
20200323075043
20200323122201
+20200323134519
20200324115359
\.
diff --git a/doc/user/admin_area/broadcast_messages.md b/doc/user/admin_area/broadcast_messages.md
index 416bd3bfd00..ed03e8a57f3 100644
--- a/doc/user/admin_area/broadcast_messages.md
+++ b/doc/user/admin_area/broadcast_messages.md
@@ -4,14 +4,46 @@ type: reference, howto
# Broadcast Messages **(CORE ONLY)**
-GitLab can display messages to all users of a GitLab instance in a banner that appears in the UI.
+GitLab can display broadcast messages to all users of a GitLab instance. There are two types of broadcast messages:
-![Broadcast Message](img/broadcast_messages.png)
+- banners
+- notifications
+
+You can style a message's content using the `a` and `br` HTML tags. The `br` tag inserts a line break. The `a` HTML tag accepts `class` and `style` attributes with the following CSS properties:
+
+- `color`
+- `border`
+- `background`
+- `padding`
+- `margin`
+- `text-decoration`
+
+## Banners
+
+Banners are shown on the top of a page.
+
+![Broadcast Message Banner](img/broadcast_messages_banner_v12_10.png)
+
+## Notifications
+
+Notifications are shown on the bottom right of a page and can contain placeholders. A placeholder is replaced with an attribute of the active user. Placeholders must be surrounded by curly braces, for example `{{name}}`.
+The available placeholders are:
+
+- `{{email}}`
+- `{{name}}`
+- `{{user_id}}`
+- `{{username}}`
+- `{{instance_id}}`
+
+If the user is not signed in, user related values will be empty.
+
+![Broadcast Message Notification](img/broadcast_messages_notification_v12_10.png)
Broadcast messages can be managed using the [broadcast messages API](../../api/broadcast_messages.md).
NOTE: **Note:**
If more than one banner message is active at one time, they are displayed in a stack in order of creation.
+If more than one notification message is active at one time, only the newest is shown.
## Adding a broadcast message
diff --git a/doc/user/admin_area/img/broadcast_messages.png b/doc/user/admin_area/img/broadcast_messages.png
deleted file mode 100644
index f0ae92f8c17..00000000000
--- a/doc/user/admin_area/img/broadcast_messages.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/admin_area/img/broadcast_messages_banner_v12_10.png b/doc/user/admin_area/img/broadcast_messages_banner_v12_10.png
new file mode 100644
index 00000000000..f3c468d158e
--- /dev/null
+++ b/doc/user/admin_area/img/broadcast_messages_banner_v12_10.png
Binary files differ
diff --git a/doc/user/admin_area/img/broadcast_messages_notification_v12_10.png b/doc/user/admin_area/img/broadcast_messages_notification_v12_10.png
new file mode 100644
index 00000000000..98ea48ccd3c
--- /dev/null
+++ b/doc/user/admin_area/img/broadcast_messages_notification_v12_10.png
Binary files differ
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 58d834bb9d2..a99444c013f 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -87,6 +87,9 @@ or over the size limit, you can [reduce your repository size with Git](../projec
| ----------- | ----------------- | ------------- |
| Repository size including LFS | 10G | Unlimited |
+NOTE: **Note:**
+A single `git push` is limited to 5GB. LFS is not affected by this limit.
+
## IP range
GitLab.com is using the IP range `34.74.90.64/28` for traffic from its Web/API
diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb
index 12de20a2c37..71a0d528bd7 100644
--- a/lib/gitlab/prometheus_client.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -6,6 +6,7 @@ module Gitlab
include Gitlab::Utils::StrongMemoize
Error = Class.new(StandardError)
QueryError = Class.new(Gitlab::PrometheusClient::Error)
+ HEALTHY_RESPONSE = "Prometheus is Healthy.\n"
# Target number of data points for `query_range`.
# Please don't exceed the limit of 11000 data points
@@ -32,13 +33,20 @@ module Gitlab
json_api_get('query', query: '1')
end
+ def healthy?
+ response_body = handle_management_api_response(get(health_url, {}))
+
+ # From Prometheus docs: This endpoint always returns 200 and should be used to check Prometheus health.
+ response_body == HEALTHY_RESPONSE
+ end
+
def proxy(type, args)
path = api_path(type)
get(path, args)
rescue Gitlab::HTTP::ResponseError => ex
raise PrometheusClient::Error, "Network connection error" unless ex.response && ex.response.try(:code)
- handle_response(ex.response)
+ handle_querying_api_response(ex.response)
end
def query(query, time: Time.now)
@@ -79,6 +87,10 @@ module Gitlab
[QUERY_RANGE_MIN_STEP, step].max
end
+ def health_url
+ [api_url, '-/healthy'].join('/')
+ end
+
private
def api_path(type)
@@ -88,11 +100,11 @@ module Gitlab
def json_api_get(type, args = {})
path = api_path(type)
response = get(path, args)
- handle_response(response)
+ handle_querying_api_response(response)
rescue Gitlab::HTTP::ResponseError => ex
raise PrometheusClient::Error, "Network connection error" unless ex.response && ex.response.try(:code)
- handle_response(ex.response)
+ handle_querying_api_response(ex.response)
end
def gitlab_http_key(key)
@@ -119,7 +131,15 @@ module Gitlab
raise PrometheusClient::Error, 'Connection refused'
end
- def handle_response(response)
+ def handle_management_api_response(response)
+ if response.code == 200
+ response.body
+ else
+ raise PrometheusClient::Error, "#{response.code} - #{response.body}"
+ end
+ end
+
+ def handle_querying_api_response(response)
response_code = response.try(:code)
response_body = response.try(:body)
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 5eb133a5bf4..e869a384b29 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -16,6 +16,26 @@ describe Gitlab::PrometheusClient do
end
end
+ describe '#healthy?' do
+ it 'returns true when status code is 200 and healthy response body' do
+ stub_request(:get, subject.health_url).to_return(status: 200, body: described_class::HEALTHY_RESPONSE)
+
+ expect(subject.healthy?).to eq(true)
+ end
+
+ it 'returns false when status code is 200 and unhealthy response body' do
+ stub_request(:get, subject.health_url).to_return(status: 200, body: '')
+
+ expect(subject.healthy?).to eq(false)
+ end
+
+ it 'raises error when status code not 200' do
+ stub_request(:get, subject.health_url).to_return(status: 500, body: '')
+
+ expect { subject.healthy? }.to raise_error(Gitlab::PrometheusClient::Error)
+ end
+ end
+
# This shared examples expect:
# - query_url: A query URL
# - execute_query: A query call
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 137795dcbc3..f37e39a9ee9 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -3223,6 +3223,14 @@ describe MergeRequest do
subject.mark_as_unmergeable
end
+ it 'notifies conflict, with enabled async mergability check' do
+ expect(notification_service).to receive(:merge_request_unmergeable).with(subject).once
+ expect(todo_service).to receive(:merge_request_became_unmergeable).with(subject).once
+
+ subject.mark_as_checking
+ subject.mark_as_unmergeable
+ end
+
it 'does not notify whenever merge request is newly unmergeable due to other reasons' do
allow(subject.project.repository).to receive(:can_be_merged?).and_return(true)
diff --git a/spec/services/clusters/applications/prometheus_health_check_service_spec.rb b/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
new file mode 100644
index 00000000000..5c4127e4938
--- /dev/null
+++ b/spec/services/clusters/applications/prometheus_health_check_service_spec.rb
@@ -0,0 +1,113 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Applications::PrometheusHealthCheckService, '#execute' do
+ let(:service) { described_class.new(cluster) }
+
+ subject { service.execute }
+
+ RSpec.shared_examples 'no alert' do
+ it 'does not send alert' do
+ expect(Projects::Alerting::NotifyService).not_to receive(:new)
+
+ subject
+ end
+ end
+
+ RSpec.shared_examples 'sends alert' do
+ it 'sends an alert' do
+ expect_next_instance_of(Projects::Alerting::NotifyService) do |notify_service|
+ expect(notify_service).to receive(:execute).with(alerts_service.token)
+ end
+
+ subject
+ end
+ end
+
+ RSpec.shared_examples 'correct health stored' do
+ it 'stores the correct health of prometheus app' do
+ subject
+
+ expect(prometheus.healthy).to eq(client_healthy)
+ end
+ end
+
+ context 'when cluster is not project_type' do
+ let(:cluster) { create(:cluster, :instance) }
+
+ it { expect { subject }.to raise_error(RuntimeError, 'Invalid cluster type. Only project types are allowed.') }
+ end
+
+ context 'when cluster is project_type' do
+ let_it_be(:alerts_service) { create(:alerts_service) }
+ let_it_be(:project) { create(:project, alerts_service: alerts_service) }
+ let(:applications_prometheus_healthy) { true }
+ let(:prometheus) { create(:clusters_applications_prometheus, status: prometheus_status_value, healthy: applications_prometheus_healthy) }
+ let(:cluster) { create(:cluster, :project, application_prometheus: prometheus, projects: [project]) }
+
+ context 'when prometheus not installed' do
+ let(:prometheus_status_value) { Clusters::Applications::Prometheus.state_machine.states[:installing].value }
+
+ it { expect(subject).to eq(nil) }
+ include_examples 'no alert'
+ end
+
+ context 'when prometheus installed' do
+ let(:prometheus_status_value) { Clusters::Applications::Prometheus.state_machine.states[:installed].value }
+
+ before do
+ client = instance_double('PrometheusClient', healthy?: client_healthy)
+ expect(prometheus).to receive(:prometheus_client).and_return(client)
+ end
+
+ context 'when newly unhealthy' do
+ let(:applications_prometheus_healthy) { true }
+ let(:client_healthy) { false }
+
+ include_examples 'sends alert'
+ include_examples 'correct health stored'
+ end
+
+ context 'when newly healthy' do
+ let(:applications_prometheus_healthy) { false }
+ let(:client_healthy) { true }
+
+ include_examples 'no alert'
+ include_examples 'correct health stored'
+ end
+
+ context 'when continuously unhealthy' do
+ let(:applications_prometheus_healthy) { false }
+ let(:client_healthy) { false }
+
+ include_examples 'no alert'
+ include_examples 'correct health stored'
+ end
+
+ context 'when continuously healthy' do
+ let(:applications_prometheus_healthy) { true }
+ let(:client_healthy) { true }
+
+ include_examples 'no alert'
+ include_examples 'correct health stored'
+ end
+
+ context 'when first health check and healthy' do
+ let(:applications_prometheus_healthy) { nil }
+ let(:client_healthy) { true }
+
+ include_examples 'no alert'
+ include_examples 'correct health stored'
+ end
+
+ context 'when first health check and not healthy' do
+ let(:applications_prometheus_healthy) { nil }
+ let(:client_healthy) { false }
+
+ include_examples 'sends alert'
+ include_examples 'correct health stored'
+ end
+ end
+ end
+end