diff options
Diffstat (limited to 'app/services/clusters')
16 files changed, 345 insertions, 139 deletions
diff --git a/app/services/clusters/applications/base_helm_service.rb b/app/services/clusters/applications/base_helm_service.rb index 8a71730d5ec..3e7f55f0c63 100644 --- a/app/services/clusters/applications/base_helm_service.rb +++ b/app/services/clusters/applications/base_helm_service.rb @@ -13,19 +13,37 @@ module Clusters def log_error(error) meta = { - exception: error.class.name, error_code: error.respond_to?(:error_code) ? error.error_code : nil, service: self.class.name, app_id: app.id, + app_name: app.name, project_ids: app.cluster.project_ids, - group_ids: app.cluster.group_ids, - message: error.message + group_ids: app.cluster.group_ids } - logger.error(meta) + logger_meta = meta.merge( + exception: error.class.name, + message: error.message, + backtrace: Gitlab::Profiler.clean_backtrace(error.backtrace) + ) + + logger.error(logger_meta) Gitlab::Sentry.track_acceptable_exception(error, extra: meta) end + def log_event(event) + meta = { + service: self.class.name, + app_id: app.id, + app_name: app.name, + project_ids: app.cluster.project_ids, + group_ids: app.cluster.group_ids, + event: event + } + + logger.info(meta) + end + def logger @logger ||= Gitlab::Kubernetes::Logger.build end @@ -46,6 +64,10 @@ module Clusters @install_command ||= app.install_command end + def update_command + @update_command ||= app.update_command + end + def upgrade_command(new_values = "") app.upgrade_command(new_values) end diff --git a/app/services/clusters/applications/base_service.rb b/app/services/clusters/applications/base_service.rb new file mode 100644 index 00000000000..a9feb60be6e --- /dev/null +++ b/app/services/clusters/applications/base_service.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class BaseService + InvalidApplicationError = Class.new(StandardError) + + attr_reader :cluster, :current_user, :params + + def initialize(cluster, user, params = {}) + @cluster = cluster + @current_user = user + @params = params.dup + end + + def execute(request) + instantiate_application.tap do |application| + if application.has_attribute?(:hostname) + application.hostname = params[:hostname] + end + + if application.has_attribute?(:email) + application.email = params[:email] + end + + if application.respond_to?(:oauth_application) + application.oauth_application = create_oauth_application(application, request) + end + + worker = worker_class(application) + + application.make_scheduled! + + worker.perform_async(application.name, application.id) + end + end + + protected + + def worker_class(application) + raise NotImplementedError + end + + def builder + raise NotImplementedError + end + + def project_builders + raise NotImplementedError + end + + def instantiate_application + raise_invalid_application_error if invalid_application? + + builder || raise(InvalidApplicationError, "invalid application: #{application_name}") + end + + def raise_invalid_application_error + raise(InvalidApplicationError, "invalid application: #{application_name}") + end + + def invalid_application? + unknown_application? || (!cluster.project_type? && project_only_application?) + end + + def unknown_application? + Clusters::Cluster::APPLICATIONS.keys.exclude?(application_name) + end + + # These applications will need extra configuration to enable them to work + # with groups of projects + def project_only_application? + Clusters::Cluster::PROJECT_ONLY_APPLICATIONS.include?(application_name) + end + + def application_name + params[:application] + end + + def create_oauth_application(application, request) + oauth_application_params = { + name: params[:application], + redirect_uri: application.callback_url, + scopes: application.oauth_scopes, + owner: current_user + } + + ::Applications::CreateService.new(current_user, oauth_application_params).execute(request) + end + end + end +end diff --git a/app/services/clusters/applications/check_ingress_ip_address_service.rb b/app/services/clusters/applications/check_ingress_ip_address_service.rb index 0ec06e776a7..e254a0358a0 100644 --- a/app/services/clusters/applications/check_ingress_ip_address_service.rb +++ b/app/services/clusters/applications/check_ingress_ip_address_service.rb @@ -11,9 +11,13 @@ module Clusters def execute return if app.external_ip + return if app.external_hostname return unless try_obtain_lease - app.update!(external_ip: ingress_ip) if ingress_ip + app.external_ip = ingress_ip if ingress_ip + app.external_hostname = ingress_hostname if ingress_hostname + + app.save! if app.changed? end private @@ -25,12 +29,16 @@ module Clusters end def ingress_ip - service.status.loadBalancer.ingress&.first&.ip + ingress_service&.ip + end + + def ingress_hostname + ingress_service&.hostname end - def service + def ingress_service strong_memoize(:ingress_service) do - app.ingress_service + app.ingress_service.status.loadBalancer.ingress&.first end end end diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb index c592d608b89..3c6803d24e6 100644 --- a/app/services/clusters/applications/check_installation_progress_service.rb +++ b/app/services/clusters/applications/check_installation_progress_service.rb @@ -37,7 +37,7 @@ module Clusters end def check_timeout - if timeouted? + if timed_out? begin app.make_errored!("Operation timed out. Check pod logs for #{pod_name} for more details.") end @@ -51,8 +51,8 @@ module Clusters install_command.pod_name end - def timeouted? - Time.now.utc - app.updated_at.to_time.utc > ClusterWaitForAppInstallationWorker::TIMEOUT + def timed_out? + Time.now.utc - app.updated_at.utc > ClusterWaitForAppInstallationWorker::TIMEOUT end def remove_installation_pod diff --git a/app/services/clusters/applications/check_uninstall_progress_service.rb b/app/services/clusters/applications/check_uninstall_progress_service.rb new file mode 100644 index 00000000000..8786d295d6a --- /dev/null +++ b/app/services/clusters/applications/check_uninstall_progress_service.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class CheckUninstallProgressService < BaseHelmService + def execute + return unless app.uninstalling? + + case installation_phase + when Gitlab::Kubernetes::Pod::SUCCEEDED + on_success + when Gitlab::Kubernetes::Pod::FAILED + on_failed + else + check_timeout + end + rescue Kubeclient::HttpError => e + log_error(e) + + app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code }) + end + + private + + def on_success + app.destroy! + rescue StandardError => e + app.make_errored!(_('Application uninstalled but failed to destroy: %{error_message}') % { error_message: e.message }) + ensure + remove_installation_pod + end + + def on_failed + app.make_errored!(_('Operation failed. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name }) + end + + def check_timeout + if timed_out? + app.make_errored!(_('Operation timed out. Check pod logs for %{pod_name} for more details.') % { pod_name: pod_name }) + else + WaitForUninstallAppWorker.perform_in(WaitForUninstallAppWorker::INTERVAL, app.name, app.id) + end + end + + def pod_name + app.uninstall_command.pod_name + end + + def timed_out? + Time.now.utc - app.updated_at.utc > WaitForUninstallAppWorker::TIMEOUT + end + + def remove_installation_pod + helm_api.delete_pod!(pod_name) + end + + def installation_phase + helm_api.status(pod_name) + end + end + end +end diff --git a/app/services/clusters/applications/create_service.rb b/app/services/clusters/applications/create_service.rb index 92c2c1b9834..f723c42c049 100644 --- a/app/services/clusters/applications/create_service.rb +++ b/app/services/clusters/applications/create_service.rb @@ -2,81 +2,16 @@ module Clusters module Applications - class CreateService - InvalidApplicationError = Class.new(StandardError) - - attr_reader :cluster, :current_user, :params - - def initialize(cluster, user, params = {}) - @cluster = cluster - @current_user = user - @params = params.dup - end - - def execute(request) - create_application.tap do |application| - if application.has_attribute?(:hostname) - application.hostname = params[:hostname] - end - - if application.has_attribute?(:email) - application.email = params[:email] - end - - if application.respond_to?(:oauth_application) - application.oauth_application = create_oauth_application(application, request) - end - - application.save! - - Clusters::Applications::ScheduleInstallationService.new(application).execute - end - end - + class CreateService < Clusters::Applications::BaseService private - def create_application - builder.call(@cluster) + def worker_class(application) + application.updateable? ? ClusterUpgradeAppWorker : ClusterInstallAppWorker end def builder - builders[application_name] || raise(InvalidApplicationError, "invalid application: #{application_name}") - end - - def builders - { - "helm" => -> (cluster) { cluster.application_helm || cluster.build_application_helm }, - "ingress" => -> (cluster) { cluster.application_ingress || cluster.build_application_ingress }, - "cert_manager" => -> (cluster) { cluster.application_cert_manager || cluster.build_application_cert_manager } - }.tap do |hash| - hash.merge!(project_builders) if cluster.project_type? - end - end - - # These applications will need extra configuration to enable them to work - # with groups of projects - def project_builders - { - "prometheus" => -> (cluster) { cluster.application_prometheus || cluster.build_application_prometheus }, - "runner" => -> (cluster) { cluster.application_runner || cluster.build_application_runner }, - "jupyter" => -> (cluster) { cluster.application_jupyter || cluster.build_application_jupyter }, - "knative" => -> (cluster) { cluster.application_knative || cluster.build_application_knative } - } - end - - def application_name - params[:application] - end - - def create_oauth_application(application, request) - oauth_application_params = { - name: params[:application], - redirect_uri: application.callback_url, - scopes: 'api read_user openid', - owner: current_user - } - - ::Applications::CreateService.new(current_user, oauth_application_params).execute(request) + cluster.public_send(:"application_#{application_name}") || # rubocop:disable GitlabSecurity/PublicSend + cluster.public_send(:"build_application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend end end end diff --git a/app/services/clusters/applications/destroy_service.rb b/app/services/clusters/applications/destroy_service.rb new file mode 100644 index 00000000000..f3a4c4f754a --- /dev/null +++ b/app/services/clusters/applications/destroy_service.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class DestroyService < ::Clusters::Applications::BaseService + def execute(_request) + instantiate_application.tap do |application| + break unless application.can_uninstall? + + application.make_scheduled! + + Clusters::Applications::UninstallWorker.perform_async(application.name, application.id) + end + end + + private + + def builder + cluster.public_send(:"application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend + end + end + end +end diff --git a/app/services/clusters/applications/install_service.rb b/app/services/clusters/applications/install_service.rb index 5a65dc4ef59..dffb4ce65ab 100644 --- a/app/services/clusters/applications/install_service.rb +++ b/app/services/clusters/applications/install_service.rb @@ -6,19 +6,26 @@ module Clusters def execute return unless app.scheduled? - begin - app.make_installing! - helm_api.install(install_command) + app.make_installing! - ClusterWaitForAppInstallationWorker.perform_in( - ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) - rescue Kubeclient::HttpError => e - log_error(e) - app.make_errored!("Kubernetes error: #{e.error_code}") - rescue StandardError => e - log_error(e) - app.make_errored!("Can't start installation process.") - end + install + end + + private + + def install + log_event(:begin_install) + helm_api.install(install_command) + + log_event(:schedule_wait_for_installation) + ClusterWaitForAppInstallationWorker.perform_in( + ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) + rescue Kubeclient::HttpError => e + log_error(e) + app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code }) + rescue StandardError => e + log_error(e) + app.make_errored!(_('Failed to install.')) end end end diff --git a/app/services/clusters/applications/patch_service.rb b/app/services/clusters/applications/patch_service.rb new file mode 100644 index 00000000000..fbea18bae6b --- /dev/null +++ b/app/services/clusters/applications/patch_service.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class PatchService < BaseHelmService + def execute + return unless app.scheduled? + + app.make_updating! + + patch + end + + private + + def patch + log_event(:begin_patch) + helm_api.update(update_command) + + log_event(:schedule_wait_for_patch) + ClusterWaitForAppInstallationWorker.perform_in( + ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) + rescue Kubeclient::HttpError => e + log_error(e) + app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code }) + rescue StandardError => e + log_error(e) + app.make_errored!(_('Failed to update.')) + end + end + end +end diff --git a/app/services/clusters/applications/schedule_installation_service.rb b/app/services/clusters/applications/schedule_installation_service.rb deleted file mode 100644 index 15c93f1e79b..00000000000 --- a/app/services/clusters/applications/schedule_installation_service.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Clusters - module Applications - class ScheduleInstallationService - attr_reader :application - - def initialize(application) - @application = application - end - - def execute - application.updateable? ? schedule_upgrade : schedule_install - end - - private - - def schedule_upgrade - application.make_scheduled! - - ClusterUpgradeAppWorker.perform_async(application.name, application.id) - end - - def schedule_install - application.make_scheduled! - - ClusterInstallAppWorker.perform_async(application.name, application.id) - end - end - end -end diff --git a/app/services/clusters/applications/uninstall_service.rb b/app/services/clusters/applications/uninstall_service.rb new file mode 100644 index 00000000000..50c8d806c14 --- /dev/null +++ b/app/services/clusters/applications/uninstall_service.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class UninstallService < BaseHelmService + def execute + return unless app.scheduled? + + app.make_uninstalling! + uninstall + end + + private + + def uninstall + helm_api.uninstall(app.uninstall_command) + + Clusters::Applications::WaitForUninstallAppWorker.perform_in( + Clusters::Applications::WaitForUninstallAppWorker::INTERVAL, app.name, app.id) + rescue Kubeclient::HttpError => e + log_error(e) + app.make_errored!("Kubernetes error: #{e.error_code}") + rescue StandardError => e + log_error(e) + app.make_errored!('Failed to uninstall.') + end + end + end +end diff --git a/app/services/clusters/applications/update_service.rb b/app/services/clusters/applications/update_service.rb new file mode 100644 index 00000000000..0fa937da865 --- /dev/null +++ b/app/services/clusters/applications/update_service.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Clusters + module Applications + class UpdateService < Clusters::Applications::BaseService + private + + def worker_class(application) + ClusterPatchAppWorker + end + + def builder + cluster.public_send(:"application_#{application_name}") # rubocop:disable GitlabSecurity/PublicSend + end + end + end +end diff --git a/app/services/clusters/applications/upgrade_service.rb b/app/services/clusters/applications/upgrade_service.rb index a0ece1d2635..ac68e64af38 100644 --- a/app/services/clusters/applications/upgrade_service.rb +++ b/app/services/clusters/applications/upgrade_service.rb @@ -6,22 +6,28 @@ module Clusters def execute return unless app.scheduled? - begin - app.make_updating! + app.make_updating! - # install_command works with upgrades too - # as it basically does `helm upgrade --install` - helm_api.update(install_command) + upgrade + end + + private + + def upgrade + # install_command works with upgrades too + # as it basically does `helm upgrade --install` + log_event(:begin_upgrade) + helm_api.update(install_command) - ClusterWaitForAppInstallationWorker.perform_in( - ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) - rescue Kubeclient::HttpError => e - log_error(e) - app.make_update_errored!("Kubernetes error: #{e.error_code}") - rescue StandardError => e - log_error(e) - app.make_update_errored!("Can't start upgrade process.") - end + log_event(:schedule_wait_for_upgrade) + ClusterWaitForAppInstallationWorker.perform_in( + ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id) + rescue Kubeclient::HttpError => e + log_error(e) + app.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code }) + rescue StandardError => e + log_error(e) + app.make_errored!(_('Failed to upgrade.')) end end end diff --git a/app/services/clusters/build_service.rb b/app/services/clusters/build_service.rb index 8de73831164..b1ac5549e30 100644 --- a/app/services/clusters/build_service.rb +++ b/app/services/clusters/build_service.rb @@ -12,6 +12,8 @@ module Clusters cluster.cluster_type = :project_type when ::Group cluster.cluster_type = :group_type + when Instance + cluster.cluster_type = :instance_type else raise NotImplementedError end diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb index 5a9da053780..886e484caaf 100644 --- a/app/services/clusters/create_service.rb +++ b/app/services/clusters/create_service.rb @@ -38,6 +38,8 @@ module Clusters { cluster_type: :project_type, projects: [clusterable] } when ::Group { cluster_type: :group_type, groups: [clusterable] } + when Instance + { cluster_type: :instance_type } else raise NotImplementedError end diff --git a/app/services/clusters/refresh_service.rb b/app/services/clusters/refresh_service.rb index 7c82b98a33f..3752a306793 100644 --- a/app/services/clusters/refresh_service.rb +++ b/app/services/clusters/refresh_service.rb @@ -21,7 +21,7 @@ module Clusters private_class_method :projects_with_missing_kubernetes_namespaces_for_cluster def self.clusters_with_missing_kubernetes_namespaces_for_project(project) - project.all_clusters.missing_kubernetes_namespace(project.kubernetes_namespaces) + project.clusters.managed.missing_kubernetes_namespace(project.kubernetes_namespaces) end private_class_method :clusters_with_missing_kubernetes_namespaces_for_project |