Commit ba5633c8 authored by Adrien Kohlbecker's avatar Adrien Kohlbecker Committed by Dylan Griffith

Add all pods view to log explorer

adds a new filter
parent 36a5f50e
...@@ -5,11 +5,13 @@ import { ...@@ -5,11 +5,13 @@ import {
GlSprintf, GlSprintf,
GlAlert, GlAlert,
GlDropdown, GlDropdown,
GlDropdownDivider,
GlDropdownItem, GlDropdownItem,
GlFormGroup, GlFormGroup,
GlSearchBoxByClick, GlSearchBoxByClick,
GlInfiniteScroll, GlInfiniteScroll,
} from '@gitlab/ui'; } from '@gitlab/ui';
import { s__ } from '~/locale';
import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue'; import DateTimePicker from '~/vue_shared/components/date_time_picker/date_time_picker.vue';
import LogControlButtons from './log_control_buttons.vue'; import LogControlButtons from './log_control_buttons.vue';
...@@ -22,6 +24,7 @@ export default { ...@@ -22,6 +24,7 @@ export default {
GlSprintf, GlSprintf,
GlAlert, GlAlert,
GlDropdown, GlDropdown,
GlDropdownDivider,
GlDropdownItem, GlDropdownItem,
GlFormGroup, GlFormGroup,
GlSearchBoxByClick, GlSearchBoxByClick,
...@@ -90,6 +93,16 @@ export default { ...@@ -90,6 +93,16 @@ export default {
shouldShowElasticStackCallout() { shouldShowElasticStackCallout() {
return !this.isElasticStackCalloutDismissed && this.disableAdvancedControls; return !this.isElasticStackCalloutDismissed && this.disableAdvancedControls;
}, },
podDropdownText() {
if (this.pods.current) {
return this.pods.current;
} else if (this.advancedFeaturesEnabled) {
// "All pods" is a valid option when advanced querying is available
return s__('Environments|All pods');
}
return s__('Environments|No pod selected');
},
}, },
mounted() { mounted() {
this.setInitData({ this.setInitData({
...@@ -178,11 +191,17 @@ export default { ...@@ -178,11 +191,17 @@ export default {
> >
<gl-dropdown <gl-dropdown
id="pods-dropdown" id="pods-dropdown"
:text="pods.current || s__('Environments|No pods to display')" :text="podDropdownText"
:disabled="environments.isLoading" :disabled="environments.isLoading"
class="d-flex gl-h-32 js-pods-dropdown" class="d-flex gl-h-32 js-pods-dropdown"
toggle-class="dropdown-menu-toggle" toggle-class="dropdown-menu-toggle"
> >
<template v-if="advancedFeaturesEnabled">
<gl-dropdown-item key="all-pods" @click="showPodLogs(null)">
{{ s__('Environments|All pods') }}
</gl-dropdown-item>
<gl-dropdown-divider />
</template>
<gl-dropdown-item <gl-dropdown-item
v-for="podName in pods.options" v-for="podName in pods.options"
:key="podName" :key="podName"
......
...@@ -82,7 +82,6 @@ export const setTimeRange = ({ dispatch, commit }, timeRange) => { ...@@ -82,7 +82,6 @@ export const setTimeRange = ({ dispatch, commit }, timeRange) => {
export const showEnvironment = ({ dispatch, commit }, environmentName) => { export const showEnvironment = ({ dispatch, commit }, environmentName) => {
commit(types.SET_PROJECT_ENVIRONMENT, environmentName); commit(types.SET_PROJECT_ENVIRONMENT, environmentName);
commit(types.SET_CURRENT_POD_NAME, null);
dispatch('fetchLogs'); dispatch('fetchLogs');
}; };
...@@ -107,16 +106,16 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => { ...@@ -107,16 +106,16 @@ export const fetchEnvironments = ({ commit, dispatch }, environmentsPath) => {
}; };
export const fetchLogs = ({ commit, state }) => { export const fetchLogs = ({ commit, state }) => {
commit(types.REQUEST_PODS_DATA);
commit(types.REQUEST_LOGS_DATA); commit(types.REQUEST_LOGS_DATA);
return requestLogsUntilData(state) return requestLogsUntilData(state)
.then(({ data }) => { .then(({ data }) => {
const { pod_name, pods, logs, cursor } = data; const { pod_name, pods, logs, cursor } = data;
commit(types.RECEIVE_LOGS_DATA_SUCCESS, { logs, cursor });
commit(types.SET_CURRENT_POD_NAME, pod_name); commit(types.SET_CURRENT_POD_NAME, pod_name);
commit(types.RECEIVE_PODS_DATA_SUCCESS, pods); commit(types.RECEIVE_PODS_DATA_SUCCESS, pods);
commit(types.RECEIVE_LOGS_DATA_SUCCESS, { logs, cursor });
}) })
.catch(() => { .catch(() => {
commit(types.RECEIVE_PODS_DATA_ERROR); commit(types.RECEIVE_PODS_DATA_ERROR);
......
import { formatDate } from '../utils'; import { formatDate } from '../utils';
const mapTrace = ({ timestamp = null, message = '' }) => const mapTrace = ({ timestamp = null, pod = '', message = '' }) =>
[timestamp ? formatDate(timestamp) : '', message].join(' | '); [timestamp ? formatDate(timestamp) : '', pod, message].join(' | ');
export const trace = state => state.logs.lines.map(mapTrace).join('\n'); export const trace = state => state.logs.lines.map(mapTrace).join('\n');
......
...@@ -14,6 +14,5 @@ export const REQUEST_LOGS_DATA_PREPEND = 'REQUEST_LOGS_DATA_PREPEND'; ...@@ -14,6 +14,5 @@ export const REQUEST_LOGS_DATA_PREPEND = 'REQUEST_LOGS_DATA_PREPEND';
export const RECEIVE_LOGS_DATA_PREPEND_SUCCESS = 'RECEIVE_LOGS_DATA_PREPEND_SUCCESS'; export const RECEIVE_LOGS_DATA_PREPEND_SUCCESS = 'RECEIVE_LOGS_DATA_PREPEND_SUCCESS';
export const RECEIVE_LOGS_DATA_PREPEND_ERROR = 'RECEIVE_LOGS_DATA_PREPEND_ERROR'; export const RECEIVE_LOGS_DATA_PREPEND_ERROR = 'RECEIVE_LOGS_DATA_PREPEND_ERROR';
export const REQUEST_PODS_DATA = 'REQUEST_PODS_DATA';
export const RECEIVE_PODS_DATA_SUCCESS = 'RECEIVE_PODS_DATA_SUCCESS'; export const RECEIVE_PODS_DATA_SUCCESS = 'RECEIVE_PODS_DATA_SUCCESS';
export const RECEIVE_PODS_DATA_ERROR = 'RECEIVE_PODS_DATA_ERROR'; export const RECEIVE_PODS_DATA_ERROR = 'RECEIVE_PODS_DATA_ERROR';
import * as types from './mutation_types'; import * as types from './mutation_types';
import { convertToFixedRange } from '~/lib/utils/datetime_range'; import { convertToFixedRange } from '~/lib/utils/datetime_range';
const mapLine = ({ timestamp, message }) => ({ const mapLine = ({ timestamp, pod, message }) => ({
timestamp, timestamp,
pod,
message, message,
}); });
...@@ -21,6 +22,10 @@ export default { ...@@ -21,6 +22,10 @@ export default {
// Environments Data // Environments Data
[types.SET_PROJECT_ENVIRONMENT](state, environmentName) { [types.SET_PROJECT_ENVIRONMENT](state, environmentName) {
state.environments.current = environmentName; state.environments.current = environmentName;
// Clear current pod options
state.pods.current = null;
state.pods.options = [];
}, },
[types.REQUEST_ENVIRONMENTS_DATA](state) { [types.REQUEST_ENVIRONMENTS_DATA](state) {
state.environments.options = []; state.environments.options = [];
...@@ -81,9 +86,6 @@ export default { ...@@ -81,9 +86,6 @@ export default {
[types.SET_CURRENT_POD_NAME](state, podName) { [types.SET_CURRENT_POD_NAME](state, podName) {
state.pods.current = podName; state.pods.current = podName;
}, },
[types.REQUEST_PODS_DATA](state) {
state.pods.options = [];
},
[types.RECEIVE_PODS_DATA_SUCCESS](state, podOptions) { [types.RECEIVE_PODS_DATA_SUCCESS](state, podOptions) {
state.pods.options = podOptions; state.pods.options = podOptions;
}, },
......
...@@ -55,22 +55,10 @@ module PodLogs ...@@ -55,22 +55,10 @@ module PodLogs
return error(_('Cluster does not exist')) if cluster.nil? return error(_('Cluster does not exist')) if cluster.nil?
return error(_('Namespace is empty')) if namespace.blank? return error(_('Namespace is empty')) if namespace.blank?
success(result) result[:pod_name] = params['pod_name'].presence
end result[:container_name] = params['container_name'].presence
def check_param_lengths(_result)
pod_name = params['pod_name'].presence
container_name = params['container_name'].presence
if pod_name&.length.to_i > K8S_NAME_MAX_LENGTH success(result)
return error(_('pod_name cannot be larger than %{max_length}'\
' chars' % { max_length: K8S_NAME_MAX_LENGTH }))
elsif container_name&.length.to_i > K8S_NAME_MAX_LENGTH
return error(_('container_name cannot be larger than'\
' %{max_length} chars' % { max_length: K8S_NAME_MAX_LENGTH }))
end
success(pod_name: pod_name, container_name: container_name)
end end
def get_raw_pods(result) def get_raw_pods(result)
...@@ -85,40 +73,6 @@ module PodLogs ...@@ -85,40 +73,6 @@ module PodLogs
success(result) success(result)
end end
def check_pod_name(result)
# If pod_name is not received as parameter, get the pod logs of the first
# pod of this namespace.
result[:pod_name] ||= result[:pods].first
unless result[:pod_name]
return error(_('No pods available'))
end
unless result[:pods].include?(result[:pod_name])
return error(_('Pod does not exist'))
end
success(result)
end
def check_container_name(result)
pod_details = result[:raw_pods].first { |p| p.metadata.name == result[:pod_name] }
containers = pod_details.spec.containers.map(&:name)
# select first container if not specified
result[:container_name] ||= containers.first
unless result[:container_name]
return error(_('No containers available'))
end
unless containers.include?(result[:container_name])
return error(_('Container does not exist'))
end
success(result)
end
def pod_logs(result) def pod_logs(result)
raise NotImplementedError raise NotImplementedError
end end
......
...@@ -3,11 +3,8 @@ ...@@ -3,11 +3,8 @@
module PodLogs module PodLogs
class ElasticsearchService < PodLogs::BaseService class ElasticsearchService < PodLogs::BaseService
steps :check_arguments, steps :check_arguments,
:check_param_lengths,
:get_raw_pods, :get_raw_pods,
:get_pod_names, :get_pod_names,
:check_pod_name,
:check_container_name,
:check_times, :check_times,
:check_search, :check_search,
:check_cursor, :check_cursor,
...@@ -53,7 +50,7 @@ module PodLogs ...@@ -53,7 +50,7 @@ module PodLogs
response = ::Gitlab::Elasticsearch::Logs.new(client).pod_logs( response = ::Gitlab::Elasticsearch::Logs.new(client).pod_logs(
namespace, namespace,
result[:pod_name], pod_name: result[:pod_name],
container_name: result[:container_name], container_name: result[:container_name],
search: result[:search], search: result[:search],
start_time: result[:start], start_time: result[:start],
......
...@@ -8,7 +8,6 @@ module PodLogs ...@@ -8,7 +8,6 @@ module PodLogs
EncodingHelperError = Class.new(StandardError) EncodingHelperError = Class.new(StandardError)
steps :check_arguments, steps :check_arguments,
:check_param_lengths,
:get_raw_pods, :get_raw_pods,
:get_pod_names, :get_pod_names,
:check_pod_name, :check_pod_name,
...@@ -22,6 +21,50 @@ module PodLogs ...@@ -22,6 +21,50 @@ module PodLogs
private private
def check_pod_name(result)
# If pod_name is not received as parameter, get the pod logs of the first
# pod of this namespace.
result[:pod_name] ||= result[:pods].first
unless result[:pod_name]
return error(_('No pods available'))
end
unless result[:pod_name].length.to_i <= K8S_NAME_MAX_LENGTH
return error(_('pod_name cannot be larger than %{max_length}'\
' chars' % { max_length: K8S_NAME_MAX_LENGTH }))
end
unless result[:pods].include?(result[:pod_name])
return error(_('Pod does not exist'))
end
success(result)
end
def check_container_name(result)
pod_details = result[:raw_pods].first { |p| p.metadata.name == result[:pod_name] }
containers = pod_details.spec.containers.map(&:name)
# select first container if not specified
result[:container_name] ||= containers.first
unless result[:container_name]
return error(_('No containers available'))
end
unless result[:container_name].length.to_i <= K8S_NAME_MAX_LENGTH
return error(_('container_name cannot be larger than'\
' %{max_length} chars' % { max_length: K8S_NAME_MAX_LENGTH }))
end
unless containers.include?(result[:container_name])
return error(_('Container does not exist'))
end
success(result)
end
def pod_logs(result) def pod_logs(result)
result[:logs] = cluster.kubeclient.get_pod_log( result[:logs] = cluster.kubeclient.get_pod_log(
result[:pod_name], result[:pod_name],
...@@ -62,7 +105,8 @@ module PodLogs ...@@ -62,7 +105,8 @@ module PodLogs
values = line.split(' ', 2) values = line.split(' ', 2)
{ {
timestamp: values[0], timestamp: values[0],
message: values[1] message: values[1],
pod: result[:pod_name]
} }
end end
......
---
title: Add all pods view to logs explorer
merge_request: 26883
author:
type: added
...@@ -57,7 +57,7 @@ describe 'Environment > Pod Logs', :js do ...@@ -57,7 +57,7 @@ describe 'Environment > Pod Logs', :js do
expect(item.text).to eq(pod_names[i]) expect(item.text).to eq(pod_names[i])
end end
end end
expect(page).to have_content("Dec 13 14:04:22.123Z | Log 1 Dec 13 14:04:23.123Z | Log 2 Dec 13 14:04:24.123Z | Log 3") expect(page).to have_content("Dec 13 14:04:22.123Z | kube-pod | Log 1 Dec 13 14:04:23.123Z | kube-pod | Log 2 Dec 13 14:04:24.123Z | kube-pod | Log 3")
end end
end end
end end
...@@ -12,7 +12,7 @@ module Gitlab ...@@ -12,7 +12,7 @@ module Gitlab
@client = client @client = client
end end
def pod_logs(namespace, pod_name, container_name: nil, search: nil, start_time: nil, end_time: nil, cursor: nil) def pod_logs(namespace, pod_name: nil, container_name: nil, search: nil, start_time: nil, end_time: nil, cursor: nil)
query = { bool: { must: [] } }.tap do |q| query = { bool: { must: [] } }.tap do |q|
filter_pod_name(q, pod_name) filter_pod_name(q, pod_name)
filter_namespace(q, namespace) filter_namespace(q, namespace)
...@@ -38,7 +38,7 @@ module Gitlab ...@@ -38,7 +38,7 @@ module Gitlab
{ "offset": { order: :desc } } { "offset": { order: :desc } }
], ],
# only return these fields in the response # only return these fields in the response
_source: ["@timestamp", "message"], _source: ["@timestamp", "message", "kubernetes.pod.name"],
# fixed limit for now, we should support paginated queries # fixed limit for now, we should support paginated queries
size: ::Gitlab::Elasticsearch::Logs::LOGS_LIMIT size: ::Gitlab::Elasticsearch::Logs::LOGS_LIMIT
} }
...@@ -51,6 +51,9 @@ module Gitlab ...@@ -51,6 +51,9 @@ module Gitlab
end end
def filter_pod_name(query, pod_name) def filter_pod_name(query, pod_name)
# We can filter by "all pods" with a null pod_name
return if pod_name.nil?
query[:bool][:must] << { query[:bool][:must] << {
match_phrase: { match_phrase: {
"kubernetes.pod.name" => { "kubernetes.pod.name" => {
...@@ -113,7 +116,8 @@ module Gitlab ...@@ -113,7 +116,8 @@ module Gitlab
results = results.map do |hit| results = results.map do |hit|
{ {
timestamp: hit["_source"]["@timestamp"], timestamp: hit["_source"]["@timestamp"],
message: hit["_source"]["message"] message: hit["_source"]["message"],
pod: hit["_source"]["kubernetes"]["pod"]["name"]
} }
end end
......
...@@ -7651,6 +7651,9 @@ msgstr "" ...@@ -7651,6 +7651,9 @@ msgstr ""
msgid "EnvironmentsDashboard|This dashboard displays a maximum of 7 projects and 3 environments per project. %{readMoreLink}" msgid "EnvironmentsDashboard|This dashboard displays a maximum of 7 projects and 3 environments per project. %{readMoreLink}"
msgstr "" msgstr ""
msgid "Environments|All pods"
msgstr ""
msgid "Environments|An error occurred while canceling the auto stop, please try again" msgid "Environments|An error occurred while canceling the auto stop, please try again"
msgstr "" msgstr ""
...@@ -7732,7 +7735,7 @@ msgstr "" ...@@ -7732,7 +7735,7 @@ msgstr ""
msgid "Environments|No deployments yet" msgid "Environments|No deployments yet"
msgstr "" msgstr ""
msgid "Environments|No pods to display" msgid "Environments|No pod selected"
msgstr "" msgstr ""
msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file." msgid "Environments|Note that this action will stop the environment, but it will %{emphasisStart}not%{emphasisEnd} have an effect on any existing deployment due to no “stop environment action” being defined in the %{ciConfigLinkStart}.gitlab-ci.yml%{ciConfigLinkEnd} file."
......
...@@ -19,7 +19,12 @@ ...@@ -19,7 +19,12 @@
"_score": null, "_score": null,
"_source": { "_source": {
"message": "10.8.2.1 - - [25/Oct/2019:08:03:22 UTC] \"GET / HTTP/1.1\" 200 13", "message": "10.8.2.1 - - [25/Oct/2019:08:03:22 UTC] \"GET / HTTP/1.1\" 200 13",
"@timestamp": "2019-12-13T14:35:34.034Z" "@timestamp": "2019-12-13T14:35:34.034Z",
"kubernetes": {
"pod": {
"name": "production-6866bc8974-m4sk4"
}
}
}, },
"sort": [ "sort": [
9999998, 9999998,
...@@ -33,7 +38,12 @@ ...@@ -33,7 +38,12 @@
"_score": null, "_score": null,
"_source": { "_source": {
"message": "10.8.2.1 - - [27/Oct/2019:23:49:54 UTC] \"GET / HTTP/1.1\" 200 13", "message": "10.8.2.1 - - [27/Oct/2019:23:49:54 UTC] \"GET / HTTP/1.1\" 200 13",
"@timestamp": "2019-12-13T14:35:35.034Z" "@timestamp": "2019-12-13T14:35:35.034Z",
"kubernetes": {
"pod": {
"name": "production-6866bc8974-m4sk4"
}
}
}, },
"sort": [ "sort": [
9999949, 9999949,
...@@ -47,7 +57,12 @@ ...@@ -47,7 +57,12 @@
"_score": null, "_score": null,
"_source": { "_source": {
"message": "10.8.2.1 - - [04/Nov/2019:23:09:24 UTC] \"GET / HTTP/1.1\" 200 13", "message": "10.8.2.1 - - [04/Nov/2019:23:09:24 UTC] \"GET / HTTP/1.1\" 200 13",
"@timestamp": "2019-12-13T14:35:36.034Z" "@timestamp": "2019-12-13T14:35:36.034Z",
"kubernetes": {
"pod": {
"name": "production-6866bc8974-m4sk4"
}
}
}, },
"sort": [ "sort": [
9999944, 9999944,
...@@ -61,7 +76,12 @@ ...@@ -61,7 +76,12 @@
"_score": null, "_score": null,
"_source": { "_source": {
"message": "- -\u003e /", "message": "- -\u003e /",
"@timestamp": "2019-12-13T14:35:37.034Z" "@timestamp": "2019-12-13T14:35:37.034Z",
"kubernetes": {
"pod": {
"name": "production-6866bc8974-m4sk4"
}
}
}, },
"sort": [ "sort": [
9999934, 9999934,
......
...@@ -33,7 +33,8 @@ ...@@ -33,7 +33,8 @@
], ],
"_source": [ "_source": [
"@timestamp", "@timestamp",
"message" "message",
"kubernetes.pod.name"
], ],
"size": 500 "size": 500
} }
...@@ -40,7 +40,8 @@ ...@@ -40,7 +40,8 @@
], ],
"_source": [ "_source": [
"@timestamp", "@timestamp",
"message" "message",
"kubernetes.pod.name"
], ],
"size": 500 "size": 500
} }
...@@ -37,7 +37,8 @@ ...@@ -37,7 +37,8 @@
], ],
"_source": [ "_source": [
"@timestamp", "@timestamp",
"message" "message",
"kubernetes.pod.name"
], ],
"size": 500 "size": 500
} }
...@@ -42,7 +42,8 @@ ...@@ -42,7 +42,8 @@
], ],
"_source": [ "_source": [
"@timestamp", "@timestamp",
"message" "message",
"kubernetes.pod.name"
], ],
"size": 500 "size": 500
} }
...@@ -42,7 +42,8 @@ ...@@ -42,7 +42,8 @@
], ],
"_source": [ "_source": [
"@timestamp", "@timestamp",
"message" "message",
"kubernetes.pod.name"
], ],
"size": 500 "size": 500
} }
...@@ -42,7 +42,8 @@ ...@@ -42,7 +42,8 @@
], ],
"_source": [ "_source": [
"@timestamp", "@timestamp",
"message" "message",
"kubernetes.pod.name"
], ],
"size": 500 "size": 500
} }
...@@ -43,7 +43,8 @@ ...@@ -43,7 +43,8 @@
], ],
"_source": [ "_source": [
"@timestamp", "@timestamp",
"message" "message",
"kubernetes.pod.name"
], ],
"size": 500 "size": 500
} }
...@@ -300,9 +300,10 @@ describe('EnvironmentLogs', () => { ...@@ -300,9 +300,10 @@ describe('EnvironmentLogs', () => {
const items = findPodsDropdown().findAll(GlDropdownItem); const items = findPodsDropdown().findAll(GlDropdownItem);
expect(findPodsDropdown().props('text')).toBe(mockPodName); expect(findPodsDropdown().props('text')).toBe(mockPodName);
expect(items.length).toBe(mockPods.length); expect(items.length).toBe(mockPods.length + 1);
expect(items.at(0).text()).toBe('All pods');
mockPods.forEach((pod, i) => { mockPods.forEach((pod, i) => {
const item = items.at(i); const item = items.at(i + 1);
expect(item.text()).toBe(pod); expect(item.text()).toBe(pod);
}); });
}); });
...@@ -345,7 +346,7 @@ describe('EnvironmentLogs', () => { ...@@ -345,7 +346,7 @@ describe('EnvironmentLogs', () => {
expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything()); expect(dispatch).not.toHaveBeenCalledWith(`${module}/showPodLogs`, expect.anything());
items.at(index).vm.$emit('click'); items.at(index + 1).vm.$emit('click');
expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]); expect(dispatch).toHaveBeenCalledWith(`${module}/showPodLogs`, mockPods[index]);
}); });
......
...@@ -32,15 +32,93 @@ export const mockPods = [ ...@@ -32,15 +32,93 @@ export const mockPods = [
]; ];
export const mockLogsResult = [ export const mockLogsResult = [
{ timestamp: '2019-12-13T13:43:18.2760123Z', message: 'Log 1' }, {
{ timestamp: '2019-12-13T13:43:18.2760123Z', message: 'Log 2' }, timestamp: '2019-12-13T13:43:18.2760123Z',
{ timestamp: '2019-12-13T13:43:26.8420123Z', message: 'Log 3' }, message: '10.36.0.1 - - [16/Oct/2019:06:29:48 UTC] "GET / HTTP/1.1" 200 13',
pod: 'foo',
},
{
timestamp: '2019-12-13T13:43:18.2760123Z',
message: '- -> /',
pod: 'bar',
},
{
timestamp: '2019-12-13T13:43:26.8420123Z',
message: '10.36.0.1 - - [16/Oct/2019:06:29:57 UTC] "GET / HTTP/1.1" 200 13',
pod: 'foo',
},
{
timestamp: '2019-12-13T13:43:26.8420123Z',
message: '- -> /',
pod: 'bar',
},
{
timestamp: '2019-12-13T13:43:28.3710123Z',
message: '10.36.0.1 - - [16/Oct/2019:06:29:58 UTC] "GET / HTTP/1.1" 200 13',
pod: 'foo',
},
{
timestamp: '2019-12-13T13:43:28.3710123Z',
message: '- -> /',
pod: 'bar',
},
{
timestamp: '2019-12-13T13:43:36.8860123Z',
message: '10.36.0.1 - - [16/Oct/2019:06:30:07 UTC] "GET / HTTP/1.1" 200 13',
pod: 'foo',
},
{
timestamp: '2019-12-13T13:43:36.8860123Z',
message: '- -> /',
pod: 'bar',
},
{
timestamp: '2019-12-13T13:43:38.4000123Z',
message: '10.36.0.1 - - [16/Oct/2019:06:30:08 UTC] "GET / HTTP/1.1" 200 13',
pod: 'foo',
},
{
timestamp: '2019-12-13T13:43:38.4000123Z',
message: '- -> /',
pod: 'bar',
},
{
timestamp: '2019-12-13T13:43:46.8420123Z',
message: '10.36.0.1 - - [16/Oct/2019:06:30:17 UTC] "GET / HTTP/1.1" 200 13',
pod: 'foo',
},
{
timestamp: '2019-12-13T13:43:46.8430123Z',
message: '- -> /',
pod: 'bar',
},
{
timestamp: '2019-12-13T13:43:48.3240123Z',
message: '10.36.0.1 - - [16/Oct/2019:06:30:18 UTC] "GET / HTTP/1.1" 200 13',
pod: 'foo',
},
{
timestamp: '2019-12-13T13:43:48.3250123Z',
message: '- -> /',
pod: 'bar',
},
]; ];
export const mockTrace = [ export const mockTrace = [
'Dec 13 13:43:18.276Z | Log 1', 'Dec 13 13:43:18.276Z | foo | 10.36.0.1 - - [16/Oct/2019:06:29:48 UTC] "GET / HTTP/1.1" 200 13',
'Dec 13 13:43:18.276Z | Log 2', 'Dec 13 13:43:18.276Z | bar | - -> /',
'Dec 13 13:43:26.842Z | Log 3', 'Dec 13 13:43:26.842Z | foo | 10.36.0.1 - - [16/Oct/2019:06:29:57 UTC] "GET / HTTP/1.1" 200 13',
'Dec 13 13:43:26.842Z | bar | - -> /',
'Dec 13 13:43:28.371Z | foo | 10.36.0.1 - - [16/Oct/2019:06:29:58 UTC] "GET / HTTP/1.1" 200 13',
'Dec 13 13:43:28.371Z | bar | - -> /',
'Dec 13 13:43:36.886Z | foo | 10.36.0.1 - - [16/Oct/2019:06:30:07 UTC] "GET / HTTP/1.1" 200 13',
'Dec 13 13:43:36.886Z | bar | - -> /',
'Dec 13 13:43:38.400Z | foo | 10.36.0.1 - - [16/Oct/2019:06:30:08 UTC] "GET / HTTP/1.1" 200 13',
'Dec 13 13:43:38.400Z | bar | - -> /',
'Dec 13 13:43:46.842Z | foo | 10.36.0.1 - - [16/Oct/2019:06:30:17 UTC] "GET / HTTP/1.1" 200 13',
'Dec 13 13:43:46.843Z | bar | - -> /',
'Dec 13 13:43:48.324Z | foo | 10.36.0.1 - - [16/Oct/2019:06:30:18 UTC] "GET / HTTP/1.1" 200 13',
'Dec 13 13:43:48.325Z | bar | - -> /',
]; ];
export const mockResponse = { export const mockResponse = {
......
...@@ -172,14 +172,13 @@ describe('Logs Store actions', () => { ...@@ -172,14 +172,13 @@ describe('Logs Store actions', () => {
describe('fetchLogs', () => { describe('fetchLogs', () => {
beforeEach(() => { beforeEach(() => {
expectedMutations = [ expectedMutations = [
{ type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA }, { type: types.REQUEST_LOGS_DATA },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
{ {
type: types.RECEIVE_LOGS_DATA_SUCCESS, type: types.RECEIVE_LOGS_DATA_SUCCESS,
payload: { logs: mockLogsResult, cursor: mockNextCursor }, payload: { logs: mockLogsResult, cursor: mockNextCursor },
}, },
{ type: types.SET_CURRENT_POD_NAME, payload: mockPodName },
{ type: types.RECEIVE_PODS_DATA_SUCCESS, payload: mockPods },
]; ];
expectedActions = []; expectedActions = [];
...@@ -364,7 +363,6 @@ describe('Logs Store actions', () => { ...@@ -364,7 +363,6 @@ describe('Logs Store actions', () => {
null, null,
state, state,
[ [
{ type: types.REQUEST_PODS_DATA },
{ type: types.REQUEST_LOGS_DATA }, { type: types.REQUEST_LOGS_DATA },
{ type: types.RECEIVE_PODS_DATA_ERROR }, { type: types.RECEIVE_PODS_DATA_ERROR },
{ type: types.RECEIVE_LOGS_DATA_ERROR }, { type: types.RECEIVE_LOGS_DATA_ERROR },
......
...@@ -223,17 +223,6 @@ describe('Logs Store Mutations', () => { ...@@ -223,17 +223,6 @@ describe('Logs Store Mutations', () => {
}); });
}); });
describe('REQUEST_PODS_DATA', () => {
it('receives pods data', () => {
mutations[types.REQUEST_PODS_DATA](state);
expect(state.pods).toEqual(
expect.objectContaining({
options: [],
}),
);
});
});
describe('RECEIVE_PODS_DATA_SUCCESS', () => { describe('RECEIVE_PODS_DATA_SUCCESS', () => {
it('receives pods data success', () => { it('receives pods data success', () => {
mutations[types.RECEIVE_PODS_DATA_SUCCESS](state, mockPods); mutations[types.RECEIVE_PODS_DATA_SUCCESS](state, mockPods);
......
...@@ -5,10 +5,10 @@ require 'spec_helper' ...@@ -5,10 +5,10 @@ require 'spec_helper'
describe Gitlab::Elasticsearch::Logs do describe Gitlab::Elasticsearch::Logs do
let(:client) { Elasticsearch::Transport::Client } let(:client) { Elasticsearch::Transport::Client }
let(:es_message_1) { { timestamp: "2019-12-13T14:35:34.034Z", message: "10.8.2.1 - - [25/Oct/2019:08:03:22 UTC] \"GET / HTTP/1.1\" 200 13" } } let(:es_message_1) { { timestamp: "2019-12-13T14:35:34.034Z", pod: "production-6866bc8974-m4sk4", message: "10.8.2.1 - - [25/Oct/2019:08:03:22 UTC] \"GET / HTTP/1.1\" 200 13" } }
let(:es_message_2) { { timestamp: "2019-12-13T14:35:35.034Z", message: "10.8.2.1 - - [27/Oct/2019:23:49:54 UTC] \"GET / HTTP/1.1\" 200 13" } } let(:es_message_2) { { timestamp: "2019-12-13T14:35:35.034Z", pod: "production-6866bc8974-m4sk4", message: "10.8.2.1 - - [27/Oct/2019:23:49:54 UTC] \"GET / HTTP/1.1\" 200 13" } }
let(:es_message_3) { { timestamp: "2019-12-13T14:35:36.034Z", message: "10.8.2.1 - - [04/Nov/2019:23:09:24 UTC] \"GET / HTTP/1.1\" 200 13" } } let(:es_message_3) { { timestamp: "2019-12-13T14:35:36.034Z", pod: "production-6866bc8974-m4sk4", message: "10.8.2.1 - - [04/Nov/2019:23:09:24 UTC] \"GET / HTTP/1.1\" 200 13" } }
let(:es_message_4) { { timestamp: "2019-12-13T14:35:37.034Z", message: "- -\u003e /" } } let(:es_message_4) { { timestamp: "2019-12-13T14:35:37.034Z", pod: "production-6866bc8974-m4sk4", message: "- -\u003e /" } }
let(:es_response) { JSON.parse(fixture_file('lib/elasticsearch/logs_response.json')) } let(:es_response) { JSON.parse(fixture_file('lib/elasticsearch/logs_response.json')) }
...@@ -40,49 +40,49 @@ describe Gitlab::Elasticsearch::Logs do ...@@ -40,49 +40,49 @@ describe Gitlab::Elasticsearch::Logs do
it 'returns the logs as an array' do it 'returns the logs as an array' do
expect(client).to receive(:search).with(body: a_hash_equal_to_json(body)).and_return(es_response) expect(client).to receive(:search).with(body: a_hash_equal_to_json(body)).and_return(es_response)
result = subject.pod_logs(namespace, pod_name) result = subject.pod_logs(namespace, pod_name: pod_name)
expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor) expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
end end
it 'can further filter the logs by container name' do it 'can further filter the logs by container name' do
expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_container)).and_return(es_response) expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_container)).and_return(es_response)
result = subject.pod_logs(namespace, pod_name, container_name: container_name) result = subject.pod_logs(namespace, pod_name: pod_name, container_name: container_name)
expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor) expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
end end
it 'can further filter the logs by search' do it 'can further filter the logs by search' do
expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_search)).and_return(es_response) expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_search)).and_return(es_response)
result = subject.pod_logs(namespace, pod_name, search: search) result = subject.pod_logs(namespace, pod_name: pod_name, search: search)
expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor) expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
end end
it 'can further filter the logs by start_time and end_time' do it 'can further filter the logs by start_time and end_time' do
expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_times)).and_return(es_response) expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_times)).and_return(es_response)
result = subject.pod_logs(namespace, pod_name, start_time: start_time, end_time: end_time) result = subject.pod_logs(namespace, pod_name: pod_name, start_time: start_time, end_time: end_time)
expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor) expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
end end
it 'can further filter the logs by only start_time' do it 'can further filter the logs by only start_time' do
expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_start_time)).and_return(es_response) expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_start_time)).and_return(es_response)
result = subject.pod_logs(namespace, pod_name, start_time: start_time) result = subject.pod_logs(namespace, pod_name: pod_name, start_time: start_time)
expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor) expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
end end
it 'can further filter the logs by only end_time' do it 'can further filter the logs by only end_time' do
expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_end_time)).and_return(es_response) expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_end_time)).and_return(es_response)
result = subject.pod_logs(namespace, pod_name, end_time: end_time) result = subject.pod_logs(namespace, pod_name: pod_name, end_time: end_time)
expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor) expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
end end
it 'can search after a cursor' do it 'can search after a cursor' do
expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_cursor)).and_return(es_response) expect(client).to receive(:search).with(body: a_hash_equal_to_json(body_with_cursor)).and_return(es_response)
result = subject.pod_logs(namespace, pod_name, cursor: cursor) result = subject.pod_logs(namespace, pod_name: pod_name, cursor: cursor)
expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor) expect(result).to eq(logs: [es_message_4, es_message_3, es_message_2, es_message_1], cursor: cursor)
end end
end end
......
...@@ -78,9 +78,7 @@ describe ::PodLogs::BaseService do ...@@ -78,9 +78,7 @@ describe ::PodLogs::BaseService do
expect(result[:message]).to eq('Namespace is empty') expect(result[:message]).to eq('Namespace is empty')
end end
end end
end
describe '#check_param_lengths' do
context 'when pod_name and container_name are provided' do context 'when pod_name and container_name are provided' do
let(:params) do let(:params) do
{ {
...@@ -90,43 +88,13 @@ describe ::PodLogs::BaseService do ...@@ -90,43 +88,13 @@ describe ::PodLogs::BaseService do
end end
it 'returns success' do it 'returns success' do
result = subject.send(:check_param_lengths, {}) result = subject.send(:check_arguments, {})
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
expect(result[:pod_name]).to eq(pod_name) expect(result[:pod_name]).to eq(pod_name)
expect(result[:container_name]).to eq(container_name) expect(result[:container_name]).to eq(container_name)
end end
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 end
describe '#get_raw_pods' do describe '#get_raw_pods' do
...@@ -150,80 +118,4 @@ describe ::PodLogs::BaseService do ...@@ -150,80 +118,4 @@ describe ::PodLogs::BaseService do
expect(result[:pods]).to eq([pod_name]) expect(result[:pods]).to eq([pod_name])
end end
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 end
...@@ -170,7 +170,7 @@ describe ::PodLogs::ElasticsearchService do ...@@ -170,7 +170,7 @@ describe ::PodLogs::ElasticsearchService do
.and_return(Elasticsearch::Transport::Client.new) .and_return(Elasticsearch::Transport::Client.new)
allow_any_instance_of(::Gitlab::Elasticsearch::Logs) allow_any_instance_of(::Gitlab::Elasticsearch::Logs)
.to receive(:pod_logs) .to receive(:pod_logs)
.with(namespace, pod_name, container_name: container_name, search: search, start_time: start_time, end_time: end_time, cursor: cursor) .with(namespace, pod_name: pod_name, container_name: container_name, search: search, start_time: start_time, end_time: end_time, cursor: cursor)
.and_return({ logs: expected_logs, cursor: expected_cursor }) .and_return({ logs: expected_logs, cursor: expected_cursor })
result = subject.send(:pod_logs, result_arg) result = subject.send(:pod_logs, result_arg)
......
...@@ -9,13 +9,18 @@ describe ::PodLogs::KubernetesService do ...@@ -9,13 +9,18 @@ describe ::PodLogs::KubernetesService do
let(:namespace) { 'autodevops-deploy-9-production' } let(:namespace) { 'autodevops-deploy-9-production' }
let(:pod_name) { 'pod-1' } let(:pod_name) { 'pod-1' }
let(:container_name) { 'container-1' } let(:container_name) { 'container-0' }
let(:params) { {} } let(:params) { {} }
let(:raw_logs) do 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:22.123456Z Log 1\n2019-12-13T14:04:23.123456Z Log 2\n" \
"2019-12-13T14:04:24.123456Z Log 3" "2019-12-13T14:04:24.123456Z Log 3"
end end
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) } subject { described_class.new(cluster, namespace, params: params) }
...@@ -140,9 +145,9 @@ describe ::PodLogs::KubernetesService do ...@@ -140,9 +145,9 @@ describe ::PodLogs::KubernetesService do
let(:expected_logs) do let(:expected_logs) do
[ [
{ message: "Log 1", timestamp: "2019-12-13T14:04:22.123456Z" }, { message: "Log 1", pod: 'pod-1', timestamp: "2019-12-13T14:04:22.123456Z" },
{ message: "Log 2", timestamp: "2019-12-13T14:04:23.123456Z" }, { message: "Log 2", pod: 'pod-1', timestamp: "2019-12-13T14:04:23.123456Z" },
{ message: "Log 3", timestamp: "2019-12-13T14:04:24.123456Z" } { message: "Log 3", pod: 'pod-1', timestamp: "2019-12-13T14:04:24.123456Z" }
] ]
end end
...@@ -163,4 +168,98 @@ describe ::PodLogs::KubernetesService do ...@@ -163,4 +168,98 @@ describe ::PodLogs::KubernetesService do
end end
end end
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
it 'returns error if pod_name is too long' do
result = subject.send(:check_pod_name, pod_name: "a very long string." * 15, pods: [pod_name])
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('pod_name cannot be larger than 253 chars')
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
it 'returns error if container_name is too long' do
result = subject.send(:check_container_name,
container_name: "a very long string." * 15,
pod_name: pod_name,
raw_pods: raw_pods
)
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('container_name cannot be larger than 253 chars')
end
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment