2022-04-22 08:33:13 -05:00
|
|
|
import { noop } from 'lodash';
|
2020-12-07 03:13:57 -06:00
|
|
|
import { Observable, Subject, of, throwError, concat } from 'rxjs';
|
|
|
|
import { mergeMap } from 'rxjs/operators';
|
2019-09-05 07:04:01 -05:00
|
|
|
import * as rxJsWebSocket from 'rxjs/webSocket';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2020-03-12 04:22:33 -05:00
|
|
|
import { DataFrame, DataFrameView, formatLabels, Labels } from '@grafana/data';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2022-09-14 07:55:15 -05:00
|
|
|
import { LiveStreams } from './LiveStreams';
|
2020-04-22 06:59:06 -05:00
|
|
|
import { LokiTailResponse } from './types';
|
2019-09-05 07:04:01 -05:00
|
|
|
|
2022-09-26 09:42:53 -05:00
|
|
|
interface ErrorException extends Error {
|
|
|
|
code?: number;
|
|
|
|
}
|
|
|
|
|
|
|
|
let fakeSocket: Subject<LokiTailResponse>;
|
2019-09-05 07:04:01 -05:00
|
|
|
jest.mock('rxjs/webSocket', () => {
|
|
|
|
return {
|
|
|
|
__esModule: true,
|
|
|
|
webSocket: () => fakeSocket,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('Live Stream Tests', () => {
|
|
|
|
afterAll(() => {
|
|
|
|
jest.restoreAllMocks();
|
|
|
|
});
|
|
|
|
|
2020-04-22 06:59:06 -05:00
|
|
|
const msg0: LokiTailResponse = {
|
2019-09-05 07:04:01 -05:00
|
|
|
streams: [
|
|
|
|
{
|
2020-04-22 06:59:06 -05:00
|
|
|
stream: { filename: '/var/log/sntpc.log', job: 'varlogs' },
|
|
|
|
values: [['1567025440118944705', 'Kittens']],
|
2019-09-05 07:04:01 -05:00
|
|
|
},
|
|
|
|
],
|
|
|
|
dropped_entries: null,
|
|
|
|
};
|
|
|
|
|
2021-01-20 00:59:48 -06:00
|
|
|
it('reads the values into the buffer', (done) => {
|
2022-09-26 09:42:53 -05:00
|
|
|
fakeSocket = new Subject<LokiTailResponse>();
|
2019-09-05 07:04:01 -05:00
|
|
|
const labels: Labels = { job: 'varlogs' };
|
|
|
|
const target = makeTarget('fake', labels);
|
2020-04-22 06:59:06 -05:00
|
|
|
const stream = new LiveStreams().getStream(target);
|
2022-07-27 01:11:09 -05:00
|
|
|
expect.assertions(3);
|
2019-09-05 07:04:01 -05:00
|
|
|
|
|
|
|
const tests = [
|
|
|
|
(val: DataFrame[]) => {
|
|
|
|
expect(val[0].length).toEqual(7);
|
|
|
|
},
|
|
|
|
(val: DataFrame[]) => {
|
|
|
|
expect(val[0].length).toEqual(8);
|
|
|
|
const view = new DataFrameView(val[0]);
|
|
|
|
const last = { ...view.get(view.length - 1) };
|
|
|
|
expect(last).toEqual({
|
2022-05-20 10:27:49 -05:00
|
|
|
Time: '2019-08-28T20:50:40.118Z',
|
2023-05-17 08:28:25 -05:00
|
|
|
id: 'A_25d81461-a66f-53ff-98d5-e39515af4735',
|
2022-05-20 10:27:49 -05:00
|
|
|
Line: 'Kittens',
|
2019-09-05 07:04:01 -05:00
|
|
|
});
|
|
|
|
},
|
|
|
|
];
|
|
|
|
stream.subscribe({
|
2021-01-20 00:59:48 -06:00
|
|
|
next: (val) => {
|
2019-09-05 07:04:01 -05:00
|
|
|
const test = tests.shift();
|
2020-03-12 04:22:33 -05:00
|
|
|
test!(val);
|
2019-09-05 07:04:01 -05:00
|
|
|
},
|
|
|
|
complete: () => done(),
|
|
|
|
});
|
|
|
|
|
|
|
|
// Send it the initial list of things
|
|
|
|
fakeSocket.next(initialRawResponse);
|
|
|
|
// Send it a single update
|
|
|
|
fakeSocket.next(msg0);
|
|
|
|
fakeSocket.complete();
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns the same subscription if the url matches existing one', () => {
|
2022-09-26 09:42:53 -05:00
|
|
|
fakeSocket = new Subject<LokiTailResponse>();
|
2019-09-05 07:04:01 -05:00
|
|
|
const liveStreams = new LiveStreams();
|
2020-04-22 06:59:06 -05:00
|
|
|
const stream1 = liveStreams.getStream(makeTarget('url_to_match'));
|
|
|
|
const stream2 = liveStreams.getStream(makeTarget('url_to_match'));
|
2019-09-05 07:04:01 -05:00
|
|
|
expect(stream1).toBe(stream2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns new subscription when the previous unsubscribed', () => {
|
2022-09-26 09:42:53 -05:00
|
|
|
fakeSocket = new Subject<LokiTailResponse>();
|
2019-09-05 07:04:01 -05:00
|
|
|
const liveStreams = new LiveStreams();
|
2020-04-22 06:59:06 -05:00
|
|
|
const stream1 = liveStreams.getStream(makeTarget('url_to_match'));
|
2019-09-05 07:04:01 -05:00
|
|
|
const subscription = stream1.subscribe({
|
|
|
|
next: noop,
|
|
|
|
});
|
|
|
|
subscription.unsubscribe();
|
|
|
|
|
2020-04-22 06:59:06 -05:00
|
|
|
const stream2 = liveStreams.getStream(makeTarget('url_to_match'));
|
2019-09-05 07:04:01 -05:00
|
|
|
expect(stream1).not.toBe(stream2);
|
|
|
|
});
|
|
|
|
|
|
|
|
it('returns new subscription when the previous is unsubscribed and correctly unsubscribes from source', () => {
|
|
|
|
let unsubscribed = false;
|
2022-09-26 09:42:53 -05:00
|
|
|
const fakeSocket = new Observable(() => {
|
2019-09-05 07:04:01 -05:00
|
|
|
return () => (unsubscribed = true);
|
2022-09-26 09:42:53 -05:00
|
|
|
});
|
2021-12-03 11:09:25 -06:00
|
|
|
jest.spyOn(rxJsWebSocket, 'webSocket').mockReturnValue(fakeSocket as rxJsWebSocket.WebSocketSubject<unknown>);
|
2019-09-05 07:04:01 -05:00
|
|
|
|
|
|
|
const liveStreams = new LiveStreams();
|
2020-04-22 06:59:06 -05:00
|
|
|
const stream1 = liveStreams.getStream(makeTarget('url_to_match'));
|
2019-09-05 07:04:01 -05:00
|
|
|
const subscription = stream1.subscribe({
|
|
|
|
next: noop,
|
|
|
|
});
|
|
|
|
subscription.unsubscribe();
|
|
|
|
expect(unsubscribed).toBe(true);
|
|
|
|
});
|
2020-12-07 03:13:57 -06:00
|
|
|
it('should reconnect when abnormal error', async () => {
|
2022-09-26 09:42:53 -05:00
|
|
|
const abnormalError = new Error('weird error') as ErrorException;
|
2020-12-07 03:13:57 -06:00
|
|
|
abnormalError.code = 1006;
|
|
|
|
const logStreamBeforeError = of({
|
|
|
|
streams: [
|
|
|
|
{
|
|
|
|
stream: { filename: '/var/log/sntpc.log', job: 'varlogs' },
|
|
|
|
values: [['1567025440118944705', 'Kittens']],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
dropped_entries: null,
|
|
|
|
});
|
|
|
|
const logStreamAfterError = of({
|
|
|
|
streams: [
|
|
|
|
{
|
|
|
|
stream: { filename: '/var/log/sntpc.log', job: 'varlogs' },
|
|
|
|
values: [['1567025440118944705', 'Doggos']],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
dropped_entries: null,
|
|
|
|
});
|
|
|
|
const errorStream = throwError(abnormalError);
|
|
|
|
let retries = 0;
|
2022-09-26 09:42:53 -05:00
|
|
|
const fakeSocket = of({}).pipe(
|
2020-12-07 03:13:57 -06:00
|
|
|
mergeMap(() => {
|
|
|
|
// When subscribed first time, return logStream and errorStream
|
|
|
|
if (retries++ === 0) {
|
|
|
|
return concat(logStreamBeforeError, errorStream);
|
|
|
|
}
|
|
|
|
// When re-subsribed after abnormal error, return just logStream
|
|
|
|
return logStreamAfterError;
|
|
|
|
})
|
2022-09-26 09:42:53 -05:00
|
|
|
);
|
2021-12-03 11:09:25 -06:00
|
|
|
jest.spyOn(rxJsWebSocket, 'webSocket').mockReturnValue(fakeSocket as rxJsWebSocket.WebSocketSubject<unknown>);
|
2020-12-07 03:13:57 -06:00
|
|
|
const liveStreams = new LiveStreams();
|
2021-01-20 00:59:48 -06:00
|
|
|
await expect(liveStreams.getStream(makeTarget('url_to_match'), 100)).toEmitValuesWith((received) => {
|
2020-12-07 03:13:57 -06:00
|
|
|
const data = received[0];
|
|
|
|
const view = new DataFrameView(data[0]);
|
|
|
|
const firstLog = { ...view.get(0) };
|
|
|
|
const secondLog = { ...view.get(1) };
|
|
|
|
|
2022-05-20 10:27:49 -05:00
|
|
|
expect(firstLog.Line).toBe('Kittens');
|
|
|
|
expect(secondLog.Line).toBe('Doggos');
|
2020-12-07 03:13:57 -06:00
|
|
|
expect(retries).toBe(2);
|
|
|
|
});
|
|
|
|
});
|
2019-09-05 07:04:01 -05:00
|
|
|
});
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create target (query to run). Url is what is used as cache key.
|
|
|
|
*/
|
|
|
|
function makeTarget(url: string, labels?: Labels) {
|
|
|
|
labels = labels || { job: 'varlogs' };
|
|
|
|
return {
|
|
|
|
url,
|
|
|
|
size: 10,
|
|
|
|
query: formatLabels(labels),
|
|
|
|
refId: 'A',
|
|
|
|
regexp: '',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
// Added this at the end so the top is more readable
|
|
|
|
//----------------------------------------------------------------
|
|
|
|
|
2020-04-22 06:59:06 -05:00
|
|
|
const initialRawResponse: LokiTailResponse = {
|
2019-09-05 07:04:01 -05:00
|
|
|
streams: [
|
|
|
|
{
|
2020-04-22 06:59:06 -05:00
|
|
|
stream: {
|
|
|
|
filename: '/var/log/docker.log',
|
|
|
|
job: 'varlogs',
|
|
|
|
},
|
|
|
|
values: [
|
|
|
|
[
|
|
|
|
'1567025018215000000',
|
|
|
|
'level=debug msg="[resolver] received AAAA record \\"::1\\" for \\"localhost.\\" from udp:192.168.65.1"',
|
|
|
|
],
|
|
|
|
[
|
|
|
|
'1567025018215000000',
|
|
|
|
'2019-08-28T20:43:38Z docker time="2019-08-28T20:43:38.147224630Z" ' +
|
2019-09-05 07:04:01 -05:00
|
|
|
'level=debug msg="[resolver] received AAAA record \\"fe80::1\\" for \\"localhost.\\" from udp:192.168.65.1"',
|
2020-04-22 06:59:06 -05:00
|
|
|
],
|
|
|
|
['1567025020452000000', '2019-08-28T20:43:40Z sntpc sntpc[1]: offset=-0.022171, delay=0.000463'],
|
|
|
|
['1567025050297000000', '2019-08-28T20:44:10Z sntpc sntpc[1]: offset=-0.022327, delay=0.000527'],
|
|
|
|
[
|
|
|
|
'1567025078152000000',
|
|
|
|
'2019-08-28T20:44:38Z lifecycle-server time="2019-08-28T20:44:38.095444834Z" ' +
|
2019-09-05 07:04:01 -05:00
|
|
|
'level=debug msg="Name To resolve: localhost."',
|
2020-04-22 06:59:06 -05:00
|
|
|
],
|
|
|
|
[
|
|
|
|
'1567025078152000000',
|
|
|
|
'2019-08-28T20:44:38Z lifecycle-server time="2019-08-28T20:44:38.095896074Z" ' +
|
2019-09-05 07:04:01 -05:00
|
|
|
'level=debug msg="[resolver] query localhost. (A) from 172.22.0.4:53748, forwarding to udp:192.168.65.1"',
|
2020-04-22 06:59:06 -05:00
|
|
|
],
|
|
|
|
[
|
|
|
|
'1567025078152000000',
|
|
|
|
'2019-08-28T20:44:38Z docker time="2019-08-28T20:44:38.095444834Z" level=debug msg="Name To resolve: localhost."',
|
|
|
|
],
|
2019-09-05 07:04:01 -05:00
|
|
|
],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
dropped_entries: null,
|
|
|
|
};
|