Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Search] Session SO polling #84225

Merged
merged 44 commits into from
Dec 9, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
1c82050
Monitor ids
Nov 24, 2020
52032fc
import fix
Nov 24, 2020
60abe6c
Merge remote-tracking branch 'upstream/master' into sessions/retry-logic
Nov 25, 2020
8a12991
solve circular dep
Nov 25, 2020
d692c1e
eslint
Nov 30, 2020
9a02d9c
Merge branch 'master' of github.com:elastic/kibana into sessions/retr…
Nov 30, 2020
1092c7a
mock circular dep
Nov 30, 2020
500f15d
max retries test
Nov 30, 2020
c6fea68
mock circular dep
Nov 30, 2020
e5325e9
test
Nov 30, 2020
c540fde
jest <(-:C
Dec 1, 2020
5f7db2d
jestttttt
Dec 1, 2020
80fdf82
Merge branch 'master' of github.com:elastic/kibana into sessions/retr…
Dec 1, 2020
4886819
[data.search] Move search method inside session service and add tests
lukasolson Dec 1, 2020
7387405
Merge branch 'master' of github.com:elastic/kibana into sessions/retr…
Dec 2, 2020
a91ea10
merge
Dec 2, 2020
0de7730
Move background session service to data_enhanced plugin
lukasolson Dec 2, 2020
5253b42
Merge branch 'master' of github.com:elastic/kibana into sessions/retr…
Dec 3, 2020
ec54b8b
Merge branch 'master' into search-session-search
lukasolson Dec 3, 2020
4ff6eaf
Merge branch 'search-session-search' into search-session-enhanced
lukasolson Dec 3, 2020
93128a1
Better logs
Dec 3, 2020
ea14d29
Fix types
lukasolson Dec 3, 2020
4ce9fef
Merge branch 'master' into search-session-enhanced
lukasolson Dec 3, 2020
7537e69
Merge branch 'master' into search-session-enhanced
kibanamachine Dec 3, 2020
53b8c32
Merge branch 'master' into search-session-enhanced
kibanamachine Dec 4, 2020
a0e2142
Space aware session service
Dec 6, 2020
54b440a
Merge branch 'master' of github.com:elastic/kibana into sessions/retr…
Dec 6, 2020
e8ec140
Merge remote-tracking branch 'lukasolson/search-session-enhanced' int…
Dec 6, 2020
149d94e
ts
Dec 6, 2020
0b875ac
Fix session service saving
Dec 7, 2020
f40fcca
Merge branch 'master' of github.com:elastic/kibana into sessions/retr…
Dec 7, 2020
c1ba50c
Merge remote-tracking branch 'upstream/master' into sessions/retry-logic
Dec 7, 2020
b5b0895
merge fix
Dec 7, 2020
f14f042
stable stringify
Dec 7, 2020
5b9782d
INMEM_MAX_SESSIONS
Dec 7, 2020
0858b98
INMEM_MAX_SESSIONS
Dec 7, 2020
6c9e345
Merge branch 'master' of github.com:elastic/kibana into sessions/retr…
Dec 8, 2020
e85da48
Merge branch 'sessions/retry-logic' of github.com:lizozom/kibana into…
Dec 8, 2020
4876f3d
Update x-pack/plugins/data_enhanced/server/search/session/session_ser…
lizozom Dec 8, 2020
ffabaa9
Update x-pack/plugins/data_enhanced/server/search/session/session_ser…
lizozom Dec 8, 2020
63c7d6a
Use setTimeout to schedule monitoring steps
Dec 8, 2020
51143af
Merge branch 'master' of github.com:elastic/kibana into sessions/retr…
Dec 8, 2020
bf54579
Merge branch 'master' of github.com:elastic/kibana into sessions/retr…
Dec 9, 2020
07fe4bf
settimeout
Dec 9, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@

import Chance from 'chance';

import { getUpgradeableConfigMock } from './get_upgradeable_config.test.mock';
import { SavedObjectsErrorHelpers } from '../../saved_objects';
import { savedObjectsClientMock } from '../../saved_objects/service/saved_objects_client.mock';
import { loggingSystemMock } from '../../logging/logging_system.mock';
import { getUpgradeableConfigMock } from './get_upgradeable_config.test.mock';

import { createOrUpgradeSavedConfig } from './create_or_upgrade_saved_config';

Expand Down
7 changes: 5 additions & 2 deletions src/plugins/data/server/search/search_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,15 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
private readonly searchSourceService = new SearchSourceService();
private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY;
private searchStrategies: StrategyMap = {};
private sessionService: BackgroundSessionService;
private coreStart?: CoreStart;
private sessionService: BackgroundSessionService = new BackgroundSessionService();

constructor(
private initializerContext: PluginInitializerContext<ConfigSchema>,
private readonly logger: Logger
) {}
) {
this.sessionService = new BackgroundSessionService(logger);
}

public setup(
core: CoreSetup<{}, DataPluginStart>,
Expand Down Expand Up @@ -230,6 +232,7 @@ export class SearchService implements Plugin<ISearchSetup, ISearchStart> {
): ISearchStart {
const { elasticsearch, savedObjects, uiSettings } = core;
const asScoped = this.asScopedProvider(core);
this.sessionService.start(core);
return {
aggs: this.aggsService.start({
fieldFormats,
Expand Down
246 changes: 243 additions & 3 deletions src/plugins/data/server/search/session/session_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,82 @@
* under the License.
*/

import type { SavedObject, SavedObjectsClientContract } from 'kibana/server';
import { coreMock } from 'src/core/server/mocks';
import type { SavedObject, SavedObjectsClientContract } from 'src/core/server';
import { savedObjectsClientMock } from '../../../../../core/server/mocks';
import { BackgroundSessionStatus } from '../../../common';
import { BACKGROUND_SESSION_TYPE } from '../../saved_objects';
import { BackgroundSessionService } from './session_service';
import {
BackgroundSessionService,
INMEM_TRACKING_INTERVAL,
MAX_UPDATE_RETRIES,
SessionInfo,
} from './session_service';
import { createRequestHash } from './utils';
import moment from 'moment';

describe('BackgroundSessionService', () => {
let savedObjectsClient: jest.Mocked<SavedObjectsClientContract>;
let service: BackgroundSessionService;
const mockCoreStart = coreMock.createStart();

const MOCK_SESSION_ID = 'session-id-mock';
const MOCK_ASYNC_ID = '123456';
const MOCK_KEY_HASH = '608de49a4600dbb5b173492759792e4a';

const createMockInternalSavedObjectClient = (
bulkGetSpy?: jest.SpyInstance<any>,
bulkUpdateSpy?: jest.SpyInstance<any>
) => {
Object.defineProperty(service, 'internalSavedObjectsClient', {
get: () => {
const bulkGet =
bulkGetSpy ||
(() => {
return {
saved_objects: [
{
attributes: {
sessionId: MOCK_SESSION_ID,
idMapping: {
'another-key': 'another-async-id',
},
},
id: MOCK_SESSION_ID,
version: '1',
},
],
};
});

const bulkUpdate =
bulkUpdateSpy ||
(() => {
return {
saved_objects: [],
};
});
return {
bulkGet,
bulkUpdate,
};
},
});
};

const createMockIdMapping = (
mapValues: any[],
insertTime?: moment.Moment,
retryCount?: number
): Map<string, SessionInfo> => {
const fakeMap = new Map();
fakeMap.set(MOCK_SESSION_ID, {
ids: new Map(mapValues),
insertTime: insertTime || moment(),
retryCount: retryCount || 0,
});
return fakeMap;
};

const mockSavedObject: SavedObject = {
id: 'd7170a35-7e2c-48d6-8dec-9a056721b489',
Expand All @@ -40,7 +106,12 @@ describe('BackgroundSessionService', () => {

beforeEach(() => {
savedObjectsClient = savedObjectsClientMock.create();
service = new BackgroundSessionService();
const mockLogger: any = {
debug: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
};
service = new BackgroundSessionService(mockLogger);
});

it('save throws if `name` is not provided', () => {
Expand Down Expand Up @@ -230,4 +301,173 @@ describe('BackgroundSessionService', () => {
expect(id).toBe(searchId);
});
});

describe('Monitor', () => {
beforeEach(() => {
jest.useFakeTimers();
service.start(mockCoreStart);
});

afterEach(() => {
jest.useRealTimers();
service.stop();
});

it('should delete expired IDs', async () => {
const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] });
createMockInternalSavedObjectClient(bulkGetSpy);

const mockIdMapping = createMockIdMapping(
[[MOCK_KEY_HASH, MOCK_ASYNC_ID]],
moment().subtract(2, 'm')
);

const deleteSpy = jest.spyOn(mockIdMapping, 'delete');
Object.defineProperty(service, 'sessionSearchMap', {
get: () => mockIdMapping,
});

// Get setInterval to fire
jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL);

expect(bulkGetSpy).not.toHaveBeenCalled();
expect(deleteSpy).toHaveBeenCalledTimes(1);
});

it('should delete IDs that passed max retries', async () => {
const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] });
createMockInternalSavedObjectClient(bulkGetSpy);

const mockIdMapping = createMockIdMapping(
[[MOCK_KEY_HASH, MOCK_ASYNC_ID]],
moment(),
MAX_UPDATE_RETRIES
);

const deleteSpy = jest.spyOn(mockIdMapping, 'delete');
Object.defineProperty(service, 'sessionSearchMap', {
get: () => mockIdMapping,
});

// Get setInterval to fire
jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL);

expect(bulkGetSpy).not.toHaveBeenCalled();
expect(deleteSpy).toHaveBeenCalledTimes(1);
});

it('should bot fetch when no IDs are mapped', async () => {
const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] });
createMockInternalSavedObjectClient(bulkGetSpy);

jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL);
expect(bulkGetSpy).not.toHaveBeenCalled();
});

it('should try to fetch saved objects if some ids are mapped', async () => {
const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]]);
Object.defineProperty(service, 'sessionSearchMap', {
get: () => mockIdMapping,
});

const bulkGetSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] });
const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] });
createMockInternalSavedObjectClient(bulkGetSpy, bulkUpdateSpy);

jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL);
expect(bulkGetSpy).toHaveBeenCalledTimes(1);
expect(bulkUpdateSpy).not.toHaveBeenCalled();
});

it('should update saved objects if they are found, and delete ids on success', async () => {
const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]], undefined, 1);
const mockMapDeleteSpy = jest.fn();
mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockMapDeleteSpy;
Object.defineProperty(service, 'sessionSearchMap', {
get: () => mockIdMapping,
});

const bulkGetSpy = jest.fn().mockResolvedValueOnce({
saved_objects: [
{
id: MOCK_SESSION_ID,
attributes: {
idMapping: {
b: 'c',
},
},
},
],
});
const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({
saved_objects: [
{
id: MOCK_SESSION_ID,
attributes: {
idMapping: {
b: 'c',
[MOCK_KEY_HASH]: MOCK_ASYNC_ID,
},
},
},
],
});
createMockInternalSavedObjectClient(bulkGetSpy, bulkUpdateSpy);

jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL);

// Release timers to call check after test actions are done.
jest.useRealTimers();
await new Promise((r) => setTimeout(r, 15));

expect(bulkGetSpy).toHaveBeenCalledTimes(1);
expect(bulkUpdateSpy).toHaveBeenCalledTimes(1);
expect(mockMapDeleteSpy).toHaveBeenCalledTimes(2);
expect(mockMapDeleteSpy).toBeCalledWith('b');
expect(mockMapDeleteSpy).toBeCalledWith(MOCK_KEY_HASH);
expect(mockIdMapping.get(MOCK_SESSION_ID)?.retryCount).toBe(0);
});

it('should update saved objects if they are found, and increase retryCount on error', async () => {
const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]]);
const mockMapDeleteSpy = jest.fn();
mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockMapDeleteSpy;
Object.defineProperty(service, 'sessionSearchMap', {
get: () => mockIdMapping,
});

const bulkGetSpy = jest.fn().mockResolvedValueOnce({
saved_objects: [
{
id: MOCK_SESSION_ID,
attributes: {
idMapping: {
b: 'c',
},
},
},
],
});
const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({
saved_objects: [
{
id: MOCK_SESSION_ID,
error: 'not ok',
},
],
});
createMockInternalSavedObjectClient(bulkGetSpy, bulkUpdateSpy);

jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL);

// Release timers to call check after test actions are done.
jest.useRealTimers();
await new Promise((r) => setTimeout(r, 15));

expect(bulkGetSpy).toHaveBeenCalledTimes(1);
expect(bulkUpdateSpy).toHaveBeenCalledTimes(1);
expect(mockMapDeleteSpy).not.toHaveBeenCalled();
expect(mockIdMapping.get(MOCK_SESSION_ID)?.retryCount).toBe(1);
});
});
});
Loading