summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThong Kuah <tkuah@gitlab.com>2018-10-29 15:28:36 +1300
committerThong Kuah <tkuah@gitlab.com>2018-12-05 10:16:44 +1300
commit5bb2814ae6eb45463bb1fa4c5ed5fdd376afa2ca (patch)
treeb8442b8ecaabd9db1e32dfe8fdcebd8ea93c8025
parentd3866fb48cdf640cd16d48b9825dba2c261a78f3 (diff)
downloadgitlab-ce-5bb2814ae6eb45463bb1fa4c5ed5fdd376afa2ca.tar.gz
Deploy to clusters for a project's groups
Look for matching clusters starting from the closest ancestor, then go up the ancestor tree. Then use Ruby to get clusters for each group in order. Not that efficient, considering we will doing up to `NUMBER_OF_ANCESTORS_ALLOWED` number of queries, but it's a finite number Explicitly order query by depth This allows us to control ordering explicitly and also to reverse the order which is useful to allow us to be consistent with Clusters::Cluster.on_environment (EE) which does reverse ordering. Puts querying group clusters behind Feature Flag. Just in case we have issues with performance, we can easily disable this
-rw-r--r--app/models/clusters/cluster.rb10
-rw-r--r--app/models/concerns/deployment_platform.rb13
-rw-r--r--changelogs/unreleased/34758-deployment-cluster.yml5
-rw-r--r--lib/gitlab/group_hierarchy.rb23
-rw-r--r--spec/lib/gitlab/group_hierarchy_spec.rb10
-rw-r--r--spec/models/clusters/cluster_spec.rb47
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb77
7 files changed, 180 insertions, 5 deletions
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 13906c903b9..a3d26d62a22 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -86,6 +86,16 @@ module Clusters
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
+ # Returns an ordered list of group clusters order from clusters of closest
+ # group up to furthest ancestor group
+ def self.ordered_group_clusters_for_project(project_id)
+ project_groups = ::Group.joins(:projects).where(projects: { id: project_id })
+ hierarchy_groups = Gitlab::GroupHierarchy.new(project_groups)
+ .base_and_ancestors(depth: :desc)
+
+ hierarchy_groups.flat_map(&:clusters)
+ end
+
def status_name
if provider
provider.status_name
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index e57a3383544..d785e0d505f 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -13,6 +13,7 @@ module DeploymentPlatform
def find_deployment_platform(environment)
find_cluster_platform_kubernetes(environment: environment) ||
+ find_group_cluster_platform_kubernetes_with_feature_guard(environment: environment) ||
find_kubernetes_service_integration ||
build_cluster_and_deployment_platform
end
@@ -23,6 +24,18 @@ module DeploymentPlatform
.last&.platform_kubernetes
end
+ def find_group_cluster_platform_kubernetes_with_feature_guard(environment: nil)
+ return unless Feature.enabled?(:deploy_group_clusters, default_enabled: true)
+
+ find_group_cluster_platform_kubernetes(environment: environment)
+ end
+
+ # EE would override this and utilize environment argument
+ def find_group_cluster_platform_kubernetes(environment: nil)
+ Clusters::Cluster.enabled.default_environment.ordered_group_clusters_for_project(id)
+ .last&.platform_kubernetes
+ end
+
def find_kubernetes_service_integration
services.deployment.reorder(nil).find_by(active: true)
end
diff --git a/changelogs/unreleased/34758-deployment-cluster.yml b/changelogs/unreleased/34758-deployment-cluster.yml
new file mode 100644
index 00000000000..06374098343
--- /dev/null
+++ b/changelogs/unreleased/34758-deployment-cluster.yml
@@ -0,0 +1,5 @@
+---
+title: Use group clusters when deploying (DeploymentPlatform)
+merge_request: 22308
+author:
+type: changed
diff --git a/lib/gitlab/group_hierarchy.rb b/lib/gitlab/group_hierarchy.rb
index c940ea7305e..2502887065f 100644
--- a/lib/gitlab/group_hierarchy.rb
+++ b/lib/gitlab/group_hierarchy.rb
@@ -45,11 +45,21 @@ module Gitlab
# Passing an `upto` will stop the recursion once the specified parent_id is
# reached. So all ancestors *lower* than the specified acestor will be
# included.
- def base_and_ancestors(upto: nil)
+ #
+ # Passing an `depth` with either `:asc` or `:desc` will cause the recursive
+ # query to use a depth column to order by depth (`:asc` returns most nested
+ # group to root; `desc` returns opposite order). We define 1 as the depth
+ # for the base and increment as we go up each parent.
+ # rubocop: disable CodeReuse/ActiveRecord
+ def base_and_ancestors(upto: nil, depth: nil)
return ancestors_base unless Group.supports_nested_groups?
- read_only(base_and_ancestors_cte(upto).apply_to(model.all))
+ recursive_query = base_and_ancestors_cte(upto, depth).apply_to(model.all)
+ recursive_query = recursive_query.order(depth: depth) if depth
+
+ read_only(recursive_query)
end
+ # rubocop: enable CodeReuse/ActiveRecord
# Returns a relation that includes the descendants_base set of groups
# and all their descendants (recursively).
@@ -107,16 +117,21 @@ module Gitlab
private
# rubocop: disable CodeReuse/ActiveRecord
- def base_and_ancestors_cte(stop_id = nil)
+ def base_and_ancestors_cte(stop_id = nil, depth = nil)
cte = SQL::RecursiveCTE.new(:base_and_ancestors)
- cte << ancestors_base.except(:order)
+ base_query = ancestors_base.except(:order)
+ base_query = base_query.select('1 AS depth', groups_table[Arel.star]) if depth
+
+ cte << base_query
# Recursively get all the ancestors of the base set.
parent_query = model
.from([groups_table, cte.table])
.where(groups_table[:id].eq(cte.table[:parent_id]))
.except(:order)
+
+ parent_query = parent_query.select(cte.table[:depth] + 1, groups_table[Arel.star]) if depth
parent_query = parent_query.where(cte.table[:parent_id].not_eq(stop_id)) if stop_id
cte << parent_query
diff --git a/spec/lib/gitlab/group_hierarchy_spec.rb b/spec/lib/gitlab/group_hierarchy_spec.rb
index 30686634af4..edc03ff2e59 100644
--- a/spec/lib/gitlab/group_hierarchy_spec.rb
+++ b/spec/lib/gitlab/group_hierarchy_spec.rb
@@ -34,6 +34,16 @@ describe Gitlab::GroupHierarchy, :postgresql do
expect { relation.update_all(share_with_group_lock: false) }
.to raise_error(ActiveRecord::ReadOnlyRecord)
end
+
+ context 'with depth option' do
+ let(:relation) do
+ described_class.new(Group.where(id: child2.id)).base_and_ancestors(depth: :asc)
+ end
+
+ it 'orders by depth' do
+ expect(relation.map(&:depth)).to eq([1, 2, 3])
+ end
+ end
end
describe '#base_and_descendants' do
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 7dcf97276b2..c24c796f0ad 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -233,6 +233,53 @@ describe Clusters::Cluster do
end
end
+ describe '.ordered_group_clusters_for_project' do
+ let(:group_cluster) { create(:cluster, :provided_by_gcp, :group) }
+ let(:group) { group_cluster.group }
+
+ subject { described_class.ordered_group_clusters_for_project(project.id) }
+
+ context 'when project does not belong to this group' do
+ let(:project) { create(:project, group: create(:group)) }
+
+ it 'returns nothing' do
+ expect(subject).to be_empty
+ end
+ end
+
+ context 'when group has a configured kubernetes cluster' do
+ let(:project) { create(:project, group: group) }
+
+ it 'returns the group cluster' do
+ expect(subject).to eq([group_cluster])
+ end
+ end
+
+ context 'when sub-group has configured kubernetes cluster', :postgresql do
+ let(:sub_group_cluster) { create(:cluster, :provided_by_gcp, :group) }
+ let(:sub_group) { sub_group_cluster.group }
+ let(:project) { create(:project, group: sub_group) }
+
+ before do
+ sub_group.update!(parent: group)
+ end
+
+ it 'returns clusters in order, ascending the hierachy' do
+ expect(subject).to eq([group_cluster, sub_group_cluster])
+ end
+ end
+
+ context 'cluster_scope arg' do
+ let(:project) { create(:project, group: group) }
+
+ subject { described_class.none.ordered_group_clusters_for_project(project.id) }
+
+ it 'returns nothing' do
+ expect(subject).to be_empty
+ end
+ end
+ end
+
describe '#provider' do
subject { cluster.provider }
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index 7bb89fe41dc..bdd352c52b8 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -43,13 +43,88 @@ describe DeploymentPlatform do
it { is_expected.to be_nil }
end
- context 'when user configured kubernetes from CI/CD > Clusters' do
+ context 'when project has configured kubernetes from CI/CD > Clusters' do
let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
let(:platform_kubernetes) { cluster.platform_kubernetes }
it 'returns the Kubernetes platform' do
expect(subject).to eq(platform_kubernetes)
end
+
+ context 'with a group level kubernetes cluster' do
+ let(:group_cluster) { create(:cluster, :provided_by_gcp, :group) }
+
+ before do
+ project.update!(group: group_cluster.group)
+ end
+
+ it 'returns the Kubernetes platform from the project cluster' do
+ expect(subject).to eq(platform_kubernetes)
+ end
+ end
+ end
+
+ context 'when group has configured kubernetes cluster' do
+ let!(:group_cluster) { create(:cluster, :provided_by_gcp, :group) }
+ let(:group) { group_cluster.group }
+
+ before do
+ stub_feature_flags(deploy_group_clusters: true)
+
+ project.update!(group: group)
+ end
+
+ it 'returns the Kubernetes platform' do
+ is_expected.to eq(group_cluster.platform_kubernetes)
+ end
+
+ context 'when child group has configured kubernetes cluster', :nested_groups do
+ let!(:child_group1_cluster) { create(:cluster, :provided_by_gcp, :group) }
+ let(:child_group1) { child_group1_cluster.group }
+
+ before do
+ project.update!(group: child_group1)
+ child_group1.update!(parent: group)
+ end
+
+ it 'returns the Kubernetes platform for the child group' do
+ is_expected.to eq(child_group1_cluster.platform_kubernetes)
+ end
+
+ context 'deeply nested group' do
+ let!(:child_group2_cluster) { create(:cluster, :provided_by_gcp, :group) }
+ let(:child_group2) { child_group2_cluster.group }
+
+ before do
+ child_group2.update!(parent: child_group1)
+ project.update!(group: child_group2)
+ end
+
+ it 'returns most nested group cluster Kubernetes platform' do
+ is_expected.to eq(child_group2_cluster.platform_kubernetes)
+ end
+
+ context 'cluster in the middle of hierarchy is disabled' do
+ before do
+ child_group2_cluster.update!(enabled: false)
+ end
+
+ it 'returns closest enabled Kubenetes platform' do
+ is_expected.to eq(child_group1_cluster.platform_kubernetes)
+ end
+ end
+ end
+ end
+
+ context 'feature flag disabled' do
+ before do
+ stub_feature_flags(deploy_group_clusters: false)
+ end
+
+ it 'returns nil' do
+ is_expected.to be_nil
+ end
+ end
end
context 'when user configured kubernetes integration from project services' do