Skip to content

Commit eff7cb5

Browse files
authored
Add api registry and allow it to be added into client config in data source plugin (#5895)
* add api registry and allow it to be added into client config Signed-off-by: Lu Yu <[email protected]> * add changelog Signed-off-by: Lu Yu <[email protected]> * add documentation for multi data source plugin api registry Signed-off-by: Lu Yu <[email protected]> * change to resolve promise before calling getQueryClient Signed-off-by: Lu Yu <[email protected]> --------- Signed-off-by: Lu Yu <[email protected]>
1 parent 55443f7 commit eff7cb5

13 files changed

+129
-9
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
4040
- [Multiple Datasource] Add datasource picker to import saved object flyout when multiple data source is enabled ([#5781](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5781))
4141
- [Multiple Datasource] Add interfaces to register add-on authentication method from plug-in module ([#5851](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5851))
4242
- [Multiple Datasource] Able to Hide "Local Cluster" option from datasource DropDown ([#5827](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5827))
43+
- [Multiple Datasource] Add api registry and allow it to be added into client config in data source plugin ([#5895](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5895))
4344

4445
### 🐛 Bug Fixes
4546

docs/multi-datasource/client_management_design.md

+25-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ This design is part of the OpenSearch Dashboards multi data source project [[RFC
1010
2. How to expose data source clients to callers through clean interfaces?
1111
3. How to maintain backwards compatibility if user turn off this feature?
1212
4. How to manage multiple clients/connection efficiently, and not consume all the memory?
13+
5. Where should we implement the core logic?
14+
6. How to register custom API schema and add into the client during initialization?
1315

1416
## 2. Requirements
1517

@@ -87,6 +89,20 @@ Current `opensearch service` exists in core. The module we'll implement has simi
8789
2. We don't mess up with OpenSearch Dashboards core, since this is an experimental feature, the potential risk of breaking existing behavior will be lowered if we use plugin. Worst case, user could just uninstall the plugin.
8890
3. Complexity wise, it's about the same amount of work.
8991

92+
**6.How to register custom API schema and add into the client during initialization?**
93+
Currently, OpenSearch Dashboards plugins uses the following to initialize instance of Cluster client and register the custom API schema via the plugins configuration option.
94+
```ts
95+
core.opensearch.legacy.createClient(
96+
'exampleName',
97+
{
98+
plugins: [ExamplePlugin],
99+
}
100+
);
101+
```
102+
The downside of this approach is the schema is defined inside the plugin and there is no centralized registry for the schema making it not easy to access. This will be resolved by implementing a centralized API schema registry, and consumers can add data source plugin as dependency and be able to consume all the registered schema, eg. `dataSource.registerCustomApiSchema(sqlPlugin)`.
103+
104+
The schema will be added into the client configuration when multi data source client is initiated.
105+
90106
### 4.1 Data Source Plugin
91107

92108
Create a data source plugin that only has server side code, to hold most core logic of data source feature. Including data service, crypto service, and client management. A plugin will have all setup, start and stop as lifecycle.
@@ -146,12 +162,20 @@ context.core.opensearch.legacy.client.callAsCurrentUser;
146162
context.core.opensearch.client.asCurrentUser;
147163
```
148164

149-
Since deprecating legacy client could be a bigger scope of project, multiple data source feature still need to implement a substitute for it as for now. Implementation should be done in a way that's decoupled with data source client as much as possible, for easier deprecation. Similar to [opensearch legacy service](https://github.com/opensearch-project/OpenSearch-Dashboards/tree/main/src/core/server/opensearch/legacy) in core.
165+
Since deprecating legacy client could be a bigger scope of project, multiple data source feature still need to implement a substitute for it as for now. Implementation should be done in a way that's decoupled with data source client as much as possible, for easier deprecation. Similar to [opensearch legacy service](https://github.com/opensearch-project/OpenSearch-Dashboards/tree/main/src/core/server/opensearch/legacy) in core. See how to intialize the data source client below:
150166

151167
```ts
152168
context.dataSource.opensearch.legacy.getClient(dataSourceId);
153169
```
154170

171+
If using Legacy cluster client with asScoped and callAsCurrentUser, the following is the equivalent when using data source client:
172+
```ts
173+
//legacy cluster client
174+
const response = client.asScoped(request).callAsCurrentUser(format, params);
175+
//equivalent when using data source client instead
176+
const response = client.callAPI(format, params);
177+
```
178+
155179
### 4.3 Register datasource client to core context
156180

157181
This is for plugin to access data source client via request handler. For example, by `core.client.search(params)`. It’s a very common use case for plugin to access cluster while handling request. In fact data plugin uses it in its search module to get client, and I’ll talk about it in details in next section.

src/plugins/data_source/server/client/client_config.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,16 @@ import { DataSourcePluginConfigType } from '../../config';
1515
export function parseClientOptions(
1616
// TODO: will use client configs, that comes from a merge result of user config and default opensearch client config,
1717
config: DataSourcePluginConfigType,
18-
endpoint: string
18+
endpoint: string,
19+
registeredSchema: any[]
1920
): ClientOptions {
2021
const clientOptions: ClientOptions = {
2122
node: endpoint,
2223
ssl: {
2324
requestCert: true,
2425
rejectUnauthorized: true,
2526
},
27+
plugins: registeredSchema,
2628
};
2729

2830
return clientOptions;

src/plugins/data_source/server/client/configure_client.test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { opensearchClientMock } from '../../../../core/server/opensearch/client/
2222
import { cryptographyServiceSetupMock } from '../cryptography_service.mocks';
2323
import { CryptographyServiceSetup } from '../cryptography_service';
2424
import { DataSourceClientParams } from '../types';
25+
import { CustomApiSchemaRegistry } from '../schema_registry';
2526

2627
const DATA_SOURCE_ID = 'a54b76ec86771ee865a0f74a305dfff8';
2728

@@ -38,12 +39,14 @@ describe('configureClient', () => {
3839
let dataSourceClientParams: DataSourceClientParams;
3940
let usernamePasswordAuthContent: UsernamePasswordTypedContent;
4041
let sigV4AuthContent: SigV4Content;
42+
let customApiSchemaRegistry: CustomApiSchemaRegistry;
4143

4244
beforeEach(() => {
4345
dsClient = opensearchClientMock.createInternalClient();
4446
logger = loggingSystemMock.createLogger();
4547
savedObjectsMock = savedObjectsClientMock.create();
4648
cryptographyMock = cryptographyServiceSetupMock.create();
49+
customApiSchemaRegistry = new CustomApiSchemaRegistry();
4750

4851
config = {
4952
enabled: true,
@@ -95,6 +98,7 @@ describe('configureClient', () => {
9598
dataSourceId: DATA_SOURCE_ID,
9699
savedObjects: savedObjectsMock,
97100
cryptography: cryptographyMock,
101+
customApiSchemaRegistryPromise: Promise.resolve(customApiSchemaRegistry),
98102
};
99103

100104
ClientMock.mockImplementation(() => dsClient);

src/plugins/data_source/server/client/configure_client.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,13 @@ import {
2929
} from './configure_client_utils';
3030

3131
export const configureClient = async (
32-
{ dataSourceId, savedObjects, cryptography, testClientDataSourceAttr }: DataSourceClientParams,
32+
{
33+
dataSourceId,
34+
savedObjects,
35+
cryptography,
36+
testClientDataSourceAttr,
37+
customApiSchemaRegistryPromise,
38+
}: DataSourceClientParams,
3339
openSearchClientPoolSetup: OpenSearchClientPoolSetup,
3440
config: DataSourcePluginConfigType,
3541
logger: Logger
@@ -64,10 +70,13 @@ export const configureClient = async (
6470
dataSourceId
6571
) as Client;
6672

73+
const registeredSchema = (await customApiSchemaRegistryPromise).getAll();
74+
6775
return await getQueryClient(
6876
dataSource,
6977
openSearchClientPoolSetup.addClientToPool,
7078
config,
79+
registeredSchema,
7180
cryptography,
7281
rootClient,
7382
dataSourceId,
@@ -87,6 +96,7 @@ export const configureClient = async (
8796
*
8897
* @param rootClient root client for the given data source.
8998
* @param dataSourceAttr data source saved object attributes
99+
* @param registeredSchema registered API schema
90100
* @param cryptography cryptography service for password encryption / decryption
91101
* @param config data source config
92102
* @param addClientToPool function to add client to client pool
@@ -98,6 +108,7 @@ const getQueryClient = async (
98108
dataSourceAttr: DataSourceAttributes,
99109
addClientToPool: (endpoint: string, authType: AuthType, client: Client | LegacyClient) => void,
100110
config: DataSourcePluginConfigType,
111+
registeredSchema: any[],
101112
cryptography?: CryptographyServiceSetup,
102113
rootClient?: Client,
103114
dataSourceId?: string,
@@ -107,7 +118,7 @@ const getQueryClient = async (
107118
auth: { type },
108119
endpoint,
109120
} = dataSourceAttr;
110-
const clientOptions = parseClientOptions(config, endpoint);
121+
const clientOptions = parseClientOptions(config, endpoint, registeredSchema);
111122
const cacheKey = generateCacheKey(dataSourceAttr, dataSourceId);
112123

113124
switch (type) {

src/plugins/data_source/server/legacy/client_config.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ import { DataSourcePluginConfigType } from '../../config';
1515
export function parseClientOptions(
1616
// TODO: will use client configs, that comes from a merge result of user config and default legacy client config,
1717
config: DataSourcePluginConfigType,
18-
endpoint: string
18+
endpoint: string,
19+
registeredSchema: any[]
1920
): ConfigOptions {
2021
const configOptions: ConfigOptions = {
2122
host: endpoint,
2223
ssl: {
2324
rejectUnauthorized: true,
2425
},
26+
plugins: registeredSchema,
2527
};
2628

2729
return configOptions;

src/plugins/data_source/server/legacy/configure_legacy_client.test.ts

+3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { OpenSearchClientPoolSetup } from '../client';
1515
import { ConfigOptions } from 'elasticsearch';
1616
import { ClientMock, parseClientOptionsMock } from './configure_legacy_client.test.mocks';
1717
import { configureLegacyClient } from './configure_legacy_client';
18+
import { CustomApiSchemaRegistry } from '../schema_registry';
1819

1920
const DATA_SOURCE_ID = 'a54b76ec86771ee865a0f74a305dfff8';
2021

@@ -35,6 +36,7 @@ describe('configureLegacyClient', () => {
3536
};
3637
let dataSourceClientParams: DataSourceClientParams;
3738
let callApiParams: LegacyClientCallAPIParams;
39+
const customApiSchemaRegistry = new CustomApiSchemaRegistry();
3840

3941
const mockResponse = { data: 'ping' };
4042

@@ -98,6 +100,7 @@ describe('configureLegacyClient', () => {
98100
dataSourceId: DATA_SOURCE_ID,
99101
savedObjects: savedObjectsMock,
100102
cryptography: cryptographyMock,
103+
customApiSchemaRegistryPromise: Promise.resolve(customApiSchemaRegistry),
101104
};
102105

103106
ClientMock.mockImplementation(() => mockOpenSearchClientInstance);

src/plugins/data_source/server/legacy/configure_legacy_client.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ import {
3636
} from '../client/configure_client_utils';
3737

3838
export const configureLegacyClient = async (
39-
{ dataSourceId, savedObjects, cryptography }: DataSourceClientParams,
39+
{
40+
dataSourceId,
41+
savedObjects,
42+
cryptography,
43+
customApiSchemaRegistryPromise,
44+
}: DataSourceClientParams,
4045
callApiParams: LegacyClientCallAPIParams,
4146
openSearchClientPoolSetup: OpenSearchClientPoolSetup,
4247
config: DataSourcePluginConfigType,
@@ -50,12 +55,15 @@ export const configureLegacyClient = async (
5055
dataSourceId
5156
) as LegacyClient;
5257

58+
const registeredSchema = (await customApiSchemaRegistryPromise).getAll();
59+
5360
return await getQueryClient(
5461
dataSourceAttr,
5562
cryptography,
5663
callApiParams,
5764
openSearchClientPoolSetup.addClientToPool,
5865
config,
66+
registeredSchema,
5967
rootClient,
6068
dataSourceId
6169
);
@@ -75,6 +83,7 @@ export const configureLegacyClient = async (
7583
* @param dataSourceAttr data source saved object attributes
7684
* @param cryptography cryptography service for password encryption / decryption
7785
* @param config data source config
86+
* @param registeredSchema registered API schema
7887
* @param addClientToPool function to add client to client pool
7988
* @param dataSourceId id of data source saved Object
8089
* @returns child client.
@@ -85,14 +94,15 @@ const getQueryClient = async (
8594
{ endpoint, clientParams, options }: LegacyClientCallAPIParams,
8695
addClientToPool: (endpoint: string, authType: AuthType, client: Client | LegacyClient) => void,
8796
config: DataSourcePluginConfigType,
97+
registeredSchema: any[],
8898
rootClient?: LegacyClient,
8999
dataSourceId?: string
90100
) => {
91101
const {
92102
auth: { type },
93103
endpoint: nodeUrl,
94104
} = dataSourceAttr;
95-
const clientOptions = parseClientOptions(config, nodeUrl);
105+
const clientOptions = parseClientOptions(config, nodeUrl, registeredSchema);
96106
const cacheKey = generateCacheKey(dataSourceAttr, dataSourceId);
97107

98108
switch (type) {

src/plugins/data_source/server/plugin.ts

+15-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { ensureRawRequest } from '../../../../src/core/server/http/router';
3131
import { createDataSourceError } from './lib/error';
3232
import { registerTestConnectionRoute } from './routes/test_connection';
3333
import { AuthenticationMethodRegistery, IAuthenticationMethodRegistery } from './auth_registry';
34+
import { CustomApiSchemaRegistry } from './schema_registry';
3435

3536
export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourcePluginStart> {
3637
private readonly logger: Logger;
@@ -39,6 +40,7 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
3940
private readonly config$: Observable<DataSourcePluginConfigType>;
4041
private started = false;
4142
private authMethodsRegistry = new AuthenticationMethodRegistery();
43+
private customApiSchemaRegistry = new CustomApiSchemaRegistry();
4244

4345
constructor(private initializerContext: PluginInitializerContext<DataSourcePluginConfigType>) {
4446
this.logger = this.initializerContext.logger.get();
@@ -104,6 +106,11 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
104106
return dataSourcePluginStart.getAuthenticationMethodRegistery();
105107
});
106108

109+
const customApiSchemaRegistryPromise = core.getStartServices().then(([, , selfStart]) => {
110+
const dataSourcePluginStart = selfStart as DataSourcePluginStart;
111+
return dataSourcePluginStart.getCustomApiSchemaRegistry();
112+
});
113+
107114
// Register data source plugin context to route handler context
108115
core.http.registerRouteHandlerContext(
109116
'dataSource',
@@ -112,7 +119,8 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
112119
cryptographyServiceSetup,
113120
this.logger,
114121
auditTrailPromise,
115-
authRegistryPromise
122+
authRegistryPromise,
123+
customApiSchemaRegistryPromise
116124
)
117125
);
118126

@@ -135,6 +143,7 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
135143
return {
136144
createDataSourceError: (e: any) => createDataSourceError(e),
137145
registerCredentialProvider,
146+
registerCustomApiSchema: (schema: any) => this.customApiSchemaRegistry.register(schema),
138147
};
139148
}
140149

@@ -143,6 +152,7 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
143152
this.started = true;
144153
return {
145154
getAuthenticationMethodRegistery: () => this.authMethodsRegistry,
155+
getCustomApiSchemaRegistry: () => this.customApiSchemaRegistry,
146156
};
147157
}
148158

@@ -155,7 +165,8 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
155165
cryptography: CryptographyServiceSetup,
156166
logger: Logger,
157167
auditTrailPromise: Promise<AuditorFactory>,
158-
authRegistryPromise: Promise<IAuthenticationMethodRegistery>
168+
authRegistryPromise: Promise<IAuthenticationMethodRegistery>,
169+
customApiSchemaRegistryPromise: Promise<CustomApiSchemaRegistry>
159170
): IContextProvider<RequestHandler<unknown, unknown, unknown>, 'dataSource'> => {
160171
return (context, req) => {
161172
return {
@@ -169,6 +180,7 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
169180
dataSourceId,
170181
savedObjects: context.core.savedObjects.client,
171182
cryptography,
183+
customApiSchemaRegistryPromise,
172184
});
173185
},
174186
legacy: {
@@ -177,6 +189,7 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
177189
dataSourceId,
178190
savedObjects: context.core.savedObjects.client,
179191
cryptography,
192+
customApiSchemaRegistryPromise,
180193
});
181194
},
182195
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { CustomApiSchemaRegistry } from './custom_api_schema_registry';
7+
8+
describe('CustomApiSchemaRegistry', () => {
9+
let registry: CustomApiSchemaRegistry;
10+
11+
beforeEach(() => {
12+
registry = new CustomApiSchemaRegistry();
13+
});
14+
15+
it('allows to register and get api schema', () => {
16+
const sqlPlugin = () => {};
17+
registry.register(sqlPlugin);
18+
expect(registry.getAll()).toEqual([sqlPlugin]);
19+
});
20+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
export class CustomApiSchemaRegistry {
6+
private readonly schemaRegistry: any[];
7+
8+
constructor() {
9+
this.schemaRegistry = new Array();
10+
}
11+
12+
public register(schema: any) {
13+
this.schemaRegistry.push(schema);
14+
}
15+
16+
public getAll(): any[] {
17+
return this.schemaRegistry;
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
export { CustomApiSchemaRegistry } from './custom_api_schema_registry';

0 commit comments

Comments
 (0)