diff --git a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/create-db-cluster.e2e.ts b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/create-db-cluster.e2e.ts index 1556fb09d..e7e4800ab 100644 --- a/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/create-db-cluster.e2e.ts +++ b/ui/apps/everest/.e2e/pr/db-cluster/db-wizard/create-db-cluster/create-db-cluster.e2e.ts @@ -139,7 +139,16 @@ test.describe('DB Cluster creation', () => { await resourcesStepCheck(page); - // Same number of proxies as nodes, as user hasn't changed it + // Sharding off, no routers available + await expect(page.getByText('Routers (3)')).not.toBeVisible(); + + await moveBack(page); + await page + .getByTestId('switch-input-sharding-label') + .getByRole('checkbox') + .check(); + await moveForward(page); + await expect(page.getByText('Routers (3)')).toBeVisible(); await page.getByTestId('proxies-accordion').getByRole('button').click(); await page.getByTestId('toggle-button-routers-1').click(); @@ -180,6 +189,10 @@ test.describe('DB Cluster creation', () => { await page.getByTestId('button-edit-preview-basic-information').click(); // Because 2 nodes is not valid for MongoDB, the default will be picked await page.getByTestId('mongodb-toggle-button').click(); + await page + .getByTestId('switch-input-sharding-label') + .getByRole('checkbox') + .check(); await expect(page.getByText('NÂș nodes: 3')).toBeVisible(); await page.getByTestId('button-edit-preview-backups').click(); diff --git a/ui/apps/everest/src/components/cluster-form/resources/constants.ts b/ui/apps/everest/src/components/cluster-form/resources/constants.ts index b91fec4e3..07e4e0912 100644 --- a/ui/apps/everest/src/components/cluster-form/resources/constants.ts +++ b/ui/apps/everest/src/components/cluster-form/resources/constants.ts @@ -162,6 +162,25 @@ export const getDefaultNumberOfconfigServersByNumberOfNodes = ( } else return '7'; }; +const numberOfResourcesValidator = ( + numberOfResourcesStr: string, + customNrOfResoucesStr: string, + fieldPath: string, + ctx: z.RefinementCtx +) => { + if (numberOfResourcesStr === CUSTOM_NR_UNITS_INPUT_VALUE) { + const intNr = parseInt(customNrOfResoucesStr, 10); + + if (Number.isNaN(intNr) || intNr < 1) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Please enter a valid number', + path: [fieldPath], + }); + } + } +}; + export const resourcesFormSchema = (passthrough?: boolean) => { const objectShape = { [DbWizardFormFields.shardNr]: z.string().optional(), @@ -199,26 +218,21 @@ export const resourcesFormSchema = (passthrough?: boolean) => { }, ctx ) => { - [ - [numberOfNodes, customNrOfNodes, DbWizardFormFields.customNrOfNodes], - [ + numberOfResourcesValidator( + numberOfNodes, + customNrOfNodes, + DbWizardFormFields.customNrOfNodes, + ctx + ); + + if (dbType !== DbType.Mongo || (dbType === DbType.Mongo && !!sharding)) { + numberOfResourcesValidator( numberOfProxies, - customNrOfProxies, + customNrOfNodes, DbWizardFormFields.customNrOfProxies, - ], - ].forEach(([nr, customNr, path]) => { - if (nr === CUSTOM_NR_UNITS_INPUT_VALUE) { - const intNr = parseInt(customNr, 10); - - if (Number.isNaN(intNr) || intNr < 1) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: 'Please enter a valid number', - path: [path], - }); - } - } - }); + ctx + ); + } if ( numberOfNodes === CUSTOM_NR_UNITS_INPUT_VALUE && diff --git a/ui/apps/everest/src/components/cluster-form/resources/resources.tsx b/ui/apps/everest/src/components/cluster-form/resources/resources.tsx index 9f659ad38..39ed016a9 100644 --- a/ui/apps/everest/src/components/cluster-form/resources/resources.tsx +++ b/ui/apps/everest/src/components/cluster-form/resources/resources.tsx @@ -423,8 +423,10 @@ const ResourcesForm = ({ allowDiskInputUpdate, pairProxiesWithNodes, showSharding, + hideProxies = false, }: { dbType: DbType; + hideProxies?: boolean; disableDiskInput?: boolean; allowDiskInputUpdate?: boolean; pairProxiesWithNodes?: boolean; @@ -559,37 +561,41 @@ const ResourcesForm = ({ disableCustom={dbType === DbType.Mysql} /> - - - - - {proxyFieldError && ( - - {proxyFieldError?.message} - - )} - + {!hideProxies && ( + + + + + {proxyFieldError && ( + + {proxyFieldError?.message} + + )} + + )} {!!showSharding && !!sharding && ( diff --git a/ui/apps/everest/src/hooks/api/db-cluster/useCreateDbCluster.ts b/ui/apps/everest/src/hooks/api/db-cluster/useCreateDbCluster.ts index 3942d3e5e..b43f4ab11 100644 --- a/ui/apps/everest/src/hooks/api/db-cluster/useCreateDbCluster.ts +++ b/ui/apps/everest/src/hooks/api/db-cluster/useCreateDbCluster.ts @@ -111,6 +111,7 @@ const formValuesToPayloadMapping = ( dbPayload.externalAccess, dbPayload.proxyCpu, dbPayload.proxyMemory, + dbPayload.sharding, dbPayload.sourceRanges || [] ), ...(dbPayload.dbType === DbType.Mongo && { diff --git a/ui/apps/everest/src/hooks/api/db-cluster/useUpdateDbCluster.ts b/ui/apps/everest/src/hooks/api/db-cluster/useUpdateDbCluster.ts index 2fbd80151..0e6f25627 100644 --- a/ui/apps/everest/src/hooks/api/db-cluster/useUpdateDbCluster.ts +++ b/ui/apps/everest/src/hooks/api/db-cluster/useUpdateDbCluster.ts @@ -15,12 +15,13 @@ import { UseMutationOptions, useMutation } from '@tanstack/react-query'; import { updateDbClusterFn } from 'api/dbClusterApi'; -import { DbCluster } from 'shared-types/dbCluster.types'; +import { DbCluster, Proxy } from 'shared-types/dbCluster.types'; import { DbWizardType } from 'pages/database-form/database-form-schema.ts'; import cronConverter from 'utils/cron-converter'; import { CUSTOM_NR_UNITS_INPUT_VALUE } from 'components/cluster-form'; import { getProxySpec } from './utils'; import { DbType } from '@percona/types'; +import { DbEngineType } from 'shared-types/dbEngines.types'; type UpdateDbClusterArgType = { dbPayload: DbWizardType; @@ -95,31 +96,35 @@ const formValuesToPayloadOverrides = ( monitoringConfigName: dbPayload?.monitoringInstance!, }), }, - proxy: { - ...dbCluster.spec.proxy, - ...getProxySpec( - dbPayload.dbType, - dbPayload.numberOfProxies, - dbPayload.customNrOfProxies || '', - dbPayload.externalAccess, - dbPayload.proxyCpu, - dbPayload.proxyMemory, - dbPayload.sourceRanges || [] - ), - // replicas: numberOfNodes, - // expose: { - // ...dbCluster.spec.proxy.expose, - // type: dbPayload.externalAccess - // ? ProxyExposeType.external - // : ProxyExposeType.internal, - // ...(!!dbPayload.externalAccess && - // dbPayload.sourceRanges && { - // ipSourceRanges: dbPayload.sourceRanges.flatMap((source) => - // source.sourceRange ? [source.sourceRange] : [] - // ), - // }), - // }, - }, + proxy: + dbPayload.dbType === DbType.Mongo && !dbPayload.sharding + ? {} + : { + ...dbCluster.spec.proxy, + ...getProxySpec( + dbPayload.dbType, + dbPayload.numberOfProxies, + dbPayload.customNrOfProxies || '', + dbPayload.externalAccess, + dbPayload.proxyCpu, + dbPayload.proxyMemory, + dbPayload.sharding, + dbPayload.sourceRanges || [] + ), + // replicas: numberOfNodes, + // expose: { + // ...dbCluster.spec.proxy.expose, + // type: dbPayload.externalAccess + // ? ProxyExposeType.external + // : ProxyExposeType.internal, + // ...(!!dbPayload.externalAccess && + // dbPayload.sourceRanges && { + // ipSourceRanges: dbPayload.sourceRanges.flatMap((source) => + // source.sourceRange ? [source.sourceRange] : [] + // ), + // }), + // }, + }, ...(dbPayload.dbType === DbType.Mongo && { sharding: { enabled: dbPayload.sharding, @@ -235,6 +240,7 @@ export const useUpdateDbClusterResources = () => mutationFn: ({ dbCluster, newResources, + sharding, }: { dbCluster: DbCluster; newResources: { @@ -247,6 +253,7 @@ export const useUpdateDbClusterResources = () => proxyMemory: number; numberOfProxies: number; }; + sharding: boolean; }) => updateDbClusterFn(dbCluster.metadata.name, dbCluster.metadata.namespace, { ...dbCluster, @@ -264,14 +271,17 @@ export const useUpdateDbClusterResources = () => size: `${newResources.disk}${newResources.diskUnit}`, }, }, - proxy: { - ...dbCluster.spec.proxy, - replicas: newResources.numberOfProxies, - resources: { - cpu: `${newResources.proxyCpu}`, - memory: `${newResources.proxyMemory}G`, - }, - }, + proxy: + dbCluster.spec.engine.type === DbEngineType.PSMDB && !sharding + ? {} + : ({ + ...dbCluster.spec.proxy, + replicas: newResources.numberOfProxies, + resources: { + cpu: `${newResources.proxyCpu}`, + memory: `${newResources.proxyMemory}G`, + }, + } as Proxy), }, }), }); diff --git a/ui/apps/everest/src/hooks/api/db-cluster/utils.ts b/ui/apps/everest/src/hooks/api/db-cluster/utils.ts index 7e92ff65a..3b8a26647 100644 --- a/ui/apps/everest/src/hooks/api/db-cluster/utils.ts +++ b/ui/apps/everest/src/hooks/api/db-cluster/utils.ts @@ -10,14 +10,24 @@ export const getProxySpec = ( externalAccess: boolean, cpu: number, memory: number, + sharding: boolean, sourceRanges?: Array<{ sourceRange?: string }> -): Proxy => { +): Proxy | Record => { + console.log('dbType', dbType); + console.log('sharding', sharding); + if (dbType === DbType.Mongo && !sharding) { + console.log('returning empty object'); + return {}; + } const proxyNr = parseInt( numberOfProxies === CUSTOM_NR_UNITS_INPUT_VALUE ? customNrOfProxies : numberOfProxies, 10 ); + // const showResources = + // dbType !== DbType.Mongo || (dbType === DbType.Mongo && !sharding); + return { type: dbTypeToProxyType(dbType), replicas: proxyNr, diff --git a/ui/apps/everest/src/pages/database-form/database-form-body/steps/resources/resources-step.tsx b/ui/apps/everest/src/pages/database-form/database-form-body/steps/resources/resources-step.tsx index 3c70b50e1..b3f36d1ec 100644 --- a/ui/apps/everest/src/pages/database-form/database-form-body/steps/resources/resources-step.tsx +++ b/ui/apps/everest/src/pages/database-form/database-form-body/steps/resources/resources-step.tsx @@ -10,6 +10,7 @@ export const ResourcesStep = () => { const { watch } = useFormContext(); const mode = useDatabasePageMode(); const dbType: DbType = watch(DbWizardFormFields.dbType); + const shardingEnabled = watch(DbWizardFormFields.sharding); return ( <> @@ -23,6 +24,7 @@ export const ResourcesStep = () => { disableDiskInput={mode === 'edit'} allowDiskInputUpdate={mode !== 'edit'} showSharding={dbType === DbType.Mongo} + hideProxies={dbType === DbType.Mongo && !shardingEnabled} /> ); diff --git a/ui/apps/everest/src/pages/database-form/database-form.utils.ts b/ui/apps/everest/src/pages/database-form/database-form.utils.ts index b570662af..0138cdc6d 100644 --- a/ui/apps/everest/src/pages/database-form/database-form.utils.ts +++ b/ui/apps/everest/src/pages/database-form/database-form.utils.ts @@ -49,7 +49,7 @@ export const DbClusterPayloadToFormValues = ( ): DbWizardType => { const backup = dbCluster?.spec?.backup; const replicas = dbCluster?.spec?.engine?.replicas.toString(); - const proxies = dbCluster?.spec?.proxy?.replicas.toString(); + const proxies = (dbCluster?.spec?.proxy?.replicas || 0).toString(); const diskValues = memoryParser( dbCluster?.spec?.engine?.storage?.size.toString() ); diff --git a/ui/apps/everest/src/pages/databases/DbClusterView.utils.ts b/ui/apps/everest/src/pages/databases/DbClusterView.utils.ts index 291278551..dd25080c2 100644 --- a/ui/apps/everest/src/pages/databases/DbClusterView.utils.ts +++ b/ui/apps/everest/src/pages/databases/DbClusterView.utils.ts @@ -18,6 +18,7 @@ import { DbClusterForNamespaceResult } from '../../hooks/api/db-clusters/useDbCl import { Messages } from './dbClusterView.messages'; import { DbClusterTableElement } from './dbClusterView.types'; import { Backup, BackupStatus } from 'shared-types/backups.types'; +import { isProxy } from 'utils/db'; const DB_CLUSTER_STATUS_HUMANIFIED: Record = { [DbClusterStatus.ready]: Messages.statusProvider.up, @@ -55,7 +56,9 @@ export const convertDbClusterPayloadToTableFormat = ( storage: cluster.spec.engine.storage.size, nodes: cluster.spec.engine.replicas, hostName: cluster.status ? cluster.status.hostname : '', - exposetype: cluster.spec.proxy.expose.type, + exposetype: isProxy(cluster.spec.proxy) + ? cluster.spec.proxy.expose.type + : undefined, port: cluster.status?.port, monitoringConfigName: cluster.spec.monitoring?.monitoringConfigName ?? '', diff --git a/ui/apps/everest/src/pages/databases/dbClusterView.types.ts b/ui/apps/everest/src/pages/databases/dbClusterView.types.ts index b5009d1d7..42efe92e3 100644 --- a/ui/apps/everest/src/pages/databases/dbClusterView.types.ts +++ b/ui/apps/everest/src/pages/databases/dbClusterView.types.ts @@ -38,7 +38,7 @@ export interface DbClusterTableElement { nodes: number; hostName: string; port?: number; - exposetype: ProxyExposeType; + exposetype?: ProxyExposeType; monitoringConfigName?: string; raw: DbCluster; } diff --git a/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cards/resources-details.tsx b/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cards/resources-details.tsx index 54d0c2c86..f29109123 100644 --- a/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cards/resources-details.tsx +++ b/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cards/resources-details.tsx @@ -39,6 +39,7 @@ import { import { dbEngineToDbType } from '@percona/utils'; import { DB_CLUSTER_QUERY, useUpdateDbClusterResources } from 'hooks'; import { DbType } from '@percona/types'; +import { isProxy } from 'utils/db'; export const ResourcesDetails = ({ dbCluster, @@ -58,7 +59,9 @@ export const ResourcesDetails = ({ const parsedMemoryValues = memoryParser(memory.toString()); const dbType = dbEngineToDbType(dbCluster.spec.engine.type); const replicas = dbCluster.spec.engine.replicas.toString(); - const proxies = dbCluster.spec.proxy.replicas.toString(); + const proxies = isProxy(dbCluster.spec.proxy) + ? (dbCluster.spec.proxy.replicas || 0).toString() + : ''; const numberOfNodes = NODES_DB_TYPE_MAP[dbType].includes(replicas) ? replicas : CUSTOM_NR_UNITS_INPUT_VALUE; @@ -103,6 +106,7 @@ export const ResourcesDetails = ({ 10 ), }, + sharding: !!sharding?.enabled, }, { onSuccess: () => { @@ -194,6 +198,7 @@ export const ResourcesDetails = ({ {openEditModal && ( setOpenEditModal(false)} onSubmit={onSubmit} defaultValues={{ @@ -213,7 +218,9 @@ export const ResourcesDetails = ({ ), resourceSizePerProxy: matchFieldsValueToResourceSize( dbType, - dbCluster.spec.proxy.resources + isProxy(dbCluster.spec.proxy) + ? dbCluster.spec.proxy.resources + : undefined ), }} /> diff --git a/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cards/resources/resources-edit-modal.tsx b/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cards/resources/resources-edit-modal.tsx index cca4e68f4..c66f48baf 100644 --- a/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cards/resources/resources-edit-modal.tsx +++ b/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cards/resources/resources-edit-modal.tsx @@ -6,6 +6,7 @@ import { FormDialog } from 'components/form-dialog'; type Props = { handleCloseModal: () => void; dbType: DbType; + shardingEnabled: boolean; onSubmit: SubmitHandler>>; defaultValues: z.infer>; }; @@ -13,6 +14,7 @@ type Props = { const ResourcesEditModal = ({ handleCloseModal, dbType, + shardingEnabled, onSubmit, defaultValues, }: Props) => { @@ -33,6 +35,7 @@ const ResourcesEditModal = ({ showSharding={false} disableDiskInput allowDiskInputUpdate={false} + hideProxies={dbType === DbType.Mongo && !shardingEnabled} /> ); diff --git a/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cluster-overview.tsx b/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cluster-overview.tsx index 41411240b..050cef706 100644 --- a/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cluster-overview.tsx +++ b/ui/apps/everest/src/pages/db-cluster-details/cluster-overview/cluster-overview.tsx @@ -25,6 +25,7 @@ import { useDbClusterCredentials } from 'hooks/api/db-cluster/useCreateDbCluster import { useDbBackups } from 'hooks/api/backups/useBackups'; import { DbEngineType } from 'shared-types/dbEngines.types'; import { useRBACPermissions } from 'hooks/rbac'; +import { isProxy } from 'utils/db'; export const ClusterOverview = () => { const { dbClusterName, namespace = '' } = useParams(); @@ -90,6 +91,7 @@ export const ClusterOverview = () => { username={dbClusterDetails?.username!} password={dbClusterDetails?.password!} externalAccess={ + isProxy(dbCluster.spec.proxy) && dbCluster.spec.proxy.expose.type === ProxyExposeType.external } monitoring={dbCluster?.spec.monitoring.monitoringConfigName} diff --git a/ui/apps/everest/src/shared-types/dbCluster.types.ts b/ui/apps/everest/src/shared-types/dbCluster.types.ts index 37814f659..288712dab 100644 --- a/ui/apps/everest/src/shared-types/dbCluster.types.ts +++ b/ui/apps/everest/src/shared-types/dbCluster.types.ts @@ -71,7 +71,7 @@ interface Engine { } export interface Proxy { - replicas: number; + replicas?: number; expose: { type: ProxyExposeType; ipSourceRanges?: string[]; @@ -106,7 +106,7 @@ export interface Spec { allowUnsafeConfiguration?: boolean; backup?: Backup; engine: Engine; - proxy: Proxy; + proxy: Proxy | Record; paused?: boolean; dataSource?: DataSource; monitoring: Monitoring; diff --git a/ui/apps/everest/src/utils/db.tsx b/ui/apps/everest/src/utils/db.tsx index d0a29ccdf..2508bc6af 100644 --- a/ui/apps/everest/src/utils/db.tsx +++ b/ui/apps/everest/src/utils/db.tsx @@ -1,6 +1,7 @@ import { MongoIcon, MySqlIcon, PostgreSqlIcon } from '@percona/ui-lib'; import { DbType } from '@percona/types'; import { ProxyType } from 'shared-types/dbEngines.types'; +import { Proxy } from 'shared-types/dbCluster.types'; export const dbTypeToIcon = (dbType: DbType) => { switch (dbType) { @@ -39,3 +40,11 @@ export const dbTypeToProxyType = (dbType: DbType): ProxyType => { return 'pgbouncer'; } }; + +export const isProxy = ( + proxy: Proxy | Record +): proxy is Proxy => { + return ( + proxy && typeof proxy.expose === 'object' && typeof proxy.type === 'string' + ); +};