Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add saved objects service status api #4696

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
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`);

Check warning on line 280 in src/core/server/legacy/legacy_service.ts

View check run for this annotation

Codecov / codecov/patch

src/core/server/legacy/legacy_service.ts#L280

Added line #L280 was not covered by tests
},
},
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 { 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 @@
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 @@
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 @@
});

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 @@
}
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 @@

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 @@
};
}

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

Check warning on line 540 in src/core/server/saved_objects/saved_objects_service.ts

View check run for this annotation

Codecov / codecov/patch

src/core/server/saved_objects/saved_objects_service.ts#L540

Added line #L540 was not covered by tests
}

private createMigrator(
opensearchDashboardsConfig: OpenSearchDashboardsConfigType,
Expand Down
Loading