Commit e8d3dce3 authored by Mikołaj Wawrzyniak's avatar Mikołaj Wawrzyniak Committed by Rémy Coutable

Add groups_projects association to cluster model

When installing application on group level cluster, we need to
create services for each project that belongs to this group.
That's why we need to traverse through all group connected projects.
parent acbb9393
...@@ -13,15 +13,21 @@ module Clusters ...@@ -13,15 +13,21 @@ module Clusters
include ::Clusters::Concerns::ApplicationStatus include ::Clusters::Concerns::ApplicationStatus
include ::Clusters::Concerns::ApplicationVersion include ::Clusters::Concerns::ApplicationVersion
include ::Clusters::Concerns::ApplicationData include ::Clusters::Concerns::ApplicationData
include AfterCommitQueue
default_value_for :version, VERSION default_value_for :version, VERSION
after_destroy :disable_prometheus_integration after_destroy do
run_after_commit do
disable_prometheus_integration
end
end
state_machine :status do state_machine :status do
after_transition any => [:installed] do |application| after_transition any => [:installed] do |application|
application.cluster.projects.each do |project| application.run_after_commit do
project.find_or_initialize_service('prometheus').update!(active: true) Clusters::Applications::ActivateServiceWorker
.perform_async(application.cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass
end end
end end
end end
...@@ -98,9 +104,8 @@ module Clusters ...@@ -98,9 +104,8 @@ module Clusters
private private
def disable_prometheus_integration def disable_prometheus_integration
cluster.projects.each do |project| ::Clusters::Applications::DeactivateServiceWorker
project.prometheus_service&.update!(active: false) .perform_async(cluster_id, ::PrometheusService.to_param) # rubocop:disable CodeReuse/ServiceClass
end
end end
def kube_client def kube_client
......
...@@ -34,6 +34,7 @@ module Clusters ...@@ -34,6 +34,7 @@ module Clusters
has_many :cluster_groups, class_name: 'Clusters::Group' has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :groups, through: :cluster_groups, class_name: '::Group' has_many :groups, through: :cluster_groups, class_name: '::Group'
has_many :groups_projects, through: :groups, source: :projects, class_name: '::Project'
# we force autosave to happen when we save `Cluster` model # we force autosave to happen when we save `Cluster` model
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
...@@ -177,6 +178,13 @@ module Clusters ...@@ -177,6 +178,13 @@ module Clusters
end end
end end
def all_projects
return projects if project_type?
return groups_projects if group_type?
::Project.all
end
def status_name def status_name
return cleanup_status_name if cleanup_errored? return cleanup_status_name if cleanup_errored?
return :cleanup_ongoing unless cleanup_not_started? return :cleanup_ongoing unless cleanup_not_started?
......
...@@ -404,6 +404,7 @@ class Project < ApplicationRecord ...@@ -404,6 +404,7 @@ class Project < ApplicationRecord
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) } scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') } scope :with_project_feature, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id') }
scope :with_statistics, -> { includes(:statistics) } scope :with_statistics, -> { includes(:statistics) }
scope :with_service, ->(service) { joins(service).eager_load(service) }
scope :with_shared_runners, -> { where(shared_runners_enabled: true) } scope :with_shared_runners, -> { where(shared_runners_enabled: true) }
scope :with_container_registry, -> { where(container_registry_enabled: true) } scope :with_container_registry, -> { where(container_registry_enabled: true) }
scope :inside_path, ->(path) do scope :inside_path, ->(path) do
...@@ -1256,8 +1257,9 @@ class Project < ApplicationRecord ...@@ -1256,8 +1257,9 @@ class Project < ApplicationRecord
def all_clusters def all_clusters
group_clusters = Clusters::Cluster.joins(:groups).where(cluster_groups: { group_id: ancestors_upto } ) group_clusters = Clusters::Cluster.joins(:groups).where(cluster_groups: { group_id: ancestors_upto } )
instance_clusters = Clusters::Cluster.instance_type
Clusters::Cluster.from_union([clusters, group_clusters]) Clusters::Cluster.from_union([clusters, group_clusters, instance_clusters])
end end
def items_for(entity) def items_for(entity)
......
...@@ -88,7 +88,7 @@ class PrometheusService < MonitoringService ...@@ -88,7 +88,7 @@ class PrometheusService < MonitoringService
return false if template? return false if template?
return false unless project return false unless project
project.clusters.enabled.any? { |cluster| cluster.application_prometheus_available? } project.all_clusters.enabled.any? { |cluster| cluster.application_prometheus_available? }
end end
def allow_local_api_url? def allow_local_api_url?
......
...@@ -51,6 +51,8 @@ ...@@ -51,6 +51,8 @@
- gcp_cluster:clusters_cleanup_app - gcp_cluster:clusters_cleanup_app
- gcp_cluster:clusters_cleanup_project_namespace - gcp_cluster:clusters_cleanup_project_namespace
- gcp_cluster:clusters_cleanup_service_account - gcp_cluster:clusters_cleanup_service_account
- gcp_cluster:clusters_applications_activate_service
- gcp_cluster:clusters_applications_deactivate_service
- github_import_advance_stage - github_import_advance_stage
- github_importer:github_import_import_diff_note - github_importer:github_import_import_diff_note
......
# frozen_string_literal: true
module Clusters
module Applications
class ActivateServiceWorker
include ApplicationWorker
include ClusterQueue
def perform(cluster_id, service_name)
cluster = Clusters::Cluster.find_by_id(cluster_id)
return unless cluster
cluster.all_projects.find_each do |project|
project.find_or_initialize_service(service_name).update!(active: true)
end
end
end
end
end
# frozen_string_literal: true
module Clusters
module Applications
class DeactivateServiceWorker
include ApplicationWorker
include ClusterQueue
def perform(cluster_id, service_name)
cluster = Clusters::Cluster.find_by_id(cluster_id)
raise cluster_missing_error(service_name) unless cluster
service = "#{service_name}_service".to_sym
cluster.all_projects.with_service(service).find_each do |project|
project.public_send(service).update!(active: false) # rubocop:disable GitlabSecurity/PublicSend
end
end
def cluster_missing_error(service)
ActiveRecord::RecordNotFound.new("Can't deactivate #{service} services, host cluster not found! Some inconsistent records may be left in database.")
end
end
end
end
---
title: Activate projects Prometheus service integration when Prometheus managed application is installed on shared cluster
merge_request:
author:
type: fixed
...@@ -12,35 +12,29 @@ describe Clusters::Applications::Prometheus do ...@@ -12,35 +12,29 @@ describe Clusters::Applications::Prometheus do
include_examples 'cluster application initial status specs' include_examples 'cluster application initial status specs'
describe 'after_destroy' do describe 'after_destroy' do
let(:project) { create(:project) } context 'cluster type is project' do
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) } let(:cluster) { create(:cluster, :with_installed_helm) }
let!(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } let(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
let!(:prometheus_service) { project.create_prometheus_service(active: true) }
it 'deactivates prometheus_service after destroy' do it 'deactivates prometheus_service after destroy' do
expect do expect(Clusters::Applications::DeactivateServiceWorker)
application.destroy! .to receive(:perform_async).with(cluster.id, 'prometheus')
prometheus_service.reload application.destroy!
end.to change(prometheus_service, :active).from(true).to(false) end
end end
end end
describe 'transition to installed' do describe 'transition to installed' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) } let(:cluster) { create(:cluster, :with_installed_helm) }
let(:prometheus_service) { double('prometheus_service') } let(:application) { create(:clusters_applications_prometheus, :installing, cluster: cluster) }
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 it 'schedules post installation job' do
expect(prometheus_service).to receive(:update!).with(active: true) expect(Clusters::Applications::ActivateServiceWorker)
.to receive(:perform_async).with(cluster.id, 'prometheus')
subject.make_installed application.make_installed
end end
end end
......
...@@ -16,6 +16,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -16,6 +16,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
it { is_expected.to have_many(:projects) } it { is_expected.to have_many(:projects) }
it { is_expected.to have_many(:cluster_groups) } it { is_expected.to have_many(:cluster_groups) }
it { is_expected.to have_many(:groups) } it { is_expected.to have_many(:groups) }
it { is_expected.to have_many(:groups_projects) }
it { is_expected.to have_one(:provider_gcp) } it { is_expected.to have_one(:provider_gcp) }
it { is_expected.to have_one(:provider_aws) } it { is_expected.to have_one(:provider_aws) }
it { is_expected.to have_one(:platform_kubernetes) } it { is_expected.to have_one(:platform_kubernetes) }
...@@ -616,6 +617,36 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do ...@@ -616,6 +617,36 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end end
end end
describe '#all_projects' do
context 'cluster_type is project_type' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
it 'returns projects' do
expect(cluster.all_projects).to match_array [project]
end
end
context 'cluster_type is group_type' do
let(:group) { create(:group) }
let!(:project) { create(:project, group: group) }
let(:cluster) { create(:cluster_for_group, :with_installed_helm, groups: [group]) }
it 'returns group projects' do
expect(cluster.all_projects.ids).to match_array [project.id]
end
end
context 'cluster_type is instance_type' do
let!(:project) { create(:project) }
let(:cluster) { create(:cluster, :instance) }
it "returns all instance's projects" do
expect(cluster.all_projects.ids).to match_array [project.id]
end
end
end
describe '#kube_ingress_domain' do describe '#kube_ingress_domain' do
let(:cluster) { create(:cluster, :provided_by_gcp) } let(:cluster) { create(:cluster, :provided_by_gcp) }
......
...@@ -156,11 +156,34 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do ...@@ -156,11 +156,34 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
describe '#prometheus_available?' do describe '#prometheus_available?' do
context 'clusters with installed prometheus' do context 'clusters with installed prometheus' do
let!(:cluster) { create(:cluster, projects: [project]) } before do
let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } create(:clusters_applications_prometheus, :installed, cluster: cluster)
end
it 'returns true' do context 'cluster belongs to project' do
expect(service.prometheus_available?).to be(true) let(:cluster) { create(:cluster, projects: [project]) }
it 'returns true' do
expect(service.prometheus_available?).to be(true)
end
end
context 'cluster belongs to projects group' do
set(:group) { create(:group) }
let(:project) { create(:prometheus_project, group: group) }
let(:cluster) { create(:cluster_for_group, :with_installed_helm, groups: [group]) }
it 'returns true' do
expect(service.prometheus_available?).to be(true)
end
end
context 'cluster belongs to gitlab instance' do
let(:cluster) { create(:cluster, :instance) }
it 'returns true' do
expect(service.prometheus_available?).to be(true)
end
end end
end end
......
...@@ -1376,6 +1376,16 @@ describe Project do ...@@ -1376,6 +1376,16 @@ describe Project do
end end
end end
describe '.with_service' do
before do
create_list(:prometheus_project, 2)
end
it 'avoid n + 1' do
expect { described_class.with_service(:prometheus_service).map(&:prometheus_service) }.not_to exceed_query_limit(1)
end
end
context 'repository storage by default' do context 'repository storage by default' do
let(:project) { build(:project) } let(:project) { build(:project) }
...@@ -5099,6 +5109,17 @@ describe Project do ...@@ -5099,6 +5109,17 @@ describe Project do
expect(subject).to contain_exactly(cluster, group_cluster) expect(subject).to contain_exactly(cluster, group_cluster)
end end
end end
context 'project is hosted on instance with integrated cluster' do
let(:group_cluster) { create(:cluster, :group) }
let(:instance_cluster) { create(:cluster, :instance) }
let(:group) { group_cluster.group }
let(:project) { create(:project, group: group) }
it 'returns all available clusters for this project' do
expect(subject).to contain_exactly(cluster, group_cluster, instance_cluster)
end
end
end end
describe '#object_pool_params' do describe '#object_pool_params' do
......
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Applications::ActivateServiceWorker, '#perform' do
context 'cluster exists' do
describe 'prometheus service' do
let(:service_name) { 'prometheus' }
before do
create(:clusters_applications_prometheus, :installed, cluster: cluster)
end
context 'cluster type: group' do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:cluster) { create(:cluster_for_group, :with_installed_helm, groups: [group]) }
it 'ensures Prometheus service is activated' do
expect { described_class.new.perform(cluster.id, service_name) }
.to change { project.reload.prometheus_service&.active }.from(nil).to(true)
end
end
context 'cluster type: project' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
it 'ensures Prometheus service is activated' do
expect { described_class.new.perform(cluster.id, service_name) }
.to change { project.reload.prometheus_service&.active }.from(nil).to(true)
end
end
context 'cluster type: instance' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :instance) }
it 'ensures Prometheus service is activated' do
expect { described_class.new.perform(cluster.id, service_name) }
.to change { project.reload.prometheus_service&.active }.from(nil).to(true)
end
end
end
end
context 'cluster does not exist' do
it 'does not raise Record Not Found error' do
expect { described_class.new.perform(0, 'ignored in this context') }.not_to raise_error(ActiveRecord::RecordNotFound)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Applications::DeactivateServiceWorker, '#perform' do
context 'cluster exists' do
describe 'prometheus service' do
let(:service_name) { 'prometheus' }
let!(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
context 'prometheus service exists' do
let!(:prometheus_service) { create(:prometheus_service, project: project, manual_configuration: false, active: true) }
before do
application.delete # prometheus service before save synchronises active stated with application existance.
end
context 'cluster type: group' do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:cluster) { create(:cluster_for_group, :with_installed_helm, groups: [group]) }
it 'ensures Prometheus service is deactivated' do
expect { described_class.new.perform(cluster.id, service_name) }
.to change { prometheus_service.reload.active }.from(true).to(false)
end
end
context 'cluster type: project' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
it 'ensures Prometheus service is deactivated' do
expect { described_class.new.perform(cluster.id, service_name) }
.to change { prometheus_service.reload.active }.from(true).to(false)
end
end
context 'cluster type: instance' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, :instance) }
it 'ensures Prometheus service is deactivated' do
expect { described_class.new.perform(cluster.id, service_name) }
.to change { prometheus_service.reload.active }.from(true).to(false)
end
end
end
context 'prometheus service does not exist' do
context 'cluster type: project' do
let(:project) { create(:project) }
let(:cluster) { create(:cluster, :with_installed_helm, projects: [project]) }
it 'does not raise errors' do
expect { described_class.new.perform(cluster.id, service_name) }.not_to raise_error
end
end
end
end
end
context 'cluster does not exist' do
it 'raises Record Not Found error' do
expect { described_class.new.perform(0, 'ignored in this context') }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment