mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
* Infra server NP shim + config/routing API adoption (#45299)
This commit is contained in:
parent
49ee79118d
commit
a2df167306
65 changed files with 1268 additions and 1240 deletions
|
@ -78,7 +78,10 @@ async function getParamsForSearchRequest(
|
|||
) {
|
||||
const { uiSettings } = context.core;
|
||||
const [indices, includeFrozen] = await Promise.all([
|
||||
getApmIndices(context),
|
||||
getApmIndices({
|
||||
savedObjectsClient: context.core.savedObjects.client,
|
||||
config: context.config
|
||||
}),
|
||||
uiSettings.client.get('search:includeFrozen')
|
||||
]);
|
||||
|
||||
|
|
|
@ -50,6 +50,11 @@ function getMockRequest() {
|
|||
client: {
|
||||
get: jest.fn().mockResolvedValue(false)
|
||||
}
|
||||
},
|
||||
savedObjects: {
|
||||
client: {
|
||||
get: jest.fn()
|
||||
}
|
||||
}
|
||||
}
|
||||
} as unknown) as APMRequestHandlerContext & {
|
||||
|
@ -65,6 +70,11 @@ function getMockRequest() {
|
|||
get: jest.Mock<any, any>;
|
||||
};
|
||||
};
|
||||
savedObjects: {
|
||||
client: {
|
||||
get: jest.Mock<any, any>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -73,7 +73,10 @@ export async function setupRequest<TParams extends SetupRequestParams>(
|
|||
const { config } = context;
|
||||
const { query } = context.params;
|
||||
|
||||
const indices = await getApmIndices(context);
|
||||
const indices = await getApmIndices({
|
||||
savedObjectsClient: context.core.savedObjects.client,
|
||||
config
|
||||
});
|
||||
|
||||
const dynamicIndexPattern = await getDynamicIndexPattern({
|
||||
context,
|
||||
|
|
|
@ -54,15 +54,25 @@ export function getApmIndicesConfig(config: APMConfig): ApmIndicesConfig {
|
|||
};
|
||||
}
|
||||
|
||||
export async function getApmIndices(context: APMRequestHandlerContext) {
|
||||
// export async function getApmIndices(context: APMRequestHandlerContext) {
|
||||
// return _getApmIndices(context.core, context.config);
|
||||
// }
|
||||
|
||||
export async function getApmIndices({
|
||||
config,
|
||||
savedObjectsClient
|
||||
}: {
|
||||
config: APMConfig;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
}) {
|
||||
try {
|
||||
const apmIndicesSavedObject = await getApmIndicesSavedObject(
|
||||
context.core.savedObjects.client
|
||||
savedObjectsClient
|
||||
);
|
||||
const apmIndicesConfig = getApmIndicesConfig(context.config);
|
||||
const apmIndicesConfig = getApmIndicesConfig(config);
|
||||
return merge({}, apmIndicesConfig, apmIndicesSavedObject);
|
||||
} catch (error) {
|
||||
return getApmIndicesConfig(context.config);
|
||||
return getApmIndicesConfig(config);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,10 @@ export const apmIndicesRoute = createRoute(() => ({
|
|||
method: 'GET',
|
||||
path: '/api/apm/settings/apm-indices',
|
||||
handler: async ({ context }) => {
|
||||
return await getApmIndices(context);
|
||||
return await getApmIndices({
|
||||
savedObjectsClient: context.core.savedObjects.client,
|
||||
config: context.config
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import { InfraWrappableRequest } from '../../server/lib/adapters/framework';
|
||||
|
||||
export const InfraMetadataNodeTypeRT = rt.keyof({
|
||||
host: null,
|
||||
|
@ -67,6 +66,7 @@ export const InfraMetadataInfoRT = rt.partial({
|
|||
});
|
||||
|
||||
const InfraMetadataRequiredRT = rt.type({
|
||||
id: rt.string,
|
||||
name: rt.string,
|
||||
features: rt.array(InfraMetadataFeatureRT),
|
||||
});
|
||||
|
@ -81,8 +81,6 @@ export type InfraMetadata = rt.TypeOf<typeof InfraMetadataRT>;
|
|||
|
||||
export type InfraMetadataRequest = rt.TypeOf<typeof InfraMetadataRequestRT>;
|
||||
|
||||
export type InfraMetadataWrappedRequest = InfraWrappableRequest<InfraMetadataRequest>;
|
||||
|
||||
export type InfraMetadataFeature = rt.TypeOf<typeof InfraMetadataFeatureRT>;
|
||||
|
||||
export type InfraMetadataInfo = rt.TypeOf<typeof InfraMetadataInfoRT>;
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
import * as rt from 'io-ts';
|
||||
import { InventoryMetricRT, ItemTypeRT } from '../inventory_models/types';
|
||||
import { InfraWrappableRequest } from '../../server/lib/adapters/framework';
|
||||
import { InfraTimerangeInputRT } from './snapshot_api';
|
||||
|
||||
const NodeDetailsDataPointRT = rt.intersection([
|
||||
|
@ -53,6 +52,4 @@ export const NodeDetailsRequestRT = rt.intersection([
|
|||
// export type NodeDetailsRequest = InfraWrappableRequest<NodesArgs & SourceArgs>;
|
||||
|
||||
export type NodeDetailsRequest = rt.TypeOf<typeof NodeDetailsRequestRT>;
|
||||
export type NodeDetailsWrappedRequest = InfraWrappableRequest<NodeDetailsRequest>;
|
||||
|
||||
export type NodeDetailsMetricDataResponse = rt.TypeOf<typeof NodeDetailsMetricDataResponseRT>;
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
*/
|
||||
|
||||
import * as rt from 'io-ts';
|
||||
import { InfraWrappableRequest } from '../../server/lib/adapters/framework';
|
||||
import { SnapshotMetricTypeRT, ItemTypeRT } from '../inventory_models/types';
|
||||
|
||||
export const SnapshotNodePathRT = rt.intersection([
|
||||
|
@ -64,6 +63,5 @@ export const SnapshotRequestRT = rt.intersection([
|
|||
]);
|
||||
|
||||
export type SnapshotRequest = rt.TypeOf<typeof SnapshotRequestRT>;
|
||||
export type SnapshotWrappedRequest = InfraWrappableRequest<SnapshotRequest>;
|
||||
export type SnapshotNode = rt.TypeOf<typeof SnapshotNodeRT>;
|
||||
export type SnapshotNodeResponse = rt.TypeOf<typeof SnapshotNodeResponseRT>;
|
||||
|
|
|
@ -7,9 +7,14 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import JoiNamespace from 'joi';
|
||||
import { resolve } from 'path';
|
||||
|
||||
import { getConfigSchema, initServerWithKibana } from './server/kibana.index';
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||
import KbnServer from 'src/legacy/server/kbn_server';
|
||||
import { getConfigSchema } from './server/kibana.index';
|
||||
import { savedObjectMappings } from './server/saved_objects';
|
||||
import { plugin, InfraServerPluginDeps } from './server/new_platform_index';
|
||||
import { InfraSetup } from '../../../plugins/infra/server';
|
||||
import { APMPluginContract } from '../../../plugins/apm/server/plugin';
|
||||
|
||||
const APP_ID = 'infra';
|
||||
const logsSampleDataLinkLabel = i18n.translate('xpack.infra.sampleDataLinkLabel', {
|
||||
|
@ -70,9 +75,52 @@ export function infra(kibana: any) {
|
|||
config(Joi: typeof JoiNamespace) {
|
||||
return getConfigSchema(Joi);
|
||||
},
|
||||
init(server: any) {
|
||||
initServerWithKibana(server);
|
||||
server.addAppLinksToSampleDataset('logs', [
|
||||
init(legacyServer: any) {
|
||||
const { newPlatform } = legacyServer as KbnServer;
|
||||
const { core, plugins } = newPlatform.setup;
|
||||
|
||||
const infraSetup = (plugins.infra as unknown) as InfraSetup; // chef's kiss
|
||||
|
||||
const initContext = ({
|
||||
config: infraSetup.__legacy.config,
|
||||
} as unknown) as PluginInitializerContext;
|
||||
// NP_TODO: Use real types from the other plugins as they are migrated
|
||||
const pluginDeps: InfraServerPluginDeps = {
|
||||
usageCollection: plugins.usageCollection as UsageCollectionSetup,
|
||||
indexPatterns: {
|
||||
indexPatternsServiceFactory: legacyServer.indexPatternsServiceFactory,
|
||||
},
|
||||
metrics: legacyServer.plugins.metrics,
|
||||
spaces: plugins.spaces,
|
||||
features: plugins.features,
|
||||
// NP_NOTE: [TSVB_GROUP] Huge hack to make TSVB (getVisData()) work with raw requests that
|
||||
// originate from the New Platform router (and are very different to the old request object).
|
||||
// Once TSVB has migrated over to NP, and can work with the new raw requests, or ideally just
|
||||
// the requestContext, this can be removed.
|
||||
___legacy: {
|
||||
tsvb: {
|
||||
elasticsearch: legacyServer.plugins.elasticsearch,
|
||||
__internals: legacyServer.newPlatform.__internals,
|
||||
},
|
||||
},
|
||||
apm: plugins.apm as APMPluginContract,
|
||||
};
|
||||
|
||||
const infraPluginInstance = plugin(initContext);
|
||||
infraPluginInstance.setup(core, pluginDeps);
|
||||
|
||||
// NP_TODO: EVERYTHING BELOW HERE IS LEGACY
|
||||
|
||||
const libs = infraPluginInstance.getLibs();
|
||||
|
||||
// NP_TODO how do we replace this? Answer: return from setup function.
|
||||
legacyServer.expose(
|
||||
'defineInternalSourceConfiguration',
|
||||
libs.sources.defineInternalSourceConfiguration.bind(libs.sources)
|
||||
);
|
||||
|
||||
// NP_TODO: How do we move this to new platform?
|
||||
legacyServer.addAppLinksToSampleDataset('logs', [
|
||||
{
|
||||
path: `/app/${APP_ID}#/logs`,
|
||||
label: logsSampleDataLinkLabel,
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
const ROOT_ELEMENT_ID = 'react-infra-root';
|
||||
const BREADCRUMBS_ELEMENT_ID = 'react-infra-breadcrumbs';
|
||||
|
||||
export class InfraKibanaFrameworkAdapter implements InfraFrameworkAdapter {
|
||||
export class KibanaFramework implements InfraFrameworkAdapter {
|
||||
public appState: object;
|
||||
public kbnVersion?: string;
|
||||
public timezone?: string;
|
||||
|
|
|
@ -20,7 +20,7 @@ import { HttpLink } from 'apollo-link-http';
|
|||
import { withClientState } from 'apollo-link-state';
|
||||
import { InfraFrontendLibs } from '../lib';
|
||||
import introspectionQueryResultData from '../../graphql/introspection.json';
|
||||
import { InfraKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter';
|
||||
import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter';
|
||||
import { InfraKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api';
|
||||
|
||||
export function compose(): InfraFrontendLibs {
|
||||
|
@ -57,7 +57,7 @@ export function compose(): InfraFrontendLibs {
|
|||
|
||||
const infraModule = uiModules.get('app/infa');
|
||||
|
||||
const framework = new InfraKibanaFrameworkAdapter(infraModule, uiRoutes, timezoneProvider);
|
||||
const framework = new KibanaFramework(infraModule, uiRoutes, timezoneProvider);
|
||||
|
||||
const libs: InfraFrontendLibs = {
|
||||
apolloClient,
|
||||
|
|
|
@ -17,7 +17,7 @@ import { InMemoryCache } from 'apollo-cache-inmemory';
|
|||
import ApolloClient from 'apollo-client';
|
||||
import { SchemaLink } from 'apollo-link-schema';
|
||||
import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools';
|
||||
import { InfraKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter';
|
||||
import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter';
|
||||
import { InfraKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api';
|
||||
import { InfraFrontendLibs } from '../lib';
|
||||
|
||||
|
@ -27,7 +27,7 @@ export function compose(): InfraFrontendLibs {
|
|||
basePath: chrome.getBasePath(),
|
||||
xsrfToken: chrome.getXsrfToken(),
|
||||
});
|
||||
const framework = new InfraKibanaFrameworkAdapter(infraModule, uiRoutes, timezoneProvider);
|
||||
const framework = new KibanaFramework(infraModule, uiRoutes, timezoneProvider);
|
||||
const typeDefs = `
|
||||
Query {}
|
||||
`;
|
||||
|
|
65
x-pack/legacy/plugins/infra/server/features.ts
Normal file
65
x-pack/legacy/plugins/infra/server/features.ts
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const METRICS_FEATURE = {
|
||||
id: 'infrastructure',
|
||||
name: i18n.translate('xpack.infra.featureRegistry.linkInfrastructureTitle', {
|
||||
defaultMessage: 'Infrastructure',
|
||||
}),
|
||||
icon: 'infraApp',
|
||||
navLinkId: 'infra:home',
|
||||
app: ['infra', 'kibana'],
|
||||
catalogue: ['infraops'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: ['infrastructure-ui-source'],
|
||||
read: ['index-pattern'],
|
||||
},
|
||||
ui: ['show', 'configureSource', 'save'],
|
||||
},
|
||||
read: {
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['infrastructure-ui-source', 'index-pattern'],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const LOGS_FEATURE = {
|
||||
id: 'logs',
|
||||
name: i18n.translate('xpack.infra.featureRegistry.linkLogsTitle', {
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
icon: 'loggingApp',
|
||||
navLinkId: 'infra:logs',
|
||||
app: ['infra', 'kibana'],
|
||||
catalogue: ['infralogging'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: ['infrastructure-ui-source'],
|
||||
read: [],
|
||||
},
|
||||
ui: ['show', 'configureSource', 'save'],
|
||||
},
|
||||
read: {
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['infrastructure-ui-source'],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
},
|
||||
};
|
|
@ -30,7 +30,7 @@ export const initInfraServer = (libs: InfraBackendLibs) => {
|
|||
typeDefs: schemas,
|
||||
});
|
||||
|
||||
libs.framework.registerGraphQLEndpoint('/api/infra/graphql', schema);
|
||||
libs.framework.registerGraphQLEndpoint('/graphql', schema);
|
||||
|
||||
initIpToHostName(libs);
|
||||
initLogAnalysisGetLogEntryRateRoute(libs);
|
||||
|
|
|
@ -4,97 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Server } from 'hapi';
|
||||
import JoiNamespace from 'joi';
|
||||
import { initInfraServer } from './infra_server';
|
||||
import { compose } from './lib/compose/kibana';
|
||||
import { UsageCollector } from './usage/usage_collector';
|
||||
import { inventoryViewSavedObjectType } from '../common/saved_objects/inventory_view';
|
||||
import { metricsExplorerViewSavedObjectType } from '../common/saved_objects/metrics_explorer_view';
|
||||
|
||||
export const initServerWithKibana = (kbnServer: Server) => {
|
||||
const { usageCollection } = kbnServer.newPlatform.setup.plugins;
|
||||
const libs = compose(kbnServer);
|
||||
initInfraServer(libs);
|
||||
|
||||
kbnServer.expose(
|
||||
'defineInternalSourceConfiguration',
|
||||
libs.sources.defineInternalSourceConfiguration.bind(libs.sources)
|
||||
);
|
||||
|
||||
// Register a function with server to manage the collection of usage stats
|
||||
UsageCollector.registerUsageCollector(usageCollection);
|
||||
|
||||
const xpackMainPlugin = kbnServer.plugins.xpack_main;
|
||||
xpackMainPlugin.registerFeature({
|
||||
id: 'infrastructure',
|
||||
name: i18n.translate('xpack.infra.featureRegistry.linkInfrastructureTitle', {
|
||||
defaultMessage: 'Metrics',
|
||||
}),
|
||||
icon: 'metricsApp',
|
||||
navLinkId: 'infra:home',
|
||||
app: ['infra', 'kibana'],
|
||||
catalogue: ['infraops'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: [
|
||||
'infrastructure-ui-source',
|
||||
inventoryViewSavedObjectType,
|
||||
metricsExplorerViewSavedObjectType,
|
||||
],
|
||||
read: ['index-pattern'],
|
||||
},
|
||||
ui: ['show', 'configureSource', 'save'],
|
||||
},
|
||||
read: {
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [
|
||||
'infrastructure-ui-source',
|
||||
'index-pattern',
|
||||
inventoryViewSavedObjectType,
|
||||
metricsExplorerViewSavedObjectType,
|
||||
],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
xpackMainPlugin.registerFeature({
|
||||
id: 'logs',
|
||||
name: i18n.translate('xpack.infra.featureRegistry.linkLogsTitle', {
|
||||
defaultMessage: 'Logs',
|
||||
}),
|
||||
icon: 'logsApp',
|
||||
navLinkId: 'infra:logs',
|
||||
app: ['infra', 'kibana'],
|
||||
catalogue: ['infralogging'],
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: ['infrastructure-ui-source'],
|
||||
read: [],
|
||||
},
|
||||
ui: ['show', 'configureSource', 'save'],
|
||||
},
|
||||
read: {
|
||||
api: ['infra'],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['infrastructure-ui-source'],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
export interface KbnServer extends Server {
|
||||
usage: any;
|
||||
}
|
||||
|
||||
// NP_TODO: this is only used in the root index file AFAICT, can remove after migrating to NP
|
||||
export const getConfigSchema = (Joi: typeof JoiNamespace) => {
|
||||
const InfraDefaultSourceConfigSchema = Joi.object({
|
||||
metricAlias: Joi.string(),
|
||||
|
@ -111,6 +28,7 @@ export const getConfigSchema = (Joi: typeof JoiNamespace) => {
|
|||
}),
|
||||
});
|
||||
|
||||
// NP_TODO: make sure this is all represented in the NP config schema
|
||||
const InfraRootConfigSchema = Joi.object({
|
||||
enabled: Joi.boolean().default(true),
|
||||
query: Joi.object({
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export interface InfraConfigurationAdapter<
|
||||
Configuration extends InfraBaseConfiguration = InfraBaseConfiguration
|
||||
> {
|
||||
get(): Promise<Configuration>;
|
||||
}
|
||||
|
||||
export interface InfraBaseConfiguration {
|
||||
enabled: boolean;
|
||||
query: {
|
||||
partitionSize: number;
|
||||
partitionFactor: number;
|
||||
};
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
export * from './adapter_types';
|
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InfraBaseConfiguration, InfraConfigurationAdapter } from './adapter_types';
|
||||
|
||||
export class InfraInmemoryConfigurationAdapter<Configuration extends InfraBaseConfiguration>
|
||||
implements InfraConfigurationAdapter<Configuration> {
|
||||
constructor(private readonly configuration: Configuration) {}
|
||||
|
||||
public async get() {
|
||||
return this.configuration;
|
||||
}
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InfraKibanaConfigurationAdapter } from './kibana_configuration_adapter';
|
||||
|
||||
describe('the InfraKibanaConfigurationAdapter', () => {
|
||||
test('queries the xpack.infra configuration of the server', async () => {
|
||||
const mockConfig = {
|
||||
get: jest.fn(),
|
||||
};
|
||||
|
||||
const configurationAdapter = new InfraKibanaConfigurationAdapter({
|
||||
config: () => mockConfig,
|
||||
});
|
||||
|
||||
await configurationAdapter.get();
|
||||
|
||||
expect(mockConfig.get).toBeCalledWith('xpack.infra');
|
||||
});
|
||||
|
||||
test('applies the query defaults', async () => {
|
||||
const configurationAdapter = new InfraKibanaConfigurationAdapter({
|
||||
config: () => ({
|
||||
get: () => ({}),
|
||||
}),
|
||||
});
|
||||
|
||||
const configuration = await configurationAdapter.get();
|
||||
|
||||
expect(configuration).toMatchObject({
|
||||
query: {
|
||||
partitionSize: expect.any(Number),
|
||||
partitionFactor: expect.any(Number),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,73 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Joi from 'joi';
|
||||
|
||||
import { InfraBaseConfiguration, InfraConfigurationAdapter } from './adapter_types';
|
||||
|
||||
export class InfraKibanaConfigurationAdapter implements InfraConfigurationAdapter {
|
||||
private readonly server: ServerWithConfig;
|
||||
|
||||
constructor(server: any) {
|
||||
if (!isServerWithConfig(server)) {
|
||||
throw new Error('Failed to find configuration on server.');
|
||||
}
|
||||
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public async get() {
|
||||
const config = this.server.config();
|
||||
|
||||
if (!isKibanaConfiguration(config)) {
|
||||
throw new Error('Failed to access configuration of server.');
|
||||
}
|
||||
|
||||
const configuration = config.get('xpack.infra') || {};
|
||||
const configurationWithDefaults: InfraBaseConfiguration = {
|
||||
enabled: true,
|
||||
query: {
|
||||
partitionSize: 75,
|
||||
partitionFactor: 1.2,
|
||||
...(configuration.query || {}),
|
||||
},
|
||||
...configuration,
|
||||
};
|
||||
|
||||
// we assume this to be the configuration because Kibana would have already validated it
|
||||
return configurationWithDefaults;
|
||||
}
|
||||
}
|
||||
|
||||
interface ServerWithConfig {
|
||||
config(): any;
|
||||
}
|
||||
|
||||
function isServerWithConfig(maybeServer: any): maybeServer is ServerWithConfig {
|
||||
return (
|
||||
Joi.validate(
|
||||
maybeServer,
|
||||
Joi.object({
|
||||
config: Joi.func().required(),
|
||||
}).unknown()
|
||||
).error === null
|
||||
);
|
||||
}
|
||||
|
||||
interface KibanaConfiguration {
|
||||
get(key: string): any;
|
||||
}
|
||||
|
||||
function isKibanaConfiguration(maybeConfiguration: any): maybeConfiguration is KibanaConfiguration {
|
||||
return (
|
||||
Joi.validate(
|
||||
maybeConfiguration,
|
||||
Joi.object({
|
||||
get: Joi.func().required(),
|
||||
}).unknown()
|
||||
).error === null
|
||||
);
|
||||
}
|
|
@ -4,11 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InfraFrameworkRequest } from '../framework';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
|
||||
export interface FieldsAdapter {
|
||||
getIndexFields(
|
||||
req: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
indices: string,
|
||||
timefield: string
|
||||
): Promise<IndexFieldDescriptor[]>;
|
||||
|
|
|
@ -5,11 +5,9 @@
|
|||
*/
|
||||
|
||||
import { startsWith, uniq, first } from 'lodash';
|
||||
import {
|
||||
InfraBackendFrameworkAdapter,
|
||||
InfraFrameworkRequest,
|
||||
InfraDatabaseSearchResponse,
|
||||
} from '../framework';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { InfraDatabaseSearchResponse } from '../framework';
|
||||
import { KibanaFramework } from '../framework/kibana_framework_adapter';
|
||||
import { FieldsAdapter, IndexFieldDescriptor } from './adapter_types';
|
||||
import { getAllowedListForPrefix } from '../../../../common/ecs_allowed_list';
|
||||
import { getAllCompositeData } from '../../../utils/get_all_composite_data';
|
||||
|
@ -30,22 +28,26 @@ interface DataSetResponse {
|
|||
}
|
||||
|
||||
export class FrameworkFieldsAdapter implements FieldsAdapter {
|
||||
private framework: InfraBackendFrameworkAdapter;
|
||||
private framework: KibanaFramework;
|
||||
|
||||
constructor(framework: InfraBackendFrameworkAdapter) {
|
||||
constructor(framework: KibanaFramework) {
|
||||
this.framework = framework;
|
||||
}
|
||||
|
||||
public async getIndexFields(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
indices: string,
|
||||
timefield: string
|
||||
): Promise<IndexFieldDescriptor[]> {
|
||||
const indexPatternsService = this.framework.getIndexPatternsService(request);
|
||||
const indexPatternsService = this.framework.getIndexPatternsService(requestContext);
|
||||
const response = await indexPatternsService.getFieldsForWildcard({
|
||||
pattern: indices,
|
||||
});
|
||||
const { dataSets, modules } = await this.getDataSetsAndModules(request, indices, timefield);
|
||||
const { dataSets, modules } = await this.getDataSetsAndModules(
|
||||
requestContext,
|
||||
indices,
|
||||
timefield
|
||||
);
|
||||
const allowedList = modules.reduce(
|
||||
(acc, name) => uniq([...acc, ...getAllowedListForPrefix(name)]),
|
||||
[] as string[]
|
||||
|
@ -58,7 +60,7 @@ export class FrameworkFieldsAdapter implements FieldsAdapter {
|
|||
}
|
||||
|
||||
private async getDataSetsAndModules(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
indices: string,
|
||||
timefield: string
|
||||
): Promise<{ dataSets: string[]; modules: string[] }> {
|
||||
|
@ -109,7 +111,7 @@ export class FrameworkFieldsAdapter implements FieldsAdapter {
|
|||
|
||||
const buckets = await getAllCompositeData<DataSetResponse, Bucket>(
|
||||
this.framework,
|
||||
request,
|
||||
requestContext,
|
||||
params,
|
||||
bucketSelector,
|
||||
handleAfterKey
|
||||
|
|
|
@ -4,91 +4,37 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { SearchResponse } from 'elasticsearch';
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
import { Lifecycle, ResponseToolkit, RouteOptions } from 'hapi';
|
||||
import { Legacy } from 'kibana';
|
||||
import { SearchResponse, GenericParams } from 'elasticsearch';
|
||||
import { Lifecycle } from 'hapi';
|
||||
import { ObjectType } from '@kbn/config-schema';
|
||||
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
|
||||
import { RouteMethod, RouteConfig } from '../../../../../../../../src/core/server';
|
||||
import { APMPluginContract } from '../../../../../../../plugins/apm/server/plugin';
|
||||
|
||||
import { KibanaConfig } from 'src/legacy/server/kbn_server';
|
||||
import { JsonObject } from '../../../../common/typed_json';
|
||||
import { TSVBMetricModel } from '../../../../common/inventory_models/types';
|
||||
|
||||
export const internalInfraFrameworkRequest = Symbol('internalInfraFrameworkRequest');
|
||||
|
||||
/* eslint-disable @typescript-eslint/unified-signatures */
|
||||
export interface InfraBackendFrameworkAdapter {
|
||||
version: string;
|
||||
exposeStaticDir(urlPath: string, dir: string): void;
|
||||
registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void;
|
||||
registerRoute<RouteRequest extends InfraWrappableRequest, RouteResponse extends InfraResponse>(
|
||||
route: InfraFrameworkRouteOptions<RouteRequest, RouteResponse>
|
||||
): void;
|
||||
callWithRequest<Hit = {}, Aggregation = undefined>(
|
||||
req: InfraFrameworkRequest,
|
||||
method: 'search',
|
||||
options?: object
|
||||
): Promise<InfraDatabaseSearchResponse<Hit, Aggregation>>;
|
||||
callWithRequest<Hit = {}, Aggregation = undefined>(
|
||||
req: InfraFrameworkRequest,
|
||||
method: 'msearch',
|
||||
options?: object
|
||||
): Promise<InfraDatabaseMultiResponse<Hit, Aggregation>>;
|
||||
callWithRequest(
|
||||
req: InfraFrameworkRequest,
|
||||
method: 'fieldCaps',
|
||||
options?: object
|
||||
): Promise<InfraDatabaseFieldCapsResponse>;
|
||||
callWithRequest(
|
||||
req: InfraFrameworkRequest,
|
||||
method: 'indices.existsAlias',
|
||||
options?: object
|
||||
): Promise<boolean>;
|
||||
callWithRequest(
|
||||
req: InfraFrameworkRequest,
|
||||
method: 'indices.getAlias',
|
||||
options?: object
|
||||
): Promise<InfraDatabaseGetIndicesAliasResponse>;
|
||||
callWithRequest(
|
||||
req: InfraFrameworkRequest,
|
||||
method: 'indices.get',
|
||||
options?: object
|
||||
): Promise<InfraDatabaseGetIndicesResponse>;
|
||||
callWithRequest(
|
||||
req: InfraFrameworkRequest,
|
||||
method: 'ml.getBuckets',
|
||||
options?: object
|
||||
): Promise<InfraDatabaseGetIndicesResponse>;
|
||||
callWithRequest(
|
||||
req: InfraFrameworkRequest,
|
||||
method: string,
|
||||
options?: object
|
||||
): Promise<InfraDatabaseSearchResponse>;
|
||||
getIndexPatternsService(req: InfraFrameworkRequest<any>): Legacy.IndexPatternsService;
|
||||
getSavedObjectsService(): Legacy.SavedObjectsService;
|
||||
getSpaceId(request: InfraFrameworkRequest<any>): string;
|
||||
makeTSVBRequest(
|
||||
req: InfraFrameworkRequest,
|
||||
model: TSVBMetricModel,
|
||||
timerange: { min: number; max: number },
|
||||
filters: JsonObject[]
|
||||
): Promise<InfraTSVBResponse>;
|
||||
config(req: InfraFrameworkRequest): KibanaConfig;
|
||||
}
|
||||
/* eslint-enable @typescript-eslint/unified-signatures */
|
||||
|
||||
export interface InfraFrameworkRequest<
|
||||
InternalRequest extends InfraWrappableRequest = InfraWrappableRequest
|
||||
> {
|
||||
[internalInfraFrameworkRequest]: InternalRequest;
|
||||
payload: InternalRequest['payload'];
|
||||
params: InternalRequest['params'];
|
||||
query: InternalRequest['query'];
|
||||
// NP_TODO: Compose real types from plugins we depend on, no "any"
|
||||
export interface InfraServerPluginDeps {
|
||||
usageCollection: UsageCollectionSetup;
|
||||
spaces: any;
|
||||
metrics: {
|
||||
getVisData: any;
|
||||
};
|
||||
indexPatterns: {
|
||||
indexPatternsServiceFactory: any;
|
||||
};
|
||||
features: any;
|
||||
apm: APMPluginContract;
|
||||
___legacy: any;
|
||||
}
|
||||
|
||||
export interface InfraWrappableRequest<Payload = any, Params = any, Query = any> {
|
||||
payload: Payload;
|
||||
params: Params;
|
||||
query: Query;
|
||||
export interface CallWithRequestParams extends GenericParams {
|
||||
max_concurrent_shard_requests?: number;
|
||||
name?: string;
|
||||
index?: string;
|
||||
ignore_unavailable?: boolean;
|
||||
allow_no_indices?: boolean;
|
||||
size?: number;
|
||||
terminate_after?: number;
|
||||
fields?: string;
|
||||
}
|
||||
|
||||
export type InfraResponse = Lifecycle.ReturnValue;
|
||||
|
@ -98,22 +44,6 @@ export interface InfraFrameworkPluginOptions {
|
|||
options: any;
|
||||
}
|
||||
|
||||
export interface InfraFrameworkRouteOptions<
|
||||
RouteRequest extends InfraWrappableRequest,
|
||||
RouteResponse extends InfraResponse
|
||||
> {
|
||||
path: string;
|
||||
method: string | string[];
|
||||
vhost?: string;
|
||||
handler: InfraFrameworkRouteHandler<RouteRequest, RouteResponse>;
|
||||
options?: Pick<RouteOptions, Exclude<keyof RouteOptions, 'handler'>>;
|
||||
}
|
||||
|
||||
export type InfraFrameworkRouteHandler<
|
||||
RouteRequest extends InfraWrappableRequest,
|
||||
RouteResponse extends InfraResponse
|
||||
> = (request: InfraFrameworkRequest<RouteRequest>, h: ResponseToolkit) => RouteResponse;
|
||||
|
||||
export interface InfraDatabaseResponse {
|
||||
took: number;
|
||||
timeout: boolean;
|
||||
|
@ -232,3 +162,12 @@ export interface InfraTSVBSeries {
|
|||
}
|
||||
|
||||
export type InfraTSVBDataPoint = [number, number];
|
||||
|
||||
export type InfraRouteConfig<
|
||||
params extends ObjectType,
|
||||
query extends ObjectType,
|
||||
body extends ObjectType,
|
||||
method extends RouteMethod
|
||||
> = {
|
||||
method: RouteMethod;
|
||||
} & RouteConfig<params, query, body, method>;
|
||||
|
|
|
@ -1,117 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import * as GraphiQL from 'apollo-server-module-graphiql';
|
||||
import Boom from 'boom';
|
||||
import { Plugin, Request, ResponseToolkit, RouteOptions, Server } from 'hapi';
|
||||
|
||||
import { GraphQLOptions, runHttpQuery } from 'apollo-server-core';
|
||||
|
||||
export type HapiOptionsFunction = (req: Request) => GraphQLOptions | Promise<GraphQLOptions>;
|
||||
|
||||
export interface HapiGraphQLPluginOptions {
|
||||
path: string;
|
||||
vhost?: string;
|
||||
route?: RouteOptions;
|
||||
graphqlOptions: GraphQLOptions | HapiOptionsFunction;
|
||||
}
|
||||
|
||||
export const graphqlHapi: Plugin<HapiGraphQLPluginOptions> = {
|
||||
name: 'graphql',
|
||||
register: (server: Server, options: HapiGraphQLPluginOptions) => {
|
||||
if (!options || !options.graphqlOptions) {
|
||||
throw new Error('Apollo Server requires options.');
|
||||
}
|
||||
|
||||
server.route({
|
||||
options: options.route || {},
|
||||
handler: async (request: Request, h: ResponseToolkit) => {
|
||||
try {
|
||||
const query =
|
||||
request.method === 'post'
|
||||
? (request.payload as Record<string, any>)
|
||||
: (request.query as Record<string, any>);
|
||||
|
||||
const gqlResponse = await runHttpQuery([request], {
|
||||
method: request.method.toUpperCase(),
|
||||
options: options.graphqlOptions,
|
||||
query,
|
||||
});
|
||||
|
||||
return h.response(gqlResponse).type('application/json');
|
||||
} catch (error) {
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
const queryError = Boom.boomify(error);
|
||||
|
||||
queryError.output.payload.message = error.message;
|
||||
|
||||
return queryError;
|
||||
}
|
||||
|
||||
if (error.isGraphQLError === true) {
|
||||
return h
|
||||
.response(error.message)
|
||||
.code(error.statusCode)
|
||||
.type('application/json');
|
||||
}
|
||||
|
||||
const genericError = new Boom(error.message, { statusCode: error.statusCode });
|
||||
|
||||
if (error.headers) {
|
||||
Object.keys(error.headers).forEach(header => {
|
||||
genericError.output.headers[header] = error.headers[header];
|
||||
});
|
||||
}
|
||||
|
||||
// Boom hides the error when status code is 500
|
||||
|
||||
genericError.output.payload.message = error.message;
|
||||
|
||||
throw genericError;
|
||||
}
|
||||
},
|
||||
method: ['GET', 'POST'],
|
||||
path: options.path || '/graphql',
|
||||
vhost: options.vhost || undefined,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export type HapiGraphiQLOptionsFunction = (
|
||||
req?: Request
|
||||
) => GraphiQL.GraphiQLData | Promise<GraphiQL.GraphiQLData>;
|
||||
|
||||
export interface HapiGraphiQLPluginOptions {
|
||||
path: string;
|
||||
|
||||
route?: any;
|
||||
|
||||
graphiqlOptions: GraphiQL.GraphiQLData | HapiGraphiQLOptionsFunction;
|
||||
}
|
||||
|
||||
export const graphiqlHapi: Plugin<HapiGraphiQLPluginOptions> = {
|
||||
name: 'graphiql',
|
||||
register: (server: Server, options: HapiGraphiQLPluginOptions) => {
|
||||
if (!options || !options.graphiqlOptions) {
|
||||
throw new Error('Apollo Server GraphiQL requires options.');
|
||||
}
|
||||
|
||||
server.route({
|
||||
options: options.route || {},
|
||||
handler: async (request: Request, h: ResponseToolkit) => {
|
||||
const graphiqlString = await GraphiQL.resolveGraphiQLString(
|
||||
request.query,
|
||||
options.graphiqlOptions,
|
||||
request
|
||||
);
|
||||
|
||||
return h.response(graphiqlString).type('text/html');
|
||||
},
|
||||
method: 'GET',
|
||||
path: options.path || '/graphiql',
|
||||
});
|
||||
},
|
||||
};
|
|
@ -4,116 +4,207 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/array-type */
|
||||
|
||||
import { GenericParams } from 'elasticsearch';
|
||||
import { GraphQLSchema } from 'graphql';
|
||||
import { Legacy } from 'kibana';
|
||||
|
||||
import { KibanaConfig } from 'src/legacy/server/kbn_server';
|
||||
import { get } from 'lodash';
|
||||
import { runHttpQuery } from 'apollo-server-core';
|
||||
import { schema, TypeOf, ObjectType } from '@kbn/config-schema';
|
||||
import {
|
||||
InfraBackendFrameworkAdapter,
|
||||
InfraFrameworkRequest,
|
||||
InfraFrameworkRouteOptions,
|
||||
InfraResponse,
|
||||
InfraRouteConfig,
|
||||
InfraTSVBResponse,
|
||||
InfraWrappableRequest,
|
||||
internalInfraFrameworkRequest,
|
||||
InfraServerPluginDeps,
|
||||
CallWithRequestParams,
|
||||
InfraDatabaseSearchResponse,
|
||||
InfraDatabaseMultiResponse,
|
||||
InfraDatabaseFieldCapsResponse,
|
||||
InfraDatabaseGetIndicesResponse,
|
||||
InfraDatabaseGetIndicesAliasResponse,
|
||||
} from './adapter_types';
|
||||
import {
|
||||
graphiqlHapi,
|
||||
graphqlHapi,
|
||||
HapiGraphiQLPluginOptions,
|
||||
HapiGraphQLPluginOptions,
|
||||
} from './apollo_server_hapi';
|
||||
import { TSVBMetricModel } from '../../../../common/inventory_models/types';
|
||||
import {
|
||||
CoreSetup,
|
||||
IRouter,
|
||||
KibanaRequest,
|
||||
RequestHandlerContext,
|
||||
KibanaResponseFactory,
|
||||
RouteMethod,
|
||||
} from '../../../../../../../../src/core/server';
|
||||
import { RequestHandler } from '../../../../../../../../src/core/server';
|
||||
import { InfraConfig } from '../../../../../../../plugins/infra/server';
|
||||
|
||||
interface CallWithRequestParams extends GenericParams {
|
||||
max_concurrent_shard_requests?: number;
|
||||
}
|
||||
export class KibanaFramework {
|
||||
public router: IRouter;
|
||||
private core: CoreSetup;
|
||||
public plugins: InfraServerPluginDeps;
|
||||
|
||||
export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFrameworkAdapter {
|
||||
public version: string;
|
||||
|
||||
constructor(private server: Legacy.Server) {
|
||||
this.version = server.config().get('pkg.version');
|
||||
}
|
||||
|
||||
public config(req: InfraFrameworkRequest<Legacy.Request>): KibanaConfig {
|
||||
const internalRequest = req[internalInfraFrameworkRequest];
|
||||
return internalRequest.server.config();
|
||||
}
|
||||
|
||||
public exposeStaticDir(urlPath: string, dir: string): void {
|
||||
this.server.route({
|
||||
handler: {
|
||||
directory: {
|
||||
path: dir,
|
||||
},
|
||||
},
|
||||
method: 'GET',
|
||||
path: urlPath,
|
||||
});
|
||||
}
|
||||
|
||||
public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void {
|
||||
this.server.register<HapiGraphQLPluginOptions>({
|
||||
options: {
|
||||
graphqlOptions: (req: Legacy.Request) => ({
|
||||
context: { req: wrapRequest(req) },
|
||||
schema,
|
||||
}),
|
||||
path: routePath,
|
||||
route: {
|
||||
tags: ['access:infra'],
|
||||
},
|
||||
},
|
||||
plugin: graphqlHapi,
|
||||
});
|
||||
|
||||
this.server.register<HapiGraphiQLPluginOptions>({
|
||||
options: {
|
||||
graphiqlOptions: request => ({
|
||||
endpointURL: request ? `${request.getBasePath()}${routePath}` : routePath,
|
||||
passHeader: `'kbn-version': '${this.version}'`,
|
||||
}),
|
||||
path: `${routePath}/graphiql`,
|
||||
route: {
|
||||
tags: ['access:infra'],
|
||||
},
|
||||
},
|
||||
plugin: graphiqlHapi,
|
||||
});
|
||||
constructor(core: CoreSetup, config: InfraConfig, plugins: InfraServerPluginDeps) {
|
||||
this.router = core.http.createRouter();
|
||||
this.core = core;
|
||||
this.plugins = plugins;
|
||||
}
|
||||
|
||||
public registerRoute<
|
||||
RouteRequest extends InfraWrappableRequest,
|
||||
RouteResponse extends InfraResponse
|
||||
>(route: InfraFrameworkRouteOptions<RouteRequest, RouteResponse>) {
|
||||
const wrappedHandler = (request: any, h: Legacy.ResponseToolkit) =>
|
||||
route.handler(wrapRequest(request), h);
|
||||
|
||||
this.server.route({
|
||||
handler: wrappedHandler,
|
||||
options: route.options,
|
||||
method: route.method,
|
||||
path: route.path,
|
||||
});
|
||||
params extends ObjectType = any,
|
||||
query extends ObjectType = any,
|
||||
body extends ObjectType = any,
|
||||
method extends RouteMethod = any
|
||||
>(
|
||||
config: InfraRouteConfig<params, query, body, method>,
|
||||
handler: RequestHandler<params, query, body>
|
||||
) {
|
||||
const defaultOptions = {
|
||||
tags: ['access:infra'],
|
||||
};
|
||||
const routeConfig = {
|
||||
path: config.path,
|
||||
validate: config.validate,
|
||||
// Currently we have no use of custom options beyond tags, this can be extended
|
||||
// beyond defaultOptions if it's needed.
|
||||
options: defaultOptions,
|
||||
};
|
||||
switch (config.method) {
|
||||
case 'get':
|
||||
this.router.get(routeConfig, handler);
|
||||
break;
|
||||
case 'post':
|
||||
this.router.post(routeConfig, handler);
|
||||
break;
|
||||
case 'delete':
|
||||
this.router.delete(routeConfig, handler);
|
||||
break;
|
||||
case 'put':
|
||||
this.router.put(routeConfig, handler);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public async callWithRequest(
|
||||
req: InfraFrameworkRequest<Legacy.Request>,
|
||||
public registerGraphQLEndpoint(routePath: string, gqlSchema: GraphQLSchema) {
|
||||
// These endpoints are validated by GraphQL at runtime and with GraphQL generated types
|
||||
const body = schema.object({}, { allowUnknowns: true });
|
||||
type Body = TypeOf<typeof body>;
|
||||
|
||||
const routeOptions = {
|
||||
path: `/api/infra${routePath}`,
|
||||
validate: {
|
||||
body,
|
||||
},
|
||||
options: {
|
||||
tags: ['access:infra'],
|
||||
},
|
||||
};
|
||||
async function handler(
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest<unknown, unknown, Body>,
|
||||
response: KibanaResponseFactory
|
||||
) {
|
||||
try {
|
||||
const query =
|
||||
request.route.method === 'post'
|
||||
? (request.body as Record<string, any>)
|
||||
: (request.query as Record<string, any>);
|
||||
|
||||
const gqlResponse = await runHttpQuery([context, request], {
|
||||
method: request.route.method.toUpperCase(),
|
||||
options: (req: RequestHandlerContext, rawReq: KibanaRequest) => ({
|
||||
context: { req, rawReq },
|
||||
schema: gqlSchema,
|
||||
}),
|
||||
query,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: gqlResponse,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const errorBody = {
|
||||
message: error.message,
|
||||
};
|
||||
|
||||
if ('HttpQueryError' !== error.name) {
|
||||
return response.internalError({
|
||||
body: errorBody,
|
||||
});
|
||||
}
|
||||
|
||||
if (error.isGraphQLError === true) {
|
||||
return response.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: errorBody,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const { headers = [], statusCode = 500 } = error;
|
||||
return response.customError({
|
||||
statusCode,
|
||||
headers,
|
||||
body: errorBody,
|
||||
});
|
||||
|
||||
// NP_TODO: Do we still need to re-throw this error in this case? if we do, can we
|
||||
// still call the response.customError method to control the HTTP response?
|
||||
// throw error;
|
||||
}
|
||||
}
|
||||
this.router.post(routeOptions, handler);
|
||||
this.router.get(routeOptions, handler);
|
||||
}
|
||||
|
||||
callWithRequest<Hit = {}, Aggregation = undefined>(
|
||||
requestContext: RequestHandlerContext,
|
||||
endpoint: 'search',
|
||||
options?: CallWithRequestParams
|
||||
): Promise<InfraDatabaseSearchResponse<Hit, Aggregation>>;
|
||||
callWithRequest<Hit = {}, Aggregation = undefined>(
|
||||
requestContext: RequestHandlerContext,
|
||||
endpoint: 'msearch',
|
||||
options?: CallWithRequestParams
|
||||
): Promise<InfraDatabaseMultiResponse<Hit, Aggregation>>;
|
||||
callWithRequest(
|
||||
requestContext: RequestHandlerContext,
|
||||
endpoint: 'fieldCaps',
|
||||
options?: CallWithRequestParams
|
||||
): Promise<InfraDatabaseFieldCapsResponse>;
|
||||
callWithRequest(
|
||||
requestContext: RequestHandlerContext,
|
||||
endpoint: 'indices.existsAlias',
|
||||
options?: CallWithRequestParams
|
||||
): Promise<boolean>;
|
||||
callWithRequest(
|
||||
requestContext: RequestHandlerContext,
|
||||
method: 'indices.getAlias',
|
||||
options?: object
|
||||
): Promise<InfraDatabaseGetIndicesAliasResponse>;
|
||||
callWithRequest(
|
||||
requestContext: RequestHandlerContext,
|
||||
method: 'indices.get' | 'ml.getBuckets',
|
||||
options?: object
|
||||
): Promise<InfraDatabaseGetIndicesResponse>;
|
||||
callWithRequest(
|
||||
requestContext: RequestHandlerContext,
|
||||
endpoint: string,
|
||||
params: CallWithRequestParams,
|
||||
...rest: any[]
|
||||
options?: CallWithRequestParams
|
||||
): Promise<InfraDatabaseSearchResponse>;
|
||||
|
||||
public async callWithRequest<Hit = {}, Aggregation = undefined>(
|
||||
requestContext: RequestHandlerContext,
|
||||
endpoint: string,
|
||||
params: CallWithRequestParams
|
||||
) {
|
||||
const internalRequest = req[internalInfraFrameworkRequest];
|
||||
const { elasticsearch } = internalRequest.server.plugins;
|
||||
const { callWithRequest } = elasticsearch.getCluster('data');
|
||||
const includeFrozen = await internalRequest.getUiSettingsService().get('search:includeFrozen');
|
||||
const { elasticsearch, uiSettings } = requestContext.core;
|
||||
|
||||
const includeFrozen = await uiSettings.client.get('search:includeFrozen');
|
||||
if (endpoint === 'msearch') {
|
||||
const maxConcurrentShardRequests = await internalRequest
|
||||
.getUiSettingsService()
|
||||
.get('courier:maxConcurrentShardRequests');
|
||||
const maxConcurrentShardRequests = await uiSettings.client.get(
|
||||
'courier:maxConcurrentShardRequests'
|
||||
);
|
||||
if (maxConcurrentShardRequests > 0) {
|
||||
params = { ...params, max_concurrent_shard_requests: maxConcurrentShardRequests };
|
||||
}
|
||||
|
@ -125,95 +216,79 @@ export class InfraKibanaBackendFrameworkAdapter implements InfraBackendFramework
|
|||
}
|
||||
: {};
|
||||
|
||||
const fields = await callWithRequest(
|
||||
internalRequest,
|
||||
endpoint,
|
||||
{
|
||||
...params,
|
||||
...frozenIndicesParams,
|
||||
},
|
||||
...rest
|
||||
);
|
||||
return fields;
|
||||
return elasticsearch.dataClient.callAsCurrentUser(endpoint, {
|
||||
...params,
|
||||
...frozenIndicesParams,
|
||||
});
|
||||
}
|
||||
|
||||
public getIndexPatternsService(
|
||||
request: InfraFrameworkRequest<Legacy.Request>
|
||||
requestContext: RequestHandlerContext
|
||||
): Legacy.IndexPatternsService {
|
||||
return this.server.indexPatternsServiceFactory({
|
||||
return this.plugins.indexPatterns.indexPatternsServiceFactory({
|
||||
callCluster: async (method: string, args: [GenericParams], ...rest: any[]) => {
|
||||
const fieldCaps = await this.callWithRequest(
|
||||
request,
|
||||
method,
|
||||
{ ...args, allowNoIndices: true } as GenericParams,
|
||||
...rest
|
||||
);
|
||||
const fieldCaps = await this.callWithRequest(requestContext, method, {
|
||||
...args,
|
||||
allowNoIndices: true,
|
||||
} as GenericParams);
|
||||
return fieldCaps;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
public getSpaceId(request: InfraFrameworkRequest): string {
|
||||
const spacesPlugin = this.server.plugins.spaces;
|
||||
public getSpaceId(request: KibanaRequest): string {
|
||||
const spacesPlugin = this.plugins.spaces;
|
||||
|
||||
if (spacesPlugin && typeof spacesPlugin.getSpaceId === 'function') {
|
||||
return spacesPlugin.getSpaceId(request[internalInfraFrameworkRequest]);
|
||||
if (
|
||||
spacesPlugin &&
|
||||
spacesPlugin.spacesService &&
|
||||
typeof spacesPlugin.spacesService.getSpaceId === 'function'
|
||||
) {
|
||||
return spacesPlugin.spacesService.getSpaceId(request);
|
||||
} else {
|
||||
return 'default';
|
||||
}
|
||||
}
|
||||
|
||||
public getSavedObjectsService() {
|
||||
return this.server.savedObjects;
|
||||
}
|
||||
|
||||
// NP_TODO: This method needs to no longer require full KibanaRequest
|
||||
public async makeTSVBRequest(
|
||||
req: InfraFrameworkRequest<Legacy.Request>,
|
||||
request: KibanaRequest,
|
||||
model: TSVBMetricModel,
|
||||
timerange: { min: number; max: number },
|
||||
filters: any[]
|
||||
) {
|
||||
const internalRequest = req[internalInfraFrameworkRequest];
|
||||
const server = internalRequest.server;
|
||||
const getVisData = get(server, 'plugins.metrics.getVisData');
|
||||
filters: any[],
|
||||
requestContext: RequestHandlerContext
|
||||
): Promise<InfraTSVBResponse> {
|
||||
const { getVisData } = this.plugins.metrics;
|
||||
if (typeof getVisData !== 'function') {
|
||||
throw new Error('TSVB is not available');
|
||||
}
|
||||
|
||||
// getBasePath returns randomized base path AND spaces path
|
||||
const basePath = internalRequest.getBasePath();
|
||||
const url = `${basePath}/api/metrics/vis/data`;
|
||||
|
||||
const url = this.core.http.basePath.prepend('/api/metrics/vis/data');
|
||||
// For the following request we need a copy of the instnace of the internal request
|
||||
// but modified for our TSVB request. This will ensure all the instance methods
|
||||
// are available along with our overriden values
|
||||
const request = Object.assign(
|
||||
Object.create(Object.getPrototypeOf(internalRequest)),
|
||||
internalRequest,
|
||||
{
|
||||
url,
|
||||
method: 'POST',
|
||||
payload: {
|
||||
timerange,
|
||||
panels: [model],
|
||||
filters,
|
||||
const requestCopy = Object.assign({}, request, {
|
||||
url,
|
||||
method: 'POST',
|
||||
payload: {
|
||||
timerange,
|
||||
panels: [model],
|
||||
filters,
|
||||
},
|
||||
// NP_NOTE: [TSVB_GROUP] Huge hack to make TSVB (getVisData()) work with raw requests that
|
||||
// originate from the New Platform router (and are very different to the old request object).
|
||||
// Once TSVB has migrated over to NP, and can work with the new raw requests, or ideally just
|
||||
// the requestContext, this can be removed.
|
||||
server: {
|
||||
plugins: {
|
||||
elasticsearch: this.plugins.___legacy.tsvb.elasticsearch,
|
||||
},
|
||||
}
|
||||
);
|
||||
const result = await getVisData(request);
|
||||
return result as InfraTSVBResponse;
|
||||
newPlatform: {
|
||||
__internals: this.plugins.___legacy.tsvb.__internals,
|
||||
},
|
||||
},
|
||||
getUiSettingsService: () => requestContext.core.uiSettings.client,
|
||||
getSavedObjectsClient: () => requestContext.core.savedObjects.client,
|
||||
});
|
||||
return getVisData(requestCopy);
|
||||
}
|
||||
}
|
||||
|
||||
export function wrapRequest<InternalRequest extends InfraWrappableRequest>(
|
||||
req: InternalRequest
|
||||
): InfraFrameworkRequest<InternalRequest> {
|
||||
const { params, payload, query } = req;
|
||||
|
||||
return {
|
||||
[internalInfraFrameworkRequest]: req,
|
||||
params,
|
||||
payload,
|
||||
query,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import zip from 'lodash/fp/zip';
|
|||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { map, fold } from 'fp-ts/lib/Either';
|
||||
import { identity, constant } from 'fp-ts/lib/function';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { compareTimeKeys, isTimeKey, TimeKey } from '../../../../common/time';
|
||||
import { JsonObject } from '../../../../common/typed_json';
|
||||
import {
|
||||
|
@ -24,8 +25,8 @@ import {
|
|||
LogSummaryBucket,
|
||||
} from '../../domains/log_entries_domain';
|
||||
import { InfraSourceConfiguration } from '../../sources';
|
||||
import { InfraFrameworkRequest, SortedSearchHit } from '../framework';
|
||||
import { InfraBackendFrameworkAdapter } from '../framework';
|
||||
import { SortedSearchHit } from '../framework';
|
||||
import { KibanaFramework } from '../framework/kibana_framework_adapter';
|
||||
|
||||
const DAY_MILLIS = 24 * 60 * 60 * 1000;
|
||||
const LOOKUP_OFFSETS = [0, 1, 7, 30, 365, 10000, Infinity].map(days => days * DAY_MILLIS);
|
||||
|
@ -39,10 +40,10 @@ interface LogItemHit {
|
|||
}
|
||||
|
||||
export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
|
||||
constructor(private readonly framework: InfraBackendFrameworkAdapter) {}
|
||||
constructor(private readonly framework: KibanaFramework) {}
|
||||
|
||||
public async getAdjacentLogEntryDocuments(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
fields: string[],
|
||||
start: TimeKey,
|
||||
|
@ -64,7 +65,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
|
|||
}
|
||||
|
||||
const documentsInInterval = await this.getLogEntryDocumentsBetween(
|
||||
request,
|
||||
requestContext,
|
||||
sourceConfiguration,
|
||||
fields,
|
||||
intervalStart,
|
||||
|
@ -82,7 +83,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
|
|||
}
|
||||
|
||||
public async getContainedLogEntryDocuments(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
fields: string[],
|
||||
start: TimeKey,
|
||||
|
@ -91,7 +92,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
|
|||
highlightQuery?: LogEntryQuery
|
||||
): Promise<LogEntryDocument[]> {
|
||||
const documents = await this.getLogEntryDocumentsBetween(
|
||||
request,
|
||||
requestContext,
|
||||
sourceConfiguration,
|
||||
fields,
|
||||
start.time,
|
||||
|
@ -106,7 +107,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
|
|||
}
|
||||
|
||||
public async getContainedLogSummaryBuckets(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
start: number,
|
||||
end: number,
|
||||
|
@ -165,7 +166,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
|
|||
},
|
||||
};
|
||||
|
||||
const response = await this.framework.callWithRequest<any, {}>(request, 'search', query);
|
||||
const response = await this.framework.callWithRequest<any, {}>(requestContext, 'search', query);
|
||||
|
||||
return pipe(
|
||||
LogSummaryResponseRuntimeType.decode(response),
|
||||
|
@ -179,12 +180,12 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
|
|||
}
|
||||
|
||||
public async getLogItem(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
id: string,
|
||||
sourceConfiguration: InfraSourceConfiguration
|
||||
) {
|
||||
const search = (searchOptions: object) =>
|
||||
this.framework.callWithRequest<LogItemHit, {}>(request, 'search', searchOptions);
|
||||
this.framework.callWithRequest<LogItemHit, {}>(requestContext, 'search', searchOptions);
|
||||
|
||||
const params = {
|
||||
index: sourceConfiguration.logAlias,
|
||||
|
@ -212,7 +213,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
|
|||
}
|
||||
|
||||
private async getLogEntryDocumentsBetween(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
fields: string[],
|
||||
start: number,
|
||||
|
@ -298,7 +299,7 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter {
|
|||
};
|
||||
|
||||
const response = await this.framework.callWithRequest<SortedSearchHit>(
|
||||
request,
|
||||
requestContext,
|
||||
'search',
|
||||
query
|
||||
);
|
||||
|
|
|
@ -4,15 +4,14 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RequestHandlerContext, KibanaRequest } from 'src/core/server';
|
||||
import {
|
||||
InfraMetric,
|
||||
InfraMetricData,
|
||||
InfraNodeType,
|
||||
InfraTimerangeInput,
|
||||
} from '../../../graphql/types';
|
||||
|
||||
import { InfraSourceConfiguration } from '../../sources';
|
||||
import { InfraFrameworkRequest } from '../framework';
|
||||
|
||||
export interface InfraMetricsRequestOptions {
|
||||
nodeIds: {
|
||||
|
@ -27,8 +26,9 @@ export interface InfraMetricsRequestOptions {
|
|||
|
||||
export interface InfraMetricsAdapter {
|
||||
getMetrics(
|
||||
req: InfraFrameworkRequest,
|
||||
options: InfraMetricsRequestOptions
|
||||
requestContext: RequestHandlerContext,
|
||||
options: InfraMetricsRequestOptions,
|
||||
request: KibanaRequest // NP_TODO: temporarily needed until metrics getVisData no longer needs full request
|
||||
): Promise<InfraMetricData[]>;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,10 +6,9 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { flatten, get } from 'lodash';
|
||||
|
||||
import Boom from 'boom';
|
||||
import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
|
||||
import { InfraMetric, InfraMetricData, InfraNodeType } from '../../../graphql/types';
|
||||
import { InfraBackendFrameworkAdapter, InfraFrameworkRequest } from '../framework';
|
||||
import { KibanaFramework } from '../framework/kibana_framework_adapter';
|
||||
import { InfraMetricsAdapter, InfraMetricsRequestOptions } from './adapter_types';
|
||||
import { checkValidNode } from './lib/check_valid_node';
|
||||
import { metrics } from '../../../../common/inventory_models';
|
||||
|
@ -17,15 +16,16 @@ import { TSVBMetricModelCreator } from '../../../../common/inventory_models/type
|
|||
import { calculateMetricInterval } from '../../../utils/calculate_metric_interval';
|
||||
|
||||
export class KibanaMetricsAdapter implements InfraMetricsAdapter {
|
||||
private framework: InfraBackendFrameworkAdapter;
|
||||
private framework: KibanaFramework;
|
||||
|
||||
constructor(framework: InfraBackendFrameworkAdapter) {
|
||||
constructor(framework: KibanaFramework) {
|
||||
this.framework = framework;
|
||||
}
|
||||
|
||||
public async getMetrics(
|
||||
req: InfraFrameworkRequest,
|
||||
options: InfraMetricsRequestOptions
|
||||
requestContext: RequestHandlerContext,
|
||||
options: InfraMetricsRequestOptions,
|
||||
rawRequest: KibanaRequest // NP_TODO: Temporarily needed until metrics getVisData no longer needs full request
|
||||
): Promise<InfraMetricData[]> {
|
||||
const fields = {
|
||||
[InfraNodeType.host]: options.sourceConfiguration.fields.host,
|
||||
|
@ -35,11 +35,11 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
|
|||
const indexPattern = `${options.sourceConfiguration.metricAlias},${options.sourceConfiguration.logAlias}`;
|
||||
const nodeField = fields[options.nodeType];
|
||||
const search = <Aggregation>(searchOptions: object) =>
|
||||
this.framework.callWithRequest<{}, Aggregation>(req, 'search', searchOptions);
|
||||
this.framework.callWithRequest<{}, Aggregation>(requestContext, 'search', searchOptions);
|
||||
|
||||
const validNode = await checkValidNode(search, indexPattern, nodeField, options.nodeIds.nodeId);
|
||||
if (!validNode) {
|
||||
throw Boom.notFound(
|
||||
throw new Error(
|
||||
i18n.translate('xpack.infra.kibanaMetrics.nodeDoesNotExistErrorMessage', {
|
||||
defaultMessage: '{nodeId} does not exist.',
|
||||
values: {
|
||||
|
@ -50,7 +50,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
|
|||
}
|
||||
|
||||
const requests = options.metrics.map(metricId =>
|
||||
this.makeTSVBRequest(metricId, options, req, nodeField)
|
||||
this.makeTSVBRequest(metricId, options, rawRequest, nodeField, requestContext)
|
||||
);
|
||||
|
||||
return Promise.all(requests)
|
||||
|
@ -92,12 +92,13 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
|
|||
async makeTSVBRequest(
|
||||
metricId: InfraMetric,
|
||||
options: InfraMetricsRequestOptions,
|
||||
req: InfraFrameworkRequest,
|
||||
nodeField: string
|
||||
req: KibanaRequest,
|
||||
nodeField: string,
|
||||
requestContext: RequestHandlerContext
|
||||
) {
|
||||
const createTSVBModel = get(metrics, ['tsvb', metricId]) as TSVBMetricModelCreator | undefined;
|
||||
if (!createTSVBModel) {
|
||||
throw Boom.badRequest(
|
||||
throw new Error(
|
||||
i18n.translate('xpack.infra.metrics.missingTSVBModelError', {
|
||||
defaultMessage: 'The TSVB model for {metricId} does not exist for {nodeType}',
|
||||
values: {
|
||||
|
@ -121,7 +122,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
|
|||
);
|
||||
const calculatedInterval = await calculateMetricInterval(
|
||||
this.framework,
|
||||
req,
|
||||
requestContext,
|
||||
{
|
||||
indexPattern: `${options.sourceConfiguration.logAlias},${options.sourceConfiguration.metricAlias}`,
|
||||
timestampField: options.sourceConfiguration.fields.timestamp,
|
||||
|
@ -135,7 +136,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
|
|||
}
|
||||
|
||||
if (model.id_type === 'cloud' && !options.nodeIds.cloudId) {
|
||||
throw Boom.badRequest(
|
||||
throw new Error(
|
||||
i18n.translate('xpack.infra.kibanaMetrics.cloudIdMissingErrorMessage', {
|
||||
defaultMessage:
|
||||
'Model for {metricId} requires a cloudId, but none was given for {nodeId}.',
|
||||
|
@ -152,6 +153,6 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter {
|
|||
? [{ match: { [model.map_field_to]: id } }]
|
||||
: [{ match: { [nodeField]: id } }];
|
||||
|
||||
return this.framework.makeTSVBRequest(req, model, timerange, filters);
|
||||
return this.framework.makeTSVBRequest(req, model, timerange, filters, requestContext);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,26 +4,24 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { InfraSourceStatusAdapter } from '../../source_status';
|
||||
import {
|
||||
InfraBackendFrameworkAdapter,
|
||||
InfraDatabaseGetIndicesResponse,
|
||||
InfraFrameworkRequest,
|
||||
} from '../framework';
|
||||
import { InfraDatabaseGetIndicesResponse } from '../framework';
|
||||
import { KibanaFramework } from '../framework/kibana_framework_adapter';
|
||||
|
||||
export class InfraElasticsearchSourceStatusAdapter implements InfraSourceStatusAdapter {
|
||||
constructor(private readonly framework: InfraBackendFrameworkAdapter) {}
|
||||
constructor(private readonly framework: KibanaFramework) {}
|
||||
|
||||
public async getIndexNames(request: InfraFrameworkRequest, aliasName: string) {
|
||||
public async getIndexNames(requestContext: RequestHandlerContext, aliasName: string) {
|
||||
const indexMaps = await Promise.all([
|
||||
this.framework
|
||||
.callWithRequest(request, 'indices.getAlias', {
|
||||
.callWithRequest(requestContext, 'indices.getAlias', {
|
||||
name: aliasName,
|
||||
filterPath: '*.settings.index.uuid', // to keep the response size as small as possible
|
||||
})
|
||||
.catch(withDefaultIfNotFound<InfraDatabaseGetIndicesResponse>({})),
|
||||
this.framework
|
||||
.callWithRequest(request, 'indices.get', {
|
||||
.callWithRequest(requestContext, 'indices.get', {
|
||||
index: aliasName,
|
||||
filterPath: '*.settings.index.uuid', // to keep the response size as small as possible
|
||||
})
|
||||
|
@ -36,15 +34,15 @@ export class InfraElasticsearchSourceStatusAdapter implements InfraSourceStatusA
|
|||
);
|
||||
}
|
||||
|
||||
public async hasAlias(request: InfraFrameworkRequest, aliasName: string) {
|
||||
return await this.framework.callWithRequest(request, 'indices.existsAlias', {
|
||||
public async hasAlias(requestContext: RequestHandlerContext, aliasName: string) {
|
||||
return await this.framework.callWithRequest(requestContext, 'indices.existsAlias', {
|
||||
name: aliasName,
|
||||
});
|
||||
}
|
||||
|
||||
public async hasIndices(request: InfraFrameworkRequest, indexNames: string) {
|
||||
public async hasIndices(requestContext: RequestHandlerContext, indexNames: string) {
|
||||
return await this.framework
|
||||
.callWithRequest(request, 'search', {
|
||||
.callWithRequest(requestContext, 'search', {
|
||||
ignore_unavailable: true,
|
||||
allow_no_indices: true,
|
||||
index: indexNames,
|
||||
|
|
|
@ -3,12 +3,8 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Server } from 'hapi';
|
||||
|
||||
import { InfraKibanaConfigurationAdapter } from '../adapters/configuration/kibana_configuration_adapter';
|
||||
import { FrameworkFieldsAdapter } from '../adapters/fields/framework_fields_adapter';
|
||||
import { InfraKibanaBackendFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter';
|
||||
import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter';
|
||||
import { InfraKibanaLogEntriesAdapter } from '../adapters/log_entries/kibana_log_entries_adapter';
|
||||
import { KibanaMetricsAdapter } from '../adapters/metrics/kibana_metrics_adapter';
|
||||
import { InfraElasticsearchSourceStatusAdapter } from '../adapters/source_status';
|
||||
|
@ -20,13 +16,14 @@ import { InfraLogAnalysis } from '../log_analysis';
|
|||
import { InfraSnapshot } from '../snapshot';
|
||||
import { InfraSourceStatus } from '../source_status';
|
||||
import { InfraSources } from '../sources';
|
||||
import { InfraConfig } from '../../../../../../plugins/infra/server';
|
||||
import { CoreSetup } from '../../../../../../../src/core/server';
|
||||
import { InfraServerPluginDeps } from '../adapters/framework/adapter_types';
|
||||
|
||||
export function compose(server: Server): InfraBackendLibs {
|
||||
const configuration = new InfraKibanaConfigurationAdapter(server);
|
||||
const framework = new InfraKibanaBackendFrameworkAdapter(server);
|
||||
export function compose(core: CoreSetup, config: InfraConfig, plugins: InfraServerPluginDeps) {
|
||||
const framework = new KibanaFramework(core, config, plugins);
|
||||
const sources = new InfraSources({
|
||||
configuration,
|
||||
savedObjects: framework.getSavedObjectsService(),
|
||||
config,
|
||||
});
|
||||
const sourceStatus = new InfraSourceStatus(new InfraElasticsearchSourceStatusAdapter(framework), {
|
||||
sources,
|
||||
|
@ -34,6 +31,7 @@ export function compose(server: Server): InfraBackendLibs {
|
|||
const snapshot = new InfraSnapshot({ sources, framework });
|
||||
const logAnalysis = new InfraLogAnalysis({ framework });
|
||||
|
||||
// TODO: separate these out individually and do away with "domains" as a temporary group
|
||||
const domainLibs: InfraDomainLibs = {
|
||||
fields: new InfraFieldsDomain(new FrameworkFieldsAdapter(framework), {
|
||||
sources,
|
||||
|
@ -45,7 +43,7 @@ export function compose(server: Server): InfraBackendLibs {
|
|||
};
|
||||
|
||||
const libs: InfraBackendLibs = {
|
||||
configuration,
|
||||
configuration: config, // NP_TODO: Do we ever use this anywhere?
|
||||
framework,
|
||||
logAnalysis,
|
||||
snapshot,
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { InfraIndexField, InfraIndexType } from '../../graphql/types';
|
||||
import { FieldsAdapter } from '../adapters/fields';
|
||||
import { InfraFrameworkRequest } from '../adapters/framework';
|
||||
import { InfraSources } from '../sources';
|
||||
|
||||
export class InfraFieldsDomain {
|
||||
|
@ -16,16 +16,19 @@ export class InfraFieldsDomain {
|
|||
) {}
|
||||
|
||||
public async getFields(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string,
|
||||
indexType: InfraIndexType
|
||||
): Promise<InfraIndexField[]> {
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const includeMetricIndices = [InfraIndexType.ANY, InfraIndexType.METRICS].includes(indexType);
|
||||
const includeLogIndices = [InfraIndexType.ANY, InfraIndexType.LOGS].includes(indexType);
|
||||
|
||||
const fields = await this.adapter.getIndexFields(
|
||||
request,
|
||||
requestContext,
|
||||
`${includeMetricIndices ? configuration.metricAlias : ''},${
|
||||
includeLogIndices ? configuration.logAlias : ''
|
||||
}`,
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import stringify from 'json-stable-stringify';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { TimeKey } from '../../../../common/time';
|
||||
import { JsonObject } from '../../../../common/typed_json';
|
||||
import {
|
||||
|
@ -16,7 +17,6 @@ import {
|
|||
InfraLogSummaryBucket,
|
||||
InfraLogSummaryHighlightBucket,
|
||||
} from '../../../graphql/types';
|
||||
import { InfraFrameworkRequest } from '../../adapters/framework';
|
||||
import {
|
||||
InfraSourceConfiguration,
|
||||
InfraSources,
|
||||
|
@ -40,7 +40,7 @@ export class InfraLogEntriesDomain {
|
|||
) {}
|
||||
|
||||
public async getLogEntriesAround(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string,
|
||||
key: TimeKey,
|
||||
maxCountBefore: number,
|
||||
|
@ -55,14 +55,17 @@ export class InfraLogEntriesDomain {
|
|||
};
|
||||
}
|
||||
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const messageFormattingRules = compileFormattingRules(
|
||||
getBuiltinRules(configuration.fields.message)
|
||||
);
|
||||
const requiredFields = getRequiredFields(configuration, messageFormattingRules);
|
||||
|
||||
const documentsBefore = await this.adapter.getAdjacentLogEntryDocuments(
|
||||
request,
|
||||
requestContext,
|
||||
configuration,
|
||||
requiredFields,
|
||||
key,
|
||||
|
@ -80,7 +83,7 @@ export class InfraLogEntriesDomain {
|
|||
};
|
||||
|
||||
const documentsAfter = await this.adapter.getAdjacentLogEntryDocuments(
|
||||
request,
|
||||
requestContext,
|
||||
configuration,
|
||||
requiredFields,
|
||||
lastKeyBefore,
|
||||
|
@ -101,20 +104,23 @@ export class InfraLogEntriesDomain {
|
|||
}
|
||||
|
||||
public async getLogEntriesBetween(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string,
|
||||
startKey: TimeKey,
|
||||
endKey: TimeKey,
|
||||
filterQuery?: LogEntryQuery,
|
||||
highlightQuery?: LogEntryQuery
|
||||
): Promise<InfraLogEntry[]> {
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const messageFormattingRules = compileFormattingRules(
|
||||
getBuiltinRules(configuration.fields.message)
|
||||
);
|
||||
const requiredFields = getRequiredFields(configuration, messageFormattingRules);
|
||||
const documents = await this.adapter.getContainedLogEntryDocuments(
|
||||
request,
|
||||
requestContext,
|
||||
configuration,
|
||||
requiredFields,
|
||||
startKey,
|
||||
|
@ -129,7 +135,7 @@ export class InfraLogEntriesDomain {
|
|||
}
|
||||
|
||||
public async getLogEntryHighlights(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string,
|
||||
startKey: TimeKey,
|
||||
endKey: TimeKey,
|
||||
|
@ -140,7 +146,10 @@ export class InfraLogEntriesDomain {
|
|||
}>,
|
||||
filterQuery?: LogEntryQuery
|
||||
): Promise<InfraLogEntry[][]> {
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const messageFormattingRules = compileFormattingRules(
|
||||
getBuiltinRules(configuration.fields.message)
|
||||
);
|
||||
|
@ -158,7 +167,7 @@ export class InfraLogEntriesDomain {
|
|||
: highlightQuery;
|
||||
const [documentsBefore, documents, documentsAfter] = await Promise.all([
|
||||
this.adapter.getAdjacentLogEntryDocuments(
|
||||
request,
|
||||
requestContext,
|
||||
configuration,
|
||||
requiredFields,
|
||||
startKey,
|
||||
|
@ -168,7 +177,7 @@ export class InfraLogEntriesDomain {
|
|||
highlightQuery
|
||||
),
|
||||
this.adapter.getContainedLogEntryDocuments(
|
||||
request,
|
||||
requestContext,
|
||||
configuration,
|
||||
requiredFields,
|
||||
startKey,
|
||||
|
@ -177,7 +186,7 @@ export class InfraLogEntriesDomain {
|
|||
highlightQuery
|
||||
),
|
||||
this.adapter.getAdjacentLogEntryDocuments(
|
||||
request,
|
||||
requestContext,
|
||||
configuration,
|
||||
requiredFields,
|
||||
endKey,
|
||||
|
@ -203,16 +212,19 @@ export class InfraLogEntriesDomain {
|
|||
}
|
||||
|
||||
public async getLogSummaryBucketsBetween(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string,
|
||||
start: number,
|
||||
end: number,
|
||||
bucketSize: number,
|
||||
filterQuery?: LogEntryQuery
|
||||
): Promise<InfraLogSummaryBucket[]> {
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const dateRangeBuckets = await this.adapter.getContainedLogSummaryBuckets(
|
||||
request,
|
||||
requestContext,
|
||||
configuration,
|
||||
start,
|
||||
end,
|
||||
|
@ -223,7 +235,7 @@ export class InfraLogEntriesDomain {
|
|||
}
|
||||
|
||||
public async getLogSummaryHighlightBucketsBetween(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string,
|
||||
start: number,
|
||||
end: number,
|
||||
|
@ -231,7 +243,10 @@ export class InfraLogEntriesDomain {
|
|||
highlightQueries: string[],
|
||||
filterQuery?: LogEntryQuery
|
||||
): Promise<InfraLogSummaryHighlightBucket[][]> {
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const { configuration } = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const messageFormattingRules = compileFormattingRules(
|
||||
getBuiltinRules(configuration.fields.message)
|
||||
);
|
||||
|
@ -248,7 +263,7 @@ export class InfraLogEntriesDomain {
|
|||
}
|
||||
: highlightQuery;
|
||||
const summaryBuckets = await this.adapter.getContainedLogSummaryBuckets(
|
||||
request,
|
||||
requestContext,
|
||||
configuration,
|
||||
start,
|
||||
end,
|
||||
|
@ -266,11 +281,11 @@ export class InfraLogEntriesDomain {
|
|||
}
|
||||
|
||||
public async getLogItem(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
id: string,
|
||||
sourceConfiguration: InfraSourceConfiguration
|
||||
): Promise<InfraLogItem> {
|
||||
const document = await this.adapter.getLogItem(request, id, sourceConfiguration);
|
||||
const document = await this.adapter.getLogItem(requestContext, id, sourceConfiguration);
|
||||
const defaultFields = [
|
||||
{ field: '_index', value: document._index },
|
||||
{ field: '_id', value: document._id },
|
||||
|
@ -300,7 +315,7 @@ interface LogItemHit {
|
|||
|
||||
export interface LogEntriesAdapter {
|
||||
getAdjacentLogEntryDocuments(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
fields: string[],
|
||||
start: TimeKey,
|
||||
|
@ -311,7 +326,7 @@ export interface LogEntriesAdapter {
|
|||
): Promise<LogEntryDocument[]>;
|
||||
|
||||
getContainedLogEntryDocuments(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
fields: string[],
|
||||
start: TimeKey,
|
||||
|
@ -321,7 +336,7 @@ export interface LogEntriesAdapter {
|
|||
): Promise<LogEntryDocument[]>;
|
||||
|
||||
getContainedLogSummaryBuckets(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
start: number,
|
||||
end: number,
|
||||
|
@ -330,7 +345,7 @@ export interface LogEntriesAdapter {
|
|||
): Promise<LogSummaryBucket[]>;
|
||||
|
||||
getLogItem(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
id: string,
|
||||
source: InfraSourceConfiguration
|
||||
): Promise<LogItemHit>;
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
|
||||
import { InfraMetricData } from '../../graphql/types';
|
||||
import { InfraFrameworkRequest } from '../adapters/framework/adapter_types';
|
||||
import { InfraMetricsAdapter, InfraMetricsRequestOptions } from '../adapters/metrics/adapter_types';
|
||||
|
||||
export class InfraMetricsDomain {
|
||||
|
@ -16,9 +16,10 @@ export class InfraMetricsDomain {
|
|||
}
|
||||
|
||||
public async getMetrics(
|
||||
req: InfraFrameworkRequest,
|
||||
options: InfraMetricsRequestOptions
|
||||
requestContext: RequestHandlerContext,
|
||||
options: InfraMetricsRequestOptions,
|
||||
rawRequest: KibanaRequest // NP_TODO: temporarily needed until metrics getVisData no longer needs full request
|
||||
): Promise<InfraMetricData[]> {
|
||||
return await this.adapter.getMetrics(req, options);
|
||||
return await this.adapter.getMetrics(requestContext, options, rawRequest);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
*/
|
||||
|
||||
import { InfraSourceConfiguration } from '../../public/graphql/types';
|
||||
import { InfraConfigurationAdapter } from './adapters/configuration';
|
||||
import { InfraBackendFrameworkAdapter, InfraFrameworkRequest } from './adapters/framework';
|
||||
import { InfraFieldsDomain } from './domains/fields_domain';
|
||||
import { InfraLogEntriesDomain } from './domains/log_entries_domain';
|
||||
import { InfraMetricsDomain } from './domains/metrics_domain';
|
||||
|
@ -14,6 +12,15 @@ import { InfraLogAnalysis } from './log_analysis/log_analysis';
|
|||
import { InfraSnapshot } from './snapshot';
|
||||
import { InfraSources } from './sources';
|
||||
import { InfraSourceStatus } from './source_status';
|
||||
import { InfraConfig } from '../../../../../plugins/infra/server';
|
||||
import { KibanaFramework } from './adapters/framework/kibana_framework_adapter';
|
||||
|
||||
// NP_TODO: We shouldn't need this context anymore but I am
|
||||
// not sure how the graphql stuff uses it, so we can't remove it yet
|
||||
export interface InfraContext {
|
||||
req: any;
|
||||
rawReq?: any;
|
||||
}
|
||||
|
||||
export interface InfraDomainLibs {
|
||||
fields: InfraFieldsDomain;
|
||||
|
@ -22,8 +29,8 @@ export interface InfraDomainLibs {
|
|||
}
|
||||
|
||||
export interface InfraBackendLibs extends InfraDomainLibs {
|
||||
configuration: InfraConfigurationAdapter;
|
||||
framework: InfraBackendFrameworkAdapter;
|
||||
configuration: InfraConfig;
|
||||
framework: KibanaFramework;
|
||||
logAnalysis: InfraLogAnalysis;
|
||||
snapshot: InfraSnapshot;
|
||||
sources: InfraSources;
|
||||
|
@ -40,7 +47,3 @@ export interface InfraConfiguration {
|
|||
default: InfraSourceConfiguration;
|
||||
};
|
||||
}
|
||||
|
||||
export interface InfraContext {
|
||||
req: InfraFrameworkRequest;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import { map, fold } from 'fp-ts/lib/Either';
|
|||
import { identity } from 'fp-ts/lib/function';
|
||||
import { getJobId } from '../../../common/log_analysis';
|
||||
import { throwErrors, createPlainError } from '../../../common/runtime_types';
|
||||
import { InfraBackendFrameworkAdapter, InfraFrameworkRequest } from '../adapters/framework';
|
||||
import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter';
|
||||
import { NoLogRateResultsIndexError } from './errors';
|
||||
import {
|
||||
logRateModelPlotResponseRT,
|
||||
|
@ -17,37 +17,38 @@ import {
|
|||
LogRateModelPlotBucket,
|
||||
CompositeTimestampPartitionKey,
|
||||
} from './queries';
|
||||
import { RequestHandlerContext, KibanaRequest } from '../../../../../../../src/core/server';
|
||||
|
||||
const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000;
|
||||
|
||||
export class InfraLogAnalysis {
|
||||
constructor(
|
||||
private readonly libs: {
|
||||
framework: InfraBackendFrameworkAdapter;
|
||||
framework: KibanaFramework;
|
||||
}
|
||||
) {}
|
||||
|
||||
public getJobIds(request: InfraFrameworkRequest, sourceId: string) {
|
||||
public getJobIds(request: KibanaRequest, sourceId: string) {
|
||||
return {
|
||||
logEntryRate: getJobId(this.libs.framework.getSpaceId(request), sourceId, 'log-entry-rate'),
|
||||
};
|
||||
}
|
||||
|
||||
public async getLogEntryRateBuckets(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string,
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
bucketDuration: number
|
||||
bucketDuration: number,
|
||||
request: KibanaRequest
|
||||
) {
|
||||
const logRateJobId = this.getJobIds(request, sourceId).logEntryRate;
|
||||
|
||||
let mlModelPlotBuckets: LogRateModelPlotBucket[] = [];
|
||||
let afterLatestBatchKey: CompositeTimestampPartitionKey | undefined;
|
||||
|
||||
while (true) {
|
||||
const mlModelPlotResponse = await this.libs.framework.callWithRequest(
|
||||
request,
|
||||
requestContext,
|
||||
'search',
|
||||
createLogEntryRateQuery(
|
||||
logRateJobId,
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import {
|
||||
InfraSnapshotGroupbyInput,
|
||||
InfraSnapshotMetricInput,
|
||||
|
@ -12,11 +13,8 @@ import {
|
|||
InfraNodeType,
|
||||
InfraSourceConfiguration,
|
||||
} from '../../graphql/types';
|
||||
import {
|
||||
InfraBackendFrameworkAdapter,
|
||||
InfraFrameworkRequest,
|
||||
InfraDatabaseSearchResponse,
|
||||
} from '../adapters/framework';
|
||||
import { InfraDatabaseSearchResponse } from '../adapters/framework';
|
||||
import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter';
|
||||
import { InfraSources } from '../sources';
|
||||
|
||||
import { JsonObject } from '../../../common/typed_json';
|
||||
|
@ -48,20 +46,18 @@ export interface InfraSnapshotRequestOptions {
|
|||
}
|
||||
|
||||
export class InfraSnapshot {
|
||||
constructor(
|
||||
private readonly libs: { sources: InfraSources; framework: InfraBackendFrameworkAdapter }
|
||||
) {}
|
||||
constructor(private readonly libs: { sources: InfraSources; framework: KibanaFramework }) {}
|
||||
|
||||
public async getNodes(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
options: InfraSnapshotRequestOptions
|
||||
): Promise<InfraSnapshotNode[]> {
|
||||
// Both requestGroupedNodes and requestNodeMetrics may send several requests to elasticsearch
|
||||
// in order to page through the results of their respective composite aggregations.
|
||||
// Both chains of requests are supposed to run in parallel, and their results be merged
|
||||
// when they have both been completed.
|
||||
const groupedNodesPromise = requestGroupedNodes(request, options, this.libs.framework);
|
||||
const nodeMetricsPromise = requestNodeMetrics(request, options, this.libs.framework);
|
||||
const groupedNodesPromise = requestGroupedNodes(requestContext, options, this.libs.framework);
|
||||
const nodeMetricsPromise = requestNodeMetrics(requestContext, options, this.libs.framework);
|
||||
|
||||
const groupedNodeBuckets = await groupedNodesPromise;
|
||||
const nodeMetricBuckets = await nodeMetricsPromise;
|
||||
|
@ -79,9 +75,9 @@ const handleAfterKey = createAfterKeyHandler(
|
|||
);
|
||||
|
||||
const requestGroupedNodes = async (
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
options: InfraSnapshotRequestOptions,
|
||||
framework: InfraBackendFrameworkAdapter
|
||||
framework: KibanaFramework
|
||||
): Promise<InfraSnapshotNodeGroupByBucket[]> => {
|
||||
const query = {
|
||||
allowNoIndices: true,
|
||||
|
@ -130,13 +126,13 @@ const requestGroupedNodes = async (
|
|||
return await getAllCompositeData<
|
||||
InfraSnapshotAggregationResponse,
|
||||
InfraSnapshotNodeGroupByBucket
|
||||
>(framework, request, query, bucketSelector, handleAfterKey);
|
||||
>(framework, requestContext, query, bucketSelector, handleAfterKey);
|
||||
};
|
||||
|
||||
const requestNodeMetrics = async (
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
options: InfraSnapshotRequestOptions,
|
||||
framework: InfraBackendFrameworkAdapter
|
||||
framework: KibanaFramework
|
||||
): Promise<InfraSnapshotNodeMetricsBucket[]> => {
|
||||
const index =
|
||||
options.metric.type === 'logRate'
|
||||
|
@ -191,7 +187,7 @@ const requestNodeMetrics = async (
|
|||
return await getAllCompositeData<
|
||||
InfraSnapshotAggregationResponse,
|
||||
InfraSnapshotNodeMetricsBucket
|
||||
>(framework, request, query, bucketSelector, handleAfterKey);
|
||||
>(framework, requestContext, query, bucketSelector, handleAfterKey);
|
||||
};
|
||||
|
||||
// buckets can be InfraSnapshotNodeGroupByBucket[] or InfraSnapshotNodeMetricsBucket[]
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InfraFrameworkRequest } from './adapters/framework';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { InfraSources } from './sources';
|
||||
|
||||
export class InfraSourceStatus {
|
||||
|
@ -14,58 +14,85 @@ export class InfraSourceStatus {
|
|||
) {}
|
||||
|
||||
public async getLogIndexNames(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string
|
||||
): Promise<string[]> {
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const indexNames = await this.adapter.getIndexNames(
|
||||
request,
|
||||
requestContext,
|
||||
sourceConfiguration.configuration.logAlias
|
||||
);
|
||||
return indexNames;
|
||||
}
|
||||
public async getMetricIndexNames(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string
|
||||
): Promise<string[]> {
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const indexNames = await this.adapter.getIndexNames(
|
||||
request,
|
||||
requestContext,
|
||||
sourceConfiguration.configuration.metricAlias
|
||||
);
|
||||
return indexNames;
|
||||
}
|
||||
public async hasLogAlias(request: InfraFrameworkRequest, sourceId: string): Promise<boolean> {
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
public async hasLogAlias(
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string
|
||||
): Promise<boolean> {
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const hasAlias = await this.adapter.hasAlias(
|
||||
request,
|
||||
requestContext,
|
||||
sourceConfiguration.configuration.logAlias
|
||||
);
|
||||
return hasAlias;
|
||||
}
|
||||
public async hasMetricAlias(request: InfraFrameworkRequest, sourceId: string): Promise<boolean> {
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
public async hasMetricAlias(
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string
|
||||
): Promise<boolean> {
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const hasAlias = await this.adapter.hasAlias(
|
||||
request,
|
||||
requestContext,
|
||||
sourceConfiguration.configuration.metricAlias
|
||||
);
|
||||
return hasAlias;
|
||||
}
|
||||
public async hasLogIndices(request: InfraFrameworkRequest, sourceId: string): Promise<boolean> {
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
public async hasLogIndices(
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string
|
||||
): Promise<boolean> {
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const hasIndices = await this.adapter.hasIndices(
|
||||
request,
|
||||
requestContext,
|
||||
sourceConfiguration.configuration.logAlias
|
||||
);
|
||||
return hasIndices;
|
||||
}
|
||||
public async hasMetricIndices(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string
|
||||
): Promise<boolean> {
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(request, sourceId);
|
||||
const sourceConfiguration = await this.libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const hasIndices = await this.adapter.hasIndices(
|
||||
request,
|
||||
requestContext,
|
||||
sourceConfiguration.configuration.metricAlias
|
||||
);
|
||||
return hasIndices;
|
||||
|
@ -73,7 +100,7 @@ export class InfraSourceStatus {
|
|||
}
|
||||
|
||||
export interface InfraSourceStatusAdapter {
|
||||
getIndexNames(request: InfraFrameworkRequest, aliasName: string): Promise<string[]>;
|
||||
hasAlias(request: InfraFrameworkRequest, aliasName: string): Promise<boolean>;
|
||||
hasIndices(request: InfraFrameworkRequest, indexNames: string): Promise<boolean>;
|
||||
getIndexNames(requestContext: RequestHandlerContext, aliasName: string): Promise<string[]>;
|
||||
hasAlias(requestContext: RequestHandlerContext, aliasName: string): Promise<boolean>;
|
||||
hasIndices(requestContext: RequestHandlerContext, indexNames: string): Promise<boolean>;
|
||||
}
|
||||
|
|
|
@ -3,34 +3,31 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InfraInmemoryConfigurationAdapter } from '../adapters/configuration/inmemory_configuration_adapter';
|
||||
import { InfraSources } from './sources';
|
||||
|
||||
describe('the InfraSources lib', () => {
|
||||
describe('getSourceConfiguration method', () => {
|
||||
test('returns a source configuration if it exists', async () => {
|
||||
const sourcesLib = new InfraSources({
|
||||
configuration: createMockStaticConfiguration({}),
|
||||
savedObjects: createMockSavedObjectsService({
|
||||
id: 'TEST_ID',
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
logAlias: 'LOG_ALIAS',
|
||||
fields: {
|
||||
container: 'CONTAINER',
|
||||
host: 'HOST',
|
||||
pod: 'POD',
|
||||
tiebreaker: 'TIEBREAKER',
|
||||
timestamp: 'TIMESTAMP',
|
||||
},
|
||||
},
|
||||
}),
|
||||
config: createMockStaticConfiguration({}),
|
||||
});
|
||||
|
||||
const request: any = Symbol();
|
||||
const request: any = createRequestContext({
|
||||
id: 'TEST_ID',
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
logAlias: 'LOG_ALIAS',
|
||||
fields: {
|
||||
container: 'CONTAINER',
|
||||
host: 'HOST',
|
||||
pod: 'POD',
|
||||
tiebreaker: 'TIEBREAKER',
|
||||
timestamp: 'TIMESTAMP',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({
|
||||
id: 'TEST_ID',
|
||||
|
@ -52,7 +49,7 @@ describe('the InfraSources lib', () => {
|
|||
|
||||
test('adds missing attributes from the static configuration to a source configuration', async () => {
|
||||
const sourcesLib = new InfraSources({
|
||||
configuration: createMockStaticConfiguration({
|
||||
config: createMockStaticConfiguration({
|
||||
default: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
logAlias: 'LOG_ALIAS',
|
||||
|
@ -64,19 +61,18 @@ describe('the InfraSources lib', () => {
|
|||
},
|
||||
},
|
||||
}),
|
||||
savedObjects: createMockSavedObjectsService({
|
||||
id: 'TEST_ID',
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {
|
||||
fields: {
|
||||
container: 'CONTAINER',
|
||||
},
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const request: any = Symbol();
|
||||
const request: any = createRequestContext({
|
||||
id: 'TEST_ID',
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {
|
||||
fields: {
|
||||
container: 'CONTAINER',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({
|
||||
id: 'TEST_ID',
|
||||
|
@ -98,16 +94,15 @@ describe('the InfraSources lib', () => {
|
|||
|
||||
test('adds missing attributes from the default configuration to a source configuration', async () => {
|
||||
const sourcesLib = new InfraSources({
|
||||
configuration: createMockStaticConfiguration({}),
|
||||
savedObjects: createMockSavedObjectsService({
|
||||
id: 'TEST_ID',
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {},
|
||||
}),
|
||||
config: createMockStaticConfiguration({}),
|
||||
});
|
||||
|
||||
const request: any = Symbol();
|
||||
const request: any = createRequestContext({
|
||||
id: 'TEST_ID',
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {},
|
||||
});
|
||||
|
||||
expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({
|
||||
id: 'TEST_ID',
|
||||
|
@ -129,29 +124,30 @@ describe('the InfraSources lib', () => {
|
|||
});
|
||||
});
|
||||
|
||||
const createMockStaticConfiguration = (sources: any) =>
|
||||
new InfraInmemoryConfigurationAdapter({
|
||||
enabled: true,
|
||||
query: {
|
||||
partitionSize: 1,
|
||||
partitionFactor: 1,
|
||||
},
|
||||
sources,
|
||||
});
|
||||
|
||||
const createMockSavedObjectsService = (savedObject?: any) => ({
|
||||
getScopedSavedObjectsClient() {
|
||||
return {
|
||||
async get() {
|
||||
return savedObject;
|
||||
},
|
||||
} as any;
|
||||
},
|
||||
SavedObjectsClient: {
|
||||
errors: {
|
||||
isNotFoundError() {
|
||||
return typeof savedObject === 'undefined';
|
||||
},
|
||||
},
|
||||
const createMockStaticConfiguration = (sources: any) => ({
|
||||
enabled: true,
|
||||
query: {
|
||||
partitionSize: 1,
|
||||
partitionFactor: 1,
|
||||
},
|
||||
sources,
|
||||
});
|
||||
|
||||
const createRequestContext = (savedObject?: any) => {
|
||||
return {
|
||||
core: {
|
||||
savedObjects: {
|
||||
client: {
|
||||
async get() {
|
||||
return savedObject;
|
||||
},
|
||||
errors: {
|
||||
isNotFoundError() {
|
||||
return typeof savedObject === 'undefined';
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -6,14 +6,10 @@
|
|||
|
||||
import * as runtimeTypes from 'io-ts';
|
||||
import { failure } from 'io-ts/lib/PathReporter';
|
||||
import { Legacy } from 'kibana';
|
||||
|
||||
import { identity, constant } from 'fp-ts/lib/function';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { map, fold } from 'fp-ts/lib/Either';
|
||||
import { Pick3 } from '../../../common/utility_types';
|
||||
import { InfraConfigurationAdapter } from '../adapters/configuration';
|
||||
import { InfraFrameworkRequest, internalInfraFrameworkRequest } from '../adapters/framework';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { defaultSourceConfiguration } from './defaults';
|
||||
import { NotFoundError } from './errors';
|
||||
import { infraSourceConfigurationSavedObjectType } from './saved_object_mappings';
|
||||
|
@ -25,19 +21,21 @@ import {
|
|||
SourceConfigurationSavedObjectRuntimeType,
|
||||
StaticSourceConfigurationRuntimeType,
|
||||
} from './types';
|
||||
import { InfraConfig } from '../../../../../../plugins/infra/server';
|
||||
|
||||
interface Libs {
|
||||
config: InfraConfig;
|
||||
}
|
||||
|
||||
export class InfraSources {
|
||||
private internalSourceConfigurations: Map<string, InfraStaticSourceConfiguration> = new Map();
|
||||
private readonly libs: Libs;
|
||||
|
||||
constructor(
|
||||
private readonly libs: {
|
||||
configuration: InfraConfigurationAdapter;
|
||||
savedObjects: Pick<Legacy.SavedObjectsService, 'getScopedSavedObjectsClient'> &
|
||||
Pick3<Legacy.SavedObjectsService, 'SavedObjectsClient', 'errors', 'isNotFoundError'>;
|
||||
}
|
||||
) {}
|
||||
constructor(libs: Libs) {
|
||||
this.libs = libs;
|
||||
}
|
||||
|
||||
public async getSourceConfiguration(request: InfraFrameworkRequest, sourceId: string) {
|
||||
public async getSourceConfiguration(requestContext: RequestHandlerContext, sourceId: string) {
|
||||
const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration();
|
||||
|
||||
const savedSourceConfiguration = await this.getInternalSourceConfiguration(sourceId)
|
||||
|
@ -53,7 +51,7 @@ export class InfraSources {
|
|||
}))
|
||||
.catch(err =>
|
||||
err instanceof NotFoundError
|
||||
? this.getSavedSourceConfiguration(request, sourceId).then(result => ({
|
||||
? this.getSavedSourceConfiguration(requestContext, sourceId).then(result => ({
|
||||
...result,
|
||||
configuration: mergeSourceConfiguration(
|
||||
staticDefaultSourceConfiguration,
|
||||
|
@ -63,7 +61,7 @@ export class InfraSources {
|
|||
: Promise.reject(err)
|
||||
)
|
||||
.catch(err =>
|
||||
this.libs.savedObjects.SavedObjectsClient.errors.isNotFoundError(err)
|
||||
requestContext.core.savedObjects.client.errors.isNotFoundError(err)
|
||||
? Promise.resolve({
|
||||
id: sourceId,
|
||||
version: undefined,
|
||||
|
@ -77,10 +75,10 @@ export class InfraSources {
|
|||
return savedSourceConfiguration;
|
||||
}
|
||||
|
||||
public async getAllSourceConfigurations(request: InfraFrameworkRequest) {
|
||||
public async getAllSourceConfigurations(requestContext: RequestHandlerContext) {
|
||||
const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration();
|
||||
|
||||
const savedSourceConfigurations = await this.getAllSavedSourceConfigurations(request);
|
||||
const savedSourceConfigurations = await this.getAllSavedSourceConfigurations(requestContext);
|
||||
|
||||
return savedSourceConfigurations.map(savedSourceConfiguration => ({
|
||||
...savedSourceConfiguration,
|
||||
|
@ -92,7 +90,7 @@ export class InfraSources {
|
|||
}
|
||||
|
||||
public async createSourceConfiguration(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string,
|
||||
source: InfraSavedSourceConfiguration
|
||||
) {
|
||||
|
@ -104,13 +102,11 @@ export class InfraSources {
|
|||
);
|
||||
|
||||
const createdSourceConfiguration = convertSavedObjectToSavedSourceConfiguration(
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalInfraFrameworkRequest])
|
||||
.create(
|
||||
infraSourceConfigurationSavedObjectType,
|
||||
pickSavedSourceConfiguration(newSourceConfiguration) as any,
|
||||
{ id: sourceId }
|
||||
)
|
||||
await requestContext.core.savedObjects.client.create(
|
||||
infraSourceConfigurationSavedObjectType,
|
||||
pickSavedSourceConfiguration(newSourceConfiguration) as any,
|
||||
{ id: sourceId }
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -122,20 +118,21 @@ export class InfraSources {
|
|||
};
|
||||
}
|
||||
|
||||
public async deleteSourceConfiguration(request: InfraFrameworkRequest, sourceId: string) {
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalInfraFrameworkRequest])
|
||||
.delete(infraSourceConfigurationSavedObjectType, sourceId);
|
||||
public async deleteSourceConfiguration(requestContext: RequestHandlerContext, sourceId: string) {
|
||||
await requestContext.core.savedObjects.client.delete(
|
||||
infraSourceConfigurationSavedObjectType,
|
||||
sourceId
|
||||
);
|
||||
}
|
||||
|
||||
public async updateSourceConfiguration(
|
||||
request: InfraFrameworkRequest,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string,
|
||||
sourceProperties: InfraSavedSourceConfiguration
|
||||
) {
|
||||
const staticDefaultSourceConfiguration = await this.getStaticDefaultSourceConfiguration();
|
||||
|
||||
const { configuration, version } = await this.getSourceConfiguration(request, sourceId);
|
||||
const { configuration, version } = await this.getSourceConfiguration(requestContext, sourceId);
|
||||
|
||||
const updatedSourceConfigurationAttributes = mergeSourceConfiguration(
|
||||
configuration,
|
||||
|
@ -143,16 +140,14 @@ export class InfraSources {
|
|||
);
|
||||
|
||||
const updatedSourceConfiguration = convertSavedObjectToSavedSourceConfiguration(
|
||||
await this.libs.savedObjects
|
||||
.getScopedSavedObjectsClient(request[internalInfraFrameworkRequest])
|
||||
.update(
|
||||
infraSourceConfigurationSavedObjectType,
|
||||
sourceId,
|
||||
pickSavedSourceConfiguration(updatedSourceConfigurationAttributes) as any,
|
||||
{
|
||||
version,
|
||||
}
|
||||
)
|
||||
await requestContext.core.savedObjects.client.update(
|
||||
infraSourceConfigurationSavedObjectType,
|
||||
sourceId,
|
||||
pickSavedSourceConfiguration(updatedSourceConfigurationAttributes) as any,
|
||||
{
|
||||
version,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
|
@ -184,7 +179,6 @@ export class InfraSources {
|
|||
}
|
||||
|
||||
private async getStaticDefaultSourceConfiguration() {
|
||||
const staticConfiguration = await this.libs.configuration.get();
|
||||
const staticSourceConfiguration = pipe(
|
||||
runtimeTypes
|
||||
.type({
|
||||
|
@ -192,7 +186,7 @@ export class InfraSources {
|
|||
default: StaticSourceConfigurationRuntimeType,
|
||||
}),
|
||||
})
|
||||
.decode(staticConfiguration),
|
||||
.decode(this.libs.config),
|
||||
map(({ sources: { default: defaultConfiguration } }) => defaultConfiguration),
|
||||
fold(constant({}), identity)
|
||||
);
|
||||
|
@ -200,12 +194,11 @@ export class InfraSources {
|
|||
return mergeSourceConfiguration(defaultSourceConfiguration, staticSourceConfiguration);
|
||||
}
|
||||
|
||||
private async getSavedSourceConfiguration(request: InfraFrameworkRequest, sourceId: string) {
|
||||
const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient(
|
||||
request[internalInfraFrameworkRequest]
|
||||
);
|
||||
|
||||
const savedObject = await savedObjectsClient.get(
|
||||
private async getSavedSourceConfiguration(
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceId: string
|
||||
) {
|
||||
const savedObject = await requestContext.core.savedObjects.client.get(
|
||||
infraSourceConfigurationSavedObjectType,
|
||||
sourceId
|
||||
);
|
||||
|
@ -213,12 +206,8 @@ export class InfraSources {
|
|||
return convertSavedObjectToSavedSourceConfiguration(savedObject);
|
||||
}
|
||||
|
||||
private async getAllSavedSourceConfigurations(request: InfraFrameworkRequest) {
|
||||
const savedObjectsClient = this.libs.savedObjects.getScopedSavedObjectsClient(
|
||||
request[internalInfraFrameworkRequest]
|
||||
);
|
||||
|
||||
const savedObjects = await savedObjectsClient.find({
|
||||
private async getAllSavedSourceConfigurations(requestContext: RequestHandlerContext) {
|
||||
const savedObjects = await requestContext.core.savedObjects.client.find({
|
||||
type: infraSourceConfigurationSavedObjectType,
|
||||
});
|
||||
|
||||
|
|
16
x-pack/legacy/plugins/infra/server/new_platform_index.ts
Normal file
16
x-pack/legacy/plugins/infra/server/new_platform_index.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
import { InfraServerPlugin } from './new_platform_plugin';
|
||||
import { config, InfraConfig } from '../../../../plugins/infra/server';
|
||||
import { InfraServerPluginDeps } from './lib/adapters/framework';
|
||||
|
||||
export { config, InfraConfig, InfraServerPluginDeps };
|
||||
|
||||
export function plugin(context: PluginInitializerContext) {
|
||||
return new InfraServerPlugin(context);
|
||||
}
|
107
x-pack/legacy/plugins/infra/server/new_platform_plugin.ts
Normal file
107
x-pack/legacy/plugins/infra/server/new_platform_plugin.ts
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { CoreSetup, PluginInitializerContext } from 'src/core/server';
|
||||
import { Server } from 'hapi';
|
||||
import { InfraConfig } from '../../../../plugins/infra/server';
|
||||
import { initInfraServer } from './infra_server';
|
||||
import { InfraBackendLibs, InfraDomainLibs } from './lib/infra_types';
|
||||
import { FrameworkFieldsAdapter } from './lib/adapters/fields/framework_fields_adapter';
|
||||
import { KibanaFramework } from './lib/adapters/framework/kibana_framework_adapter';
|
||||
import { InfraKibanaLogEntriesAdapter } from './lib/adapters/log_entries/kibana_log_entries_adapter';
|
||||
import { KibanaMetricsAdapter } from './lib/adapters/metrics/kibana_metrics_adapter';
|
||||
import { InfraElasticsearchSourceStatusAdapter } from './lib/adapters/source_status';
|
||||
import { InfraFieldsDomain } from './lib/domains/fields_domain';
|
||||
import { InfraLogEntriesDomain } from './lib/domains/log_entries_domain';
|
||||
import { InfraMetricsDomain } from './lib/domains/metrics_domain';
|
||||
import { InfraLogAnalysis } from './lib/log_analysis';
|
||||
import { InfraSnapshot } from './lib/snapshot';
|
||||
import { InfraSourceStatus } from './lib/source_status';
|
||||
import { InfraSources } from './lib/sources';
|
||||
import { InfraServerPluginDeps } from './lib/adapters/framework';
|
||||
import { METRICS_FEATURE, LOGS_FEATURE } from './features';
|
||||
import { UsageCollector } from './usage/usage_collector';
|
||||
|
||||
export interface KbnServer extends Server {
|
||||
usage: any;
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG: InfraConfig = {
|
||||
enabled: true,
|
||||
query: {
|
||||
partitionSize: 75,
|
||||
partitionFactor: 1.2,
|
||||
},
|
||||
};
|
||||
|
||||
export class InfraServerPlugin {
|
||||
public config: InfraConfig = DEFAULT_CONFIG;
|
||||
public libs: InfraBackendLibs | undefined;
|
||||
|
||||
constructor(context: PluginInitializerContext) {
|
||||
const config$ = context.config.create<InfraConfig>();
|
||||
config$.subscribe(configValue => {
|
||||
this.config = {
|
||||
...DEFAULT_CONFIG,
|
||||
enabled: configValue.enabled,
|
||||
query: {
|
||||
...DEFAULT_CONFIG.query,
|
||||
...configValue.query,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
getLibs() {
|
||||
if (!this.libs) {
|
||||
throw new Error('libs not set up yet');
|
||||
}
|
||||
return this.libs;
|
||||
}
|
||||
|
||||
setup(core: CoreSetup, plugins: InfraServerPluginDeps) {
|
||||
const framework = new KibanaFramework(core, this.config, plugins);
|
||||
const sources = new InfraSources({
|
||||
config: this.config,
|
||||
});
|
||||
const sourceStatus = new InfraSourceStatus(
|
||||
new InfraElasticsearchSourceStatusAdapter(framework),
|
||||
{
|
||||
sources,
|
||||
}
|
||||
);
|
||||
const snapshot = new InfraSnapshot({ sources, framework });
|
||||
const logAnalysis = new InfraLogAnalysis({ framework });
|
||||
|
||||
// TODO: separate these out individually and do away with "domains" as a temporary group
|
||||
const domainLibs: InfraDomainLibs = {
|
||||
fields: new InfraFieldsDomain(new FrameworkFieldsAdapter(framework), {
|
||||
sources,
|
||||
}),
|
||||
logEntries: new InfraLogEntriesDomain(new InfraKibanaLogEntriesAdapter(framework), {
|
||||
sources,
|
||||
}),
|
||||
metrics: new InfraMetricsDomain(new KibanaMetricsAdapter(framework)),
|
||||
};
|
||||
|
||||
this.libs = {
|
||||
configuration: this.config,
|
||||
framework,
|
||||
logAnalysis,
|
||||
snapshot,
|
||||
sources,
|
||||
sourceStatus,
|
||||
...domainLibs,
|
||||
};
|
||||
|
||||
plugins.features.registerFeature(METRICS_FEATURE);
|
||||
plugins.features.registerFeature(LOGS_FEATURE);
|
||||
|
||||
initInfraServer(this.libs);
|
||||
|
||||
// Telemetry
|
||||
UsageCollector.registerUsageCollector(plugins.usageCollection);
|
||||
}
|
||||
}
|
|
@ -3,18 +3,9 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Joi from 'joi';
|
||||
import { boomify, notFound } from 'boom';
|
||||
import { first } from 'lodash';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { InfraBackendLibs } from '../lib/infra_types';
|
||||
import { InfraWrappableRequest } from '../lib/adapters/framework';
|
||||
|
||||
interface IpToHostRequest {
|
||||
ip: string;
|
||||
index_pattern: string;
|
||||
}
|
||||
|
||||
type IpToHostWrappedRequest = InfraWrappableRequest<IpToHostRequest>;
|
||||
|
||||
export interface IpToHostResponse {
|
||||
host: string;
|
||||
|
@ -28,40 +19,47 @@ interface HostDoc {
|
|||
};
|
||||
}
|
||||
|
||||
const ipToHostSchema = Joi.object({
|
||||
ip: Joi.string().required(),
|
||||
index_pattern: Joi.string().required(),
|
||||
const ipToHostSchema = schema.object({
|
||||
ip: schema.string(),
|
||||
index_pattern: schema.string(),
|
||||
});
|
||||
|
||||
export const initIpToHostName = ({ framework }: InfraBackendLibs) => {
|
||||
const { callWithRequest } = framework;
|
||||
framework.registerRoute<IpToHostWrappedRequest, Promise<IpToHostResponse>>({
|
||||
method: 'POST',
|
||||
path: '/api/infra/ip_to_host',
|
||||
options: {
|
||||
validate: { payload: ipToHostSchema },
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'post',
|
||||
path: '/api/infra/ip_to_host',
|
||||
validate: {
|
||||
body: ipToHostSchema,
|
||||
},
|
||||
},
|
||||
handler: async req => {
|
||||
async (requestContext, { body }, response) => {
|
||||
try {
|
||||
const params = {
|
||||
index: req.payload.index_pattern,
|
||||
index: body.index_pattern,
|
||||
body: {
|
||||
size: 1,
|
||||
query: {
|
||||
match: { 'host.ip': req.payload.ip },
|
||||
match: { 'host.ip': body.ip },
|
||||
},
|
||||
_source: ['host.name'],
|
||||
},
|
||||
};
|
||||
const response = await callWithRequest<HostDoc>(req, 'search', params);
|
||||
if (response.hits.total.value === 0) {
|
||||
throw notFound('Host with matching IP address not found.');
|
||||
const { hits } = await callWithRequest<HostDoc>(requestContext, 'search', params);
|
||||
if (hits.total.value === 0) {
|
||||
return response.notFound({
|
||||
body: { message: 'Host with matching IP address not found.' },
|
||||
});
|
||||
}
|
||||
const hostDoc = first(response.hits.hits);
|
||||
return { host: hostDoc._source.host.name };
|
||||
} catch (e) {
|
||||
throw boomify(e);
|
||||
const hostDoc = first(hits.hits);
|
||||
return response.ok({ body: { host: hostDoc._source.host.name } });
|
||||
} catch ({ statusCode = 500, message = 'Unknown error occurred' }) {
|
||||
return response.customError({
|
||||
statusCode,
|
||||
body: { message },
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -8,7 +8,7 @@ import Boom from 'boom';
|
|||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { InfraBackendLibs } from '../../../lib/infra_types';
|
||||
import {
|
||||
LOG_ANALYSIS_VALIDATION_INDICES_PATH,
|
||||
|
@ -20,64 +20,75 @@ import {
|
|||
import { throwErrors } from '../../../../common/runtime_types';
|
||||
|
||||
const partitionField = 'event.dataset';
|
||||
const escapeHatch = schema.object({}, { allowUnknowns: true });
|
||||
|
||||
export const initIndexPatternsValidateRoute = ({ framework }: InfraBackendLibs) => {
|
||||
framework.registerRoute({
|
||||
method: 'POST',
|
||||
path: LOG_ANALYSIS_VALIDATION_INDICES_PATH,
|
||||
handler: async (req, res) => {
|
||||
const payload = pipe(
|
||||
validationIndicesRequestPayloadRT.decode(req.payload),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'post',
|
||||
path: LOG_ANALYSIS_VALIDATION_INDICES_PATH,
|
||||
validate: { body: escapeHatch },
|
||||
},
|
||||
async (requestContext, request, response) => {
|
||||
try {
|
||||
const payload = pipe(
|
||||
validationIndicesRequestPayloadRT.decode(request.body),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
|
||||
const { timestampField, indices } = payload.data;
|
||||
const errors: ValidationIndicesError[] = [];
|
||||
const { timestampField, indices } = payload.data;
|
||||
const errors: ValidationIndicesError[] = [];
|
||||
|
||||
// Query each pattern individually, to map correctly the errors
|
||||
await Promise.all(
|
||||
indices.map(async index => {
|
||||
const fieldCaps = await framework.callWithRequest(req, 'fieldCaps', {
|
||||
index,
|
||||
fields: `${timestampField},${partitionField}`,
|
||||
});
|
||||
|
||||
if (fieldCaps.indices.length === 0) {
|
||||
errors.push({
|
||||
error: 'INDEX_NOT_FOUND',
|
||||
// Query each pattern individually, to map correctly the errors
|
||||
await Promise.all(
|
||||
indices.map(async index => {
|
||||
const fieldCaps = await framework.callWithRequest(requestContext, 'fieldCaps', {
|
||||
index,
|
||||
fields: `${timestampField},${partitionField}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
([
|
||||
[timestampField, 'date'],
|
||||
[partitionField, 'keyword'],
|
||||
] as const).forEach(([field, fieldType]) => {
|
||||
const fieldMetadata = fieldCaps.fields[field];
|
||||
|
||||
if (fieldMetadata === undefined) {
|
||||
if (fieldCaps.indices.length === 0) {
|
||||
errors.push({
|
||||
error: 'FIELD_NOT_FOUND',
|
||||
error: 'INDEX_NOT_FOUND',
|
||||
index,
|
||||
field,
|
||||
});
|
||||
} else {
|
||||
const fieldTypes = Object.keys(fieldMetadata);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldTypes.length > 1 || fieldTypes[0] !== fieldType) {
|
||||
([
|
||||
[timestampField, 'date'],
|
||||
[partitionField, 'keyword'],
|
||||
] as const).forEach(([field, fieldType]) => {
|
||||
const fieldMetadata = fieldCaps.fields[field];
|
||||
|
||||
if (fieldMetadata === undefined) {
|
||||
errors.push({
|
||||
error: `FIELD_NOT_VALID`,
|
||||
error: 'FIELD_NOT_FOUND',
|
||||
index,
|
||||
field,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
} else {
|
||||
const fieldTypes = Object.keys(fieldMetadata);
|
||||
|
||||
return res.response(validationIndicesResponsePayloadRT.encode({ data: { errors } }));
|
||||
},
|
||||
});
|
||||
if (fieldTypes.length > 1 || fieldTypes[0] !== fieldType) {
|
||||
errors.push({
|
||||
error: `FIELD_NOT_VALID`,
|
||||
index,
|
||||
field,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
return response.ok({
|
||||
body: validationIndicesResponsePayloadRT.encode({ data: { errors } }),
|
||||
});
|
||||
} catch (error) {
|
||||
return response.internalError({
|
||||
body: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ import Boom from 'boom';
|
|||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { InfraBackendLibs } from '../../../lib/infra_types';
|
||||
import {
|
||||
LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH,
|
||||
|
@ -19,46 +20,58 @@ import {
|
|||
import { throwErrors } from '../../../../common/runtime_types';
|
||||
import { NoLogRateResultsIndexError } from '../../../lib/log_analysis';
|
||||
|
||||
const anyObject = schema.object({}, { allowUnknowns: true });
|
||||
|
||||
export const initLogAnalysisGetLogEntryRateRoute = ({
|
||||
framework,
|
||||
logAnalysis,
|
||||
}: InfraBackendLibs) => {
|
||||
framework.registerRoute({
|
||||
method: 'POST',
|
||||
path: LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH,
|
||||
handler: async (req, res) => {
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'post',
|
||||
path: LOG_ANALYSIS_GET_LOG_ENTRY_RATE_PATH,
|
||||
validate: {
|
||||
// short-circuit forced @kbn/config-schema validation so we can do io-ts validation
|
||||
body: anyObject,
|
||||
},
|
||||
},
|
||||
async (requestContext, request, response) => {
|
||||
const payload = pipe(
|
||||
getLogEntryRateRequestPayloadRT.decode(req.payload),
|
||||
getLogEntryRateRequestPayloadRT.decode(request.body),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
|
||||
const logEntryRateBuckets = await logAnalysis
|
||||
.getLogEntryRateBuckets(
|
||||
req,
|
||||
try {
|
||||
const logEntryRateBuckets = await logAnalysis.getLogEntryRateBuckets(
|
||||
requestContext,
|
||||
payload.data.sourceId,
|
||||
payload.data.timeRange.startTime,
|
||||
payload.data.timeRange.endTime,
|
||||
payload.data.bucketDuration
|
||||
)
|
||||
.catch(err => {
|
||||
if (err instanceof NoLogRateResultsIndexError) {
|
||||
throw Boom.boomify(err, { statusCode: 404 });
|
||||
}
|
||||
payload.data.bucketDuration,
|
||||
request
|
||||
);
|
||||
|
||||
throw Boom.boomify(err, { statusCode: ('statusCode' in err && err.statusCode) || 500 });
|
||||
return response.ok({
|
||||
body: getLogEntryRateSuccessReponsePayloadRT.encode({
|
||||
data: {
|
||||
bucketDuration: payload.data.bucketDuration,
|
||||
histogramBuckets: logEntryRateBuckets,
|
||||
totalNumberOfLogEntries: getTotalNumberOfLogEntries(logEntryRateBuckets),
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
return res.response(
|
||||
getLogEntryRateSuccessReponsePayloadRT.encode({
|
||||
data: {
|
||||
bucketDuration: payload.data.bucketDuration,
|
||||
histogramBuckets: logEntryRateBuckets,
|
||||
totalNumberOfLogEntries: getTotalNumberOfLogEntries(logEntryRateBuckets),
|
||||
},
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
const { statusCode = 500, message = 'Unknown error occurred' } = e;
|
||||
if (e instanceof NoLogRateResultsIndexError) {
|
||||
return response.notFound({ body: { message } });
|
||||
}
|
||||
return response.customError({
|
||||
statusCode,
|
||||
body: { message },
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const getTotalNumberOfLogEntries = (
|
||||
|
|
|
@ -4,14 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Boom, { boomify } from 'boom';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import Boom from 'boom';
|
||||
import { get } from 'lodash';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
import {
|
||||
InfraMetadata,
|
||||
InfraMetadataWrappedRequest,
|
||||
InfraMetadataFeature,
|
||||
InfraMetadataRequestRT,
|
||||
InfraMetadataRT,
|
||||
|
@ -24,23 +23,33 @@ import { getCloudMetricsMetadata } from './lib/get_cloud_metric_metadata';
|
|||
import { getNodeInfo } from './lib/get_node_info';
|
||||
import { throwErrors } from '../../../common/runtime_types';
|
||||
|
||||
const escapeHatch = schema.object({}, { allowUnknowns: true });
|
||||
|
||||
export const initMetadataRoute = (libs: InfraBackendLibs) => {
|
||||
const { framework } = libs;
|
||||
|
||||
framework.registerRoute<InfraMetadataWrappedRequest, Promise<InfraMetadata>>({
|
||||
method: 'POST',
|
||||
path: '/api/infra/metadata',
|
||||
handler: async req => {
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'post',
|
||||
path: '/api/infra/metadata',
|
||||
validate: {
|
||||
body: escapeHatch,
|
||||
},
|
||||
},
|
||||
async (requestContext, request, response) => {
|
||||
try {
|
||||
const { nodeId, nodeType, sourceId } = pipe(
|
||||
InfraMetadataRequestRT.decode(req.payload),
|
||||
InfraMetadataRequestRT.decode(request.body),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
|
||||
const { configuration } = await libs.sources.getSourceConfiguration(req, sourceId);
|
||||
const { configuration } = await libs.sources.getSourceConfiguration(
|
||||
requestContext,
|
||||
sourceId
|
||||
);
|
||||
const metricsMetadata = await getMetricMetadata(
|
||||
framework,
|
||||
req,
|
||||
requestContext,
|
||||
configuration,
|
||||
nodeId,
|
||||
nodeType
|
||||
|
@ -49,35 +58,35 @@ export const initMetadataRoute = (libs: InfraBackendLibs) => {
|
|||
nameToFeature('metrics')
|
||||
);
|
||||
|
||||
const info = await getNodeInfo(framework, req, configuration, nodeId, nodeType);
|
||||
const info = await getNodeInfo(framework, requestContext, configuration, nodeId, nodeType);
|
||||
const cloudInstanceId = get<string>(info, 'cloud.instance.id');
|
||||
|
||||
const cloudMetricsMetadata = cloudInstanceId
|
||||
? await getCloudMetricsMetadata(framework, req, configuration, cloudInstanceId)
|
||||
? await getCloudMetricsMetadata(framework, requestContext, configuration, cloudInstanceId)
|
||||
: { buckets: [] };
|
||||
const cloudMetricsFeatures = pickFeatureName(cloudMetricsMetadata.buckets).map(
|
||||
nameToFeature('metrics')
|
||||
);
|
||||
|
||||
const hasAPM = await hasAPMData(framework, req, configuration, nodeId, nodeType);
|
||||
const hasAPM = await hasAPMData(framework, requestContext, configuration, nodeId, nodeType);
|
||||
const apmMetricFeatures = hasAPM ? [{ name: 'apm.transaction', source: 'apm' }] : [];
|
||||
|
||||
const id = metricsMetadata.id;
|
||||
const name = metricsMetadata.name || id;
|
||||
return pipe(
|
||||
InfraMetadataRT.decode({
|
||||
return response.ok({
|
||||
body: InfraMetadataRT.encode({
|
||||
id,
|
||||
name,
|
||||
features: [...metricFeatures, ...cloudMetricsFeatures, ...apmMetricFeatures],
|
||||
info,
|
||||
}),
|
||||
fold(throwErrors(Boom.badImplementation), identity)
|
||||
);
|
||||
});
|
||||
} catch (error) {
|
||||
throw boomify(error);
|
||||
return response.internalError({
|
||||
body: error.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const nameToFeature = (source: string) => (name: string): InfraMetadataFeature => ({
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import {
|
||||
InfraBackendFrameworkAdapter,
|
||||
InfraFrameworkRequest,
|
||||
InfraMetadataAggregationBucket,
|
||||
InfraMetadataAggregationResponse,
|
||||
} from '../../../lib/adapters/framework';
|
||||
import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter';
|
||||
import { InfraSourceConfiguration } from '../../../lib/sources';
|
||||
import { CLOUD_METRICS_MODULES } from '../../../lib/constants';
|
||||
|
||||
|
@ -18,8 +18,8 @@ export interface InfraCloudMetricsAdapterResponse {
|
|||
}
|
||||
|
||||
export const getCloudMetricsMetadata = async (
|
||||
framework: InfraBackendFrameworkAdapter,
|
||||
req: InfraFrameworkRequest,
|
||||
framework: KibanaFramework,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
instanceId: string
|
||||
): Promise<InfraCloudMetricsAdapterResponse> => {
|
||||
|
@ -51,7 +51,7 @@ export const getCloudMetricsMetadata = async (
|
|||
{
|
||||
metrics?: InfraMetadataAggregationResponse;
|
||||
}
|
||||
>(req, 'search', metricQuery);
|
||||
>(requestContext, 'search', metricQuery);
|
||||
|
||||
const buckets =
|
||||
response.aggregations && response.aggregations.metrics
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
*/
|
||||
|
||||
import { get } from 'lodash';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import {
|
||||
InfraFrameworkRequest,
|
||||
InfraMetadataAggregationBucket,
|
||||
InfraBackendFrameworkAdapter,
|
||||
InfraMetadataAggregationResponse,
|
||||
} from '../../../lib/adapters/framework';
|
||||
import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter';
|
||||
import { InfraSourceConfiguration } from '../../../lib/sources';
|
||||
import { getIdFieldName } from './get_id_field_name';
|
||||
import { NAME_FIELDS } from '../../../lib/constants';
|
||||
|
@ -22,8 +22,8 @@ export interface InfraMetricsAdapterResponse {
|
|||
}
|
||||
|
||||
export const getMetricMetadata = async (
|
||||
framework: InfraBackendFrameworkAdapter,
|
||||
req: InfraFrameworkRequest,
|
||||
framework: KibanaFramework,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
nodeId: string,
|
||||
nodeType: 'host' | 'pod' | 'container'
|
||||
|
@ -69,7 +69,7 @@ export const getMetricMetadata = async (
|
|||
metrics?: InfraMetadataAggregationResponse;
|
||||
nodeName?: InfraMetadataAggregationResponse;
|
||||
}
|
||||
>(req, 'search', metricQuery);
|
||||
>(requestContext, 'search', metricQuery);
|
||||
|
||||
const buckets =
|
||||
response.aggregations && response.aggregations.metrics
|
||||
|
|
|
@ -5,10 +5,8 @@
|
|||
*/
|
||||
|
||||
import { first } from 'lodash';
|
||||
import {
|
||||
InfraFrameworkRequest,
|
||||
InfraBackendFrameworkAdapter,
|
||||
} from '../../../lib/adapters/framework';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter';
|
||||
import { InfraSourceConfiguration } from '../../../lib/sources';
|
||||
import { InfraNodeType } from '../../../graphql/types';
|
||||
import { InfraMetadataInfo } from '../../../../common/http_api/metadata_api';
|
||||
|
@ -17,8 +15,8 @@ import { CLOUD_METRICS_MODULES } from '../../../lib/constants';
|
|||
import { getIdFieldName } from './get_id_field_name';
|
||||
|
||||
export const getNodeInfo = async (
|
||||
framework: InfraBackendFrameworkAdapter,
|
||||
req: InfraFrameworkRequest,
|
||||
framework: KibanaFramework,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
nodeId: string,
|
||||
nodeType: 'host' | 'pod' | 'container'
|
||||
|
@ -31,7 +29,7 @@ export const getNodeInfo = async (
|
|||
if (nodeType === InfraNodeType.pod) {
|
||||
const kubernetesNodeName = await getPodNodeName(
|
||||
framework,
|
||||
req,
|
||||
requestContext,
|
||||
sourceConfiguration,
|
||||
nodeId,
|
||||
nodeType
|
||||
|
@ -39,7 +37,7 @@ export const getNodeInfo = async (
|
|||
if (kubernetesNodeName) {
|
||||
return getNodeInfo(
|
||||
framework,
|
||||
req,
|
||||
requestContext,
|
||||
sourceConfiguration,
|
||||
kubernetesNodeName,
|
||||
InfraNodeType.host
|
||||
|
@ -64,7 +62,7 @@ export const getNodeInfo = async (
|
|||
},
|
||||
};
|
||||
const response = await framework.callWithRequest<{ _source: InfraMetadataInfo }, {}>(
|
||||
req,
|
||||
requestContext,
|
||||
'search',
|
||||
params
|
||||
);
|
||||
|
|
|
@ -5,16 +5,14 @@
|
|||
*/
|
||||
|
||||
import { first, get } from 'lodash';
|
||||
import {
|
||||
InfraFrameworkRequest,
|
||||
InfraBackendFrameworkAdapter,
|
||||
} from '../../../lib/adapters/framework';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter';
|
||||
import { InfraSourceConfiguration } from '../../../lib/sources';
|
||||
import { getIdFieldName } from './get_id_field_name';
|
||||
|
||||
export const getPodNodeName = async (
|
||||
framework: InfraBackendFrameworkAdapter,
|
||||
req: InfraFrameworkRequest,
|
||||
framework: KibanaFramework,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
nodeId: string,
|
||||
nodeType: 'host' | 'pod' | 'container'
|
||||
|
@ -40,7 +38,7 @@ export const getPodNodeName = async (
|
|||
const response = await framework.callWithRequest<
|
||||
{ _source: { kubernetes: { node: { name: string } } } },
|
||||
{}
|
||||
>(req, 'search', params);
|
||||
>(requestContext, 'search', params);
|
||||
const firstHit = first(response.hits.hits);
|
||||
if (firstHit) {
|
||||
return get(firstHit, '_source.kubernetes.node.name');
|
||||
|
|
|
@ -4,22 +4,24 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
InfraFrameworkRequest,
|
||||
InfraBackendFrameworkAdapter,
|
||||
} from '../../../lib/adapters/framework';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
|
||||
import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter';
|
||||
import { InfraSourceConfiguration } from '../../../lib/sources';
|
||||
import { getIdFieldName } from './get_id_field_name';
|
||||
|
||||
export const hasAPMData = async (
|
||||
framework: InfraBackendFrameworkAdapter,
|
||||
req: InfraFrameworkRequest,
|
||||
framework: KibanaFramework,
|
||||
requestContext: RequestHandlerContext,
|
||||
sourceConfiguration: InfraSourceConfiguration,
|
||||
nodeId: string,
|
||||
nodeType: 'host' | 'pod' | 'container'
|
||||
) => {
|
||||
const config = framework.config(req);
|
||||
const apmIndex = config.get('apm_oss.transactionIndices') || 'apm-*';
|
||||
const apmIndices = await framework.plugins.apm.getApmIndices(
|
||||
requestContext.core.savedObjects.client
|
||||
);
|
||||
const apmIndex = apmIndices['apm_oss.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 =
|
||||
|
@ -48,6 +50,6 @@ export const hasAPMData = async (
|
|||
},
|
||||
},
|
||||
};
|
||||
const response = await framework.callWithRequest<{}, {}>(req, 'search', params);
|
||||
const response = await framework.callWithRequest<{}, {}>(requestContext, 'search', params);
|
||||
return response.hits.total.value !== 0;
|
||||
};
|
||||
|
|
|
@ -4,42 +4,50 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { boomify } from 'boom';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { InfraBackendLibs } from '../../lib/infra_types';
|
||||
import { getGroupings } from './lib/get_groupings';
|
||||
import { populateSeriesWithTSVBData } from './lib/populate_series_with_tsvb_data';
|
||||
import { metricsExplorerSchema } from './schema';
|
||||
import { MetricsExplorerResponse, MetricsExplorerWrappedRequest } from './types';
|
||||
import { MetricsExplorerRequestBody } from './types';
|
||||
// import { metricsExplorerSchema } from './schema';
|
||||
// import { MetricsExplorerResponse, MetricsExplorerRequestBody } from './types';
|
||||
|
||||
// NP_TODO: need to replace all of this with real types or io-ts or something?
|
||||
const escapeHatch = schema.object({}, { allowUnknowns: true });
|
||||
|
||||
export const initMetricExplorerRoute = (libs: InfraBackendLibs) => {
|
||||
const { framework } = libs;
|
||||
const { callWithRequest } = framework;
|
||||
|
||||
framework.registerRoute<MetricsExplorerWrappedRequest, Promise<MetricsExplorerResponse>>({
|
||||
method: 'POST',
|
||||
path: '/api/infra/metrics_explorer',
|
||||
options: {
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'post',
|
||||
path: '/api/infra/metrics_explorer',
|
||||
validate: {
|
||||
payload: metricsExplorerSchema,
|
||||
body: escapeHatch,
|
||||
},
|
||||
},
|
||||
handler: async req => {
|
||||
async (requestContext, request, response) => {
|
||||
try {
|
||||
const search = <Aggregation>(searchOptions: object) =>
|
||||
callWithRequest<{}, Aggregation>(req, 'search', searchOptions);
|
||||
const options = req.payload;
|
||||
callWithRequest<{}, Aggregation>(requestContext, 'search', searchOptions);
|
||||
const options = request.body as MetricsExplorerRequestBody; // Need to remove this casting and swap in config-schema demands :(
|
||||
// First we get the groupings from a composite aggregation
|
||||
const response = await getGroupings(search, options);
|
||||
const groupings = await getGroupings(search, options);
|
||||
|
||||
// Then we take the results and fill in the data from TSVB with the
|
||||
// user's custom metrics
|
||||
const seriesWithMetrics = await Promise.all(
|
||||
response.series.map(populateSeriesWithTSVBData(req, options, framework))
|
||||
groupings.series.map(
|
||||
populateSeriesWithTSVBData(request, options, framework, requestContext)
|
||||
)
|
||||
);
|
||||
return { ...response, series: seriesWithMetrics };
|
||||
return response.ok({ body: { ...groupings, series: seriesWithMetrics } });
|
||||
} catch (error) {
|
||||
throw boomify(error);
|
||||
return response.internalError({
|
||||
body: error.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
*/
|
||||
|
||||
import { InfraMetricModelMetricType } from '../../../lib/adapters/metrics';
|
||||
import { MetricsExplorerAggregation, MetricsExplorerRequest } from '../types';
|
||||
import { MetricsExplorerAggregation, MetricsExplorerRequestBody } from '../types';
|
||||
import { InfraMetric } from '../../../graphql/types';
|
||||
import { TSVBMetricModel } from '../../../../common/inventory_models/types';
|
||||
export const createMetricModel = (options: MetricsExplorerRequest): TSVBMetricModel => {
|
||||
export const createMetricModel = (options: MetricsExplorerRequestBody): TSVBMetricModel => {
|
||||
return {
|
||||
id: InfraMetric.custom,
|
||||
requires: [],
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { isObject, set } from 'lodash';
|
||||
import { InfraDatabaseSearchResponse } from '../../../lib/adapters/framework';
|
||||
import { MetricsExplorerRequest, MetricsExplorerResponse } from '../types';
|
||||
import { MetricsExplorerRequestBody, MetricsExplorerResponse } from '../types';
|
||||
|
||||
interface GroupingAggregation {
|
||||
groupingsCount: {
|
||||
|
@ -27,7 +27,7 @@ const EMPTY_RESPONSE = {
|
|||
|
||||
export const getGroupings = async (
|
||||
search: <Aggregation>(options: object) => Promise<InfraDatabaseSearchResponse<{}, Aggregation>>,
|
||||
options: MetricsExplorerRequest
|
||||
options: MetricsExplorerRequestBody
|
||||
): Promise<MetricsExplorerResponse> => {
|
||||
if (!options.groupBy) {
|
||||
return EMPTY_RESPONSE;
|
||||
|
|
|
@ -5,25 +5,23 @@
|
|||
*/
|
||||
|
||||
import { union } from 'lodash';
|
||||
import {
|
||||
InfraBackendFrameworkAdapter,
|
||||
InfraFrameworkRequest,
|
||||
} from '../../../lib/adapters/framework';
|
||||
import { KibanaRequest, RequestHandlerContext } from 'src/core/server';
|
||||
import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter';
|
||||
import {
|
||||
MetricsExplorerColumnType,
|
||||
MetricsExplorerRequest,
|
||||
MetricsExplorerRow,
|
||||
MetricsExplorerSeries,
|
||||
MetricsExplorerWrappedRequest,
|
||||
MetricsExplorerRequestBody,
|
||||
} from '../types';
|
||||
import { createMetricModel } from './create_metrics_model';
|
||||
import { JsonObject } from '../../../../common/typed_json';
|
||||
import { calculateMetricInterval } from '../../../utils/calculate_metric_interval';
|
||||
|
||||
export const populateSeriesWithTSVBData = (
|
||||
req: InfraFrameworkRequest<MetricsExplorerWrappedRequest>,
|
||||
options: MetricsExplorerRequest,
|
||||
framework: InfraBackendFrameworkAdapter
|
||||
request: KibanaRequest,
|
||||
options: MetricsExplorerRequestBody,
|
||||
framework: KibanaFramework,
|
||||
requestContext: RequestHandlerContext
|
||||
) => async (series: MetricsExplorerSeries) => {
|
||||
// IF there are no metrics selected then we should return an empty result.
|
||||
if (options.metrics.length === 0) {
|
||||
|
@ -57,7 +55,7 @@ export const populateSeriesWithTSVBData = (
|
|||
const model = createMetricModel(options);
|
||||
const calculatedInterval = await calculateMetricInterval(
|
||||
framework,
|
||||
req,
|
||||
requestContext,
|
||||
{
|
||||
indexPattern: options.indexPattern,
|
||||
timestampField: options.timerange.field,
|
||||
|
@ -78,7 +76,13 @@ export const populateSeriesWithTSVBData = (
|
|||
}
|
||||
|
||||
// Get TSVB results using the model, timerange and filters
|
||||
const tsvbResults = await framework.makeTSVBRequest(req, model, timerange, filters);
|
||||
const tsvbResults = await framework.makeTSVBRequest(
|
||||
request,
|
||||
model,
|
||||
timerange,
|
||||
filters,
|
||||
requestContext
|
||||
);
|
||||
|
||||
// If there is no data `custom` will not exist.
|
||||
if (!tsvbResults.custom) {
|
||||
|
|
|
@ -4,8 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InfraWrappableRequest } from '../../lib/adapters/framework';
|
||||
|
||||
export interface InfraTimerange {
|
||||
field: string;
|
||||
from: number;
|
||||
|
@ -27,7 +25,7 @@ export interface MetricsExplorerMetric {
|
|||
field?: string | undefined;
|
||||
}
|
||||
|
||||
export interface MetricsExplorerRequest {
|
||||
export interface MetricsExplorerRequestBody {
|
||||
timerange: InfraTimerange;
|
||||
indexPattern: string;
|
||||
metrics: MetricsExplorerMetric[];
|
||||
|
@ -37,8 +35,6 @@ export interface MetricsExplorerRequest {
|
|||
filterQuery?: string;
|
||||
}
|
||||
|
||||
export type MetricsExplorerWrappedRequest = InfraWrappableRequest<MetricsExplorerRequest>;
|
||||
|
||||
export interface MetricsExplorerPageInfo {
|
||||
total: number;
|
||||
afterKey?: string | null;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Boom from 'boom';
|
||||
import { boomify } from 'boom';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
|
@ -13,27 +13,34 @@ import { UsageCollector } from '../../usage/usage_collector';
|
|||
import { InfraMetricsRequestOptions } from '../../lib/adapters/metrics';
|
||||
import { InfraNodeType, InfraMetric } from '../../graphql/types';
|
||||
import {
|
||||
NodeDetailsWrappedRequest,
|
||||
NodeDetailsRequestRT,
|
||||
NodeDetailsMetricDataResponse,
|
||||
NodeDetailsMetricDataResponseRT,
|
||||
} from '../../../common/http_api/node_details_api';
|
||||
import { throwErrors } from '../../../common/runtime_types';
|
||||
|
||||
const escapeHatch = schema.object({}, { allowUnknowns: true });
|
||||
|
||||
export const initNodeDetailsRoute = (libs: InfraBackendLibs) => {
|
||||
const { framework } = libs;
|
||||
|
||||
framework.registerRoute<NodeDetailsWrappedRequest, Promise<NodeDetailsMetricDataResponse>>({
|
||||
method: 'POST',
|
||||
path: '/api/metrics/node_details',
|
||||
handler: async req => {
|
||||
const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe(
|
||||
NodeDetailsRequestRT.decode(req.payload),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'post',
|
||||
path: '/api/metrics/node_details',
|
||||
validate: {
|
||||
body: escapeHatch,
|
||||
},
|
||||
},
|
||||
async (requestContext, request, response) => {
|
||||
try {
|
||||
const source = await libs.sources.getSourceConfiguration(req, sourceId);
|
||||
const { nodeId, cloudId, nodeType, metrics, timerange, sourceId } = pipe(
|
||||
NodeDetailsRequestRT.decode(request.body),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
const source = await libs.sources.getSourceConfiguration(requestContext, sourceId);
|
||||
|
||||
UsageCollector.countNode(nodeType);
|
||||
|
||||
const options: InfraMetricsRequestOptions = {
|
||||
nodeIds: {
|
||||
nodeId,
|
||||
|
@ -44,13 +51,16 @@ export const initNodeDetailsRoute = (libs: InfraBackendLibs) => {
|
|||
metrics: metrics as InfraMetric[],
|
||||
timerange,
|
||||
};
|
||||
|
||||
return {
|
||||
metrics: await libs.metrics.getMetrics(req, options),
|
||||
};
|
||||
} catch (e) {
|
||||
throw boomify(e);
|
||||
return response.ok({
|
||||
body: NodeDetailsMetricDataResponseRT.encode({
|
||||
metrics: await libs.metrics.getMetrics(requestContext, options, request),
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
return response.internalError({
|
||||
body: error.message,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Boom from 'boom';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { fold } from 'fp-ts/lib/Either';
|
||||
import { identity } from 'fp-ts/lib/function';
|
||||
|
@ -12,37 +13,50 @@ import { InfraSnapshotRequestOptions } from '../../lib/snapshot';
|
|||
import { UsageCollector } from '../../usage/usage_collector';
|
||||
import { parseFilterQuery } from '../../utils/serialized_query';
|
||||
import { InfraNodeType, InfraSnapshotMetricInput } from '../../../public/graphql/types';
|
||||
import {
|
||||
SnapshotRequestRT,
|
||||
SnapshotWrappedRequest,
|
||||
SnapshotNodeResponse,
|
||||
} from '../../../common/http_api/snapshot_api';
|
||||
import { SnapshotRequestRT, SnapshotNodeResponseRT } from '../../../common/http_api/snapshot_api';
|
||||
import { throwErrors } from '../../../common/runtime_types';
|
||||
|
||||
const escapeHatch = schema.object({}, { allowUnknowns: true });
|
||||
|
||||
export const initSnapshotRoute = (libs: InfraBackendLibs) => {
|
||||
const { framework } = libs;
|
||||
|
||||
framework.registerRoute<SnapshotWrappedRequest, Promise<SnapshotNodeResponse>>({
|
||||
method: 'POST',
|
||||
path: '/api/metrics/snapshot',
|
||||
handler: async req => {
|
||||
const { filterQuery, nodeType, groupBy, sourceId, metric, timerange } = pipe(
|
||||
SnapshotRequestRT.decode(req.payload),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
const source = await libs.sources.getSourceConfiguration(req, sourceId);
|
||||
UsageCollector.countNode(nodeType);
|
||||
const options: InfraSnapshotRequestOptions = {
|
||||
filterQuery: parseFilterQuery(filterQuery),
|
||||
// TODO: Use common infra metric and replace graphql type
|
||||
nodeType: nodeType as InfraNodeType,
|
||||
groupBy,
|
||||
sourceConfiguration: source.configuration,
|
||||
// TODO: Use common infra metric and replace graphql type
|
||||
metric: metric as InfraSnapshotMetricInput,
|
||||
timerange,
|
||||
};
|
||||
return { nodes: await libs.snapshot.getNodes(req, options) };
|
||||
framework.registerRoute(
|
||||
{
|
||||
method: 'post',
|
||||
path: '/api/metrics/snapshot',
|
||||
validate: {
|
||||
body: escapeHatch,
|
||||
},
|
||||
},
|
||||
});
|
||||
async (requestContext, request, response) => {
|
||||
try {
|
||||
const { filterQuery, nodeType, groupBy, sourceId, metric, timerange } = pipe(
|
||||
SnapshotRequestRT.decode(request.body),
|
||||
fold(throwErrors(Boom.badRequest), identity)
|
||||
);
|
||||
const source = await libs.sources.getSourceConfiguration(requestContext, sourceId);
|
||||
UsageCollector.countNode(nodeType);
|
||||
const options: InfraSnapshotRequestOptions = {
|
||||
filterQuery: parseFilterQuery(filterQuery),
|
||||
// TODO: Use common infra metric and replace graphql type
|
||||
nodeType: nodeType as InfraNodeType,
|
||||
groupBy,
|
||||
sourceConfiguration: source.configuration,
|
||||
// TODO: Use common infra metric and replace graphql type
|
||||
metric: metric as InfraSnapshotMetricInput,
|
||||
timerange,
|
||||
};
|
||||
return response.ok({
|
||||
body: SnapshotNodeResponseRT.encode({
|
||||
nodes: await libs.snapshot.getNodes(requestContext, options),
|
||||
}),
|
||||
});
|
||||
} catch (error) {
|
||||
return response.internalError({
|
||||
body: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { InfraBackendFrameworkAdapter, InfraFrameworkRequest } from '../lib/adapters/framework';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { KibanaFramework } from '../lib/adapters/framework/kibana_framework_adapter';
|
||||
|
||||
interface Options {
|
||||
indexPattern: string;
|
||||
|
@ -20,8 +21,8 @@ interface Options {
|
|||
* This is useful for visualizing metric modules like s3 that only send metrics once per day.
|
||||
*/
|
||||
export const calculateMetricInterval = async (
|
||||
framework: InfraBackendFrameworkAdapter,
|
||||
request: InfraFrameworkRequest,
|
||||
framework: KibanaFramework,
|
||||
requestContext: RequestHandlerContext,
|
||||
options: Options,
|
||||
modules: string[]
|
||||
) => {
|
||||
|
@ -64,7 +65,11 @@ export const calculateMetricInterval = async (
|
|||
},
|
||||
};
|
||||
|
||||
const resp = await framework.callWithRequest<{}, PeriodAggregationData>(request, 'search', query);
|
||||
const resp = await framework.callWithRequest<{}, PeriodAggregationData>(
|
||||
requestContext,
|
||||
'search',
|
||||
query
|
||||
);
|
||||
|
||||
// if ES doesn't return an aggregations key, something went seriously wrong.
|
||||
if (!resp.aggregations) {
|
||||
|
|
|
@ -4,25 +4,27 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
InfraBackendFrameworkAdapter,
|
||||
InfraFrameworkRequest,
|
||||
InfraDatabaseSearchResponse,
|
||||
} from '../lib/adapters/framework';
|
||||
import { RequestHandlerContext } from 'src/core/server';
|
||||
import { KibanaFramework } from '../lib/adapters/framework/kibana_framework_adapter';
|
||||
import { InfraDatabaseSearchResponse } from '../lib/adapters/framework';
|
||||
|
||||
export const getAllCompositeData = async <
|
||||
Aggregation = undefined,
|
||||
Bucket = {},
|
||||
Options extends object = {}
|
||||
>(
|
||||
framework: InfraBackendFrameworkAdapter,
|
||||
request: InfraFrameworkRequest,
|
||||
framework: KibanaFramework,
|
||||
requestContext: RequestHandlerContext,
|
||||
options: Options,
|
||||
bucketSelector: (response: InfraDatabaseSearchResponse<{}, Aggregation>) => Bucket[],
|
||||
onAfterKey: (options: Options, response: InfraDatabaseSearchResponse<{}, Aggregation>) => Options,
|
||||
previousBuckets: Bucket[] = []
|
||||
): Promise<Bucket[]> => {
|
||||
const response = await framework.callWithRequest<{}, Aggregation>(request, 'search', options);
|
||||
const response = await framework.callWithRequest<{}, Aggregation>(
|
||||
requestContext,
|
||||
'search',
|
||||
options
|
||||
);
|
||||
|
||||
// Nothing available, return the previous buckets.
|
||||
if (response.hits.total.value === 0) {
|
||||
|
@ -45,7 +47,7 @@ export const getAllCompositeData = async <
|
|||
const newOptions = onAfterKey(options, response);
|
||||
return getAllCompositeData(
|
||||
framework,
|
||||
request,
|
||||
requestContext,
|
||||
newOptions,
|
||||
bucketSelector,
|
||||
onAfterKey,
|
||||
|
|
|
@ -3,7 +3,12 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { PluginInitializerContext, Plugin, CoreSetup } from 'src/core/server';
|
||||
import {
|
||||
PluginInitializerContext,
|
||||
Plugin,
|
||||
CoreSetup,
|
||||
SavedObjectsClientContract,
|
||||
} from 'src/core/server';
|
||||
import { Observable, combineLatest, AsyncSubject } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Server } from 'hapi';
|
||||
|
@ -11,6 +16,7 @@ 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';
|
||||
import { getApmIndices } from '../../../legacy/plugins/apm/server/lib/settings/apm_indices/get_apm_indices';
|
||||
import { APMConfig, mergeConfigs, APMXPackConfig } from '.';
|
||||
|
||||
export interface LegacySetup {
|
||||
|
@ -20,13 +26,18 @@ export interface LegacySetup {
|
|||
export interface APMPluginContract {
|
||||
config$: Observable<APMConfig>;
|
||||
registerLegacyAPI: (__LEGACY: LegacySetup) => void;
|
||||
getApmIndices: (
|
||||
savedObjectsClient: SavedObjectsClientContract
|
||||
) => ReturnType<typeof getApmIndices>;
|
||||
}
|
||||
|
||||
export class APMPlugin implements Plugin<APMPluginContract> {
|
||||
legacySetup$: AsyncSubject<LegacySetup>;
|
||||
currentConfig: APMConfig;
|
||||
constructor(private readonly initContext: PluginInitializerContext) {
|
||||
this.initContext = initContext;
|
||||
this.legacySetup$ = new AsyncSubject();
|
||||
this.currentConfig = {} as APMConfig;
|
||||
}
|
||||
|
||||
public async setup(
|
||||
|
@ -49,6 +60,7 @@ export class APMPlugin implements Plugin<APMPluginContract> {
|
|||
await new Promise(resolve => {
|
||||
combineLatest(mergedConfig$, core.elasticsearch.dataClient$).subscribe(
|
||||
async ([config, dataClient]) => {
|
||||
this.currentConfig = config;
|
||||
await createApmAgentConfigurationIndex({
|
||||
esClient: dataClient,
|
||||
config,
|
||||
|
@ -64,6 +76,9 @@ export class APMPlugin implements Plugin<APMPluginContract> {
|
|||
this.legacySetup$.next(__LEGACY);
|
||||
this.legacySetup$.complete();
|
||||
}),
|
||||
getApmIndices: async (savedObjectsClient: SavedObjectsClientContract) => {
|
||||
return getApmIndices({ savedObjectsClient, config: this.currentConfig });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
6
x-pack/plugins/infra/kibana.json
Normal file
6
x-pack/plugins/infra/kibana.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"id": "infra",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": true
|
||||
}
|
24
x-pack/plugins/infra/server/index.ts
Normal file
24
x-pack/plugins/infra/server/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { PluginInitializerContext } from 'src/core/server';
|
||||
import { InfraPlugin } from './plugin';
|
||||
|
||||
export const config = {
|
||||
schema: schema.object({
|
||||
enabled: schema.maybe(schema.boolean()),
|
||||
query: schema.object({
|
||||
partitionSize: schema.maybe(schema.number()),
|
||||
partitionFactor: schema.maybe(schema.number()),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
export const plugin = (initContext: PluginInitializerContext) => new InfraPlugin(initContext);
|
||||
|
||||
export type InfraConfig = TypeOf<typeof config.schema>;
|
||||
export { InfraSetup } from './plugin';
|
33
x-pack/plugins/infra/server/plugin.ts
Normal file
33
x-pack/plugins/infra/server/plugin.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { Plugin, PluginInitializerContext } from 'src/core/server';
|
||||
|
||||
export class InfraPlugin implements Plugin<InfraSetup> {
|
||||
private readonly initContext: PluginInitializerContext;
|
||||
|
||||
constructor(initContext: PluginInitializerContext) {
|
||||
this.initContext = initContext;
|
||||
}
|
||||
|
||||
public setup() {
|
||||
return {
|
||||
__legacy: {
|
||||
config: this.initContext.config,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public start() {}
|
||||
public stop() {}
|
||||
}
|
||||
|
||||
export interface InfraSetup {
|
||||
/** @deprecated */
|
||||
__legacy: {
|
||||
config: PluginInitializerContext['config'];
|
||||
};
|
||||
}
|
|
@ -19,7 +19,6 @@ const introspectionQuery = gql`
|
|||
`;
|
||||
|
||||
export default function({ getService }: FtrProviderContext) {
|
||||
const supertest = getService('supertestWithoutAuth');
|
||||
const security = getService('security');
|
||||
const spaces = getService('spaces');
|
||||
const clientFactory = getService('infraOpsGraphQLClientFactory');
|
||||
|
@ -37,18 +36,6 @@ export default function({ getService }: FtrProviderContext) {
|
|||
expect(result.response.data).to.be.an('object');
|
||||
};
|
||||
|
||||
const expectGraphIQL404 = (result: any) => {
|
||||
expect(result.error).to.be(undefined);
|
||||
expect(result.response).not.to.be(undefined);
|
||||
expect(result.response).to.have.property('statusCode', 404);
|
||||
};
|
||||
|
||||
const expectGraphIQLResponse = (result: any) => {
|
||||
expect(result.error).to.be(undefined);
|
||||
expect(result.response).not.to.be(undefined);
|
||||
expect(result.response).to.have.property('statusCode', 200);
|
||||
};
|
||||
|
||||
const executeGraphQLQuery = async (username: string, password: string, spaceId?: string) => {
|
||||
const queryOptions = {
|
||||
query: introspectionQuery,
|
||||
|
@ -70,16 +57,6 @@ export default function({ getService }: FtrProviderContext) {
|
|||
};
|
||||
};
|
||||
|
||||
const executeGraphIQLRequest = async (username: string, password: string, spaceId?: string) => {
|
||||
const basePath = spaceId ? `/s/${spaceId}` : '';
|
||||
|
||||
return supertest
|
||||
.get(`${basePath}/api/infra/graphql/graphiql`)
|
||||
.auth(username, password)
|
||||
.then((response: any) => ({ error: undefined, response }))
|
||||
.catch((error: any) => ({ error, response: undefined }));
|
||||
};
|
||||
|
||||
describe('feature controls', () => {
|
||||
it(`APIs can't be accessed by user with logstash-* "read" privileges`, async () => {
|
||||
const username = 'logstash_read';
|
||||
|
@ -105,9 +82,6 @@ export default function({ getService }: FtrProviderContext) {
|
|||
|
||||
const graphQLResult = await executeGraphQLQuery(username, password);
|
||||
expectGraphQL404(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password);
|
||||
expectGraphIQL404(graphQLIResult);
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
|
@ -144,9 +118,6 @@ export default function({ getService }: FtrProviderContext) {
|
|||
|
||||
const graphQLResult = await executeGraphQLQuery(username, password);
|
||||
expectGraphQLResponse(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password);
|
||||
expectGraphIQLResponse(graphQLIResult);
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
|
@ -186,9 +157,6 @@ export default function({ getService }: FtrProviderContext) {
|
|||
|
||||
const graphQLResult = await executeGraphQLQuery(username, password);
|
||||
expectGraphQL404(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password);
|
||||
expectGraphIQL404(graphQLIResult);
|
||||
} finally {
|
||||
await security.role.delete(roleName);
|
||||
await security.user.delete(username);
|
||||
|
@ -268,25 +236,16 @@ export default function({ getService }: FtrProviderContext) {
|
|||
it('user_1 can access APIs in space_1', async () => {
|
||||
const graphQLResult = await executeGraphQLQuery(username, password, space1Id);
|
||||
expectGraphQLResponse(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password, space1Id);
|
||||
expectGraphIQLResponse(graphQLIResult);
|
||||
});
|
||||
|
||||
it(`user_1 can access APIs in space_2`, async () => {
|
||||
const graphQLResult = await executeGraphQLQuery(username, password, space2Id);
|
||||
expectGraphQLResponse(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password, space2Id);
|
||||
expectGraphIQLResponse(graphQLIResult);
|
||||
});
|
||||
|
||||
it(`user_1 can't access APIs in space_3`, async () => {
|
||||
const graphQLResult = await executeGraphQLQuery(username, password, space3Id);
|
||||
expectGraphQL404(graphQLResult);
|
||||
|
||||
const graphQLIResult = await executeGraphIQLRequest(username, password, space3Id);
|
||||
expectGraphIQL404(graphQLIResult);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
26
x-pack/test/typings/rison_node.d.ts
vendored
26
x-pack/test/typings/rison_node.d.ts
vendored
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
declare module 'rison-node' {
|
||||
export type RisonValue = null | boolean | number | string | RisonObject | RisonArray;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface RisonArray extends Array<RisonValue> {}
|
||||
|
||||
export interface RisonObject {
|
||||
[key: string]: RisonValue;
|
||||
}
|
||||
|
||||
export const decode: (input: string) => RisonValue;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
export const decode_object: (input: string) => RisonObject;
|
||||
|
||||
export const encode: <Input extends RisonValue>(input: Input) => string;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
export const encode_object: <Input extends RisonObject>(input: Input) => string;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue