Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[Draft][Rule Registry] Create index template per namespace #107700

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 10 additions & 22 deletions x-pack/plugins/apm/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,11 @@ export class APMPlugin
const getCoreStart = () =>
core.getStartServices().then(([coreStart]) => coreStart);

const alertsIndexPattern = ruleDataService.getFullAssetName(
'observability-apm*'
const assetName = 'observability-apm';
const componentTemplateName = ruleDataService.getFullAssetName(
'apm-mappings'
);

const initializeRuleDataTemplates = once(async () => {
const componentTemplateName = ruleDataService.getFullAssetName(
'apm-mappings'
);

if (!ruleDataService.isWriteEnabled()) {
return;
}
Expand Down Expand Up @@ -154,19 +150,7 @@ export class APMPlugin
},
});

await ruleDataService.createOrUpdateIndexTemplate({
name: ruleDataService.getFullAssetName('apm-index-template'),
body: {
index_patterns: [alertsIndexPattern],
composed_of: [
ruleDataService.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME),
componentTemplateName,
],
},
});
await ruleDataService.updateIndexMappingsMatchingPattern(
alertsIndexPattern
);
await ruleDataService.updateIndexMappingsForAsset(assetName);
});

// initialize eagerly
Expand All @@ -178,8 +162,12 @@ export class APMPlugin

const ruleDataClient = ruleDataService.getRuleDataClient(
APM_SERVER_FEATURE_ID,
ruleDataService.getFullAssetName('observability-apm'),
() => initializeRuleDataTemplatesPromise
assetName,
() => initializeRuleDataTemplatesPromise,
[
ruleDataService.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME),
componentTemplateName,
]
);

const resourcePlugins = mapValues(plugins, (value, key) => {
Expand Down
23 changes: 12 additions & 11 deletions x-pack/plugins/infra/server/services/rules/rule_data_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,10 @@ export const createRuleDataClient = ({
logger: Logger;
ruleDataService: RuleRegistryPluginSetupContract['ruleDataService'];
}) => {
const componentTemplateName = ruleDataService.getFullAssetName(
`${registrationContext}-mappings`
);
const initializeRuleDataTemplates = once(async () => {
const componentTemplateName = ruleDataService.getFullAssetName(
`${registrationContext}-mappings`
);

const indexNamePattern = ruleDataService.getFullAssetName(`${registrationContext}*`);

if (!ruleDataService.isWriteEnabled()) {
return;
}
Expand All @@ -48,7 +45,7 @@ export const createRuleDataClient = ({
},
});

await ruleDataService.createOrUpdateIndexTemplate({
/*await ruleDataService.createOrUpdateIndexTemplate({
name: ruleDataService.getFullAssetName(registrationContext),
body: {
index_patterns: [indexNamePattern],
Expand All @@ -57,9 +54,9 @@ export const createRuleDataClient = ({
componentTemplateName,
],
},
});
});*/

await ruleDataService.updateIndexMappingsMatchingPattern(indexNamePattern);
await ruleDataService.updateIndexMappingsForAsset(registrationContext);
});

// initialize eagerly
Expand All @@ -69,7 +66,11 @@ export const createRuleDataClient = ({

return ruleDataService.getRuleDataClient(
ownerFeatureId,
ruleDataService.getFullAssetName(registrationContext),
() => initializeRuleDataTemplatesPromise
registrationContext,
() => initializeRuleDataTemplatesPromise,
[
ruleDataService.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME),
componentTemplateName,
]
);
};
6 changes: 4 additions & 2 deletions x-pack/plugins/observability/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,12 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {

const start = () => core.getStartServices().then(([coreStart]) => coreStart);

// ???? This appears to be a stub
const ruleDataClient = plugins.ruleRegistry.ruleDataService.getRuleDataClient(
'observability',
plugins.ruleRegistry.ruleDataService.getFullAssetName(),
() => Promise.resolve()
'',
() => Promise.resolve(),
[],
);

registerRoutes({
Expand Down
72 changes: 68 additions & 4 deletions x-pack/plugins/rule_registry/server/rule_data_client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
* 2.0.
*/

import { IndicesPutIndexTemplateRequest } from '@elastic/elasticsearch/api/types';
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
import { IndexPatternsFetcher } from '../../../../../src/plugins/data/server';
import { DEFAULT_ILM_POLICY_ID } from '../../common/assets';
import { RuleDataWriteDisabledError } from '../rule_data_plugin_service/errors';
import {
IRuleDataClient,
Expand Down Expand Up @@ -111,24 +113,86 @@ export class RuleDataClient implements IRuleDataClient {
};
}

createNamespacedIndexTemplate({
primaryNamespacedAlias,
secondaryNamespacedAlias,
namespace,
}: {
primaryNamespacedAlias: string;
secondaryNamespacedAlias?: string;
namespace?: string;
}): IndicesPutIndexTemplateRequest {
return {
name: primaryNamespacedAlias,
body: {
index_patterns: [`${primaryNamespacedAlias}-*`],
composed_of: [...this.options.componentTemplateNames],
template: {
aliases:
secondaryNamespacedAlias != null
? {
[secondaryNamespacedAlias]: {
is_write_index: false,
},
}
: undefined,
settings: {
'index.lifecycle': {
name: DEFAULT_ILM_POLICY_ID,
// TODO: fix the types in the ES package, they don't include rollover_alias???
// @ts-expect-error
rollover_alias: primaryNamespacedAlias,
},
},
},
_meta: {
namespace,
},
// By setting the priority to namespace.length, we ensure that if one namespace is a prefix of another namespace
// then newly created indices will use the matching template with the *longest* namespace
priority: namespace?.length,
Comment on lines +151 to +153
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I got this part. Why do we need to set the priority if namespace cannot contain dashes?

I mean, let's say we have indices like

.alerts-security.alerts-foo-000001
.alerts-security.alerts-foobar-000001

Index template of the first one will have index_patterns: ['.alerts-security.alerts-foo-*'] which will exclude the second index.

Sorry, maybe I'm just completely not seeing the idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The namespace can contain dashes. Even if we prevent new namespaces from having dashes, we plan to use existing space IDs as namespaces for backwards compatibility, and space IDs can have dashes in them already.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh... Holy moly, yes you're right!

Copy link
Contributor Author

@marshallmain marshallmain Aug 5, 2021

Choose a reason for hiding this comment

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

Here's an issue I wrote up on the problem with a little more detail: #107704

Proposal 4 on there is what I implemented here and I think it's definitely preferable to the other 3, but I had that idea after I already wrote the first 3 ideas so I just left them there 😄

},
};
}

async createWriteTargetIfNeeded({ namespace }: { namespace?: string }) {
const alias = getNamespacedAlias({ alias: this.options.alias, namespace });
const primaryNamespacedAlias = getNamespacedAlias({ alias: this.options.alias, namespace });

const clusterClient = await this.getClusterClient();

const { body: aliasExists } = await clusterClient.indices.existsAlias({
name: alias,
name: primaryNamespacedAlias,
});

const concreteIndexName = `${alias}-000001`;
const concreteIndexName = `${primaryNamespacedAlias}-000001`;

if (!aliasExists) {
const secondaryNamespacedAlias =
this.options.secondaryAlias != null
? getNamespacedAlias({ alias: this.options.secondaryAlias, namespace })
: undefined;
const template = this.createNamespacedIndexTemplate({
primaryNamespacedAlias,
secondaryNamespacedAlias,
namespace,
});
// TODO: need a way to update this template if/when we decide to make changes to the
// built in index template. Probably do it as part of updateIndexMappingsForAsset?
Comment on lines +179 to +180
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I think we will need it... One simple case that comes to my mind is when we add or remove a component template, we will need to update the index template as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Definitely true. It may not be critical for 7.15 though since it's the first release.

// (Before upgrading any indices, find and upgrade all namespaced index templates - component templates
// will already have been upgraded by solutions or rule registry, in the case of technical/ECS templates)
// With the current structure, it's tricky because the index template creation
// depends on both the namespace and secondary alias, both of which are not currently available
// to updateIndexMappingsForAsset. We can make the secondary alias available since
// it's known at plugin startup time, but
// the namespace values can really only come from the existing templates that we're trying to update
// - maybe we want to store the namespace as a _meta field on the index template for easy retrieval
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, we will need to somehow retrieve the namespace from existing index templates, I think it's a good idea to store it in _meta 👍 Thanks for this idea!

await clusterClient.indices.putIndexTemplate(template);
try {
await clusterClient.indices.create({
index: concreteIndexName,
body: {
aliases: {
[alias]: {
[primaryNamespacedAlias]: {
is_write_index: true,
},
},
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/rule_registry/server/rule_data_client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export interface RuleDataClientConstructorOptions {
getClusterClient: () => Promise<ElasticsearchClient>;
isWriteEnabled: boolean;
ready: () => Promise<void>;
componentTemplateNames: string[];
alias: string;
feature: ValidFeatureId;
secondaryAlias?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -195,10 +195,11 @@ export class RuleDataPluginService {
return this._createOrUpdateLifecyclePolicy(policy);
}

async updateIndexMappingsMatchingPattern(pattern: string) {
async updateIndexMappingsForAsset(assetName: string) {
await this.wait();
const clusterClient = await this.getClusterClient();
const { body: aliasesResponse } = await clusterClient.indices.getAlias({ index: pattern });
const pattern = `${this.getFullAssetName(assetName)}-*`;
const { body: aliasesResponse } = await clusterClient.indices.getAlias({ name: pattern });
const writeIndicesAndAliases: Array<{ index: string; alias: string }> = [];
Object.entries(aliasesResponse).forEach(([index, aliases]) => {
Object.entries(aliases.aliases).forEach(([aliasName, aliasProperties]) => {
Expand Down Expand Up @@ -235,13 +236,21 @@ export class RuleDataPluginService {
return [this.options.index, assetName].filter(Boolean).join('-');
}

getRuleDataClient(feature: ValidFeatureId, alias: string, initialize: () => Promise<void>) {
getRuleDataClient(
feature: ValidFeatureId,
assetName: string,
initialize: () => Promise<void>,
componentTemplateNames: string[],
secondaryAlias?: string
) {
return new RuleDataClient({
alias,
alias: this.getFullAssetName(assetName),
feature,
getClusterClient: () => this.getClusterClient(),
isWriteEnabled: this.isWriteEnabled(),
ready: initialize,
componentTemplateNames,
secondaryAlias,
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const createRuleDataPluginService = () => {
createOrUpdateIndexTemplate: jest.fn(),
createOrUpdateLifecyclePolicy: jest.fn(),
getRuleDataClient: jest.fn(),
updateIndexMappingsMatchingPattern: jest.fn(),
updateIndexMappingsForAsset: jest.fn(),
};
return mocked;
};
Expand Down
22 changes: 13 additions & 9 deletions x-pack/plugins/security_solution/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,14 +203,12 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
let ruleDataClient: RuleDataClient | null = null;
if (isRuleRegistryEnabled) {
const { ruleDataService } = plugins.ruleRegistry;

const alertsIndexPattern = ruleDataService.getFullAssetName('security.alerts*');

const assetName = 'security.alerts';
const componentTemplateName = ruleDataService.getFullAssetName('security.alerts-mappings');
const initializeRuleDataTemplates = once(async () => {
if (!ruleDataService.isWriteEnabled()) {
return;
}
const componentTemplateName = ruleDataService.getFullAssetName('security.alerts-mappings');

await ruleDataService.createOrUpdateComponentTemplate({
name: componentTemplateName,
Expand All @@ -224,7 +222,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
},
});

await ruleDataService.createOrUpdateIndexTemplate({
/* await ruleDataService.createOrUpdateIndexTemplate({
name: ruleDataService.getFullAssetName('security.alerts-index-template'),
body: {
index_patterns: [alertsIndexPattern],
Expand All @@ -234,8 +232,8 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
componentTemplateName,
],
},
});
await ruleDataService.updateIndexMappingsMatchingPattern(alertsIndexPattern);
});*/
await ruleDataService.updateIndexMappingsForAsset(assetName);
});

// initialize eagerly
Expand All @@ -247,8 +245,14 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S

ruleDataClient = ruleDataService.getRuleDataClient(
SERVER_APP_ID,
indexAlias,
() => initializeRuleDataTemplatesPromise
assetName,
() => initializeRuleDataTemplatesPromise,
[
ruleDataService.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME),
ruleDataService.getFullAssetName(ECS_COMPONENT_TEMPLATE_NAME),
componentTemplateName,
],
config.signalsIndex
);

// Register rule types via rule-registry
Expand Down
24 changes: 6 additions & 18 deletions x-pack/plugins/uptime/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,9 @@ export class Plugin implements PluginType {
public setup(core: CoreSetup, plugins: UptimeCorePlugins) {
this.logger = this.initContext.logger.get();
const { ruleDataService } = plugins.ruleRegistry;

const assetName = 'observability.synthetics';
const componentTemplateName = ruleDataService.getFullAssetName('synthetics-mappings');
const ready = once(async () => {
const componentTemplateName = ruleDataService.getFullAssetName('synthetics-mappings');
const alertsIndexPattern = ruleDataService.getFullAssetName('observability.synthetics*');

if (!ruleDataService.isWriteEnabled()) {
return;
}
Expand All @@ -55,18 +53,7 @@ export class Plugin implements PluginType {
},
});

await ruleDataService.createOrUpdateIndexTemplate({
name: ruleDataService.getFullAssetName('synthetics-index-template'),
body: {
index_patterns: [alertsIndexPattern],
composed_of: [
ruleDataService.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME),
componentTemplateName,
],
},
});

await ruleDataService.updateIndexMappingsMatchingPattern(alertsIndexPattern);
await ruleDataService.updateIndexMappingsForAsset(assetName);
});

// initialize eagerly
Expand All @@ -76,8 +63,9 @@ export class Plugin implements PluginType {

const ruleDataClient = ruleDataService.getRuleDataClient(
'synthetics',
ruleDataService.getFullAssetName('observability.synthetics'),
() => initializeRuleDataTemplatesPromise
assetName,
() => initializeRuleDataTemplatesPromise,
[ruleDataService.getFullAssetName(TECHNICAL_COMPONENT_TEMPLATE_NAME), componentTemplateName]
);

initServerWithKibana(
Expand Down