Skip to content

Commit

Permalink
add version number to newly created datasource object
Browse files Browse the repository at this point in the history
Signed-off-by: Zilong Xia <[email protected]>
  • Loading branch information
ZilongX committed Mar 18, 2024
1 parent 05abf5e commit 2c900dd
Show file tree
Hide file tree
Showing 9 changed files with 436 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- [Workspace] Add workspace id in basePath ([#6060](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6060))
- Implement new home page ([#6065](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6065))
- Add sidecar service ([#5920](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5920))
- [Multiple Datasource] Add datasource version number to newly created data source object([#6178](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6178))


### 🐛 Bug Fixes
Expand Down
8 changes: 8 additions & 0 deletions src/plugins/data_source/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../common';
import { ensureRawRequest } from '../../../../src/core/server/http/router';
import { createDataSourceError } from './lib/error';
import { registerTestConnectionRoute } from './routes/test_connection';
import { registerFetchDataSourceVersionRoute } from './routes/fetch_data_source_version';
import { AuthenticationMethodRegistery, IAuthenticationMethodRegistery } from './auth_registry';
import { CustomApiSchemaRegistry } from './schema_registry';

Expand Down Expand Up @@ -133,6 +134,13 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
authRegistryPromise,
customApiSchemaRegistryPromise
);
registerFetchDataSourceVersionRoute(
router,
dataSourceService,
cryptographyServiceSetup,
authRegistryPromise,
customApiSchemaRegistryPromise
);

const registerCredentialProvider = (method: AuthenticationMethod) => {
this.logger.debug(`Registered Credential Provider for authType = ${method.name}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ describe('DataSourceManagement: data_source_connection_validator.ts', () => {
expect(validateDataSourcesResponse.statusCode).toBe(200);
});

test('fetchDataSourceVersion - Success: opensearch client response code is 200 and response body have version number', async () => {
const opensearchClient = opensearchServiceMock.createOpenSearchClient();
opensearchClient.info.mockResolvedValue(
opensearchServiceMock.createApiResponse({
statusCode: 200,
body: {
version: {
number: '2.11.0',
},
},
})
);
const dataSourceValidator = new DataSourceConnectionValidator(opensearchClient, {});
const fetchDataSourcesVersionResponse = await dataSourceValidator.fetchDataSourceVersion();
expect(fetchDataSourcesVersionResponse).toBe('2.11.0');
});

test('failure: opensearch client response code is 200 but response body not have cluster name', async () => {
try {
const opensearchClient = opensearchServiceMock.createOpenSearchClient();
Expand All @@ -43,6 +60,22 @@ describe('DataSourceManagement: data_source_connection_validator.ts', () => {
}
});

// In case fetchDataSourceVersion call succeeded yet did not return version number, return an empty version instead of raising exceptions
test('fetchDataSourceVersion - Success:opensearch client response code is 200 but response body does not have version number', async () => {
const opensearchClient = opensearchServiceMock.createOpenSearchClient();
opensearchClient.info.mockResolvedValue(
opensearchServiceMock.createApiResponse({
statusCode: 200,
body: {
Message: 'Response without version number.',
},
})
);
const dataSourceValidator = new DataSourceConnectionValidator(opensearchClient, {});
const fetchDataSourcesVersionResponse = await dataSourceValidator.fetchDataSourceVersion();
expect(fetchDataSourcesVersionResponse).toBe('');
});

test('failure: opensearch client response code is other than 200', async () => {
const statusCodeList = [100, 202, 300, 400, 500];
statusCodeList.forEach(async function (code) {
Expand All @@ -64,6 +97,25 @@ describe('DataSourceManagement: data_source_connection_validator.ts', () => {
}
});
});

// In case fetchDataSourceVersion call failed, return an empty version instead of raising exceptions
test('fetchDataSourceVersion - Failure: opensearch client response code is other than 200', async () => {
const statusCodeList = [100, 202, 300, 400, 500];
statusCodeList.forEach(async function (code) {
const opensearchClient = opensearchServiceMock.createOpenSearchClient();
opensearchClient.info.mockResolvedValue(
opensearchServiceMock.createApiResponse({
statusCode: code,
body: {
Message: 'Your request is not correct.',
},
})
);
const dataSourceValidator = new DataSourceConnectionValidator(opensearchClient, {});
const fetchDataSourcesVersionResponse = await dataSourceValidator.fetchDataSourceVersion();
expect(fetchDataSourcesVersionResponse).toBe('');
});
});
});

describe('Test datasource connection for SigV4 auth', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,27 @@ export class DataSourceConnectionValidator {
throw createDataSourceError(e);
}
}

async fetchDataSourceVersion() {
let dataSourceVersion = '';
try {
// OpenSearch Serverless does not have version concept
if (
this.dataSourceAttr.auth?.credentials?.service === SigV4ServiceName.OpenSearchServerless
) {
return dataSourceVersion;

Check warning on line 46 in src/plugins/data_source/server/routes/data_source_connection_validator.ts

View check run for this annotation

Codecov / codecov/patch

src/plugins/data_source/server/routes/data_source_connection_validator.ts#L46

Added line #L46 was not covered by tests
}
await this.callDataCluster
.info()
.then((response) => response.body)
.then((body) => {
dataSourceVersion = body.version.number;
});

return dataSourceVersion;
} catch (e) {
// return empty dataSoyrce version instead of throwing exception in case info() api call fails
return dataSourceVersion;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import supertest from 'supertest';
import { UnwrapPromise } from '@osd/utility-types';
import { setupServer } from '../../../../../src/core/server/test_utils';

import { IAuthenticationMethodRegistery } from '../auth_registry';
import { authenticationMethodRegisteryMock } from '../auth_registry/authentication_methods_registry.mock';
import { CustomApiSchemaRegistry } from '../schema_registry';
import { DataSourceServiceSetup } from '../../server/data_source_service';
import { CryptographyServiceSetup } from '../cryptography_service';
import { registerFetchDataSourceVersionRoute } from './fetch_data_source_version';
import { AuthType } from '../../common/data_sources';
// eslint-disable-next-line @osd/eslint/no-restricted-paths
import { opensearchClientMock } from '../../../../../src/core/server/opensearch/client/mocks';

type SetupServerReturn = UnwrapPromise<ReturnType<typeof setupServer>>;

const URL = '/internal/data-source-management/fetchDataSourceVersion';

describe(`Fetch DataSource Version ${URL}`, () => {
let server: SetupServerReturn['server'];
let httpSetup: SetupServerReturn['httpSetup'];
let handlerContext: SetupServerReturn['handlerContext'];
let cryptographyMock: jest.Mocked<CryptographyServiceSetup>;
const customApiSchemaRegistry = new CustomApiSchemaRegistry();
let customApiSchemaRegistryPromise: Promise<CustomApiSchemaRegistry>;
let dataSourceClient: ReturnType<typeof opensearchClientMock.createInternalClient>;
let dataSourceServiceSetupMock: DataSourceServiceSetup;
let authRegistryPromiseMock: Promise<IAuthenticationMethodRegistery>;
const dataSourceAttr = {
endpoint: 'https://test.com',
auth: {
type: AuthType.UsernamePasswordType,
credentials: {
username: 'testUser',
password: 'testPassword',
},
},
};

const dataSourceAttrMissingCredentialForNoAuth = {
endpoint: 'https://test.com',
auth: {
type: AuthType.NoAuth,
credentials: {},
},
};

const dataSourceAttrMissingCredentialForBasicAuth = {
endpoint: 'https://test.com',
auth: {
type: AuthType.UsernamePasswordType,
credentials: {},
},
};

const dataSourceAttrMissingCredentialForSigV4Auth = {
endpoint: 'https://test.com',
auth: {
type: AuthType.SigV4,
credentials: {},
},
};

const dataSourceAttrPartialCredentialForSigV4Auth = {
endpoint: 'https://test.com',
auth: {
type: AuthType.SigV4,
credentials: {
accessKey: 'testKey',
service: 'service',
},
},
};

const dataSourceAttrPartialCredentialForBasicAuth = {
endpoint: 'https://test.com',
auth: {
type: AuthType.UsernamePasswordType,
credentials: {
username: 'testName',
},
},
};

const dataSourceAttrForSigV4Auth = {
endpoint: 'https://test.com',
auth: {
type: AuthType.SigV4,
credentials: {
accessKey: 'testKey',
service: 'es',
secretKey: 'testSecret',
region: 'testRegion',
},
},
};

beforeEach(async () => {
({ server, httpSetup, handlerContext } = await setupServer());
customApiSchemaRegistryPromise = Promise.resolve(customApiSchemaRegistry);
authRegistryPromiseMock = Promise.resolve(authenticationMethodRegisteryMock.create());
dataSourceClient = opensearchClientMock.createInternalClient();

dataSourceServiceSetupMock = {
getDataSourceClient: jest.fn(() => Promise.resolve(dataSourceClient)),
getDataSourceLegacyClient: jest.fn(),
};

const router = httpSetup.createRouter('');
dataSourceClient.info.mockImplementationOnce(() =>
opensearchClientMock.createSuccessTransportRequestPromise({ version: { number: '2.11.0' } })
);
registerFetchDataSourceVersionRoute(
router,
dataSourceServiceSetupMock,
cryptographyMock,
authRegistryPromiseMock,
customApiSchemaRegistryPromise
);

await server.start();
});

afterEach(async () => {
await server.stop();
});

it('shows successful response', async () => {
const result = await supertest(httpSetup.server.listener)
.post(URL)
.send({
id: 'testId',
dataSourceAttr,
})
.expect(200);
expect(result.body).toEqual({ dataSourceVersion: '2.11.0' });
expect(dataSourceServiceSetupMock.getDataSourceClient).toHaveBeenCalledWith(
expect.objectContaining({
savedObjects: handlerContext.savedObjects.client,
cryptography: cryptographyMock,
dataSourceId: 'testId',
testClientDataSourceAttr: dataSourceAttr,
customApiSchemaRegistryPromise,
})
);
});

it('no credential with no auth should succeed', async () => {
const result = await supertest(httpSetup.server.listener)
.post(URL)
.send({
id: 'testId',
dataSourceAttr: dataSourceAttrMissingCredentialForNoAuth,
})
.expect(200);
expect(result.body).toEqual({ dataSourceVersion: '2.11.0' });
});

it('no credential with basic auth should fail', async () => {
const result = await supertest(httpSetup.server.listener)
.post(URL)
.send({
id: 'testId',
dataSourceAttr: dataSourceAttrMissingCredentialForBasicAuth,
})
.expect(400);
expect(result.body.error).toEqual('Bad Request');
});

it('no credential with sigv4 auth should fail', async () => {
const result = await supertest(httpSetup.server.listener)
.post(URL)
.send({
id: 'testId',
dataSourceAttr: dataSourceAttrMissingCredentialForSigV4Auth,
})
.expect(400);
expect(result.body.error).toEqual('Bad Request');
});

it('partial credential with sigv4 auth should fail', async () => {
const result = await supertest(httpSetup.server.listener)
.post(URL)
.send({
id: 'testId',
dataSourceAttr: dataSourceAttrPartialCredentialForSigV4Auth,
})
.expect(400);
expect(result.body.error).toEqual('Bad Request');
});

it('partial credential with basic auth should fail', async () => {
const result = await supertest(httpSetup.server.listener)
.post(URL)
.send({
id: 'testId',
dataSourceAttr: dataSourceAttrPartialCredentialForBasicAuth,
})
.expect(400);
expect(result.body.error).toEqual('Bad Request');
});

it('full credential with sigV4 auth should success', async () => {
const result = await supertest(httpSetup.server.listener)
.post(URL)
.send({
id: 'testId',
dataSourceAttr: dataSourceAttrForSigV4Auth,
})
.expect(200);
expect(result.body).toEqual({ dataSourceVersion: '2.11.0' });
});
});
Loading

0 comments on commit 2c900dd

Please sign in to comment.