Skip to content

Commit

Permalink
Add Dynamic Config Service to Core Service (#7194)
Browse files Browse the repository at this point in the history
* Add dynamic config service to core

Signed-off-by: Huy Nguyen <[email protected]>

* Add opensearch client implementation

Signed-off-by: Huy Nguyen <[email protected]>

* Add descriptions for the DAO clients

Signed-off-by: Huy Nguyen <[email protected]>

* Refactor DynamicConfigService

Signed-off-by: Huy Nguyen <[email protected]>

* Refactor dynamic config service start

Signed-off-by: Huy Nguyen <[email protected]>

---------

Signed-off-by: Huy Nguyen <[email protected]>
(cherry picked from commit 8a96664)

Fix issue when bootstrapping on 2.x (#7901)

* Fix issue bootstrapping dynamic config service

Signed-off-by: Huy Nguyen <[email protected]>

* Changeset file for PR #7901 created/updated

---------

Signed-off-by: Huy Nguyen <[email protected]>
Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
(cherry picked from commit 07504f6)

Fix mock import (#7922)

Signed-off-by: Huy Nguyen <[email protected]>
(cherry picked from commit b370871)
  • Loading branch information
huyaboo committed Sep 2, 2024
1 parent bb0d30e commit b40165e
Show file tree
Hide file tree
Showing 79 changed files with 3,637 additions and 260 deletions.
2 changes: 2 additions & 0 deletions changelogs/fragments/7901.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
fix:
- Fix bootstrap errors in 2.x ([#7901](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7901))
3 changes: 3 additions & 0 deletions config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,9 @@
# Set the value to true to enable enhancements for the data plugin
# data.enhancements.enabled: false

# Set the value to true to enable dynamic config service to obtain configs from a config store. By default, it's disabled
# dynamic_config_service.enabled: false

# 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.
# Dashboard admin will have the access to all the workspaces(workspace.enabled: true) and objects inside OpenSearch Dashboards.
# opensearchDashboards.dashboardAdmin.groups: ["dashboard_admin"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { Env } from '../../config';
import { getEnvOptions } from '../../config/mocks';
import { CapabilitiesService, CapabilitiesSetup } from '..';
import { createHttpServer } from '../../http/test_utils';
import { dynamicConfigServiceMock } from '../../config/dynamic_config_service.mock';

const coreId = Symbol('core');

Expand All @@ -59,9 +60,12 @@ describe('CapabilitiesService', () => {
env,
logger: loggingSystemMock.create(),
configService: {} as any,
dynamicConfigService: dynamicConfigServiceMock.create(),
});
serviceSetup = await service.setup({ http: httpSetup });
await server.start();
await server.start({
dynamicConfigService: dynamicConfigServiceMock.createInternalStartContract(),
});
});

afterEach(async () => {
Expand Down
96 changes: 96 additions & 0 deletions src/core/server/config/dynamic_config_service.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { IDynamicConfigService } from './dynamic_config_service';
import {
DynamicConfigurationClientMockProps,
dynamicConfigurationClientMock,
} from './service/configuration_client.mock';
import {
AsyncLocalStorageContext,
DynamicConfigServiceSetup,
DynamicConfigServiceStart,
InternalDynamicConfigServiceSetup,
InternalDynamicConfigServiceStart,
} from './types';

const createDynamicConfigServiceMock = (
mockClientReturnValues?: DynamicConfigurationClientMockProps,
mockAsyncLocalStoreValues?: AsyncLocalStorageContext
) => {
const mocked: jest.Mocked<IDynamicConfigService> = {
setup: jest.fn().mockReturnValue(createInternalSetupContractMock()),
start: jest
.fn()
.mockReturnValue(
createInternalStartContractMock(mockClientReturnValues, mockAsyncLocalStoreValues)
),
stop: jest.fn(),
setSchema: jest.fn(),
hasDefaultConfigs: jest.fn(),
registerRoutesAndHandlers: jest.fn(),
};

return mocked;
};

const createSetupContractMock = () => {
const mocked: jest.Mocked<DynamicConfigServiceSetup> = {
registerDynamicConfigClientFactory: jest.fn(),
registerAsyncLocalStoreRequestHeader: jest.fn(),
getStartService: jest.fn(),
};

return mocked;
};
const createInternalSetupContractMock = () => {
const mocked: jest.Mocked<InternalDynamicConfigServiceSetup> = {
registerDynamicConfigClientFactory: jest.fn(),
registerAsyncLocalStoreRequestHeader: jest.fn(),
getStartService: jest.fn(),
};

return mocked;
};
const createStartContractMock = (
mockClientReturnValues?: DynamicConfigurationClientMockProps,
mockAsyncLocalStoreValues?: AsyncLocalStorageContext
) => {
const client = mockClientReturnValues
? dynamicConfigurationClientMock.create(mockClientReturnValues)
: dynamicConfigurationClientMock.create();

const mocked: jest.Mocked<DynamicConfigServiceStart> = {
getClient: jest.fn().mockReturnValue(client),
getAsyncLocalStore: jest.fn().mockReturnValue(mockAsyncLocalStoreValues),
createStoreFromRequest: jest.fn().mockRejectedValue(mockAsyncLocalStoreValues),
};

return mocked;
};
const createInternalStartContractMock = (
mockClientReturnValues?: DynamicConfigurationClientMockProps,
mockAsyncLocalStoreValues?: AsyncLocalStorageContext
) => {
const client = mockClientReturnValues
? dynamicConfigurationClientMock.create(mockClientReturnValues)
: dynamicConfigurationClientMock.create();

const mocked: jest.Mocked<InternalDynamicConfigServiceStart> = {
getClient: jest.fn().mockReturnValue(client),
getAsyncLocalStore: jest.fn().mockReturnValue(mockAsyncLocalStoreValues),
createStoreFromRequest: jest.fn().mockRejectedValue(mockAsyncLocalStoreValues),
};

return mocked;
};

export const dynamicConfigServiceMock = {
create: createDynamicConfigServiceMock,
createInternalSetupContract: createInternalSetupContractMock,
createInternalStartContract: createInternalStartContractMock,
createSetupContract: createSetupContractMock,
createStartContract: createStartContractMock,
};
103 changes: 103 additions & 0 deletions src/core/server/config/dynamic_config_service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { DynamicConfigService, IDynamicConfigService } from './dynamic_config_service';
import { configServiceMock, httpServiceMock, opensearchServiceMock } from '../mocks';
import { loggerMock } from '../logging/logger.mock';
import { LoggerFactory } from '@osd/logging';
import { schema, Type } from '@osd/config-schema';
import { IDynamicConfigStoreClient } from 'opensearch-dashboards/server';

describe('DynamicConfigService', () => {
let dynamicConfigService: IDynamicConfigService;
const openSearchMock = opensearchServiceMock.createStart();

beforeEach(() => {
const loggerFactoryMock = {} as LoggerFactory;
loggerFactoryMock.get = jest.fn().mockReturnValue(loggerMock.create());

dynamicConfigService = new DynamicConfigService(
configServiceMock.create(),
{} as any,
loggerFactoryMock
);
});

it('setup() and start() should return the same clients/async local stores', async () => {
const dynamicConfigServiceSetup = await dynamicConfigService.setup();
expect(dynamicConfigServiceSetup.getStartService()).toBeDefined();
expect(dynamicConfigServiceSetup.registerDynamicConfigClientFactory).toBeDefined();
expect(dynamicConfigServiceSetup.registerAsyncLocalStoreRequestHeader).toBeDefined();

dynamicConfigServiceSetup.registerDynamicConfigClientFactory({
create: () => {
return {} as IDynamicConfigStoreClient;
},
});

const dynamicConfigServiceStart = await dynamicConfigService.start({
opensearch: openSearchMock,
});
expect(dynamicConfigServiceStart.getAsyncLocalStore).toBeDefined();
expect(dynamicConfigServiceStart.getClient()).toBeDefined();

const actualGetStartServices = await dynamicConfigServiceSetup.getStartService();

expect(actualGetStartServices.getClient()).toMatchObject(dynamicConfigServiceStart.getClient());
expect(actualGetStartServices.getAsyncLocalStore).toBeDefined();
});

describe('After http is setup', () => {
it('setupHTTP() should add the async local store preAuth middleware', () => {
const httpSetupMock = httpServiceMock.createInternalSetupContract();
dynamicConfigService.registerRoutesAndHandlers({ http: httpSetupMock });
expect(httpSetupMock.registerOnPostAuth).toHaveBeenCalled();
});
});

it('setSchema() and hasDefaultConfigs() should set and check if schemas have been registered', () => {
const schemaList: Map<string, Type<unknown>> = new Map();

schemaList.set(
'foo',
schema.object({
a: schema.boolean(),
b: schema.object({
c: schema.string(),
}),
})
);
schemaList.set(
'bar',
schema.object({
a: schema.boolean(),
b: schema.boolean(),
c: schema.object({
d: schema.string(),
}),
})
);
schemaList.set(
'baz',
schema.object({
a: schema.object({
c: schema.object({
d: schema.boolean(),
}),
}),
})
);

schemaList.forEach((value, key) => {
dynamicConfigService.setSchema(key, value);
});

[...schemaList.keys()].forEach((key) => {
expect(dynamicConfigService.hasDefaultConfigs({ name: key })).toBe(true);
});

expect(dynamicConfigService.hasDefaultConfigs({ name: 'nonexistent_config' })).toBe(false);
});
});
Loading

0 comments on commit b40165e

Please sign in to comment.