Skip to content

Commit

Permalink
feat: [M3-7390] - Add AGLB protocol to Service Targets (#9891)
Browse files Browse the repository at this point in the history
* initial changes

* fix e2e and validation schemas

* Added changeset: Add `protocol` to `ServiceTargetPayload`

* Added changeset: Change `ca_certificate` to `certificate_id` in `CreateServiceTargetSchema` and `UpdateServiceTargetSchema`

* validation changes and changesets

* fix incorrect schema

* feedback

---------

Co-authored-by: Banks Nussman <[email protected]>
  • Loading branch information
bnussman-akamai and bnussman authored Nov 13, 2023
1 parent bb8ee0b commit 7515a10
Show file tree
Hide file tree
Showing 16 changed files with 278 additions and 115 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Upcoming Features
---

Add `protocol` to `ServiceTargetPayload` ([#9891](https://github.com/linode/manager/pull/9891))
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/api-v4": Upcoming Features
---

Change `ca_certificate` to `certificate_id` in AGLB `ServiceTargetPayload` ([#9891](https://github.com/linode/manager/pull/9891))
3 changes: 2 additions & 1 deletion packages/api-v4/src/aglb/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,9 @@ export interface RouteServiceTargetPayload {

export interface ServiceTargetPayload {
label: string;
protocol: Protocol;
endpoints: Endpoint[];
ca_certificate: string;
certificate_id: number | null;
load_balancing_policy: Policy;
healthcheck: HealthCheck;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

Add AGLB Service Target `protocol` field in Create/Edit Service Target drawer and "Protocol" column to Service Targets table ([#9891](https://github.com/linode/manager/pull/9891))
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,11 @@ describe('Akamai Global Load Balancer service targets', () => {
regions: [loadBalancerRegion.id],
});
const mockServiceTarget = serviceTargetFactory.build({
ca_certificate: 'my-certificate',
certificate_id: 0,
load_balancing_policy: 'random',
});
const mockCertificate = certificateFactory.build({
id: 0,
label: 'my-certificate',
});
const mockNewCertificate = certificateFactory.build({
Expand Down Expand Up @@ -351,7 +352,7 @@ describe('Akamai Global Load Balancer service targets', () => {

// Select the certificate mocked for this load balancer.
cy.findByLabelText('Certificate')
.should('have.value', mockServiceTarget.ca_certificate)
.should('have.value', mockCertificate.label)
.clear()
.type('my-new-certificate');

Expand Down
9 changes: 6 additions & 3 deletions packages/manager/src/factories/aglb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export const createLoadbalancerWithAllChildrenFactory = Factory.Sync.makeFactory
},
service_targets: [
{
ca_certificate: 'my-cms-certificate',
certificate_id: 0,
endpoints: [
{
ip: '192.168.0.100',
Expand All @@ -149,6 +149,7 @@ export const createLoadbalancerWithAllChildrenFactory = Factory.Sync.makeFactory
},
label: 'my-service-target',
load_balancing_policy: 'round_robin',
protocol: 'https',
},
],
},
Expand Down Expand Up @@ -258,7 +259,7 @@ export const createRouteFactory = Factory.Sync.makeFactory<CreateRoutePayload>({
// *************************

export const serviceTargetFactory = Factory.Sync.makeFactory<ServiceTarget>({
ca_certificate: 'certificate-0',
certificate_id: 0,
endpoints: [
{
ip: '192.168.0.100',
Expand All @@ -278,11 +279,12 @@ export const serviceTargetFactory = Factory.Sync.makeFactory<ServiceTarget>({
id: Factory.each((i) => i),
label: Factory.each((i) => `service-target-${i}`),
load_balancing_policy: 'round_robin',
protocol: 'https',
});

export const createServiceTargetFactory = Factory.Sync.makeFactory<ServiceTargetPayload>(
{
ca_certificate: 'my-cms-certificate',
certificate_id: 0,
endpoints: [
{
ip: '192.168.0.100',
Expand All @@ -301,6 +303,7 @@ export const createServiceTargetFactory = Factory.Sync.makeFactory<ServiceTarget
},
label: 'my-service-target',
load_balancing_policy: 'least_request',
protocol: 'https',
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ interface Props {
* Error text to display as helper text under the TextField. Useful for validation errors.
*/
errorText?: string;
/**
* An optional API filter to apply to the API request
*/
filter?: Filter;
/**
* The TextField label
* @default Certificate
Expand All @@ -24,18 +28,17 @@ interface Props {
*/
onChange: (certificate: Certificate | null) => void;
/**
* The id of the selected certificate OR a function that should return `true`
* if the certificate should be selected.
* The id of the selected certificate
*/
value: ((certificate: Certificate) => boolean) | number;
value: null | number;
}

export const CertificateSelect = (props: Props) => {
const { errorText, label, loadbalancerId, onChange, value } = props;

const [inputValue, setInputValue] = React.useState<string>('');

const filter: Filter = {};
const filter: Filter = props.filter ?? {};

if (inputValue) {
filter['label'] = { '+contains': inputValue };
Expand All @@ -52,9 +55,7 @@ export const CertificateSelect = (props: Props) => {
const certificates = data?.pages.flatMap((page) => page.data);

const selectedCertificate =
typeof value === 'function'
? certificates?.find(value) ?? null
: certificates?.find((cert) => cert.id === value) ?? null;
certificates?.find((cert) => cert.id === value) ?? null;

const onScroll = (event: React.SyntheticEvent) => {
const listboxNode = event.currentTarget;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,11 @@ import { Hidden, IconButton } from '@mui/material';
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';

import { ActionMenu } from 'src/components/ActionMenu';
import { Button } from 'src/components/Button/Button';
import { CircleProgress } from 'src/components/CircleProgress';
import { InputAdornment } from 'src/components/InputAdornment';
import { PaginationFooter } from 'src/components/PaginationFooter/PaginationFooter';
import { Stack } from 'src/components/Stack';
import { StatusIcon } from 'src/components/StatusIcon/StatusIcon';
import { Table } from 'src/components/Table';
import { TableBody } from 'src/components/TableBody';
import { TableCell } from 'src/components/TableCell';
Expand All @@ -20,13 +18,13 @@ import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty';
import { TableRowError } from 'src/components/TableRowError/TableRowError';
import { TableSortCell } from 'src/components/TableSortCell';
import { TextField } from 'src/components/TextField';
import { Typography } from 'src/components/Typography';
import { useOrder } from 'src/hooks/useOrder';
import { usePagination } from 'src/hooks/usePagination';
import { useLoadBalancerServiceTargetsQuery } from 'src/queries/aglb/serviceTargets';

import { DeleteServiceTargetDialog } from './ServiceTargets/DeleteServiceTargetDialog';
import { ServiceTargetDrawer } from './ServiceTargets/ServiceTargetDrawer';
import { ServiceTargetRow } from './ServiceTargets/ServiceTargetRow';

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

Expand Down Expand Up @@ -141,8 +139,23 @@ export const LoadBalancerServiceTargets = () => {
Label
</TableSortCell>
<TableCell>Endpoints</TableCell>
<TableSortCell
active={orderBy === 'protocol'}
direction={order}
handleClick={handleOrderChange}
label="protocol"
>
Protocol
</TableSortCell>
<Hidden smDown>
<TableCell>Algorithm</TableCell>
<TableSortCell
active={orderBy === 'algorithm'}
direction={order}
handleClick={handleOrderChange}
label="algorithm"
>
Algorithm
</TableSortCell>
</Hidden>
<Hidden mdDown>
<TableCell>Certificate</TableCell>
Expand All @@ -164,52 +177,16 @@ export const LoadBalancerServiceTargets = () => {
</TableRow>
</TableHead>
<TableBody>
{error && <TableRowError colSpan={6} message={error?.[0]?.reason} />}
{data?.results === 0 && <TableRowEmpty colSpan={6} />}
{error && <TableRowError colSpan={8} message={error?.[0]?.reason} />}
{data?.results === 0 && <TableRowEmpty colSpan={8} />}
{data?.data.map((serviceTarget) => (
<TableRow key={serviceTarget.label}>
<TableCell>{serviceTarget.label}</TableCell>
<TableCell>
<Stack alignItems="center" direction="row" spacing={1}>
<StatusIcon status="active" />
<Typography noWrap>4 up</Typography>
<Typography>&mdash;</Typography>
<StatusIcon status="error" />
<Typography noWrap>6 down</Typography>
</Stack>
</TableCell>
<Hidden smDown>
<TableCell sx={{ textTransform: 'capitalize' }}>
{serviceTarget.load_balancing_policy.replace('_', ' ')}
</TableCell>
</Hidden>
<Hidden mdDown>
<TableCell>{serviceTarget.ca_certificate}</TableCell>
</Hidden>
<Hidden lgDown>
<TableCell>
{serviceTarget.healthcheck.interval !== 0 ? 'Yes' : 'No'}
</TableCell>
</Hidden>
<Hidden smDown>
<TableCell>{serviceTarget.id}</TableCell>
</Hidden>
<TableCell actionCell>
<ActionMenu
actionsList={[
{
onClick: () => handleEditServiceTarget(serviceTarget),
title: 'Edit',
},
{
onClick: () => handleDeleteServiceTarget(serviceTarget),
title: 'Delete',
},
]}
ariaLabel={`Action Menu for service target ${serviceTarget.label}`}
/>
</TableCell>
</TableRow>
<ServiceTargetRow
key={serviceTarget.id}
loadbalancerId={Number(loadbalancerId)}
onDelete={() => handleDeleteServiceTarget(serviceTarget)}
onEdit={() => handleEditServiceTarget(serviceTarget)}
serviceTarget={serviceTarget}
/>
))}
</TableBody>
</Table>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import { scrollErrorIntoView } from 'src/utilities/scrollErrorIntoView';
import { CertificateSelect } from '../Certificates/CertificateSelect';
import { AddEndpointForm } from './AddEndpointForm';
import { EndpointTable } from './EndpointTable';
import { algorithmOptions, initialValues, protocolOptions } from './utils';

interface Props {
loadbalancerId: number;
Expand All @@ -39,52 +40,6 @@ interface Props {
serviceTarget?: ServiceTarget;
}

const algorithmOptions = [
{
description: 'Sequential routing to each instance.',
label: 'Round Robin',
value: 'round_robin',
},
{
description:
'Sends requests to the target with the least amount of current connections.',
label: 'Least Request',
value: 'least_request',
},
{
description: 'Reads the request hash value and routes accordingly.',
label: 'Ring Hash',
value: 'ring_hash',
},
{
description: 'Requests are distributed randomly.',
label: 'Random',
value: 'random',
},
{
description:
'Reads the upstream hash to make content aware routing decisions.',
label: 'Maglev',
value: 'maglev',
},
];

const initialValues: ServiceTargetPayload = {
ca_certificate: '',
endpoints: [],
healthcheck: {
healthy_threshold: 3,
host: '',
interval: 10,
path: '',
protocol: 'http',
timeout: 5,
unhealthy_threshold: 3,
},
label: '',
load_balancing_policy: 'round_robin',
};

export const ServiceTargetDrawer = (props: Props) => {
const { loadbalancerId, onClose: _onClose, open, serviceTarget } = props;

Expand Down Expand Up @@ -197,6 +152,16 @@ export const ServiceTargetDrawer = (props: Props) => {
onChange={formik.handleChange}
value={formik.values.label}
/>
<Autocomplete
value={protocolOptions.find(
(option) => option.value === formik.values.protocol
)}
disableClearable
errorText={formik.errors.protocol}
label="Service Target Protocol"
onChange={(_, { value }) => formik.setFieldValue('protocol', value)}
options={protocolOptions}
/>
<Autocomplete
onChange={(e, selected) =>
formik.setFieldValue('load_balancing_policy', selected.value)
Expand Down Expand Up @@ -238,11 +203,12 @@ export const ServiceTargetDrawer = (props: Props) => {
</Stack>
<CertificateSelect
onChange={(cert) =>
formik.setFieldValue('ca_certificate', cert?.label ?? null)
formik.setFieldValue('certificate_id', cert?.id ?? null)
}
errorText={formik.errors.ca_certificate}
errorText={formik.errors.certificate_id}
filter={{ type: 'ca' }}
loadbalancerId={loadbalancerId}
value={(cert) => cert.label === formik.values.ca_certificate}
value={formik.values.certificate_id}
/>
<Divider spacingBottom={12} spacingTop={24} />
<Stack alignItems="center" direction="row">
Expand Down
Loading

0 comments on commit 7515a10

Please sign in to comment.