Skip to content

Commit 8a96664

Browse files
authored
Add Dynamic Config Service to Core Service (opensearch-project#7194)
* Add dynamic config service to core Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> * Add opensearch client implementation Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> * Add descriptions for the DAO clients Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> * Refactor DynamicConfigService Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> * Refactor dynamic config service start Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com> --------- Signed-off-by: Huy Nguyen <73027756+huyaboo@users.noreply.github.com>
1 parent 741c0d6 commit 8a96664

File tree

77 files changed

+3543
-261
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+3543
-261
lines changed

config/opensearch_dashboards.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,13 @@
359359
# This publishes the Application Usage and UI Metrics into the saved object, which can be accessed by /api/stats?extended=true&legacy=true&exclude_usage=false
360360
# usageCollection.uiMetric.enabled: false
361361

362+
# Set the value to true to enable enhancements for the data plugin
363+
# data.enhancements.enabled: false
364+
365+
# Set the value to true to enable dynamic config service to obtain configs from a config store. By default, it's disabled
366+
# dynamic_config_service.enabled: false
367+
362368
# Set the backend roles in groups or users, whoever has the backend roles or exactly match the user ids defined in this config will be regard as dashboard admin.
363369
# Dashboard admin will have the access to all the workspaces(workspace.enabled: true) and objects inside OpenSearch Dashboards.
364370
# opensearchDashboards.dashboardAdmin.groups: ["dashboard_admin"]
365-
# opensearchDashboards.dashboardAdmin.users: ["dashboard_admin"]
371+
# opensearchDashboards.dashboardAdmin.users: ["dashboard_admin"]

src/core/server/capabilities/integration_tests/capabilities_service.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import { Env } from '../../config';
3737
import { getEnvOptions } from '../../config/mocks';
3838
import { CapabilitiesService, CapabilitiesSetup } from '..';
3939
import { createHttpServer } from '../../http/test_utils';
40+
import { dynamicConfigServiceMock } from '../../config/dynamic_config_service.mock';
4041

4142
const coreId = Symbol('core');
4243

@@ -59,9 +60,12 @@ describe('CapabilitiesService', () => {
5960
env,
6061
logger: loggingSystemMock.create(),
6162
configService: {} as any,
63+
dynamicConfigService: dynamicConfigServiceMock.create(),
6264
});
6365
serviceSetup = await service.setup({ http: httpSetup });
64-
await server.start();
66+
await server.start({
67+
dynamicConfigService: dynamicConfigServiceMock.createInternalStartContract(),
68+
});
6569
});
6670

6771
afterEach(async () => {
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { IDynamicConfigService } from './dynamic_config_service';
7+
import {
8+
DynamicConfigurationClientMockProps,
9+
dynamicConfigurationClientMock,
10+
} from './service/configuration_client.mock';
11+
import {
12+
AsyncLocalStorageContext,
13+
DynamicConfigServiceSetup,
14+
DynamicConfigServiceStart,
15+
InternalDynamicConfigServiceSetup,
16+
InternalDynamicConfigServiceStart,
17+
} from './types';
18+
19+
const createDynamicConfigServiceMock = (
20+
mockClientReturnValues?: DynamicConfigurationClientMockProps,
21+
mockAsyncLocalStoreValues?: AsyncLocalStorageContext
22+
) => {
23+
const mocked: jest.Mocked<IDynamicConfigService> = {
24+
setup: jest.fn().mockReturnValue(createInternalSetupContractMock()),
25+
start: jest
26+
.fn()
27+
.mockReturnValue(
28+
createInternalStartContractMock(mockClientReturnValues, mockAsyncLocalStoreValues)
29+
),
30+
stop: jest.fn(),
31+
setSchema: jest.fn(),
32+
hasDefaultConfigs: jest.fn(),
33+
registerRoutesAndHandlers: jest.fn(),
34+
};
35+
36+
return mocked;
37+
};
38+
39+
const createSetupContractMock = () => {
40+
const mocked: jest.Mocked<DynamicConfigServiceSetup> = {
41+
registerDynamicConfigClientFactory: jest.fn(),
42+
registerAsyncLocalStoreRequestHeader: jest.fn(),
43+
getStartService: jest.fn(),
44+
};
45+
46+
return mocked;
47+
};
48+
const createInternalSetupContractMock = () => {
49+
const mocked: jest.Mocked<InternalDynamicConfigServiceSetup> = {
50+
registerDynamicConfigClientFactory: jest.fn(),
51+
registerAsyncLocalStoreRequestHeader: jest.fn(),
52+
getStartService: jest.fn(),
53+
};
54+
55+
return mocked;
56+
};
57+
const createStartContractMock = (
58+
mockClientReturnValues?: DynamicConfigurationClientMockProps,
59+
mockAsyncLocalStoreValues?: AsyncLocalStorageContext
60+
) => {
61+
const client = mockClientReturnValues
62+
? dynamicConfigurationClientMock.create(mockClientReturnValues)
63+
: dynamicConfigurationClientMock.create();
64+
65+
const mocked: jest.Mocked<DynamicConfigServiceStart> = {
66+
getClient: jest.fn().mockReturnValue(client),
67+
getAsyncLocalStore: jest.fn().mockReturnValue(mockAsyncLocalStoreValues),
68+
createStoreFromRequest: jest.fn().mockRejectedValue(mockAsyncLocalStoreValues),
69+
};
70+
71+
return mocked;
72+
};
73+
const createInternalStartContractMock = (
74+
mockClientReturnValues?: DynamicConfigurationClientMockProps,
75+
mockAsyncLocalStoreValues?: AsyncLocalStorageContext
76+
) => {
77+
const client = mockClientReturnValues
78+
? dynamicConfigurationClientMock.create(mockClientReturnValues)
79+
: dynamicConfigurationClientMock.create();
80+
81+
const mocked: jest.Mocked<InternalDynamicConfigServiceStart> = {
82+
getClient: jest.fn().mockReturnValue(client),
83+
getAsyncLocalStore: jest.fn().mockReturnValue(mockAsyncLocalStoreValues),
84+
createStoreFromRequest: jest.fn().mockRejectedValue(mockAsyncLocalStoreValues),
85+
};
86+
87+
return mocked;
88+
};
89+
90+
export const dynamicConfigServiceMock = {
91+
create: createDynamicConfigServiceMock,
92+
createInternalSetupContract: createInternalSetupContractMock,
93+
createInternalStartContract: createInternalStartContractMock,
94+
createSetupContract: createSetupContractMock,
95+
createStartContract: createStartContractMock,
96+
};
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
import { DynamicConfigService, IDynamicConfigService } from './dynamic_config_service';
7+
import { configServiceMock, httpServiceMock, opensearchServiceMock } from '../mocks';
8+
import { loggerMock } from '../logging/logger.mock';
9+
import { LoggerFactory } from '@osd/logging';
10+
import { schema, Type } from '@osd/config-schema';
11+
import { IDynamicConfigStoreClient } from 'opensearch-dashboards/server';
12+
13+
describe('DynamicConfigService', () => {
14+
let dynamicConfigService: IDynamicConfigService;
15+
const openSearchMock = opensearchServiceMock.createStart();
16+
17+
beforeEach(() => {
18+
const loggerFactoryMock = {} as LoggerFactory;
19+
loggerFactoryMock.get = jest.fn().mockReturnValue(loggerMock.create());
20+
21+
dynamicConfigService = new DynamicConfigService(
22+
configServiceMock.create(),
23+
{} as any,
24+
loggerFactoryMock
25+
);
26+
});
27+
28+
it('setup() and start() should return the same clients/async local stores', async () => {
29+
const dynamicConfigServiceSetup = await dynamicConfigService.setup();
30+
expect(dynamicConfigServiceSetup.getStartService()).toBeDefined();
31+
expect(dynamicConfigServiceSetup.registerDynamicConfigClientFactory).toBeDefined();
32+
expect(dynamicConfigServiceSetup.registerAsyncLocalStoreRequestHeader).toBeDefined();
33+
34+
dynamicConfigServiceSetup.registerDynamicConfigClientFactory({
35+
create: () => {
36+
return {} as IDynamicConfigStoreClient;
37+
},
38+
});
39+
40+
const dynamicConfigServiceStart = await dynamicConfigService.start({
41+
opensearch: openSearchMock,
42+
});
43+
expect(dynamicConfigServiceStart.getAsyncLocalStore).toBeDefined();
44+
expect(dynamicConfigServiceStart.getClient()).toBeDefined();
45+
46+
const actualGetStartServices = await dynamicConfigServiceSetup.getStartService();
47+
48+
expect(actualGetStartServices.getClient()).toMatchObject(dynamicConfigServiceStart.getClient());
49+
expect(actualGetStartServices.getAsyncLocalStore).toBeDefined();
50+
});
51+
52+
describe('After http is setup', () => {
53+
it('setupHTTP() should add the async local store preAuth middleware', () => {
54+
const httpSetupMock = httpServiceMock.createInternalSetupContract();
55+
dynamicConfigService.registerRoutesAndHandlers({ http: httpSetupMock });
56+
expect(httpSetupMock.registerOnPostAuth).toHaveBeenCalled();
57+
});
58+
});
59+
60+
it('setSchema() and hasDefaultConfigs() should set and check if schemas have been registered', () => {
61+
const schemaList: Map<string, Type<unknown>> = new Map();
62+
63+
schemaList.set(
64+
'foo',
65+
schema.object({
66+
a: schema.boolean(),
67+
b: schema.object({
68+
c: schema.string(),
69+
}),
70+
})
71+
);
72+
schemaList.set(
73+
'bar',
74+
schema.object({
75+
a: schema.boolean(),
76+
b: schema.boolean(),
77+
c: schema.object({
78+
d: schema.string(),
79+
}),
80+
})
81+
);
82+
schemaList.set(
83+
'baz',
84+
schema.object({
85+
a: schema.object({
86+
c: schema.object({
87+
d: schema.boolean(),
88+
}),
89+
}),
90+
})
91+
);
92+
93+
schemaList.forEach((value, key) => {
94+
dynamicConfigService.setSchema(key, value);
95+
});
96+
97+
[...schemaList.keys()].forEach((key) => {
98+
expect(dynamicConfigService.hasDefaultConfigs({ name: key })).toBe(true);
99+
});
100+
101+
expect(dynamicConfigService.hasDefaultConfigs({ name: 'nonexistent_config' })).toBe(false);
102+
});
103+
});

0 commit comments

Comments
 (0)