[Snapshot & Restore] Migrated from _cat/plugins to _nodes/plugins to check for installed repo plugins (#121204)

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Yulia Čech 2021-12-17 13:23:50 +01:00 committed by GitHub
parent 981bbd9497
commit b6c9651031
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 60 additions and 27 deletions

View file

@ -5,7 +5,11 @@
* 2.0.
*/
import { DEFAULT_REPOSITORY_TYPES, REPOSITORY_PLUGINS_MAP } from '../../../common/constants';
import {
DEFAULT_REPOSITORY_TYPES,
REPOSITORY_PLUGINS_MAP,
REPOSITORY_TYPES,
} from '../../../common';
import { addBasePath } from '../helpers';
import { registerRepositoriesRoutes } from './repositories';
import { RouterMock, routeDependencies, RequestMock } from '../../test/helpers';
@ -32,7 +36,7 @@ describe('[Snapshot and Restore API Routes] Repositories', () => {
const getClusterSettingsFn = router.getMockApiFn('cluster.getSettings');
const getSnapshotFn = router.getMockApiFn('snapshot.get');
const verifyRepoFn = router.getMockApiFn('snapshot.verifyRepository');
const catPluginsFn = router.getMockApiFn('cat.plugins');
const nodesInfoFn = router.getMockApiFn('nodes.info');
beforeAll(() => {
registerRepositoriesRoutes({
@ -253,38 +257,60 @@ describe('[Snapshot and Restore API Routes] Repositories', () => {
path: addBasePath('repository_types'),
};
it('should return default types if no repository plugins returned from ES', async () => {
catPluginsFn.mockResolvedValue({ body: {} });
it('returns default types if no repository plugins returned from ES', async () => {
nodesInfoFn.mockResolvedValue({ body: { nodes: { testNodeId: { plugins: [] } } } });
const expectedResponse = [...DEFAULT_REPOSITORY_TYPES];
await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse });
});
it('should return default types with any repository plugins returned from ES', async () => {
it('returns default types with any repository plugins returned from ES', async () => {
const pluginNames = Object.keys(REPOSITORY_PLUGINS_MAP);
const pluginTypes = Object.entries(REPOSITORY_PLUGINS_MAP).map(([key, value]) => value);
const mockEsResponse = [...pluginNames.map((key) => ({ component: key }))];
catPluginsFn.mockResolvedValue({ body: mockEsResponse });
const mockEsResponse = {
nodes: { testNodeId: { plugins: [...pluginNames.map((key) => ({ name: key }))] } },
};
nodesInfoFn.mockResolvedValue({ body: mockEsResponse });
const expectedResponse = [...DEFAULT_REPOSITORY_TYPES, ...pluginTypes];
await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse });
});
it('should not return non-repository plugins returned from ES', async () => {
it(`doesn't return non-repository plugins returned from ES`, async () => {
const pluginNames = ['foo-plugin', 'bar-plugin'];
const mockEsResponse = [...pluginNames.map((key) => ({ component: key }))];
catPluginsFn.mockResolvedValue({ body: mockEsResponse });
const mockEsResponse = {
nodes: { testNodeId: { plugins: [...pluginNames.map((key) => ({ name: key }))] } },
};
nodesInfoFn.mockResolvedValue({ body: mockEsResponse });
const expectedResponse = [...DEFAULT_REPOSITORY_TYPES];
await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse });
});
it('should throw if ES error', async () => {
catPluginsFn.mockRejectedValueOnce(new Error('Error getting plugins'));
it(`doesn't return repository plugins that are not installed on all nodes`, async () => {
const dataNodePlugins = ['repository-s3', 'repository-azure'];
const masterNodePlugins = ['repository-azure'];
const mockEsResponse = {
nodes: {
dataNode: { plugins: [...dataNodePlugins.map((key) => ({ name: key }))] },
masterNode: { plugins: [...masterNodePlugins.map((key) => ({ name: key }))] },
},
};
nodesInfoFn.mockResolvedValue({ body: mockEsResponse });
await expect(router.runRequest(mockRequest)).rejects.toThrowError('Error getting plugins');
const expectedResponse = [...DEFAULT_REPOSITORY_TYPES, REPOSITORY_TYPES.azure];
await expect(router.runRequest(mockRequest)).resolves.toEqual({ body: expectedResponse });
});
it('should throw if ES error', async () => {
nodesInfoFn.mockRejectedValueOnce(new Error('Error getting cluster stats'));
await expect(router.runRequest(mockRequest)).rejects.toThrowError(
'Error getting cluster stats'
);
});
});

View file

@ -9,9 +9,10 @@ import { TypeOf } from '@kbn/config-schema';
import type {
SnapshotGetRepositoryResponse,
SnapshotRepositorySettings,
PluginStats,
} from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
import { DEFAULT_REPOSITORY_TYPES, REPOSITORY_PLUGINS_MAP } from '../../../common/constants';
import { DEFAULT_REPOSITORY_TYPES, REPOSITORY_PLUGINS_MAP } from '../../../common';
import { Repository, RepositoryType } from '../../../common/types';
import { RouteDependencies } from '../../types';
import { addBasePath } from '../helpers';
@ -162,21 +163,27 @@ export function registerRepositoriesRoutes({
const types: RepositoryType[] = isCloudEnabled ? [] : [...DEFAULT_REPOSITORY_TYPES];
try {
// Call with internal user so that the requesting user does not need `monitoring` cluster
// privilege just to see list of available repository types
const { body: plugins } = await clusterClient.asCurrentUser.cat.plugins({ format: 'json' });
const {
body: { nodes },
} = await clusterClient.asCurrentUser.nodes.info({
node_id: '_all',
metric: 'plugins',
});
const pluginNamesAllNodes = Object.keys(nodes).map((key: string) => {
// extract plugin names
return (nodes[key].plugins ?? []).map((plugin: PluginStats) => plugin.name);
});
// Filter list of plugins to repository-related ones
if (plugins && plugins.length) {
const pluginNames: string[] = [
...new Set(plugins.map((plugin) => plugin.component ?? '')),
];
pluginNames.forEach((pluginName) => {
if (REPOSITORY_PLUGINS_MAP[pluginName]) {
types.push(REPOSITORY_PLUGINS_MAP[pluginName]);
}
});
}
Object.keys(REPOSITORY_PLUGINS_MAP).forEach((repoTypeName: string) => {
if (
// check if this repository plugin is installed on every node
pluginNamesAllNodes.every((pluginNames: string[]) => pluginNames.includes(repoTypeName))
) {
types.push(REPOSITORY_PLUGINS_MAP[repoTypeName]);
}
});
return res.ok({ body: types });
} catch (e) {
return handleEsError({ error: e, response: res });