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

feat: [M3-7487] - Add AGLB Endpoint Health #10008

Merged
Merged
Show file tree
Hide file tree
Changes from 13 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
5 changes: 5 additions & 0 deletions packages/api-v4/.changeset/pr-10008-added-1704919102710.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Added
---
Copy link
Contributor

Choose a reason for hiding this comment

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

Just to confirm, this isn't an upcoming feature anymore, is it?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm going to keep considering it an upcoming feature until it's in public beta. We're probably a few weeks away from that

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm going to keep considering it an upcoming feature until it's in public beta.

In that case, should this changeset entry be under Upcoming Features rather than Added?

Copy link
Member Author

Choose a reason for hiding this comment

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

I think I wen't with added because api-v4 has no way to to "gate" features. Once the code is released, it's "added" to the package

Copy link
Member Author

Choose a reason for hiding this comment

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

Should it still be Upcoming Features?

Copy link
Contributor

Choose a reason for hiding this comment

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

Once the code is released, it's "added" to the package

Yeah, that's fair. I was thinking we'd been putting other AGLB api-v4 work under Upcoming, so suggested it for consistency, but I might be misremembering.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I'm not sure. Maybe api-v4 shouldn't have an Upcoming Features? I'll add a cafe item


AGLB endpoint health endpoints ([#10008](https://github.com/linode/manager/pull/10008))
18 changes: 18 additions & 0 deletions packages/api-v4/src/aglb/configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BETA_API_ROOT } from '../constants';
import type {
Configuration,
ConfigurationPayload,
ConfigurationsEndpointHealth,
UpdateConfigurationPayload,
} from './types';
import {
Expand Down Expand Up @@ -56,6 +57,23 @@ export const getLoadbalancerConfiguration = (
setMethod('GET')
);

/**
* getLoadbalancerConfigurationsEndpointHealth
*
* Returns endpoint health for an Akamai Global Load Balancer configuration
*/
export const getLoadbalancerConfigurationsEndpointHealth = (
loadbalancerId: number
) =>
Request<ConfigurationsEndpointHealth>(
setURL(
`${BETA_API_ROOT}/aglb/${encodeURIComponent(
loadbalancerId
)}/configurations/endpoints-health`
),
setMethod('GET')
);

/**
* createLoadbalancerConfiguration
*
Expand Down
12 changes: 12 additions & 0 deletions packages/api-v4/src/aglb/loadbalancers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Filter, Params, ResourcePage } from '../types';
import type {
CreateBasicLoadbalancerPayload,
CreateLoadbalancerPayload,
LoadBalancerEndpointHealth,
Loadbalancer,
UpdateLoadbalancerPayload,
} from './types';
Expand Down Expand Up @@ -39,6 +40,17 @@ export const getLoadbalancer = (id: number) =>
setMethod('GET')
);

/**
* getLoadbalancerEndpointHealth
*
* Returns the general endpoint health of an Akamai Global Load Balancer
*/
export const getLoadbalancerEndpointHealth = (id: number) =>
Request<LoadBalancerEndpointHealth>(
setURL(`${BETA_API_ROOT}/aglb/${encodeURIComponent(id)}/endpoints-health`),
setMethod('GET')
);

/**
* createLoadbalancer
*
Expand Down
21 changes: 20 additions & 1 deletion packages/api-v4/src/aglb/service-targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import Request, {
} from '../request';
import { Filter, Params, ResourcePage } from '../types';
import { BETA_API_ROOT } from '../constants';
import type { ServiceTarget, ServiceTargetPayload } from './types';
import type {
ServiceTarget,
ServiceTargetPayload,
ServiceTargetsEndpointHealth,
} from './types';
import {
CreateServiceTargetSchema,
UpdateServiceTargetSchema,
Expand Down Expand Up @@ -52,6 +56,21 @@ export const getServiceTarget = (
setMethod('GET')
);

/**
* getServiceTargetsEndpointHealth
*
* Returns endpoint health data for each service targets on an Akamai Global Load Balancer
*/
export const getServiceTargetsEndpointHealth = (loadbalancerId: number) =>
Request<ServiceTargetsEndpointHealth>(
setURL(
`${BETA_API_ROOT}/aglb/${encodeURIComponent(
loadbalancerId
)}/service-targets/endpoints-health`
),
setMethod('GET')
);

/**
* createLoadbalancerServiceTarget
*
Expand Down
39 changes: 39 additions & 0 deletions packages/api-v4/src/aglb/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,3 +201,42 @@ export interface UpdateCertificatePayload {
label?: string;
type?: CertificateType;
}

export interface LoadBalancerEndpointHealth {
id: number;
healthy_endpoints: number;
total_endpoints: number;
timestamp: string;
}

export interface EndpointHealth {
id: number;
label: string;
url: string;
type: string;
healthy_endpoints: number;
total_endpoints: number;
timestamp: string;
}

export interface ConfigurationsEndpointHealth {
/**
* The id of the AGLB
*/
id: number;
/**
* An array of health data for each configuration on the AGLB
*/
configurations: EndpointHealth[];
}

export interface ServiceTargetsEndpointHealth {
/**
* The id of the AGLB
*/
id: number;
/**
* An array of health data for each service target on the AGLB
*/
service_targets: EndpointHealth[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Added AGLB Endpoint Health ([#10008](https://github.com/linode/manager/pull/10008))
bnussman-akamai marked this conversation as resolved.
Show resolved Hide resolved
37 changes: 37 additions & 0 deletions packages/manager/src/factories/aglb.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import {
Certificate,
Configuration,
ConfigurationsEndpointHealth,
CreateCertificatePayload,
CreateLoadbalancerPayload,
CreateRoutePayload,
Endpoint,
EndpointHealth,
LoadBalancerEndpointHealth,
Loadbalancer,
Route,
ServiceTarget,
ServiceTargetPayload,
ServiceTargetsEndpointHealth,
UpdateLoadbalancerPayload,
} from '@linode/api-v4/lib/aglb/types';
import * as Factory from 'factory.ts';
Expand Down Expand Up @@ -337,3 +341,36 @@ export const endpointFactory = Factory.Sync.makeFactory<Endpoint>({
port: 80,
rate_capacity: 10_000,
});

export const loadbalancerEndpointHealthFactory = Factory.Sync.makeFactory<LoadBalancerEndpointHealth>(
{
healthy_endpoints: 4,
id: Factory.each((i) => i),
timestamp: '2020-01-31T12:00:00',
total_endpoints: 6,
}
);

export const endpointHealthFactory = Factory.Sync.makeFactory<EndpointHealth>({
healthy_endpoints: 4,
id: Factory.each((i) => i),
label: Factory.each((i) => `endpoint-${i}`),
timestamp: '2020-01-31T12:00:00',
total_endpoints: 6,
type: '',
url: '',
});

export const configurationsEndpointHealthFactory = Factory.Sync.makeFactory<ConfigurationsEndpointHealth>(
{
configurations: endpointHealthFactory.buildList(5),
id: Factory.each((i) => i),
}
);

export const serviceTargetsEndpointHealthFactory = Factory.Sync.makeFactory<ServiceTargetsEndpointHealth>(
{
id: Factory.each((i) => i),
service_targets: endpointHealthFactory.buildList(5),
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,20 @@ interface Props {

export const ConfigurationAccordion = (props: Props) => {
const { configuration } = props;
const { configurationId } = useParams<{ configurationId: string }>();
const { configurationId, loadbalancerId } = useParams<{
configurationId: string;
loadbalancerId: string;
}>();

return (
<Accordion
heading={
<ConfigurationAccordionHeader
configuration={configuration}
loadbalancerId={Number(loadbalancerId)}
/>
}
defaultExpanded={configuration.id === Number(configurationId)}
heading={<ConfigurationAccordionHeader configuration={configuration} />}
headingProps={{ sx: { width: '100%' } }}
>
<ConfigurationForm configuration={configuration} mode="edit" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React from 'react';

import {
configurationFactory,
configurationsEndpointHealthFactory,
endpointHealthFactory,
} from 'src/factories';
import { rest, server } from 'src/mocks/testServer';
import { renderWithTheme } from 'src/utilities/testHelpers';

import { ConfigurationAccordionHeader } from './ConfigurationAccordionHeader';

describe('ConfigurationAccordionHeader', () => {
it('renders configuration information', () => {
const configuration = configurationFactory.build({ routes: [] });

const { getByText } = renderWithTheme(
<ConfigurationAccordionHeader
configuration={configuration}
loadbalancerId={0}
/>
);

// Label
expect(getByText(configuration.label)).toBeVisible();

// ID
expect(getByText(`ID: ${configuration.id}`)).toBeVisible();

// Port
expect(
getByText(`Port ${configuration.port}`, { exact: false })
).toBeVisible();

// Number of Routes
expect(getByText('0 Routes', { exact: false })).toBeVisible();
});

it('renders endpoint health for a configuration', async () => {
const configuration = configurationFactory.build({ routes: [] });

const configurationHealth = endpointHealthFactory.build({
healthy_endpoints: 5,
id: configuration.id,
total_endpoints: 9,
});

const allConfigurationsEndpointHealth = configurationsEndpointHealthFactory.build(
{
configurations: [configurationHealth],
id: 5,
}
);

server.use(
rest.get(
'*/v4beta/aglb/5/configurations/endpoints-health',
(req, res, ctx) => res(ctx.json(allConfigurationsEndpointHealth))
)
);

const { findByText } = renderWithTheme(
<ConfigurationAccordionHeader
configuration={configuration}
loadbalancerId={5}
/>
);

await findByText('5 up');

await findByText('4 down'); // 9 - 5
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,24 @@ import React from 'react';

import { Stack } from 'src/components/Stack';
import { Typography } from 'src/components/Typography';
import { useLoadBalancerConfigurationsEndpointsHealth } from 'src/queries/aglb/configurations';
import { pluralize } from 'src/utilities/pluralize';

import { EndpointHealth } from '../EndpointHealth';

import type { Configuration } from '@linode/api-v4';

interface Props {
configuration: Configuration;
loadbalancerId: number;
}

export const ConfigurationAccordionHeader = ({ configuration }: Props) => {
export const ConfigurationAccordionHeader = (props: Props) => {
const { configuration, loadbalancerId } = props;
const { data } = useLoadBalancerConfigurationsEndpointsHealth(loadbalancerId);

const health = data?.configurations.find((c) => c.id === configuration.id);

return (
<Stack
alignItems="center"
Expand All @@ -28,7 +37,18 @@ export const ConfigurationAccordionHeader = ({ configuration }: Props) => {
{pluralize('Route', 'Routes', configuration.routes.length)}
</Typography>
</Stack>
<Typography>ID: {configuration.id}</Typography>
<Stack direction="row" spacing={2}>
<Stack direction="row" spacing={1}>
<Typography>Endpoints:</Typography>
{health && (
<EndpointHealth
down={health.total_endpoints - health.healthy_endpoints}
up={health.healthy_endpoints}
/>
)}
</Stack>
<Typography>ID: {configuration.id}</Typography>
</Stack>
</Stack>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';

import { renderWithTheme } from 'src/utilities/testHelpers';

import { EndpointHealth } from './EndpointHealth';

describe('EndpointHealth', () => {
it('renders endpoints that are up and down', () => {
const { getByText } = renderWithTheme(<EndpointHealth down={0} up={0} />);

expect(getByText('0 up')).toBeVisible();
expect(getByText('0 down')).toBeVisible();
});
it('renders endpoints that are up and down', () => {
const { getByLabelText, getByText } = renderWithTheme(
<EndpointHealth down={6} up={18} />,
{ theme: 'light' }
);

const upStatusIcon = getByLabelText('Status is active');
const downStatusIcon = getByLabelText('Status is error');

expect(upStatusIcon).toHaveStyle({ backgroundColor: '#17cf73' });
expect(downStatusIcon).toHaveStyle({ backgroundColor: '#ca0813' });
Comment on lines +23 to +24
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice!


expect(getByText('18 up')).toBeVisible();
expect(getByText('6 down')).toBeVisible();
});
it('should render gray when the "down" number is zero', () => {
const { getByLabelText, getByText } = renderWithTheme(
<EndpointHealth down={0} up={18} />
);

const statusIcon = getByLabelText('Status is inactive');

expect(statusIcon).toBeVisible();
expect(statusIcon).toHaveStyle({ backgroundColor: '#dbdde1' });
expect(getByText('0 down')).toBeVisible();
});
it('should render gray when the "up" number is zero', () => {
const { getByLabelText, getByText } = renderWithTheme(
<EndpointHealth down={8} up={0} />
);

const statusIcon = getByLabelText('Status is inactive');

expect(statusIcon).toBeVisible();
expect(statusIcon).toHaveStyle({ backgroundColor: '#dbdde1' });
expect(getByText('0 up')).toBeVisible();
});
});
Loading