# frozen_string_literal: true require 'spec_helper' RSpec.describe Clusters::Applications::Prometheus do include KubernetesHelpers include StubRequests include_examples 'cluster application core specs', :clusters_applications_prometheus include_examples 'cluster application status specs', :clusters_applications_prometheus include_examples 'cluster application version specs', :clusters_applications_prometheus include_examples 'cluster application helm specs', :clusters_applications_prometheus include_examples 'cluster application initial status specs' describe 'after_destroy' do context 'cluster type is project' do let(:cluster) { create(:cluster, :with_installed_helm) } let(:application) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } it 'deactivates prometheus_service after destroy' do expect(Clusters::Applications::DeactivateServiceWorker) .to receive(:perform_async).with(cluster.id, 'prometheus') application.destroy! end end end describe 'transition to installed' do let(:project) { create(:project) } let(:cluster) { create(:cluster, :with_installed_helm) } let(:application) { create(:clusters_applications_prometheus, :installing, cluster: cluster) } it 'schedules post installation job' do expect(Clusters::Applications::ActivateServiceWorker) .to receive(:perform_async).with(cluster.id, 'prometheus') application.make_installed end end describe 'transition to updating' do let(:project) { create(:project) } let(:cluster) { create(:cluster, projects: [project]) } subject { create(:clusters_applications_prometheus, :installed, cluster: cluster) } it 'sets last_update_started_at to now' do freeze_time do expect { subject.make_updating }.to change { subject.reload.last_update_started_at }.to be_within(1.second).of(Time.current) end end end describe '#can_uninstall?' do let(:prometheus) { create(:clusters_applications_prometheus) } subject { prometheus.can_uninstall? } it { is_expected.to be_truthy } end describe '#prometheus_client' do shared_examples 'exception caught for prometheus client' do before do allow(kube_client).to receive(:proxy_url).and_raise(exception) end it 'returns nil' do expect(subject.prometheus_client).to be_nil end end context 'cluster is nil' do it 'returns nil' do expect(subject.cluster).to be_nil expect(subject.prometheus_client).to be_nil end end context "cluster doesn't have kubeclient" do let(:cluster) { create(:cluster) } subject { create(:clusters_applications_prometheus, cluster: cluster) } it 'returns nil' do expect(subject.prometheus_client).to be_nil end end context 'cluster has kubeclient' do let(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:kubernetes_url) { subject.cluster.platform_kubernetes.api_url } let(:kube_client) { subject.cluster.kubeclient.core_client } subject { create(:clusters_applications_prometheus, cluster: cluster) } before do subject.cluster.platform_kubernetes.namespace = 'a-namespace' stub_kubeclient_discover(cluster.platform_kubernetes.api_url) create(:cluster_kubernetes_namespace, cluster: cluster, cluster_project: cluster.cluster_project, project: cluster.cluster_project.project) end it 'creates proxy prometheus_client' do expect(subject.prometheus_client).to be_instance_of(Gitlab::PrometheusClient) end it 'merges proxy_url, options and headers from kube client with prometheus_client options' do expect(Gitlab::PrometheusClient) .to(receive(:new)) .with(a_valid_url, kube_client.rest_client.options.merge({ headers: kube_client.headers, timeout: PrometheusAdapter::DEFAULT_PROMETHEUS_REQUEST_TIMEOUT_SEC })) subject.prometheus_client end context 'when cluster is not reachable' do it_behaves_like 'exception caught for prometheus client' do let(:exception) { Kubeclient::HttpError.new(401, 'Unauthorized', nil) } end end context 'when there is a socket error while contacting cluster' do it_behaves_like 'exception caught for prometheus client' do let(:exception) { Errno::ECONNREFUSED } end it_behaves_like 'exception caught for prometheus client' do let(:exception) { Errno::ECONNRESET } end end context 'when the network is unreachable' do it_behaves_like 'exception caught for prometheus client' do let(:exception) { Errno::ENETUNREACH } end end end end describe '#install_command' do let(:prometheus) { create(:clusters_applications_prometheus) } subject { prometheus.install_command } it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::InstallCommand) } it 'is initialized with 3 arguments' do expect(subject.name).to eq('prometheus') expect(subject.chart).to eq('prometheus/prometheus') expect(subject.version).to eq('10.4.1') expect(subject).to be_rbac expect(subject.files).to eq(prometheus.files) end context 'on a non rbac enabled cluster' do before do prometheus.cluster.platform_kubernetes.abac! end it { is_expected.not_to be_rbac } end context 'application failed to install previously' do let(:prometheus) { create(:clusters_applications_prometheus, :errored, version: '2.0.0') } it 'is initialized with the locked version' do expect(subject.version).to eq('10.4.1') end end it 'does not install knative metrics' do expect(subject.postinstall).to be_empty end context 'with knative installed' do let(:knative) { create(:clusters_applications_knative, :updated ) } let(:prometheus) { create(:clusters_applications_prometheus, cluster: knative.cluster) } subject { prometheus.install_command } it 'installs knative metrics' do expect(subject.postinstall).to include("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}") end end end describe '#uninstall_command' do let(:prometheus) { create(:clusters_applications_prometheus) } subject { prometheus.uninstall_command } it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::V3::DeleteCommand) } it 'has the application name' do expect(subject.name).to eq('prometheus') end it 'has files' do expect(subject.files).to eq(prometheus.files) end it 'is rbac' do expect(subject).to be_rbac end describe '#predelete' do let(:knative) { create(:clusters_applications_knative, :updated ) } let(:prometheus) { create(:clusters_applications_prometheus, cluster: knative.cluster) } subject { prometheus.uninstall_command.predelete } it 'deletes knative metrics' do metrics_config = Clusters::Applications::Knative::METRICS_CONFIG is_expected.to include("kubectl delete -f #{metrics_config} --ignore-not-found") end end context 'on a non rbac enabled cluster' do before do prometheus.cluster.platform_kubernetes.abac! end it { is_expected.not_to be_rbac } end end describe '#patch_command' do subject(:patch_command) { prometheus.patch_command(values) } let(:prometheus) { build(:clusters_applications_prometheus) } let(:values) { prometheus.values } it { is_expected.to be_an_instance_of(::Gitlab::Kubernetes::Helm::V3::PatchCommand) } it 'is initialized with 3 arguments' do expect(patch_command.name).to eq('prometheus') expect(patch_command.chart).to eq('prometheus/prometheus') expect(patch_command.version).to eq('10.4.1') expect(patch_command.files).to eq(prometheus.files) end end describe '#update_in_progress?' do context 'when app is updating' do it 'returns true' do cluster = create(:cluster) prometheus_app = build(:clusters_applications_prometheus, :updating, cluster: cluster) expect(prometheus_app.update_in_progress?).to be true end end end describe '#update_errored?' do context 'when app errored' do it 'returns true' do cluster = create(:cluster) prometheus_app = build(:clusters_applications_prometheus, :update_errored, cluster: cluster) expect(prometheus_app.update_errored?).to be true end end end describe '#files' do let(:application) { create(:clusters_applications_prometheus) } let(:values) { subject[:'values.yaml'] } subject { application.files } it 'includes prometheus valid values' do expect(values).to include('alertmanager') expect(values).to include('kubeStateMetrics') expect(values).to include('nodeExporter') expect(values).to include('pushgateway') expect(values).to include('serverFiles') end end describe '#files_with_replaced_values' do let(:application) { build(:clusters_applications_prometheus) } let(:files) { application.files } subject { application.files_with_replaced_values({ hello: :world }) } it 'does not modify #files' do expect(subject[:'values.yaml']).not_to eq(files[:'values.yaml']) expect(files[:'values.yaml']).to eq(application.values) end it 'returns values.yaml with replaced values' do expect(subject[:'values.yaml']).to eq({ hello: :world }) end it 'uses values from #files, except for values.yaml' do allow(application).to receive(:files).and_return({ 'values.yaml': 'some value specific to files', 'file_a.txt': 'file_a', 'file_b.txt': 'file_b' }) expect(subject.except(:'values.yaml')).to eq({ 'file_a.txt': 'file_a', 'file_b.txt': 'file_b' }) end end describe '#configured?' do let(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) } subject { prometheus.configured? } context 'when a kubenetes client is present' do let(:cluster) { create(:cluster, :project, :provided_by_gcp) } it { is_expected.to be_truthy } context 'when it is not availalble' do let(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) } it { is_expected.to be_falsey } end context 'when the kubernetes URL is blocked' do before do blocked_ip = '127.0.0.1' # localhost addresses are blocked by default stub_all_dns(cluster.platform.api_url, ip_address: blocked_ip) end it { is_expected.to be_falsey } end end context 'when a kubenetes client is not present' do let(:cluster) { create(:cluster) } it { is_expected.to be_falsy } end end describe '#updated_since?' do let(:cluster) { create(:cluster) } let(:prometheus_app) { build(:clusters_applications_prometheus, cluster: cluster) } let(:timestamp) { Time.current - 5.minutes } around do |example| freeze_time { example.run } end before do prometheus_app.last_update_started_at = Time.current end context 'when app does not have status failed' do it 'returns true when last update started after the timestamp' do expect(prometheus_app.updated_since?(timestamp)).to be true end it 'returns false when last update started before the timestamp' do expect(prometheus_app.updated_since?(Time.current + 5.minutes)).to be false end end context 'when app has status failed' do it 'returns false when last update started after the timestamp' do prometheus_app.status = 6 expect(prometheus_app.updated_since?(timestamp)).to be false end end end describe 'alert manager token' do subject { create(:clusters_applications_prometheus) } context 'when not set' do it 'is empty by default' do expect(subject.alert_manager_token).to be_nil expect(subject.encrypted_alert_manager_token).to be_nil expect(subject.encrypted_alert_manager_token_iv).to be_nil end describe '#generate_alert_manager_token!' do it 'generates a token' do subject.generate_alert_manager_token! expect(subject.alert_manager_token).to match(/\A\h{32}\z/) end end end context 'when set' do let(:token) { SecureRandom.hex } before do subject.update!(alert_manager_token: token) end it 'reads the token' do expect(subject.alert_manager_token).to eq(token) expect(subject.encrypted_alert_manager_token).not_to be_nil expect(subject.encrypted_alert_manager_token_iv).not_to be_nil end describe '#generate_alert_manager_token!' do it 'does not re-generate the token' do subject.generate_alert_manager_token! expect(subject.alert_manager_token).to eq(token) end end end end end