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 Sessions] Cancel the previous session #99658

Closed
wants to merge 25 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
12b95ab
cancel the previous session
May 10, 2021
6e870dd
fixes
May 12, 2021
436dd51
cancellation
May 13, 2021
984eba2
Merge branch 'master' of github.com:elastic/kibana into session/cance…
May 13, 2021
f5fdd88
Merge branch 'master' into session/cancellation
kibanamachine May 13, 2021
768d7ed
Merge branch 'master' into session/cancellation
kibanamachine May 16, 2021
77624ff
Merge branch 'master' of github.com:elastic/kibana into session/cance…
May 18, 2021
e5904ac
cleanup previous session properly
May 18, 2021
4f32b09
Merge branch 'master' of github.com:elastic/kibana into session/cance…
May 18, 2021
f9f1ebb
Merge branch 'session/cancellation' of github.com:lizozom/kibana into…
May 18, 2021
44023c4
don't fail delete and cancel if item was already cleaned up
May 18, 2021
54bb6cd
test
May 19, 2021
9ffbb03
test
May 19, 2021
6de0b62
Merge branch 'master' of github.com:elastic/kibana into session/cance…
May 19, 2021
a343e13
Merge branch 'master' into session/cancellation
kibanamachine May 19, 2021
6d96554
Merge branch 'master' into session/cancellation
kibanamachine May 19, 2021
49fe28a
Merge branch 'master' into session/cancellation
kibanamachine May 20, 2021
b3e093a
Merge branch 'master' of github.com:elastic/kibana into session/cance…
May 23, 2021
f911163
Merge branch 'session/cancellation' of github.com:lizozom/kibana into…
May 23, 2021
a988edd
Merge branch 'master' into session/cancellation
kibanamachine May 24, 2021
25445c6
ignore resource_not_found_exception when deleting an already cleared …
May 24, 2021
e1db913
Merge branch 'session/cancellation' of github.com:lizozom/kibana into…
May 24, 2021
74c145e
Update x-pack/test/api_integration/apis/search/session.ts
lizozom May 25, 2021
6e6fcb2
limit
May 30, 2021
ee5ecc7
Merge branch 'master' of github.com:elastic/kibana into session/cance…
May 31, 2021
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
2 changes: 1 addition & 1 deletion packages/kbn-optimizer/limits.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pageLoadAssetSize:
alerting: 106936
apm: 64385
apmOss: 18996
bfetch: 41874
bfetch: 51874
canvas: 1066647
charts: 195358
cloud: 21076
Expand Down
1 change: 1 addition & 0 deletions src/plugins/data/public/search/session/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export function getSessionsClientMock(): jest.Mocked<ISessionsClient> {
extend: jest.fn(),
delete: jest.fn(),
rename: jest.fn(),
cancel: jest.fn(),
};
}

Expand Down
10 changes: 10 additions & 0 deletions src/plugins/data/public/search/session/session_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ describe('Session service', () => {
id,
attributes: { ...mockSavedObject.attributes, sessionId: id },
}));
sessionsClient.cancel.mockResolvedValue(undefined);
sessionService = new SessionService(
initializerContext,
() =>
Expand Down Expand Up @@ -98,6 +99,15 @@ describe('Session service', () => {
expect(nowProvider.reset).toHaveBeenCalled();
});

it('Calling start twice clears the previous session', async () => {
sessionService.start();
expect(sessionService.getSessionId()).not.toBeUndefined();
expect(nowProvider.set).toHaveBeenCalled();
sessionService.start();
expect(sessionService.getSessionId()).not.toBeUndefined();
expect(sessionsClient.cancel).toHaveBeenCalled();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to make sure it's called with the id returned from start()?

});

it("Can't clear other apps' session", async () => {
sessionService.start();
expect(sessionService.getSessionId()).not.toBeUndefined();
Expand Down
26 changes: 20 additions & 6 deletions src/plugins/data/public/search/session/session_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ export class SessionService {
*/
public start() {
if (!this.currentApp) throw new Error('this.currentApp is missing');

// cancel previous session if needed
this.cleanupPreviousSession();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure I understand the full lifecycle so please correct me - this function calls "cancel" under the hood so is it possible to have a race-condition between creating a new search and the time it takes to cancel the current one so that we cancel the new search?

FWIW, I was not able to reproduce this, purely based on my current understanding.


this.state.transitions.start({ appName: this.currentApp });
return this.getSessionId()!;
}
Expand Down Expand Up @@ -241,24 +245,34 @@ export class SessionService {
);
return;
}

this.cleanupPreviousSession();
this.state.transitions.clear();
this.searchSessionInfoProvider = undefined;
this.searchSessionIndicatorUiConfig = undefined;
}

private async cleanupPreviousSession() {
const { pendingSearches, sessionId, isRestore, isStored } = this.state.get();
if (sessionId && !isRestore && !isStored) {
pendingSearches.forEach((s) => {
s.abort();
});
await this.sessionsClient.cancel(sessionId).catch(() => {});
}
}

/**
* Request a cancellation of on-going search requests within current session
*/
public async cancel(): Promise<void> {
const isStoredSession = this.state.get().isStored;
this.state.get().pendingSearches.forEach((s) => {
const { pendingSearches, sessionId } = this.state.get();
if (!sessionId) return;

pendingSearches.forEach((s) => {
s.abort();
});
await this.sessionsClient.cancel(sessionId).catch(() => {});
this.state.transitions.cancel();
if (isStoredSession) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced deletion with cancellation.
The cleanup task will take care of deletion #99967

await this.sessionsClient.delete(this.state.get().sessionId!);
}
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/plugins/data/public/search/session/sessions_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ export class SessionsClient {
});
}

public cancel(sessionId: string): Promise<void> {
return this.http!.post(`/internal/session/${encodeURIComponent(sessionId)}/cancel`);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super minor nit: Can we either standardize on /_action or /action? I don't really have a preference (though saved objects are /_action), just want them to be the same format.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To me, it would make more sense to swallow the 404 errors here in the client rather in the API itself, and only log the errors on the server if it's not a 404. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't intend do to this first, but you can't eliminate the 404 from the console, and it looks bad IMO
https://stackoverflow.com/questions/12915582/eliminate-404-url-error-in-console

What do you think?

}

public delete(sessionId: string): Promise<void> {
return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}`);
}
Expand Down
78 changes: 78 additions & 0 deletions x-pack/plugins/data_enhanced/server/routes/session.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,45 @@ describe('registerSessionRoutes', () => {
expect(mockContext.search!.cancelSession).toHaveBeenCalledWith(id);
});

it('cancel doesnt fail if not found', async () => {
const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489';
const params = { id };

const mockRequest = httpServerMock.createKibanaRequest({ params });
const mockResponse = httpServerMock.createResponseFactory();

const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, cancelHandler] = mockRouter.post.mock.calls[PostHandlerIndex.CANCEL];

mockContext.search!.cancelSession = jest.fn().mockRejectedValue({
statusCode: 404,
});

await cancelHandler(mockContext, mockRequest, mockResponse);

expect(mockContext.search!.cancelSession).toHaveBeenCalledWith(id);
expect(mockResponse.ok).toHaveBeenCalledTimes(1);
});

it('cancel fail on other errors', async () => {
const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489';
const params = { id };

const mockRequest = httpServerMock.createKibanaRequest({ params });
const mockResponse = httpServerMock.createResponseFactory();

const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, cancelHandler] = mockRouter.post.mock.calls[PostHandlerIndex.CANCEL];

mockContext.search!.cancelSession = jest.fn().mockRejectedValue({
statusCode: 500,
});

await cancelHandler(mockContext, mockRequest, mockResponse).catch(() => {});

expect(mockResponse.customError).toHaveBeenCalledTimes(1);
});

it('delete calls deleteSession with id', async () => {
const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489';
const params = { id };
Expand All @@ -133,6 +172,45 @@ describe('registerSessionRoutes', () => {
expect(mockContext.search!.deleteSession).toHaveBeenCalledWith(id);
});

it('delete doesnt fail if not found', async () => {
const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489';
const params = { id };

const mockRequest = httpServerMock.createKibanaRequest({ params });
const mockResponse = httpServerMock.createResponseFactory();

const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, deleteHandler] = mockRouter.delete.mock.calls[0];

mockContext.search!.deleteSession = jest.fn().mockRejectedValue({
statusCode: 404,
});

await deleteHandler(mockContext, mockRequest, mockResponse);

expect(mockContext.search!.deleteSession).toHaveBeenCalledWith(id);
expect(mockResponse.ok).toHaveBeenCalledTimes(1);
});

it('delete returns error if another error code occurs', async () => {
const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489';
const params = { id };

const mockRequest = httpServerMock.createKibanaRequest({ params });
const mockResponse = httpServerMock.createResponseFactory();

const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value;
const [, deleteHandler] = mockRouter.delete.mock.calls[0];

mockContext.search!.deleteSession = jest.fn().mockRejectedValue({
statusCode: 500,
});

await deleteHandler(mockContext, mockRequest, mockResponse).catch(() => {});

expect(mockResponse.customError).toHaveBeenCalledTimes(1);
});

it('extend calls extendSession with id', async () => {
const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489';
const expires = new Date().toISOString();
Expand Down
10 changes: 10 additions & 0 deletions x-pack/plugins/data_enhanced/server/routes/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger:
return res.ok();
} catch (e) {
const err = e.output?.payload || e;
// Don't fail on items that were already deleted
if (err.statusCode === 404) {
logger.debug(`Search session ${id} not found. Ignoring.`);
return res.ok();
}
logger.error(err);
return reportServerError(res, err);
}
Expand All @@ -177,6 +182,11 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger:
return res.ok();
} catch (e) {
const err = e.output?.payload || e;
// Don't fail on items that were already deleted
if (err.statusCode === 404) {
logger.debug(`Search session ${id} not found. Ignoring.`);
return res.ok();
}
logger.error(err);
return reportServerError(res, err);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,11 @@ function checkRunningSessionsPage(
try {
await client.asyncSearch.delete({ id: searchInfo.id });
} catch (e) {
logger.error(
`Error while deleting async_search ${searchInfo.id}: ${e.message}`
);
if (e.message !== 'resource_not_found_exception') {
logger.error(
`Error while deleting async_search ${searchInfo.id}: ${e.message}`
);
}
}
}
});
Expand Down
18 changes: 14 additions & 4 deletions x-pack/test/api_integration/apis/search/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ export default function ({ getService }: FtrProviderContext) {
await supertest.get(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(200);
});

it('should fail to delete an unknown session', async () => {
await supertest.delete(`/internal/session/123`).set('kbn-xsrf', 'foo').expect(404);
it('should NOT fail when deleting an unknown session', async () => {
await supertest.delete(`/internal/session/123`).set('kbn-xsrf', 'foo').expect(200);
});

it('should create and delete a session', async () => {
Expand Down Expand Up @@ -487,7 +487,9 @@ export default function ({ getService }: FtrProviderContext) {
.delete(`/internal/session/${sessionId}`)
.set('kbn-xsrf', 'foo')
.auth('other_user', 'password')
.expect(404);
.expect(200);

await supertest.get(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(200);
});

it(`should prevent users from cancelling other users' sessions`, async () => {
Expand All @@ -508,7 +510,15 @@ export default function ({ getService }: FtrProviderContext) {
.post(`/internal/session/${sessionId}/cancel`)
.set('kbn-xsrf', 'foo')
.auth('other_user', 'password')
.expect(404);
.expect(200);

const resp = await supertest
.get(`/internal/session/${sessionId}`)
.set('kbn-xsrf', 'foo')
.expect(200);

const { status } = resp.body.attributes;
expect(status).not.to.equal(SearchSessionStatus.CANCELLED);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the expected status then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either IN_PROGRESS or COMPLETED :-)

});

it(`should prevent users from extending other users' sessions`, async () => {
Expand Down