mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[Fleet] Implement active agent soft limit (#161289)
This commit is contained in:
parent
2c01f7e290
commit
7709670d92
11 changed files with 168 additions and 3 deletions
|
@ -2,9 +2,11 @@ interactiveSetup.enabled: false
|
|||
newsfeed.enabled: false
|
||||
xpack.security.showNavLinks: false
|
||||
xpack.serverless.plugin.enabled: true
|
||||
# Fleet settings
|
||||
xpack.fleet.internal.fleetServerStandalone: true
|
||||
xpack.fleet.internal.disableILMPolicies: true
|
||||
xpack.fleet.internal.disableProxies: true
|
||||
xpack.fleet.internal.activeAgentsSoftLimit: 25000
|
||||
|
||||
# Enable ZDT migration algorithm
|
||||
migrations.algorithm: zdt
|
||||
|
@ -13,7 +15,7 @@ migrations.algorithm: zdt
|
|||
# until the controller is able to spawn the migrator job/pod
|
||||
migrations.zdt:
|
||||
metaPickupSyncDelaySec: 5
|
||||
runOnRoles: ["ui"]
|
||||
runOnRoles: ['ui']
|
||||
|
||||
# Ess plugins
|
||||
xpack.securitySolutionEss.enabled: false
|
||||
|
|
|
@ -221,6 +221,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
|
|||
'xpack.discoverEnhanced.actions.exploreDataInContextMenu.enabled (boolean)',
|
||||
'xpack.fleet.agents.enabled (boolean)',
|
||||
'xpack.fleet.enableExperimental (array)',
|
||||
'xpack.fleet.internal.activeAgentsSoftLimit (number)',
|
||||
'xpack.fleet.internal.disableProxies (boolean)',
|
||||
'xpack.fleet.internal.fleetServerStandalone (boolean)',
|
||||
'xpack.fleet.developer.maxAgentPoliciesWithInactivityTimeout (number)',
|
||||
|
|
|
@ -49,6 +49,7 @@ export interface FleetConfigType {
|
|||
disableILMPolicies: boolean;
|
||||
disableProxies: boolean;
|
||||
fleetServerStandalone: boolean;
|
||||
activeAgentsSoftLimit?: number;
|
||||
};
|
||||
createArtifactsBulkBatchSize?: number;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
import { FormattedMessage, FormattedNumber } from '@kbn/i18n-react';
|
||||
|
||||
import { useConfig } from '../../../../hooks';
|
||||
|
||||
export const AgentSoftLimitCallout = () => {
|
||||
const config = useConfig();
|
||||
|
||||
return (
|
||||
<EuiCallOut
|
||||
iconType="warning"
|
||||
color="warning"
|
||||
title={
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentSoftLimitCallout.calloutTitle"
|
||||
defaultMessage="Max number of online agents reached"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentSoftLimitCallout.calloutDescription"
|
||||
defaultMessage="Fleet supports a maximum of {nbAgents} active agents. You need to unenroll some agents to ensure that all active agents are able to connect and new agents can be enrolled."
|
||||
values={{
|
||||
nbAgents: <FormattedNumber value={config.internal?.activeAgentsSoftLimit ?? 25000} />,
|
||||
}}
|
||||
/>
|
||||
</EuiCallOut>
|
||||
);
|
||||
};
|
|
@ -7,3 +7,4 @@
|
|||
|
||||
export { AgentActivityFlyout } from './agent_activity_flyout';
|
||||
export { AgentActivityButton } from './agent_activity_button';
|
||||
export { AgentSoftLimitCallout } from './agent_soft_limit_callout';
|
||||
|
|
|
@ -9,3 +9,4 @@ export { useUpdateTags } from './use_update_tags';
|
|||
export { useActionStatus } from './use_action_status';
|
||||
export { useLastSeenInactiveAgentsCount } from './use_last_seen_inactive_agents_count';
|
||||
export { useInactiveAgentsCalloutHasBeenDismissed } from './use_inactive_agents_callout_has_been_dismissed';
|
||||
export { useAgentSoftLimit } from './use_agent_soft_limit';
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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 { createFleetTestRendererMock } from '../../../../../../mock';
|
||||
import { useConfig, sendGetAgents } from '../../../../hooks';
|
||||
|
||||
import { useAgentSoftLimit } from './use_agent_soft_limit';
|
||||
|
||||
jest.mock('../../../../hooks');
|
||||
|
||||
const mockedSendGetAgents = jest.mocked(sendGetAgents);
|
||||
const mockedUseConfig = jest.mocked(useConfig);
|
||||
|
||||
describe('useAgentSoftLimit', () => {
|
||||
beforeEach(() => {
|
||||
mockedSendGetAgents.mockReset();
|
||||
mockedUseConfig.mockReset();
|
||||
});
|
||||
it('should return shouldDisplayAgentSoftLimit:false if soft limit is not enabled in config', async () => {
|
||||
const renderer = createFleetTestRendererMock();
|
||||
mockedUseConfig.mockReturnValue({} as any);
|
||||
const { result } = renderer.renderHook(() => useAgentSoftLimit());
|
||||
|
||||
expect(result.current.shouldDisplayAgentSoftLimit).toEqual(false);
|
||||
|
||||
expect(mockedSendGetAgents).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should return shouldDisplayAgentSoftLimit:false if soft limit is enabled in config and there is less online agents than the limit', async () => {
|
||||
const renderer = createFleetTestRendererMock();
|
||||
mockedUseConfig.mockReturnValue({ internal: { activeAgentsSoftLimit: 10 } } as any);
|
||||
mockedSendGetAgents.mockResolvedValue({
|
||||
data: {
|
||||
total: 5,
|
||||
},
|
||||
} as any);
|
||||
const { result, waitForNextUpdate } = renderer.renderHook(() => useAgentSoftLimit());
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(mockedSendGetAgents).toBeCalled();
|
||||
expect(result.current.shouldDisplayAgentSoftLimit).toEqual(false);
|
||||
});
|
||||
|
||||
it('should return shouldDisplayAgentSoftLimit:true if soft limit is enabled in config and there is more online agents than the limit', async () => {
|
||||
const renderer = createFleetTestRendererMock();
|
||||
mockedUseConfig.mockReturnValue({ internal: { activeAgentsSoftLimit: 10 } } as any);
|
||||
mockedSendGetAgents.mockResolvedValue({
|
||||
data: {
|
||||
total: 15,
|
||||
},
|
||||
} as any);
|
||||
const { result, waitForNextUpdate } = renderer.renderHook(() => useAgentSoftLimit());
|
||||
await waitForNextUpdate();
|
||||
|
||||
expect(mockedSendGetAgents).toBeCalled();
|
||||
expect(result.current.shouldDisplayAgentSoftLimit).toEqual(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useConfig, sendGetAgents } from '../../../../hooks';
|
||||
|
||||
async function fetchTotalOnlineAgents() {
|
||||
const response = await sendGetAgents({
|
||||
kuery: 'status:online',
|
||||
perPage: 0,
|
||||
showInactive: false,
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw new Error(response.error.message);
|
||||
}
|
||||
|
||||
return response.data?.total ?? 0;
|
||||
}
|
||||
|
||||
export function useAgentSoftLimit() {
|
||||
const config = useConfig();
|
||||
|
||||
const softLimit = config.internal?.activeAgentsSoftLimit;
|
||||
|
||||
const { data: totalAgents } = useQuery(['fetch-total-online-agents'], fetchTotalOnlineAgents, {
|
||||
enabled: softLimit !== undefined,
|
||||
});
|
||||
|
||||
return {
|
||||
shouldDisplayAgentSoftLimit: softLimit && totalAgents ? totalAgents > softLimit : false,
|
||||
};
|
||||
}
|
|
@ -47,10 +47,11 @@ import { AgentTableHeader } from './components/table_header';
|
|||
import type { SelectionMode } from './components/types';
|
||||
import { SearchAndFilterBar } from './components/search_and_filter_bar';
|
||||
import { TagsAddRemove } from './components/tags_add_remove';
|
||||
import { AgentActivityFlyout } from './components';
|
||||
import { AgentActivityFlyout, AgentSoftLimitCallout } from './components';
|
||||
import { TableRowActions } from './components/table_row_actions';
|
||||
import { AgentListTable } from './components/agent_list_table';
|
||||
import { getKuery } from './utils/get_kuery';
|
||||
import { useAgentSoftLimit } from './hooks';
|
||||
|
||||
const REFRESH_INTERVAL_MS = 30000;
|
||||
|
||||
|
@ -396,6 +397,8 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
const { isFleetServerStandalone } = useFleetServerStandalone();
|
||||
const showUnhealthyCallout = isFleetServerUnhealthy && !isFleetServerStandalone;
|
||||
|
||||
const { shouldDisplayAgentSoftLimit } = useAgentSoftLimit();
|
||||
|
||||
const onClickAddFleetServer = useCallback(() => {
|
||||
flyoutContext.openFleetServerFlyout();
|
||||
}, [flyoutContext]);
|
||||
|
@ -520,6 +523,12 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
|
|||
<EuiSpacer size="l" />
|
||||
</>
|
||||
)}
|
||||
{shouldDisplayAgentSoftLimit && (
|
||||
<>
|
||||
<AgentSoftLimitCallout />
|
||||
<EuiSpacer size="l" />
|
||||
</>
|
||||
)}
|
||||
{/* TODO serverless agent soft limit */}
|
||||
{showUnhealthyCallout && (
|
||||
<>
|
||||
|
|
|
@ -13,6 +13,7 @@ import { render as reactRender, act } from '@testing-library/react';
|
|||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import type { RenderHookResult } from '@testing-library/react-hooks';
|
||||
import { Router } from '@kbn/shared-ux-router';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
import { themeServiceMock } from '@kbn/core/public/mocks';
|
||||
|
||||
|
@ -59,6 +60,8 @@ export interface TestRenderer {
|
|||
setHeaderActionMenu: Function;
|
||||
}
|
||||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
export const createFleetTestRendererMock = (): TestRenderer => {
|
||||
const basePath = '/mock';
|
||||
const extensions: UIExtensionsStorage = {};
|
||||
|
@ -72,7 +75,11 @@ export const createFleetTestRendererMock = (): TestRenderer => {
|
|||
return (
|
||||
<startServices.i18n.Context>
|
||||
<Router history={mountHistory}>
|
||||
<KibanaContextProvider services={{ ...startServices }}>{children}</KibanaContextProvider>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<KibanaContextProvider services={{ ...startServices }}>
|
||||
{children}
|
||||
</KibanaContextProvider>
|
||||
</QueryClientProvider>
|
||||
</Router>
|
||||
</startServices.i18n.Context>
|
||||
);
|
||||
|
|
|
@ -42,6 +42,7 @@ export const config: PluginConfigDescriptor = {
|
|||
internal: {
|
||||
fleetServerStandalone: true,
|
||||
disableProxies: true,
|
||||
activeAgentsSoftLimit: true,
|
||||
},
|
||||
},
|
||||
deprecations: ({ renameFromRoot, unused, unusedFromRoot }) => [
|
||||
|
@ -176,6 +177,11 @@ export const config: PluginConfigDescriptor = {
|
|||
fleetServerStandalone: schema.boolean({
|
||||
defaultValue: false,
|
||||
}),
|
||||
activeAgentsSoftLimit: schema.maybe(
|
||||
schema.number({
|
||||
min: 0,
|
||||
})
|
||||
),
|
||||
})
|
||||
),
|
||||
enabled: schema.boolean({ defaultValue: true }),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue