summaryrefslogtreecommitdiff
path: root/spec/services/pod_logs
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-11 15:09:37 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-11 15:09:37 +0000
commita210c43e0aca0311cc1d3d381763b25979ec72dc (patch)
tree0325d173da7a6e7bd6c2cdf450d0aa1c4e142d0f /spec/services/pod_logs
parentc9687bdf58e9d4a9c3942f587bd4841f42e3b5de (diff)
downloadgitlab-ce-a210c43e0aca0311cc1d3d381763b25979ec72dc.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/services/pod_logs')
-rw-r--r--spec/services/pod_logs/base_service_spec.rb229
-rw-r--r--spec/services/pod_logs/elasticsearch_service_spec.rb174
-rw-r--r--spec/services/pod_logs/kubernetes_service_spec.rb166
3 files changed, 569 insertions, 0 deletions
diff --git a/spec/services/pod_logs/base_service_spec.rb b/spec/services/pod_logs/base_service_spec.rb
new file mode 100644
index 00000000000..a18fda544df
--- /dev/null
+++ b/spec/services/pod_logs/base_service_spec.rb
@@ -0,0 +1,229 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::PodLogs::BaseService do
+ include KubernetesHelpers
+
+ let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*') }
+ let(:namespace) { 'autodevops-deploy-9-production' }
+
+ let(:pod_name) { 'pod-1' }
+ let(:container_name) { 'container-0' }
+ let(:params) { {} }
+ let(:raw_pods) do
+ JSON.parse([
+ kube_pod(name: pod_name)
+ ].to_json, object_class: OpenStruct)
+ end
+
+ subject { described_class.new(cluster, namespace, params: params) }
+
+ describe '#initialize' do
+ let(:params) do
+ {
+ 'container_name' => container_name,
+ 'another_param' => 'foo'
+ }
+ end
+
+ it 'filters the parameters' do
+ expect(subject.cluster).to eq(cluster)
+ expect(subject.namespace).to eq(namespace)
+ expect(subject.params).to eq({
+ 'container_name' => container_name
+ })
+ expect(subject.params.equal?(params)).to be(false)
+ end
+ end
+
+ describe '#check_arguments' do
+ context 'when cluster and namespace are provided' do
+ it 'returns success' do
+ result = subject.send(:check_arguments, {})
+
+ expect(result[:status]).to eq(:success)
+ end
+ end
+
+ context 'when cluster is nil' do
+ let(:cluster) { nil }
+
+ it 'returns an error' do
+ result = subject.send(:check_arguments, {})
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Cluster does not exist')
+ end
+ end
+
+ context 'when namespace is nil' do
+ let(:namespace) { nil }
+
+ it 'returns an error' do
+ result = subject.send(:check_arguments, {})
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Namespace is empty')
+ end
+ end
+
+ context 'when namespace is empty' do
+ let(:namespace) { '' }
+
+ it 'returns an error' do
+ result = subject.send(:check_arguments, {})
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Namespace is empty')
+ end
+ end
+ end
+
+ describe '#check_param_lengths' do
+ context 'when pod_name and container_name are provided' do
+ let(:params) do
+ {
+ 'pod_name' => pod_name,
+ 'container_name' => container_name
+ }
+ end
+
+ it 'returns success' do
+ result = subject.send(:check_param_lengths, {})
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:pod_name]).to eq(pod_name)
+ expect(result[:container_name]).to eq(container_name)
+ end
+ end
+
+ context 'when pod_name is too long' do
+ let(:params) do
+ {
+ 'pod_name' => "a very long string." * 15
+ }
+ end
+
+ it 'returns an error' do
+ result = subject.send(:check_param_lengths, {})
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('pod_name cannot be larger than 253 chars')
+ end
+ end
+
+ context 'when container_name is too long' do
+ let(:params) do
+ {
+ 'container_name' => "a very long string." * 15
+ }
+ end
+
+ it 'returns an error' do
+ result = subject.send(:check_param_lengths, {})
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('container_name cannot be larger than 253 chars')
+ end
+ end
+ end
+
+ describe '#get_raw_pods' do
+ let(:service) { create(:cluster_platform_kubernetes, :configured) }
+
+ it 'returns success with passthrough k8s response' do
+ stub_kubeclient_pods(namespace)
+
+ result = subject.send(:get_raw_pods, {})
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:raw_pods].first).to be_a(Kubeclient::Resource)
+ end
+ end
+
+ describe '#get_pod_names' do
+ it 'returns success with a list of pods' do
+ result = subject.send(:get_pod_names, raw_pods: raw_pods)
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:pods]).to eq([pod_name])
+ end
+ end
+
+ describe '#check_pod_name' do
+ it 'returns success if pod_name was specified' do
+ result = subject.send(:check_pod_name, pod_name: pod_name, pods: [pod_name])
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:pod_name]).to eq(pod_name)
+ end
+
+ it 'returns success if pod_name was not specified but there are pods' do
+ result = subject.send(:check_pod_name, pod_name: nil, pods: [pod_name])
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:pod_name]).to eq(pod_name)
+ end
+
+ it 'returns error if pod_name was not specified and there are no pods' do
+ result = subject.send(:check_pod_name, pod_name: nil, pods: [])
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('No pods available')
+ end
+
+ it 'returns error if pod_name was specified but does not exist' do
+ result = subject.send(:check_pod_name, pod_name: 'another_pod', pods: [pod_name])
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Pod does not exist')
+ end
+ end
+
+ describe '#check_container_name' do
+ it 'returns success if container_name was specified' do
+ result = subject.send(:check_container_name,
+ container_name: container_name,
+ pod_name: pod_name,
+ raw_pods: raw_pods
+ )
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:container_name]).to eq(container_name)
+ end
+
+ it 'returns success if container_name was not specified and there are containers' do
+ result = subject.send(:check_container_name,
+ pod_name: pod_name,
+ raw_pods: raw_pods
+ )
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:container_name]).to eq(container_name)
+ end
+
+ it 'returns error if container_name was not specified and there are no containers on the pod' do
+ raw_pods.first.spec.containers = []
+
+ result = subject.send(:check_container_name,
+ pod_name: pod_name,
+ raw_pods: raw_pods
+ )
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('No containers available')
+ end
+
+ it 'returns error if container_name was specified but does not exist' do
+ result = subject.send(:check_container_name,
+ container_name: 'foo',
+ pod_name: pod_name,
+ raw_pods: raw_pods
+ )
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Container does not exist')
+ end
+ end
+end
diff --git a/spec/services/pod_logs/elasticsearch_service_spec.rb b/spec/services/pod_logs/elasticsearch_service_spec.rb
new file mode 100644
index 00000000000..0f0c36da56a
--- /dev/null
+++ b/spec/services/pod_logs/elasticsearch_service_spec.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::PodLogs::ElasticsearchService do
+ let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*') }
+ let(:namespace) { 'autodevops-deploy-9-production' }
+
+ let(:pod_name) { 'pod-1' }
+ let(:container_name) { 'container-1' }
+ let(:search) { 'foo -bar' }
+ let(:start_time) { '2019-01-02T12:13:14+02:00' }
+ let(:end_time) { '2019-01-03T12:13:14+02:00' }
+ let(:params) { {} }
+ let(:expected_logs) do
+ [
+ { message: "Log 1", timestamp: "2019-12-13T14:04:22.123456Z" },
+ { message: "Log 2", timestamp: "2019-12-13T14:04:23.123456Z" },
+ { message: "Log 3", timestamp: "2019-12-13T14:04:24.123456Z" }
+ ]
+ end
+
+ subject { described_class.new(cluster, namespace, params: params) }
+
+ describe '#check_times' do
+ context 'with start and end provided and valid' do
+ let(:params) do
+ {
+ 'start' => start_time,
+ 'end' => end_time
+ }
+ end
+
+ it 'returns success with times' do
+ result = subject.send(:check_times, {})
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:start]).to eq(start_time)
+ expect(result[:end]).to eq(end_time)
+ end
+ end
+
+ context 'with start and end not provided' do
+ let(:params) do
+ {}
+ end
+
+ it 'returns success with nothing else' do
+ result = subject.send(:check_times, {})
+
+ expect(result.keys.length).to eq(1)
+ expect(result[:status]).to eq(:success)
+ end
+ end
+
+ context 'with start valid and end invalid' do
+ let(:params) do
+ {
+ 'start' => start_time,
+ 'end' => 'invalid date'
+ }
+ end
+
+ it 'returns error' do
+ result = subject.send(:check_times, {})
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Invalid start or end time format')
+ end
+ end
+
+ context 'with start invalid and end valid' do
+ let(:params) do
+ {
+ 'start' => 'invalid date',
+ 'end' => end_time
+ }
+ end
+
+ it 'returns error' do
+ result = subject.send(:check_times, {})
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Invalid start or end time format')
+ end
+ end
+ end
+
+ describe '#check_search' do
+ context 'with search provided and valid' do
+ let(:params) do
+ {
+ 'search' => search
+ }
+ end
+
+ it 'returns success with search' do
+ result = subject.send(:check_search, {})
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:search]).to eq(search)
+ end
+ end
+
+ context 'with search not provided' do
+ let(:params) do
+ {}
+ end
+
+ it 'returns success with nothing else' do
+ result = subject.send(:check_search, {})
+
+ expect(result.keys.length).to eq(1)
+ expect(result[:status]).to eq(:success)
+ end
+ end
+ end
+
+ describe '#pod_logs' do
+ let(:result_arg) do
+ {
+ pod_name: pod_name,
+ container_name: container_name,
+ search: search,
+ start: start_time,
+ end: end_time
+ }
+ end
+
+ before do
+ create(:clusters_applications_elastic_stack, :installed, cluster: cluster)
+ end
+
+ it 'returns the logs' do
+ allow_any_instance_of(::Clusters::Applications::ElasticStack)
+ .to receive(:elasticsearch_client)
+ .and_return(Elasticsearch::Transport::Client.new)
+ allow_any_instance_of(::Gitlab::Elasticsearch::Logs)
+ .to receive(:pod_logs)
+ .with(namespace, pod_name, container_name, search, start_time, end_time)
+ .and_return(expected_logs)
+
+ result = subject.send(:pod_logs, result_arg)
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:logs]).to eq(expected_logs)
+ end
+
+ it 'returns an error when ES is unreachable' do
+ allow_any_instance_of(::Clusters::Applications::ElasticStack)
+ .to receive(:elasticsearch_client)
+ .and_return(nil)
+
+ result = subject.send(:pod_logs, result_arg)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Unable to connect to Elasticsearch')
+ end
+
+ it 'handles server errors from elasticsearch' do
+ allow_any_instance_of(::Clusters::Applications::ElasticStack)
+ .to receive(:elasticsearch_client)
+ .and_return(Elasticsearch::Transport::Client.new)
+ allow_any_instance_of(::Gitlab::Elasticsearch::Logs)
+ .to receive(:pod_logs)
+ .and_raise(Elasticsearch::Transport::Transport::Errors::ServiceUnavailable.new)
+
+ result = subject.send(:pod_logs, result_arg)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Elasticsearch returned status code: ServiceUnavailable')
+ end
+ end
+end
diff --git a/spec/services/pod_logs/kubernetes_service_spec.rb b/spec/services/pod_logs/kubernetes_service_spec.rb
new file mode 100644
index 00000000000..9fab88a14f6
--- /dev/null
+++ b/spec/services/pod_logs/kubernetes_service_spec.rb
@@ -0,0 +1,166 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::PodLogs::KubernetesService do
+ include KubernetesHelpers
+
+ let_it_be(:cluster) { create(:cluster, :provided_by_gcp, environment_scope: '*') }
+ let(:namespace) { 'autodevops-deploy-9-production' }
+
+ let(:pod_name) { 'pod-1' }
+ let(:container_name) { 'container-1' }
+ let(:params) { {} }
+
+ let(:raw_logs) do
+ "2019-12-13T14:04:22.123456Z Log 1\n2019-12-13T14:04:23.123456Z Log 2\n" \
+ "2019-12-13T14:04:24.123456Z Log 3"
+ end
+
+ subject { described_class.new(cluster, namespace, params: params) }
+
+ describe '#pod_logs' do
+ let(:result_arg) do
+ {
+ pod_name: pod_name,
+ container_name: container_name
+ }
+ end
+
+ let(:expected_logs) { raw_logs }
+ let(:service) { create(:cluster_platform_kubernetes, :configured) }
+
+ it 'returns the logs' do
+ stub_kubeclient_logs(pod_name, namespace, container: container_name)
+
+ result = subject.send(:pod_logs, result_arg)
+
+ expect(result[:status]).to eq(:success)
+ expect(result[:logs]).to eq(expected_logs)
+ end
+
+ it 'handles Not Found errors from k8s' do
+ allow_any_instance_of(Gitlab::Kubernetes::KubeClient)
+ .to receive(:get_pod_log)
+ .with(any_args)
+ .and_raise(Kubeclient::ResourceNotFoundError.new(404, 'Not Found', {}))
+
+ result = subject.send(:pod_logs, result_arg)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Pod not found')
+ end
+
+ it 'handles HTTP errors from k8s' do
+ allow_any_instance_of(Gitlab::Kubernetes::KubeClient)
+ .to receive(:get_pod_log)
+ .with(any_args)
+ .and_raise(Kubeclient::HttpError.new(500, 'Error', {}))
+
+ result = subject.send(:pod_logs, result_arg)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Kubernetes API returned status code: 500')
+ end
+ end
+
+ describe '#encode_logs_to_utf8', :aggregate_failures do
+ let(:service) { create(:cluster_platform_kubernetes, :configured) }
+ let(:expected_logs) { '2019-12-13T14:04:22.123456Z ✔ Started logging errors to Sentry' }
+ let(:raw_logs) { expected_logs.dup.force_encoding(Encoding::ASCII_8BIT) }
+ let(:result) { subject.send(:encode_logs_to_utf8, result_arg) }
+
+ let(:result_arg) do
+ {
+ pod_name: pod_name,
+ container_name: container_name,
+ logs: raw_logs
+ }
+ end
+
+ it 'converts logs to utf-8' do
+ expect(result[:status]).to eq(:success)
+ expect(result[:logs]).to eq(expected_logs)
+ end
+
+ it 'returns error if output of encoding helper is blank' do
+ allow(Gitlab::EncodingHelper).to receive(:encode_utf8).and_return('')
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Unable to convert Kubernetes logs encoding to UTF-8')
+ end
+
+ it 'returns error if output of encoding helper is nil' do
+ allow(Gitlab::EncodingHelper).to receive(:encode_utf8).and_return(nil)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Unable to convert Kubernetes logs encoding to UTF-8')
+ end
+
+ it 'returns error if output of encoding helper is not UTF-8' do
+ allow(Gitlab::EncodingHelper).to receive(:encode_utf8)
+ .and_return(expected_logs.encode(Encoding::UTF_16BE))
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq('Unable to convert Kubernetes logs encoding to UTF-8')
+ end
+
+ context 'when logs are nil' do
+ let(:raw_logs) { nil }
+ let(:expected_logs) { nil }
+
+ it 'returns nil' do
+ expect(result[:status]).to eq(:success)
+ expect(result[:logs]).to eq(expected_logs)
+ end
+ end
+
+ context 'when logs are blank' do
+ let(:raw_logs) { (+'').force_encoding(Encoding::ASCII_8BIT) }
+ let(:expected_logs) { '' }
+
+ it 'returns blank string' do
+ expect(result[:status]).to eq(:success)
+ expect(result[:logs]).to eq(expected_logs)
+ end
+ end
+
+ context 'when logs are already in utf-8' do
+ let(:raw_logs) { expected_logs }
+
+ it 'does not fail' do
+ expect(result[:status]).to eq(:success)
+ expect(result[:logs]).to eq(expected_logs)
+ end
+ end
+ end
+
+ describe '#split_logs' do
+ let(:service) { create(:cluster_platform_kubernetes, :configured) }
+
+ let(:expected_logs) do
+ [
+ { message: "Log 1", timestamp: "2019-12-13T14:04:22.123456Z" },
+ { message: "Log 2", timestamp: "2019-12-13T14:04:23.123456Z" },
+ { message: "Log 3", timestamp: "2019-12-13T14:04:24.123456Z" }
+ ]
+ end
+
+ let(:result_arg) do
+ {
+ pod_name: pod_name,
+ container_name: container_name,
+ logs: raw_logs
+ }
+ end
+
+ it 'returns the logs' do
+ result = subject.send(:split_logs, result_arg)
+
+ aggregate_failures do
+ expect(result[:status]).to eq(:success)
+ expect(result[:logs]).to eq(expected_logs)
+ end
+ end
+ end
+end