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

[APM] Migrate server routes to NP #49455

Merged
merged 5 commits into from
Nov 21, 2019

Conversation

dgieselaar
Copy link
Member

@dgieselaar dgieselaar commented Oct 28, 2019

Closes #49238.
Closes #51156

Changes:

  • Create new platform plugin for APM & APM OSS
  • Use APM OSS configuration in APM plugin, and merge/flatten config values
  • Move createApmApi and createApmAgentConfiguration to NP. Not sure how to move makeApmUsageSelector as it depends on server.usage.
  • Signature for routes changed from ( request:hapi.Request, params:Params, h:hapi.ResponseToolkit ) to ( kibanaContext:{ context: APMRequestHandlerContext, request: Kibana.Request } ).
  • APMRequestHandlerContext is Kibana.RequestHandlerContext plus the APM configuration and the decoded request parameters.
  • Range & UI filters that are used in setup_request now have type safety, ie if a query for an endpoint uses those filters, type check will fail if any validation is missing for those query parameters
  • Removed agentConfigurationIndex from configurable UI indices. Should be created and queried with internalUser, so making this configurable could lead to security issues.

TODOs/questions (cc @rudolf):

  • The apm/apm_oss legacy configurations are used by infra and the tutorial. In this PR, we have two schemas, one for NP with @kbn/config-schema, and one for LP with Joi. Is there a way we can prevent this duplication (and still expose legacy configuration values)? Copying and pasting stuff for now.
  • We are using io-ts for validation route input. NP requires a schema from @kbn/config-schema. Right now we're using a noop schema as a workaround, but it's not pretty. Any thoughts on how this could be better? Platform will enable support for io-ts in the near future, see Decouple request validation from @kbn/config-schema #50179
  • What is the replacement for server.usage? Blocked by collectorSets in the New Platform #46924. Left in legacy plugin for now.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@rudolf
Copy link
Contributor

rudolf commented Nov 1, 2019

Legacy vs NP config

There's two options, either you keep the duplicated legacy schema around until all dependencies have moved away from Legacy. Or you migrate the dependencies to pull these config values from the NP plugin for them. For instance I saw infra uses legacy config to get the apmIndex. The code below doesn't work cause there's no server in this context, but I hope it illustrates how you would wire this up.

If there's not a lot of places depending on this legacy config, it's probably best to go all out and only keep the NP config, changing all legacy plugins to consume this NP plugins' configuration.

diff --git a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/has_apm_data.ts b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/has_apm_data.ts
index 3193cf8397..7b91e67ea2 100644
--- a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/has_apm_data.ts
+++ b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/has_apm_data.ts
@@ -19,7 +19,11 @@ export const hasAPMData = async (
   nodeType: 'host' | 'pod' | 'container'
 ) => {
   const config = framework.config(req);
-  const apmIndex = config.get('apm_oss.transactionIndices') || 'apm-*';
+  // You'll probably want to extract apmConfig in infra's init.ts and then
+  // inject it into this function (through framework?)
+  const apmConfig = await server.newPlatform.setup.plugins.apm_oss.config$.toPromise();
+  const apmIndex = apmConfig.transactionIndices || 'apm-*';
+
   // There is a bug in APM ECS data where host.name is not set.
   // This will fixed with: https://github.com/elastic/apm-server/issues/2502
   const nodeFieldName =

Server.usage

Will be replaced by the metrics plugin in NP #46924 the stack services team is responsible for this, I think they're targeting 7.6

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@mistic
Copy link
Member

mistic commented Nov 13, 2019

@elasticmachine merge upstream

@elasticmachine
Copy link
Contributor

merge conflict between base and head

@mistic
Copy link
Member

mistic commented Nov 13, 2019

@elasticmachine merge upstream

@elasticmachine
Copy link
Contributor

💔 Build Failed

@mistic
Copy link
Member

mistic commented Nov 14, 2019

@elasticmachine merge upstream

@elasticmachine
Copy link
Contributor

💔 Build Failed

@dgieselaar dgieselaar force-pushed the migrate-create-router branch 2 times, most recently from b24e7b2 to 6b597e1 Compare November 14, 2019 13:45
@dgieselaar dgieselaar marked this pull request as ready for review November 14, 2019 13:49
@dgieselaar dgieselaar requested a review from a team as a code owner November 14, 2019 13:49
@dgieselaar
Copy link
Member Author

I moved this out of draft prematurely. I still have to figure out how to create an internal saved objects client.

@elasticmachine
Copy link
Contributor

💔 Build Failed

@elasticmachine
Copy link
Contributor

💔 Build Failed

@dgieselaar dgieselaar force-pushed the migrate-create-router branch 2 times, most recently from 25e8004 to c0d153a Compare November 20, 2019 11:20
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

uiFiltersES: ESFilter[];
};

export type SetupWithAllFilters = SetupWithTimeRange & SetupWithUIFilters;
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure this'll work longer term. What happens when we add a new property that not every route needs? Then it's not "all" anymore.

Perhaps it's better to be explicit and type it in place as: Setup & TimeRange & UIFilters.

Not for this PR but: Setup itself is already pretty opaque. It contains a bunch of different stuff - perhaps it would make sense to split that up into smaller parts too?

Copy link
Member Author

@dgieselaar dgieselaar Nov 20, 2019

Choose a reason for hiding this comment

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

++ on separating Setup{WithTimeRange,WithUIFilters} and using TS unions. Composable & explicit. Fixed.

I'd think it'd be good to think (at some point) about APMRequestContext and SetupRequest, how they overlap, how they are different, and possibly if we need a APMPluginContract or something similar as well. Maybe after NP migration is completed?

}
}
}
} as unknown) as APMRequestHandlerContext;
Copy link
Member

Choose a reason for hiding this comment

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

What do you think about creating the saved objects client on Setup like we do with client and internalClient. then we could have savedObjectsClient and internalSavedObjectsClient. This way we wouldn't have to pass context around anymore.

Copy link
Member Author

Choose a reason for hiding this comment

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

context is still needed for config and some other stuff, or am I misunderstanding? the internal saved objects client is a WIP, maybe better to wait? #48882

Copy link
Member

Choose a reason for hiding this comment

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

config is already on Setup. I'm thinking that it would be nice if downstream handlers don't have to know about Context or Request - at least if they are just doing normal stuff like reading config, calling (internal) savedobjects or (internal) esClient.

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, I see what you mean. Ok if I create an issue for this that we can pick up after the migration to NP is complete (including the usage collector and the internal saved objects client)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Went ahead and created the issue: #51257

export async function getServices(setup: SetupWithAllFilters) {
const itemsPromise = getServicesItems(setup);
const hasLegacyDataPromise = getLegacyDataStatus(setup);
const items = await itemsPromise;
Copy link
Member

Choose a reason for hiding this comment

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

Any reason for not await'ing immediately? This ends up being await'ed in serial endyway.

Copy link
Member Author

@dgieselaar dgieselaar Nov 20, 2019

Choose a reason for hiding this comment

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

getLegacyDataStatus (2) is not blocked by getServicesItems (1), only hasHistoricalAgentData (3) is. So this starts 1 and 2 immediately, and if 1 finishes, 3 starts. Then the function returns when all promises are resolved. At least, that's what it should do. Do you see something else happening?

I could also rewrite with promises if it makes more sense:

const [{ items, hasHistoricalData }, hasLegacyData] = await Promise.all([
  getServicesItems(setup).then(async servicesItems => {
    const noDataInCurrentTimeRange = isEmpty(servicesItems);

    return {
      items: servicesItems,
      hasHistoricalData: noDataInCurrentTimeRange
        ? await hasHistoricalAgentData(setup)
        : true
    };
  }),
  getLegacyDataStatus(setup)
]);

WDYT?

Copy link
Member

Choose a reason for hiding this comment

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

Maybe I'm missing something but isn't this equivalent to using Promise.all for the first two promises?

export async function getServices(setup: SetupWithAllFilters) {
  const [items, hasLegacyData] = await Promise.all([
    getServicesItems(setup),
    getLegacyDataStatus(setup)
  ]);

  const noDataInCurrentTimeRange = isEmpty(items);
  const hasHistoricalData = noDataInCurrentTimeRange
    ? await hasHistoricalAgentData(setup)
    : true;

  return {
    items,
    hasHistoricalData,
    hasLegacyData
  };
}

Copy link
Member

@sorenlouv sorenlouv Nov 20, 2019

Choose a reason for hiding this comment

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

At least this approach is more clear to me.

Copy link
Member Author

@dgieselaar dgieselaar Nov 21, 2019

Choose a reason for hiding this comment

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

getServicesItems could finish before getLegacyDataStatus and then the latter would unnecessary block hasHistoricalAgentData, right? (hasHistoricalAgentData needs getServicesItems, but not hasLegacyData)

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe it's not worth it though? I do think the version that we have today is more readable for sure.

Copy link
Member

Choose a reason for hiding this comment

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

getServicesItems could finish before getLegacyDataStatus and then the latter would unnecessary block hasHistoricalAgentData, right?

Did you time this? I'd expect in most cases getLegacyDataStatus to be faster than getServicesItems - ofc this depends on both the selected time range and the data volumes.
Regardless, I think the difference is negligible and I'd optimize for the most readable version.

Copy link
Member Author

Choose a reason for hiding this comment

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

I didn't time it, I figured it would be a win anyway but agreed, let's keep it simple 👍

schema: schema.object({
servicemapEnabled: schema.boolean({ defaultValue: false }),
bucketTargetCount: schema.number({ defaultValue: 15 }),
minimumBucketSize: schema.number({ defaultValue: 15 }),
Copy link
Member

Choose a reason for hiding this comment

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

minimumBucketSize and bucketTargetCount are carry-over from the opbeat days (yeah!).
If you think it makes sense in this PR it would be great if you can just make them constants (like apmAgentConfigurationIndex)

Copy link
Member Author

Choose a reason for hiding this comment

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

Moved these to plain old constants in the server/lib/transactions/constants. They're not used outside of transactions AFAICT.

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Copy link
Member

@sorenlouv sorenlouv left a comment

Choose a reason for hiding this comment

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

looks great only a few nitpicks around how we can handle context, Setup etc.

Closes elastic#49238.

Pass legacy API to NP plugin in order to use internal SO client

Fix issues with agent configuration APIs

Update tsconfig template for TS optimization

Fix typo
@elasticmachine
Copy link
Contributor

💔 Build Failed

Copy link
Contributor

@rudolf rudolf left a comment

Choose a reason for hiding this comment

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

Left some small suggestions, but all in all this looks great!

config: APMConfig;
logger: Logger;
__LEGACY: {
server: Server;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: You're using a really broad type here which makes it hard to see which dependencies you're using. I would suggest just typing the dependencies you're using like:

__LEGACY: {
  server: {
    savedObjects: {
      getScopedSavedObjectsClient: Server['savedObjects']['getScopedSavedObjectsClient']
    }
    // similar for usage collector
  }
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Good one, I've narrowed the type to the things that we are using at the moment.

import { once } from 'lodash';
import { Plugin as APMOSSPlugin } from '../../../../src/plugins/apm_oss/server';
import { createApmAgentConfigurationIndex } from '../../../legacy/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index';
import { createApmApi } from '../../../legacy/plugins/apm/server/routes/create_apm_api';
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: it's a bit confusing when you have routes in a legacy plugin, being run from a NP plugin, but injecting dependencies from a legacy plugin, just kinda hard to wrap your head around. Do you still need these files in legacy or could you move create_apm_api and all the routes into the NP plugin?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think this makes more sense to do when the migration is completed. Otherwise legacy will import lots of stuff from np and the other way around. I've added it to the list in #32894

@elasticmachine
Copy link
Contributor

💚 Build Succeeded

@dgieselaar dgieselaar merged commit 06bee60 into elastic:master Nov 21, 2019
@dgieselaar dgieselaar deleted the migrate-create-router branch November 21, 2019 14:46
dgieselaar added a commit that referenced this pull request Nov 25, 2019
* [APM] Migrate server routes to NP

Closes #49238.

Pass legacy API to NP plugin in order to use internal SO client

Fix issues with agent configuration APIs

Update tsconfig template for TS optimization

Fix typo

* Review feedback

* Fix type issues after browser duration changes

* Revert changes in getServices due to readability concerns

* Review feedback
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[APM] Make agent configuration index non-configurable [APM] NP migration core.http.createRouter
6 participants