INFRA-4076 | Ashvin | Add availability zone field (#730)

* INFRA-4076 | Ashvin | Add availability zone field

This will let users choose the AWS AZ they want to deploy their services in.

Backend and kutegen already supported this feature, see this for details https://github.com/navi-infra/kutegen/pull/218

* INFRA-4076 | Ashvin | Show AdvancedDeploymentConfigurationCard in all environments

* INFRA-4076 | Ashvin | Add all AZ to new manifest
This commit is contained in:
Ashvin S
2024-12-17 10:52:21 +05:30
committed by GitHub
parent 3e0c23a670
commit 4e85760c85
11 changed files with 85 additions and 113 deletions

View File

@@ -1,8 +1,6 @@
FROM 193044292705.dkr.ecr.ap-south-1.amazonaws.com/common/node:18.15.0-alpine3.16 as build
WORKDIR /app
COPY . /app
RUN yarn upgrade
RUN yarn cache clean
RUN yarn install
RUN yarn build
@@ -31,4 +29,4 @@ ENTRYPOINT ["/entrypoint.sh"]
USER 4000
CMD ["nginx", "-g", "daemon off;"]
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -213,7 +213,7 @@ const Form = (props: any) => {
.forEach(breached => {
const path = breached.path
.split('/')
.filter(part => part.length > 0 && part !== '*')
.filter(part => part.length > 0)
.map(part => (isNaN(part) ? part : parseInt(part, 10)));
const pathKey = breached.limitPath;

View File

@@ -46,5 +46,4 @@ export const defaultValues = {
'/deployment/instance/minMemory': '512Mi',
'/deployment/instance/cpu': 5,
'/deployment/instance/memory': '4095Mi',
'/deployment/allowEndpoint': [],
};

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { TabPanelProps, TabType } from './Types';
import { FormikProps } from 'formik';
import { AutoCompleteOption } from '@src/types/Types';
import { CiliumServiceEntry, ZoneElement } from '@src/types/Deployment';
import { ZoneElement } from '@src/types/Deployment';
import * as _m from '../models/Manifest';
import {
clusterZoneMapping,
@@ -110,19 +110,3 @@ export const getClusters = (environment: string) => {
}
return [];
};
export const populateServiceEntriesOptions = (
preChangedServiceEntries: CiliumServiceEntry[] | undefined,
serviceEntries: CiliumServiceEntry[],
) => {
const serviceEntriesOptionsMap = preChangedServiceEntries?.reduce((acc, serviceEntry) => {
acc[`${serviceEntry.host}:${serviceEntry.port}`] = serviceEntry?.hashedIdentifier;
return acc;
}, {});
return serviceEntries?.map(serviceEntry => {
return {
...serviceEntry,
hashedIdentifier: serviceEntriesOptionsMap?.[`${serviceEntry.host}:${serviceEntry.port}`],
};
});
};

View File

@@ -1,17 +1,17 @@
import React, { FC } from 'react';
import * as React from 'react';
import { FC, useEffect, useState } from 'react';
import Grid from '@material-ui/core/Grid';
import * as _m from '../../models/Manifest';
import { FormikTextField } from '../../components/common/FormikTextField';
import { FormikAutocomplete } from '../../components/common/FormikAutocomplete';
import { populateServiceEntriesOptions, toMenuItems } from '../FormUtil';
import { useFormikContext, Field, setIn, useField } from 'formik';
import { makeStyles, TableCell, styled, FormControlLabel, Tooltip } from '@material-ui/core';
import { Field, useField, useFormikContext } from 'formik';
import { FormControlLabel, makeStyles, styled, TableCell, Tooltip } from '@material-ui/core';
import { FormikTable } from '../../components/common/FormikTable';
import HealthCheckCard from './HealthCheckCard';
import { cardStyles } from '../Styles';
import AutoscalingCard from './AutoscalingCard';
import CardLayout from '../../components/common/CardLayout';
import { useEffect, useState } from 'react';
import { httpClient } from '../../helper/api-client';
import { FormikCheckbox } from '../../components/common/FormikCheckbox';
import { Cluster } from '../../constants/Cluster';
@@ -20,10 +20,15 @@ import { Environment } from '../../constants/Environment';
import { InfoOutlined } from '@material-ui/icons';
import WarningDialog from '../../components/common/WarningDialog';
import { Manifest } from '@src/types/Manifest';
import { ciliumEnabledCluster } from '@src/coreform/deployment/constants';
import { CiliumServiceEntry } from '@src/types/Deployment';
import {
AP_SOUTH_1A,
AP_SOUTH_1B,
AP_SOUTH_1C,
ciliumEnabledCluster,
} from '@src/coreform/deployment/constants';
import { useSelector } from 'react-redux';
import { RootState } from '@src/store';
import FormikMultiSelect from '@components/common/FormikMultiSelect';
const useStyles = makeStyles({
...cardStyles,
@@ -54,6 +59,9 @@ const useStyles = makeStyles({
infoIcon: {
verticalAlign: 'middle',
},
multiSelectMarginTop: {
marginTop: 10,
},
});
const MarginedFormControlLabel = styled(FormControlLabel)({
@@ -232,17 +240,21 @@ function renderGpu(team: string) {
}
function renderSpotNodeCard(environment: string) {
if (spotNodeEnabledEnvironments.has(environment)) {
return <SpotNodeCard />;
} else {
return <></>;
}
return <AdvancedDeploymentConfigurationCard environment={environment} />;
}
const disablegRPCCheckbox = (values: any, i: number) => {
return values.deployment.exposedPorts[i].name === 'metrics';
};
const SpotNodeCard = () => {
interface AdvancedDeploymentConfigurationProps {
environment: string;
}
const AdvancedDeploymentConfigurationCard = (
props: AdvancedDeploymentConfigurationProps,
): React.JSX.Element => {
const classes = useStyles();
const { values, setFieldValue }: { values: any; setFieldValue: any } = useFormikContext();
const [isSpotNodeEnabled, setIsSpotNodeEnabled] = React.useState(
values.deployment.scheduleOnSpotNodes,
@@ -262,20 +274,34 @@ const SpotNodeCard = () => {
}, [isSpotNodeEnabled, gpu.value]);
return (
<CardLayout heading={'Spot Node Configuration'}>
<FormControlLabel
control={
<Tooltip title="Spot instances are short lived nodes but are 90% cheaper than on-demand nodes. Useful in saving cost.">
<FormikCheckbox
name={`deployment.scheduleOnSpotNodes`}
onClick={handleSpotNodeToggle}
checked={isSpotNodeEnabled}
/>
</Tooltip>
}
label={'Schedule workload on spot nodes'}
/>
</CardLayout>
<>
<CardLayout heading={'Advanced Deployment Configuration'}>
<FormikMultiSelect
fullWidth
name={`deployment.zoneAffinity`}
label={'Availability Zones'}
className={classes.multiSelectMarginTop}
>
{toMenuItems([AP_SOUTH_1A, AP_SOUTH_1B, AP_SOUTH_1C])}
</FormikMultiSelect>
{spotNodeEnabledEnvironments.has(props.environment) ? (
<FormControlLabel
control={
<Tooltip title="Spot instances are short lived nodes but are 90% cheaper than on-demand nodes. Useful in saving cost.">
<FormikCheckbox
name={`deployment.scheduleOnSpotNodes`}
onClick={handleSpotNodeToggle}
checked={isSpotNodeEnabled}
/>
</Tooltip>
}
label={'Schedule workload on spot nodes'}
/>
) : (
<></>
)}
</CardLayout>
</>
);
};

View File

@@ -98,3 +98,7 @@ export const ciliumEnabledCluster = new Set([
Cluster.LENDING_PROD,
Cluster.IAPL_PROD,
]);
export const AP_SOUTH_1A = 'ap-south-1a';
export const AP_SOUTH_1B = 'ap-south-1b';
export const AP_SOUTH_1C = 'ap-south-1c';

View File

@@ -1,6 +1,5 @@
import _ from 'lodash';
import { toast } from 'react-toastify';
import { getHashedIdentifier } from './changeRequestUtils';
interface ValueWithWeight {
type: string;
@@ -19,18 +18,6 @@ const isObject = input => {
);
};
export const getBreachPath = (
manifestPath: string,
key: string,
index: number,
hasHashedIdentifier: boolean,
): string => {
if (hasHashedIdentifier) {
return `${manifestPath}/${key}/*`;
}
return `${manifestPath}/${key}/${index}`;
};
export const parseValue = str => {
const len = str.length;
const unit = str.substring(len - 2, len);
@@ -169,16 +156,9 @@ const safeMapAccess = (preManifestObject, key) => {
return preManifestObject[key];
};
const safeArrayAccess = (array, identifier: number | string) => {
if (!Array.isArray(array)) {
return undefined;
}
if (typeof identifier === 'number') {
return array[identifier];
} else if (typeof identifier === 'string') {
return array?.find(obj => obj.hashedIdentifier == identifier);
}
return undefined;
const safeArrayAccess = (array, idx) => {
if (isUndefined(array)) return undefined;
return array[idx];
};
const getBreachedLimits = (
@@ -196,22 +176,17 @@ const getBreachedLimits = (
const currentManifestObj = manifestObject[key];
const currentPreManifestObj = safeMapAccess(preManifestObject, key);
if (isTarget(currentLimitObj)) {
if (currentLimitObj.hasOwnProperty('forEach') && currentManifestObj?.length) {
if (currentLimitObj.hasOwnProperty('forEach') && currentManifestObj?.length !== undefined) {
for (let i = 0; i < currentManifestObj.length; i++) {
const obj = currentManifestObj[i];
const hasHashedIdentifier = currentLimitObj['hasHashedIdentifier'] == true;
const isBreach =
hasHashedIdentifier ||
isChangeRequestRequired(
currentLimitObj['forEach'],
obj,
currentPreManifestObj,
manifestEnv,
);
const path = getBreachPath(manifestPath, key, i, hasHashedIdentifier);
const identifier = hasHashedIdentifier ? getHashedIdentifier(obj, key) : i;
console.log(`path:${path}, limitPath:${limitPath} key:${key} identifier:${identifier}`);
if (isBreach && !_.isEqual(obj, safeArrayAccess(currentPreManifestObj, identifier))) {
const isBreach = isChangeRequestRequired(
currentLimitObj['forEach'],
obj,
currentPreManifestObj,
manifestEnv,
);
const path = `${manifestPath}/${key}/${i}`;
if (isBreach && !_.isEqual(obj, safeArrayAccess(currentPreManifestObj, i))) {
const diffObj = {
op: getOp(currentPreManifestObj),
path: path,
@@ -219,7 +194,7 @@ const getBreachedLimits = (
value: obj,
};
if (replaceWithPreviousValue) {
manifestObject[key] = safeMapAccess(preManifestObject, key);
manifestObject[key] = safeArrayAccess(preManifestObject, key);
}
breaches = breaches.concat(diffObj);
}
@@ -233,6 +208,7 @@ const getBreachedLimits = (
);
const path = `${manifestPath}/${key}`;
if (isBreach && currentManifestObj !== currentPreManifestObj) {
console.log(`${path} is changed`);
const obj = {
op: getOp(currentPreManifestObj),
path: path,
@@ -240,11 +216,12 @@ const getBreachedLimits = (
value: currentManifestObj,
};
if (replaceWithPreviousValue) {
manifestObject[key] = safeMapAccess(preManifestObject, key);
manifestObject[key] = safeArrayAccess(preManifestObject, key);
}
breaches = breaches.concat(obj);
}
}
if (!isObject(currentLimitObj)) return breaches;
for (const nextKey in currentLimitObj) {
const nextManifestPath = `${manifestPath}/${key}`;
@@ -315,7 +292,6 @@ const getBreachedValues = (
),
);
}
console.log('Breached values:', breaches);
return breaches;
};

View File

@@ -1,8 +0,0 @@
export const getHashedIdentifier = (obj, key: string): string => {
switch (key) {
case 'allowEndpoint':
return btoa(`${obj?.host}:${obj?.port}`);
default:
return '';
}
};

View File

@@ -12,6 +12,7 @@ import { DEFAULT_ELASTIC_SEARCH_VERSION } from '../constants/ElasticSearchConsta
import { LATEST_EC_VERSION } from '@src/coreform/elasticcache/constant';
import { FormType } from '@components/manifest/ShowSelectedForm';
import { DefaultFlinkAlerts } from '@src/coreform/flink/DefaultAlerts';
import { AP_SOUTH_1A, AP_SOUTH_1B, AP_SOUTH_1C } from '@src/coreform/deployment/constants';
// lodash like path for resources in the manifest object
export const path = {
@@ -332,6 +333,7 @@ export const addDeployment = (manifest: any) => {
},
isDeployed: false,
isVpaEnabled: true,
zoneAffinity: [AP_SOUTH_1A, AP_SOUTH_1B, AP_SOUTH_1C],
perfUtility: {
mockServer: false,
postgresServer: false,
@@ -425,7 +427,7 @@ export const newOutboundConnectivity = () => {
};
export const newAllowEndpoint = () => {
return { host: '', port: '', hashedIdentifier: '' };
return { host: '', port: '' };
};
export const newHpaCronJob = () => {
return { name: '', schedule: '' };

View File

@@ -725,15 +725,12 @@ const perfValidationSchema = yup.object({
}),
});
const allowEndpointValidationSchema = yup
.array()
.of(
yup.object({
host: yup.string().required('is Required'),
port: yup.string().required('is Required'),
}),
)
.unique('Host and port combination should be unique', ({ host, port }) => `${host}:${port}`);
const allowEndpointValidationSchema = yup.array().of(
yup.object({
host: yup.string().required('is Required'),
port: yup.string().required('is Required'),
}),
);
const scyllaDbValidationSchema = yup.object({
name: yup
@@ -806,6 +803,7 @@ const deploymentValidationSchema = yup.object({
efs: efsValidationSchema.default(undefined),
fsx: fsxValidationSchema.default(undefined),
perfUtility: perfValidationSchema.default(undefined),
zoneAffinity: yup.array().of(yup.string().required('is Required')).min(1),
});
const docdbValidationScheme = yup.object({

View File

@@ -32,7 +32,6 @@ export interface Deployment {
instance: Instance;
scheduleOnSpotNodes: boolean;
isVpaEnabled: boolean;
allowEndpoint?: CiliumServiceEntry[];
}
export interface ZoneElement {
zone: string;
@@ -56,9 +55,3 @@ export type LbEndpointProps = {
getHostedZone: (index: number) => AutoCompleteOption;
setHostedZone: (index: number, value: string) => void;
};
export type CiliumServiceEntry = {
host: string;
port: string;
hashedIdentifier: string;
};