forked from opensearch-project/OpenSearch-Dashboards
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new core service to expose functionality to verify plugin compati…
…bility with OpenSearch plugins Signed-off-by: Manasvini B Suryanarayana <[email protected]>
- Loading branch information
1 parent
a23e485
commit bbfc88b
Showing
22 changed files
with
336 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
src/core/server/cross_compatibility/cross_compatibility.mock.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { CrossCompatibilityServiceStart } from './types'; | ||
|
||
const createStartContractMock = () => { | ||
const startContract: jest.Mocked<CrossCompatibilityServiceStart> = { | ||
verifyOpenSearchPluginsState: jest.fn().mockReturnValue(Promise.resolve({})), | ||
}; | ||
return startContract; | ||
}; | ||
|
||
export const crossCompatibilityServiceMock = { | ||
createStartContract: createStartContractMock, | ||
}; |
81 changes: 81 additions & 0 deletions
81
src/core/server/cross_compatibility/cross_compatibility_service.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { CrossCompatibilityService } from './cross_compatibility_service'; | ||
import { CompatibleEnginePluginVersions } from '../plugins/types'; | ||
import { mockCoreContext } from '../core_context.mock'; | ||
import { opensearchServiceMock } from '../opensearch/opensearch_service.mock'; | ||
|
||
describe('CrossCompatibilityService', () => { | ||
let service: CrossCompatibilityService; | ||
let opensearch: any; | ||
const plugins = new Map<string, CompatibleEnginePluginVersions>(); | ||
|
||
beforeEach(() => { | ||
opensearch = opensearchServiceMock.createStart(); | ||
opensearch.client.asInternalUser.cat.plugins.mockResolvedValue({ | ||
body: [ | ||
{ | ||
name: 'node1', | ||
component: 'os-plugin', | ||
version: '1.1.0.0', | ||
}, | ||
], | ||
} as any); | ||
|
||
plugins?.set('foo', { 'os-plugin': '1.0.0 - 2.0.0' }); | ||
plugins?.set('bar', { 'os-plugin': '^3.0.0' }); | ||
plugins?.set('test', {}); | ||
service = new CrossCompatibilityService(mockCoreContext.create()); | ||
}); | ||
|
||
it('should start the cross compatibility service', async () => { | ||
const startDeps = { opensearch, plugins }; | ||
const startResult = await service.start(startDeps); | ||
expect(startResult).toEqual({ | ||
verifyOpenSearchPluginsState: expect.any(Function), | ||
}); | ||
}); | ||
|
||
it('should return an array of CrossCompatibilityResult objects if plugin dependencies are specified', async () => { | ||
const pluginName = 'foo'; | ||
const startDeps = { opensearch, plugins }; | ||
const startResult = await service.start(startDeps); | ||
const results = await startResult.verifyOpenSearchPluginsState(pluginName); | ||
expect(results).not.toBeUndefined(); | ||
expect(results.length).toEqual(1); | ||
expect(results[0].pluginName).toEqual('os-plugin'); | ||
expect(results[0].isCompatible).toEqual(true); | ||
expect(results[0].incompatibleReason).toEqual(''); | ||
expect(results[0].installedVersions).toEqual(new Set(['1.1.0.0'])); | ||
expect(opensearch.client.asInternalUser.cat.plugins).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should return an empty array if no plugin dependencies are specified', async () => { | ||
const pluginName = 'test'; | ||
const startDeps = { opensearch, plugins }; | ||
const startResult = await service.start(startDeps); | ||
const results = await startResult.verifyOpenSearchPluginsState(pluginName); | ||
expect(results).not.toBeUndefined(); | ||
expect(results.length).toEqual(0); | ||
expect(opensearch.client.asInternalUser.cat.plugins).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
it('should return an array of CrossCompatibilityResult objects with the incompatible reason if the plugin is not installed', async () => { | ||
const pluginName = 'bar'; | ||
const startDeps = { opensearch, plugins }; | ||
const startResult = await service.start(startDeps); | ||
const results = await startResult.verifyOpenSearchPluginsState(pluginName); | ||
expect(results).not.toBeUndefined(); | ||
expect(results.length).toEqual(1); | ||
expect(results[0].pluginName).toEqual('os-plugin'); | ||
expect(results[0].isCompatible).toEqual(false); | ||
expect(results[0].incompatibleReason).toEqual( | ||
'OpenSearch plugin "os-plugin" in the version range "^3.0.0" is not installed on the OpenSearch for the OpenSearch Dashboards plugin to function as expected.' | ||
); | ||
expect(results[0].installedVersions).toEqual(new Set(['1.1.0.0'])); | ||
expect(opensearch.client.asInternalUser.cat.plugins).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
115 changes: 115 additions & 0 deletions
115
src/core/server/cross_compatibility/cross_compatibility_service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { CatPluginsResponse } from '@opensearch-project/opensearch/api/types'; | ||
import semver from 'semver'; | ||
import { CrossCompatibilityResult, CrossCompatibilityServiceStart } from './types'; | ||
import { CoreContext } from '../core_context'; | ||
import { Logger } from '../logging'; | ||
import { OpenSearchServiceStart } from '../opensearch'; | ||
import { CompatibleEnginePluginVersions, PluginName } from '../plugins/types'; | ||
|
||
export interface StartDeps { | ||
opensearch: OpenSearchServiceStart; | ||
plugins: Map<PluginName, CompatibleEnginePluginVersions>; | ||
} | ||
|
||
export class CrossCompatibilityService { | ||
private readonly log: Logger; | ||
|
||
constructor(coreContext: CoreContext) { | ||
this.log = coreContext.logger.get('version-compatibility-service'); | ||
} | ||
|
||
start({ opensearch, plugins }: StartDeps): CrossCompatibilityServiceStart { | ||
this.log.warn('Starting version compatibility service'); | ||
return { | ||
verifyOpenSearchPluginsState: (pluginName: string) => { | ||
const pluginOpenSearchDeps = plugins.get(pluginName) || {}; | ||
return this.verifyOpenSearchPluginsState(opensearch, pluginOpenSearchDeps); | ||
}, | ||
}; | ||
} | ||
|
||
public async getOpenSearchPlugins(opensearch: OpenSearchServiceStart) { | ||
// Makes cat.plugin api call to fetch list of OpenSearch plugins installed on the cluster | ||
try { | ||
const { body } = await opensearch.client.asInternalUser.cat.plugins<any[]>({ | ||
format: 'JSON', | ||
}); | ||
return body; | ||
} catch (error) { | ||
this.log.warn( | ||
`Cat API call to OpenSearch to get list of plugins installed on the cluster has failed: ${error}` | ||
); | ||
return []; | ||
} | ||
} | ||
|
||
public async checkPluginVersionCompatibility( | ||
pluginOpenSearchDeps: CompatibleEnginePluginVersions, | ||
opensearchInstalledPlugins: CatPluginsResponse | ||
) { | ||
const results: CrossCompatibilityResult[] = []; | ||
for (const [pluginName, versionRange] of Object.entries(pluginOpenSearchDeps)) { | ||
// add check to see if the Dashboards plugin version is compatible with installed OpenSearch plugin | ||
const { | ||
isCompatible, | ||
installedPluginVersions, | ||
} = await this.isVersionCompatibleOSPluginInstalled( | ||
opensearchInstalledPlugins, | ||
pluginName, | ||
versionRange | ||
); | ||
results.push({ | ||
pluginName, | ||
isCompatible: !isCompatible ? false : true, | ||
incompatibleReason: !isCompatible | ||
? `OpenSearch plugin "${pluginName}" in the version range "${versionRange}" is not installed on the OpenSearch for the OpenSearch Dashboards plugin to function as expected.` | ||
: '', | ||
installedVersions: installedPluginVersions, | ||
}); | ||
|
||
if (!isCompatible) { | ||
this.log.warn( | ||
`OpenSearch plugin "${pluginName}" is not installed on the cluster for the OpenSearch Dashboards plugin to function as expected.` | ||
); | ||
} | ||
} | ||
return results; | ||
} | ||
|
||
private async verifyOpenSearchPluginsState( | ||
opensearch: OpenSearchServiceStart, | ||
pluginOpenSearchDeps: CompatibleEnginePluginVersions | ||
): Promise<CrossCompatibilityResult[]> { | ||
this.log.warn('Checking OpenSearch Plugin version compatibility'); | ||
// make _cat/plugins?format=json call to the OpenSearch instance | ||
const opensearchInstalledPlugins = await this.getOpenSearchPlugins(opensearch); | ||
const results = await this.checkPluginVersionCompatibility( | ||
pluginOpenSearchDeps, | ||
opensearchInstalledPlugins | ||
); | ||
return results; | ||
} | ||
|
||
private async isVersionCompatibleOSPluginInstalled( | ||
opensearchInstalledPlugins: CatPluginsResponse, | ||
depPluginName: string, | ||
depPluginVersionRange: string | ||
) { | ||
let isCompatible = false; | ||
const installedPluginVersions = new Set<string>(); | ||
opensearchInstalledPlugins.forEach((obj) => { | ||
if (obj.component === depPluginName && obj.version) { | ||
installedPluginVersions.add(obj.version); | ||
if (semver.satisfies(semver.coerce(obj.version)!.version, depPluginVersionRange)) { | ||
isCompatible = true; | ||
} | ||
} | ||
}); | ||
return { isCompatible, installedPluginVersions }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
export { CrossCompatibilityService } from './cross_compatibility_service'; | ||
export { CrossCompatibilityResult, CrossCompatibilityServiceStart } from './types'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
import { CrossCompatibilityResult } from '../../types/cross_compatibility'; | ||
|
||
/** | ||
* API to check if the OpenSearch Dashboards plugin version is compatible with the installed OpenSearch plugin. | ||
* | ||
* @public | ||
*/ | ||
export interface CrossCompatibilityServiceStart { | ||
/** | ||
* Checks if the OpenSearch Dashboards plugin version is compatible with the installed OpenSearch plugin. | ||
* | ||
* @returns {Promise<CrossCompatibilityResult[]>} | ||
*/ | ||
verifyOpenSearchPluginsState: (pluginName: string) => Promise<CrossCompatibilityResult[]>; | ||
} | ||
|
||
export { CrossCompatibilityResult }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.