debian-mirror-gitlab/app/models/clusters/cluster.rb

476 lines
16 KiB
Ruby
Raw Normal View History

2018-11-18 11:00:15 +05:30
# frozen_string_literal: true
2018-03-17 18:26:18 +05:30
module Clusters
2019-07-07 11:18:12 +05:30
class Cluster < ApplicationRecord
2018-03-17 18:26:18 +05:30
include Presentable
2018-12-13 13:39:08 +05:30
include Gitlab::Utils::StrongMemoize
2019-02-15 15:39:39 +05:30
include FromUnion
2019-09-04 21:01:54 +05:30
include ReactiveCaching
2019-12-26 22:10:19 +05:30
include AfterCommitQueue
2018-03-17 18:26:18 +05:30
self.table_name = 'clusters'
APPLICATIONS = {
2020-04-08 14:13:33 +05:30
Clusters::Applications::Helm.application_name => Clusters::Applications::Helm,
Clusters::Applications::Ingress.application_name => Clusters::Applications::Ingress,
Clusters::Applications::CertManager.application_name => Clusters::Applications::CertManager,
Clusters::Applications::Crossplane.application_name => Clusters::Applications::Crossplane,
Clusters::Applications::Prometheus.application_name => Clusters::Applications::Prometheus,
Clusters::Applications::Runner.application_name => Clusters::Applications::Runner,
Clusters::Applications::Jupyter.application_name => Clusters::Applications::Jupyter,
Clusters::Applications::Knative.application_name => Clusters::Applications::Knative,
2020-04-22 19:07:51 +05:30
Clusters::Applications::ElasticStack.application_name => Clusters::Applications::ElasticStack,
Clusters::Applications::Fluentd.application_name => Clusters::Applications::Fluentd
2019-12-26 22:10:19 +05:30
}.freeze
2019-12-04 20:38:33 +05:30
DEFAULT_ENVIRONMENT = '*'
KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'
2020-01-01 13:55:28 +05:30
APPLICATIONS_ASSOCIATIONS = APPLICATIONS.values.map(&:association_name).freeze
2018-03-17 18:26:18 +05:30
2020-05-24 23:13:21 +05:30
self.reactive_cache_work_type = :external_dependency
2018-03-17 18:26:18 +05:30
belongs_to :user
2019-12-21 20:55:43 +05:30
belongs_to :management_project, class_name: '::Project', optional: true
2018-03-17 18:26:18 +05:30
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
2018-12-13 13:39:08 +05:30
has_one :cluster_project, -> { order(id: :desc) }, class_name: 'Clusters::Project'
2020-03-13 15:44:24 +05:30
has_many :deployment_clusters
2020-05-24 23:13:21 +05:30
has_many :deployments, inverse_of: :cluster
2020-06-23 00:09:42 +05:30
has_many :successful_deployments, -> { success }, class_name: 'Deployment'
has_many :environments, -> { distinct }, through: :deployments
2018-12-13 13:39:08 +05:30
has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :groups, through: :cluster_groups, class_name: '::Group'
2020-01-01 13:55:28 +05:30
has_many :groups_projects, through: :groups, source: :projects, class_name: '::Project'
2018-03-17 18:26:18 +05:30
# we force autosave to happen when we save `Cluster` model
has_one :provider_gcp, class_name: 'Clusters::Providers::Gcp', autosave: true
2019-12-21 20:55:43 +05:30
has_one :provider_aws, class_name: 'Clusters::Providers::Aws', autosave: true
2018-03-17 18:26:18 +05:30
2019-02-15 15:39:39 +05:30
has_one :platform_kubernetes, class_name: 'Clusters::Platforms::Kubernetes', inverse_of: :cluster, autosave: true
2018-03-17 18:26:18 +05:30
2019-12-04 20:38:33 +05:30
def self.has_one_cluster_application(name) # rubocop:disable Naming/PredicateName
application = APPLICATIONS[name.to_s]
2019-12-21 20:55:43 +05:30
has_one application.association_name, class_name: application.to_s, inverse_of: :cluster # rubocop:disable Rails/ReflectionClassName
2019-12-04 20:38:33 +05:30
end
has_one_cluster_application :helm
has_one_cluster_application :ingress
has_one_cluster_application :cert_manager
2019-12-26 22:10:19 +05:30
has_one_cluster_application :crossplane
2019-12-04 20:38:33 +05:30
has_one_cluster_application :prometheus
has_one_cluster_application :runner
has_one_cluster_application :jupyter
has_one_cluster_application :knative
2019-12-26 22:10:19 +05:30
has_one_cluster_application :elastic_stack
2020-04-22 19:07:51 +05:30
has_one_cluster_application :fluentd
2018-12-13 13:39:08 +05:30
has_many :kubernetes_namespaces
2020-04-22 19:07:51 +05:30
has_many :metrics_dashboard_annotations, class_name: 'Metrics::Dashboard::Annotation', inverse_of: :cluster
2018-03-17 18:26:18 +05:30
accepts_nested_attributes_for :provider_gcp, update_only: true
2019-12-26 22:10:19 +05:30
accepts_nested_attributes_for :provider_aws, update_only: true
2018-03-17 18:26:18 +05:30
accepts_nested_attributes_for :platform_kubernetes, update_only: true
validates :name, cluster_name: true
2018-12-13 13:39:08 +05:30
validates :cluster_type, presence: true
2019-03-02 22:35:43 +05:30
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
2019-10-12 21:52:04 +05:30
validates :namespace_per_environment, inclusion: { in: [true, false] }
2018-03-17 18:26:18 +05:30
2019-03-02 22:35:43 +05:30
validate :restrict_modification, on: :update
2018-12-13 13:39:08 +05:30
validate :no_groups, unless: :group_type?
validate :no_projects, unless: :project_type?
2019-12-21 20:55:43 +05:30
validate :unique_management_project_environment_scope
2018-12-13 13:39:08 +05:30
2019-09-04 21:01:54 +05:30
after_save :clear_reactive_cache!
2018-03-17 18:26:18 +05:30
delegate :status, to: :provider, allow_nil: true
delegate :status_reason, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
2019-12-26 22:10:19 +05:30
delegate :knative_pre_installed?, to: :provider, allow_nil: true
2018-03-17 18:26:18 +05:30
delegate :active?, to: :platform_kubernetes, prefix: true, allow_nil: true
2018-11-20 20:47:30 +05:30
delegate :rbac?, to: :platform_kubernetes, prefix: true, allow_nil: true
2018-12-13 13:39:08 +05:30
delegate :available?, to: :application_helm, prefix: true, allow_nil: true
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
2019-02-15 15:39:39 +05:30
delegate :available?, to: :application_knative, prefix: true, allow_nil: true
2019-03-02 22:35:43 +05:30
delegate :external_ip, to: :application_ingress, prefix: true, allow_nil: true
2019-07-07 11:18:12 +05:30
delegate :external_hostname, to: :application_ingress, prefix: true, allow_nil: true
2019-03-02 22:35:43 +05:30
alias_attribute :base_domain, :domain
2019-07-07 11:18:12 +05:30
alias_attribute :provided_by_user?, :user?
2018-12-13 13:39:08 +05:30
enum cluster_type: {
instance_type: 1,
group_type: 2,
project_type: 3
}
2018-03-17 18:26:18 +05:30
enum platform_type: {
kubernetes: 1
}
enum provider_type: {
user: 0,
2019-12-21 20:55:43 +05:30
gcp: 1,
aws: 2
2018-03-17 18:26:18 +05:30
}
scope :enabled, -> { where(enabled: true) }
scope :disabled, -> { where(enabled: false) }
2019-12-21 20:55:43 +05:30
scope :user_provided, -> { where(provider_type: :user) }
scope :gcp_provided, -> { where(provider_type: :gcp) }
scope :aws_provided, -> { where(provider_type: :aws) }
scope :gcp_installed, -> { gcp_provided.joins(:provider_gcp).merge(Clusters::Providers::Gcp.with_status(:created)) }
scope :aws_installed, -> { aws_provided.joins(:provider_aws).merge(Clusters::Providers::Aws.with_status(:created)) }
2020-06-23 00:09:42 +05:30
scope :with_enabled_modsecurity, -> { joins(:application_ingress).merge(::Clusters::Applications::Ingress.modsecurity_enabled) }
scope :with_available_elasticstack, -> { joins(:application_elastic_stack).merge(::Clusters::Applications::ElasticStack.available) }
scope :distinct_with_deployed_environments, -> { joins(:environments).merge(::Deployment.success).distinct }
scope :preload_elasticstack, -> { preload(:application_elastic_stack) }
scope :preload_environments, -> { preload(:environments) }
2019-07-31 22:56:46 +05:30
scope :managed, -> { where(managed: true) }
2020-01-01 13:55:28 +05:30
scope :with_persisted_applications, -> { eager_load(*APPLICATIONS_ASSOCIATIONS) }
2018-05-01 15:08:00 +05:30
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
2020-04-22 19:07:51 +05:30
scope :with_management_project, -> { where.not(management_project: nil) }
2018-03-17 18:26:18 +05:30
2019-12-26 22:10:19 +05:30
scope :for_project_namespace, -> (namespace_id) { joins(:projects).where(projects: { namespace_id: namespace_id }) }
2020-06-23 00:09:42 +05:30
scope :with_application_prometheus, -> { includes(:application_prometheus).joins(:application_prometheus) }
scope :with_project_alert_service_data, -> (project_ids) do
conditions = { projects: { alerts_service: [:data] } }
includes(conditions).joins(conditions).where(projects: { id: project_ids })
end
2019-12-26 22:10:19 +05:30
2019-02-15 15:39:39 +05:30
def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
2019-07-31 22:56:46 +05:30
return [] if clusterable.is_a?(Instance)
2019-02-15 15:39:39 +05:30
hierarchy_groups = clusterable.ancestors_upto(hierarchy_order: hierarchy_order).eager_load(:clusters)
hierarchy_groups = hierarchy_groups.merge(current_scope) if current_scope
2019-07-31 22:56:46 +05:30
hierarchy_groups.flat_map(&:clusters) + Instance.new.clusters
2019-02-15 15:39:39 +05:30
end
2019-12-26 22:10:19 +05:30
state_machine :cleanup_status, initial: :cleanup_not_started do
state :cleanup_not_started, value: 1
state :cleanup_uninstalling_applications, value: 2
state :cleanup_removing_project_namespaces, value: 3
state :cleanup_removing_service_account, value: 4
state :cleanup_errored, value: 5
event :start_cleanup do |cluster|
transition [:cleanup_not_started, :cleanup_errored] => :cleanup_uninstalling_applications
end
event :continue_cleanup do
transition(
cleanup_uninstalling_applications: :cleanup_removing_project_namespaces,
cleanup_removing_project_namespaces: :cleanup_removing_service_account)
end
event :make_cleanup_errored do
transition any => :cleanup_errored
end
before_transition any => [:cleanup_errored] do |cluster, transition|
status_reason = transition.args.first
cluster.cleanup_status_reason = status_reason if status_reason
end
after_transition [:cleanup_not_started, :cleanup_errored] => :cleanup_uninstalling_applications do |cluster|
cluster.run_after_commit do
Clusters::Cleanup::AppWorker.perform_async(cluster.id)
end
end
after_transition cleanup_uninstalling_applications: :cleanup_removing_project_namespaces do |cluster|
cluster.run_after_commit do
Clusters::Cleanup::ProjectNamespaceWorker.perform_async(cluster.id)
end
end
after_transition cleanup_removing_project_namespaces: :cleanup_removing_service_account do |cluster|
cluster.run_after_commit do
Clusters::Cleanup::ServiceAccountWorker.perform_async(cluster.id)
end
end
end
2020-01-01 13:55:28 +05:30
def all_projects
return projects if project_type?
return groups_projects if group_type?
::Project.all
end
2018-03-17 18:26:18 +05:30
def status_name
2019-12-26 22:10:19 +05:30
return cleanup_status_name if cleanup_errored?
return :cleanup_ongoing unless cleanup_not_started?
2019-09-04 21:01:54 +05:30
provider&.status_name || connection_status.presence || :created
end
def connection_status
with_reactive_cache do |data|
data[:connection_status]
2018-03-17 18:26:18 +05:30
end
end
2020-05-24 23:13:21 +05:30
def nodes
with_reactive_cache do |data|
data[:nodes]
end
end
2019-09-04 21:01:54 +05:30
def calculate_reactive_cache
return unless enabled?
2020-05-24 23:13:21 +05:30
{ connection_status: retrieve_connection_status, nodes: retrieve_nodes }
2018-03-17 18:26:18 +05:30
end
2020-01-01 13:55:28 +05:30
def persisted_applications
APPLICATIONS_ASSOCIATIONS.map(&method(:public_send)).compact
end
2018-03-17 18:26:18 +05:30
def applications
2020-05-24 23:13:21 +05:30
APPLICATIONS.each_value.map do |application_class|
find_or_build_application(application_class)
2019-12-04 20:38:33 +05:30
end
2018-03-17 18:26:18 +05:30
end
2020-05-24 23:13:21 +05:30
def find_or_build_application(application_class)
raise ArgumentError, "#{application_class} is not in APPLICATIONS" unless APPLICATIONS.value?(application_class)
association_name = application_class.association_name
public_send(association_name) || public_send("build_#{association_name}") # rubocop:disable GitlabSecurity/PublicSend
end
2018-03-17 18:26:18 +05:30
def provider
2019-12-21 20:55:43 +05:30
if gcp?
provider_gcp
elsif aws?
provider_aws
end
2018-03-17 18:26:18 +05:30
end
def platform
return platform_kubernetes if kubernetes?
end
def first_project
2018-12-13 13:39:08 +05:30
strong_memoize(:first_project) do
projects.first
end
2018-03-17 18:26:18 +05:30
end
alias_method :project, :first_project
2018-12-13 13:39:08 +05:30
def first_group
strong_memoize(:first_group) do
groups.first
end
end
alias_method :group, :first_group
2019-07-31 22:56:46 +05:30
def instance
Instance.new if instance_type?
end
2018-03-17 18:26:18 +05:30
def kubeclient
platform_kubernetes.kubeclient if kubernetes?
end
2020-04-08 14:13:33 +05:30
def kubernetes_namespace_for(environment, deployable: environment.last_deployable)
if deployable && environment.project_id != deployable.project_id
raise ArgumentError, 'environment.project_id must match deployable.project_id'
end
2020-01-01 13:55:28 +05:30
managed_namespace(environment) ||
2020-04-08 14:13:33 +05:30
ci_configured_namespace(deployable) ||
2020-01-01 13:55:28 +05:30
default_namespace(environment)
2019-02-15 15:39:39 +05:30
end
def allow_user_defined_namespace?
2019-09-04 21:01:54 +05:30
project_type? || !managed?
2018-12-13 13:39:08 +05:30
end
2019-03-02 22:35:43 +05:30
def kube_ingress_domain
2019-09-04 21:01:54 +05:30
@kube_ingress_domain ||= domain.presence || instance_domain
2019-03-02 22:35:43 +05:30
end
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
break variables unless kube_ingress_domain
variables.append(key: KUBE_INGRESS_BASE_DOMAIN, value: kube_ingress_domain)
end
end
2020-01-01 13:55:28 +05:30
def delete_cached_resources!
kubernetes_namespaces.delete_all(:delete_all)
end
def clusterable
return unless cluster_type
case cluster_type
when 'project_type'
project
when 'group_type'
group
when 'instance_type'
instance
else
raise NotImplementedError
end
end
2020-03-13 15:44:24 +05:30
def serverless_domain
strong_memoize(:serverless_domain) do
self.application_knative&.serverless_domain_cluster
end
end
2020-06-23 00:09:42 +05:30
def local_tiller_enabled?
Feature.enabled?(:managed_apps_local_tiller, clusterable, default_enabled: false)
end
2018-03-17 18:26:18 +05:30
private
2019-12-21 20:55:43 +05:30
def unique_management_project_environment_scope
return unless management_project
duplicate_management_clusters = management_project.management_clusters
.where(environment_scope: environment_scope)
.where.not(id: id)
if duplicate_management_clusters.any?
2020-04-08 14:13:33 +05:30
errors.add(:environment_scope, 'cannot add duplicated environment scope')
2019-12-21 20:55:43 +05:30
end
end
2020-01-01 13:55:28 +05:30
def managed_namespace(environment)
Clusters::KubernetesNamespaceFinder.new(
self,
project: environment.project,
environment_name: environment.name
).execute&.namespace
end
2020-04-08 14:13:33 +05:30
def ci_configured_namespace(deployable)
# YAML configuration of namespaces not supported for managed clusters
return if managed?
deployable&.expanded_kubernetes_namespace
2020-01-01 13:55:28 +05:30
end
def default_namespace(environment)
Gitlab::Kubernetes::DefaultNamespace.new(
self,
project: environment.project
).from_environment_slug(environment.slug)
end
2019-03-02 22:35:43 +05:30
def instance_domain
@instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
end
2019-09-04 21:01:54 +05:30
def retrieve_connection_status
2020-05-24 23:13:21 +05:30
result = ::Gitlab::Kubernetes::KubeClient.graceful_request(id) { kubeclient.core_client.discover }
result[:status]
end
def retrieve_nodes
result = ::Gitlab::Kubernetes::KubeClient.graceful_request(id) { kubeclient.get_nodes }
2020-06-23 00:09:42 +05:30
return unless result[:response]
cluster_nodes = result[:response]
2020-05-24 23:13:21 +05:30
result = ::Gitlab::Kubernetes::KubeClient.graceful_request(id) { kubeclient.metrics_client.get_nodes }
nodes_metrics = result[:response].to_a
cluster_nodes.inject([]) do |memo, node|
sliced_node = filter_relevant_node_attributes(node)
matched_node_metric = nodes_metrics.find { |node_metric| node_metric.metadata.name == node.metadata.name }
sliced_node_metrics = matched_node_metric ? filter_relevant_node_metrics_attributes(matched_node_metric) : {}
memo << sliced_node.merge(sliced_node_metrics)
2019-09-04 21:01:54 +05:30
end
end
2020-05-24 23:13:21 +05:30
def filter_relevant_node_attributes(node)
{
'metadata' => {
'name' => node.metadata.name
},
'status' => {
'capacity' => {
'cpu' => node.status.capacity.cpu,
'memory' => node.status.capacity.memory
},
'allocatable' => {
'cpu' => node.status.allocatable.cpu,
'memory' => node.status.allocatable.memory
}
}
}
end
def filter_relevant_node_metrics_attributes(node_metrics)
{
'usage' => {
'cpu' => node_metrics.usage.cpu,
'memory' => node_metrics.usage.memory
}
}
end
2019-03-02 22:35:43 +05:30
# To keep backward compatibility with AUTO_DEVOPS_DOMAIN
# environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
# is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
# ProjectAutoDevops#Domain, project variables or group variables,
# as the AUTO_DEVOPS_DOMAIN is needed for CI_ENVIRONMENT_URL
#
# This method should is scheduled to be removed on
2019-12-04 20:38:33 +05:30
# https://gitlab.com/gitlab-org/gitlab-foss/issues/56959
2019-03-02 22:35:43 +05:30
def legacy_auto_devops_domain
if project_type?
project&.auto_devops&.domain.presence ||
project.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence ||
project.group&.variables&.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
elsif group_type?
group.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
end
end
2018-03-17 18:26:18 +05:30
def restrict_modification
if provider&.on_creation?
2020-04-08 14:13:33 +05:30
errors.add(:base, _('Cannot modify provider during creation'))
2018-03-17 18:26:18 +05:30
return false
end
true
end
2018-12-13 13:39:08 +05:30
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
2018-03-17 18:26:18 +05:30
end
end
2019-12-04 20:38:33 +05:30
Clusters::Cluster.prepend_if_ee('EE::Clusters::Cluster')