mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
# Backport This will backport the following commits from `main` to `8.7`: - [[Fleet] Bugfix: prevent status runtime query going over character limit (#150910)](https://github.com/elastic/kibana/pull/150910) <!--- Backport version: 8.9.7 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Mark Hopkin","email":"mark.hopkin@elastic.co"},"sourceCommit":{"committedDate":"2023-02-14T14:05:34Z","message":"[Fleet] Bugfix: prevent status runtime query going over character limit (#150910)\n\n## Summary\r\n\r\nCloses #150577 \r\n\r\nif there are too many agent policies in a system, we were creating too\r\nbig a runtime query for elastic and the query would be rejected.\r\n\r\nThis PR adds a limit, if the user has more than 750 agent policies then\r\nagents will not be marked as inactive anymore. If the user reaches the\r\nlimit then a warning badge is displayed (the text underlined has also\r\nbeen added):\r\n\r\n<img width=\"968\" alt=\"Screenshot 2023-02-13 at 20 14 31\"\r\nsrc=\"https://user-images.githubusercontent.com/3315046/218565456-f5758e4b-74f6-4e7c-9b49-22f0fd6f9102.png\">\r\n\r\n\r\nIntegration test added.","sha":"687294d4d30e936aa95b4a4c566b29c4462ab362","branchLabelMapping":{"^v8.8.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["bug","release_note:skip","Team:Fleet","v8.7.0","v8.8.0"],"number":150910,"url":"https://github.com/elastic/kibana/pull/150910","mergeCommit":{"message":"[Fleet] Bugfix: prevent status runtime query going over character limit (#150910)\n\n## Summary\r\n\r\nCloses #150577 \r\n\r\nif there are too many agent policies in a system, we were creating too\r\nbig a runtime query for elastic and the query would be rejected.\r\n\r\nThis PR adds a limit, if the user has more than 750 agent policies then\r\nagents will not be marked as inactive anymore. If the user reaches the\r\nlimit then a warning badge is displayed (the text underlined has also\r\nbeen added):\r\n\r\n<img width=\"968\" alt=\"Screenshot 2023-02-13 at 20 14 31\"\r\nsrc=\"https://user-images.githubusercontent.com/3315046/218565456-f5758e4b-74f6-4e7c-9b49-22f0fd6f9102.png\">\r\n\r\n\r\nIntegration test added.","sha":"687294d4d30e936aa95b4a4c566b29c4462ab362"}},"sourceBranch":"main","suggestedTargetBranches":["8.7"],"targetPullRequestStates":[{"branch":"8.7","label":"v8.7.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.8.0","labelRegex":"^v8.8.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/150910","number":150910,"mergeCommit":{"message":"[Fleet] Bugfix: prevent status runtime query going over character limit (#150910)\n\n## Summary\r\n\r\nCloses #150577 \r\n\r\nif there are too many agent policies in a system, we were creating too\r\nbig a runtime query for elastic and the query would be rejected.\r\n\r\nThis PR adds a limit, if the user has more than 750 agent policies then\r\nagents will not be marked as inactive anymore. If the user reaches the\r\nlimit then a warning badge is displayed (the text underlined has also\r\nbeen added):\r\n\r\n<img width=\"968\" alt=\"Screenshot 2023-02-13 at 20 14 31\"\r\nsrc=\"https://user-images.githubusercontent.com/3315046/218565456-f5758e4b-74f6-4e7c-9b49-22f0fd6f9102.png\">\r\n\r\n\r\nIntegration test added.","sha":"687294d4d30e936aa95b4a4c566b29c4462ab362"}}]}] BACKPORT--> Co-authored-by: Mark Hopkin <mark.hopkin@elastic.co>
This commit is contained in:
parent
28fb73eefe
commit
6050429f09
9 changed files with 239 additions and 251 deletions
|
@ -190,6 +190,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
'xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled (boolean)',
|
||||
'xpack.fleet.agents.enabled (boolean)',
|
||||
'xpack.fleet.enableExperimental (array)',
|
||||
'xpack.fleet.developer.maxAgentPoliciesWithInactivityTimeout (number)',
|
||||
'xpack.global_search.search_timeout (duration)',
|
||||
'xpack.graph.canEditDrillDownUrls (boolean)',
|
||||
'xpack.graph.savePolicy (alternatives)',
|
||||
|
|
|
@ -40,6 +40,7 @@ export interface FleetConfigType {
|
|||
agentPolicySchemaUpgradeBatchSize?: number;
|
||||
};
|
||||
developer?: {
|
||||
maxAgentPoliciesWithInactivityTimeout?: number;
|
||||
disableRegistryVersionCheck?: boolean;
|
||||
bundledPackageLocation?: string;
|
||||
};
|
||||
|
|
|
@ -24,13 +24,14 @@ import {
|
|||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiBetaBadge,
|
||||
EuiBadge,
|
||||
} from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { dataTypes } from '../../../../../../../common/constants';
|
||||
import { AGENT_POLICY_SAVED_OBJECT_TYPE, dataTypes } from '../../../../../../../common/constants';
|
||||
import type { NewAgentPolicy, AgentPolicy } from '../../../../types';
|
||||
import { useStartServices } from '../../../../hooks';
|
||||
import { useStartServices, useConfig, useGetAgentPolicies } from '../../../../hooks';
|
||||
|
||||
import { AgentPolicyPackageBadge } from '../../../../components';
|
||||
|
||||
|
@ -63,12 +64,26 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
|
|||
}) => {
|
||||
const { agentFqdnMode: agentFqdnModeEnabled } = ExperimentalFeaturesService.get();
|
||||
const { docLinks } = useStartServices();
|
||||
const config = useConfig();
|
||||
const maxAgentPoliciesWithInactivityTimeout =
|
||||
config.developer?.maxAgentPoliciesWithInactivityTimeout ?? 0;
|
||||
const [touchedFields, setTouchedFields] = useState<{ [key: string]: boolean }>({});
|
||||
const {
|
||||
dataOutputOptions,
|
||||
monitoringOutputOptions,
|
||||
isLoading: isLoadingOptions,
|
||||
} = useOutputOptions(agentPolicy);
|
||||
|
||||
const { data: agentPoliciesData } = useGetAgentPolicies({
|
||||
page: 1,
|
||||
perPage: 0,
|
||||
kuery: `${AGENT_POLICY_SAVED_OBJECT_TYPE}.inactivity_timeout:*`,
|
||||
});
|
||||
|
||||
const totalAgentPoliciesWithInactivityTimeout = agentPoliciesData?.total ?? 0;
|
||||
const tooManyAgentPoliciesForInactivityTimeout =
|
||||
maxAgentPoliciesWithInactivityTimeout !== undefined &&
|
||||
totalAgentPoliciesWithInactivityTimeout > (maxAgentPoliciesWithInactivityTimeout ?? 0);
|
||||
const { dataDownloadSourceOptions, isLoading: isLoadingDownloadSources } =
|
||||
useDownloadSourcesOptions(agentPolicy);
|
||||
|
||||
|
@ -273,12 +288,32 @@ export const AgentPolicyAdvancedOptionsContent: React.FunctionComponent<Props> =
|
|||
id="xpack.fleet.agentPolicyForm.inactivityTimeoutLabel"
|
||||
defaultMessage="Inactivity timeout"
|
||||
/>
|
||||
{tooManyAgentPoliciesForInactivityTimeout && (
|
||||
<>
|
||||
|
||||
<EuiToolTip
|
||||
content={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.inactivityTimeoutTooltip"
|
||||
defaultMessage="The maximum of 750 agent policies with an inactivity timeout has been exceeded. Remove inactivity timeouts or agent policies to allow agents to become inactive again."
|
||||
/>
|
||||
}
|
||||
>
|
||||
<EuiBadge color="warning">
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.inactivityTimeoutBadge"
|
||||
defaultMessage="Warning"
|
||||
/>
|
||||
</EuiBadge>
|
||||
</EuiToolTip>
|
||||
</>
|
||||
)}
|
||||
</h4>
|
||||
}
|
||||
description={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.inactivityTimeoutDescription"
|
||||
defaultMessage="An optional timeout in seconds. If provided, an agent will automatically change to inactive status and be filtered out of the agents list."
|
||||
defaultMessage="An optional timeout in seconds. If provided, an agent will automatically change to inactive status and be filtered out of the agents list. A maximum of 750 agent policies can have an inactivity timeout."
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
|
|
@ -35,6 +35,9 @@ export const config: PluginConfigDescriptor = {
|
|||
enabled: true,
|
||||
},
|
||||
enableExperimental: true,
|
||||
developer: {
|
||||
maxAgentPoliciesWithInactivityTimeout: true,
|
||||
},
|
||||
},
|
||||
deprecations: ({ renameFromRoot, unused, unusedFromRoot }) => [
|
||||
// Unused settings before Fleet server exists
|
||||
|
@ -126,6 +129,7 @@ export const config: PluginConfigDescriptor = {
|
|||
})
|
||||
),
|
||||
developer: schema.object({
|
||||
maxAgentPoliciesWithInactivityTimeout: schema.maybe(schema.number()),
|
||||
disableRegistryVersionCheck: schema.boolean({ defaultValue: false }),
|
||||
allowAgentUpgradeSourceUri: schema.boolean({ defaultValue: false }),
|
||||
bundledPackageLocation: schema.string({ defaultValue: DEFAULT_BUNDLED_PACKAGE_LOCATION }),
|
||||
|
|
|
@ -15,54 +15,13 @@ describe('buildStatusRuntimeField', () => {
|
|||
});
|
||||
it('should build the correct runtime field if there are no inactivity timeouts', () => {
|
||||
const inactivityTimeouts: InactivityTimeouts = [];
|
||||
const runtimeField = _buildStatusRuntimeField(inactivityTimeouts);
|
||||
const runtimeField = _buildStatusRuntimeField({ inactivityTimeouts });
|
||||
expect(runtimeField).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"status": Object {
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "
|
||||
long lastCheckinMillis = doc['last_checkin'].size() > 0
|
||||
? doc['last_checkin'].value.toInstant().toEpochMilli()
|
||||
: (
|
||||
doc['enrolled_at'].size() > 0
|
||||
? doc['enrolled_at'].value.toInstant().toEpochMilli()
|
||||
: -1
|
||||
);
|
||||
if (doc['active'].size() > 0 && doc['active'].value == false) {
|
||||
emit('unenrolled');
|
||||
} else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && false) {
|
||||
emit('inactive');
|
||||
} else if (
|
||||
lastCheckinMillis > 0
|
||||
&& lastCheckinMillis
|
||||
< (1234567590123L)
|
||||
) {
|
||||
emit('offline');
|
||||
} else if (
|
||||
doc['policy_revision_idx'].size() == 0 || (
|
||||
doc['upgrade_started_at'].size() > 0 &&
|
||||
doc['upgraded_at'].size() == 0
|
||||
)
|
||||
) {
|
||||
emit('updating');
|
||||
} else if (doc['last_checkin'].size() == 0) {
|
||||
emit('enrolling');
|
||||
} else if (doc['unenrollment_started_at'].size() > 0) {
|
||||
emit('unenrolling');
|
||||
} else if (
|
||||
doc['last_checkin_status'].size() > 0 &&
|
||||
doc['last_checkin_status'].value.toLowerCase() == 'error'
|
||||
) {
|
||||
emit('error');
|
||||
} else if (
|
||||
doc['last_checkin_status'].size() > 0 &&
|
||||
doc['last_checkin_status'].value.toLowerCase() == 'degraded'
|
||||
) {
|
||||
emit('degraded');
|
||||
} else {
|
||||
emit('online');
|
||||
}",
|
||||
"source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }",
|
||||
},
|
||||
"type": "keyword",
|
||||
},
|
||||
|
@ -71,54 +30,13 @@ describe('buildStatusRuntimeField', () => {
|
|||
});
|
||||
it('should build the correct runtime field if there are no inactivity timeouts (prefix)', () => {
|
||||
const inactivityTimeouts: InactivityTimeouts = [];
|
||||
const runtimeField = _buildStatusRuntimeField(inactivityTimeouts, 'my.prefix.');
|
||||
const runtimeField = _buildStatusRuntimeField({ inactivityTimeouts, pathPrefix: 'my.prefix.' });
|
||||
expect(runtimeField).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"status": Object {
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "
|
||||
long lastCheckinMillis = doc['my.prefix.last_checkin'].size() > 0
|
||||
? doc['my.prefix.last_checkin'].value.toInstant().toEpochMilli()
|
||||
: (
|
||||
doc['my.prefix.enrolled_at'].size() > 0
|
||||
? doc['my.prefix.enrolled_at'].value.toInstant().toEpochMilli()
|
||||
: -1
|
||||
);
|
||||
if (doc['my.prefix.active'].size() > 0 && doc['my.prefix.active'].value == false) {
|
||||
emit('unenrolled');
|
||||
} else if (lastCheckinMillis > 0 && doc['my.prefix.policy_id'].size() > 0 && false) {
|
||||
emit('inactive');
|
||||
} else if (
|
||||
lastCheckinMillis > 0
|
||||
&& lastCheckinMillis
|
||||
< (1234567590123L)
|
||||
) {
|
||||
emit('offline');
|
||||
} else if (
|
||||
doc['my.prefix.policy_revision_idx'].size() == 0 || (
|
||||
doc['my.prefix.upgrade_started_at'].size() > 0 &&
|
||||
doc['my.prefix.upgraded_at'].size() == 0
|
||||
)
|
||||
) {
|
||||
emit('updating');
|
||||
} else if (doc['my.prefix.last_checkin'].size() == 0) {
|
||||
emit('enrolling');
|
||||
} else if (doc['my.prefix.unenrollment_started_at'].size() > 0) {
|
||||
emit('unenrolling');
|
||||
} else if (
|
||||
doc['my.prefix.last_checkin_status'].size() > 0 &&
|
||||
doc['my.prefix.last_checkin_status'].value.toLowerCase() == 'error'
|
||||
) {
|
||||
emit('error');
|
||||
} else if (
|
||||
doc['my.prefix.last_checkin_status'].size() > 0 &&
|
||||
doc['my.prefix.last_checkin_status'].value.toLowerCase() == 'degraded'
|
||||
) {
|
||||
emit('degraded');
|
||||
} else {
|
||||
emit('online');
|
||||
}",
|
||||
"source": " long lastCheckinMillis = doc['my.prefix.last_checkin'].size() > 0 ? doc['my.prefix.last_checkin'].value.toInstant().toEpochMilli() : ( doc['my.prefix.enrolled_at'].size() > 0 ? doc['my.prefix.enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['my.prefix.active'].size() > 0 && doc['my.prefix.active'].value == false) { emit('unenrolled'); } else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['my.prefix.policy_revision_idx'].size() == 0 || ( doc['my.prefix.upgrade_started_at'].size() > 0 && doc['my.prefix.upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['my.prefix.last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['my.prefix.unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['my.prefix.last_checkin_status'].size() > 0 && doc['my.prefix.last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['my.prefix.last_checkin_status'].size() > 0 && doc['my.prefix.last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }",
|
||||
},
|
||||
"type": "keyword",
|
||||
},
|
||||
|
@ -132,54 +50,13 @@ describe('buildStatusRuntimeField', () => {
|
|||
policyIds: ['policy-1'],
|
||||
},
|
||||
];
|
||||
const runtimeField = _buildStatusRuntimeField(inactivityTimeouts);
|
||||
const runtimeField = _buildStatusRuntimeField({ inactivityTimeouts });
|
||||
expect(runtimeField).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"status": Object {
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "
|
||||
long lastCheckinMillis = doc['last_checkin'].size() > 0
|
||||
? doc['last_checkin'].value.toInstant().toEpochMilli()
|
||||
: (
|
||||
doc['enrolled_at'].size() > 0
|
||||
? doc['enrolled_at'].value.toInstant().toEpochMilli()
|
||||
: -1
|
||||
);
|
||||
if (doc['active'].size() > 0 && doc['active'].value == false) {
|
||||
emit('unenrolled');
|
||||
} else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && (doc['policy_id'].value == 'policy-1') && lastCheckinMillis < 1234567590123L) {
|
||||
emit('inactive');
|
||||
} else if (
|
||||
lastCheckinMillis > 0
|
||||
&& lastCheckinMillis
|
||||
< (1234567590123L)
|
||||
) {
|
||||
emit('offline');
|
||||
} else if (
|
||||
doc['policy_revision_idx'].size() == 0 || (
|
||||
doc['upgrade_started_at'].size() > 0 &&
|
||||
doc['upgraded_at'].size() == 0
|
||||
)
|
||||
) {
|
||||
emit('updating');
|
||||
} else if (doc['last_checkin'].size() == 0) {
|
||||
emit('enrolling');
|
||||
} else if (doc['unenrollment_started_at'].size() > 0) {
|
||||
emit('unenrolling');
|
||||
} else if (
|
||||
doc['last_checkin_status'].size() > 0 &&
|
||||
doc['last_checkin_status'].value.toLowerCase() == 'error'
|
||||
) {
|
||||
emit('error');
|
||||
} else if (
|
||||
doc['last_checkin_status'].size() > 0 &&
|
||||
doc['last_checkin_status'].value.toLowerCase() == 'degraded'
|
||||
) {
|
||||
emit('degraded');
|
||||
} else {
|
||||
emit('online');
|
||||
}",
|
||||
"source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && ['policy-1'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567590123L) {emit('inactive');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }",
|
||||
},
|
||||
"type": "keyword",
|
||||
},
|
||||
|
@ -193,54 +70,37 @@ describe('buildStatusRuntimeField', () => {
|
|||
policyIds: ['policy-1', 'policy-2'],
|
||||
},
|
||||
];
|
||||
const runtimeField = _buildStatusRuntimeField(inactivityTimeouts);
|
||||
const runtimeField = _buildStatusRuntimeField({ inactivityTimeouts });
|
||||
expect(runtimeField).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"status": Object {
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "
|
||||
long lastCheckinMillis = doc['last_checkin'].size() > 0
|
||||
? doc['last_checkin'].value.toInstant().toEpochMilli()
|
||||
: (
|
||||
doc['enrolled_at'].size() > 0
|
||||
? doc['enrolled_at'].value.toInstant().toEpochMilli()
|
||||
: -1
|
||||
);
|
||||
if (doc['active'].size() > 0 && doc['active'].value == false) {
|
||||
emit('unenrolled');
|
||||
} else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && (doc['policy_id'].value == 'policy-1' || doc['policy_id'].value == 'policy-2') && lastCheckinMillis < 1234567590123L) {
|
||||
emit('inactive');
|
||||
} else if (
|
||||
lastCheckinMillis > 0
|
||||
&& lastCheckinMillis
|
||||
< (1234567590123L)
|
||||
) {
|
||||
emit('offline');
|
||||
} else if (
|
||||
doc['policy_revision_idx'].size() == 0 || (
|
||||
doc['upgrade_started_at'].size() > 0 &&
|
||||
doc['upgraded_at'].size() == 0
|
||||
)
|
||||
) {
|
||||
emit('updating');
|
||||
} else if (doc['last_checkin'].size() == 0) {
|
||||
emit('enrolling');
|
||||
} else if (doc['unenrollment_started_at'].size() > 0) {
|
||||
emit('unenrolling');
|
||||
} else if (
|
||||
doc['last_checkin_status'].size() > 0 &&
|
||||
doc['last_checkin_status'].value.toLowerCase() == 'error'
|
||||
) {
|
||||
emit('error');
|
||||
} else if (
|
||||
doc['last_checkin_status'].size() > 0 &&
|
||||
doc['last_checkin_status'].value.toLowerCase() == 'degraded'
|
||||
) {
|
||||
emit('degraded');
|
||||
} else {
|
||||
emit('online');
|
||||
}",
|
||||
"source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && ['policy-1','policy-2'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567590123L) {emit('inactive');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }",
|
||||
},
|
||||
"type": "keyword",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
it('should not perform inactivity check if there are too many agent policies with an inactivity timeout', () => {
|
||||
const inactivityTimeouts: InactivityTimeouts = [
|
||||
{
|
||||
inactivityTimeout: 300,
|
||||
// default max is 750
|
||||
policyIds: new Array(1000).fill(0).map((_, i) => `policy-${i}`),
|
||||
},
|
||||
];
|
||||
|
||||
const runtimeField = _buildStatusRuntimeField({ inactivityTimeouts });
|
||||
|
||||
expect(runtimeField).not.toContain('policy-');
|
||||
expect(runtimeField).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"status": Object {
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }",
|
||||
},
|
||||
"type": "keyword",
|
||||
},
|
||||
|
@ -258,54 +118,13 @@ describe('buildStatusRuntimeField', () => {
|
|||
policyIds: ['policy-3'],
|
||||
},
|
||||
];
|
||||
const runtimeField = _buildStatusRuntimeField(inactivityTimeouts);
|
||||
const runtimeField = _buildStatusRuntimeField({ inactivityTimeouts });
|
||||
expect(runtimeField).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"status": Object {
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "
|
||||
long lastCheckinMillis = doc['last_checkin'].size() > 0
|
||||
? doc['last_checkin'].value.toInstant().toEpochMilli()
|
||||
: (
|
||||
doc['enrolled_at'].size() > 0
|
||||
? doc['enrolled_at'].value.toInstant().toEpochMilli()
|
||||
: -1
|
||||
);
|
||||
if (doc['active'].size() > 0 && doc['active'].value == false) {
|
||||
emit('unenrolled');
|
||||
} else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && (doc['policy_id'].value == 'policy-1' || doc['policy_id'].value == 'policy-2') && lastCheckinMillis < 1234567590123L || (doc['policy_id'].value == 'policy-3') && lastCheckinMillis < 1234567490123L) {
|
||||
emit('inactive');
|
||||
} else if (
|
||||
lastCheckinMillis > 0
|
||||
&& lastCheckinMillis
|
||||
< (1234567590123L)
|
||||
) {
|
||||
emit('offline');
|
||||
} else if (
|
||||
doc['policy_revision_idx'].size() == 0 || (
|
||||
doc['upgrade_started_at'].size() > 0 &&
|
||||
doc['upgraded_at'].size() == 0
|
||||
)
|
||||
) {
|
||||
emit('updating');
|
||||
} else if (doc['last_checkin'].size() == 0) {
|
||||
emit('enrolling');
|
||||
} else if (doc['unenrollment_started_at'].size() > 0) {
|
||||
emit('unenrolling');
|
||||
} else if (
|
||||
doc['last_checkin_status'].size() > 0 &&
|
||||
doc['last_checkin_status'].value.toLowerCase() == 'error'
|
||||
) {
|
||||
emit('error');
|
||||
} else if (
|
||||
doc['last_checkin_status'].size() > 0 &&
|
||||
doc['last_checkin_status'].value.toLowerCase() == 'degraded'
|
||||
) {
|
||||
emit('degraded');
|
||||
} else {
|
||||
emit('online');
|
||||
}",
|
||||
"source": " long lastCheckinMillis = doc['last_checkin'].size() > 0 ? doc['last_checkin'].value.toInstant().toEpochMilli() : ( doc['enrolled_at'].size() > 0 ? doc['enrolled_at'].value.toInstant().toEpochMilli() : -1 ); if (doc['active'].size() > 0 && doc['active'].value == false) { emit('unenrolled'); } else if (lastCheckinMillis > 0 && doc['policy_id'].size() > 0 && ['policy-1','policy-2'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567590123L || ['policy-3'].contains(doc['policy_id'].value) && lastCheckinMillis < 1234567490123L) {emit('inactive');} else if ( lastCheckinMillis > 0 && lastCheckinMillis < 1234567590123L ) { emit('offline'); } else if ( doc['policy_revision_idx'].size() == 0 || ( doc['upgrade_started_at'].size() > 0 && doc['upgraded_at'].size() == 0 ) ) { emit('updating'); } else if (doc['last_checkin'].size() == 0) { emit('enrolling'); } else if (doc['unenrollment_started_at'].size() > 0) { emit('unenrolling'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'error' ) { emit('error'); } else if ( doc['last_checkin_status'].size() > 0 && doc['last_checkin_status'].value.toLowerCase() == 'degraded' ) { emit('degraded'); } else { emit('online'); }",
|
||||
},
|
||||
"type": "keyword",
|
||||
},
|
||||
|
@ -314,8 +133,14 @@ describe('buildStatusRuntimeField', () => {
|
|||
});
|
||||
it('should build the same runtime field if path ends with. or not', () => {
|
||||
const inactivityTimeouts: InactivityTimeouts = [];
|
||||
const runtimeFieldWithDot = _buildStatusRuntimeField(inactivityTimeouts, 'my.prefix.');
|
||||
const runtimeFieldNoDot = _buildStatusRuntimeField(inactivityTimeouts, 'my.prefix');
|
||||
const runtimeFieldWithDot = _buildStatusRuntimeField({
|
||||
inactivityTimeouts,
|
||||
pathPrefix: 'my.prefix.',
|
||||
});
|
||||
const runtimeFieldNoDot = _buildStatusRuntimeField({
|
||||
inactivityTimeouts,
|
||||
pathPrefix: 'my.prefix',
|
||||
});
|
||||
expect(runtimeFieldWithDot).toEqual(runtimeFieldNoDot);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,41 +7,88 @@
|
|||
|
||||
import type * as estypes from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core/server';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
|
||||
import { AGENT_POLLING_THRESHOLD_MS } from '../../constants';
|
||||
import { agentPolicyService } from '../agent_policy';
|
||||
import { appContextService } from '../app_context';
|
||||
const MISSED_INTERVALS_BEFORE_OFFLINE = 10;
|
||||
const MS_BEFORE_OFFLINE = MISSED_INTERVALS_BEFORE_OFFLINE * AGENT_POLLING_THRESHOLD_MS;
|
||||
|
||||
const DEFAULT_MAX_AGENT_POLICIES_WITH_INACTIVITY_TIMEOUT = 750;
|
||||
export type InactivityTimeouts = Awaited<
|
||||
ReturnType<typeof agentPolicyService['getInactivityTimeouts']>
|
||||
>;
|
||||
|
||||
const _buildInactiveClause = (
|
||||
now: number,
|
||||
inactivityTimeouts: InactivityTimeouts,
|
||||
field: (path: string) => string
|
||||
) => {
|
||||
let inactivityTimeoutsDisabled = false;
|
||||
const _buildInactiveCondition = (opts: {
|
||||
now: number;
|
||||
inactivityTimeouts: InactivityTimeouts;
|
||||
maxAgentPoliciesWithInactivityTimeout: number;
|
||||
field: (path: string) => string;
|
||||
logger?: Logger;
|
||||
}): string | null => {
|
||||
const { now, inactivityTimeouts, maxAgentPoliciesWithInactivityTimeout, field, logger } = opts;
|
||||
// if there are no policies with inactivity timeouts, then no agents are inactive
|
||||
if (inactivityTimeouts.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const totalAgentPoliciesWithInactivityTimeouts = inactivityTimeouts.reduce(
|
||||
(total, { policyIds }) => total + policyIds.length,
|
||||
0
|
||||
);
|
||||
|
||||
// if too many agent policies have inactivity timeouts, then we can't use the inactivity timeout
|
||||
// as the query becomes too large see github.com/elastic/kibana/issues/150577
|
||||
if (totalAgentPoliciesWithInactivityTimeouts > maxAgentPoliciesWithInactivityTimeout) {
|
||||
if (!inactivityTimeoutsDisabled) {
|
||||
// only log this once as this function is executed a lot
|
||||
logger?.warn(
|
||||
`There are ${totalAgentPoliciesWithInactivityTimeouts} agent policies with an inactivity timeout set but the maximum allowed is ${maxAgentPoliciesWithInactivityTimeout}. Agents will not be marked as inactive.`
|
||||
);
|
||||
inactivityTimeoutsDisabled = true;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inactivityTimeoutsDisabled) {
|
||||
logger?.info(
|
||||
`There are ${totalAgentPoliciesWithInactivityTimeouts} agent policies which is now below the maximum allowed of ${maxAgentPoliciesWithInactivityTimeout}. Agents will now be marked as inactive again.`
|
||||
);
|
||||
inactivityTimeoutsDisabled = false;
|
||||
}
|
||||
|
||||
const policyClauses = inactivityTimeouts
|
||||
.map(({ inactivityTimeout, policyIds }) => {
|
||||
const inactivityTimeoutMs = inactivityTimeout * 1000;
|
||||
const policyOrs = policyIds
|
||||
.map((policyId) => `${field('policy_id')}.value == '${policyId}'`)
|
||||
.join(' || ');
|
||||
const policyIdMatches = `[${policyIds.map((id) => `'${id}'`).join(',')}].contains(${field(
|
||||
'policy_id'
|
||||
)}.value)`;
|
||||
|
||||
return `(${policyOrs}) && lastCheckinMillis < ${now - inactivityTimeoutMs}L`;
|
||||
return `${policyIdMatches} && lastCheckinMillis < ${now - inactivityTimeoutMs}L`;
|
||||
})
|
||||
.join(' || ');
|
||||
|
||||
const agentIsInactive = policyClauses.length ? `${policyClauses}` : 'false'; // if no policies have inactivity timeouts, then no agents are inactive
|
||||
|
||||
return `lastCheckinMillis > 0 && ${field('policy_id')}.size() > 0 && ${agentIsInactive}`;
|
||||
return `lastCheckinMillis > 0 && ${field('policy_id')}.size() > 0 && ${policyClauses}`;
|
||||
};
|
||||
|
||||
function _buildSource(inactivityTimeouts: InactivityTimeouts, pathPrefix?: string) {
|
||||
function _buildSource(
|
||||
inactivityTimeouts: InactivityTimeouts,
|
||||
maxAgentPoliciesWithInactivityTimeout: number,
|
||||
pathPrefix?: string,
|
||||
logger?: Logger
|
||||
) {
|
||||
const normalizedPrefix = pathPrefix ? `${pathPrefix}${pathPrefix.endsWith('.') ? '' : '.'}` : '';
|
||||
const field = (path: string) => `doc['${normalizedPrefix + path}']`;
|
||||
const now = Date.now();
|
||||
const agentIsInactiveCondition = _buildInactiveCondition({
|
||||
now,
|
||||
inactivityTimeouts,
|
||||
maxAgentPoliciesWithInactivityTimeout,
|
||||
field,
|
||||
logger,
|
||||
});
|
||||
|
||||
return `
|
||||
long lastCheckinMillis = ${field('last_checkin')}.size() > 0
|
||||
? ${field('last_checkin')}.value.toInstant().toEpochMilli()
|
||||
|
@ -52,12 +99,11 @@ function _buildSource(inactivityTimeouts: InactivityTimeouts, pathPrefix?: strin
|
|||
);
|
||||
if (${field('active')}.size() > 0 && ${field('active')}.value == false) {
|
||||
emit('unenrolled');
|
||||
} else if (${_buildInactiveClause(now, inactivityTimeouts, field)}) {
|
||||
emit('inactive');
|
||||
} else if (
|
||||
} ${agentIsInactiveCondition ? `else if (${agentIsInactiveCondition}) {emit('inactive');}` : ''}
|
||||
else if (
|
||||
lastCheckinMillis > 0
|
||||
&& lastCheckinMillis
|
||||
< (${now - MS_BEFORE_OFFLINE}L)
|
||||
< ${now - MS_BEFORE_OFFLINE}L
|
||||
) {
|
||||
emit('offline');
|
||||
} else if (
|
||||
|
@ -83,15 +129,28 @@ function _buildSource(inactivityTimeouts: InactivityTimeouts, pathPrefix?: strin
|
|||
emit('degraded');
|
||||
} else {
|
||||
emit('online');
|
||||
}`;
|
||||
}`.replace(/\s{2,}/g, ' '); // replace newlines and double spaces to save characters
|
||||
}
|
||||
|
||||
// exported for testing
|
||||
export function _buildStatusRuntimeField(
|
||||
inactivityTimeouts: InactivityTimeouts,
|
||||
pathPrefix?: string
|
||||
): NonNullable<estypes.SearchRequest['runtime_mappings']> {
|
||||
const source = _buildSource(inactivityTimeouts, pathPrefix);
|
||||
export function _buildStatusRuntimeField(opts: {
|
||||
inactivityTimeouts: InactivityTimeouts;
|
||||
maxAgentPoliciesWithInactivityTimeout?: number;
|
||||
pathPrefix?: string;
|
||||
logger?: Logger;
|
||||
}): NonNullable<estypes.SearchRequest['runtime_mappings']> {
|
||||
const {
|
||||
inactivityTimeouts,
|
||||
maxAgentPoliciesWithInactivityTimeout = DEFAULT_MAX_AGENT_POLICIES_WITH_INACTIVITY_TIMEOUT,
|
||||
pathPrefix,
|
||||
logger,
|
||||
} = opts;
|
||||
const source = _buildSource(
|
||||
inactivityTimeouts,
|
||||
maxAgentPoliciesWithInactivityTimeout,
|
||||
pathPrefix,
|
||||
logger
|
||||
);
|
||||
return {
|
||||
status: {
|
||||
type: 'keyword',
|
||||
|
@ -111,7 +170,23 @@ export async function buildAgentStatusRuntimeField(
|
|||
soClient: SavedObjectsClientContract,
|
||||
pathPrefix?: string
|
||||
) {
|
||||
const config = appContextService.getConfig();
|
||||
|
||||
let logger: Logger | undefined;
|
||||
try {
|
||||
logger = appContextService.getLogger();
|
||||
} catch (e) {
|
||||
// ignore, logger is optional
|
||||
// this code can be used and tested without an app context
|
||||
}
|
||||
const maxAgentPoliciesWithInactivityTimeout =
|
||||
config?.developer?.maxAgentPoliciesWithInactivityTimeout;
|
||||
const inactivityTimeouts = await agentPolicyService.getInactivityTimeouts(soClient);
|
||||
|
||||
return _buildStatusRuntimeField(inactivityTimeouts, pathPrefix);
|
||||
return _buildStatusRuntimeField({
|
||||
inactivityTimeouts,
|
||||
maxAgentPoliciesWithInactivityTimeout,
|
||||
pathPrefix,
|
||||
logger,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,14 +16,11 @@ import { updateAgentTags } from './update_agent_tags';
|
|||
import { UpdateAgentTagsActionRunner, updateTagsBatch } from './update_agent_tags_action_runner';
|
||||
|
||||
jest.mock('../app_context', () => {
|
||||
const { loggerMock } = jest.requireActual('@kbn/logging-mocks');
|
||||
return {
|
||||
appContextService: {
|
||||
getLogger: jest.fn().mockReturnValue({
|
||||
debug: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
} as any),
|
||||
getLogger: () => loggerMock.create(),
|
||||
getConfig: () => {},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -257,5 +257,54 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
statusCode: 403,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not perform inactivity check if there are too many agent policies with inactivity timeout', async () => {
|
||||
// the test server is started with --xpack.fleet.developer.maxAgentPoliciesWithInactivityTimeout=10
|
||||
// so we create 11 policies with inactivity timeout then no agents should turn inactive
|
||||
|
||||
const policiesToAdd = new Array(11).fill(0).map((_, i) => `policy-inactivity-timeout-${i}`);
|
||||
|
||||
await Promise.all(
|
||||
policiesToAdd.map((policyId) =>
|
||||
es.create({
|
||||
id: 'ingest-agent-policies:' + policyId,
|
||||
index: '.kibana',
|
||||
refresh: 'wait_for',
|
||||
document: {
|
||||
type: 'ingest-agent-policies',
|
||||
'ingest-agent-policies': {
|
||||
name: policyId,
|
||||
namespace: 'default',
|
||||
description: 'Policy with inactivity timeout',
|
||||
status: 'active',
|
||||
is_default: true,
|
||||
monitoring_enabled: ['logs', 'metrics'],
|
||||
revision: 2,
|
||||
updated_at: '2020-05-07T19:34:42.533Z',
|
||||
updated_by: 'system',
|
||||
inactivity_timeout: 60,
|
||||
},
|
||||
migrationVersion: {
|
||||
'ingest-agent-policies': '7.10.0',
|
||||
},
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
const { body: apiResponse } = await supertest.get(`/api/fleet/agent_status`).expect(200);
|
||||
expect(apiResponse).to.eql({
|
||||
results: {
|
||||
events: 0,
|
||||
other: 0,
|
||||
total: 10,
|
||||
online: 3,
|
||||
error: 2,
|
||||
offline: 1,
|
||||
updating: 4,
|
||||
inactive: 0,
|
||||
unenrolled: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
...(registryPort ? [`--xpack.fleet.registryUrl=http://localhost:${registryPort}`] : []),
|
||||
`--xpack.fleet.developer.bundledPackageLocation=${BUNDLED_PACKAGE_DIR}`,
|
||||
'--xpack.cloudSecurityPosture.enabled=true',
|
||||
`--xpack.fleet.developer.maxAgentPoliciesWithInactivityTimeout=10`,
|
||||
`--xpack.fleet.packageVerification.gpgKeyPath=${getFullPath(
|
||||
'./apis/fixtures/package_verification/signatures/fleet_test_key_public.asc'
|
||||
)}`,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue