Skip to content

Commit

Permalink
Add saved objects service status api (#4696)
Browse files Browse the repository at this point in the history
Signed-off-by: Bandini Bhopi <bandinib@amazon.com>
  • Loading branch information
bandinib-amzn authored Aug 9, 2023
1 parent 04065fa commit d8c7f48
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Optimize `augment-vis` saved obj searching by adding arg to saved obj client ([#4595](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4595))
- Add resource ID filtering in fetch `augment-vis` obj queries ([#4608](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4608))
- Reduce the amount of comments in compiled CSS ([#4648](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4648))
- [Saved Object Service] Customize saved objects service status ([#4696](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4696))
- [Discover] Update styles to compatible with OUI `next` theme ([#4644](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/4644))

### 🐛 Bug Fixes
Expand Down
3 changes: 3 additions & 0 deletions src/core/server/legacy/legacy_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ export class LegacyService implements CoreService {
registerType: setupDeps.core.savedObjects.registerType,
getImportExportObjectLimit: setupDeps.core.savedObjects.getImportExportObjectLimit,
setRepositoryFactoryProvider: setupDeps.core.savedObjects.setRepositoryFactoryProvider,
setStatus: () => {
throw new Error(`core.savedObjects.setStatus is unsupported in legacy`);
},
},
status: {
isStatusPageAnonymous: setupDeps.core.status.isStatusPageAnonymous,
Expand Down
1 change: 1 addition & 0 deletions src/core/server/plugins/plugin_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
registerType: deps.savedObjects.registerType,
getImportExportObjectLimit: deps.savedObjects.getImportExportObjectLimit,
setRepositoryFactoryProvider: deps.savedObjects.setRepositoryFactoryProvider,
setStatus: deps.savedObjects.setStatus,
},
status: {
core$: deps.status.core$,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const createSetupContractMock = () => {
registerType: jest.fn(),
getImportExportObjectLimit: jest.fn(),
setRepositoryFactoryProvider: jest.fn(),
setStatus: jest.fn(),
};

setupContract.getImportExportObjectLimit.mockReturnValue(100);
Expand Down
72 changes: 71 additions & 1 deletion src/core/server/saved_objects/saved_objects_service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import {
clientProviderInstanceMock,
typeRegistryInstanceMock,
} from './saved_objects_service.test.mocks';
import { BehaviorSubject } from 'rxjs';
import { BehaviorSubject, of } from 'rxjs';
import { first } from 'rxjs/operators';
import { ByteSizeValue } from '@osd/config-schema';
import { errors as opensearchErrors } from '@opensearch-project/opensearch';

Expand All @@ -50,6 +51,7 @@ import { SavedObjectsClientFactoryProvider } from './service/lib';
import { NodesVersionCompatibility } from '../opensearch/version_check/ensure_opensearch_version';
import { SavedObjectsRepository } from './service/lib/repository';
import { SavedObjectRepositoryFactoryProvider } from './service/lib/scoped_client_provider';
import { ServiceStatusLevels } from '../status';

jest.mock('./service/lib/repository');

Expand Down Expand Up @@ -191,6 +193,31 @@ describe('SavedObjectsService', () => {
);
});
});

describe('#setStatus', () => {
it('throws error if custom status is already set', async () => {
const coreContext = createCoreContext();
const soService = new SavedObjectsService(coreContext);
const setup = await soService.setup(createSetupDeps());

const customStatus1$ = of({
level: ServiceStatusLevels.available,
summary: 'Saved Object Service is using external storage and it is up',
});
const customStatus2$ = of({
level: ServiceStatusLevels.unavailable,
summary: 'Saved Object Service is not connected to external storage and it is down',
});

setup.setStatus(customStatus1$);

expect(() => {
setup.setStatus(customStatus2$);
}).toThrowErrorMatchingInlineSnapshot(
`"custom saved object service status is already set, and can only be set once"`
);
});
});
});

describe('#start()', () => {
Expand Down Expand Up @@ -312,6 +339,15 @@ describe('SavedObjectsService', () => {
}).toThrowErrorMatchingInlineSnapshot(
'"cannot call `setRepositoryFactoryProvider` after service startup."'
);

const customStatus$ = of({
level: ServiceStatusLevels.available,
summary: 'Saved Object Service is using external storage and it is up',
});

expect(() => {
setup.setStatus(customStatus$);
}).toThrowErrorMatchingInlineSnapshot('"cannot call `setStatus` after service startup."');
});

describe('#getTypeRegistry', () => {
Expand Down Expand Up @@ -430,5 +466,39 @@ describe('SavedObjectsService', () => {
expect(SavedObjectsRepository.createRepository as jest.Mocked<any>).toHaveBeenCalled();
});
});

describe('#savedObjectServiceStatus', () => {
it('Saved objects service status should be custom when set using setStatus', async () => {
const coreContext = createCoreContext({});
const soService = new SavedObjectsService(coreContext);
const coreSetup = createSetupDeps();
const setup = await soService.setup(coreSetup);

const customStatus$ = of({
level: ServiceStatusLevels.available,
summary: 'Saved Object Service is using external storage and it is up',
});
setup.setStatus(customStatus$);
const coreStart = createStartDeps();
await soService.start(coreStart);
expect(await setup.status$.pipe(first()).toPromise()).toMatchObject({
level: ServiceStatusLevels.available,
summary: 'Saved Object Service is using external storage and it is up',
});
});

it('Saved objects service should be default when custom status is not set', async () => {
const coreContext = createCoreContext({});
const soService = new SavedObjectsService(coreContext);
const coreSetup = createSetupDeps();
const setup = await soService.setup(coreSetup);
const coreStart = createStartDeps();
await soService.start(coreStart);
expect(await setup.status$.pipe(first()).toPromise()).toMatchObject({
level: ServiceStatusLevels.available,
summary: 'SavedObjects service has completed migrations and is available',
});
});
});
});
});
62 changes: 54 additions & 8 deletions src/core/server/saved_objects/saved_objects_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@
* under the License.
*/

import { Subject, Observable } from 'rxjs';
import { first, filter, take, switchMap } from 'rxjs/operators';
import { Subject, Observable, BehaviorSubject } from 'rxjs';
import { first, filter, take, switchMap, map, distinctUntilChanged } from 'rxjs/operators';
import { isDeepStrictEqual } from 'util';

import { CoreService } from '../../types';
import {
SavedObjectsClient,
Expand Down Expand Up @@ -62,7 +64,7 @@ import { Logger } from '../logging';
import { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry';
import { SavedObjectsSerializer } from './serialization';
import { registerRoutes } from './routes';
import { ServiceStatus } from '../status';
import { ServiceStatus, ServiceStatusLevels } from '../status';
import { calculateStatus$ } from './status';
import { createMigrationOpenSearchClient } from './migrations/core/';
/**
Expand Down Expand Up @@ -175,6 +177,12 @@ export interface SavedObjectsServiceSetup {
setRepositoryFactoryProvider: (
respositoryFactoryProvider: SavedObjectRepositoryFactoryProvider
) => void;

/**
* Allows a plugin to specify a custom status dependent on its own criteria.
* Completely overrides the default status.
*/
setStatus(status$: Observable<ServiceStatus<SavedObjectStatusMeta>>): void;
}

/**
Expand Down Expand Up @@ -301,6 +309,11 @@ export class SavedObjectsService
private started = false;

private respositoryFactoryProvider?: SavedObjectRepositoryFactoryProvider;
private savedObjectServiceCustomStatus$?: Observable<ServiceStatus<SavedObjectStatusMeta>>;
private savedObjectServiceStatus$ = new BehaviorSubject<ServiceStatus<SavedObjectStatusMeta>>({
level: ServiceStatusLevels.unavailable,
summary: `waiting`,
});

constructor(private readonly coreContext: CoreContext) {
this.logger = coreContext.logger.get('savedobjects-service');
Expand Down Expand Up @@ -329,10 +342,7 @@ export class SavedObjectsService
});

return {
status$: calculateStatus$(
this.migrator$.pipe(switchMap((migrator) => migrator.getStatus$())),
setupDeps.opensearch.status$
),
status$: this.savedObjectServiceStatus$.asObservable(),
setClientFactoryProvider: (provider) => {
if (this.started) {
throw new Error('cannot call `setClientFactoryProvider` after service startup.');
Expand Down Expand Up @@ -368,6 +378,17 @@ export class SavedObjectsService
}
this.respositoryFactoryProvider = repositoryProvider;
},
setStatus: (status$) => {
if (this.started) {
throw new Error('cannot call `setStatus` after service startup.');
}
if (this.savedObjectServiceCustomStatus$) {
throw new Error(
'custom saved object service status is already set, and can only be set once'
);
}
this.savedObjectServiceCustomStatus$ = status$;
},
};
}

Expand All @@ -381,6 +402,29 @@ export class SavedObjectsService

this.logger.debug('Starting SavedObjects service');

if (this.savedObjectServiceCustomStatus$) {
this.savedObjectServiceCustomStatus$
.pipe(
map((savedObjectServiceCustomStatus) => {
return savedObjectServiceCustomStatus;
}),
distinctUntilChanged<ServiceStatus<SavedObjectStatusMeta>>(isDeepStrictEqual)
)
.subscribe((value) => this.savedObjectServiceStatus$.next(value));
} else {
calculateStatus$(
this.migrator$.pipe(switchMap((migrator) => migrator.getStatus$())),
this.setupDeps.opensearch.status$
)
.pipe(
map((defaultstatus) => {
return defaultstatus;
}),
distinctUntilChanged<ServiceStatus<SavedObjectStatusMeta>>(isDeepStrictEqual)
)
.subscribe((value) => this.savedObjectServiceStatus$.next(value));
}

const opensearchDashboardsConfig = await this.coreContext.configService
.atPath<OpenSearchDashboardsConfigType>('opensearchDashboards')
.pipe(first())
Expand Down Expand Up @@ -492,7 +536,9 @@ export class SavedObjectsService
};
}

public async stop() {}
public async stop() {
this.savedObjectServiceStatus$.unsubscribe();
}

private createMigrator(
opensearchDashboardsConfig: OpenSearchDashboardsConfigType,
Expand Down

0 comments on commit d8c7f48

Please sign in to comment.