[Alerting] Use rule ID as task ID when scheduling an alerting rule task (#117397)

* Using a static taskId for alerting tasks

* Making taskId required

* Fixing types

* Fixing tests

* Cleanup

* Updating unit tests

* Fixing types

* Using rule id for scheduled task id

* Optionally throw conflict errors depending on operation

* Adding functional test

* Adding try/catch to task manager get in rule disable

* Reverting change to disable behavior

* More functional tests

* Fixing functional test

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
ymao1 2021-11-11 10:31:50 -05:00 committed by GitHub
parent 6a7574d6c4
commit 703198227b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 813 additions and 32 deletions

View file

@ -377,10 +377,11 @@ export class RulesClient {
if (data.enabled) {
let scheduledTask;
try {
scheduledTask = await this.scheduleAlert(
scheduledTask = await this.scheduleRule(
createdAlert.id,
rawAlert.alertTypeId,
data.schedule
data.schedule,
true
);
} catch (e) {
// Cleanup data, something went wrong scheduling the task
@ -1149,10 +1150,11 @@ export class RulesClient {
);
throw e;
}
const scheduledTask = await this.scheduleAlert(
const scheduledTask = await this.scheduleRule(
id,
attributes.alertTypeId,
attributes.schedule as IntervalSchedule
attributes.schedule as IntervalSchedule,
false
);
await this.unsecuredSavedObjectsClient.update('alert', id, {
scheduledTaskId: scheduledTask.id,
@ -1563,9 +1565,15 @@ export class RulesClient {
return this.spaceId;
}
private async scheduleAlert(id: string, alertTypeId: string, schedule: IntervalSchedule) {
return await this.taskManager.schedule({
taskType: `alerting:${alertTypeId}`,
private async scheduleRule(
id: string,
ruleTypeId: string,
schedule: IntervalSchedule,
throwOnConflict: boolean // whether to throw conflict errors or swallow them
) {
const taskInstance = {
id, // use the same ID for task document as the rule
taskType: `alerting:${ruleTypeId}`,
schedule,
params: {
alertId: id,
@ -1577,7 +1585,15 @@ export class RulesClient {
alertInstances: {},
},
scope: ['alerting'],
});
};
try {
return await this.taskManager.schedule(taskInstance);
} catch (err) {
if (err.statusCode === 409 && !throwOnConflict) {
return taskInstance;
}
throw err;
}
}
private injectReferencesIntoActions(

View file

@ -441,6 +441,7 @@ describe('create()', () => {
expect(taskManager.schedule.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Object {
"id": "1",
"params": Object {
"alertId": "1",
"spaceId": "default",
@ -1923,6 +1924,52 @@ describe('create()', () => {
});
});
test('fails if task scheduling fails due to conflict', async () => {
const data = getMockData();
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
alertTypeId: '123',
schedule: { interval: '10s' },
params: {
bar: true,
},
actions: [
{
group: 'default',
actionRef: 'action_0',
actionTypeId: 'test',
params: {
foo: true,
},
},
],
},
references: [
{
name: 'action_0',
type: 'action',
id: '1',
},
],
});
taskManager.schedule.mockRejectedValueOnce(
Object.assign(new Error('Conflict!'), { statusCode: 409 })
);
unsecuredSavedObjectsClient.delete.mockResolvedValueOnce({});
await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot(
`"Conflict!"`
);
expect(unsecuredSavedObjectsClient.delete).toHaveBeenCalledTimes(1);
expect(unsecuredSavedObjectsClient.delete.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"alert",
"1",
]
`);
});
test('attempts to remove saved object if scheduling failed', async () => {
const data = getMockData();
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({

View file

@ -98,7 +98,7 @@ describe('enable()', () => {
},
});
taskManager.schedule.mockResolvedValue({
id: 'task-123',
id: '1',
scheduledAt: new Date(),
attempts: 0,
status: TaskStatus.Idle,
@ -113,27 +113,6 @@ describe('enable()', () => {
});
describe('authorization', () => {
beforeEach(() => {
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValue(existingAlert);
unsecuredSavedObjectsClient.get.mockResolvedValue(existingAlert);
rulesClientParams.createAPIKey.mockResolvedValue({
apiKeysEnabled: false,
});
taskManager.schedule.mockResolvedValue({
id: 'task-123',
scheduledAt: new Date(),
attempts: 0,
status: TaskStatus.Idle,
runAt: new Date(),
state: {},
params: {},
taskType: '',
startedAt: null,
retryAt: null,
ownerId: null,
});
});
test('ensures user is authorised to enable this type of alert under the consumer', async () => {
await rulesClient.enable({ id: '1' });
@ -203,7 +182,7 @@ describe('enable()', () => {
});
});
test('enables an alert', async () => {
test('enables a rule', async () => {
const createdAt = new Date().toISOString();
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
...existingAlert,
@ -270,6 +249,7 @@ describe('enable()', () => {
}
);
expect(taskManager.schedule).toHaveBeenCalledWith({
id: '1',
taskType: `alerting:myType`,
params: {
alertId: '1',
@ -286,7 +266,7 @@ describe('enable()', () => {
scope: ['alerting'],
});
expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith('alert', '1', {
scheduledTaskId: 'task-123',
scheduledTaskId: '1',
});
});
@ -477,4 +457,95 @@ describe('enable()', () => {
expect(rulesClientParams.createAPIKey).toHaveBeenCalled();
expect(unsecuredSavedObjectsClient.update).toHaveBeenCalled();
});
test('enables a rule if conflict errors received when scheduling a task', async () => {
const createdAt = new Date().toISOString();
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
...existingAlert,
attributes: {
...existingAlert.attributes,
enabled: true,
apiKey: null,
apiKeyOwner: null,
updatedBy: 'elastic',
},
});
unsecuredSavedObjectsClient.create.mockResolvedValueOnce({
id: '1',
type: 'api_key_pending_invalidation',
attributes: {
apiKeyId: '123',
createdAt,
},
references: [],
});
taskManager.schedule.mockRejectedValueOnce(
Object.assign(new Error('Conflict!'), { statusCode: 409 })
);
await rulesClient.enable({ id: '1' });
expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled();
expect(encryptedSavedObjects.getDecryptedAsInternalUser).toHaveBeenCalledWith('alert', '1', {
namespace: 'default',
});
expect(unsecuredSavedObjectsClient.create).not.toBeCalledWith('api_key_pending_invalidation');
expect(rulesClientParams.createAPIKey).toHaveBeenCalled();
expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith(
'alert',
'1',
{
schedule: { interval: '10s' },
alertTypeId: 'myType',
consumer: 'myApp',
enabled: true,
meta: {
versionApiKeyLastmodified: kibanaVersion,
},
updatedAt: '2019-02-12T21:01:22.479Z',
updatedBy: 'elastic',
apiKey: null,
apiKeyOwner: null,
actions: [
{
group: 'default',
id: '1',
actionTypeId: '1',
actionRef: '1',
params: {
foo: true,
},
},
],
executionStatus: {
status: 'pending',
lastDuration: 0,
lastExecutionDate: '2019-02-12T21:01:22.479Z',
error: null,
},
},
{
version: '123',
}
);
expect(taskManager.schedule).toHaveBeenCalledWith({
id: '1',
taskType: `alerting:myType`,
params: {
alertId: '1',
spaceId: 'default',
},
schedule: {
interval: '10s',
},
state: {
alertInstances: {},
alertTypeState: {},
previousStartedAt: null,
},
scope: ['alerting'],
});
expect(unsecuredSavedObjectsClient.update).toHaveBeenCalledWith('alert', '1', {
scheduledTaskId: '1',
});
});
});

View file

@ -40,6 +40,7 @@ export default function alertingTests({ loadTestFile, getService }: FtrProviderC
loadTestFile(require.resolve('./notify_when'));
loadTestFile(require.resolve('./ephemeral'));
loadTestFile(require.resolve('./event_log_alerts'));
loadTestFile(require.resolve('./scheduled_task_id'));
// note that this test will destroy existing spaces
loadTestFile(require.resolve('./migrations'));

View file

@ -0,0 +1,112 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import expect from '@kbn/expect';
import { getUrlPrefix, TaskManagerDoc, ObjectRemover, getTestAlertData } from '../../../common/lib';
import { FtrProviderContext } from '../../../common/ftr_provider_context';
const MIGRATED_RULE_ID = '74f3e6d7-b7bb-477d-ac28-92ee22728e6e';
const MIGRATED_TASK_ID = '329798f0-b0b0-11ea-9510-fdf248d5f2a4';
// eslint-disable-next-line import/no-default-export
export default function createScheduledTaskIdTests({ getService }: FtrProviderContext) {
const es = getService('es');
const supertest = getService('supertest');
const supertestWithoutAuth = getService('supertestWithoutAuth');
const esArchiver = getService('esArchiver');
describe('scheduled task id', () => {
const objectRemover = new ObjectRemover(supertest);
async function getScheduledTask(id: string): Promise<TaskManagerDoc> {
const scheduledTask = await es.get<TaskManagerDoc>({
id: `task:${id}`,
index: '.kibana_task_manager',
});
return scheduledTask._source!;
}
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/rules_scheduled_task_id');
});
after(async () => {
await esArchiver.unload('x-pack/test/functional/es_archives/rules_scheduled_task_id');
});
it('cannot create rule with same ID as a scheduled task ID used by another rule', async () => {
const response = await supertest.get(
`${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_RULE_ID}`
);
expect(response.status).to.eql(200);
expect(response.body.scheduled_task_id).to.eql(MIGRATED_TASK_ID);
await supertest
.post(`${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_TASK_ID}`)
.set('kbn-xsrf', 'foo')
.send(getTestAlertData())
.expect(409);
});
it('for migrated rules - sets scheduled task id to match rule id when rule is disabled then enabled', async () => {
const response = await supertest.get(
`${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_RULE_ID}`
);
expect(response.status).to.eql(200);
expect(response.body.scheduled_task_id).to.eql(MIGRATED_TASK_ID);
// scheduled task id should exist
const taskRecordLoaded = await getScheduledTask(MIGRATED_TASK_ID);
expect(JSON.parse(taskRecordLoaded.task.params)).to.eql({
alertId: MIGRATED_RULE_ID,
spaceId: 'default',
});
await supertestWithoutAuth
.post(`${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_RULE_ID}/_disable`)
.set('kbn-xsrf', 'foo')
.expect(204);
await supertestWithoutAuth
.post(`${getUrlPrefix(``)}/api/alerting/rule/${MIGRATED_RULE_ID}/_enable`)
.set('kbn-xsrf', 'foo')
.expect(204);
try {
await getScheduledTask(MIGRATED_TASK_ID);
throw new Error('Should have removed scheduled task');
} catch (e) {
expect(e.meta.statusCode).to.eql(404);
}
// scheduled task id that is same as rule id should exist
const taskRecordNew = await getScheduledTask(MIGRATED_RULE_ID);
expect(JSON.parse(taskRecordNew.task.params)).to.eql({
alertId: MIGRATED_RULE_ID,
spaceId: 'default',
});
});
it('sets scheduled task id to rule id when rule is created', async () => {
const response = await supertestWithoutAuth
.post(`${getUrlPrefix(``)}/api/alerting/rule`)
.set('kbn-xsrf', 'foo')
.send(getTestAlertData());
expect(response.status).to.eql(200);
objectRemover.add('default', response.body.id, 'rule', 'alerting');
expect(response.body.scheduled_task_id).to.eql(response.body.id);
const taskRecord = await getScheduledTask(response.body.scheduled_task_id);
expect(taskRecord.type).to.eql('task');
expect(taskRecord.task.taskType).to.eql('alerting:test.noop');
expect(JSON.parse(taskRecord.task.params)).to.eql({
alertId: response.body.id,
spaceId: 'default',
});
});
});
}

View file

@ -0,0 +1,74 @@
{
"type": "doc",
"value": {
"id": "alert:74f3e6d7-b7bb-477d-ac28-92ee22728e6e",
"index": ".kibana_1",
"source": {
"alert": {
"actions": [
],
"alertTypeId": "example.always-firing",
"apiKey": "QIUT8u0/kbOakEHSj50jDpVR90MrqOxanEscboYOoa8PxQvcA5jfHash+fqH3b+KNjJ1LpnBcisGuPkufY9j1e32gKzwGZV5Bfys87imHvygJvIM8uKiFF8bQ8Y4NTaxOJO9fAmZPrFy07ZcQMCAQz+DUTgBFqs=",
"apiKeyOwner": "elastic",
"consumer": "alerts",
"createdAt": "2020-06-17T15:35:38.497Z",
"createdBy": "elastic",
"enabled": true,
"muteAll": false,
"mutedInstanceIds": [
],
"name": "always-firing-alert",
"params": {
},
"schedule": {
"interval": "1m"
},
"scheduledTaskId": "329798f0-b0b0-11ea-9510-fdf248d5f2a4",
"tags": [
],
"throttle": null,
"updatedBy": "elastic"
},
"migrationVersion": {
"alert": "7.16.0"
},
"references": [
],
"type": "alert",
"updated_at": "2020-06-17T15:35:39.839Z"
}
}
}
{
"type": "doc",
"value": {
"id": "task:329798f0-b0b0-11ea-9510-fdf248d5f2a4",
"index": ".kibana_task_manager_1",
"source": {
"migrationVersion": {
"task": "7.16.0"
},
"task": {
"attempts": 0,
"ownerId": null,
"params": "{\"alertId\":\"74f3e6d7-b7bb-477d-ac28-92ee22728e6e\",\"spaceId\":\"default\"}",
"retryAt": null,
"runAt": "2021-11-05T16:21:52.148Z",
"schedule": {
"interval": "1m"
},
"scheduledAt": "2021-11-05T15:28:42.055Z",
"scope": [
"alerting"
],
"startedAt": null,
"status": "idle",
"taskType": "alerting:example.always-firing"
},
"references": [],
"type": "task",
"updated_at": "2021-11-05T16:21:37.629Z"
}
}
}

View file

@ -0,0 +1,460 @@
{
"type": "index",
"value": {
"aliases": {
".kibana": {
}
},
"index": ".kibana_1",
"mappings": {
"_meta": {
"migrationMappingPropertyHashes": {
"action": "6e96ac5e648f57523879661ea72525b7",
"action_task_params": "a9d49f184ee89641044be0ca2950fa3a",
"alert": "7b44fba6773e37c806ce290ea9b7024e",
"apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd",
"apm-telemetry": "3525d7c22c42bc80f5e6e9cb3f2b26a2",
"application_usage_totals": "c897e4310c5f24b07caaff3db53ae2c1",
"application_usage_transactional": "965839e75f809fefe04f92dc4d99722a",
"canvas-element": "7390014e1091044523666d97247392fc",
"canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231",
"cases": "32aa96a6d3855ddda53010ae2048ac22",
"cases-comments": "c2061fb929f585df57425102fa928b4b",
"cases-configure": "42711cbb311976c0687853f4c1354572",
"cases-user-actions": "32277330ec6b721abe3b846cfd939a71",
"config": "ae24d22d5986d04124cc6568f771066f",
"dashboard": "d00f614b29a80360e1190193fd333bab",
"file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e",
"graph-workspace": "cd7ba1330e6682e9cc00b78850874be1",
"index-pattern": "66eccb05066c5a89924f48a9e9736499",
"infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c",
"inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2",
"kql-telemetry": "d12a98a6f19a2d273696597547e064ee",
"lens": "d33c68a69ff1e78c9888dedd2164ac22",
"lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327",
"map": "23d7aa4a720d4938ccde3983f87bd58d",
"maps-telemetry": "bfd39d88aadadb4be597ea984d433dbe",
"metrics-explorer-view": "428e319af3e822c80a84cf87123ca35c",
"migrationVersion": "4a1746014a75ade3a714e1db5763276f",
"ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9",
"namespace": "2f4316de49999235636386fe51dc06c1",
"namespaces": "2f4316de49999235636386fe51dc06c1",
"query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9",
"references": "7997cf5a56cc02bdc9c93361bde732b0",
"sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4",
"search": "181661168bbadd1eff5902361e2a0d5c",
"space": "c5ca8acafa0beaa4d08d014a97b6bc6b",
"telemetry": "36a616f7026dfa617d6655df850fe16d",
"todo": "082a2cc96a590268344d5cd74c159ac4",
"tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215",
"type": "2f4316de49999235636386fe51dc06c1",
"ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3",
"updated_at": "00da57df13e94e9d98437d13ace4bfe0",
"upgrade-assistant-reindex-operation": "296a89039fc4260292be36b1b005d8f2",
"upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b",
"uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80",
"url": "b675c3be8d76ecf029294d51dc7ec65d",
"visualization": "52d7a13ad68a150c4525b292d23e12cc"
}
},
"dynamic": "strict",
"properties": {
"action": {
"properties": {
"actionTypeId": {
"type": "keyword"
},
"config": {
"enabled": false,
"type": "object"
},
"name": {
"fields": {
"keyword": {
"type": "keyword"
}
},
"type": "text"
},
"secrets": {
"type": "binary"
}
}
},
"action_task_params": {
"properties": {
"actionId": {
"type": "keyword"
},
"apiKey": {
"type": "binary"
},
"params": {
"enabled": false,
"type": "object"
}
}
},
"alert": {
"properties": {
"actions": {
"properties": {
"actionRef": {
"type": "keyword"
},
"actionTypeId": {
"type": "keyword"
},
"group": {
"type": "keyword"
},
"params": {
"enabled": false,
"type": "object"
}
},
"type": "nested"
},
"alertTypeId": {
"type": "keyword"
},
"apiKey": {
"type": "binary"
},
"apiKeyOwner": {
"type": "keyword"
},
"consumer": {
"type": "keyword"
},
"createdAt": {
"type": "date"
},
"createdBy": {
"type": "keyword"
},
"enabled": {
"type": "boolean"
},
"muteAll": {
"type": "boolean"
},
"mutedInstanceIds": {
"type": "keyword"
},
"name": {
"fields": {
"keyword": {
"type": "keyword"
}
},
"type": "text"
},
"params": {
"enabled": false,
"type": "object"
},
"schedule": {
"properties": {
"interval": {
"type": "keyword"
}
}
},
"scheduledTaskId": {
"type": "keyword"
},
"tags": {
"type": "keyword"
},
"throttle": {
"type": "keyword"
},
"updatedBy": {
"type": "keyword"
}
}
},
"config": {
"dynamic": "true",
"properties": {
"buildNum": {
"type": "keyword"
}
}
},
"migrationVersion": {
"dynamic": "true",
"properties": {
"alert": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"config": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"space": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
}
}
},
"namespace": {
"type": "keyword"
},
"namespaces": {
"type": "keyword"
},
"query": {
"properties": {
"description": {
"type": "text"
},
"filters": {
"enabled": false,
"type": "object"
},
"query": {
"properties": {
"language": {
"type": "keyword"
},
"query": {
"index": false,
"type": "keyword"
}
}
},
"timefilter": {
"enabled": false,
"type": "object"
},
"title": {
"type": "text"
}
}
},
"references": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
},
"type": "nested"
},
"search": {
"properties": {
"columns": {
"type": "keyword"
},
"description": {
"type": "text"
},
"hits": {
"type": "integer"
},
"kibanaSavedObjectMeta": {
"properties": {
"searchSourceJSON": {
"type": "text"
}
}
},
"sort": {
"type": "keyword"
},
"title": {
"type": "text"
},
"version": {
"type": "integer"
}
}
},
"space": {
"properties": {
"_reserved": {
"type": "boolean"
},
"color": {
"type": "keyword"
},
"description": {
"type": "text"
},
"disabledFeatures": {
"type": "keyword"
},
"imageUrl": {
"index": false,
"type": "text"
},
"initials": {
"type": "keyword"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 2048,
"type": "keyword"
}
},
"type": "text"
}
}
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
}
}
},
"settings": {
"index": {
"auto_expand_replicas": "0-1",
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}
{
"type": "index",
"value": {
"aliases": {
".kibana_task_manager": {
}
},
"index": ".kibana_task_manager_1",
"mappings": {
"_meta": {
"migrationMappingPropertyHashes": {
"migrationVersion": "4a1746014a75ade3a714e1db5763276f",
"namespace": "2f4316de49999235636386fe51dc06c1",
"namespaces": "2f4316de49999235636386fe51dc06c1",
"originId": "2f4316de49999235636386fe51dc06c1",
"references": "7997cf5a56cc02bdc9c93361bde732b0",
"task": "235412e52d09e7165fac8a67a43ad6b4",
"type": "2f4316de49999235636386fe51dc06c1",
"updated_at": "00da57df13e94e9d98437d13ace4bfe0"
}
},
"dynamic": "strict",
"properties": {
"migrationVersion": {
"dynamic": "true",
"properties": {
"task": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
}
}
},
"references": {
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
},
"type": "nested"
},
"task": {
"properties": {
"attempts": {
"type": "integer"
},
"ownerId": {
"type": "keyword"
},
"params": {
"type": "text"
},
"retryAt": {
"type": "date"
},
"runAt": {
"type": "date"
},
"schedule": {
"properties": {
"interval": {
"type": "keyword"
}
}
},
"scheduledAt": {
"type": "date"
},
"scope": {
"type": "keyword"
},
"startedAt": {
"type": "date"
},
"state": {
"type": "text"
},
"status": {
"type": "keyword"
},
"taskType": {
"type": "keyword"
},
"user": {
"type": "keyword"
}
}
},
"type": {
"type": "keyword"
},
"updated_at": {
"type": "date"
}
}
},
"settings": {
"index": {
"auto_expand_replicas": "0-1",
"number_of_replicas": "0",
"number_of_shards": "1"
}
}
}
}