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:
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -46,5 +46,4 @@ export const defaultValues = {
|
||||
'/deployment/instance/minMemory': '512Mi',
|
||||
'/deployment/instance/cpu': 5,
|
||||
'/deployment/instance/memory': '4095Mi',
|
||||
'/deployment/allowEndpoint': [],
|
||||
};
|
||||
|
||||
@@ -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}`],
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export const getHashedIdentifier = (obj, key: string): string => {
|
||||
switch (key) {
|
||||
case 'allowEndpoint':
|
||||
return btoa(`${obj?.host}:${obj?.port}`);
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
@@ -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: '' };
|
||||
|
||||
@@ -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({
|
||||
|
||||
7
src/types/Deployment.d.ts
vendored
7
src/types/Deployment.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user