Associate clusters model to groups

Even though we currently only should have one group for a cluster, we
allow the flexibility to associate to other groups in the future.

This also matches the runner <=> groups association.

- Adds Cluster#first_group, aliased to Cluster#group. For the
conceivable future, a cluster will have at most one group.

- Prevent mixing of group and project clusters. If project type
clusters, it should only have projects assigned.  Similarly with groups.

- Default cluster_type to :project_type. As it's very small table we can
set default and null: false in one release.
This commit is contained in:
Thong Kuah 2018-10-15 09:42:29 +13:00
parent b868b02c62
commit 0e15eec86d
13 changed files with 188 additions and 3 deletions

View file

@ -20,6 +20,12 @@ module Clusters
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :groups, through: :cluster_groups, class_name: '::Group'
has_one :cluster_group, -> { order(id: :desc) }, class_name: 'Clusters::Group'
has_one :group, through: :cluster_group, class_name: '::Group'
# we force autosave to happen when we save `Cluster` model
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
@ -38,8 +44,12 @@ module Clusters
accepts_nested_attributes_for :platform_kubernetes, update_only: true
validates :name, cluster_name: true
validates :cluster_type, presence: true
validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
@ -50,6 +60,12 @@ module Clusters
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
enum cluster_type: {
instance_type: 1,
group_type: 2,
project_type: 3
}
enum platform_type: {
kubernetes: 1
}
@ -122,5 +138,17 @@ module Clusters
true
end
def no_groups
if groups.any?
errors.add(:cluster, 'cannot have groups assigned')
end
end
def no_projects
if projects.any?
errors.add(:cluster, 'cannot have projects assigned')
end
end
end
end

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Clusters
class Group < ActiveRecord::Base
self.table_name = 'cluster_groups'
belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :group, class_name: '::Group'
end
end

View file

@ -42,6 +42,7 @@ module DeploymentPlatform
{
name: 'kubernetes-template',
projects: [self],
cluster_type: :project_type,
provider_type: :user,
platform_type: :kubernetes,
platform_kubernetes_attributes: platform_kubernetes_attributes_from_service_template

View file

@ -41,6 +41,9 @@ class Group < Namespace
has_many :boards
has_many :badges, class_name: 'GroupBadge'
has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :clusters, through: :cluster_groups, class_name: 'Clusters::Cluster'
has_many :todos
accepts_nested_attributes_for :variables, allow_destroy: true

View file

@ -9,9 +9,9 @@ module Clusters
end
def execute(project:, access_token: nil)
raise ArgumentError.new(_('Instance does not support multiple Kubernetes clusters')) unless can_create_cluster?(project)
raise ArgumentError, _('Instance does not support multiple Kubernetes clusters') unless can_create_cluster?(project)
cluster_params = params.merge(user: current_user, projects: [project])
cluster_params = params.merge(user: current_user, cluster_type: :project_type, projects: [project])
cluster_params[:provider_gcp_attributes].try do |provider|
provider[:access_token] = access_token
end

View file

@ -0,0 +1,5 @@
---
title: Adds model and migrations to enable group level clusters
merge_request: 22307
author:
type: other

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
class CreateClusterGroups < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :cluster_groups do |t|
t.references :cluster, null: false, foreign_key: { on_delete: :cascade }
t.references :group, null: false, index: true
t.index [:cluster_id, :group_id], unique: true
t.foreign_key :namespaces, column: :group_id, on_delete: :cascade
end
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
class AddClusterTypeToClusters < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
PROJECT_CLUSTER_TYPE = 3
disable_ddl_transaction!
def up
add_column_with_default(:clusters, :cluster_type, :smallint, default: PROJECT_CLUSTER_TYPE)
end
def down
remove_column(:clusters, :cluster_type)
end
end

View file

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20181016152238) do
ActiveRecord::Schema.define(version: 20181017001059) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -599,6 +599,14 @@ ActiveRecord::Schema.define(version: 20181016152238) do
add_index "ci_variables", ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
create_table "cluster_groups", force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "group_id", null: false
end
add_index "cluster_groups", ["cluster_id", "group_id"], name: "index_cluster_groups_on_cluster_id_and_group_id", unique: true, using: :btree
add_index "cluster_groups", ["group_id"], name: "index_cluster_groups_on_group_id", using: :btree
create_table "cluster_platforms_kubernetes", force: :cascade do |t|
t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false
@ -654,6 +662,7 @@ ActiveRecord::Schema.define(version: 20181016152238) do
t.boolean "enabled", default: true
t.string "name", null: false
t.string "environment_scope", default: "*", null: false
t.integer "cluster_type", limit: 2, default: 3, null: false
end
add_index "clusters", ["enabled"], name: "index_clusters_on_enabled", using: :btree
@ -2372,6 +2381,8 @@ ActiveRecord::Schema.define(version: 20181016152238) do
add_foreign_key "ci_triggers", "projects", name: "fk_e3e63f966e", on_delete: :cascade
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
add_foreign_key "ci_variables", "projects", name: "fk_ada5eb64b3", on_delete: :cascade
add_foreign_key "cluster_groups", "clusters", on_delete: :cascade
add_foreign_key "cluster_groups", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "cluster_platforms_kubernetes", "clusters", on_delete: :cascade
add_foreign_key "cluster_projects", "clusters", on_delete: :cascade
add_foreign_key "cluster_projects", "projects", on_delete: :cascade

View file

@ -2,13 +2,28 @@ FactoryBot.define do
factory :cluster, class: Clusters::Cluster do
user
name 'test-cluster'
cluster_type :project_type
trait :instance do
cluster_type { Clusters::Cluster.cluster_types[:instance_type] }
end
trait :project do
cluster_type { Clusters::Cluster.cluster_types[:project_type] }
before(:create) do |cluster, evaluator|
cluster.projects << create(:project, :repository)
end
end
trait :group do
cluster_type { Clusters::Cluster.cluster_types[:group_type] }
before(:create) do |cluster, evalutor|
cluster.groups << create(:group)
end
end
trait :provided_by_user do
provider_type :user
platform_type :kubernetes

View file

@ -4,7 +4,10 @@ require 'spec_helper'
describe Clusters::Cluster do
it { is_expected.to belong_to(:user) }
it { is_expected.to have_many(:cluster_projects) }
it { is_expected.to have_many(:projects) }
it { is_expected.to have_many(:cluster_groups) }
it { is_expected.to have_many(:groups) }
it { is_expected.to have_one(:provider_gcp) }
it { is_expected.to have_one(:platform_kubernetes) }
it { is_expected.to have_one(:application_helm) }
@ -178,6 +181,53 @@ describe Clusters::Cluster do
it { expect(cluster.update(enabled: false)).to be_truthy }
end
end
describe 'cluster_type validations' do
let(:instance_cluster) { create(:cluster, :instance) }
let(:group_cluster) { create(:cluster, :group) }
let(:project_cluster) { create(:cluster, :project) }
it 'validates presence' do
cluster = build(:cluster, :project, cluster_type: nil)
expect(cluster).not_to be_valid
expect(cluster.errors.full_messages).to include("Cluster type can't be blank")
end
context 'project_type cluster' do
it 'does not allow setting group' do
project_cluster.groups << build(:group)
expect(project_cluster).not_to be_valid
expect(project_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
end
end
context 'group_type cluster' do
it 'does not allow setting project' do
group_cluster.projects << build(:project)
expect(group_cluster).not_to be_valid
expect(group_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
end
end
context 'instance_type cluster' do
it 'does not allow setting group' do
instance_cluster.groups << build(:group)
expect(instance_cluster).not_to be_valid
expect(instance_cluster.errors.full_messages).to include('Cluster cannot have groups assigned')
end
it 'does not allow setting project' do
instance_cluster.projects << build(:project)
expect(instance_cluster).not_to be_valid
expect(instance_cluster.errors.full_messages).to include('Cluster cannot have projects assigned')
end
end
end
end
describe '#provider' do
@ -229,6 +279,23 @@ describe Clusters::Cluster do
end
end
describe '#group' do
subject { cluster.group }
context 'when cluster belongs to a group' do
let(:cluster) { create(:cluster, :group) }
let(:group) { cluster.groups.first }
it { is_expected.to eq(group) }
end
context 'when cluster does not belong to any group' do
let(:cluster) { create(:cluster) }
it { is_expected.to be_nil }
end
end
describe '#applications' do
set(:cluster) { create(:cluster) }

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
require 'spec_helper'
describe Clusters::Group do
it { is_expected.to belong_to(:cluster) }
it { is_expected.to belong_to(:group) }
end

View file

@ -19,6 +19,8 @@ describe Group do
it { is_expected.to have_one(:chat_team) }
it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') }
it { is_expected.to have_many(:badges).class_name('GroupBadge') }
it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') }
it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
describe '#members & #requesters' do
let(:requester) { create(:user) }