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

Run SO migration after plugins setup phase. #55012

Merged

Conversation

pgayvallet
Copy link
Contributor

@pgayvallet pgayvallet commented Jan 16, 2020

Summary

Fix #52071

PR does the following:

  • moves createScopedRepository and createInternalRepository from setup contract to start contract, and removes getScopedClient from the setup contract. There is no longer a possibility to be performing any call to SO before core's start is done
  • creates the migrator, the repository accessors and run the SO migration at the start of SavedObjectsService.start(), meaning that any SO call performed is assured to be done after the migration
  • change the setClientFactory API to setClientFactoryProvider to provides a SO repository provider when registering the client factory
  • adapt the existing plugin calls to the removed APIs from setup to be now using getStartServices to access them on the core start contract

Depends on #55156 (merged)

This is also a requirement for SO issues regarding registration of SO mappings / migrations / schemas / validations from NP (#50313, #50311, #50309)

Dev Docs

  • moves createScopedRepository and createInternalRepository from setup contract to start contract, and removes getScopedClient from the setup contract. There is no longer a possibility to be performing any call to SO before core's start is done
  • creates the migrator, the repository accessors and run the SO migration at the start of SavedObjectsService.start(), meaning that any SO call performed is assured to be done after the migration
  • change the setClientFactory API to setClientFactoryProvider to provides a SO repository provider when registering the client factory
  • adapt the existing plugin calls to the removed APIs from setup to be now using getStartServices to access them on the core start contract

Checklist

Use strikethroughs to remove checklist items you don't feel are applicable to this PR.

For maintainers

Devs Docs

core savedObjects APIs are no longer exposing methods that allows to perform SO calls from it's setup contract. createScopedRepository, createInternalRepository and getScopedClient are now only accessible from the service start contract.

When registering asynchronous handlers during a plugin setup phase that relies on these APIs, getStartServices should be used:

before

class MyPlugin implements Plugin {
  setup(core: CoreSetup, plugins: PluginDeps) {
    plugins.usageCollection.registerCollector({
      type: 'MY_TYPE',
      fetch: async () => {
        const internalRepo = core.savedObjects.createInternalRepository();
        // ...
      },
    });
  }
  start() {}
}

after

class MyPlugin implements Plugin {
  setup(core: CoreSetup, plugins: PluginDeps) {
    plugins.usageCollection.registerCollector({
      type: 'MY_TYPE',
      fetch: async () => {
        const [{ savedObjects }] = await core.getStartServices();
        const internalRepo = savedObjects.createInternalRepository();
        // ...
      },
    });
  }
  start() {}
}

Copy link
Contributor Author

@pgayvallet pgayvallet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thoughts on this first draft.

Finishing the PR implies heavy changes on plugins code, and also now got #54886 as a requirement. I would like to be sure that this is the direction we want to take before continuing this work.

I also got the feeling that is we exposes everything SO-client related in start, we should probably do the same for elasticsearch and uisettings

src/core/server/saved_objects/saved_objects_service.ts Outdated Show resolved Hide resolved
src/core/server/saved_objects/saved_objects_service.ts Outdated Show resolved Hide resolved
Comment on lines 348 to 360
const clientProvider = new SavedObjectsClientProvider<KibanaRequest>({
defaultClientFactory({ request }) {
const repository = repositoryFactory.createScopedRepository(request);
return new SavedObjectsClient(repository);
},
});
if (this.clientFactoryProvider) {
const clientFactory = this.clientFactoryProvider(repositoryFactory);
clientProvider.setClientFactory(clientFactory);
}
this.clientFactoryWrappers.forEach(({ id, factory, priority }) => {
clientProvider.addClientWrapperFactory(priority, id, factory);
});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that the clientProvider is created after everything has been registered to the service, we should probably use a simple constructor instead,

const clientProvider = new SavedObjectsClientProvider<KibanaRequest>({
   clientFactory,
   clientWrappers,
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to do it, but TIL is that the clientProvider is exposed in the internal API to be directly used from the legacy SO service in src/legacy/server/saved_objects/saved_objects_mixin.js, so changing the class is not trivial. This should wait until legacy has been removed.

src/core/server/server.ts Outdated Show resolved Hide resolved
@pgayvallet pgayvallet changed the title Run SO migration after plugins setup phase. POC: run SO migration after plugins setup phase. Jan 16, 2020
@pgayvallet pgayvallet added Feature:New Platform Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc labels Jan 16, 2020
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-platform (Team:Platform)

@pgayvallet
Copy link
Contributor Author

@elastic/kibana-platform I would like you opinion on this because going further into the development.

@rudolf
Copy link
Contributor

rudolf commented Jan 16, 2020

This is a pain point. As of PR state, server#registerCoreContext now requires API from both core setup and start. Not sure what is the best way to address that.

I'm wondering if we could resolve this if we keep creating the repository in setup and keep exposing getScopedClient as part of InternalSavedObjectsSetup, but make mappings, migrations and schemas observables? So we construct the repository (and KibanaMigrator) in setup, but they keep accepting new changes until the end of the setup lifecycle.

There's quite a bit of complexity in how all the SO parts get stitched together, so we would probably have to create a new POC to test this out and make sure it doesn't lead to some sort of dead end.

@pgayvallet
Copy link
Contributor Author

pgayvallet commented Jan 16, 2020

I'm wondering if we could resolve this if we keep creating the repository in setup and keep exposing getScopedClient as part of InternalSavedObjectsSetup, but make mappings, migrations and schemas observables? So we construct the repository (and KibanaMigrator) in setup, but they keep accepting new changes until the end of the setup lifecycle.

I wanted to try that, however:

  • that's some big changes on a lot of underneath SO classes, as it was really not conceived with this idea in mind.
  • even with observables, we would still be instantiating a repository with a not-yet-configured migrator, so there will always be technically the risk that at some point a call could be performed to SO before the migrator have received proper/final configuration

If the only thing pushing in that direction is this single own core usage for registerCoreContext, my feeling is that may not be worth it, and that a - way less elegant but yet working - trick with a closure to use a reference to the start contract that would be stored to the server instance during start is probably more pragmatic.

But the discussion is still widely open. I really don't have a clear vision of what the best thing to do is...

Copy link
Contributor

@joshdover joshdover left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like the right direction to me.

I agree that uiSettings and Elasticsearch maybe shouldn't be exposed before migrations are ran. uiSettings I'm very sure shouldn't be. However, Elasticsearch might be safe. Many plugins set up or query other non-SO indices. However, I'm not sure there's any advantage to allowing these during setup rather than start.

The strict separation between setup and start has been very helpful on the client. Keeping registration logic and setup and "running" logic in start seems like the way to go, though I do fear the number of plugins that this will affect.

src/core/server/server.ts Outdated Show resolved Hide resolved
*
* @public
*/
export type SavedObjectsClientFactoryProvider<Request = unknown> = (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh no, a factory provider ☕️

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seen on a real previous project: RPCClientFactoryBridgedProviderCachedRegistry. We still got some margin.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, in legacy we have Scoped- too so we could create an impressive ScopedClientFactoryWrapperProvider

setScopedSavedObjectsClientFactory: (...args) => provider.setClientFactory(...args),
addScopedSavedObjectsClientWrapperFactory: (...args) =>

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK all usages of setScopedSavedObjectsClientFactory has been migrated to be using NP API from legacy instead (which is not the case of setScopedSavedObjectsClientFactory ). I think I might removes it in this PR. will do after a first green CI though

src/core/server/server.ts Outdated Show resolved Hide resolved
@pgayvallet
Copy link
Contributor Author

The strict separation between setup and start has been very helpful on the client. Keeping registration logic and setup and "running" logic in start seems like the way to go, though I do fear the number of plugins that this will affect.

That's my feeling too. The getStartServices should probably mitigate this a lot however, as these API are already async, it should be trivial to add another async call to core.getStartService() to retrieve the moved API. I just 'hope' plugin teams did not add massive coverage over their migrated code, as tests would probably the hardest to adapt.

I guess we need to finish this attempt on SO to see if it becomes clear that it should be done also on UI (and maybe ES)

@rudolf
Copy link
Contributor

rudolf commented Jan 17, 2020

If the only thing pushing in that direction is this single own core usage for registerCoreContext, my feeling is that may not be worth it

Looking at this again I agree. Registering mappings, schemas and migrations aren't fundamentally things we would ever want to be reactive. Modelling these as observables will probably reduce the clarity of the code.

@pgayvallet
Copy link
Contributor Author

pgayvallet commented Jan 17, 2020

core changes are mostly done. Tests has been updated and added.

Now waiting for #55156 to be integrated to be able to properly updates plugin's calls of the moved APIs

@pgayvallet pgayvallet changed the title POC: run SO migration after plugins setup phase. Run SO migration after plugins setup phase. Jan 17, 2020
@pgayvallet
Copy link
Contributor Author

#55156 has been merged on master and integrated in the PR branch. Should now be able to continue with the PR.

@pgayvallet pgayvallet added release_note:skip Skip the PR/issue when compiling release notes v8.0.0 labels Jan 21, 2020
@pgayvallet
Copy link
Contributor Author

Created #55396 for UiSettings and #55397 to discuss about ES API

Copy link
Contributor

@kobelb kobelb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security changes LGTM

Comment on lines +131 to +136
getServices(rawRequest: Hapi.Request): Services {
const request = KibanaRequest.from(rawRequest);
return {
callCluster: (...args) =>
adminClient!.asScoped(KibanaRequest.from(request)).callAsCurrentUser(...args),
savedObjectsClient: core.savedObjects.getScopedSavedObjectsClient(request),
callCluster: (...args) => adminClient!.asScoped(request).callAsCurrentUser(...args),
// rawRequest is actually a fake request, converting it to KibanaRequest causes issue in SO access
savedObjectsClient: core.savedObjects.getScopedSavedObjectsClient(rawRequest as any),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did properly adapt this to use core.savedObjects.getScopedSavedObjectsClient(request). However I got test failures, and I isolated it to this change.

Calling core.savedObjects.getScopedSavedObjectsClient with KibanaRequest.from(request) instead of the raw HAPI request makes the test fail, with
Saved object [alert/dbbfe2a1-e3ab-4ebe-84f2-9ed495b9d81b] not found

Forcecasting the raw request fix the failure:

savedObjectsClient: core.savedObjects.getScopedSavedObjectsClient(rawRequest as any)

AFAIK it's because the rawRequest, even if typed as Hapi.Request, is actually a fake request:

const fakeRequest = {
headers: requestHeaders,
getBasePath: () => this.context.getBasePath(spaceId),
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
};
return this.context.getServices(fakeRequest);

and the GetServices signature differs from between the type and the actual implementation, allowing to call it without a proper hapi request...

export type GetServicesFunction = (request: any) => Services;

Not sure what the underlying cause is though. I though only the headers from the request were actually used when using the request to create the scoped repository. But the fake request does miss some properties used when converting from request to kibanaRequest such as method.

Is this comment acceptable/enough for now, or should I investigate in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is related to #39430 btw

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment acceptable/enough for now, or should I investigate in this PR?

As for me, it's not a blocker for the current PR. But we should prioritize #39430 to be able to cut off legacy server-side plugins in 7.7


return {
addInstall: async (dataSet: string) => {
try {
await internalRepository.incrementCounter(SAVED_OBJECT_ID, dataSet, `installCount`);
await (await internalRepository).incrementCounter(SAVED_OBJECT_ID, dataSet, `installCount`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missed this one when cleanup up the calls, thanks

@@ -83,58 +87,22 @@ export interface SavedObjectsServiceSetup {
* Set a default factory for creating Saved Objects clients. Only one client
* factory can be set, subsequent calls to this method will fail.
*/
setClientFactory: (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void;
setClientFactoryProvider: (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we update the comment above to make it clear that this sets a default factory provider. Also it'd be nice if we used {@link SavedObjectsClientFactoryProvider | factory provider}

@pgayvallet pgayvallet merged commit a75436d into elastic:master Jan 23, 2020
pgayvallet added a commit to pgayvallet/kibana that referenced this pull request Jan 23, 2020
* change setClientFactory api to setClientFactoryProvider

* cleanup and add test for service

* change the signatures of SO start/setup

* fix registerCoreContext by accessing stored start contract reference

* move migration inside `start`

* adapt and add service tests

* add doc and export new types

* adapt plugins code

* update generated doc

* better core access

* address some review comments

* remove parametrized type from SavedObjectsClientFactory, use KibanaRequest instead

* add logs when starting and ending so migration

* fix KibanaRequest imports

* NITs and review comments

* fix alerting FTR test

* review comments
pgayvallet added a commit that referenced this pull request Jan 23, 2020
* change setClientFactory api to setClientFactoryProvider

* cleanup and add test for service

* change the signatures of SO start/setup

* fix registerCoreContext by accessing stored start contract reference

* move migration inside `start`

* adapt and add service tests

* add doc and export new types

* adapt plugins code

* update generated doc

* better core access

* address some review comments

* remove parametrized type from SavedObjectsClientFactory, use KibanaRequest instead

* add logs when starting and ending so migration

* fix KibanaRequest imports

* NITs and review comments

* fix alerting FTR test

* review comments
@kibanamachine
Copy link
Contributor

💔 Build Failed

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature:New Platform release_note:plugin_api_changes Contains a Plugin API changes section for the breaking plugin API changes section. Team:Core Core services & architecture: plugins, logging, config, saved objects, http, ES client, i18n, etc v7.7.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Platform] Run SavedObject Migrations after PluginService#setup
9 participants