Skip to content

Commit

Permalink
[Fleet] Implement active agent soft limit (#161289)
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet authored Jul 6, 2023
1 parent 2c01f7e commit 7709670
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 3 deletions.
4 changes: 3 additions & 1 deletion config/serverless.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)',
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface FleetConfigType {
disableILMPolicies: boolean;
disableProxies: boolean;
fleetServerStandalone: boolean;
activeAgentsSoftLimit?: number;
};
createArtifactsBulkBatchSize?: number;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@

export { AgentActivityFlyout } from './agent_activity_flyout';
export { AgentActivityButton } from './agent_activity_button';
export { AgentSoftLimitCallout } from './agent_soft_limit_callout';
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -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,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -520,6 +523,12 @@ export const AgentListPage: React.FunctionComponent<{}> = () => {
<EuiSpacer size="l" />
</>
)}
{shouldDisplayAgentSoftLimit && (
<>
<AgentSoftLimitCallout />
<EuiSpacer size="l" />
</>
)}
{/* TODO serverless agent soft limit */}
{showUnhealthyCallout && (
<>
Expand Down
9 changes: 8 additions & 1 deletion x-pack/plugins/fleet/public/mock/create_test_renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -59,6 +60,8 @@ export interface TestRenderer {
setHeaderActionMenu: Function;
}

const queryClient = new QueryClient();

export const createFleetTestRendererMock = (): TestRenderer => {
const basePath = '/mock';
const extensions: UIExtensionsStorage = {};
Expand All @@ -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>
);
Expand Down
6 changes: 6 additions & 0 deletions x-pack/plugins/fleet/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const config: PluginConfigDescriptor = {
internal: {
fleetServerStandalone: true,
disableProxies: true,
activeAgentsSoftLimit: true,
},
},
deprecations: ({ renameFromRoot, unused, unusedFromRoot }) => [
Expand Down Expand Up @@ -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 }),
Expand Down

0 comments on commit 7709670

Please sign in to comment.